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,6 +1,9 @@
1
1
  import { Route } from '@/types';
2
2
  import got from '@/utils/got';
3
- import utils from './utils';
3
+ import utils, { getVideoUrl } from './utils';
4
+ import { parseDuration } from '@/utils/helpers';
5
+ import { config } from '@/config';
6
+ import cache from './cache';
4
7
 
5
8
  export const route: Route = {
6
9
  path: '/weekly/:embed?',
@@ -46,10 +49,23 @@ async function handler(ctx) {
46
49
  title: 'B站每周必看',
47
50
  link: 'https://www.bilibili.com/h5/weekly-recommend',
48
51
  description: 'B站每周必看',
49
- item: data.map((item) => ({
50
- title: item.title,
51
- description: utils.renderUGCDescription(embed, item.cover, `${weekly_name} ${item.title} - ${item.rcmd_reason}`, item.param, undefined, item.bvid),
52
- link: weekly_number > 60 && item.bvid ? `https://www.bilibili.com/video/${item.bvid}` : `https://www.bilibili.com/video/av${item.param}`,
53
- })),
52
+ item: data.map(async (item) => {
53
+ const subtitles = !config.bilibili.excludeSubtitles && item.bvid ? await cache.getVideoSubtitleAttachment(item.bvid) : [];
54
+ return {
55
+ title: item.title,
56
+ description: utils.renderUGCDescription(embed, item.cover, `${weekly_name} ${item.title} - ${item.rcmd_reason}`, item.param, undefined, item.bvid),
57
+ link: weekly_number > 60 && item.bvid ? `https://www.bilibili.com/video/${item.bvid}` : `https://www.bilibili.com/video/av${item.param}`,
58
+ attachments: item.bvid
59
+ ? [
60
+ {
61
+ url: getVideoUrl(item.bvid),
62
+ mime_type: 'text/html',
63
+ duration_in_seconds: parseDuration(item.cover_right_text_1),
64
+ },
65
+ ...subtitles,
66
+ ]
67
+ : undefined,
68
+ };
69
+ }),
54
70
  };
55
71
  }
@@ -48,7 +48,7 @@ async function handler(ctx) {
48
48
  pubDate: timezone(parseDate(e.find('span').text().replace(':', ''), 'YYYY-MM-DD'), 8),
49
49
  };
50
50
  })
51
- .sort((a, b) => b.pubDate - a.pubDate)
51
+ .toSorted((a, b) => b.pubDate - a.pubDate)
52
52
  .slice(0, ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 10);
53
53
 
54
54
  const items = await Promise.all(
@@ -115,7 +115,7 @@ function getItems(list) {
115
115
  const { data: descrptionResponse } = await got(item.link);
116
116
  const $descrption = load(descrptionResponse);
117
117
  const desc = $descrption('#main > div.content > div.search_height > div.search_con:has(p)').html();
118
- item.description = desc?.replace(/(\r|\n)+/g, '<br />');
118
+ item.description = desc?.replaceAll(/(\r|\n)+/g, '<br />');
119
119
  item.author = $descrption('#main > div.content > div.search_height > span.search_con').text().split('发布者:').at(-1) || '教务部';
120
120
  return item;
121
121
  })
@@ -21,7 +21,7 @@ export const handler = async (ctx: Context): Promise<Data> => {
21
21
 
22
22
  let items: DataItem[] = [];
23
23
 
24
- items = $('section#block-views-latest-articles-block div.media, section#block-system-main table.views-table tr')
24
+ items = $('section#block-bootstrap-views-block-latest-articles-block div.media, div.gold-news-content table tr')
25
25
  .slice(0, limit)
26
26
  .toArray()
27
27
  .map((el): Element => {
@@ -66,8 +66,8 @@ export const handler = async (ctx: Context): Promise<Data> => {
66
66
  const detailResponse = await ofetch(item.link);
67
67
  const $$: CheerioAPI = load(detailResponse);
68
68
 
69
- const title: string = $$('header h1').text();
70
- const description: string | undefined = $$('div[property="content:encoded"]').html() ?? '';
69
+ const title: string = $$('article.article h1').text();
70
+ const description: string | undefined = $$('div.content').html() ?? '';
71
71
  const pubDateStr: string | undefined = $$('div.submitted').text().split(/,/).pop();
72
72
  const categories: string[] = $$('meta[name="news_keywords"]').attr('content')?.split(/,/) ?? [];
73
73
  const authorEls: Element[] = $$('div.view-author-bio').toArray();
@@ -94,7 +94,7 @@ async function handler(ctx) {
94
94
  } else {
95
95
  const buildUnitScript = $('script[parseType="bulidstatic"]');
96
96
  const queryUrl = `${baseUrl}${buildUnitScript.attr('url')}`;
97
- const queryData = JSON.parse(buildUnitScript.attr('querydata')?.replace(/'/g, '"') ?? '{}');
97
+ const queryData = JSON.parse(buildUnitScript.attr('querydata')?.replaceAll("'", '"') ?? '{}');
98
98
  queryData.paramJson = `{"pageNo":1,"pageSize":${limit}}`;
99
99
 
100
100
  const { data } = await got.get<{ data: { html: string } }>(queryUrl, {
@@ -24,6 +24,7 @@ const get_app_token = () => {
24
24
  };
25
25
 
26
26
  const base_url = 'https://api.coolapk.com';
27
+ const v2_api_url = 'https://api2.coolapk.com';
27
28
 
28
29
  const getHeaders = () => ({
29
30
  'X-Requested-With': 'XMLHttpRequest',
@@ -58,10 +59,10 @@ const parseTuwenFromRaw = (raw) =>
58
59
 
59
60
  const parseDynamic = async (item) => {
60
61
  const pubDate = parseDate(item.dateline, 'X');
61
- if (item.entityType === 'sponsorCard' || item.shareUrl === undefined) {
62
+ if (item.entityType === 'sponsorCard' || !item.url) {
62
63
  return;
63
64
  }
64
- const itemUrl = item.shareUrl.split('?')[0];
65
+ const itemUrl = `${v2_api_url}/v6${item.url.replace('/feed/', '/feed/detail?id=')}`;
65
66
  let description, title;
66
67
  const type = Number.parseInt(item.type);
67
68
  switch (type) {
@@ -135,8 +136,8 @@ const parseDynamic = async (item) => {
135
136
  title,
136
137
  description,
137
138
  pubDate,
138
- link: item.shareUrl,
139
- guid: itemUrl,
139
+ link: `https://www.coolapk.com${item.url}`,
140
+ // guid: itemUrl,
140
141
  author: item.username,
141
142
  };
142
143
  };
@@ -0,0 +1,106 @@
1
+ import { type Data, type DataItem, type Route, ViewType } from '@/types';
2
+
3
+ import { art } from '@/utils/render';
4
+ import ofetch from '@/utils/ofetch';
5
+
6
+ import { type CheerioAPI, load } from 'cheerio';
7
+ import { type Context } from 'hono';
8
+ import path from 'node:path';
9
+
10
+ export const handler = async (ctx: Context): Promise<Data> => {
11
+ const limit: number = Number.parseInt(ctx.req.query('limit') ?? '50', 10);
12
+
13
+ const baseUrl: string = 'https://coolbuy.com';
14
+ const imageBaseUrl: string = 'https://mcache.ifanr.cn';
15
+ const apiUrl: string = new URL('api/v1.4/product_preview', baseUrl).href;
16
+
17
+ const response = await ofetch(apiUrl, {
18
+ query: {
19
+ order_by: '-id',
20
+ limit,
21
+ page: 0,
22
+ offset: 0,
23
+ },
24
+ });
25
+
26
+ const targetResponse = await ofetch(baseUrl);
27
+ const $: CheerioAPI = load(targetResponse);
28
+ const language = $('html').attr('lang') ?? 'zh';
29
+
30
+ const items: DataItem[] = response.objects.slice(0, limit).map((item): DataItem => {
31
+ const title: string = item.title;
32
+ const image: string | undefined = item.cover_image?.split(/\?/)?.[0];
33
+ const banner: string | undefined = item.display_image?.split(/\?/)?.[0];
34
+
35
+ const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), {
36
+ summary: item.summary,
37
+ price: item.price,
38
+ original_price: item.original_price,
39
+ highest_price: item.highest_price,
40
+ highest_original_price: item.highest_original_price,
41
+ images: [banner, image].filter(Boolean).map((image) => ({
42
+ src: image,
43
+ alt: title,
44
+ })),
45
+ });
46
+
47
+ const linkUrl: string | undefined = item.visit_url;
48
+ const guid: string = `coolbuy-${item.id}#${item.price}`;
49
+
50
+ const processedItem: DataItem = {
51
+ title,
52
+ description,
53
+ link: linkUrl ?? new URL(item.id, baseUrl).href,
54
+ guid,
55
+ id: guid,
56
+ content: {
57
+ html: description,
58
+ text: description,
59
+ },
60
+ image,
61
+ banner: image,
62
+ language,
63
+ };
64
+
65
+ return processedItem;
66
+ });
67
+
68
+ return {
69
+ title: $('title').text(),
70
+ description: $('meta[property="og:description"]').attr('content'),
71
+ link: baseUrl,
72
+ item: items,
73
+ allowEmpty: true,
74
+ image: new URL('static/coolbuy/packages/dongguan/dist/images/97be46f6.png', imageBaseUrl).href,
75
+ language,
76
+ id: baseUrl,
77
+ };
78
+ };
79
+
80
+ export const route: Route = {
81
+ path: '/',
82
+ name: '产品',
83
+ url: 'coolbuy.com',
84
+ maintainers: ['nczitzk'],
85
+ handler,
86
+ example: '/coolbuy',
87
+ parameters: undefined,
88
+ description: undefined,
89
+ categories: ['shopping'],
90
+ features: {
91
+ requireConfig: false,
92
+ requirePuppeteer: false,
93
+ antiCrawler: false,
94
+ supportRadar: true,
95
+ supportBT: false,
96
+ supportPodcast: false,
97
+ supportScihub: false,
98
+ },
99
+ radar: [
100
+ {
101
+ source: ['coolbuy.com'],
102
+ target: '/',
103
+ },
104
+ ],
105
+ view: ViewType.Articles,
106
+ };
@@ -1,9 +1,9 @@
1
1
  import type { Namespace } from '@/types';
2
2
 
3
3
  export const namespace: Namespace = {
4
- name: '新华网',
5
- url: 'xinhuanet.com',
6
- categories: ['traditional-media'],
4
+ name: '玩物志',
5
+ url: 'coolbuy.com',
6
+ categories: ['shopping'],
7
7
  description: '',
8
8
  lang: 'zh-CN',
9
9
  };
@@ -0,0 +1,48 @@
1
+ <table>
2
+ <tbody>
3
+ {{ if summary }}
4
+ <tr>
5
+ <th>简介</th>
6
+ <td>{{ summary }}</td>
7
+ </tr>
8
+ {{ /if }}
9
+ {{ if price }}
10
+ <tr>
11
+ <th>价格</th>
12
+ <td>{{ price }}</td>
13
+ </tr>
14
+ {{ /if }}
15
+ {{ if original_price }}
16
+ <tr>
17
+ <th>原价</th>
18
+ <td>{{ original_price }}</td>
19
+ </tr>
20
+ {{ /if }}
21
+ {{ if highest_price }}
22
+ <tr>
23
+ <th>价格(最高)</th>
24
+ <td>{{ highest_price }}</td>
25
+ </tr>
26
+ {{ /if }}
27
+ {{ if highest_original_price }}
28
+ <tr>
29
+ <th>原价(最高)</th>
30
+ <td>{{ highest_original_price }}</td>
31
+ </tr>
32
+ {{ /if }}
33
+ </tbody>
34
+ </table>
35
+
36
+ {{ if images }}
37
+ {{ each images image }}
38
+ {{ if image?.src }}
39
+ <figure>
40
+ <img
41
+ {{ if image.alt }}
42
+ alt="{{ image.alt }}"
43
+ {{ /if }}
44
+ src="{{ image.src }}">
45
+ </figure>
46
+ {{ /if }}
47
+ {{ /each }}
48
+ {{ /if }}
@@ -0,0 +1,60 @@
1
+ import { type Route } from '@/types';
2
+ import ofetch from '@/utils/ofetch';
3
+ import { load } from 'cheerio';
4
+ import { art } from '@/utils/render';
5
+ import path from 'node:path';
6
+
7
+ const handler = async () => {
8
+ const link = 'https://coolidge.org/film-guide';
9
+ const html = await ofetch(link);
10
+ const $ = load(html);
11
+
12
+ const container = $('#block-coolidge-content > article > div.node__content > div').first();
13
+
14
+ const cover = container.find('p').eq(0).find('img').first().attr('src');
15
+ const title = container.find('p').eq(1).text().trim();
16
+ const description = container.find('p').eq(2).text().trim();
17
+ const linkEl = container.find('a').first();
18
+ const itemLink = linkEl.attr('href');
19
+
20
+ const absoluteCover = cover ? new URL(cover, link).href : undefined;
21
+ const absoluteItemLink = itemLink ? new URL(itemLink, link).href : undefined;
22
+
23
+ const rendered = art(path.join(__dirname, 'templates/description.art'), {
24
+ image: absoluteCover,
25
+ intro: description,
26
+ });
27
+
28
+ return {
29
+ title: 'Coolidge Corner Theatre - Film Guide',
30
+ link,
31
+ description: 'Film Guide',
32
+ item: [
33
+ {
34
+ title,
35
+ description: rendered,
36
+ link: absoluteItemLink ?? link,
37
+ guid: absoluteItemLink ?? absoluteCover ?? link,
38
+ },
39
+ ],
40
+ };
41
+ };
42
+
43
+ export const route: Route = {
44
+ path: '/film-guide',
45
+ name: 'Film Guide',
46
+ url: 'coolidge.org/film-guide',
47
+ maintainers: ['johan456789'],
48
+ example: '/coolidge/film-guide',
49
+ categories: ['blog'],
50
+ features: {
51
+ requireConfig: false,
52
+ requirePuppeteer: false,
53
+ antiCrawler: false,
54
+ supportRadar: false,
55
+ supportBT: false,
56
+ supportPodcast: false,
57
+ supportScihub: false,
58
+ },
59
+ handler,
60
+ };
@@ -0,0 +1,7 @@
1
+ import type { Namespace } from '@/types';
2
+
3
+ export const namespace: Namespace = {
4
+ name: 'Coolidge Corner Theatre',
5
+ url: 'coolidge.org',
6
+ lang: 'en',
7
+ };
@@ -0,0 +1,65 @@
1
+ import { type Route } from '@/types';
2
+ import ofetch from '@/utils/ofetch';
3
+ import { load } from 'cheerio';
4
+ import { art } from '@/utils/render';
5
+ import path from 'node:path';
6
+
7
+ const handler = async () => {
8
+ const link = 'https://coolidge.org/about-us/news-media';
9
+ const html = await ofetch(link);
10
+ const $ = load(html);
11
+
12
+ const container = $('#block-coolidge-content > div > div > div.view-content').first();
13
+ const elements = container.find('div.news-item').toArray();
14
+
15
+ const items = elements.map((el) => {
16
+ const element = $(el);
17
+
18
+ const titleEl = element.find('h2.news-item__title > a').first();
19
+ const title = titleEl.text().trim();
20
+ const href = titleEl.attr('href');
21
+ const descriptionText = element.find('div.news-item__content > p').first().text().trim();
22
+ const imageSrc = element.find('div.news-item__image img').first().attr('src');
23
+
24
+ const absoluteLink = href ? new URL(href, link).href : undefined;
25
+ const absoluteImage = imageSrc ? new URL(imageSrc, link).href : undefined;
26
+
27
+ const rendered = art(path.join(__dirname, 'templates/description.art'), {
28
+ image: absoluteImage,
29
+ intro: descriptionText,
30
+ });
31
+
32
+ return {
33
+ title,
34
+ description: rendered,
35
+ link: absoluteLink ?? link,
36
+ guid: absoluteLink ?? absoluteImage ?? title,
37
+ };
38
+ });
39
+
40
+ return {
41
+ title: 'Coolidge Corner Theatre - News',
42
+ link,
43
+ description: 'News',
44
+ item: items,
45
+ };
46
+ };
47
+
48
+ export const route: Route = {
49
+ path: '/news',
50
+ name: 'News',
51
+ url: 'coolidge.org/about-us/news-media',
52
+ maintainers: ['johan456789'],
53
+ example: '/coolidge/news',
54
+ categories: ['blog'],
55
+ features: {
56
+ requireConfig: false,
57
+ requirePuppeteer: false,
58
+ antiCrawler: false,
59
+ supportRadar: false,
60
+ supportBT: false,
61
+ supportPodcast: false,
62
+ supportScihub: false,
63
+ },
64
+ handler,
65
+ };
@@ -0,0 +1,4 @@
1
+ {{if image}}
2
+ <p><img src="{{ image }}"></p>
3
+ {{/if}}
4
+ <p>{{ intro }}</p>
@@ -82,7 +82,7 @@ async function handler(ctx) {
82
82
  ordered,
83
83
  // index,
84
84
  }))
85
- .sort((a, b) => b.ordered - a.ordered);
85
+ .toSorted((a, b) => b.ordered - a.ordered);
86
86
 
87
87
  return chapters;
88
88
  },
@@ -50,7 +50,7 @@ const handler: Route['handler'] = async (ctx) => {
50
50
  link: absoluteLink,
51
51
  };
52
52
  })
53
- .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
53
+ .toSorted((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
54
54
  .slice(0, 10);
55
55
 
56
56
  const fetchDataItem = (item: { title: string; date: string; link: string }) =>
@@ -42,7 +42,7 @@ async function handler(ctx) {
42
42
 
43
43
  const items = await Promise.all(
44
44
  chapters.chapters
45
- .sort((a, b) => b.idx - a.idx)
45
+ .toSorted((a, b) => b.idx - a.idx)
46
46
  .slice(0, ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 3)
47
47
  .map(async (c) => {
48
48
  let pages;
@@ -26,7 +26,7 @@ export const handler = async (ctx: Context): Promise<Data> => {
26
26
 
27
27
  while ((match = updateRegex.exec(response)) !== null && items.length < limit) {
28
28
  const headerLine: string | undefined = match[2].trim();
29
- const description: string | undefined = match[4].trim()?.replace(/(\s[+-])/g, '<br>$1');
29
+ const description: string | undefined = match[4].trim()?.replaceAll(/(\s[+-])/g, '<br>$1');
30
30
 
31
31
  let version: string = 'N/A';
32
32
  let pubDateStr: string | undefined = undefined;
@@ -3,7 +3,7 @@ import md5 from '@/utils/md5';
3
3
  function hash(images) {
4
4
  const entries = Object.entries(images)
5
5
  .map((x) => `${x[1].os}/${x[1].architecture},${x[1].digest}`)
6
- .sort((a, b) => a.localeCompare(b));
6
+ .toSorted((a, b) => a.localeCompare(b));
7
7
  const text = entries.join('|');
8
8
  return md5(text);
9
9
  }
@@ -82,7 +82,7 @@ async function getContent(nextBuildId: string, contentId: string) {
82
82
  const description =
83
83
  content
84
84
  .html()
85
- ?.replace(rubyRegex, '$1($2)')
86
- ?.replace(/[^\u0009\u000A\u000D\u0020-\uD7FF\uE000-\uFDCF\uFDE0-\uFFFD]/gm, '') ?? '';
85
+ ?.replaceAll(rubyRegex, '$1($2)')
86
+ ?.replaceAll(/[^\u0009\u000A\u000D\u0020-\uD7FF\uE000-\uFDCF\uFDE0-\uFFFD]/gm, '') ?? '';
87
87
  return description;
88
88
  }
@@ -29,8 +29,8 @@ function ehgot(url) {
29
29
  function ehgot_thumb(cache, thumb_url) {
30
30
  return cache.tryGet(thumb_url, async () => {
31
31
  try {
32
- const buffer = await got({ method: 'get', url: thumb_url, headers });
33
- const data = new Buffer.from(buffer.rawBody).toString('base64');
32
+ const buffer = await got({ method: 'get', responseType: 'buffer', url: thumb_url, headers });
33
+ const data = buffer.body.toString('base64');
34
34
  const ext = path.extname(thumb_url).slice(1);
35
35
  return `data:image/${ext};base64,${data}`;
36
36
  } catch (error) {
@@ -72,7 +72,7 @@ async function parsePage(cache, data, get_bittorrent = false, embed_thumb = fals
72
72
 
73
73
  async function parseElement(cache, element) {
74
74
  const el = $(element);
75
- const title = el.find('div.glink').html();
75
+ const title = el.find('.glink').html();
76
76
  const rawDate = el.find('div[id^="posted_"]').text();
77
77
  const pubDate = rawDate ? timezone(rawDate, 0) : rawDate;
78
78
  let el_a;
@@ -0,0 +1,152 @@
1
+ import type { Data, DataItem, Route } from '@/types';
2
+ import { toTitleCase } from '@/utils/common-utils';
3
+ import ofetch from '@/utils/ofetch';
4
+ import { parseDate } from '@/utils/parse-date';
5
+ import { load } from 'cheerio';
6
+ import type { Context } from 'hono';
7
+
8
+ export const route: Route = {
9
+ path: '/:region/:eventType?/:includePromoted?',
10
+ categories: ['other'],
11
+ example: '/eventbrite/canada--toronto/all-events',
12
+ parameters: { eventType: 'category of events for filtering', region: 'Region or scope of events' },
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: ['eventbrite.com/d/:region/:eventType'],
24
+ target: '/:region/:eventType',
25
+ },
26
+ {
27
+ source: ['eventbrite.ca/d/:region/:eventType'],
28
+ target: '/:region/:eventType',
29
+ },
30
+ ],
31
+ name: 'Events',
32
+ maintainers: ['elibroftw'],
33
+ handler: async (ctx: Context): Promise<Data> => {
34
+ const params = ctx.req.param();
35
+ const { region, includePromoted = 'false' } = params;
36
+ let eventType = params.eventType || 'all-events';
37
+ if (eventType === 'events') {
38
+ eventType = 'all-events';
39
+ }
40
+ const link = `https://www.eventbrite.com/d/${region}/${eventType}/`;
41
+ const response = await ofetch(link);
42
+ const $ = load(response);
43
+ const eventsApiUrl = new URL('https://www.eventbrite.com/api/v3/destination/events');
44
+ // exclude promoted events
45
+ const includePromotedBool = includePromoted !== 'false';
46
+ const eventListEl = $('div.search-results-panel-content section>ul>li')
47
+ .toArray()
48
+ .filter((item) => includePromotedBool || !$(item).text().includes('Promoted'));
49
+ const eventIds = eventListEl.map((item) => $(item).find('a.event-card-link').first().attr('data-event-id'));
50
+ eventsApiUrl.searchParams.append('event_ids', eventIds.join(','));
51
+ eventsApiUrl.searchParams.append('page_size', eventIds.length.toString());
52
+ eventsApiUrl.searchParams.append('expand', 'image,primary_venue,ticket_availability,primary_organizer');
53
+ const eventsData: EventbriteEvent[] = (await ofetch(eventsApiUrl.href)).events;
54
+ const items = eventListEl.map((item, index): DataItem => {
55
+ const el = $(item);
56
+ const a = el.find('.event-card-details a.event-card-link').first();
57
+ const fallbackTitle = a.attr('aria-label') || a.text() || a.toString();
58
+
59
+ const eventData = eventsData[index];
60
+ if (eventData === undefined) {
61
+ const pElements = el.find('p');
62
+ return {
63
+ title: fallbackTitle,
64
+ author: pElements.length > 1 ? $(pElements[1]).text() : undefined,
65
+ };
66
+ }
67
+ const isPromoted = el.text().includes('Promoted');
68
+ return {
69
+ title: isPromoted ? `${eventData.name} (Promoted)` : eventData.name,
70
+ link: eventData.url,
71
+ pubDate: parseDate(eventData.published),
72
+ author: eventData.primary_organizer === undefined ? JSON.stringify(eventData) : eventData.primary_organizer.name,
73
+ category: eventData.tags.map((tag) => tag.display_name),
74
+ image: eventData.image.original.url,
75
+ description: eventData.summary,
76
+ id: eventData.eventbrite_event_id,
77
+ content: {
78
+ html: `${eventDate(eventData)}<br>${getAuthor(eventData)}<br>${getTicketPriceRange(eventData)}`,
79
+ text: '',
80
+ },
81
+ };
82
+ });
83
+
84
+ const formattedEventType = toTitleCase(eventType.replaceAll('-', ' '));
85
+ const formattedRegion = toTitleCase(region.replaceAll('-', ' '));
86
+
87
+ return {
88
+ title: `${formattedEventType} in ${formattedRegion}`,
89
+ link,
90
+ item: items,
91
+ };
92
+ },
93
+ };
94
+
95
+ function getAuthor(event: EventbriteEvent) {
96
+ return `<a href="${event.primary_organizer.url}">${event.primary_organizer.name}</a>`;
97
+ }
98
+
99
+ function getTicketPriceRange(event: EventbriteEvent) {
100
+ const minPrice = event.ticket_availability.minimum_ticket_price.display;
101
+ const maxPrice = event.ticket_availability.maximum_ticket_price.display;
102
+ if (minPrice === maxPrice) {
103
+ return minPrice;
104
+ }
105
+ return `${minPrice} - ${maxPrice}`;
106
+ }
107
+
108
+ function eventDate(event: EventbriteEvent): string {
109
+ const endDate = event.end_date;
110
+ const startDate = event.start_date;
111
+ const startTime = event.start_time;
112
+ const endTime = event.end_time;
113
+ if (startDate === endDate) {
114
+ return `${startDate} ${startTime} - ${endTime}`;
115
+ }
116
+ return `${startDate} ${startTime} - ${endDate} ${endTime}`;
117
+ }
118
+
119
+ interface EventbriteEvent {
120
+ name: string;
121
+ url: string;
122
+ published: string;
123
+ primary_organizer: {
124
+ name: string;
125
+ url: string;
126
+ };
127
+ summary: string;
128
+ tags: {
129
+ display_name: string;
130
+ }[];
131
+ image: {
132
+ url: string;
133
+ original: {
134
+ url: string;
135
+ };
136
+ };
137
+ eventbrite_event_id: string;
138
+ ticket_availability: {
139
+ maximum_ticket_price: {
140
+ display: string;
141
+ };
142
+ minimum_ticket_price: {
143
+ display: string;
144
+ };
145
+ is_free: boolean;
146
+ };
147
+ timezone: string;
148
+ start_date: string;
149
+ start_time: string;
150
+ end_date: string;
151
+ end_time: string;
152
+ }