rsshub 1.0.0-master.f9b85f5 → 1.0.0-master.f9c381a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/config.ts +10 -0
- package/lib/middleware/cache.ts +4 -0
- package/lib/middleware/parameter.ts +1 -1
- package/lib/registry.ts +6 -2
- package/lib/routes/51cto/utils.ts +1 -1
- package/lib/routes/abc/index.ts +1 -1
- package/lib/routes/acgvinyl/namespace.ts +6 -0
- package/lib/routes/acgvinyl/news.ts +86 -0
- package/lib/routes/ally/rail.ts +1 -1
- package/lib/routes/anthropic/news.ts +13 -11
- package/lib/routes/apnews/mobile-api.ts +1 -1
- package/lib/routes/apnews/sitemap.ts +1 -1
- package/lib/routes/apple/apps.ts +2 -2
- package/lib/routes/apple/podcast.ts +58 -25
- package/lib/routes/bangumi.tv/group/reply.ts +1 -1
- package/lib/routes/bilibili/cache.ts +18 -2
- package/lib/routes/bilibili/dynamic.ts +31 -7
- package/lib/routes/bilibili/page.ts +1 -1
- package/lib/routes/bilibili/ranking.ts +23 -17
- package/lib/routes/bilibili/wasm-exec.ts +1 -1
- package/lib/routes/bilibili/weekly-recommend.ts +22 -6
- package/lib/routes/bjp/apod.ts +1 -1
- package/lib/routes/buaa/jiaowu.ts +1 -1
- package/lib/routes/bullionvault/gold-news.ts +3 -3
- package/lib/routes/cast/index.ts +1 -1
- package/lib/routes/coolapk/utils.ts +5 -4
- package/lib/routes/coolbuy/index.ts +106 -0
- package/lib/routes/{xinhuanet → coolbuy}/namespace.ts +3 -3
- package/lib/routes/coolbuy/templates/description.art +48 -0
- package/lib/routes/coolidge/film-guide.ts +60 -0
- package/lib/routes/coolidge/namespace.ts +7 -0
- package/lib/routes/coolidge/news.ts +65 -0
- package/lib/routes/coolidge/templates/description.art +4 -0
- package/lib/routes/copymanga/comic.ts +1 -1
- package/lib/routes/cpta/handler.ts +1 -1
- package/lib/routes/creative-comic/book.ts +1 -1
- package/lib/routes/daum/potplayer.ts +1 -1
- package/lib/routes/dockerhub/utils.ts +1 -1
- package/lib/routes/dora-world/article.ts +2 -2
- package/lib/routes/ehentai/ehapi.ts +3 -3
- package/lib/routes/eventbrite/events.ts +152 -0
- package/lib/routes/eventbrite/namespace.ts +7 -0
- package/lib/routes/github/activity.ts +1 -1
- package/lib/routes/google/jules.ts +63 -0
- package/lib/routes/gov/mem/namespace.ts +7 -0
- package/lib/routes/gov/mem/zfxxgkpt.ts +96 -0
- package/lib/routes/gov/mot/index.ts +158 -53
- package/lib/routes/hameln/chapter.ts +1 -1
- package/lib/routes/hpoi/banner-item.ts +28 -11
- package/lib/routes/hpoi/info.ts +1 -1
- package/lib/routes/huggingface/daily-papers.ts +1 -1
- package/lib/routes/hupu/index.ts +158 -74
- package/lib/routes/hupu/types.ts +163 -0
- package/lib/routes/hust/gs.ts +1 -1
- package/lib/routes/hust/mse.ts +1 -1
- package/lib/routes/huxiu/util.ts +2 -2
- package/lib/routes/infoq/presentations.ts +1 -1
- package/lib/routes/instagram/common-utils.ts +3 -3
- package/lib/routes/itch/devlog.ts +7 -3
- package/lib/routes/javbus/index.ts +1 -1
- package/lib/routes/javlibrary/utils.ts +1 -1
- package/lib/routes/jbma/namespace.ts +17 -0
- package/lib/routes/jbma/report.ts +473 -0
- package/lib/routes/jetbrains/comments.ts +1 -1
- package/lib/routes/jingzhengu/utils.ts +1 -1
- package/lib/routes/juejin/aicoding.ts +102 -0
- package/lib/routes/juejin/utils.ts +36 -51
- package/lib/routes/kakuyomu/works.ts +1 -1
- package/lib/routes/kemono/index.ts +2 -2
- package/lib/routes/komiic/comic.ts +1 -1
- package/lib/routes/koyso/index.ts +338 -0
- package/lib/routes/koyso/namespace.ts +9 -0
- package/lib/routes/koyso/templates/description.art +13 -0
- package/lib/routes/letterboxd/index.ts +65 -0
- package/lib/routes/letterboxd/namespace.ts +8 -0
- package/lib/routes/maccms/index.ts +1 -1
- package/lib/routes/mercari/util.ts +1 -1
- package/lib/routes/mingpao/index.ts +1 -1
- package/lib/routes/nankai/ai-notice.ts +142 -0
- package/lib/routes/nankai/graduate-notice.ts +162 -0
- package/lib/routes/natgeo/natgeo.ts +1 -0
- package/lib/routes/nhk/news-web-easy.ts +1 -1
- package/lib/routes/nicovideo/mylist.ts +39 -0
- package/lib/routes/nicovideo/types.ts +27 -0
- package/lib/routes/nicovideo/utils.ts +27 -1
- package/lib/routes/nikkei/cn/index.ts +1 -4
- package/lib/routes/now/news.ts +1 -1
- package/lib/routes/nytimes/index.ts +1 -1
- package/lib/routes/pixiv/novel-api/user-novels/sfw.ts +1 -1
- package/lib/routes/pixivision/utils.ts +1 -1
- package/lib/routes/pku/hr.ts +1 -1
- package/lib/routes/pku/scc/recruit.ts +1 -1
- package/lib/routes/producthunt/templates/description.art +2 -2
- package/lib/routes/producthunt/today.ts +17 -8
- package/lib/routes/ps/trophy.ts +1 -1
- package/lib/routes/pubscholar/utils.ts +14 -1
- package/lib/routes/qweather/3days.ts +14 -14
- package/lib/routes/qweather/now.ts +12 -10
- package/lib/routes/qweather/util.tsx +89 -0
- package/lib/routes/qwenlm/blog.ts +75 -0
- package/lib/routes/qwenlm/namespace.ts +6 -0
- package/lib/routes/radio-canada/latest.ts +30 -17
- package/lib/routes/ruc/ai.ts +1 -1
- package/lib/routes/ruc/hr.ts +1 -1
- package/lib/routes/samrdprc/index.ts +241 -0
- package/lib/routes/samrdprc/namespace.ts +1 -1
- package/lib/routes/sdo/ff14risingstones/api.ts +78 -0
- package/lib/routes/sdo/ff14risingstones/constant.ts +338 -0
- package/lib/routes/sdo/ff14risingstones/posts.ts +80 -0
- package/lib/routes/sdo/ff14risingstones/strats.ts +75 -0
- package/lib/routes/sdo/ff14risingstones/templates/duties-party.art +41 -0
- package/lib/routes/sdo/ff14risingstones/templates/fc-party.art +26 -0
- package/lib/routes/sdo/ff14risingstones/templates/novice-network-party.art +9 -0
- package/lib/routes/sdo/ff14risingstones/templates/rp-party.art +15 -0
- package/lib/routes/sdo/ff14risingstones/timeline.ts +31 -0
- package/lib/routes/sdo/ff14risingstones/types/dynamic.ts +50 -0
- package/lib/routes/sdo/ff14risingstones/types/index.ts +3 -0
- package/lib/routes/sdo/ff14risingstones/types/other.ts +57 -0
- package/lib/routes/sdo/ff14risingstones/types/party.ts +111 -0
- package/lib/routes/sdo/ff14risingstones/user-dynamics.ts +32 -0
- package/lib/routes/sdo/ff14risingstones/user-posts.ts +32 -0
- package/lib/routes/sdo/ff14risingstones/user-resently.ts +38 -0
- package/lib/routes/sdo/ff14risingstones/user-strats.ts +32 -0
- package/lib/routes/sdo/ff14risingstones/utils.ts +215 -0
- package/lib/routes/sdo/namespace.ts +7 -0
- package/lib/routes/showstart/utils.ts +1 -1
- package/lib/routes/sohu/mp.ts +1 -1
- package/lib/routes/sotwe/user.ts +1 -1
- package/lib/routes/surfshark/blog.ts +273 -77
- package/lib/routes/surfshark/templates/description.art +16 -4
- package/lib/routes/sustainabilitymag/articles.ts +1 -1
- package/lib/routes/syosetu/dev.ts +1 -1
- package/lib/routes/syosetu/ranking-isekai.ts +1 -1
- package/lib/routes/szse/disclosure/listed-notice.ts +44 -6
- package/lib/routes/telegram/channel-media.ts +249 -0
- package/lib/routes/telegram/channel.ts +5 -4
- package/lib/routes/telegram/stories.ts +130 -0
- package/lib/routes/telegram/tglib/channel.ts +136 -118
- package/lib/routes/telegram/tglib/client.ts +37 -139
- package/lib/routes/tesla/cx.ts +1 -1
- package/lib/routes/theverge/index.ts +20 -6
- package/lib/routes/threads/utils.ts +7 -3
- package/lib/routes/tidb/blog.ts +1 -1
- package/lib/routes/toutiao/user.ts +2 -2
- package/lib/routes/twitter/api/mobile-api/api.ts +1 -1
- package/lib/routes/txrjy/fornumtopic.ts +2 -2
- package/lib/routes/typst/universe.ts +1 -1
- package/lib/routes/uber/blog.ts +87 -46
- package/lib/routes/weibo/utils.ts +17 -9
- package/lib/routes/xiaohongshu/user.ts +1 -1
- package/lib/routes/xjtu/ee-jzxx.ts +1 -1
- package/lib/routes/yahoo/news/utils.ts +1 -1
- package/lib/routes/ymgal/article.ts +1 -1
- package/lib/routes/yoasobi-music/media.ts +1 -1
- package/lib/routes/youtube/api/youtubei.ts +1 -1
- package/lib/routes/youtube/community.ts +4 -4
- package/lib/routes/zaker/utils.ts +1 -1
- package/lib/routes/zaobao/util.tsx +1 -1
- package/lib/server.ts +1 -1
- package/lib/types.ts +1 -1
- package/lib/utils/puppeteer-utils.test.ts +2 -2
- package/lib/views/index.tsx +4 -4
- package/package.json +40 -40
- package/lib/routes/qweather/templates/3days.art +0 -22
- package/lib/routes/qweather/templates/now.art +0 -16
- package/lib/routes/xinhuanet/app.ts +0 -109
|
@@ -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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
}
|
package/lib/routes/bjp/apod.ts
CHANGED
|
@@ -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
|
-
.
|
|
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?.
|
|
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,
|
|
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 = $$('
|
|
70
|
-
const description: string | undefined = $$('div
|
|
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();
|
package/lib/routes/cast/index.ts
CHANGED
|
@@ -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')?.
|
|
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.
|
|
62
|
+
if (item.entityType === 'sponsorCard' || !item.url) {
|
|
62
63
|
return;
|
|
63
64
|
}
|
|
64
|
-
const itemUrl = item.
|
|
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.
|
|
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: '
|
|
6
|
-
categories: ['
|
|
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,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
|
+
};
|
|
@@ -50,7 +50,7 @@ const handler: Route['handler'] = async (ctx) => {
|
|
|
50
50
|
link: absoluteLink,
|
|
51
51
|
};
|
|
52
52
|
})
|
|
53
|
-
.
|
|
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
|
-
.
|
|
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()?.
|
|
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
|
-
.
|
|
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
|
-
?.
|
|
86
|
-
?.
|
|
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 =
|
|
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('
|
|
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
|
+
}
|