diff --git a/lib/routes/ehentai/ehapi.ts b/lib/routes/ehentai/ehapi.ts
index 7753a10bb12fb6..842a1b6c0854c0 100644
--- a/lib/routes/ehentai/ehapi.ts
+++ b/lib/routes/ehentai/ehapi.ts
@@ -5,7 +5,7 @@ import { load } from 'cheerio';
import path from 'node:path';
import { config } from '@/config';
-const headers = {};
+const headers: any = {};
const has_cookie = config.ehentai.ipb_member_id && config.ehentai.ipb_pass_hash && config.ehentai.sk;
const from_ex = has_cookie && config.ehentai.igneous;
if (has_cookie) {
@@ -40,28 +40,19 @@ function ehgot_thumb(cache, thumb_url) {
});
}
-async function parsePage(cache, data, get_bittorrent = false, embed_thumb = false) {
+async function parsePage(cache, data, get_bittorrent = false, embed_thumb = false, my_tags = false) {
const $ = load(data);
- // "m" for Minimal
- // "p" for Minimal+
- // "l" for Compact
- // "e" for Extended
- // "t" for Thumbnail
let layout = 't';
- // "itg gld" for Thumbnail
let galleries = $('div[class^="itg gld"]');
- // "itg gltm" for Minimal or Minimal+
if (galleries.length <= 0) {
galleries = $('table[class^="itg gltm"] tbody');
layout = 'm';
}
- // "itg gltc" for Compact
if (galleries.length <= 0) {
galleries = $('table[class^="itg gltc"] tbody');
layout = 'l';
}
- // "itg glte" for Extended
if (galleries.length <= 0) {
galleries = $('table[class^="itg glte"] tbody');
layout = 'e';
@@ -69,72 +60,155 @@ async function parsePage(cache, data, get_bittorrent = false, embed_thumb = fals
if (galleries.length <= 0) {
return [];
}
-
- async function parseElement(cache, element) {
- const el = $(element);
- const title = el.find('.glink').html();
- const rawDate = el.find('div[id^="posted_"]').text();
- const pubDate = rawDate ? timezone(rawDate, 0) : rawDate;
- let el_a;
- let el_img;
- // match layout
- if ('mpl'.includes(layout)) {
- // Minimal, Minimal+, Compact
- el_a = el.find('td[class^="gl3"] a');
- el_img = el.find('td[class^=gl2] div.glthumb div img');
- } else if (layout === 'e') {
- // Extended
- el_a = el.find('td[class^="gl1"] a');
- el_img = el_a.find('img');
- } else if (layout === 't') {
+ const layoutConfigs = {
+ t: {
// Thumbnail
- el_a = el.find('div[class^="gl3t"] a');
- el_img = el_a.find('img');
- }
- const link = el_a.attr('href');
+ link_selector: 'div[class^="gl3t"] a',
+ thumb_selector: 'div[class^="gl3t"] a img',
+ category_selector: 'div.gl5t .cs',
+ tags_selector: 'div.gl6t .gt',
+ has_author: false,
+ },
+ m: {
+ // Minimal, Minimal+
+ link_selector: 'td[class^="gl3"] a',
+ thumb_selector: 'td[class^=gl2] div.glthumb div img',
+ category_selector: 'td.gl1c.glcat .cn',
+ tags_selector: 'td.gl3c.glname div.gt',
+ has_author: false,
+ },
+ l: {
+ // Compact
+ link_selector: 'td[class^="gl3"] a',
+ thumb_selector: 'td[class^=gl2] div.glthumb div img',
+ category_selector: 'td.gl1c.glcat .cn',
+ tags_selector: 'td.gl3c.glname div.gt',
+ has_author: true,
+ },
+ e: {
+ // Extended
+ link_selector: 'td[class^="gl1"] a',
+ thumb_selector: 'td[class^="gl1"] a img',
+ category_selector: 'div.gl3e .cn',
+ tags_selector: 'table div[title]',
+ has_author: true,
+ },
+ };
+
+ // --- 辅助函数:处理缩略图 ---
+ async function processThumbnail(el_img, cache) {
let thumbnail = el_img.data('src') ?? el_img.attr('src');
- if (config.ehentai.img_proxy && thumbnail) {
+ if (!thumbnail) {
+ return '';
+ }
+
+ if (config.ehentai.img_proxy) {
const url = new URL(thumbnail);
thumbnail = config.ehentai.img_proxy + url.pathname + url.search;
}
- if (embed_thumb && thumbnail) {
- thumbnail = await ehgot_thumb(cache, thumbnail);
+
+ if (embed_thumb) {
+ return await ehgot_thumb(cache, thumbnail);
}
- const description = `
`;
- if (title && link) {
- const item = { title, description, pubDate, link };
- if (get_bittorrent) {
- const el_down = el.find('div.gldown');
- const bittorrent_page_url = el_down.find('a').attr('href');
- if (bittorrent_page_url) {
- const bittorrent_url = await getBittorrent(cache, bittorrent_page_url);
- if (bittorrent_url) {
- item.enclosure_url = bittorrent_url;
- item.enclosure_type = 'application/x-bittorrent';
- item.bittorrent_page_url = bittorrent_page_url;
- }
- }
+ return thumbnail;
+ }
+
+ // --- 辅助函数:构建描述 ---
+ function buildDescription(thumbnail, el, tags_selector) {
+ let description = thumbnail ? `
` : '';
+ if (my_tags && tags_selector) {
+ const highlighted_tags = el.find(`${tags_selector}[style]`);
+ if (highlighted_tags.length > 0) {
+ let highlighted_tags_html = '
';
+ highlighted_tags.each((_, tag) => {
+ highlighted_tags_html += `${$(tag).text()} `;
+ });
+ highlighted_tags_html += '
';
+ description += highlighted_tags_html;
+ }
+ }
+ return description;
+ }
+
+ // --- 辅助函数:获取BT种子信息 ---
+ async function getEnclosureInfo(el, cache) {
+ const el_down = el.find('div.gldown');
+ const bittorrent_page_url = el_down.find('a').attr('href');
+ if (bittorrent_page_url) {
+ const bittorrent_url = await getBittorrent(cache, bittorrent_page_url);
+ if (bittorrent_url) {
+ return {
+ enclosure_url: bittorrent_url,
+ enclosure_type: 'application/x-bittorrent',
+ bittorrent_page_url,
+ };
}
- if ('le'.includes(layout)) {
- // artist tags will only show in Compact or Extended layout
- // get artist names as author
- item.author = $(el)
- .find('div.gt[title^="artist:"]')
- .toArray()
- .map((tag) => $(tag).text())
- .join(' / ');
+ }
+ return null;
+ }
+
+ // --- 重构后的核心解析函数 ---
+ async function parseElement(cache, element) {
+ const el = $(element);
+ const config = layoutConfigs[layout];
+
+ // 1. 基本信息提取
+ const title = el.find('.glink').html();
+ const rawDate = el.find('div[id^="posted_"]').text();
+ const el_a = el.find(config.link_selector);
+ const link = el_a.attr('href');
+ if (!title || !rawDate || !link) {
+ return null; // 如果没有标题、日期或链接,则为无效条目
+ }
+
+ const pubDate = timezone(rawDate, 0);
+ const category = el.find(config.category_selector).text();
+
+ const tags = el
+ .find(config.tags_selector)
+ .toArray()
+ .map((tag) => $(tag).attr('title'));
+
+ // 2. 调用辅助函数处理复杂逻辑
+ const thumbnail = await processThumbnail(el.find(config.thumb_selector), cache);
+ const description = buildDescription(thumbnail, el, config.tags_selector);
+
+ // 3. 组装核心 item
+ const item: any = {
+ title,
+ description,
+ pubDate,
+ link,
+ category: [`category:${category.toLowerCase()}`, ...(tags || [])],
+ };
+
+ // 4. 按需附加额外信息
+ if (get_bittorrent) {
+ const enclosure = await getEnclosureInfo(el, cache);
+ if (enclosure) {
+ Object.assign(item, enclosure);
}
- return item;
}
+
+ if (config.has_author) {
+ item.author = el
+ .find('div.gt[title^="artist:"]')
+ .toArray()
+ .map((tag) => $(tag).text())
+ .join(' / ');
+ }
+
+ return item;
}
- const item_Promises = [];
+ // --- 后续逻辑保持不变 ---
+ const item_Promises: any[] = [];
galleries.children().each((index, element) => {
item_Promises.push(parseElement(cache, element));
});
const items_with_null = await Promise.all(item_Promises);
- const items = [];
+ const items: any[] = [];
for (const item of items_with_null) {
if (item) {
items.push(item);
@@ -184,28 +258,49 @@ function updateBittorrent_url(cache, items) {
return items;
}
-async function gatherItemsByPage(cache, url, get_bittorrent = false, embed_thumb = false) {
+async function gatherItemsByPage(cache, url, get_bittorrent = false, embed_thumb = false, my_tags = false) {
const response = await ehgot(url);
- const items = await parsePage(cache, response.data, get_bittorrent, embed_thumb);
+ const items = await parsePage(cache, response.data, get_bittorrent, embed_thumb, my_tags);
return updateBittorrent_url(cache, items);
}
-async function getFavoritesItems(cache, favcat, inline_set, page, get_bittorrent = false, embed_thumb = false) {
+async function getFavoritesItems(cache, favcat, inline_set, page, get_bittorrent = false, embed_thumb = false, my_tags = false) {
const response = await ehgot(`favorites.php?favcat=${favcat}&inline_set=${inline_set}`);
if (page) {
- return gatherItemsByPage(cache, `favorites.php?favcat=${favcat}&next=${page}`, get_bittorrent, embed_thumb);
+ return gatherItemsByPage(cache, `favorites.php?favcat=${favcat}&next=${page}`, get_bittorrent, embed_thumb, my_tags);
} else {
- const items = await parsePage(cache, response.data, get_bittorrent, embed_thumb);
+ const items = await parsePage(cache, response.data, get_bittorrent, embed_thumb, my_tags);
return updateBittorrent_url(cache, items);
}
}
-function getSearchItems(cache, params, page, get_bittorrent = false, embed_thumb = false) {
- return page ? gatherItemsByPage(cache, `?${params}&next=${page}`, get_bittorrent, embed_thumb) : gatherItemsByPage(cache, `?${params}`, get_bittorrent, embed_thumb);
+function getSearchItems(cache, params, page, get_bittorrent = false, embed_thumb = false, my_tags = false) {
+ return page ? gatherItemsByPage(cache, `?${params}&next=${page}`, get_bittorrent, embed_thumb, my_tags) : gatherItemsByPage(cache, `?${params}`, get_bittorrent, embed_thumb, my_tags);
+}
+
+function getTagItems(cache, tag, page, get_bittorrent = false, embed_thumb = false, my_tags = false) {
+ return page ? gatherItemsByPage(cache, `tag/${tag}?next=${page}`, get_bittorrent, embed_thumb, my_tags) : gatherItemsByPage(cache, `tag/${tag}`, get_bittorrent, embed_thumb, my_tags);
+}
+
+function getWatchedItems(cache, params, get_bittorrent = false, embed_thumb = false, my_tags = false) {
+ const url = `watched?${params || ''}`;
+ return gatherItemsByPage(cache, url, get_bittorrent, embed_thumb, my_tags);
+}
+
+function getPopularItems(cache, params, get_bittorrent = false, embed_thumb = false, my_tags = false) {
+ const url = `popular?${params || ''}`;
+ return gatherItemsByPage(cache, url, get_bittorrent, embed_thumb, my_tags);
}
-function getTagItems(cache, tag, page, get_bittorrent = false, embed_thumb = false) {
- return page ? gatherItemsByPage(cache, `tag/${tag}?next=${page}`, get_bittorrent, embed_thumb) : gatherItemsByPage(cache, `tag/${tag}`, get_bittorrent, embed_thumb);
+async function getToplistItems(cache, tl, page, get_bittorrent = false, embed_thumb = false, my_tags = false) {
+ let url = `toplist.php?tl=${tl}`;
+ if (page) {
+ url = `${url}&p=${page}`;
+ }
+ // toplist is e-hentai only
+ const response = await got({ method: 'get', url: `https://e-hentai.org/${url}`, headers });
+ const items = await parsePage(cache, response.data, get_bittorrent, embed_thumb, my_tags);
+ return updateBittorrent_url(cache, items);
}
-export default { getFavoritesItems, getSearchItems, getTagItems, has_cookie, from_ex };
+export default { getFavoritesItems, getSearchItems, getTagItems, getWatchedItems, getPopularItems, getToplistItems, has_cookie, from_ex };
diff --git a/lib/routes/ehentai/favorites.ts b/lib/routes/ehentai/favorites.ts
index 14abc9d46dd1ca..f133f467e571d0 100644
--- a/lib/routes/ehentai/favorites.ts
+++ b/lib/routes/ehentai/favorites.ts
@@ -6,7 +6,7 @@ import ConfigNotFoundError from '@/errors/types/config-not-found';
export const route: Route = {
path: '/favorites/:favcat?/:order?/:page?/:routeParams?',
categories: ['picture'],
- example: '/ehentai/favorites/0/posted/0/bittorrent=true&embed_thumb=false',
+ example: '/ehentai/favorites/0/posted/0/bittorrent=true&embed_thumb=false&my_tags=true',
parameters: {
favcat: 'Favorites folder number',
order: '`posted`(Sort by gallery release time) , `favorited`(Sort by time added to favorites)',
@@ -34,10 +34,11 @@ async function handler(ctx) {
const favcat = ctx.req.param('favcat') ? Number.parseInt(ctx.req.param('favcat')) : 0;
const page = ctx.req.param('page');
const routeParams = new URLSearchParams(ctx.req.param('routeParams'));
- const bittorrent = routeParams.get('bittorrent') || false;
- const embed_thumb = routeParams.get('embed_thumb') || false;
+ const bittorrent = routeParams.get('bittorrent') === 'true';
+ const embed_thumb = routeParams.get('embed_thumb') === 'true';
+ const my_tags = routeParams.get('my_tags') === 'true';
const inline_set = ctx.req.param('order') === 'posted' ? 'fs_p' : 'fs_f';
- const items = await EhAPI.getFavoritesItems(cache, favcat, inline_set, page, bittorrent, embed_thumb);
+ const items = await EhAPI.getFavoritesItems(cache, favcat, inline_set, page, bittorrent, embed_thumb, my_tags);
return EhAPI.from_ex
? {
diff --git a/lib/routes/ehentai/namespace.ts b/lib/routes/ehentai/namespace.ts
index 65b118d97b5756..0add71124a8123 100644
--- a/lib/routes/ehentai/namespace.ts
+++ b/lib/routes/ehentai/namespace.ts
@@ -7,6 +7,7 @@ export const namespace: Namespace = {
| Key | Meaning | Accepted keys | Default value |
| ------------ | ------------------------------------------------------------------------------- | -------------- | ------------- |
| bittorrent | Whether include a link to the latest torrent | 0/1/true/false | false |
-| embed\_thumb | Whether the cover image is embedded in the RSS feed rather than given as a link | 0/1/true/false | false |`,
+| embed\_thumb | Whether the cover image is embedded in the RSS feed rather than given as a link | 0/1/true/false | false |
+| my\_tags | Whether to include highlighted tags from My Tags in the description | 0/1/true/false | false |`,
lang: 'en',
};
diff --git a/lib/routes/ehentai/popular.ts b/lib/routes/ehentai/popular.ts
new file mode 100644
index 00000000000000..667440f6f0f5cb
--- /dev/null
+++ b/lib/routes/ehentai/popular.ts
@@ -0,0 +1,53 @@
+import { Route } from '@/types';
+import cache from '@/utils/cache';
+import EhAPI from './ehapi';
+
+export const route: Route = {
+ path: '/popular/:params?/:routeParams?',
+ categories: ['picture'],
+ example: '/ehentai/popular/f_sft=on&f_sfu=on&f_sfl=on/bittorrent=true&embed_thumb=false&my_tags=true',
+ parameters: {
+ params: 'Filter parameters. You can copy the content after `https://e-hentai.org/popular?`',
+ routeParams: 'Additional parameters, see the table above. E.g. `bittorrent=true&embed_thumb=false`',
+ },
+ features: {
+ requireConfig: false,
+ requirePuppeteer: false,
+ antiCrawler: true,
+ supportBT: true,
+ supportPodcast: false,
+ supportScihub: false,
+ nsfw: true,
+ },
+ name: 'Popular',
+ maintainers: ['yindaheng98', 'syrinka', 'rosystain'],
+ handler,
+};
+
+async function handler(ctx) {
+ let params = ctx.req.param('params') ?? '';
+ let routeParams = ctx.req.param('routeParams');
+
+ if (params && !routeParams && (params.includes('bittorrent=') || params.includes('embed_thumb=') || params.includes('my_tags='))) {
+ routeParams = params;
+ params = '';
+ }
+
+ const routeParamsParsed = new URLSearchParams(routeParams);
+ const bittorrent = routeParamsParsed.get('bittorrent') === 'true';
+ const embed_thumb = routeParamsParsed.get('embed_thumb') === 'true';
+ const my_tags = routeParamsParsed.get('my_tags') === 'true';
+ const items = await EhAPI.getPopularItems(cache, params, bittorrent, embed_thumb, my_tags);
+
+ return EhAPI.from_ex
+ ? {
+ title: `ExHentai Popular`,
+ link: `https://exhentai.org/popular${params || ''}`,
+ item: items,
+ }
+ : {
+ title: `E-Hentai Popular`,
+ link: `https://e-hentai.org/popular${params || ''}`,
+ item: items,
+ };
+}
diff --git a/lib/routes/ehentai/search.ts b/lib/routes/ehentai/search.ts
index f52936925b5910..e114134b2040da 100644
--- a/lib/routes/ehentai/search.ts
+++ b/lib/routes/ehentai/search.ts
@@ -5,7 +5,7 @@ import EhAPI from './ehapi';
export const route: Route = {
path: '/search/:params?/:page?/:routeParams?',
categories: ['picture'],
- example: '/ehentai/search/f_cats=1021/0/bittorrent=true&embed_thumb=false',
+ example: '/ehentai/search/f_cats=1021/0/bittorrent=true&embed_thumb=false&my_tags=true',
parameters: { params: 'Search parameters. You can copy the content after `https://e-hentai.org/?`', page: 'Page number, set 0 to get latest', routeParams: 'Additional parameters, see the table above' },
features: {
requireConfig: false,
@@ -25,15 +25,16 @@ async function handler(ctx) {
const page = ctx.req.param('page');
let params = ctx.req.param('params');
const routeParams = new URLSearchParams(ctx.req.param('routeParams'));
- const bittorrent = routeParams.get('bittorrent') || false;
- const embed_thumb = routeParams.get('embed_thumb') || false;
+ const bittorrent = routeParams.get('bittorrent') === 'true';
+ const embed_thumb = routeParams.get('embed_thumb') === 'true';
+ const my_tags = routeParams.get('my_tags') === 'true';
let items;
if (page) {
// 如果定义了page,就要覆盖params
params = params.replace(/&*next=[^&]$/, '').replace(/next=[^&]&/, '');
- items = await EhAPI.getSearchItems(cache, params, page, bittorrent, embed_thumb);
+ items = await EhAPI.getSearchItems(cache, params, page, bittorrent, embed_thumb, my_tags);
} else {
- items = await EhAPI.getSearchItems(cache, params, undefined, bittorrent, embed_thumb);
+ items = await EhAPI.getSearchItems(cache, params, undefined, bittorrent, embed_thumb, my_tags);
}
let title = params;
const match = /f_search=([^&]+)/.exec(title);
diff --git a/lib/routes/ehentai/tag.ts b/lib/routes/ehentai/tag.ts
index b5d37229d81534..7d2575d1af9534 100644
--- a/lib/routes/ehentai/tag.ts
+++ b/lib/routes/ehentai/tag.ts
@@ -5,7 +5,7 @@ import EhAPI from './ehapi';
export const route: Route = {
path: '/tag/:tag/:page?/:routeParams?',
categories: ['picture'],
- example: '/ehentai/tag/language:chinese/0/bittorrent=true&embed_thumb=false',
+ example: '/ehentai/tag/language:chinese/0/bittorrent=true&embed_thumb=false&my_tags=true',
parameters: { tag: 'Tag', page: 'Page number, set 0 to get latest', routeParams: 'Additional parameters, see the table above' },
features: {
requireConfig: false,
@@ -25,9 +25,10 @@ async function handler(ctx) {
const page = ctx.req.param('page');
const tag = ctx.req.param('tag');
const routeParams = new URLSearchParams(ctx.req.param('routeParams'));
- const bittorrent = routeParams.get('bittorrent') || false;
- const embed_thumb = routeParams.get('embed_thumb') || false;
- const items = await EhAPI.getTagItems(cache, tag, page, bittorrent, embed_thumb);
+ const bittorrent = routeParams.get('bittorrent') === 'true';
+ const embed_thumb = routeParams.get('embed_thumb') === 'true';
+ const my_tags = routeParams.get('my_tags') === 'true';
+ const items = await EhAPI.getTagItems(cache, tag, page, bittorrent, embed_thumb, my_tags);
return EhAPI.from_ex
? {
diff --git a/lib/routes/ehentai/toplist.ts b/lib/routes/ehentai/toplist.ts
new file mode 100644
index 00000000000000..88ddf28ec0a1f7
--- /dev/null
+++ b/lib/routes/ehentai/toplist.ts
@@ -0,0 +1,83 @@
+import { Route } from '@/types';
+import cache from '@/utils/cache';
+import EhAPI from './ehapi';
+
+const categoryMap = {
+ yesterday: 15,
+ pastmonth: 13,
+ pastyear: 12,
+ alltime: 11,
+};
+
+export const route: Route = {
+ path: '/toplist/:category?/:page?/:routeParams?',
+ categories: ['picture'],
+ example: '/ehentai/toplist/yesterday/0/bittorrent=true&embed_thumb=false&my_tags=true',
+ parameters: {
+ category: `Category, see table below. Defaults to 'yesterday'`,
+ page: 'Page number',
+ routeParams: 'Additional parameters, see the table above. E.g. `bittorrent=true&embed_thumb=false`',
+ },
+ features: {
+ requireConfig: false,
+ requirePuppeteer: false,
+ antiCrawler: true,
+ supportBT: true,
+ supportPodcast: false,
+ supportScihub: false,
+ nsfw: true,
+ },
+ name: 'Toplist',
+ maintainers: ['yindaheng98', 'syrinka', 'rosystain'],
+ handler,
+ description: `
+| Yesterday | Past Month | Past Year | All Time |
+| :-------: | :--------: | :-------: | :-------: |
+| yesterday | pastmonth | pastyear | alltime |
+`,
+};
+
+async function handler(ctx) {
+ let category = ctx.req.param('category');
+ let page = ctx.req.param('page');
+ let routeParams = ctx.req.param('routeParams');
+
+ // Case 3: /toplist/0/bittorrent=true -> category='0', page='bittorrent=true', routeParams=undefined
+ if (page && !routeParams && (page.includes('bittorrent=') || page.includes('embed_thumb=') || page.includes('my_tags='))) {
+ routeParams = page;
+ page = category;
+ category = 'yesterday';
+ }
+
+ // Case 1: /toplist/0 -> category='0', page=undefined
+ // Case 2: /toplist/bittorrent=true -> category='bittorrent=true', page=undefined
+ if (category && !page && !routeParams) {
+ if (/^\d+$/.test(category)) {
+ // Case 1
+ page = category;
+ category = 'yesterday';
+ } else if (category.includes('bittorrent=') || category.includes('embed_thumb=') || category.includes('my_tags=')) {
+ // Case 2
+ routeParams = category;
+ category = 'yesterday';
+ }
+ }
+
+ category = category ?? 'yesterday';
+
+ const tl = categoryMap[category] || 15;
+ const routeParamsParsed = new URLSearchParams(routeParams);
+ const bittorrent = routeParamsParsed.get('bittorrent') === 'true';
+ const embed_thumb = routeParamsParsed.get('embed_thumb') === 'true';
+ const my_tags = routeParamsParsed.get('my_tags') === 'true';
+
+ const items = await EhAPI.getToplistItems(cache, tl, page, bittorrent, embed_thumb, my_tags);
+
+ const title = Object.keys(categoryMap).find((key) => categoryMap[key] === tl) || 'yesterday';
+
+ return {
+ title: `E-Hentai Toplist - ${title}`,
+ link: `https://e-hentai.org/toplist.php?tl=${tl}`,
+ item: items,
+ };
+}
diff --git a/lib/routes/ehentai/watched.ts b/lib/routes/ehentai/watched.ts
new file mode 100644
index 00000000000000..bcfe3b936b44a3
--- /dev/null
+++ b/lib/routes/ehentai/watched.ts
@@ -0,0 +1,64 @@
+import { Route } from '@/types';
+import cache from '@/utils/cache';
+import EhAPI from './ehapi';
+import ConfigNotFoundError from '@/errors/types/config-not-found';
+import { URLSearchParams } from 'node:url';
+
+export const route: Route = {
+ path: '/watched/:params?/:routeParams?',
+ categories: ['picture'],
+ example: '/ehentai/watched/f_cats=1021/bittorrent=true&embed_thumb=false&my_tags=true',
+ parameters: {
+ params: 'Search parameters. You can copy the content after `https://e-hentai.org/watched?`',
+ routeParams: 'Additional parameters, see the table above',
+ },
+ features: {
+ requireConfig: false,
+ requirePuppeteer: false,
+ antiCrawler: true,
+ supportBT: true,
+ supportPodcast: false,
+ supportScihub: false,
+ nsfw: true,
+ },
+ name: 'Watched',
+ maintainers: ['yindaheng98', 'syrinka', 'rosystain'],
+ handler,
+};
+
+async function handler(ctx) {
+ if (!EhAPI.has_cookie) {
+ throw new ConfigNotFoundError('Ehentai watched RSS is disabled due to the lack of cookie config');
+ }
+
+ let params = ctx.req.param('params');
+ let routeParams = ctx.req.param('routeParams');
+
+ if (params && !routeParams && (params.includes('bittorrent=') || params.includes('embed_thumb=') || params.includes('my_tags='))) {
+ routeParams = params;
+ params = '';
+ }
+
+ const routeParamsParsed = new URLSearchParams(routeParams);
+ const bittorrent = routeParamsParsed.get('bittorrent') === 'true';
+ const embed_thumb = routeParamsParsed.get('embed_thumb') === 'true';
+ const my_tags = routeParamsParsed.get('my_tags') === 'true';
+
+ const items = await EhAPI.getWatchedItems(cache, params, bittorrent, embed_thumb, my_tags);
+
+ let title = params;
+ const match = /f_search=([^&]+)/.exec(title);
+ title = match?.[1] ? decodeURIComponent(match[1]) : 'Watched';
+
+ return EhAPI.from_ex
+ ? {
+ title: `${title} - ExHentai Watched`,
+ link: `https://exhentai.org/watched?${params || ''}`,
+ item: items,
+ }
+ : {
+ title: `${title} - E-Hentai Watched`,
+ link: `https://e-hentai.org/watched?${params || ''}`,
+ item: items,
+ };
+}