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
@@ -0,0 +1,142 @@
1
+ import { Route } from '@/types';
2
+ import got from '@/utils/got';
3
+ import { load } from 'cheerio';
4
+ import { parseDate } from '@/utils/parse-date';
5
+ import cache from '@/utils/cache';
6
+ import timezone from '@/utils/timezone';
7
+
8
+ export const route: Route = {
9
+ path: '/ai/:type?',
10
+ categories: ['university'],
11
+ example: '/nankai/ai/zxdt',
12
+ parameters: { type: '栏目类型(若为空则默认为"最新动态")' },
13
+ features: {
14
+ requireConfig: false,
15
+ requirePuppeteer: false,
16
+ antiCrawler: false,
17
+ supportBT: false,
18
+ supportPodcast: false,
19
+ supportScihub: false,
20
+ },
21
+ radar: [
22
+ {
23
+ source: ['ai.nankai.edu.cn', 'ai.nankai.edu.cn/xwzx/:type.htm'],
24
+ target: '/ai/:type?',
25
+ },
26
+ ],
27
+ name: '人工智能学院',
28
+ maintainers: ['LMark'],
29
+ description: `| 最新动态 | 学院公告 | 学生之窗 | 科研信息 | 本科生教学 | 党团园地 | 研究生招生 | 研究生教学 | 就业信息 | 国际交流 |
30
+ | -------- | -------- | -------- | -------- | ---------- | -------- | ---------- | ---------- | -------- | -------- |
31
+ | zxdt | xygg | xszc | kyxx | bksjx | dtyd | yjszs | yjsjx | jyxx | gjjl |`,
32
+ url: 'ai.nankai.edu.cn',
33
+ handler: async (ctx) => {
34
+ // 从 URL 参数中获取通知分类
35
+ const { type = 'zxdt' } = ctx.req.param();
36
+ const baseUrl = 'https://ai.nankai.edu.cn';
37
+ const { data: response } = await got(`${baseUrl}/xwzx/${type}.htm`);
38
+ const $ = load(response);
39
+
40
+ // 获取分类名称映射
41
+ const categoryMap: Record<string, string> = {
42
+ zxdt: '最新动态',
43
+ xygg: '学院公告',
44
+ xszc: '学生之窗',
45
+ kyxx: '科研信息',
46
+ bksjx: '本科生教学',
47
+ dtyd: '党团园地',
48
+ yjszs: '研究生招生',
49
+ yjsjx: '研究生教学',
50
+ jyxx: '就业信息',
51
+ gjjl: '国际交流',
52
+ };
53
+
54
+ const categoryName = categoryMap[type] || '最新动态';
55
+
56
+ // 解析新闻列表
57
+ const list = $('.gage-list-news table tr')
58
+ .slice(1) // 跳过表头
59
+ .toArray()
60
+ .map((tr) => {
61
+ const $tr = $(tr);
62
+ const cells = $tr.find('td');
63
+
64
+ if (cells.length < 3) {
65
+ return null;
66
+ }
67
+
68
+ const titleCell = cells.eq(0);
69
+ const sourceCell = cells.eq(1);
70
+ const dateCell = cells.eq(2);
71
+
72
+ // 提取标题和链接
73
+ const $titleLink = titleCell.find('a');
74
+ const title = $titleLink.text().trim();
75
+ let link = $titleLink.attr('href') || '';
76
+
77
+ // 处理相对链接
78
+ link = link && !link.startsWith('http') ? `${baseUrl}/${link}` : link;
79
+
80
+ // 提取日期
81
+ const dateStr = dateCell.text().trim();
82
+ const pubDate = dateStr.includes('/') ? timezone(parseDate(dateStr, 'YYYY/MM/DD'), +8) : timezone(parseDate(dateStr), +8);
83
+
84
+ // 提取来源
85
+ const source = sourceCell.text().trim();
86
+
87
+ return {
88
+ title,
89
+ link,
90
+ pubDate,
91
+ author: source || '人工智能学院',
92
+ description: '', // 初始化description属性
93
+ };
94
+ })
95
+ .filter((item) => item && item.link && item.title); // 过滤掉空项目和没有链接的项目
96
+
97
+ // 获取每篇文章的详细内容
98
+ const items = await Promise.all(
99
+ list.map((item) =>
100
+ item
101
+ ? cache.tryGet(item.link, async () => {
102
+ try {
103
+ const { data: response } = await got(item.link);
104
+ const $ = load(response);
105
+
106
+ const $description = $('.v_news_content');
107
+
108
+ // 处理相对链接,转换为绝对链接
109
+ if ($description.length > 0) {
110
+ // 处理图片
111
+ $description.find('img').each((i, el) => {
112
+ const $el = $(el);
113
+ let src = $el.attr('src');
114
+
115
+ if (src && !src.startsWith('http')) {
116
+ src = `${baseUrl}${src}`;
117
+ $el.attr('src', src);
118
+ }
119
+ });
120
+ }
121
+
122
+ item.description = $description.html() || item.title;
123
+ } catch {
124
+ // 如果获取详细内容失败,返回基本信息
125
+ item.description = item.title + ' (获取详细内容失败)';
126
+ }
127
+ return item;
128
+ })
129
+ : null
130
+ )
131
+ );
132
+
133
+ return {
134
+ // 源标题
135
+ title: `南开大学人工智能学院-${categoryName}`,
136
+ // 源链接
137
+ link: `${baseUrl}/xwzx/${type}.htm`,
138
+ // 源文章
139
+ item: items,
140
+ };
141
+ },
142
+ };
@@ -0,0 +1,162 @@
1
+ import { Route } from '@/types';
2
+ import got from '@/utils/got';
3
+ import { load } from 'cheerio';
4
+ import { parseDate } from '@/utils/parse-date';
5
+ import cache from '@/utils/cache';
6
+ import timezone from '@/utils/timezone';
7
+
8
+ export const route: Route = {
9
+ path: '/graduate/:type?',
10
+ categories: ['university'],
11
+ example: '/nankai/graduate/zxdt',
12
+ parameters: { type: '栏目编号(若为空则默认为"zxdt")' },
13
+ features: {
14
+ requireConfig: false,
15
+ requirePuppeteer: false,
16
+ antiCrawler: false,
17
+ supportBT: false,
18
+ supportPodcast: false,
19
+ supportScihub: false,
20
+ },
21
+ radar: [
22
+ {
23
+ source: ['graduate.nankai.edu.cn', 'graduate.nankai.edu.cn/:type/list.htm'],
24
+ target: '/graduate/:type?',
25
+ },
26
+ ],
27
+ name: '研究生院',
28
+ maintainers: ['ladeng07'],
29
+ description: `| 最新动态 | 综合信息 | 招生工作 | 培养管理 | 国际交流 | 学科建设 | 学位管理 |
30
+ | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
31
+ | zxdt | 82 | 83 | 84 | 85 | 86 | 87 |`,
32
+ url: 'graduate.nankai.edu.cn',
33
+ handler: async (ctx) => {
34
+ // 从 URL 参数中获取通知分类
35
+ const { type = 'zxdt' } = ctx.req.param();
36
+ const baseUrl = 'https://graduate.nankai.edu.cn';
37
+ const { data: response } = await got(`${baseUrl}/${type}/list.htm`);
38
+ const $ = load(response);
39
+
40
+ // 获取分类名称映射
41
+ const categoryMap: Record<string, string> = {
42
+ zxdt: '最新动态',
43
+ '82': '综合信息',
44
+ '83': '招生工作',
45
+ '84': '培养管理',
46
+ '85': '国际交流',
47
+ '86': '学科建设',
48
+ '87': '学位管理',
49
+ };
50
+
51
+ const categoryName = categoryMap[type] || '最新动态';
52
+
53
+ // 解析新闻列表
54
+ const list = $('.newslist li')
55
+ .not('#wp_paging_w6 li')
56
+ .toArray()
57
+ .map((li) => {
58
+ const $li = $(li);
59
+ const $titleDiv = $li.find('.title');
60
+ const $link = $titleDiv.find('a');
61
+ const $timeDiv = $li.find('.time');
62
+
63
+ const title = $link.attr('title');
64
+ let link = $link.attr('href') || '';
65
+
66
+ // 处理相对链接
67
+ link = link && !link.startsWith('http') ? `${baseUrl}${link}` : link;
68
+
69
+ // 提取日期
70
+ const dateStr = $timeDiv.text().trim();
71
+ const pubDate = timezone(parseDate(dateStr, 'YYYY-MM-DD'), +8);
72
+
73
+ return {
74
+ title,
75
+ link,
76
+ pubDate,
77
+ author: '研究生院',
78
+ description: '', // 初始化description属性
79
+ };
80
+ })
81
+ .filter((item) => item && item.link && item.title); // 过滤掉空项目和没有链接的项目
82
+
83
+ // 获取每篇文章的详细内容
84
+ const items = await Promise.all(
85
+ list.map((item) =>
86
+ cache.tryGet(item.link, async () => {
87
+ try {
88
+ const { data: response } = await got(item.link);
89
+ const $ = load(response);
90
+
91
+ // 尝试多种内容选择器
92
+ const $description = $('.wp_articlecontent');
93
+
94
+ // 处理相对链接,转换为绝对链接
95
+ if ($description.length > 0) {
96
+ // 处理链接
97
+ $description.find('a').each((i, el) => {
98
+ const $el = $(el);
99
+ const href = $el.attr('href');
100
+ if (href && !href.startsWith('http')) {
101
+ if (href.startsWith('/')) {
102
+ $el.attr('href', `${baseUrl}${href}`);
103
+ } else {
104
+ $el.attr('href', `${baseUrl}/${href}`);
105
+ }
106
+ }
107
+ });
108
+
109
+ // 处理图片
110
+ $description.find('img').each((i, el) => {
111
+ const $el = $(el);
112
+ let src = $el.attr('src');
113
+
114
+ if (src && !src.startsWith('http')) {
115
+ src = src.startsWith('/') ? `${baseUrl}${src}` : `${baseUrl}/${src}`;
116
+ $el.attr('src', src);
117
+ }
118
+ });
119
+
120
+ // 处理PDF播放器div,提取PDF链接
121
+ $description.find('.wp_pdf_player').each((i, el) => {
122
+ const $el = $(el);
123
+ const pdfSrc = $el.attr('pdfsrc');
124
+ const sudyfileAttr = ($el.attr('sudyfile-attr') || '{}').replaceAll("'", '"');
125
+
126
+ try {
127
+ const sudyfileAttrJson = JSON.parse(sudyfileAttr);
128
+ const fileName = sudyfileAttrJson.title || '未命名文件.pdf';
129
+ if (pdfSrc) {
130
+ let pdfUrl = pdfSrc;
131
+ if (!pdfUrl.startsWith('http')) {
132
+ pdfUrl = `${baseUrl}${pdfUrl}`;
133
+ }
134
+ // 替换PDF播放器为下载链接
135
+ $el.replaceWith(`<p><a href="${pdfUrl}" target="_blank">📄 ${fileName}</a></p>`);
136
+ }
137
+ } catch {
138
+ // 如果解析失败,保留原始内容
139
+ }
140
+ });
141
+ }
142
+
143
+ item.description = $description.html() || item.title;
144
+ } catch {
145
+ // 如果获取详细内容失败,返回基本信息
146
+ item.description = item.title + ' (获取详细内容失败)';
147
+ }
148
+ return item;
149
+ })
150
+ )
151
+ );
152
+
153
+ return {
154
+ // 源标题
155
+ title: `南开大学研究生院-${categoryName}`,
156
+ // 源链接
157
+ link: `${baseUrl}/${type}/list.htm`,
158
+ // 源文章
159
+ item: items,
160
+ };
161
+ },
162
+ };
@@ -66,6 +66,7 @@ async function handler(ctx) {
66
66
 
67
67
  const urlList = $('.article-link-content h4')
68
68
  .toArray()
69
+ .filter((i) => $(i).closest('.article-link-right').length === 0) // 移除右側熱門精選
69
70
  .map((i) => ({
70
71
  link: $(i).find('a[href]').first().attr('href'),
71
72
  }))
@@ -49,7 +49,7 @@ async function handler(ctx) {
49
49
  }))
50
50
  );
51
51
 
52
- items = items.sort((a, b) => b.pubDate - a.pubDate).slice(0, ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 30);
52
+ items = items.toSorted((a, b) => b.pubDate - a.pubDate).slice(0, ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 30);
53
53
 
54
54
  items = await Promise.all(
55
55
  items.map((item) =>
@@ -0,0 +1,39 @@
1
+ import { DataItem, Route } from '@/types';
2
+
3
+ import { getMylist, renderVideo } from './utils';
4
+ import { parseDate } from '@/utils/parse-date';
5
+
6
+ export const route: Route = {
7
+ name: 'Mylist',
8
+ path: '/mylist/:id',
9
+ parameters: { id: 'Mylist ID' },
10
+ example: '/nicovideo/mylist/2973737',
11
+ maintainers: ['esperecyan'],
12
+ radar: [
13
+ {
14
+ source: ['www.nicovideo.jp/user/:user/mylist/:id'],
15
+ target: '/mylist/:id',
16
+ },
17
+ ],
18
+ handler: async (ctx) => {
19
+ const { id } = ctx.req.param();
20
+
21
+ const mylist = await getMylist(id);
22
+
23
+ return {
24
+ title: `マイリスト ${mylist.name}‐ニコニコ動画`,
25
+ link: `https://www.nicovideo.jp/user/${mylist.owner.id}/mylist/${mylist.id}`,
26
+ language: 'ja',
27
+ item: mylist.items.map(
28
+ (item): DataItem => ({
29
+ title: item.video.title,
30
+ link: `https://www.nicovideo.jp/watch/${item.video.id}`,
31
+ pubDate: parseDate(item.addedAt),
32
+ author: [{ name: item.video.owner.name, avatar: item.video.owner.iconUrl, url: `https://www.nicovideo.jp/user/${item.video.owner.id}` }],
33
+ description: renderVideo(item.video, false),
34
+ image: item.video.thumbnail.nHdUrl ?? item.video.thumbnail.largeUrl ?? item.video.thumbnail.middleUrl ?? undefined,
35
+ })
36
+ ),
37
+ };
38
+ },
39
+ };
@@ -52,3 +52,30 @@ export interface VideoItem {
52
52
  series: null;
53
53
  essential: Essential;
54
54
  }
55
+
56
+ export type Mylist = {
57
+ id: number;
58
+ name: string;
59
+ description: string;
60
+ decoratedDescriptionHtml: string;
61
+ defaultSortKey: string;
62
+ defaultSortOrder: string;
63
+ items: MylistItem[];
64
+ totalItemCount: number;
65
+ hasNext: boolean;
66
+ isPublic: boolean;
67
+ owner: Owner;
68
+ hasInvisibleItems: boolean;
69
+ followerCount: number;
70
+ isFollowing: boolean;
71
+ };
72
+
73
+ type MylistItem = {
74
+ itemId: number;
75
+ watchId: string;
76
+ description: string;
77
+ decoratedDescriptionHtml: string;
78
+ addedAt: string;
79
+ status: string;
80
+ video: Essential;
81
+ };
@@ -1,4 +1,4 @@
1
- import { Essential, UserInfo, VideoItem } from './types';
1
+ import { Essential, Mylist, UserInfo, VideoItem } from './types';
2
2
 
3
3
  import ofetch from '@/utils/ofetch';
4
4
  import cache from '@/utils/cache';
@@ -31,4 +31,30 @@ export const getUserVideosById = (id: string) =>
31
31
  false
32
32
  ) as Promise<VideoItem[]>;
33
33
 
34
+ export const getMylist = (id: string): Promise<Mylist> =>
35
+ cache.tryGet<Mylist>(
36
+ `nicovideo:mylist:${id}`,
37
+ async () => {
38
+ const { data } = await ofetch(`https://nvapi.nicovideo.jp/v2/mylists/${id}`, {
39
+ headers: {
40
+ 'X-Frontend-Id': '6',
41
+ },
42
+ query: {
43
+ sortKey: 'addedAt',
44
+ sortOrder: 'desc',
45
+ /**
46
+ * @remarks
47
+ * Up to 500 items can be registered in a single mylist.
48
+ * @see https://qa.nicovideo.jp/faq/show/648?site_domain=default
49
+ */
50
+ pageSize: 500,
51
+ page: 1,
52
+ },
53
+ });
54
+ return data.mylist;
55
+ },
56
+ config.cache.routeExpire,
57
+ false
58
+ );
59
+
34
60
  export const renderVideo = (video: Essential, embed: boolean) => art(path.join(__dirname, 'templates/video.art'), { video, embed });
@@ -136,10 +136,7 @@ async function handler(ctx) {
136
136
 
137
137
  item.author = content('meta[name="author"]').attr('content');
138
138
  item.title = item.title ?? content('meta[name="twitter:title"]').attr('content');
139
- item.description = content('#contentDiv')
140
- .html()
141
- ?.replace(/&nbsp;/g, '')
142
- .replaceAll('<p></p>', '');
139
+ item.description = content('#contentDiv').html()?.replaceAll('&nbsp;', '').replaceAll('<p></p>', '');
143
140
 
144
141
  return item;
145
142
  })
@@ -100,7 +100,7 @@ async function handler(ctx) {
100
100
  link: `https://news.now.com/home/${category}/player?newsId=${item.newsId}`,
101
101
  pubDate: parseDate(item.publishDate, 'x'),
102
102
  category: [...item.sportTypes.map((t) => t.sportTypeNameChi), ...item.players.map((p) => p.playerFullNameChi), ...item.teams.map((t) => t.teamCodeChi)],
103
- image: item.newsPhotos?.filter((p) => p.sizeType === '3')?.[0]?.imageUrl,
103
+ image: item.newsPhotos?.find((p) => p.sizeType === '3')?.imageUrl,
104
104
  newsId: item.newsId,
105
105
  };
106
106
  })
@@ -136,7 +136,7 @@ async function handler(ctx) {
136
136
  // Match 感谢|謝.*?cn.letters@nytimes.com。
137
137
  const ending = /&#x611F;(&#x8C22|&#x8B1D);.*?cn\.letters@nytimes\.com&#x3002;/g;
138
138
 
139
- single.description = result.description?.replace(ending, '');
139
+ single.description = result.description?.replaceAll(ending, '');
140
140
 
141
141
  if (hasEnVersion) {
142
142
  single.title = result.title;
@@ -15,7 +15,7 @@ export async function getSFWUserNovels(id: string, fullContent: boolean = false,
15
15
  });
16
16
 
17
17
  const novels = Object.keys(allData.body.novels)
18
- .sort((a, b) => Number(b) - Number(a))
18
+ .toSorted((a, b) => Number(b) - Number(a))
19
19
  .slice(0, Number.parseInt(String(limit), 10));
20
20
 
21
21
  if (novels.length === 0) {
@@ -81,6 +81,6 @@ export function processContent($: CheerioAPI, lang: string): string {
81
81
  return (
82
82
  $('.am__body')
83
83
  .html()
84
- ?.replace(/https:\/\/i\.pximg\.net/g, config.pixiv.imgProxy || '') || ''
84
+ ?.replaceAll('https://i.pximg.net', config.pixiv.imgProxy || '') || ''
85
85
  );
86
86
  }
@@ -34,7 +34,7 @@ export const route: Route = {
34
34
  };
35
35
 
36
36
  async function handler(ctx) {
37
- const category = ctx.req.param('category')?.replace(/-/g, '/') ?? 'zxgg';
37
+ const category = ctx.req.param('category')?.replaceAll('-', '/') ?? 'zxgg';
38
38
 
39
39
  const rootUrl = 'https://hr.pku.edu.cn/';
40
40
  const currentUrl = `${rootUrl}/${category}/index.htm`;
@@ -56,7 +56,7 @@ async function handler(ctx) {
56
56
  };
57
57
  });
58
58
 
59
- const sorted = list.sort((a, b) => b.pubDate.getTime() - a.pubDate.getTime()).slice(0, 10);
59
+ const sorted = list.toSorted((a, b) => b.pubDate.getTime() - a.pubDate.getTime()).slice(0, 10);
60
60
 
61
61
  return {
62
62
  title: `北京大学学生就业指导服务中心 - ${feed_title}`,
@@ -1,5 +1,5 @@
1
- {{ if headerImage }}
2
- <img src="https://ph-files.imgix.net/{{ headerImage }}"><br>
1
+ {{ if tagline }}
2
+ <blockquote>{{ tagline }}</blockquote>
3
3
  {{ /if }}
4
4
 
5
5
  {{ if description }}
@@ -6,6 +6,7 @@ import { load } from 'cheerio';
6
6
  import { parseDate } from '@/utils/parse-date';
7
7
  import { art } from '@/utils/render';
8
8
  import path from 'node:path';
9
+ import { config } from '@/config';
9
10
 
10
11
  export const route: Route = {
11
12
  path: '/today',
@@ -24,23 +25,28 @@ export const route: Route = {
24
25
  source: ['www.producthunt.com/'],
25
26
  },
26
27
  ],
27
- name: 'Today Popular',
28
+ name: 'Top Products Launching Today',
28
29
  maintainers: ['miaoyafeng', 'Fatpandac'],
29
30
  handler,
30
31
  url: 'www.producthunt.com/',
31
32
  };
32
33
 
33
34
  async function handler() {
34
- const response = await ofetch('https://www.producthunt.com/');
35
+ const response = await ofetch('https://www.producthunt.com/', {
36
+ headers: {
37
+ 'User-Agent': config.trueUA,
38
+ },
39
+ });
35
40
 
36
41
  const $ = load(response);
37
42
  const match = $('script:contains("ApolloSSRDataTransport")')
38
43
  .text()
39
44
  .match(/"events":(\[.+\])\}\)/)?.[1]
45
+ ?.trim()
40
46
  .replaceAll('undefined', 'null');
41
47
 
42
48
  const data = JSON.parse(match);
43
- const todayList = data.find((event) => event.type === 'data' && event.result.data.homefeed).result.data.homefeed.edges.find((edge) => edge.node.id === 'FEATURED-0').node;
49
+ const todayList = data.find((event) => event.type === 'next' && event.value.data.homefeed).value.data.homefeed.edges.find((edge) => edge.node.id === 'FEATURED-0').node;
44
50
  // 0: Top Products Launching Today
45
51
  // 1: Yesterday's Top Products
46
52
  // 2: Last Week's Top Products
@@ -50,8 +56,8 @@ async function handler() {
50
56
  .filter((i) => i.__typename === 'Post')
51
57
  .map((item) => ({
52
58
  title: item.name,
53
- link: `https://www.producthunt.com/posts/${item.slug}`,
54
- slug: item.slug,
59
+ link: `https://www.producthunt.com/products/${item.product.slug}`,
60
+ postSlug: item.slug,
55
61
  description: item.tagline,
56
62
  pubDate: parseDate(item.createdAt),
57
63
  image: `https://ph-files.imgix.net/${item.thumbnailImageUuid}`,
@@ -63,15 +69,18 @@ async function handler() {
63
69
  cache.tryGet(item.link, async () => {
64
70
  const response = await ofetch('https://www.producthunt.com/frontend/graphql', {
65
71
  method: 'POST',
72
+ headers: {
73
+ 'User-Agent': config.trueUA,
74
+ },
66
75
  body: {
67
76
  operationName: 'PostPage',
68
77
  variables: {
69
- slug: item.slug,
78
+ slug: item.postSlug,
70
79
  },
71
80
  extensions: {
72
81
  persistedQuery: {
73
82
  version: 1,
74
- sha256Hash: '3d56ad0687ad82922d71fca238e5081853609dd7b16207a5aa042dee884edaea',
83
+ sha256Hash: '488585149898ee974a51884b11e205c34ea8ad34ee01d47d7936a66a6db799ff',
75
84
  },
76
85
  },
77
86
  },
@@ -80,7 +89,7 @@ async function handler() {
80
89
 
81
90
  item.author = post.user.name;
82
91
  item.description = art(path.join(__dirname, 'templates/description.art'), {
83
- headerImage: post.headerImage?.uuid,
92
+ tagline: post.tagline,
84
93
  description: post.description,
85
94
  media: post.media,
86
95
  });
@@ -87,7 +87,7 @@ async function handler(ctx) {
87
87
  for (const item of items) {
88
88
  result = [...result, ...item];
89
89
  }
90
- result = result.sort((a, b) => new Date(b.pubDate) - new Date(a.pubDate));
90
+ result = result.toSorted((a, b) => new Date(b.pubDate) - new Date(a.pubDate));
91
91
 
92
92
  return {
93
93
  title: `${id} 的 PSN 奖杯`,
@@ -23,14 +23,27 @@ const generateNonce = (length: number): string => {
23
23
  return nonce.slice(0, length);
24
24
  };
25
25
 
26
+ /**
27
+ * Part of fingerprint2.js shim from uBlock Origin
28
+ * Taken from https://github.com/gorhill/uBlock/blob/master/src/web_accessible_resources/fingerprint2.js
29
+ * @param len
30
+ * @returns
31
+ */
32
+ const hex32 = (len) =>
33
+ Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)
34
+ .toString(16)
35
+ .slice(-len)
36
+ .padStart(len, '0');
37
+
26
38
  export const getSignedHeaders = () => {
27
39
  const nonce = generateNonce(6);
28
40
  const timestamp = Date.now().toString();
29
- const signature = sha1([salt, timestamp, nonce].sort().join(''));
41
+ const signature = sha1([salt, timestamp, nonce].toSorted().join(''));
30
42
  return {
31
43
  nonce,
32
44
  timestamp,
33
45
  signature,
46
+ 'x-finger': `${hex32(8)}${hex32(8)}${hex32(8)}${hex32(8)}`,
34
47
  };
35
48
  };
36
49