diff --git a/lib/config.ts b/lib/config.ts index 4c4a713b6..3a10349fd 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -276,6 +276,9 @@ export type Config = { ximalaya: { token?: string; }; + xueqiu: { + cookies?: string; + }; youtube: { key?: string; clientId?: string; @@ -619,6 +622,9 @@ const calculateValue = () => { ximalaya: { token: envs.XIMALAYA_TOKEN, }, + xueqiu: { + cookies: envs.XUEQIU_COOKIES, + }, youtube: { key: envs.YOUTUBE_KEY, clientId: envs.YOUTUBE_CLIENT_ID, diff --git a/lib/routes/xueqiu/timeline.ts b/lib/routes/xueqiu/timeline.ts new file mode 100644 index 000000000..46d649ba9 --- /dev/null +++ b/lib/routes/xueqiu/timeline.ts @@ -0,0 +1,93 @@ +import { DataItem, Route } from '@/types'; +import got from '@/utils/got'; +import { config } from '@/config'; +import { parseDate } from '@/utils/parse-date'; +import cache from '@/utils/cache'; +const rootUrl = 'https://xueqiu.com'; +export const route: Route = { + path: '/timeline/:usergroup_id?', + categories: ['finance'], + example: '/xueqiu/timeline/', + parameters: { usergroup_id: '用户组 ID,-1 为全部关注用户' }, + features: { + requireConfig: [ + { + name: 'XUEQIU_COOKIES', + description: '', + }, + ], + requirePuppeteer: false, + antiCrawler: true, + supportBT: false, + supportPodcast: false, + supportScihub: false, + }, + name: '用户关注时间线', + maintainers: ['ErnestDong'], + handler, + description: `:::warning + 用户关注动态需要登录后的 Cookie 值,所以只能自建,详情见部署页面的配置模块。 + ::: + + | -1 | -2 | 1 | + | ---- | -------- | ------------- | + | 全部 | 关注精选 | 自定义第 1 组 |`, +}; + +async function handler(ctx) { + const cookie = config.xueqiu.cookies; + const limit = ctx.req.query('limit') || 15; + const usergroup_id = ctx.req.param('usergroup_id') ?? -1; + if (cookie === undefined) { + throw new Error('缺少雪球用户登录后的 Cookie 值'); + } + let out: DataItem[] = []; + let max_id = -1; + + async function fetchItems() { + const data = await fetchNextID(max_id, cookie as string, usergroup_id); + const items = await Promise.all( + data.home_timeline.map((item) => + cache.tryGet(item.target, async () => { + const retweetedStatus = item.retweeted_status ? `
${item.retweeted_status.user.screen_name}: ${item.retweeted_status.description}` : ''; + const description = item.description + retweetedStatus; + const result = await Promise.resolve({ + title: item.title === '' ? item.user.screen_name + (item.retweeted_status ? ' 转发' : '') : item.title, + description: item.text ? item.text + retweetedStatus : description, + pubDate: parseDate(item.created_at), + link: rootUrl + item.target, + }); + return result; + }) + ) + ); + out = [...out, ...items]; + max_id = data.next_max_id; + + if (out.length < limit) { + await fetchItems(); + } + } + + await fetchItems(); + + return { + title: '雪球关注动态', + link: 'https://xueqiu.com/', + item: out, + }; +} +async function fetchNextID(max_id: number, cookie: string, usergroup_id = -1) { + let url = `https://xueqiu.com/v4/statuses/system/home_timeline.json?source=user&usergroup_id=${usergroup_id}`; + if (max_id > 0) { + url += `&max_id=${max_id}`; + } + const response = await got({ + method: 'get', + url, + headers: { + Cookie: cookie, + }, + }); + return response.data; +}