fix(route): 遊戲基地新聞 (#18152)
* fix(route): 遊戲基地新聞 * fix: String.fromCodePoint() over String.fromCharCode()
This commit is contained in:
parent
30615dd709
commit
e280d410a2
|
|
@ -1,98 +1,181 @@
|
|||
import { Route } from '@/types';
|
||||
import { type Data, type DataItem, type Route, ViewType } from '@/types';
|
||||
|
||||
import { art } from '@/utils/render';
|
||||
import cache from '@/utils/cache';
|
||||
import got from '@/utils/got';
|
||||
import { load } from 'cheerio';
|
||||
import timezone from '@/utils/timezone';
|
||||
import { getCurrentPath } from '@/utils/helpers';
|
||||
import InvalidParameterError from '@/errors/types/invalid-parameter';
|
||||
import ofetch from '@/utils/ofetch';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import timezone from '@/utils/timezone';
|
||||
|
||||
import { type CheerioAPI, load } from 'cheerio';
|
||||
import { type Context } from 'hono';
|
||||
import path from 'node:path';
|
||||
|
||||
const __dirname = getCurrentPath(import.meta.url);
|
||||
|
||||
const types = {
|
||||
newslist: 'newsList',
|
||||
r18list: 'newsPornList',
|
||||
};
|
||||
|
||||
export const route: Route = {
|
||||
path: '/news/:type?/:category?',
|
||||
categories: ['game'],
|
||||
example: '/gamebase/news',
|
||||
parameters: { type: '类型,见下表,默认为 newslist', category: '分类,可在对应分类页 URL 中找到,默认为 `all` 即全部' },
|
||||
features: {
|
||||
requireConfig: false,
|
||||
requirePuppeteer: false,
|
||||
antiCrawler: false,
|
||||
supportBT: false,
|
||||
supportPodcast: false,
|
||||
supportScihub: false,
|
||||
},
|
||||
name: '新聞',
|
||||
maintainers: ['nczitzk'],
|
||||
handler,
|
||||
description: `类型
|
||||
export const handler = async (ctx: Context): Promise<Data> => {
|
||||
const { type = 'newslist', category = 'all' } = ctx.req.param();
|
||||
|
||||
| newslist | r18list |
|
||||
| -------- | ------- |`,
|
||||
};
|
||||
if (!types.hasOwnProperty(type)) {
|
||||
throw new InvalidParameterError(`Invalid type: ${type}`);
|
||||
}
|
||||
|
||||
async function handler(ctx) {
|
||||
const type = ctx.req.param('type') ?? 'newslist';
|
||||
const category = ctx.req.param('category') ?? 'all';
|
||||
const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 20;
|
||||
const limit: number = Number.parseInt(ctx.req.query('limit') ?? '30', 10);
|
||||
|
||||
const rootUrl = 'https://news.gamebase.com.tw';
|
||||
const currentUrl = `${rootUrl}/news/${type}?type=${category}`;
|
||||
const baseUrl: string = 'https://news.gamebase.com.tw';
|
||||
const targetUrl: string = new URL(`news${category === 'all' ? '' : `/newslist?type=${category}`}`, baseUrl).href;
|
||||
const apiBaseUrl: string = 'https://api.gamebase.com.tw';
|
||||
const apiUrl: string = new URL('api/news/getNewsList', apiBaseUrl).href;
|
||||
|
||||
const apiRootUrl = 'https://api.gamebase.com.tw';
|
||||
const apiUrl = `${apiRootUrl}/api/news/getNewsList`;
|
||||
|
||||
const response = await got({
|
||||
const response = await ofetch(apiUrl, {
|
||||
method: 'post',
|
||||
url: apiUrl,
|
||||
json: {
|
||||
body: {
|
||||
GB_type: types[type],
|
||||
category,
|
||||
page: 1,
|
||||
},
|
||||
});
|
||||
|
||||
const titleResponse = await got({
|
||||
method: 'get',
|
||||
url: currentUrl,
|
||||
});
|
||||
const targetResponse = await ofetch(targetUrl);
|
||||
const $: CheerioAPI = load(targetResponse);
|
||||
const language = $('html').attr('lang') ?? 'zh-TW';
|
||||
|
||||
const $ = load(titleResponse.data);
|
||||
const items: DataItem[] = await Promise.all(
|
||||
response.return_msg?.list?.slice(0, limit).map((item) =>
|
||||
cache.tryGet(`gamebase-news-${item.news_no}`, async (): Promise<DataItem> => {
|
||||
const title: string = item.news_title;
|
||||
const pubDate: number | string = item.post_time;
|
||||
const linkUrl: string | undefined = item.news_no ? `news/detail/${item.news_no}` : undefined;
|
||||
const categories: string[] = [item.system];
|
||||
const authors: DataItem['author'] = item.nickname;
|
||||
const guid: string = `gamebase-news-${item.news_no}`;
|
||||
const image: string | undefined = item.news_img;
|
||||
const updated: number | string = item.updated ?? pubDate;
|
||||
|
||||
const items = await Promise.all(
|
||||
response.data.return_msg.list.slice(0, limit).map((item) =>
|
||||
cache.tryGet(`gamebase:news:${type}:${category}:${item.news_no}`, async () => {
|
||||
const i = {};
|
||||
let metaDesc = item.news_meta?.meta_des;
|
||||
|
||||
i.author = item.nickname;
|
||||
i.title = item.news_title;
|
||||
i.link = `${rootUrl}/news/detail/${item.news_no}`;
|
||||
i.description = item.news_meta?.meta_des ?? '';
|
||||
i.pubDate = timezone(parseDate(item.post_time), +8);
|
||||
i.category = [item.system];
|
||||
if (!metaDesc) {
|
||||
const detailResponse = await ofetch(item.link);
|
||||
|
||||
if (i.description) {
|
||||
return i;
|
||||
metaDesc = (detailResponse.match(/(\\u003C.*?)","/)?.[1] ?? '').replaceAll(String.raw`\"`, '"').replaceAll(/\\u([\da-f]{4})/gi, (match, hex) => String.fromCodePoint(Number.parseInt(hex, 16)));
|
||||
}
|
||||
|
||||
const detailResponse = await got({
|
||||
method: 'get',
|
||||
url: i.link,
|
||||
const description: string = art(path.join(__dirname, 'templates/description.art'), {
|
||||
images:
|
||||
image && !metaDesc
|
||||
? [
|
||||
{
|
||||
src: image,
|
||||
alt: title,
|
||||
},
|
||||
]
|
||||
: undefined,
|
||||
intro: item.news_short_desc,
|
||||
description: metaDesc,
|
||||
});
|
||||
|
||||
const description = detailResponse.data.match(/(\\u003C.*?)","/)[1].replaceAll(String.raw`\"`, '"');
|
||||
const processedItem: DataItem = {
|
||||
title,
|
||||
description,
|
||||
pubDate: pubDate ? timezone(parseDate(pubDate), +8) : undefined,
|
||||
link: linkUrl ? new URL(linkUrl, baseUrl).href : undefined,
|
||||
category: categories,
|
||||
author: authors,
|
||||
guid,
|
||||
id: guid,
|
||||
content: {
|
||||
html: description,
|
||||
text: description,
|
||||
},
|
||||
image,
|
||||
banner: image,
|
||||
updated: updated ? timezone(parseDate(updated), +8) : undefined,
|
||||
language,
|
||||
};
|
||||
|
||||
i.description = description.replaceAll(/\\u[\da-f]{4}/gi, (match) => String.fromCharCode(Number.parseInt(match.replaceAll(String.raw`\u`, ''), 16)));
|
||||
|
||||
return i;
|
||||
return processedItem;
|
||||
})
|
||||
)
|
||||
) ?? []
|
||||
);
|
||||
|
||||
return {
|
||||
title: $('title').text(),
|
||||
link: currentUrl,
|
||||
description: $('meta[property="og:description"]').attr('content'),
|
||||
link: targetUrl,
|
||||
item: items,
|
||||
allowEmpty: true,
|
||||
image: $('meta[property="og:image"]').attr('content'),
|
||||
author: $('meta[property="og:title"]').attr('content')?.split(/\|/).pop()?.trim(),
|
||||
language,
|
||||
id: $('meta[property="og:url"]').attr('content'),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const route: Route = {
|
||||
path: '/news/:type?/:category?',
|
||||
name: '新聞',
|
||||
url: 'news.gamebase.com.tw',
|
||||
maintainers: ['nczitzk'],
|
||||
handler,
|
||||
example: '/gamebase/news',
|
||||
parameters: {
|
||||
type: '類型,見下表,預設為 newslist',
|
||||
category: '分類,預設為 `all`,即全部,可在對應分類頁 URL 中找到',
|
||||
},
|
||||
description: `:::tip
|
||||
若訂閱 [手機遊戲新聞](https://news.gamebase.com.tw/news/newslist?type=mobile),網址為 \`https://news.gamebase.com.tw/news/newslist?type=mobile\`,請截取 \`https://news.gamebase.com.tw/news/\` 到末尾的部分 \`newslist\` 作為 \`type\` 參數填入,\`mobile\` 作為 \`category\` 參數填入,此時目標路由為 [\`/gamebase/news/newslist/mobile\`](https://rsshub.app/gamebase/news/newslist/mobile)。
|
||||
:::
|
||||
|
||||
| newslist | r18list |
|
||||
| -------- | ------- |
|
||||
`,
|
||||
categories: ['game'],
|
||||
features: {
|
||||
requireConfig: false,
|
||||
requirePuppeteer: false,
|
||||
antiCrawler: false,
|
||||
supportRadar: true,
|
||||
supportBT: false,
|
||||
supportPodcast: false,
|
||||
supportScihub: false,
|
||||
},
|
||||
radar: [
|
||||
{
|
||||
source: ['news.gamebase.com.tw/news', 'news.gamebase.com.tw/news/:type'],
|
||||
target: (params, url) => {
|
||||
const type: string = params.type;
|
||||
const urlObj: URL = new URL(url);
|
||||
const category: string | undefined = urlObj.searchParams.get('type') ?? undefined;
|
||||
|
||||
return `/gamebase/news${type ? `/${type}${category ? `/${category}` : ''}` : ''}`;
|
||||
},
|
||||
},
|
||||
],
|
||||
view: ViewType.Articles,
|
||||
|
||||
zh: {
|
||||
path: '/news/:type?/:category?',
|
||||
name: '新闻',
|
||||
url: 'news.gamebase.com.tw',
|
||||
maintainers: ['nczitzk'],
|
||||
handler,
|
||||
example: '/gamebase/news',
|
||||
parameters: {
|
||||
type: '类型,见下表,默认为 newslist',
|
||||
category: '分类,默认为 `all`,即全部,可在对应分类页 URL 中找到',
|
||||
},
|
||||
description: `:::tip
|
||||
若订阅 [手机游戏新闻](https://news.gamebase.com.tw/news/newslist?type=mobile),网址为 \`https://news.gamebase.com.tw/news/newslist?type=mobile\`,请截取 \`https://news.gamebase.com.tw/news/\` 到末尾的部分 \`newslist\` 作为 \`type\` 参数填入,\`mobile\` 作为 \`category\` 参数填入,此时目标路由为 [\`/gamebase/news/newslist/mobile\`](https://rsshub.app/gamebase/news/newslist/mobile)。
|
||||
:::
|
||||
|
||||
| newslist | r18list |
|
||||
| -------- | ------- |
|
||||
`,
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
{{ if images }}
|
||||
{{ each images image }}
|
||||
{{ if image?.src }}
|
||||
<figure>
|
||||
<img
|
||||
{{ if image.alt }}
|
||||
alt="{{ image.alt }}"
|
||||
{{ /if }}
|
||||
src="{{ image.src }}">
|
||||
</figure>
|
||||
{{ /if }}
|
||||
{{ /each }}
|
||||
{{ /if }}
|
||||
|
||||
{{ if intro }}
|
||||
<blockquote>{{ intro }}</blockquote>
|
||||
{{ /if }}
|
||||
|
||||
{{ if description }}
|
||||
{{@ description }}
|
||||
{{ /if }}
|
||||
Loading…
Reference in New Issue