UNPKG

rsshub

Version:
214 lines (188 loc) 8.17 kB
import { Route } from '@/types'; import cache from '@/utils/cache'; import { config } from '@/config'; import got from '@/utils/got'; import { load } from 'cheerio'; import timezone from '@/utils/timezone'; import { parseDate, parseRelativeDate } from '@/utils/parse-date'; import parser from '@/utils/rss-parser'; import queryString from 'query-string'; const threadIdRegex = /(\d+)-\d+-\d+/; const header = { Cookie: config.keylol.cookie ?? undefined, }; export const route: Route = { path: '/:path', name: '论坛', parameters: { path: '路径,默认为热点聚焦' }, categories: ['game'], example: '/keylol/f161-1', features: { requireConfig: [ { name: 'KEYLOL_COOKIE', optional: true, description: `配置后可抓取具有阅读权限的帖子內容`, }, ], requirePuppeteer: false, antiCrawler: false, supportBT: false, supportPodcast: false, supportScihub: false, }, 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 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 = queryString.stringifyUrl({ url: `${rootUrl}/forum.php`, query: queryParams }); const { data: response } = await got({ method: 'get', url: currentUrl, headers: header, }); const $ = load(response); let items = $('tbody[id^="normalthread_"]') .slice(0, limit) .toArray() .map((item) => { item = $(item); return { title: item.find('a.xst').text(), link: new URL(item.find(' a.xst').prop('href').split('&extra=')[0], rootUrl).href, author: item.find('td.by-author cite').text(), pubDate: parseRelativeDate(item.find('td.by-author em').text().replaceAll(' 发表', '')), }; }); 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({ method: 'get', url: item.link, headers: header, }); const content = load(detailResponse); let descriptionList: string[] = []; 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('<br/>'); const realAuthorName = authorNameMap.find((a) => a.threadId === threadId); if (realAuthorName) { item.author = realAuthorName.author; } item.category = content('#keyloL_thread_tags a') .toArray() .map((c) => content(c).text()); const pubDateEm = content('img.authicn').first().next(); const pubDateText = pubDateEm.find('span').prop('title') ?? pubDateEm.text(); const pubDateMatches = pubDateText.match(/(\d{4}(?:-\d{1,2}){2} (?:\d{2}:){2}\d{2})/) ?? undefined; if (pubDateMatches) { item.pubDate = timezone(parseDate(pubDateMatches[1], 'YYYY-M-D HH:mm:ss'), +8); } const updatedMatches = content('i.pstatus') .text() .match(/(\d{4}(?:-\d{1,2}){2} (?:\d{2}:){2}\d{2})/) ?? undefined; if (updatedMatches) { item.updated = timezone(parseDate(updatedMatches[1], 'YYYY-M-D HH:mm:ss'), +8); } item.comments = content('div.subforum_right_title_left_down').text() ? Number.parseInt(content('div.subforum_right_title_left_down').text(), 10) : 0; return item; }) ) ); const icon = $('link[rel="apple-touch-icon"]').prop('href'); return { item: items, title: $('title').text(), link: currentUrl, description: $('meta[name="description"]').prop('content'), language: 'zh-cn', icon, logo: icon, subtitle: $('meta[name="application-name"]').prop('content'), author: $('meta[name="author"]').prop('content'), }; } function getDescription($) { const descriptionEl = $('td.t_f'); descriptionEl.find('div.rnd_ai_pr').remove(); // remove ad image // handle lazyload image descriptionEl.find('img').each((_, img) => { img = $(img); if (img.attr('src')?.endsWith('none.gif') && img.attr('file')) { img.attr('src', img.attr('file')); img.removeAttr('file'); img.removeAttr('zoomfile'); } }); return descriptionEl.length > 0 ? descriptionEl.html() : $('div.alert_info').html(); } async function getPage(url, pageTitle) { const { data: detailResponse } = await got({ method: 'get', url, headers: header, }); const $ = load(detailResponse, { xmlMode: true }); const content = $('root').text(); return '<h3>' + pageTitle + '</h3>' + getDescription(load(content)); }