From e2c991a0edde2649bde41f82372f7b0a07c13643 Mon Sep 17 00:00:00 2001 From: YuruShiMaShiMaRin <71917424+xiaobailoves@users.noreply.github.com> Date: Wed, 11 Mar 2026 00:44:18 +0800 Subject: [PATCH] =?UTF-8?q?feat(comic-walker.com):=20=E6=96=B0=E6=B7=BB?= =?UTF-8?q?=E4=B8=80=E4=B8=AA=E8=B7=AF=E7=94=B1=20(#21310)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add route * style: auto format * Update manga.ts * Update manga.ts * ts -> tsx * Update manga.tsx * 没法了,这是我最后的手段了( * Update manga.ts * Update manga.ts * Update manga.ts * Update manga.ts --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- lib/routes/comic-walker/manga.ts | 122 +++++++++++++++++++++++++++ lib/routes/comic-walker/namespace.ts | 7 ++ 2 files changed, 129 insertions(+) create mode 100644 lib/routes/comic-walker/manga.ts create mode 100644 lib/routes/comic-walker/namespace.ts diff --git a/lib/routes/comic-walker/manga.ts b/lib/routes/comic-walker/manga.ts new file mode 100644 index 0000000000..4ee72b67ab --- /dev/null +++ b/lib/routes/comic-walker/manga.ts @@ -0,0 +1,122 @@ +import { load } from 'cheerio'; + +import type { Route } from '@/types'; +import ofetch from '@/utils/ofetch'; +import { parseDate } from '@/utils/parse-date'; + +const getEpisodes = (obj: any) => obj?.result || obj?.items || (Array.isArray(obj) ? obj : []); + +export const route: Route = { + path: '/manga/:id', + categories: ['anime'], + example: '/comic-walker/manga/KC_006778_S', + parameters: { id: 'カドコミ(Kadocomi)中对应的作品workCode,例如 KC_006778_S' }, + features: { + requireConfig: false, + requirePuppeteer: false, + antiCrawler: false, + supportBT: false, + supportPodcast: false, + supportScihub: false, + }, + radar: [ + { + source: ['comic-walker.com/detail/:id'], + target: '/manga/:id', + }, + ], + name: 'カドコミ(Kadocomi)漫画详情', + maintainers: ['xiaobailoves'], + + handler: async (ctx) => { + const { id } = ctx.req.param(); + const baseUrl = 'https://comic-walker.com'; + + const fetchUrl = `${baseUrl}/detail/${id}?episodeType=first`; + const openUrl = `${baseUrl}/detail/${id}`; + + const response = await ofetch(fetchUrl, { + headers: { + Referer: baseUrl, + 'Accept-Language': 'ja,en-US;q=0.9,en;q=0.8', + }, + }); + + const $ = load(response); + const nextDataText = $('#__NEXT_DATA__').text(); + + if (!nextDataText) { + throw new Error('无法解析页面 HTML 数据,可能触发了反爬策略或页面结构巨变'); + } + + const nextData = JSON.parse(nextDataText); + const queries = nextData.props?.pageProps?.dehydratedState?.queries || []; + + const workQuery = queries.find((q: any) => q.queryKey?.includes('/api/contents/details/work') || (Array.isArray(q.queryKey) && q.queryKey.some((k: any) => typeof k === 'string' && k.includes('work')))); + + if (!workQuery || !workQuery.state?.data) { + throw new Error('无法在 HTML 缓存中提取核心数据对象'); + } + + const data = workQuery.state.data; + const work = data.work; + + if (!work) { + throw new Error('成功获取数据对象,但未找到作品基本信息'); + } + + const mangaTitle = work.title || $('title').text().trim(); + const mangaAuthor = work.authors?.map((author: any) => author.name).join(', ') || ''; + const mangaDescription = work.summary || ''; + const coverImage = work.bookCover || work.thumbnail; + + const firstEpisodes = getEpisodes(data.firstEpisodes); + const latestEpisodes = getEpisodes(data.latestEpisodes); + const extraEpisodes = getEpisodes(data.episodes); + + const seenCodes = new Set(); + const allChapters = [...firstEpisodes, ...latestEpisodes, ...extraEpisodes] + .filter((ep) => { + if (ep?.code && !seenCodes.has(ep.code)) { + seenCodes.add(ep.code); + return true; + } + return false; + }) + .toSorted((a: any, b: any) => (b.internal?.episodeNo || 0) - (a.internal?.episodeNo || 0)); + + if (allChapters.length === 0) { + throw new Error('HTML 缓存中无章节!'); + } + + const items = allChapters.map((chapter: any) => { + const epType = chapter.type === 'normal' ? '正篇' : '特别篇/PR'; + const isReadStatus = chapter.isActive ? '' : ' (未解锁/仍需等待)'; + const fullTitle = `${chapter.title}${chapter.subTitle ? ` - ${chapter.subTitle}` : ''}${isReadStatus}`; + const thumb = chapter.originalThumbnail || chapter.thumbnail; + + const currentPubDate = chapter.updateDate ? parseDate(chapter.updateDate) : undefined; + + return { + title: fullTitle, + link: `${baseUrl}/detail/${id}/episodes/${chapter.code}`, + description: ` + ${thumb ? `
` : ''} + `, + guid: `Kadocomi-manga-${chapter.code}`, + category: epType, + author: mangaAuthor, + pubDate: currentPubDate, + }; + }); + + return { + title: `Kadocomi - ${mangaTitle}`, + link: openUrl, + description: mangaDescription, + image: coverImage, + item: items, + language: 'ja', + }; + }, +}; diff --git a/lib/routes/comic-walker/namespace.ts b/lib/routes/comic-walker/namespace.ts new file mode 100644 index 0000000000..d98db15372 --- /dev/null +++ b/lib/routes/comic-walker/namespace.ts @@ -0,0 +1,7 @@ +import type { Namespace } from '@/types'; + +export const namespace: Namespace = { + name: 'カドコミ(Kadocomi)', + url: 'comic-walker.com', + lang: 'ja', +};