diff --git a/lib/routes/gamersky/user.ts b/lib/routes/gamersky/user.ts new file mode 100644 index 000000000..fc338afe7 --- /dev/null +++ b/lib/routes/gamersky/user.ts @@ -0,0 +1,39 @@ +import type { Context } from 'hono'; +import type { Route } from '@/types'; +import { getUserArticle, getUserArticleList, parseUserArticleList } from './utils'; + +export const route: Route = { + path: '/user/:userId/:detail?', + categories: ['game'], + example: '/gamersky/user/4009731/detail', + parameters: { + userId: '用户 ID。在用户个人主页,打开“开发者工具”中的“元素”标签页,搜索 data-userid 即可找到', + detail: '是否获取文章详情。只要该参数不为空,就会获取全文内容', + }, + features: { + requireConfig: false, + requirePuppeteer: false, + antiCrawler: false, + supportBT: false, + supportPodcast: false, + supportScihub: false, + }, + name: '用户动态', + maintainers: ['hualiong'], + handler, +}; + +async function handler(ctx: Context) { + const { userId, detail } = ctx.req.param(); + + const body = await getUserArticleList(userId); + const articles = parseUserArticleList(body); + if (detail) { + articles.list = await Promise.all(articles.list.map((item) => getUserArticle(item))); + } + return { + title: `${articles.uname}的动态 - 游民星空`, + link: articles.link, + item: articles.list, + }; +} diff --git a/lib/routes/gamersky/utils.ts b/lib/routes/gamersky/utils.ts index c11f631ad..d740683d9 100644 --- a/lib/routes/gamersky/utils.ts +++ b/lib/routes/gamersky/utils.ts @@ -12,12 +12,19 @@ interface idNameMap { nodeId: string; suffix?: string; } + interface ArticleList { status: string; totalPages: number; body: string; } +interface UserArticleList { + status: string; + body: string; + total: number; +} + export const getArticleList = async (nodeId) => { const response = await ofetch( `https://db2.gamersky.com/LabelJsonpAjax.aspx?${new URLSearchParams({ @@ -88,6 +95,47 @@ export const getArticle = (item) => return item satisfies DataItem; }) as Promise; +export const getUserArticleList = async (userId: string) => { + const response = await ofetch( + `https://i.gamersky.com/u/api/v2/GetUserContent?${new URLSearchParams({ + jsondata: JSON.stringify({ pageIndex: 1, pageSize: 20, userId }), + })}`, + { + parseResponse: (txt) => + JSON.parse(txt.slice(1, -2)), + }, + ); + return response.body; +}; + +export const parseUserArticleList = (body: string) => { + const $ = load(body); + const list = $(".cmt-list"); + const info = list.find(".uname").first(); + return { + uname: info.text(), + link: info.attr("href"), + list: list.toArray().map((item) => { + const e = $(item); + const title = e.find(".qzcmt-content-tit a"); + return { + title: title.text(), + link: title.attr("href"), + pubDate: parseDate(e.attr("data-time")!), + description: e.find(".qzcmt-content-txt span").text(), + }; + }) as DataItem[], + }; +}; + +export const getUserArticle = (item: DataItem) => + cache.tryGet(item.link!, async () => { + const response = await ofetch(item.link!); + const $ = load(response); + item.description = $(".qzcmt-content-txt").html() || item.description; + return item; + }); + export function mdTableBuilder(data: idNameMap[]) { const table = '|' + data.map((item) => `${item.type}|`).join('') + '\n|' + Array.from({ length: data.length }).fill('---|').join('') + '\n|' + data.map((item) => `${item.name}|`).join('') + '\n'; return table;