rsshub 1.0.0-master.f9b85f5 → 1.0.0-master.f9c381a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/config.ts +10 -0
- package/lib/middleware/cache.ts +4 -0
- package/lib/middleware/parameter.ts +1 -1
- package/lib/registry.ts +6 -2
- package/lib/routes/51cto/utils.ts +1 -1
- package/lib/routes/abc/index.ts +1 -1
- package/lib/routes/acgvinyl/namespace.ts +6 -0
- package/lib/routes/acgvinyl/news.ts +86 -0
- package/lib/routes/ally/rail.ts +1 -1
- package/lib/routes/anthropic/news.ts +13 -11
- package/lib/routes/apnews/mobile-api.ts +1 -1
- package/lib/routes/apnews/sitemap.ts +1 -1
- package/lib/routes/apple/apps.ts +2 -2
- package/lib/routes/apple/podcast.ts +58 -25
- package/lib/routes/bangumi.tv/group/reply.ts +1 -1
- package/lib/routes/bilibili/cache.ts +18 -2
- package/lib/routes/bilibili/dynamic.ts +31 -7
- package/lib/routes/bilibili/page.ts +1 -1
- package/lib/routes/bilibili/ranking.ts +23 -17
- package/lib/routes/bilibili/wasm-exec.ts +1 -1
- package/lib/routes/bilibili/weekly-recommend.ts +22 -6
- package/lib/routes/bjp/apod.ts +1 -1
- package/lib/routes/buaa/jiaowu.ts +1 -1
- package/lib/routes/bullionvault/gold-news.ts +3 -3
- package/lib/routes/cast/index.ts +1 -1
- package/lib/routes/coolapk/utils.ts +5 -4
- package/lib/routes/coolbuy/index.ts +106 -0
- package/lib/routes/{xinhuanet → coolbuy}/namespace.ts +3 -3
- package/lib/routes/coolbuy/templates/description.art +48 -0
- package/lib/routes/coolidge/film-guide.ts +60 -0
- package/lib/routes/coolidge/namespace.ts +7 -0
- package/lib/routes/coolidge/news.ts +65 -0
- package/lib/routes/coolidge/templates/description.art +4 -0
- package/lib/routes/copymanga/comic.ts +1 -1
- package/lib/routes/cpta/handler.ts +1 -1
- package/lib/routes/creative-comic/book.ts +1 -1
- package/lib/routes/daum/potplayer.ts +1 -1
- package/lib/routes/dockerhub/utils.ts +1 -1
- package/lib/routes/dora-world/article.ts +2 -2
- package/lib/routes/ehentai/ehapi.ts +3 -3
- package/lib/routes/eventbrite/events.ts +152 -0
- package/lib/routes/eventbrite/namespace.ts +7 -0
- package/lib/routes/github/activity.ts +1 -1
- package/lib/routes/google/jules.ts +63 -0
- package/lib/routes/gov/mem/namespace.ts +7 -0
- package/lib/routes/gov/mem/zfxxgkpt.ts +96 -0
- package/lib/routes/gov/mot/index.ts +158 -53
- package/lib/routes/hameln/chapter.ts +1 -1
- package/lib/routes/hpoi/banner-item.ts +28 -11
- package/lib/routes/hpoi/info.ts +1 -1
- package/lib/routes/huggingface/daily-papers.ts +1 -1
- package/lib/routes/hupu/index.ts +158 -74
- package/lib/routes/hupu/types.ts +163 -0
- package/lib/routes/hust/gs.ts +1 -1
- package/lib/routes/hust/mse.ts +1 -1
- package/lib/routes/huxiu/util.ts +2 -2
- package/lib/routes/infoq/presentations.ts +1 -1
- package/lib/routes/instagram/common-utils.ts +3 -3
- package/lib/routes/itch/devlog.ts +7 -3
- package/lib/routes/javbus/index.ts +1 -1
- package/lib/routes/javlibrary/utils.ts +1 -1
- package/lib/routes/jbma/namespace.ts +17 -0
- package/lib/routes/jbma/report.ts +473 -0
- package/lib/routes/jetbrains/comments.ts +1 -1
- package/lib/routes/jingzhengu/utils.ts +1 -1
- package/lib/routes/juejin/aicoding.ts +102 -0
- package/lib/routes/juejin/utils.ts +36 -51
- package/lib/routes/kakuyomu/works.ts +1 -1
- package/lib/routes/kemono/index.ts +2 -2
- package/lib/routes/komiic/comic.ts +1 -1
- package/lib/routes/koyso/index.ts +338 -0
- package/lib/routes/koyso/namespace.ts +9 -0
- package/lib/routes/koyso/templates/description.art +13 -0
- package/lib/routes/letterboxd/index.ts +65 -0
- package/lib/routes/letterboxd/namespace.ts +8 -0
- package/lib/routes/maccms/index.ts +1 -1
- package/lib/routes/mercari/util.ts +1 -1
- package/lib/routes/mingpao/index.ts +1 -1
- package/lib/routes/nankai/ai-notice.ts +142 -0
- package/lib/routes/nankai/graduate-notice.ts +162 -0
- package/lib/routes/natgeo/natgeo.ts +1 -0
- package/lib/routes/nhk/news-web-easy.ts +1 -1
- package/lib/routes/nicovideo/mylist.ts +39 -0
- package/lib/routes/nicovideo/types.ts +27 -0
- package/lib/routes/nicovideo/utils.ts +27 -1
- package/lib/routes/nikkei/cn/index.ts +1 -4
- package/lib/routes/now/news.ts +1 -1
- package/lib/routes/nytimes/index.ts +1 -1
- package/lib/routes/pixiv/novel-api/user-novels/sfw.ts +1 -1
- package/lib/routes/pixivision/utils.ts +1 -1
- package/lib/routes/pku/hr.ts +1 -1
- package/lib/routes/pku/scc/recruit.ts +1 -1
- package/lib/routes/producthunt/templates/description.art +2 -2
- package/lib/routes/producthunt/today.ts +17 -8
- package/lib/routes/ps/trophy.ts +1 -1
- package/lib/routes/pubscholar/utils.ts +14 -1
- package/lib/routes/qweather/3days.ts +14 -14
- package/lib/routes/qweather/now.ts +12 -10
- package/lib/routes/qweather/util.tsx +89 -0
- package/lib/routes/qwenlm/blog.ts +75 -0
- package/lib/routes/qwenlm/namespace.ts +6 -0
- package/lib/routes/radio-canada/latest.ts +30 -17
- package/lib/routes/ruc/ai.ts +1 -1
- package/lib/routes/ruc/hr.ts +1 -1
- package/lib/routes/samrdprc/index.ts +241 -0
- package/lib/routes/samrdprc/namespace.ts +1 -1
- package/lib/routes/sdo/ff14risingstones/api.ts +78 -0
- package/lib/routes/sdo/ff14risingstones/constant.ts +338 -0
- package/lib/routes/sdo/ff14risingstones/posts.ts +80 -0
- package/lib/routes/sdo/ff14risingstones/strats.ts +75 -0
- package/lib/routes/sdo/ff14risingstones/templates/duties-party.art +41 -0
- package/lib/routes/sdo/ff14risingstones/templates/fc-party.art +26 -0
- package/lib/routes/sdo/ff14risingstones/templates/novice-network-party.art +9 -0
- package/lib/routes/sdo/ff14risingstones/templates/rp-party.art +15 -0
- package/lib/routes/sdo/ff14risingstones/timeline.ts +31 -0
- package/lib/routes/sdo/ff14risingstones/types/dynamic.ts +50 -0
- package/lib/routes/sdo/ff14risingstones/types/index.ts +3 -0
- package/lib/routes/sdo/ff14risingstones/types/other.ts +57 -0
- package/lib/routes/sdo/ff14risingstones/types/party.ts +111 -0
- package/lib/routes/sdo/ff14risingstones/user-dynamics.ts +32 -0
- package/lib/routes/sdo/ff14risingstones/user-posts.ts +32 -0
- package/lib/routes/sdo/ff14risingstones/user-resently.ts +38 -0
- package/lib/routes/sdo/ff14risingstones/user-strats.ts +32 -0
- package/lib/routes/sdo/ff14risingstones/utils.ts +215 -0
- package/lib/routes/sdo/namespace.ts +7 -0
- package/lib/routes/showstart/utils.ts +1 -1
- package/lib/routes/sohu/mp.ts +1 -1
- package/lib/routes/sotwe/user.ts +1 -1
- package/lib/routes/surfshark/blog.ts +273 -77
- package/lib/routes/surfshark/templates/description.art +16 -4
- package/lib/routes/sustainabilitymag/articles.ts +1 -1
- package/lib/routes/syosetu/dev.ts +1 -1
- package/lib/routes/syosetu/ranking-isekai.ts +1 -1
- package/lib/routes/szse/disclosure/listed-notice.ts +44 -6
- package/lib/routes/telegram/channel-media.ts +249 -0
- package/lib/routes/telegram/channel.ts +5 -4
- package/lib/routes/telegram/stories.ts +130 -0
- package/lib/routes/telegram/tglib/channel.ts +136 -118
- package/lib/routes/telegram/tglib/client.ts +37 -139
- package/lib/routes/tesla/cx.ts +1 -1
- package/lib/routes/theverge/index.ts +20 -6
- package/lib/routes/threads/utils.ts +7 -3
- package/lib/routes/tidb/blog.ts +1 -1
- package/lib/routes/toutiao/user.ts +2 -2
- package/lib/routes/twitter/api/mobile-api/api.ts +1 -1
- package/lib/routes/txrjy/fornumtopic.ts +2 -2
- package/lib/routes/typst/universe.ts +1 -1
- package/lib/routes/uber/blog.ts +87 -46
- package/lib/routes/weibo/utils.ts +17 -9
- package/lib/routes/xiaohongshu/user.ts +1 -1
- package/lib/routes/xjtu/ee-jzxx.ts +1 -1
- package/lib/routes/yahoo/news/utils.ts +1 -1
- package/lib/routes/ymgal/article.ts +1 -1
- package/lib/routes/yoasobi-music/media.ts +1 -1
- package/lib/routes/youtube/api/youtubei.ts +1 -1
- package/lib/routes/youtube/community.ts +4 -4
- package/lib/routes/zaker/utils.ts +1 -1
- package/lib/routes/zaobao/util.tsx +1 -1
- package/lib/server.ts +1 -1
- package/lib/types.ts +1 -1
- package/lib/utils/puppeteer-utils.test.ts +2 -2
- package/lib/views/index.tsx +4 -4
- package/package.json +40 -40
- package/lib/routes/qweather/templates/3days.art +0 -22
- package/lib/routes/qweather/templates/now.art +0 -16
- package/lib/routes/xinhuanet/app.ts +0 -109
package/lib/routes/hupu/index.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { Route } from '@/types';
|
|
1
|
+
import { Route, Data, DataItem } from '@/types';
|
|
2
2
|
import cache from '@/utils/cache';
|
|
3
3
|
import got from '@/utils/got';
|
|
4
4
|
import { load } from 'cheerio';
|
|
5
5
|
import timezone from '@/utils/timezone';
|
|
6
|
-
import { parseDate } from '@/utils/parse-date';
|
|
6
|
+
import { parseDate, parseRelativeDate } from '@/utils/parse-date';
|
|
7
|
+
import { type HupuApiResponse, type HomePostItem, type NewsDataItem, isHomePostItem } from './types';
|
|
7
8
|
|
|
8
9
|
const categories = {
|
|
9
10
|
nba: {
|
|
@@ -18,85 +19,168 @@ const categories = {
|
|
|
18
19
|
title: '足球',
|
|
19
20
|
data: 'news',
|
|
20
21
|
},
|
|
21
|
-
|
|
22
|
+
'': {
|
|
23
|
+
title: '首页',
|
|
24
|
+
data: 'res',
|
|
25
|
+
},
|
|
26
|
+
} as const;
|
|
22
27
|
|
|
23
28
|
export const route: Route = {
|
|
24
29
|
path: ['/dept/:category?', '/:category?'],
|
|
30
|
+
name: '手机虎扑网',
|
|
31
|
+
url: 'm.hupu.com',
|
|
32
|
+
maintainers: ['nczitzk', 'hyoban'],
|
|
33
|
+
example: '/hupu/nba',
|
|
34
|
+
parameters: {
|
|
35
|
+
category: {
|
|
36
|
+
description: '分类,可选值:nba、cba、soccer,默认为空(首页)',
|
|
37
|
+
default: '',
|
|
38
|
+
options: Object.entries(categories).map(([key, value]) => ({
|
|
39
|
+
label: value.title,
|
|
40
|
+
value: key,
|
|
41
|
+
})),
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
description: `::: tip
|
|
45
|
+
电竞分类参见 [游戏热帖](https://bbs.hupu.com/all-gg) 的对应路由 [\`/hupu/all/all-gg\`](https://rsshub.app/hupu/all/all-gg)。
|
|
46
|
+
:::`,
|
|
47
|
+
categories: ['bbs'],
|
|
25
48
|
radar: [
|
|
26
49
|
{
|
|
27
50
|
source: ['m.hupu.com/:category', 'm.hupu.com/'],
|
|
28
51
|
target: '/:category',
|
|
29
52
|
},
|
|
30
53
|
],
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
54
|
+
handler: async (ctx): Promise<Data> => {
|
|
55
|
+
const c = ctx.req.param('category') || '';
|
|
56
|
+
if (!(c in categories)) {
|
|
57
|
+
throw new Error('Invalid category. Valid options are: ' + Object.keys(categories).filter(Boolean).join(', '));
|
|
58
|
+
}
|
|
59
|
+
const category = c as keyof typeof categories;
|
|
60
|
+
|
|
61
|
+
const rootUrl = 'https://m.hupu.com';
|
|
62
|
+
const currentUrl = `${rootUrl}/${category}`;
|
|
63
|
+
|
|
64
|
+
const response = await got({
|
|
65
|
+
method: 'get',
|
|
66
|
+
url: currentUrl,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const scriptMatch = response.data.match(/<script id="__NEXT_DATA__" type="application\/json">(.*?)<\/script>/);
|
|
70
|
+
if (!scriptMatch || !scriptMatch[1]) {
|
|
71
|
+
throw new Error(`Failed to find __NEXT_DATA__ script tag in page: ${currentUrl}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const fullJsonString = scriptMatch[1];
|
|
75
|
+
let fullData;
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
fullData = JSON.parse(fullJsonString);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
throw new Error(`Failed to parse full JSON data: ${error instanceof Error ? error.message : String(error)}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const data: HupuApiResponse = fullData;
|
|
84
|
+
const { pageProps } = data.props;
|
|
85
|
+
|
|
86
|
+
const dataKey = categories[category].data;
|
|
87
|
+
if (!(dataKey in pageProps)) {
|
|
88
|
+
throw new Error(`Expected '${dataKey}' property not found in pageProps for category: ${category || 'home'}`);
|
|
89
|
+
}
|
|
42
90
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
.
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
91
|
+
const rawDataArray: (HomePostItem | NewsDataItem)[] = (() => {
|
|
92
|
+
const data = (pageProps as any)[dataKey];
|
|
93
|
+
return Array.isArray(data) ? data : [];
|
|
94
|
+
})();
|
|
95
|
+
|
|
96
|
+
let items: DataItem[] = rawDataArray.map((item) =>
|
|
97
|
+
isHomePostItem(item)
|
|
98
|
+
? ({
|
|
99
|
+
title: item.title,
|
|
100
|
+
link: item.url.replace(/bbs\.hupu.com/, 'm.hupu.com/bbs'),
|
|
101
|
+
guid: item.tid,
|
|
102
|
+
category: item.label ? [item.label] : undefined,
|
|
103
|
+
} satisfies DataItem)
|
|
104
|
+
: ({
|
|
105
|
+
title: item.title,
|
|
106
|
+
pubDate: timezone(parseDate(item.publishTime), +8),
|
|
107
|
+
link: item.link.replace(/bbs\.hupu.com/, 'm.hupu.com/bbs'),
|
|
108
|
+
guid: item.tid,
|
|
109
|
+
} satisfies DataItem)
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
items = await Promise.all(
|
|
113
|
+
items
|
|
114
|
+
.filter((item) => item.link && !/subject/.test(item.link))
|
|
115
|
+
.map((item) =>
|
|
116
|
+
cache.tryGet(item.link!, async () => {
|
|
117
|
+
try {
|
|
118
|
+
const detailResponse = await got({
|
|
119
|
+
method: 'get',
|
|
120
|
+
url: item.link,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const content = load(detailResponse.data);
|
|
124
|
+
|
|
125
|
+
const author = content('.bbs-user-info-name, .bbs-user-wrapper-content-name-span').text();
|
|
126
|
+
const pubDateString = content('.second-line-user-info span:not([class])').text();
|
|
127
|
+
// Possible formats: 10:21, 45分钟前, 09-15 19:57
|
|
128
|
+
const pubDate = [item.pubDate, timezone(parseDate(pubDateString), +8), timezone(parseRelativeDate(pubDateString), +8), timezone(parseRelativeDate(`Today ${pubDateString}`), +8)].find(
|
|
129
|
+
(d) => d instanceof Date && !Number.isNaN(d.getTime())
|
|
130
|
+
);
|
|
131
|
+
const categories = content('.basketballTobbs_tag > a, .tag-player-team')
|
|
132
|
+
.toArray()
|
|
133
|
+
.map((c) => content(c).text())
|
|
134
|
+
.filter(Boolean);
|
|
135
|
+
|
|
136
|
+
content('.basketballTobbs_tag').remove();
|
|
137
|
+
content('.hupu-img').each(function () {
|
|
138
|
+
const imgSrc = content(this).attr('data-gif') || content(this).attr('data-origin') || content(this).attr('src');
|
|
139
|
+
content(this).parent().html(`<img src="${imgSrc}">`);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// 分别获取内容元素
|
|
143
|
+
const descriptionParts: string[] = [];
|
|
144
|
+
|
|
145
|
+
// 获取主要内容
|
|
146
|
+
const mainContent = content('#bbs-thread-content, .bbs-content-font').html();
|
|
147
|
+
if (mainContent) {
|
|
148
|
+
descriptionParts.push(mainContent);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// 单独处理视频部分
|
|
152
|
+
const videoWrapper = content('.header-video-wrapper');
|
|
153
|
+
if (videoWrapper.length > 0) {
|
|
154
|
+
const videoElement = videoWrapper.find('video');
|
|
155
|
+
if (videoElement.length > 0) {
|
|
156
|
+
const videoHtml = videoElement.prop('outerHTML');
|
|
157
|
+
if (videoHtml) {
|
|
158
|
+
descriptionParts.push(videoHtml);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const description = descriptionParts.length > 0 ? descriptionParts.join('') : undefined;
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
...item,
|
|
167
|
+
author,
|
|
168
|
+
category: categories.length > 0 ? categories : item.category,
|
|
169
|
+
description,
|
|
170
|
+
pubDate,
|
|
171
|
+
};
|
|
172
|
+
} catch {
|
|
173
|
+
// no-empty
|
|
174
|
+
return item;
|
|
175
|
+
}
|
|
176
|
+
})
|
|
177
|
+
)
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
title: `虎扑 - ${categories[category].title}`,
|
|
182
|
+
link: currentUrl,
|
|
183
|
+
item: items,
|
|
184
|
+
} as Data;
|
|
185
|
+
},
|
|
186
|
+
};
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
// 虎扑 API 响应数据类型定义
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 徽章信息
|
|
5
|
+
*/
|
|
6
|
+
interface Badge {
|
|
7
|
+
name: string;
|
|
8
|
+
color: string;
|
|
9
|
+
v2DayColor: string;
|
|
10
|
+
v2NightColor: string;
|
|
11
|
+
relationUrl: string | null;
|
|
12
|
+
colorBg: string;
|
|
13
|
+
v2DayColorBg: string;
|
|
14
|
+
v2NightColorBg: string;
|
|
15
|
+
iconDay: string;
|
|
16
|
+
iconNight: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 首页帖子数据项(res 数组中的项)
|
|
21
|
+
*/
|
|
22
|
+
interface HomePostItem {
|
|
23
|
+
tid: string;
|
|
24
|
+
title: string;
|
|
25
|
+
url: string;
|
|
26
|
+
label: string;
|
|
27
|
+
lights: string;
|
|
28
|
+
replies: string;
|
|
29
|
+
type: string; // e.g., "3_pic", "1_pic", "video"
|
|
30
|
+
source: string[];
|
|
31
|
+
isNews: boolean;
|
|
32
|
+
badge: Badge[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 新闻/分类页面数据项(newsData 数组中的项)
|
|
37
|
+
*/
|
|
38
|
+
interface NewsDataItem {
|
|
39
|
+
nid: string;
|
|
40
|
+
title: string;
|
|
41
|
+
img: string;
|
|
42
|
+
link: string;
|
|
43
|
+
badge: Badge[];
|
|
44
|
+
type: string; // e.g., "LINK", "IMG_TEXT"
|
|
45
|
+
lights: number;
|
|
46
|
+
replies: number;
|
|
47
|
+
publishTime: string;
|
|
48
|
+
tid: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 推荐比赛 Toast 信息
|
|
53
|
+
*/
|
|
54
|
+
interface RecommendMatchToast {
|
|
55
|
+
date: string;
|
|
56
|
+
matchCount: number;
|
|
57
|
+
title: string;
|
|
58
|
+
toastTitle: string | null;
|
|
59
|
+
foldTitle: string;
|
|
60
|
+
matchListLink: string | null;
|
|
61
|
+
matchCountText: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 推荐比赛信息
|
|
66
|
+
*/
|
|
67
|
+
interface RecommendMatch {
|
|
68
|
+
projectId: string | null;
|
|
69
|
+
version: string | null;
|
|
70
|
+
traceId: string | null;
|
|
71
|
+
matchList: any[];
|
|
72
|
+
events: any[];
|
|
73
|
+
toast: RecommendMatchToast;
|
|
74
|
+
success: boolean;
|
|
75
|
+
is_login: number;
|
|
76
|
+
is_jrs: boolean;
|
|
77
|
+
night: number;
|
|
78
|
+
client: string | null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* 首页页面属性
|
|
83
|
+
*/
|
|
84
|
+
interface HomePageProps {
|
|
85
|
+
res: HomePostItem[];
|
|
86
|
+
totalNum: number;
|
|
87
|
+
supportHydrate: boolean;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* 篮球分类页面属性 (NBA/CBA)
|
|
92
|
+
*/
|
|
93
|
+
interface BasketballPageProps {
|
|
94
|
+
leagueType: string;
|
|
95
|
+
newsData: NewsDataItem[];
|
|
96
|
+
recommendMatch: RecommendMatch;
|
|
97
|
+
supportHydrate: boolean;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* 足球分类页面属性
|
|
102
|
+
*/
|
|
103
|
+
interface SoccerPageProps {
|
|
104
|
+
schedules: any[];
|
|
105
|
+
news: NewsDataItem[];
|
|
106
|
+
supportHydrate: boolean;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* 所有分类页面属性的联合类型
|
|
111
|
+
*/
|
|
112
|
+
type CategoryPageProps = BasketballPageProps | SoccerPageProps;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* API 响应的 props 结构
|
|
116
|
+
*/
|
|
117
|
+
interface ApiResponseProps {
|
|
118
|
+
pageProps: HomePageProps | CategoryPageProps;
|
|
119
|
+
__N_SSP: boolean;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* 完整的 API 响应结构
|
|
124
|
+
*/
|
|
125
|
+
export interface HupuApiResponse {
|
|
126
|
+
props: ApiResponseProps;
|
|
127
|
+
page: string;
|
|
128
|
+
query: Record<string, any>;
|
|
129
|
+
buildId: string;
|
|
130
|
+
assetPrefix: string;
|
|
131
|
+
isFallback: boolean;
|
|
132
|
+
gssp: boolean;
|
|
133
|
+
scriptLoader: any[];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* 用于区分不同页面类型的类型守卫
|
|
138
|
+
*/
|
|
139
|
+
export function isHomePageProps(pageProps: HomePageProps | CategoryPageProps): pageProps is HomePageProps {
|
|
140
|
+
return 'res' in pageProps;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function isBasketballPageProps(pageProps: CategoryPageProps): pageProps is BasketballPageProps {
|
|
144
|
+
return 'newsData' in pageProps && 'leagueType' in pageProps;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function isSoccerPageProps(pageProps: CategoryPageProps): pageProps is SoccerPageProps {
|
|
148
|
+
return 'news' in pageProps && 'schedules' in pageProps;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* 用于区分不同数据项类型的类型守卫
|
|
153
|
+
*/
|
|
154
|
+
export function isHomePostItem(item: HomePostItem | NewsDataItem): item is HomePostItem {
|
|
155
|
+
return 'url' in item && 'isNews' in item;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function isNewsDataItem(item: HomePostItem | NewsDataItem): item is NewsDataItem {
|
|
159
|
+
return 'nid' in item && 'link' in item && 'publishTime' in item;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// 导出具体类型以供外部使用
|
|
163
|
+
export type { Badge, HomePostItem, NewsDataItem, RecommendMatch, HomePageProps, BasketballPageProps, SoccerPageProps, CategoryPageProps, ApiResponseProps };
|
package/lib/routes/hust/gs.ts
CHANGED
|
@@ -57,7 +57,7 @@ export const handler = async (ctx) => {
|
|
|
57
57
|
)
|
|
58
58
|
);
|
|
59
59
|
|
|
60
|
-
const title = $('meta[name="keywords"]').prop('content')?.
|
|
60
|
+
const title = $('meta[name="keywords"]').prop('content')?.replaceAll(',', ' - ') ?? $('title').text();
|
|
61
61
|
const image = new URL($('div.logo img').prop('src'), rootUrl).href;
|
|
62
62
|
|
|
63
63
|
return {
|
package/lib/routes/hust/mse.ts
CHANGED
|
@@ -75,7 +75,7 @@ export const handler = async (ctx) => {
|
|
|
75
75
|
)
|
|
76
76
|
);
|
|
77
77
|
|
|
78
|
-
const title = $('meta[name="keywords"]').prop('content')?.
|
|
78
|
+
const title = $('meta[name="keywords"]').prop('content')?.replaceAll(',', ' - ') ?? $('title').text();
|
|
79
79
|
const image = new URL($('div.logo img').prop('src'), rootUrl).href;
|
|
80
80
|
|
|
81
81
|
return {
|
package/lib/routes/huxiu/util.ts
CHANGED
|
@@ -257,7 +257,7 @@ const generateSignature = () => {
|
|
|
257
257
|
|
|
258
258
|
const appSecret = 'hUzaABtNfDE-6UiyaYhfsmjW-8dnoyVc';
|
|
259
259
|
const nonce = generateNonce();
|
|
260
|
-
const r = [appSecret, timestamp, nonce].
|
|
260
|
+
const r = [appSecret, timestamp, nonce].toSorted();
|
|
261
261
|
return {
|
|
262
262
|
nonce,
|
|
263
263
|
timestamp,
|
|
@@ -363,7 +363,7 @@ const processItems = async (items, limit, tryGet) => {
|
|
|
363
363
|
return {
|
|
364
364
|
...audioItem,
|
|
365
365
|
...videoItem,
|
|
366
|
-
title: (item.title ?? item.summary ?? item.content)?.
|
|
366
|
+
title: (item.title ?? item.summary ?? item.content)?.replaceAll(/<\/?(?:em|br)?>/g, ''),
|
|
367
367
|
link,
|
|
368
368
|
description: art(path.join(__dirname, 'templates/description.art'), {
|
|
369
369
|
image: {
|
|
@@ -90,7 +90,7 @@ export const handler = async (ctx) => {
|
|
|
90
90
|
const script = $$('script[type="text/javascript"]').text();
|
|
91
91
|
const videoSrc = script.match(/P\.s\s=\s'(.*?)';/)?.[1] ?? undefined;
|
|
92
92
|
const poster = script.match(/P\.c\(.*?isWideScreen,\s'(.*?)',\s/)?.[1] ?? undefined;
|
|
93
|
-
const topicsStr = script.match(/var\stopicsInPage\s=\sJSON\.parse\('(.*?)'\);/)?.[1]?.
|
|
93
|
+
const topicsStr = script.match(/var\stopicsInPage\s=\sJSON\.parse\('(.*?)'\);/)?.[1]?.replaceAll('\\', '') ?? undefined;
|
|
94
94
|
|
|
95
95
|
if (videoSrc) {
|
|
96
96
|
$$('div.player').replaceWith(
|
|
@@ -12,7 +12,7 @@ const renderItems = (items) =>
|
|
|
12
12
|
switch (productType) {
|
|
13
13
|
case 'carousel_container': {
|
|
14
14
|
const images = item.carousel_media.map((i) => ({
|
|
15
|
-
...i.image_versions2.candidates.
|
|
15
|
+
...i.image_versions2.candidates.toSorted((a, b) => b.width - a.width)[0],
|
|
16
16
|
alt: item.accessibility_caption,
|
|
17
17
|
}));
|
|
18
18
|
description = art(path.join(__dirname, 'templates/images.art'), {
|
|
@@ -25,12 +25,12 @@ const renderItems = (items) =>
|
|
|
25
25
|
case 'igtv':
|
|
26
26
|
description = art(path.join(__dirname, 'templates/video.art'), {
|
|
27
27
|
summary,
|
|
28
|
-
image: item.image_versions2.candidates.
|
|
28
|
+
image: item.image_versions2.candidates.toSorted((a, b) => b.width - a.width)[0],
|
|
29
29
|
video: item.video_versions[0],
|
|
30
30
|
});
|
|
31
31
|
break;
|
|
32
32
|
case 'feed': {
|
|
33
|
-
const images = [{ ...item.image_versions2.candidates.
|
|
33
|
+
const images = [{ ...item.image_versions2.candidates.toSorted((a, b) => b.width - a.width)[0], alt: item.accessibility_caption }];
|
|
34
34
|
description = art(path.join(__dirname, 'templates/images.art'), {
|
|
35
35
|
summary,
|
|
36
36
|
images,
|
|
@@ -36,6 +36,7 @@ export const route: Route = {
|
|
|
36
36
|
async function handler(ctx) {
|
|
37
37
|
const user = ctx.req.param('user') ?? '';
|
|
38
38
|
const id = ctx.req.param('id') ?? '';
|
|
39
|
+
const limit = Number.parseInt(ctx.req.query('limit')) || 20;
|
|
39
40
|
if (!isValidHost(user)) {
|
|
40
41
|
throw new InvalidParameterError('Invalid user');
|
|
41
42
|
}
|
|
@@ -51,6 +52,7 @@ async function handler(ctx) {
|
|
|
51
52
|
|
|
52
53
|
let items = $('.title')
|
|
53
54
|
.toArray()
|
|
55
|
+
.slice(0, limit)
|
|
54
56
|
.map((item) => {
|
|
55
57
|
item = $(item);
|
|
56
58
|
|
|
@@ -71,12 +73,14 @@ async function handler(ctx) {
|
|
|
71
73
|
|
|
72
74
|
const content = load(detailResponse.data);
|
|
73
75
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
+
const infoJson = content('script[type="application/ld+json"]:contains("@type":"BlogPosting")');
|
|
77
|
+
const info = JSON.parse(content(infoJson).text());
|
|
78
|
+
item.author = info.author.name;
|
|
79
|
+
item.pubDate = info.datePublished;
|
|
76
80
|
item.description = art(path.join(__dirname, 'templates/description.art'), {
|
|
77
81
|
images: content('.post_image')
|
|
78
82
|
.toArray()
|
|
79
|
-
.map((
|
|
83
|
+
.map((e) => content(e).attr('src')),
|
|
80
84
|
description: content('.post_body').html(),
|
|
81
85
|
});
|
|
82
86
|
|
|
@@ -158,7 +158,7 @@ async function handler(ctx) {
|
|
|
158
158
|
});
|
|
159
159
|
|
|
160
160
|
if (magnets) {
|
|
161
|
-
item.enclosure_url = magnets.
|
|
161
|
+
item.enclosure_url = magnets.toSorted((a, b) => b.score - a.score)[0].link;
|
|
162
162
|
item.enclosure_type = 'application/x-bittorrent';
|
|
163
163
|
}
|
|
164
164
|
} catch {
|
|
@@ -68,7 +68,7 @@ const ProcessItems = async (language, currentUrl, tryGet) => {
|
|
|
68
68
|
item.description = art(path.join(__dirname, 'templates/description.art'), {
|
|
69
69
|
cover: content('#video_jacket_img').attr('src'),
|
|
70
70
|
info: content('#video_info').html().replaceAll('span><span', 'span>, <span'),
|
|
71
|
-
comment: item.description?.
|
|
71
|
+
comment: item.description?.replaceAll('[img]', '<img src="')?.replaceAll('[/img]', '"/>'),
|
|
72
72
|
thumbs: content('.previewthumbs img')
|
|
73
73
|
.toArray()
|
|
74
74
|
.map((img) => content(img).attr('src').replaceAll('-', 'jp-')),
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Namespace } from '@/types';
|
|
2
|
+
|
|
3
|
+
export const namespace: Namespace = {
|
|
4
|
+
name: 'Japan Bullion Market Association',
|
|
5
|
+
url: 'jbma.net',
|
|
6
|
+
categories: ['new-media'],
|
|
7
|
+
description: '',
|
|
8
|
+
lang: 'ja',
|
|
9
|
+
ja: {
|
|
10
|
+
name: '日本貴金属マーケット協会',
|
|
11
|
+
description: '',
|
|
12
|
+
},
|
|
13
|
+
zh: {
|
|
14
|
+
name: '日本贵金属市场协会',
|
|
15
|
+
description: '',
|
|
16
|
+
},
|
|
17
|
+
};
|