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.
Files changed (166) hide show
  1. package/lib/config.ts +10 -0
  2. package/lib/middleware/cache.ts +4 -0
  3. package/lib/middleware/parameter.ts +1 -1
  4. package/lib/registry.ts +6 -2
  5. package/lib/routes/51cto/utils.ts +1 -1
  6. package/lib/routes/abc/index.ts +1 -1
  7. package/lib/routes/acgvinyl/namespace.ts +6 -0
  8. package/lib/routes/acgvinyl/news.ts +86 -0
  9. package/lib/routes/ally/rail.ts +1 -1
  10. package/lib/routes/anthropic/news.ts +13 -11
  11. package/lib/routes/apnews/mobile-api.ts +1 -1
  12. package/lib/routes/apnews/sitemap.ts +1 -1
  13. package/lib/routes/apple/apps.ts +2 -2
  14. package/lib/routes/apple/podcast.ts +58 -25
  15. package/lib/routes/bangumi.tv/group/reply.ts +1 -1
  16. package/lib/routes/bilibili/cache.ts +18 -2
  17. package/lib/routes/bilibili/dynamic.ts +31 -7
  18. package/lib/routes/bilibili/page.ts +1 -1
  19. package/lib/routes/bilibili/ranking.ts +23 -17
  20. package/lib/routes/bilibili/wasm-exec.ts +1 -1
  21. package/lib/routes/bilibili/weekly-recommend.ts +22 -6
  22. package/lib/routes/bjp/apod.ts +1 -1
  23. package/lib/routes/buaa/jiaowu.ts +1 -1
  24. package/lib/routes/bullionvault/gold-news.ts +3 -3
  25. package/lib/routes/cast/index.ts +1 -1
  26. package/lib/routes/coolapk/utils.ts +5 -4
  27. package/lib/routes/coolbuy/index.ts +106 -0
  28. package/lib/routes/{xinhuanet → coolbuy}/namespace.ts +3 -3
  29. package/lib/routes/coolbuy/templates/description.art +48 -0
  30. package/lib/routes/coolidge/film-guide.ts +60 -0
  31. package/lib/routes/coolidge/namespace.ts +7 -0
  32. package/lib/routes/coolidge/news.ts +65 -0
  33. package/lib/routes/coolidge/templates/description.art +4 -0
  34. package/lib/routes/copymanga/comic.ts +1 -1
  35. package/lib/routes/cpta/handler.ts +1 -1
  36. package/lib/routes/creative-comic/book.ts +1 -1
  37. package/lib/routes/daum/potplayer.ts +1 -1
  38. package/lib/routes/dockerhub/utils.ts +1 -1
  39. package/lib/routes/dora-world/article.ts +2 -2
  40. package/lib/routes/ehentai/ehapi.ts +3 -3
  41. package/lib/routes/eventbrite/events.ts +152 -0
  42. package/lib/routes/eventbrite/namespace.ts +7 -0
  43. package/lib/routes/github/activity.ts +1 -1
  44. package/lib/routes/google/jules.ts +63 -0
  45. package/lib/routes/gov/mem/namespace.ts +7 -0
  46. package/lib/routes/gov/mem/zfxxgkpt.ts +96 -0
  47. package/lib/routes/gov/mot/index.ts +158 -53
  48. package/lib/routes/hameln/chapter.ts +1 -1
  49. package/lib/routes/hpoi/banner-item.ts +28 -11
  50. package/lib/routes/hpoi/info.ts +1 -1
  51. package/lib/routes/huggingface/daily-papers.ts +1 -1
  52. package/lib/routes/hupu/index.ts +158 -74
  53. package/lib/routes/hupu/types.ts +163 -0
  54. package/lib/routes/hust/gs.ts +1 -1
  55. package/lib/routes/hust/mse.ts +1 -1
  56. package/lib/routes/huxiu/util.ts +2 -2
  57. package/lib/routes/infoq/presentations.ts +1 -1
  58. package/lib/routes/instagram/common-utils.ts +3 -3
  59. package/lib/routes/itch/devlog.ts +7 -3
  60. package/lib/routes/javbus/index.ts +1 -1
  61. package/lib/routes/javlibrary/utils.ts +1 -1
  62. package/lib/routes/jbma/namespace.ts +17 -0
  63. package/lib/routes/jbma/report.ts +473 -0
  64. package/lib/routes/jetbrains/comments.ts +1 -1
  65. package/lib/routes/jingzhengu/utils.ts +1 -1
  66. package/lib/routes/juejin/aicoding.ts +102 -0
  67. package/lib/routes/juejin/utils.ts +36 -51
  68. package/lib/routes/kakuyomu/works.ts +1 -1
  69. package/lib/routes/kemono/index.ts +2 -2
  70. package/lib/routes/komiic/comic.ts +1 -1
  71. package/lib/routes/koyso/index.ts +338 -0
  72. package/lib/routes/koyso/namespace.ts +9 -0
  73. package/lib/routes/koyso/templates/description.art +13 -0
  74. package/lib/routes/letterboxd/index.ts +65 -0
  75. package/lib/routes/letterboxd/namespace.ts +8 -0
  76. package/lib/routes/maccms/index.ts +1 -1
  77. package/lib/routes/mercari/util.ts +1 -1
  78. package/lib/routes/mingpao/index.ts +1 -1
  79. package/lib/routes/nankai/ai-notice.ts +142 -0
  80. package/lib/routes/nankai/graduate-notice.ts +162 -0
  81. package/lib/routes/natgeo/natgeo.ts +1 -0
  82. package/lib/routes/nhk/news-web-easy.ts +1 -1
  83. package/lib/routes/nicovideo/mylist.ts +39 -0
  84. package/lib/routes/nicovideo/types.ts +27 -0
  85. package/lib/routes/nicovideo/utils.ts +27 -1
  86. package/lib/routes/nikkei/cn/index.ts +1 -4
  87. package/lib/routes/now/news.ts +1 -1
  88. package/lib/routes/nytimes/index.ts +1 -1
  89. package/lib/routes/pixiv/novel-api/user-novels/sfw.ts +1 -1
  90. package/lib/routes/pixivision/utils.ts +1 -1
  91. package/lib/routes/pku/hr.ts +1 -1
  92. package/lib/routes/pku/scc/recruit.ts +1 -1
  93. package/lib/routes/producthunt/templates/description.art +2 -2
  94. package/lib/routes/producthunt/today.ts +17 -8
  95. package/lib/routes/ps/trophy.ts +1 -1
  96. package/lib/routes/pubscholar/utils.ts +14 -1
  97. package/lib/routes/qweather/3days.ts +14 -14
  98. package/lib/routes/qweather/now.ts +12 -10
  99. package/lib/routes/qweather/util.tsx +89 -0
  100. package/lib/routes/qwenlm/blog.ts +75 -0
  101. package/lib/routes/qwenlm/namespace.ts +6 -0
  102. package/lib/routes/radio-canada/latest.ts +30 -17
  103. package/lib/routes/ruc/ai.ts +1 -1
  104. package/lib/routes/ruc/hr.ts +1 -1
  105. package/lib/routes/samrdprc/index.ts +241 -0
  106. package/lib/routes/samrdprc/namespace.ts +1 -1
  107. package/lib/routes/sdo/ff14risingstones/api.ts +78 -0
  108. package/lib/routes/sdo/ff14risingstones/constant.ts +338 -0
  109. package/lib/routes/sdo/ff14risingstones/posts.ts +80 -0
  110. package/lib/routes/sdo/ff14risingstones/strats.ts +75 -0
  111. package/lib/routes/sdo/ff14risingstones/templates/duties-party.art +41 -0
  112. package/lib/routes/sdo/ff14risingstones/templates/fc-party.art +26 -0
  113. package/lib/routes/sdo/ff14risingstones/templates/novice-network-party.art +9 -0
  114. package/lib/routes/sdo/ff14risingstones/templates/rp-party.art +15 -0
  115. package/lib/routes/sdo/ff14risingstones/timeline.ts +31 -0
  116. package/lib/routes/sdo/ff14risingstones/types/dynamic.ts +50 -0
  117. package/lib/routes/sdo/ff14risingstones/types/index.ts +3 -0
  118. package/lib/routes/sdo/ff14risingstones/types/other.ts +57 -0
  119. package/lib/routes/sdo/ff14risingstones/types/party.ts +111 -0
  120. package/lib/routes/sdo/ff14risingstones/user-dynamics.ts +32 -0
  121. package/lib/routes/sdo/ff14risingstones/user-posts.ts +32 -0
  122. package/lib/routes/sdo/ff14risingstones/user-resently.ts +38 -0
  123. package/lib/routes/sdo/ff14risingstones/user-strats.ts +32 -0
  124. package/lib/routes/sdo/ff14risingstones/utils.ts +215 -0
  125. package/lib/routes/sdo/namespace.ts +7 -0
  126. package/lib/routes/showstart/utils.ts +1 -1
  127. package/lib/routes/sohu/mp.ts +1 -1
  128. package/lib/routes/sotwe/user.ts +1 -1
  129. package/lib/routes/surfshark/blog.ts +273 -77
  130. package/lib/routes/surfshark/templates/description.art +16 -4
  131. package/lib/routes/sustainabilitymag/articles.ts +1 -1
  132. package/lib/routes/syosetu/dev.ts +1 -1
  133. package/lib/routes/syosetu/ranking-isekai.ts +1 -1
  134. package/lib/routes/szse/disclosure/listed-notice.ts +44 -6
  135. package/lib/routes/telegram/channel-media.ts +249 -0
  136. package/lib/routes/telegram/channel.ts +5 -4
  137. package/lib/routes/telegram/stories.ts +130 -0
  138. package/lib/routes/telegram/tglib/channel.ts +136 -118
  139. package/lib/routes/telegram/tglib/client.ts +37 -139
  140. package/lib/routes/tesla/cx.ts +1 -1
  141. package/lib/routes/theverge/index.ts +20 -6
  142. package/lib/routes/threads/utils.ts +7 -3
  143. package/lib/routes/tidb/blog.ts +1 -1
  144. package/lib/routes/toutiao/user.ts +2 -2
  145. package/lib/routes/twitter/api/mobile-api/api.ts +1 -1
  146. package/lib/routes/txrjy/fornumtopic.ts +2 -2
  147. package/lib/routes/typst/universe.ts +1 -1
  148. package/lib/routes/uber/blog.ts +87 -46
  149. package/lib/routes/weibo/utils.ts +17 -9
  150. package/lib/routes/xiaohongshu/user.ts +1 -1
  151. package/lib/routes/xjtu/ee-jzxx.ts +1 -1
  152. package/lib/routes/yahoo/news/utils.ts +1 -1
  153. package/lib/routes/ymgal/article.ts +1 -1
  154. package/lib/routes/yoasobi-music/media.ts +1 -1
  155. package/lib/routes/youtube/api/youtubei.ts +1 -1
  156. package/lib/routes/youtube/community.ts +4 -4
  157. package/lib/routes/zaker/utils.ts +1 -1
  158. package/lib/routes/zaobao/util.tsx +1 -1
  159. package/lib/server.ts +1 -1
  160. package/lib/types.ts +1 -1
  161. package/lib/utils/puppeteer-utils.test.ts +2 -2
  162. package/lib/views/index.tsx +4 -4
  163. package/package.json +40 -40
  164. package/lib/routes/qweather/templates/3days.art +0 -22
  165. package/lib/routes/qweather/templates/now.art +0 -16
  166. package/lib/routes/xinhuanet/app.ts +0 -109
@@ -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
- name: 'Unknown',
32
- maintainers: ['nczitzk'],
33
- handler,
34
- description: `| NBA | CBA | 足球 |
35
- | --- | --- | ------ |
36
- | nba | cba | soccer |
37
-
38
- ::: tip
39
- 电竞分类参见 [游戏热帖](https://bbs.hupu.com/all-gg) 的对应路由 [\`/hupu/all/all-gg\`](https://rsshub.app/hupu/all/all-gg)。
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
- async function handler(ctx) {
44
- const category = ctx.req.param('category') ?? 'soccer';
45
-
46
- const rootUrl = 'https://m.hupu.com';
47
- const currentUrl = `${rootUrl}/${category}`;
48
-
49
- const response = await got({
50
- method: 'get',
51
- url: currentUrl,
52
- });
53
-
54
- const data = JSON.parse(response.data.match(/"props":(.*),"page":"\//)[1]);
55
-
56
- let items = data.pageProps[categories[category].data].map((item) => ({
57
- title: item.title,
58
- pubDate: timezone(parseDate(item.publishTime), +8),
59
- link: item.link.replace(/bbs\.hupu.com/, 'm.hupu.com/bbs'),
60
- }));
61
-
62
- items = await Promise.all(
63
- items
64
- .filter((item) => !/subject/.test(item.link))
65
- .map((item) =>
66
- cache.tryGet(item.link, async () => {
67
- try {
68
- const detailResponse = await got({
69
- method: 'get',
70
- url: item.link,
71
- });
72
-
73
- const content = load(detailResponse.data);
74
-
75
- item.author = content('.bbs-user-info-name, .bbs-user-wrapper-content-name-span').text();
76
- item.category = content('.basketballTobbs_tag > a, .tag-player-team')
77
- .toArray()
78
- .map((c) => content(c).text());
79
-
80
- content('.basketballTobbs_tag').remove();
81
- content('.hupu-img').each(function () {
82
- content(this)
83
- .parent()
84
- .html(`<img src="${content(this).attr('data-origin')}">`);
85
- });
86
-
87
- item.description = content('#bbs-thread-content, .bbs-content-font').html();
88
- } catch {
89
- // no-empty
90
- }
91
-
92
- return item;
93
- })
94
- )
95
- );
96
-
97
- return {
98
- title: `虎扑 - ${categories[category].title}`,
99
- link: currentUrl,
100
- item: items,
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 };
@@ -57,7 +57,7 @@ export const handler = async (ctx) => {
57
57
  )
58
58
  );
59
59
 
60
- const title = $('meta[name="keywords"]').prop('content')?.replace(/,/g, ' - ') ?? $('title').text();
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 {
@@ -75,7 +75,7 @@ export const handler = async (ctx) => {
75
75
  )
76
76
  );
77
77
 
78
- const title = $('meta[name="keywords"]').prop('content')?.replace(/,/g, ' - ') ?? $('title').text();
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 {
@@ -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].sort();
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)?.replace(/<\/?(?:em|br)?>/g, ''),
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]?.replace(/\\/g, '') ?? undefined;
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.sort((a, b) => b.width - a.width)[0],
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.sort((a, b) => b.width - a.width)[0],
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.sort((a, b) => b.width - a.width)[0], alt: item.accessibility_caption }];
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
- item.author = detailResponse.data.match(/"author":{".*?","name":"(.*?)"/)[1];
75
- item.pubDate = parseDate(detailResponse.data.match(/"datePublished":"(.*?)"/)[1]);
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((i) => content(i).attr('src')),
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.sort((a, b) => b.score - a.score)[0].link;
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>,&nbsp;<span'),
71
- comment: item.description?.replace(/\[img]/g, '<img src="')?.replace(/\[\/img]/g, '"/>'),
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
+ };