-
Notifications
You must be signed in to change notification settings - Fork 7.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* fix(route): 遊戲基地新聞 * fix: String.fromCodePoint() over String.fromCharCode()
- Loading branch information
Showing
2 changed files
with
168 additions
and
64 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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: `类型 | ||
| newslist | r18list | | ||
| -------- | ------- |`, | ||
}; | ||
export const handler = async (ctx: Context): Promise<Data> => { | ||
const { type = 'newslist', category = 'all' } = ctx.req.param(); | ||
|
||
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; | ||
if (!types.hasOwnProperty(type)) { | ||
throw new InvalidParameterError(`Invalid type: ${type}`); | ||
} | ||
|
||
const rootUrl = 'https://news.gamebase.com.tw'; | ||
const currentUrl = `${rootUrl}/news/${type}?type=${category}`; | ||
const limit: number = Number.parseInt(ctx.req.query('limit') ?? '30', 10); | ||
|
||
const apiRootUrl = 'https://api.gamebase.com.tw'; | ||
const apiUrl = `${apiRootUrl}/api/news/getNewsList`; | ||
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 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`\"`, '"'); | ||
|
||
i.description = description.replaceAll(/\\u[\da-f]{4}/gi, (match) => String.fromCharCode(Number.parseInt(match.replaceAll(String.raw`\u`, ''), 16))); | ||
|
||
return i; | ||
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, | ||
}; | ||
|
||
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 | | ||
| -------- | ------- | | ||
`, | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 }} |