diff --git a/lib/routes/keylol/index.ts b/lib/routes/keylol/index.ts index 2fff6f24c12eec..d95979983c3aa2 100644 --- a/lib/routes/keylol/index.ts +++ b/lib/routes/keylol/index.ts @@ -1,35 +1,68 @@ import { Route } from '@/types'; -import { getSubPath } from '@/utils/common-utils'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { load } from 'cheerio'; import timezone from '@/utils/timezone'; import { parseDate } from '@/utils/parse-date'; +import parser from '@/utils/rss-parser'; +import queryString from 'query-string'; + +const threadIdRegex = /(\d+)-\d+-\d+/; export const route: Route = { - path: '*', - name: 'Unknown', - maintainers: [], + path: '/:path', + name: '论坛', + parameters: { path: '路径,默认为热点聚焦' }, + categories: ['game'], + example: '/keylol/f161-1', + radar: [ + { + source: ['keylol.com/:path'], + target: (params, url) => url.replaceAll('forum.php?', ''), + }, + ], + maintainers: ['nczitzk', 'kennyfong19931'], handler, + description: `:::tip + 若订阅 [热点聚焦](https://keylol.com/f161-1),网址为 \`https://keylol.com/f161-1\`。截取 \`https://keylol.com/\` 到末尾的部分 \`f161-1\` 作为参数,此时路由为 [\`/keylol/f161-1\`](https://rsshub.app/keylol/f161-1)。 + 若订阅子分类 [试玩免费 - 热点聚焦](https://keylol.com/forum.php?mod=forumdisplay&fid=161&filter=typeid&typeid=459),网址为 \`https://keylol.com/forum.php?mod=forumdisplay&fid=161&filter=typeid&typeid=459\`。提取\`fid\`及\`typeid\` 作为参数,此时路由为 [\`/keylol/fid=161&typeid=459\`](https://rsshub.app/keylol/fid=161&typeid=459)。注意不要包括\`filter\`,会调用[全局的内容过滤](https://docs.rsshub.app/guide/parameters#filtering)。 + :::`, }; async function handler(ctx) { - let thePath = getSubPath(ctx).replace(/^\//, ''); - - if (/^f\d+-\d+/.test(thePath)) { - thePath = `fid=${thePath.match(/^f(\d+)-\d+/)[1]}`; + let queryParams = {}; + const path = ctx.req.param('path'); + if (/^f\d+-\d+/.test(path)) { + queryParams.fid = path.match(/^f(\d+)-\d+/)[1]; + } else { + queryParams = queryString.parse(path); + } + queryParams.mod = 'forumdisplay'; + queryParams.orderby = 'dateline'; + queryParams.filter = 'author'; + + // get real author name from official rss feed + let authorNameMap; + try { + const feed = await parser.parseURL(`https://keylol.com/forum.php?mod=rss&fid=${queryParams.fid}&auth=0`); + authorNameMap = feed.items.map((item) => ({ + threadId: item.link.match(threadIdRegex)[1], + author: item.author, + })); + } catch { + authorNameMap = []; } const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 30; const rootUrl = 'https://keylol.com'; - const currentUrl = new URL(`forum.php?mod=forumdisplay&${thePath.replaceAll(/mod=\w+&/g, '')}`, rootUrl).href; + const currentUrl = queryString.stringifyUrl({ url: `${rootUrl}/forum.php`, query: queryParams }); const { data: response } = await got(currentUrl); const $ = load(response); - let items = $('a.xst') + let items = $('tbody[id^="normalthread_"] a.xst') .slice(0, limit) .toArray() .map((item) => { @@ -44,12 +77,41 @@ async function handler(ctx) { items = await Promise.all( items.map((item) => cache.tryGet(item.link, async () => { + const threadId = threadIdRegex.test(item.link) ? item.link.match(threadIdRegex)[1] : queryString.parseUrl(item.link).query.tid; const { data: detailResponse } = await got(item.link); const content = load(detailResponse); - item.description = content('td.t_f').html(); - item.author = content('a.xw1').first().text(); + let descriptionList: any[] = []; + const indexDiv = content('div#threadindex'); + if (indexDiv.length > 0) { + // post with page + const postId = content('div.t_fsz > script') + .text() + .match(/show_threadindex\((\d+),/)[1]; + descriptionList = await Promise.all( + indexDiv.find('a').map((i, a) => { + const pageTitle = $(a).text(); + const page = $(a).attr('page'); + return getPage( + `${rootUrl}/forum.php?${queryString.stringify({ + mod: 'viewthread', + tid: threadId, + viewpid: postId, + cp: page, + })}`, + pageTitle + ); + }) + ); + } else { + // normal post + descriptionList.push(getDescription(content)); + } + + item.description = descriptionList.join('
'); + const authorName = authorNameMap.find((a) => a.threadId === threadId); + item.author = authorName && authorName.length > 0 ? authorName[0].author : content('a.xw1').first().text(); item.category = content('#keyloL_thread_tags a') .toArray() .map((c) => content(c).text()); @@ -90,3 +152,17 @@ async function handler(ctx) { author: $('meta[name="author"]').prop('content'), }; } + +function getDescription($) { + const descriptionEl = $('td.t_f'); + descriptionEl.find('div.rnd_ai_pr').remove(); // remove ad image + return descriptionEl.html(); +} + +async function getPage(url, pageTitle) { + const { data: detailResponse } = await got(url); + + const $ = load(detailResponse, { xmlMode: true }); + const content = $('root').text(); + return '

' + pageTitle + '

' + getDescription(load(content)); +}