diff --git a/README.md b/README.md index bce31fac..775e2589 100644 --- a/README.md +++ b/README.md @@ -128,12 +128,14 @@ Cotrans 也有自己的油猴脚本 —— 「[Cotrans 漫画/图片翻译器](h - 解锁隐藏漫画 - [ehentai](https://e-hentai.org) - [关联 nhentai](#关联-nhentai) - - [快捷键翻页](#快捷键翻页) - [快捷收藏](#快捷收藏) - - [识别广告](#识别广告) + - [标签染色](#标签染色) + - [识别广告页](#识别广告页) + - [快捷评分](#快捷评分) + - [快捷键翻页](#快捷键翻页) - [nhentai](https://nhentai.net) - [彻底屏蔽漫画](#彻底屏蔽漫画) - - [自动翻页](#自动翻页) + - [无限滚动](#无限滚动) - [Yurifans](https://www.yurifans.com) - 自动签到 - [拷贝漫画](https://www.copymanga.com) @@ -226,16 +228,20 @@ Cotrans 也有自己的油猴脚本 —— 「[Cotrans 漫画/图片翻译器](h > 标签颜色数据将在 `进入「My Tags」时` 和 `在「My Tags」中修改后` 更新 -### 快捷键翻页 - -在漫画列表页和详情页增加通过左右方向键翻页的功能。 - -### 识别广告 +### 识别广告页 简单识别下广告页并自动排除,只会在有`extraneous ads(外部广告)`标签时生效。如果你有用 Hath Perks 购买了 `More Thumbs(更多缩略图)` 的话,可以通过调大缩略图行数来略微加快一点识别速度。 如有误杀还请先反馈,然后可以先在右下角的悬浮按钮菜单里关闭该功能,等脚本更新修复后再开启。 +### 快捷评分 + +让列表页显示的评分不再只是摆设,而是和详情页一样可以直接点击修改。 + +### 快捷键翻页 + +在列表页和详情页使用左右方向键翻页。 + ## nhentai 除悬浮按钮外,也会在右侧边栏会增加一个「Load comic」按钮,功能和悬浮按钮一样。 @@ -244,9 +250,9 @@ Cotrans 也有自己的油猴脚本 —— 「[Cotrans 漫画/图片翻译器](h nhentai 的屏蔽机制是在被屏蔽漫画封面加上一层半透明遮罩,所以对于那些屏蔽范围比较大的人来说,在首页或搜索结果里连续翻上几页都是满屏的被屏蔽漫画完全是家常便饭。开启此功能后,被屏蔽漫画将被彻底屏蔽,不会再出现在首页或搜索结果里了。 -> 开启此功能后可能出现一整页的漫画都被屏蔽的情况,为此有了自动翻页功能 +> 开启此功能后可能出现一整页的漫画都被屏蔽的情况,为此有了无限滚动功能 -### 自动翻页 +### 无限滚动 当网页滚动至底部时将自动在底部加载下一页的内容,加载时底部会有加载条表示正在加载,当加载条停止时表示已到最后页。 diff --git a/docs/index.md b/docs/index.md index d7950915..d734f07a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -128,12 +128,14 @@ Cotrans 也有自己的油猴脚本 —— 「[Cotrans 漫画/图片翻译器](h - 解锁隐藏漫画 - [ehentai](https://e-hentai.org) - [关联 nhentai](#关联-nhentai) - - [快捷键翻页](#快捷键翻页) - [快捷收藏](#快捷收藏) - - [识别广告](#识别广告) + - [标签染色](#标签染色) + - [识别广告页](#识别广告页) + - [快捷评分](#快捷评分) + - [快捷键翻页](#快捷键翻页) - [nhentai](https://nhentai.net) - [彻底屏蔽漫画](#彻底屏蔽漫画) - - [自动翻页](#自动翻页) + - [无限滚动](#无限滚动) - [Yurifans](https://www.yurifans.com) - 自动签到 - [拷贝漫画](https://www.copymanga.com) @@ -226,16 +228,20 @@ Cotrans 也有自己的油猴脚本 —— 「[Cotrans 漫画/图片翻译器](h > 标签颜色数据将在 `进入「My Tags」时` 和 `在「My Tags」中修改后` 更新 -### 快捷键翻页 - -在漫画列表页和详情页增加通过左右方向键翻页的功能。 - -### 识别广告 +### 识别广告页 简单识别下广告页并自动排除,只会在有`extraneous ads(外部广告)`标签时生效。如果你有用 Hath Perks 购买了 `More Thumbs(更多缩略图)` 的话,可以通过调大缩略图行数来略微加快一点识别速度。 如有误杀还请先反馈,然后可以先在右下角的悬浮按钮菜单里关闭该功能,等脚本更新修复后再开启。 +### 快捷评分 + +让列表页显示的评分不再只是摆设,而是和详情页一样可以直接点击修改。 + +### 快捷键翻页 + +在列表页和详情页使用左右方向键翻页。 + ## nhentai 除悬浮按钮外,也会在右侧边栏会增加一个「Load comic」按钮,功能和悬浮按钮一样。 @@ -244,9 +250,9 @@ Cotrans 也有自己的油猴脚本 —— 「[Cotrans 漫画/图片翻译器](h nhentai 的屏蔽机制是在被屏蔽漫画封面加上一层半透明遮罩,所以对于那些屏蔽范围比较大的人来说,在首页或搜索结果里连续翻上几页都是满屏的被屏蔽漫画完全是家常便饭。开启此功能后,被屏蔽漫画将被彻底屏蔽,不会再出现在首页或搜索结果里了。 -> 开启此功能后可能出现一整页的漫画都被屏蔽的情况,为此有了自动翻页功能 +> 开启此功能后可能出现一整页的漫画都被屏蔽的情况,为此有了无限滚动功能 -### 自动翻页 +### 无限滚动 当网页滚动至底部时将自动在底部加载下一页的内容,加载时底部会有加载条表示正在加载,当加载条停止时表示已到最后页。 diff --git a/locales/en.json b/locales/en.json index eb5511ae..33ee28c2 100644 --- a/locales/en.json +++ b/locales/en.json @@ -168,7 +168,7 @@ "site": { "add_feature": { "associate_nhentai": "Associate nhentai", - "auto_page_turn": "Auto page turning", + "auto_page_turn": "Infinite scroll", "block_totally": "Totally block comics", "colorize_tag": "Colorize tags", "detect_ad": "Detect advertise page", @@ -176,12 +176,15 @@ "load_original_image": "Load original image", "open_link_new_page": "Open links in a new page", "quick_favorite": "Quick favorite", + "quick_rating": "Quick rating", "remember_current_site": "Remember the current site" }, "changed_load_failed": "The website has undergone changes, unable to load comics", "ehentai": { "change_favorite_failed": "Failed to change the favorite", - "change_favorite_success": "Favorite change success", + "change_favorite_success": "Successfully changed the favorite", + "change_rating_failed": "Failed to change the rating", + "change_rating_success": "Successfully changed the rating", "fetch_favorite_failed": "Failed to get favorite info", "fetch_img_page_source_failed": "Failed to get the source code of the image page", "fetch_img_page_url_failed": "Failed to get the image page address from the detail page", diff --git a/locales/ru.json b/locales/ru.json index b4380082..f27d33b1 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -176,12 +176,15 @@ "load_original_image": "Загружать оригинальное изображение", "open_link_new_page": "Открывать ссылки в новой вкладке", "quick_favorite": "Быстрый фаворит", + "quick_rating": "Быстрый рейтинг", "remember_current_site": "Запомнить текущий сайт" }, "changed_load_failed": "Страница изменилась, невозможно загрузить комикс", "ehentai": { "change_favorite_failed": "Не удалось изменить избранное", - "change_favorite_success": "Любимый успех изменений", + "change_favorite_success": "Избранное успешно изменено", + "change_rating_failed": "Не удалось изменить оценку", + "change_rating_success": "Успешно изменен рейтинг", "fetch_favorite_failed": "Не удалось получить информацию о избранном", "fetch_img_page_source_failed": "Не удалось получить исходный код страницы с изображениями", "fetch_img_page_url_failed": "Не удалось получить адрес страницы изображений из деталей", diff --git a/locales/zh.json b/locales/zh.json index 66107a3f..645afa03 100644 --- a/locales/zh.json +++ b/locales/zh.json @@ -168,7 +168,7 @@ "site": { "add_feature": { "associate_nhentai": "关联nhentai", - "auto_page_turn": "自动翻页", + "auto_page_turn": "无限滚动", "block_totally": "彻底屏蔽漫画", "colorize_tag": "标签染色", "detect_ad": "识别广告页", @@ -176,12 +176,15 @@ "load_original_image": "加载原图", "open_link_new_page": "在新页面中打开链接", "quick_favorite": "快捷收藏", + "quick_rating": "快捷评分", "remember_current_site": "记住当前站点" }, "changed_load_failed": "网站发生变化,无法加载漫画", "ehentai": { "change_favorite_failed": "收藏夹修改失败", "change_favorite_success": "收藏夹修改成功", + "change_rating_failed": "评分修改失败", + "change_rating_success": "评分修改成功", "fetch_favorite_failed": "获取收藏夹信息失败", "fetch_img_page_source_failed": "获取图片页源码失败", "fetch_img_page_url_failed": "从详情页获取图片页地址失败", diff --git a/metaHeader.ts b/metaHeader.ts index 9b2faca7..ba910d4d 100644 --- a/metaHeader.ts +++ b/metaHeader.ts @@ -75,6 +75,16 @@ export const updateReadme = () => { if (newOutMd !== outMd) fs.writeFileSync(outMdPath, newOutMd); }; +const enSupportSite = [ + 'E-Hentai (Associate nhentai, Quick favorite, Colorize tags, Detect advertise page, etc.)', + 'nhentai (Totally block comics, Auto page turning)', + 'hitomi', + 'Anchira', + 'kemono', + 'nekohouse', + 'welovemanga', +]; + /** 脚本头部注释 */ export const getMetaData = (isDevMode: boolean) => { const meta = { @@ -84,7 +94,7 @@ export const getMetaData = (isDevMode: boolean) => { description: `${zh.description}${getSupportSiteList() .map((site) => site.replace(/\[(.+)]\(.+\)/, '$1')) .join('、')}`, - 'description:en': en.description, + 'description:en': `${en.description} ${enSupportSite.join(' | ')}.`, 'description:ru': ru.description, author: pkg.author, license: pkg.license, diff --git a/src/components/Manga/components/ComicImg.tsx b/src/components/Manga/components/ComicImg.tsx index fe52f1eb..de6224f9 100644 --- a/src/components/Manga/components/ComicImg.tsx +++ b/src/components/Manga/components/ComicImg.tsx @@ -108,3 +108,10 @@ export const ComicImg: Component = (img) => ( fallback={SaveComicImg(img)} /> ); + +// 目前即使是不显示的图片也必须挂载上,否则解析好的图片会被浏览器垃圾回收掉, +// 导致在 ehentai 上无法正常加载图片。但这样会在图片过多时造成性能问题, +// 虽然也尝试了将解析好的 Image 对象存储起来挂上引用和另外放到一个避免渲染的 dom 下, +// 但也都失败了,只能暂时先不管了。 +// 之后尝试新方案时必须经过如下测试:开个几百页的漫画加载完毕后,再打开二十个标签页切换过去, +// 等待一分钟再切回来,等待一小时后再切回来 diff --git a/src/helper/request.ts b/src/helper/request.ts index 91c0e3db..5c45844d 100644 --- a/src/helper/request.ts +++ b/src/helper/request.ts @@ -40,7 +40,7 @@ export const request = async ( const headers = { Referer: window.location.href }; const errorText = `${ details?.errorText ?? t('alert.comic_load_error') - } - ${url}`; + }\nurl: ${url}`; try { // 虽然 GM_xmlhttpRequest 有 fetch 选项,但在 stay 上不太稳定 @@ -56,6 +56,10 @@ export const request = async ( body: details?.data, signal: AbortSignal.timeout?.(details?.timeout ?? 1000 * 10), }); + if (!details?.noCheckCode && res.status !== 200) { + log.error(errorText, res); + throw new Error(errorText); + } let response = null as T; switch (details?.responseType) { diff --git a/src/index.ts b/src/index.ts index 4b95859e..529ca643 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,7 +17,7 @@ const main = require('main') as typeof import('./main'); try { // 匹配站点 switch (window.location.hostname) { - // #百合会——「记录阅读历史、自动签到等」 + // #百合会(记录阅读历史、自动签到等) case 'bbs.yamibo.com': { inject('yamibo'); break; @@ -75,7 +75,7 @@ try { break; } - // #动漫之家——「解锁隐藏漫画」 + // #动漫之家(解锁隐藏漫画) case 'comic.idmzj.com': case 'comic.dmzj.com': case 'manhua.idmzj.com': @@ -96,26 +96,26 @@ try { break; } - // #E-Hentai——「匹配 nhentai 漫画」 + // #E-Hentai(关联 nhentai、快捷收藏、标签染色、识别广告页等) case 'exhentai.org': case 'e-hentai.org': { inject('ehentai'); break; } - // #nhentai——「彻底屏蔽漫画、自动翻页」 + // #nhentai(彻底屏蔽漫画、无限滚动) case 'nhentai.net': { inject('nhentai'); break; } - // #Yurifans——「自动签到」 + // #Yurifans(自动签到) case 'yuri.website': { inject('yurifans'); break; } - // #拷贝漫画(copymanga)——「显示最后阅读记录」 + // #拷贝漫画(copymanga)(显示最后阅读记录) case 'mangacopy.com': case 'copymanga.site': case 'copymanga.info': diff --git a/src/site/ehentai/index.tsx b/src/site/ehentai/index.tsx index 96be9def..0d79912a 100644 --- a/src/site/ehentai/index.tsx +++ b/src/site/ehentai/index.tsx @@ -17,23 +17,37 @@ import { getAdPageByFileName, getAdPageByContent, ReactiveSet, + requestIdleCallback, } from 'main'; import { quickFavorite } from './quickFavorite'; import { associateNhentai } from './associateNhentai'; import { hotkeysPageTurn } from './hotkeys'; import { colorizeTag } from './ColorizeTag'; - -export type PageType = 'gallery' | 'mytags' | 't' | 'e' | undefined; +import { quickRating } from './quickRating'; + +type ListPageType = + /** 最小化 */ + | 'm' + /** 最小化 + 关注标签 */ + | 'p' + /** 紧凑 + 标签 */ + | 'l' + /** 扩展 */ + | 'e' + /** 缩略图 */ + | 't'; + +export type PageType = 'gallery' | 'mytags' | ListPageType; (async () => { - let pageType: PageType; + let pageType: PageType | undefined; if (Reflect.has(unsafeWindow, 'display_comment_field')) pageType = 'gallery'; else if (location.pathname === '/mytags') pageType = 'mytags'; else pageType = querySelector('#ujumpbox ~ div > select') - ?.value as PageType; + ?.value as PageType | undefined; if (!pageType) return; @@ -50,12 +64,14 @@ export type PageType = 'gallery' | 'mytags' | 't' | 'e' | undefined; associate_nhentai: true, /** 快捷键翻页 */ hotkeys_page_turn: true, - /** 识别广告 */ + /** 识别广告页 */ detect_ad: true, /** 快捷收藏 */ quick_favorite: true, /** 标签染色 */ colorize_tag: false, + /** 快捷评分 */ + quick_rating: true, autoShow: false, }); @@ -85,6 +101,9 @@ export type PageType = 'gallery' | 'mytags' | 't' | 'e' | undefined; if (options.colorize_tag) colorizeTag(pageType); // 快捷键翻页 if (options.hotkeys_page_turn) hotkeysPageTurn(pageType); + // 快捷评分 + if (options.quick_rating) + requestIdleCallback(() => quickRating(pageType), 1000); // 快捷收藏。必须处于登录状态 if (unsafeWindow.apiuid !== -1 && options.quick_favorite) quickFavorite(pageType); diff --git a/src/site/ehentai/quickRating.tsx b/src/site/ehentai/quickRating.tsx new file mode 100644 index 00000000..13653214 --- /dev/null +++ b/src/site/ehentai/quickRating.tsx @@ -0,0 +1,152 @@ +import { For } from 'solid-js'; +import { render } from 'solid-js/web'; +import { request, querySelectorAll, toast, t } from 'main'; + +import { type PageType } from '.'; + +/** 快捷评分 */ +export const quickRating = (pageType: PageType) => { + let list: HTMLElement[]; + + switch (pageType) { + case 'gallery': + case 'mytags': + return; + + case 'e': + list = querySelectorAll('#favform > table > tbody > tr'); + break; + case 'm': + case 'p': + case 'l': + list = querySelectorAll('#favform > table > tbody > tr').slice(1); + break; + case 't': + list = querySelectorAll('.gl1t'); + break; + } + + GM_addStyle(` + .comidread-quick-rating { + position: absolute; + width: 100%; + height: 100%; + pointer-events: click; + } + `); + + const coordsList = [ + '0,0,7,16', + '8,0,15,16', + '16,0,23,16', + '24,0,31,16', + '32,0,39,16', + '40,0,47,16', + '48,0,55,16', + '56,0,63,16', + '64,0,71,16', + '72,0,79,16', + ]; + + /** 修改评分 */ + const editRating = async (url: string, num: number) => { + try { + const dataRes = await request(url, { + errorText: t('site.ehentai.change_rating_failed'), + noTip: true, + }); + const reRes = + /api_url = "(.+?)".+?gid = (\d+).+?token = "(.+?)".+?apiuid = (\d+).+?apikey = "(.+?)"/s.exec( + dataRes.responseText, + ); + if (!reRes) throw new Error(t('site.ehentai.change_rating_failed')); + const [, api_url, gid, token, apiuid, apikey] = reRes; + + type ResData = { rating_cls: string; rating_usr: number }; + const res = await request(api_url, { + method: 'POST', + responseType: 'json', + data: JSON.stringify({ + method: 'rategallery', + rating: `${num}`, + apikey, + apiuid, + gid, + token, + }), + fetch: true, + noTip: true, + }); + toast.success( + `${t('site.ehentai.change_rating_success')}: ${res.response.rating_usr}`, + ); + return res.response; + } catch { + toast.error(t('site.ehentai.change_rating_failed')); + throw new Error(t('site.ehentai.change_rating_failed')); + } + }; + + /** 根据评分修改显示效果 */ + const updateRatingImage = (dom: HTMLElement, num: number) => { + // 来自 eh 详情页的 update_rating_image 函数 + let a = Math.round(num + 1); + const b = -80 + 16 * Math.ceil(a / 2); + a = a % 2 === 1 ? -21 : -1; + dom.style.backgroundPosition = `${b}px ${a}px`; + }; + + const renderQuickRating = ( + item: HTMLElement, + ir: HTMLElement, + index: number, + ) => { + let basePosition = ir.style.backgroundPosition; + + render( + () => ( + { + ir.style.backgroundPosition = basePosition; + }} + > + + + + {(coords, i) => ( + updateRatingImage(ir, i())} + onClick={async () => { + const res = await editRating( + item.querySelector('a')!.href, + i() + 1, + ); + ir.className = res.rating_cls; + updateRatingImage(ir, res.rating_usr * 2 - 1); + basePosition = ir.style.backgroundPosition; + }} + /> + )} + + + + ), + ir, + ); + }; + + for (const [index, item] of list.entries()) { + const ir = [...item.querySelectorAll('.ir')].at(-1); + if (!ir) continue; + // 快捷评分使用得并不多,所以等鼠标移上去再处理,减少性能损耗 + ir.addEventListener( + 'mouseenter', + () => renderQuickRating(item, ir, index), + { once: true }, + ); + } +}; diff --git a/src/site/nhentai.tsx b/src/site/nhentai.tsx index e8056def..c494641f 100644 --- a/src/site/nhentai.tsx +++ b/src/site/nhentai.tsx @@ -26,7 +26,7 @@ declare const gallery: { num_pages: number; media_id: string; images: Images }; (async () => { const { options, setFab, setManga, init } = await useInit('nhentai', { - /** 自动翻页 */ + /** 无限滚动 */ auto_page_turn: true, /** 彻底屏蔽漫画 */ block_totally: true,