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,15 +1,9 @@
1
1
  import { Route } from '@/types';
2
-
3
2
  import cache from '@/utils/cache';
4
3
  import got from '@/utils/got';
5
- import { art } from '@/utils/render';
6
- import path from 'node:path';
7
4
  import { config } from '@/config';
8
5
  import ConfigNotFoundError from '@/errors/types/config-not-found';
9
-
10
- const WEATHER_API = 'https://devapi.qweather.com/v7/weather/3d';
11
- const AIR_QUALITY_API = 'https://devapi.qweather.com/v7/air/5d';
12
- const CIRY_LOOKUP_API = 'https://geoapi.qweather.com/v2/city/lookup';
6
+ import { render3DaysDescription, type WeatherForecastItem } from './util';
13
7
  const author = 'QWeather';
14
8
 
15
9
  export const route: Route = {
@@ -21,7 +15,11 @@ export const route: Route = {
21
15
  requireConfig: [
22
16
  {
23
17
  name: 'HEFENG_KEY',
24
- description: '',
18
+ description: 'QWeather API KEY',
19
+ },
20
+ {
21
+ name: 'HEFENG_API_HOST',
22
+ description: 'This is required after 2026/01/01: https://blog.qweather.com/announce/public-api-domain-change-to-api-host/',
25
23
  },
26
24
  ],
27
25
  requirePuppeteer: false,
@@ -37,10 +35,14 @@ export const route: Route = {
37
35
  };
38
36
 
39
37
  async function handler(ctx) {
40
- if (!config.hefeng.key) {
38
+ if (!config.hefeng.key || !config.hefeng.apiHost) {
41
39
  throw new ConfigNotFoundError('QWeather RSS is disabled due to the lack of <a href="https://docs.rsshub.app/zh/install/config#%E5%92%8C%E9%A3%8E%E5%A4%A9%E6%B0%94">relevant config</a>');
42
40
  }
43
41
 
42
+ const WEATHER_API = `https://${config.hefeng.apiHost}/v7/weather/3d`;
43
+ const AIR_QUALITY_API = `https://${config.hefeng.apiHost}/v7/air/5d`;
44
+ const CIRY_LOOKUP_API = `https://${config.hefeng.apiHost}/geo/v2/city/lookup`;
45
+
44
46
  const id = await cache.tryGet('qweather:' + ctx.req.param('location') + ':id', async () => {
45
47
  const response = await got(`${CIRY_LOOKUP_API}?location=${ctx.req.param('location')}&key=${config.hefeng.key}`);
46
48
  return response.data.location[0].id;
@@ -67,7 +69,7 @@ async function handler(ctx) {
67
69
  const combined = {
68
70
  updateTime: weatherData.updateTime,
69
71
  fxLink: weatherData.fxLink,
70
- daily: weatherData.daily.map((weatherItem) => {
72
+ daily: weatherData.daily.map((weatherItem: WeatherForecastItem) => {
71
73
  const dailyAirQuality = airQualityData.daily.find((airQualityItem) => airQualityItem.fxDate === weatherItem.fxDate);
72
74
  if (dailyAirQuality) {
73
75
  return {
@@ -81,11 +83,9 @@ async function handler(ctx) {
81
83
  return weatherItem;
82
84
  }),
83
85
  };
84
- const items = combined.daily.map((item) => ({
86
+ const items = combined.daily.map((item: WeatherForecastItem) => ({
85
87
  title: `${item.fxDate}: ${item.textDay === item.textNight ? item.textDay : item.textDay + '转' + item.textNight} ${item.tempMin}~${item.tempMax}℃`,
86
- description: art(path.join(__dirname, 'templates/3days.art'), {
87
- item,
88
- }),
88
+ description: render3DaysDescription(item),
89
89
  pubDate: combined.updateTime,
90
90
  guid: '位置:' + ctx.req.param('location') + '--日期:' + item.fxDate,
91
91
  link: combined.fxLink,
@@ -1,14 +1,10 @@
1
1
  import { Route } from '@/types';
2
-
3
2
  import cache from '@/utils/cache';
4
3
  import got from '@/utils/got';
5
- import { art } from '@/utils/render';
6
- import path from 'node:path';
7
4
  import { parseDate } from '@/utils/parse-date';
8
5
  import { config } from '@/config';
9
6
  import ConfigNotFoundError from '@/errors/types/config-not-found';
10
-
11
- const rootUrl = 'https://devapi.qweather.com/v7/weather/now?';
7
+ import { renderNowDescription, type NowItem } from './util';
12
8
 
13
9
  export const route: Route = {
14
10
  path: '/now/:location',
@@ -21,6 +17,10 @@ export const route: Route = {
21
17
  name: 'HEFENG_KEY',
22
18
  description: '访问 `https://www.qweather.com/` 注册开发 API Key。',
23
19
  },
20
+ {
21
+ name: 'HEFENG_API_HOST',
22
+ description: 'This is required after 2026/01/01: https://blog.qweather.com/announce/public-api-domain-change-to-api-host/',
23
+ },
24
24
  ],
25
25
  requirePuppeteer: false,
26
26
  antiCrawler: false,
@@ -34,16 +34,18 @@ export const route: Route = {
34
34
  };
35
35
 
36
36
  async function handler(ctx) {
37
- if (!config.hefeng.key) {
37
+ if (!config.hefeng.key || !config.hefeng.apiHost) {
38
38
  throw new ConfigNotFoundError('QWeather RSS is disabled due to the lack of <a href="https://docs.rsshub.app/zh/install/config#%E5%92%8C%E9%A3%8E%E5%A4%A9%E6%B0%94">relevant config</a>');
39
39
  }
40
+ const NOW_WEATHER_API = `https://${config.hefeng.apiHost}/v7/weather/now`;
41
+ const CIRY_LOOKUP_API = `https://${config.hefeng.apiHost}/geo/v2/city/lookup`;
40
42
 
41
43
  const id = await cache.tryGet('qweather:' + ctx.req.param('location') + ':id', async () => {
42
- const response = await got(`https://geoapi.qweather.com/v2/city/lookup?location=${ctx.req.param('location')}&key=${config.hefeng.key}`);
44
+ const response = await got(`${CIRY_LOOKUP_API}?location=${ctx.req.param('location')}&key=${config.hefeng.key}`);
43
45
  const data = response.data.location.map((loc) => loc);
44
46
  return data[0].id;
45
47
  });
46
- const requestUrl = rootUrl + 'key=' + config.hefeng.key + '&location=' + id;
48
+ const requestUrl = `${NOW_WEATHER_API}?key=${config.hefeng.key}&location=${id}`;
47
49
  const responseData = await cache.tryGet(
48
50
  'qweather:' + ctx.req.param('location') + ':now',
49
51
  async () => {
@@ -63,9 +65,9 @@ async function handler(ctx) {
63
65
  return {
64
66
  title: ctx.req.param('location') + '实时天气',
65
67
  description: ctx.req.param('location') + '实时天气状况',
66
- item: data.map((item) => ({
68
+ item: data.map((item: NowItem) => ({
67
69
  title: '观测时间:' + time_show,
68
- description: art(path.join(__dirname, 'templates/now.art'), { item }),
70
+ description: renderNowDescription(item),
69
71
  pubDate: timeObj,
70
72
  guid: '位置:' + ctx.req.param('location') + '--时间:' + time_show,
71
73
  link: responseData.fxLink,
@@ -0,0 +1,89 @@
1
+ import { renderToString } from 'hono/jsx/dom/server';
2
+
3
+ interface WeatherForecastItem {
4
+ fxDate: string;
5
+ textDay: string;
6
+ textNight: string;
7
+ tempMin: number;
8
+ tempMax: number;
9
+ humidity: number;
10
+ aqi: number;
11
+ aqiCategory: string;
12
+ pressure: number;
13
+ uvIndex: number;
14
+ windDirDay: string;
15
+ windScaleDay: number;
16
+ windSpeedDay: number;
17
+ windDirNight: string;
18
+ windScaleNight: number;
19
+ windSpeedNight: number;
20
+ vis: number;
21
+ sunrise: string;
22
+ sunset: string;
23
+ moonPhase: string;
24
+ moonset: string;
25
+ }
26
+
27
+ interface NowItem {
28
+ text: string;
29
+ temp: number;
30
+ feelsLike: number;
31
+ windDir: string;
32
+ windScale: number;
33
+ windSpeed: number;
34
+ humidity: number;
35
+ pressure: number;
36
+ precip: number;
37
+ vis: number;
38
+ }
39
+
40
+ const render3DaysDescription = (item: WeatherForecastItem) => renderToString(<WeatherForecast item={item} />);
41
+ const renderNowDescription = (item: NowItem) => renderToString(<Now item={item} />);
42
+
43
+ const WeatherForecast = ({ item }: { item: WeatherForecastItem }) => (
44
+ <p>
45
+ 白天:{item.textDay}——夜间:{item.textNight}
46
+ <br />
47
+ 气温:{item.tempMin}℃~{item.tempMax}℃
48
+ <br />
49
+ 相对湿度:{item.humidity}%
50
+ <br />
51
+ 空气质量指数:{item.aqi} ({item.aqiCategory})
52
+ <br />
53
+ 大气压强:{item.pressure}百帕
54
+ <br />
55
+ 紫外线强度:{item.uvIndex}
56
+ <br />
57
+ 白天风向:{item.windDirDay} 风力:{item.windScaleDay}级 风速:{item.windSpeedDay}公里/小时
58
+ <br />
59
+ 夜间风向:{item.windDirNight} 风力:{item.windScaleNight}级 风速:{item.windSpeedNight}公里/小时
60
+ <br />
61
+ 能见度:{item.vis}公里
62
+ <br />
63
+ 日出:{item.sunrise} 日落: {item.sunset}
64
+ <br />
65
+ 月相:{item.moonPhase} 月出:{item.sunrise} 月落:{item.moonset}
66
+ </p>
67
+ );
68
+
69
+ const Now = ({ item }: { item: NowItem }) => (
70
+ <p>
71
+ 天气:{item.text}
72
+ <br />
73
+ 气温:{item.temp}℃
74
+ <br />
75
+ 体感温度:{item.feelsLike}℃
76
+ <br />
77
+ 风向:{item.windDir}
78
+ <br />
79
+ 风力:{item.windScale}级 风速:{item.windSpeed}km/h
80
+ <br />
81
+ 湿度:{item.humidity}% 大气压强:{item.pressure}hPa
82
+ <br />
83
+ 本小时降水量:{item.precip}mm
84
+ <br />
85
+ 能见度:{item.vis}km
86
+ </p>
87
+ );
88
+
89
+ export { render3DaysDescription, renderNowDescription, type WeatherForecastItem, type NowItem };
@@ -0,0 +1,75 @@
1
+ import { Route } from '@/types';
2
+ import ofetch from '@/utils/ofetch';
3
+ import { load } from 'cheerio';
4
+ import cache from '@/utils/cache';
5
+ import { parseDate } from '@/utils/parse-date';
6
+
7
+ export const route: Route = {
8
+ path: '/blog/:lang?',
9
+ categories: ['blog'],
10
+ example: '/qwenlm/blog/zh',
11
+ parameters: { lang: 'Blog language' },
12
+ features: {
13
+ requireConfig: false,
14
+ requirePuppeteer: false,
15
+ antiCrawler: false,
16
+ supportBT: false,
17
+ supportPodcast: false,
18
+ supportScihub: false,
19
+ },
20
+ radar: [
21
+ {
22
+ source: ['qwenlm.github.io/blog/', 'qwenlm.github.io/:lang/blog/'],
23
+ target: '/qwenlm/blog/:lang',
24
+ },
25
+ ],
26
+ name: 'Blog',
27
+ maintainers: ['Kjasn'],
28
+ handler: async (ctx) => {
29
+ const { lang } = ctx.req.param();
30
+
31
+ const blogUrl = lang ? `https://qwenlm.github.io/${lang}/blog` : 'https://qwenlm.github.io/blog';
32
+
33
+ const response = await ofetch(blogUrl);
34
+ const $ = load(response);
35
+
36
+ // get blog list
37
+ const list = $('article.post-entry')
38
+ .toArray()
39
+ .map((item) => {
40
+ item = $(item);
41
+
42
+ const dateString = item
43
+ .find('.entry-footer span')
44
+ .attr('title')
45
+ .trim()
46
+ .replace(/\+0800$/, '');
47
+ // const pubDate = timezone(parseDate(dateString, 'YYYY-MM-DD HH:mm:ss ZZ'), +8);
48
+ const pubDate = parseDate(dateString);
49
+
50
+ return {
51
+ title: item.find('header.entry-header h2').text().trim(),
52
+ link: item.find('.entry-link').attr('href'),
53
+ pubDate,
54
+ };
55
+ });
56
+
57
+ // get blog content
58
+ const items = await Promise.all(
59
+ list.map((item) =>
60
+ cache.tryGet(item.link, async () => {
61
+ const response = await ofetch(item.link);
62
+ const $ = load(response);
63
+ item.description = $('main').html();
64
+ return item;
65
+ })
66
+ )
67
+ );
68
+
69
+ return {
70
+ title: 'Qwen Blog',
71
+ link: blogUrl,
72
+ item: items,
73
+ };
74
+ },
75
+ };
@@ -0,0 +1,6 @@
1
+ import type { Namespace } from '@/types';
2
+
3
+ export const namespace: Namespace = {
4
+ name: 'Qwen Blog',
5
+ url: 'qwenlm.github.io',
6
+ };
@@ -1,6 +1,6 @@
1
1
  import { Route } from '@/types';
2
2
  import cache from '@/utils/cache';
3
- import got from '@/utils/got';
3
+ import ofetch from '@/utils/ofetch';
4
4
  import { parseDate } from '@/utils/parse-date';
5
5
  import { load } from 'cheerio';
6
6
 
@@ -37,12 +37,9 @@ async function handler(ctx) {
37
37
  const apiRootUrl = 'https://services.radio-canada.ca';
38
38
  const currentUrl = `${apiRootUrl}/neuro/sphere/v1/rci/${language}/continuous-feed?pageSize=50`;
39
39
 
40
- const response = await got({
41
- method: 'get',
42
- url: currentUrl,
43
- });
40
+ const response = await ofetch(currentUrl);
44
41
 
45
- const list = response.data.data.lineup.items.map((item) => ({
42
+ const list = response.data.lineup.items.map((item) => ({
46
43
  title: item.title,
47
44
  category: item.kicker,
48
45
  link: `${rootUrl}${item.url}`,
@@ -52,18 +49,15 @@ async function handler(ctx) {
52
49
  const items = await Promise.all(
53
50
  list.map((item) =>
54
51
  cache.tryGet(item.link, async () => {
55
- const detailResponse = await got({
56
- method: 'get',
57
- url: item.link,
58
- });
52
+ const detailResponse = await ofetch(item.link);
53
+
54
+ const $ = load(detailResponse);
59
55
 
60
- const $ = load(detailResponse.data);
61
56
  const rcState = $('script:contains("window._rcState_ = ")')
62
57
  .text()
63
- .match(/window\._rcState_ = (.*);/)[1];
64
- const rcStateJson = JSON.parse(rcState);
65
- const news = Object.values(rcStateJson.pagesV2.pages)[0];
66
- item.description = news.data.newsStory.body.html.replaceAll(String.raw`\n`, '<br>');
58
+ .match(/window\._rcState_ = (.*);/)?.[1];
59
+
60
+ item.description = rcState ? parseDescriptionFromState(rcState) : ($(`div[data-testid="newsStoryMedia"]`).html() ?? '') + ($('article > main').html() ?? '');
67
61
 
68
62
  return item;
69
63
  })
@@ -71,8 +65,27 @@ async function handler(ctx) {
71
65
  );
72
66
 
73
67
  return {
74
- title: response.data.meta.title,
75
- link: response.data.metric.metrikContent.omniture.url,
68
+ title: response.meta.title,
69
+ link: response.metric.metrikContent.omniture.url,
76
70
  item: items,
77
71
  };
78
72
  }
73
+
74
+ const parseDescriptionFromState = (rcState) => {
75
+ const rcStateJson = JSON.parse(rcState);
76
+ const news = Object.values(rcStateJson?.pages?.pages ?? {})[0] as any;
77
+
78
+ const headerImg = news?.data?.newsStory?.headerMultimediaItem?.picture;
79
+ const headerImgUrl = headerImg?.pattern ? headerImg?.pattern.replace('/q_auto,w_{width}', '').replace('{ratio}', '16x9') : '';
80
+ const header = `<figure><picture><img src="${headerImgUrl}" alt="${headerImg?.alt ?? ''}"></picture><figcaption>${headerImg?.legend ?? ''}</figcaption></figure>`;
81
+ const primer = news?.data?.newsStory?.primer?.replaceAll(String.raw`\n`, '') ?? '';
82
+ const body = news?.data?.newsStory?.body?.html?.replaceAll(String.raw`\n`, '') ?? '';
83
+ let bodyWithImg = body;
84
+ for (const [index, attachment] of (news?.data?.newsStory?.body?.attachments ?? []).entries()) {
85
+ const placeholder = `<!--body:attachment:${index}-->`;
86
+ const picture = attachment?.picture;
87
+ const imageUrl = picture?.pattern ? picture?.pattern.replace('/q_auto,w_{width}', '').replace('{ratio}', attachment?.dimensionRatio ?? '16x9') : '';
88
+ bodyWithImg = bodyWithImg.replace(placeholder, `<figure><picture><img src="${imageUrl}" alt="${picture?.alt ?? ''}"></picture><figcaption>${picture?.legend ?? ''}</figcaption></figure>`);
89
+ }
90
+ return header + primer + bodyWithImg;
91
+ };
@@ -26,7 +26,7 @@ export const route: Route = {
26
26
  name: '高瓴人工智能学院',
27
27
  maintainers: ['yinhanyan'],
28
28
  handler: async (ctx) => {
29
- const category = ctx.req.param('category')?.replace(/-/g, '/') ?? 'newslist/notice';
29
+ const category = ctx.req.param('category')?.replaceAll('-', '/') ?? 'newslist/notice';
30
30
  const baseURL = `http://ai.ruc.edu.cn/${category}/`;
31
31
  const indexUrl = baseURL + 'index.htm';
32
32
  const response = await ofetch(indexUrl);
@@ -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, '/') ?? 'tzgg';
37
+ const category = ctx.req.param('category')?.replaceAll('-', '/') ?? 'tzgg';
38
38
 
39
39
  const rootUrl = 'http://hr.ruc.edu.cn';
40
40
  const currentUrl = `${rootUrl}/${category}/index.htm`;
@@ -0,0 +1,241 @@
1
+ import { type Data, type DataItem, type Route, ViewType } from '@/types';
2
+
3
+ import cache from '@/utils/cache';
4
+ import ofetch from '@/utils/ofetch';
5
+ import { parseDate } from '@/utils/parse-date';
6
+
7
+ import { type CheerioAPI, type Cheerio, load } from 'cheerio';
8
+ import type { Element } from 'domhandler';
9
+ import { type Context } from 'hono';
10
+
11
+ export const handler = async (ctx: Context): Promise<Data> => {
12
+ const { id = 'xwdt/gzdt' } = ctx.req.param();
13
+ const limit: number = Number.parseInt(ctx.req.query('limit') ?? '17', 10);
14
+
15
+ const baseUrl: string = 'https://www.samrdprc.org.cn';
16
+ const targetUrl: string = new URL(id.endsWith('/') ? id : `${id}/`, baseUrl).href;
17
+
18
+ const response = await ofetch(targetUrl);
19
+ const $: CheerioAPI = load(response);
20
+ const language = $('html').attr('lang') ?? 'zh';
21
+
22
+ let items: DataItem[] = [];
23
+
24
+ items = $('div.boxl_ul ul li')
25
+ .slice(0, limit)
26
+ .toArray()
27
+ .map((el): Element => {
28
+ const $el: Cheerio<Element> = $(el);
29
+ const $aEl: Cheerio<Element> = $el.find('a').first();
30
+
31
+ const title: string = $aEl.text();
32
+ const pubDateStr: string | undefined = $el.find('span').text();
33
+ const linkUrl: string | undefined = $aEl.attr('href');
34
+ const upDatedStr: string | undefined = pubDateStr;
35
+
36
+ const processedItem: DataItem = {
37
+ title,
38
+ pubDate: pubDateStr ? parseDate(pubDateStr) : undefined,
39
+ link: linkUrl ? new URL(linkUrl, targetUrl).href : undefined,
40
+ updated: upDatedStr ? parseDate(upDatedStr) : undefined,
41
+ language,
42
+ };
43
+
44
+ return processedItem;
45
+ });
46
+
47
+ items = await Promise.all(
48
+ items.map((item) => {
49
+ if (!item.link) {
50
+ return item;
51
+ }
52
+
53
+ return cache.tryGet(item.link, async (): Promise<DataItem> => {
54
+ const detailResponse = await ofetch(item.link);
55
+ const $$: CheerioAPI = load(detailResponse);
56
+
57
+ const title: string = $$('div.show_tit').text();
58
+ const description: string | undefined = $$('div.TRS_Editor div.TRS_Editor').html() ?? undefined;
59
+ const pubDateStr: string | undefined = $$('div.show_tit2').text().split(/:/).pop()?.trim();
60
+ const categories: string[] = $$('meta[name="keywords"]').attr('content')?.split(/,/) ?? [];
61
+ const upDatedStr: string | undefined = pubDateStr;
62
+
63
+ const processedItem: DataItem = {
64
+ title,
65
+ description,
66
+ pubDate: pubDateStr ? parseDate(pubDateStr) : item.pubDate,
67
+ category: categories,
68
+ content: {
69
+ html: description,
70
+ text: description,
71
+ },
72
+ updated: upDatedStr ? parseDate(upDatedStr) : item.updated,
73
+ language,
74
+ };
75
+
76
+ return {
77
+ ...item,
78
+ ...processedItem,
79
+ };
80
+ });
81
+ })
82
+ );
83
+
84
+ return {
85
+ title: $('title').text(),
86
+ description: $('meta[name="description"]').attr('content'),
87
+ link: targetUrl,
88
+ item: items,
89
+ allowEmpty: true,
90
+ image: new URL('images/logo_DPRC.png', baseUrl).href,
91
+ author: $('meta[name="keyword"]').attr('content')?.split(/,/)[0],
92
+ language,
93
+ id: $('meta[property="og:url"]').attr('content'),
94
+ };
95
+ };
96
+
97
+ export const route: Route = {
98
+ path: '/:id{.+}?',
99
+ name: '栏目',
100
+ url: 'www.samrdprc.org.cn',
101
+ maintainers: ['nczitzk'],
102
+ handler,
103
+ example: '/samrdprc/xwdt/gzdt',
104
+ parameters: {
105
+ id: {
106
+ description: '栏目 id,默认为 `xwdt/gzdt`,即国内新闻,可在对应分类页 URL 中找到',
107
+ options: [
108
+ {
109
+ label: '新闻动态',
110
+ value: 'xwdt/gzdt',
111
+ },
112
+ {
113
+ label: '网站公告',
114
+ value: 'wzgg',
115
+ },
116
+ {
117
+ label: '汽车召回',
118
+ value: 'qczh',
119
+ },
120
+ {
121
+ label: '消费品召回',
122
+ value: 'xfpzh',
123
+ },
124
+ {
125
+ label: '技术报告',
126
+ value: 'yjgz/jsyj',
127
+ },
128
+ {
129
+ label: 'SAC/TC463',
130
+ value: 'yjgz/sactc',
131
+ },
132
+ {
133
+ label: '研究动态',
134
+ value: 'yjgz/yjfx',
135
+ },
136
+ {
137
+ label: '安全教育',
138
+ value: 'aqjy',
139
+ },
140
+ {
141
+ label: '国内法规',
142
+ value: 'flfg/gnfg',
143
+ },
144
+ ],
145
+ },
146
+ },
147
+ description: `:::tip
148
+ 订阅 [网站公告](https://www.samrdprc.org.cn/wzgg/),其源网址为 \`https://www.samrdprc.org.cn/wzgg/\`,请参考该 URL 指定部分构成参数,此时路由为 [\`/samrdprc/wzgg\`](https://rsshub.app/samrdprc/wzgg)。
149
+ :::
150
+
151
+ <details>
152
+ <summary>更多分类</summary>
153
+
154
+ #### 网站首页
155
+
156
+ | [新闻动态](https://www.samrdprc.org.cn/xwdt/gzdt/) | [网站公告](https://www.samrdprc.org.cn/wzgg/) | [汽车召回](https://www.samrdprc.org.cn/qczh/) | [消费品召回](https://www.samrdprc.org.cn/xfpzh/) |
157
+ | -------------------------------------------------- | --------------------------------------------- | --------------------------------------------- | ------------------------------------------------ |
158
+ | [xwdt/gzdt](https://rsshub.app/samrdprc/xwdt/gzdt) | [wzgg](https://rsshub.app/samrdprc/wzgg) | [qczh](https://rsshub.app/samrdprc/qczh) | [xfpzh](https://rsshub.app/samrdprc/xfpzh) |
159
+
160
+ #### 科学研究
161
+
162
+ | [技术报告](https://www.samrdprc.org.cn/yjgz/jsyj/) | [SAC/TC463](https://www.samrdprc.org.cn/yjgz/sactc/) | [研究动态](https://www.samrdprc.org.cn/yjgz/yjfx/) |
163
+ | -------------------------------------------------- | ---------------------------------------------------- | -------------------------------------------------- |
164
+ | [yjgz/jsyj](https://rsshub.app/samrdprc/yjgz/jsyj) | [yjgz/sactc](https://rsshub.app/samrdprc/yjgz/sactc) | [yjgz/yjfx](https://rsshub.app/samrdprc/yjgz/yjfx) |
165
+
166
+ #### 安全教育
167
+
168
+ | [安全教育](https://www.samrdprc.org.cn/aqjy/) |
169
+ | --------------------------------------------- |
170
+ | [aqjy](https://rsshub.app/samrdprc/aqjy) |
171
+
172
+ #### 法律法规
173
+
174
+ | [国内法规](https://www.samrdprc.org.cn/flfg/gnfg/) |
175
+ | -------------------------------------------------- |
176
+ | [flfg/gnfg](https://rsshub.app/samrdprc/flfg/gnfg) |
177
+ </details>
178
+ `,
179
+ categories: ['government'],
180
+ features: {
181
+ requireConfig: false,
182
+ requirePuppeteer: false,
183
+ antiCrawler: false,
184
+ supportRadar: true,
185
+ supportBT: false,
186
+ supportPodcast: false,
187
+ supportScihub: false,
188
+ },
189
+ radar: [
190
+ {
191
+ source: ['www.samrdprc.org.cn/:id'],
192
+ target: '/:id',
193
+ },
194
+ {
195
+ title: '网站首页 - 新闻动态',
196
+ source: ['www.samrdprc.org.cn/xwdt/gzdt/'],
197
+ target: '/xwdt/gzdt',
198
+ },
199
+ {
200
+ title: '网站首页 - 网站公告',
201
+ source: ['www.samrdprc.org.cn/wzgg/'],
202
+ target: '/wzgg',
203
+ },
204
+ {
205
+ title: '网站首页 - 汽车召回',
206
+ source: ['www.samrdprc.org.cn/qczh/'],
207
+ target: '/qczh',
208
+ },
209
+ {
210
+ title: '网站首页 - 消费品召回',
211
+ source: ['www.samrdprc.org.cn/xfpzh/'],
212
+ target: '/xfpzh',
213
+ },
214
+ {
215
+ title: '科学研究 - 技术报告',
216
+ source: ['www.samrdprc.org.cn/yjgz/jsyj/'],
217
+ target: '/yjgz/jsyj',
218
+ },
219
+ {
220
+ title: '科学研究 - SAC/TC463',
221
+ source: ['www.samrdprc.org.cn/yjgz/sactc/'],
222
+ target: '/yjgz/sactc',
223
+ },
224
+ {
225
+ title: '科学研究 - 研究动态',
226
+ source: ['www.samrdprc.org.cn/yjgz/yjfx/'],
227
+ target: '/yjgz/yjfx',
228
+ },
229
+ {
230
+ title: '安全教育 - 安全教育',
231
+ source: ['www.samrdprc.org.cn/aqjy/'],
232
+ target: '/aqjy',
233
+ },
234
+ {
235
+ title: '法律法规 - 国内法规',
236
+ source: ['www.samrdprc.org.cn/flfg/gnfg/'],
237
+ target: '/flfg/gnfg',
238
+ },
239
+ ],
240
+ view: ViewType.Articles,
241
+ };
@@ -1,7 +1,7 @@
1
1
  import type { Namespace } from '@/types';
2
2
 
3
3
  export const namespace: Namespace = {
4
- name: '国家市场监督管理总局',
4
+ name: '国家市场监督管理总局缺陷产品管理中心',
5
5
  url: 'www.samrdprc.org.cn',
6
6
  lang: 'zh-CN',
7
7
  };