UNPKG

rsshub

Version:
282 lines (263 loc) 9.78 kB
import { Route, Data, DataItem } from '@/types'; import type { BBobCoreTagNodeTree, PresetFactory, NodeContent } from '@bbob/types'; import got from '@/utils/got'; import bbobHTML from '@bbob/html'; import presetHTML5 from '@bbob/preset-html5'; import { getUniqAttr } from '@bbob/plugin-helper'; import { parseDate } from '@/utils/parse-date'; import type { Context } from 'hono'; export const route: Route = { path: '/news/:appid/:language?', name: 'News', url: 'steamcommunity.com', maintainers: ['keocheung'], handler, example: '/news/958260/english', parameters: { appid: 'Game App ID, all digits, can be found in the URL', language: 'Language, english by default, see below for more languages', }, description: ` <details> <summary>More languages</summary> | 语言代码 | 语言名称 | | ------------------------------------------------- | ---------- | | English | english | | Español - España (Spanish - Spain) | spanish | | Français (French) | french | | Italiano (Italian) | italian | | Deutsch (German) | german | | Ελληνικά (Greek) | greek | | 한국어 (Korean) | koreana | | 简体中文 (Simplified Chinese) | schinese | | 繁體中文 (Traditional Chinese) | tchinese | | Русский (Russian) | russian | | ไทย (Thai) | thai | | 日本語 (Japanese) | japanese | | Português (Portuguese) | portuguese | | Português - Brasil (Portuguese - Brazil) | brazilian | | Polski (Polish) | polish | | Dansk (Danish) | danish | | Nederlands (Dutch) | dutch | | Suomi (Finnish) | finnish | | Norsk (Norwegian) | norwegian | | Svenska (Swedish) | swedish | | Čeština (Czech) | czech | | Magyar (Hungarian) | hungarian | | Română (Romanian) | romanian | | Български (Bulgarian) | bulgarian | | Türkçe (Turkish) | turkish | | Українська (Ukrainian) | ukrainian | | Tiếng Việt (Vietnamese) | vietnamese | | Español - Latinoamérica (Spanish - Latin America) | latam | </details> `, categories: ['game'], features: { requireConfig: false, requirePuppeteer: false, antiCrawler: false, supportRadar: true, supportBT: false, supportPodcast: false, supportScihub: false, }, radar: [ { title: 'News', source: ['steamcommunity.com/app/:appid', 'steamcommunity.com/app/:appid/allnews', 'steamcommunity.com/app/:appid/announcements', 'steamcommunity.com/app/:appid/news'], target: '/news/:appid', }, ], }; const langMap = { english: 'en', german: 'de', french: 'fr', italian: 'it', korean: 'ko', spanish: 'es', schinese: 'zh-Hans', tchinese: 'zh-Hant', japanese: 'ja', portuguese: 'pt-PT', brazilian: 'pt-BR', russian: 'ru', polish: 'pl', danish: 'da', dutch: 'nl', finnish: 'fi', norwegian: 'no', swedish: 'sv', czech: 'cs', hungarian: 'hu', turkish: 'tr', thai: 'th', ukrainian: 'uk', vietnamese: 'vi', romanian: 'ro', greek: 'el', arabic: 'ar', latam: 'es-419', bulgarian: 'bg', }; async function handler(ctx: Context): Promise<Data> { const { appid = '958260', language = 'english' } = ctx.req.param(); const limitQuery = ctx.req.query('limit'); const limit = limitQuery ? Number.parseInt(limitQuery, 10) : 100; const rootUrl = 'https://steamcommunity.com'; const apiRootUrl = 'https://store.steampowered.com'; const clanRootUrl = 'https://clan.fastly.steamstatic.com'; const sharedRootUrl = 'https://shared.fastly.steamstatic.com'; const apiUrl = new URL('events/ajaxgetpartnereventspageable/', apiRootUrl).href; const { data: response } = await got(apiUrl, { searchParams: { clan_accountid: 0, appid, offset: 0, count: limit, l: language, }, }); const items: DataItem[] = response.events.slice(0, limit).map((item): DataItem => { const title = item.event_name; const description = `<div lang="${langMap[language] || ''}">${bbobHTML( item.announcement_body.body .replaceAll('{STEAM_CLAN_IMAGE}', `${clanRootUrl}/images`) .replaceAll('[olist]', '[list=1]') .replaceAll('[/olist]', '[/list]') .replaceAll(/(\[\/h\d\])\n/g, '$1') .replaceAll(/(\[list(?:=.*?)?\])\n/g, '$1'), [customPreset(), linebreakRenderer, plainUrlRenderer] )}</div>`; const jsondata = JSON.parse(item.jsondata); const titleImage = jsondata.localized_title_image?.[0]; const capsuleImage = jsondata.localized_capsule_image?.[0]; return { title, description, pubDate: parseDate(item.announcement_body.posttime, 'X'), link: new URL(`games/${appid}/announcements/detail/${item.announcement_body.gid}`, rootUrl).href, category: item.announcement_body.tags, content: { html: description, text: item.announcement_body.body, }, updated: parseDate(item.announcement_body.updatetime, 'X'), image: capsuleImage ? new URL(`images/${item.announcement_body.clanid}/${capsuleImage}`, clanRootUrl).href : undefined, banner: titleImage ? new URL(`images/${item.announcement_body.clanid}/${titleImage}`, clanRootUrl).href : undefined, }; }); return { title: `App ${appid} News`, link: new URL(`app/${appid}/allnews/`, rootUrl).href, image: new URL(`store_item_assets/steam/apps/${appid}/hero_capsule.jpg`, sharedRootUrl).href, item: items, language: langMap[language] || null, }; } const linebreakRenderer = (tree: BBobCoreTagNodeTree) => tree.walk((node) => { if (typeof node === 'string' && node === '\n') { return { tag: 'br', content: null, }; } return node; }); const plainUrlRenderer = (tree: BBobCoreTagNodeTree) => tree.walk((node) => { if (typeof node === 'string' && /https?:\/\/[^\s]+/.test(node)) { let lastIndex = 0; let match: RegExpExecArray | null; const content: NodeContent[] = []; const urlRe = /https?:\/\/[^\s]+/g; while ((match = urlRe.exec(node)) !== null) { if (match.index > lastIndex) { content.push(node.slice(lastIndex, match.index)); } content.push({ tag: 'a', attrs: { href: match[0], rel: 'noopener', target: '_blank', }, content: match[0], }); lastIndex = match.index + match[0].length; } if (lastIndex < node.length) { content.push(node.slice(lastIndex)); } if (content.length === 0) { return node; } if (content.length === 1) { return content[0]; } return { tag: 'span', content, }; } return node; }); const customPreset: PresetFactory = presetHTML5.extend((tags) => ({ ...tags, b: (node) => ({ tag: 'b', content: node.content, }), i: (node) => ({ tag: 'i', content: node.content, }), u: (node) => ({ tag: 'u', content: node.content, }), s: (node) => ({ tag: 's', content: node.content, }), url: (node) => ({ tag: 'a', attrs: { href: Object.keys(node.attrs as Record<string, string>)[0], rel: 'noopener', target: '_blank', }, content: node.content, }), previewyoutube: (node) => ({ tag: 'iframe', attrs: { src: `https://www.youtube-nocookie.com/embed/${(getUniqAttr(node.attrs) as string).match(/[A-Za-z0-9_-]+/)?.[0]}`, title: 'YouTube video player', frameborder: '0', allowFullScreen: '1', }, }), video: (node, { render }) => ({ tag: 'video', attrs: { controls: '', preload: 'metadata', poster: node.attrs?.poster, }, content: render( Object.entries({ webm: 'video/webm', mp4: 'video/mp4', }).map(([key, type]) => ({ tag: 'source', attrs: { src: node.attrs?.[key], type, }, })) ), }), }));