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
package/lib/config.ts
CHANGED
|
@@ -187,6 +187,7 @@ export type Config = {
|
|
|
187
187
|
};
|
|
188
188
|
hefeng: {
|
|
189
189
|
key?: string;
|
|
190
|
+
apiHost?: string;
|
|
190
191
|
};
|
|
191
192
|
infzm: {
|
|
192
193
|
cookie?: string;
|
|
@@ -325,6 +326,10 @@ export type Config = {
|
|
|
325
326
|
scihub: {
|
|
326
327
|
host?: string;
|
|
327
328
|
};
|
|
329
|
+
sdo: {
|
|
330
|
+
ff14risingstones?: string;
|
|
331
|
+
ua?: string;
|
|
332
|
+
};
|
|
328
333
|
sis001: {
|
|
329
334
|
baseUrl?: string;
|
|
330
335
|
};
|
|
@@ -659,6 +664,7 @@ const calculateValue = () => {
|
|
|
659
664
|
},
|
|
660
665
|
hefeng: {
|
|
661
666
|
key: envs.HEFENG_KEY,
|
|
667
|
+
apiHost: envs.HEFENG_API_HOST,
|
|
662
668
|
},
|
|
663
669
|
infzm: {
|
|
664
670
|
cookie: envs.INFZM_COOKIE,
|
|
@@ -797,6 +803,10 @@ const calculateValue = () => {
|
|
|
797
803
|
scihub: {
|
|
798
804
|
host: envs.SCIHUB_HOST || 'https://sci-hub.se/',
|
|
799
805
|
},
|
|
806
|
+
sdo: {
|
|
807
|
+
ff14risingstones: envs.SDO_FF14RISINGSTONES,
|
|
808
|
+
ua: envs.SDO_UA,
|
|
809
|
+
},
|
|
800
810
|
sis001: {
|
|
801
811
|
baseUrl: envs.SIS001_BASE_URL || 'https://sis001.com',
|
|
802
812
|
},
|
package/lib/middleware/cache.ts
CHANGED
|
@@ -55,6 +55,10 @@ const middleware: MiddlewareHandler = async (ctx, next) => {
|
|
|
55
55
|
// Doesn't hit the cache? We need to let others know!
|
|
56
56
|
await cacheModule.globalCache.set(controlKey, '1', config.cache.requestTimeout);
|
|
57
57
|
|
|
58
|
+
// let routers control cache
|
|
59
|
+
ctx.set('cacheKey', key);
|
|
60
|
+
ctx.set('cacheControlKey', controlKey);
|
|
61
|
+
|
|
58
62
|
try {
|
|
59
63
|
await next();
|
|
60
64
|
} catch (error) {
|
|
@@ -80,7 +80,7 @@ const middleware: MiddlewareHandler = async (ctx, next) => {
|
|
|
80
80
|
|
|
81
81
|
// sort items
|
|
82
82
|
if (ctx.req.query('sorted') !== 'false') {
|
|
83
|
-
data.item = data.item.
|
|
83
|
+
data.item = data.item.toSorted((a: DataItem, b: DataItem) => +new Date(b.pubDate || 0) - +new Date(a.pubDate || 0));
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
const handleItem = (item: DataItem) => {
|
package/lib/registry.ts
CHANGED
|
@@ -155,7 +155,7 @@ const sortRoutes = (
|
|
|
155
155
|
}
|
|
156
156
|
>
|
|
157
157
|
) =>
|
|
158
|
-
Object.entries(routes).
|
|
158
|
+
Object.entries(routes).toSorted(([pathA], [pathB]) => {
|
|
159
159
|
const segmentsA = pathA.split('/');
|
|
160
160
|
const segmentsB = pathB.split('/');
|
|
161
161
|
const lenA = segmentsA.length;
|
|
@@ -198,7 +198,11 @@ for (const namespace in namespaces) {
|
|
|
198
198
|
routeData.handler = route.handler;
|
|
199
199
|
}
|
|
200
200
|
}
|
|
201
|
-
|
|
201
|
+
const response = await routeData.handler(ctx);
|
|
202
|
+
if (response instanceof Response) {
|
|
203
|
+
return response;
|
|
204
|
+
}
|
|
205
|
+
ctx.set('data', response);
|
|
202
206
|
}
|
|
203
207
|
};
|
|
204
208
|
subApp.get(path, wrappedHandler);
|
|
@@ -16,6 +16,6 @@ export const getToken = () =>
|
|
|
16
16
|
export const sign = (requestPath: string, payload: Record<string, any> = {}, timestamp: number, token: string) => {
|
|
17
17
|
payload.timestamp = timestamp;
|
|
18
18
|
payload.token = token;
|
|
19
|
-
const sortedParams = Object.keys(payload).
|
|
19
|
+
const sortedParams = Object.keys(payload).toSorted();
|
|
20
20
|
return md5(md5(requestPath) + md5(sortedParams + md5(token) + timestamp));
|
|
21
21
|
};
|
package/lib/routes/abc/index.ts
CHANGED
|
@@ -132,7 +132,7 @@ async function handler(ctx) {
|
|
|
132
132
|
if (enclosureMatches) {
|
|
133
133
|
const enclosureMatch = enclosureMatches
|
|
134
134
|
.map((e) => e.match(new RegExp(enclosurePattern)))
|
|
135
|
-
.
|
|
135
|
+
.toSorted((a, b) => Number.parseInt(a[2], 10) - Number.parseInt(b[2], 10))
|
|
136
136
|
.pop();
|
|
137
137
|
|
|
138
138
|
item.enclosure_url = enclosureMatch[3];
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Route } from '@/types';
|
|
2
|
+
import cache from '@/utils/cache';
|
|
3
|
+
import ofetch from '@/utils/ofetch';
|
|
4
|
+
import { load } from 'cheerio';
|
|
5
|
+
import { parseDate } from '@/utils/parse-date';
|
|
6
|
+
|
|
7
|
+
export const route: Route = {
|
|
8
|
+
path: '/news',
|
|
9
|
+
categories: ['anime'],
|
|
10
|
+
example: '/news',
|
|
11
|
+
features: {
|
|
12
|
+
requireConfig: false,
|
|
13
|
+
requirePuppeteer: false,
|
|
14
|
+
antiCrawler: false,
|
|
15
|
+
supportBT: false,
|
|
16
|
+
supportPodcast: false,
|
|
17
|
+
supportScihub: false,
|
|
18
|
+
},
|
|
19
|
+
radar: [
|
|
20
|
+
{
|
|
21
|
+
source: ['www.acgvinyl.com'],
|
|
22
|
+
target: '/news',
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
name: 'News',
|
|
26
|
+
maintainers: ['williamgateszhao'],
|
|
27
|
+
handler,
|
|
28
|
+
url: 'www.acgvinyl.com/col.jsp?id=103',
|
|
29
|
+
zh: {
|
|
30
|
+
name: '黑胶新闻',
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
async function handler(ctx) {
|
|
35
|
+
const rootUrl = 'http://www.acgvinyl.com';
|
|
36
|
+
|
|
37
|
+
const newsIndexResponse = await ofetch(`${rootUrl}/col.jsp?id=103`);
|
|
38
|
+
const $ = load(newsIndexResponse);
|
|
39
|
+
const newsIndexJsonText = $('script:contains("window.__INITIAL_STATE__")').text().replaceAll('window.__INITIAL_STATE__=', '');
|
|
40
|
+
const newsIndexJson = JSON.parse(newsIndexJsonText);
|
|
41
|
+
|
|
42
|
+
const newsListResponse = await ofetch(`${rootUrl}/rajax/news_h.jsp?cmd=getWafNotCk_getList`, {
|
|
43
|
+
method: 'POST',
|
|
44
|
+
headers: {
|
|
45
|
+
'content-type': 'application/x-www-form-urlencoded',
|
|
46
|
+
},
|
|
47
|
+
body: new URLSearchParams({
|
|
48
|
+
page: '1',
|
|
49
|
+
pageSize: String(ctx.req.query('limit') ?? 20),
|
|
50
|
+
fromMid: newsIndexJson.modules.module366.id,
|
|
51
|
+
idList: `[${newsIndexJson.modules.module366.prop3}]`,
|
|
52
|
+
sortKey: newsIndexJson.modules.module366.blob0.sortKey,
|
|
53
|
+
sortType: newsIndexJson.modules.module366.blob0.sortType,
|
|
54
|
+
}).toString(),
|
|
55
|
+
});
|
|
56
|
+
const list = JSON.parse(newsListResponse);
|
|
57
|
+
|
|
58
|
+
if (!list?.success || !Array.isArray(list?.list)) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const items = await Promise.all(
|
|
63
|
+
list.list.map((item) =>
|
|
64
|
+
cache.tryGet(item.url, async () => {
|
|
65
|
+
const detailResponse = await ofetch(`${rootUrl}${item.url}`);
|
|
66
|
+
const $ = load(detailResponse);
|
|
67
|
+
const detailJsonText = $('script:contains("window.__INITIAL_STATE__")').text().replaceAll('window.__INITIAL_STATE__=', '');
|
|
68
|
+
const detailJson = JSON.parse(detailJsonText);
|
|
69
|
+
const detail = load(detailJson.modules.module2.newsInfo.content);
|
|
70
|
+
detail('[style]').removeAttr('style');
|
|
71
|
+
return {
|
|
72
|
+
title: item.title,
|
|
73
|
+
link: `${rootUrl}${item.url}`,
|
|
74
|
+
pubDate: parseDate(item.date),
|
|
75
|
+
description: detail.html(),
|
|
76
|
+
};
|
|
77
|
+
})
|
|
78
|
+
)
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
title: 'ACG Vinyl - 黑胶 - 黑胶新闻',
|
|
83
|
+
link: 'http://www.acgvinyl.com/col.jsp?id=103',
|
|
84
|
+
item: items,
|
|
85
|
+
};
|
|
86
|
+
}
|
package/lib/routes/ally/rail.ts
CHANGED
|
@@ -86,7 +86,7 @@ async function handler(ctx) {
|
|
|
86
86
|
uniqueItems.push(item!);
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
|
-
items = uniqueItems.
|
|
89
|
+
items = uniqueItems.toSorted((a, b) => b.pubDate - a.pubDate).slice(0, ctx.req.query('limit') || 20);
|
|
90
90
|
|
|
91
91
|
items = await Promise.all(
|
|
92
92
|
items.map((item) =>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import ofetch from '@/utils/ofetch';
|
|
2
2
|
import { load } from 'cheerio';
|
|
3
3
|
import cache from '@/utils/cache';
|
|
4
|
-
import { Route } from '@/types';
|
|
4
|
+
import { DataItem, Route } from '@/types';
|
|
5
5
|
import pMap from 'p-map';
|
|
6
6
|
|
|
7
7
|
export const route: Route = {
|
|
@@ -20,18 +20,20 @@ export const route: Route = {
|
|
|
20
20
|
url: 'www.anthropic.com/news',
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
-
async function handler() {
|
|
23
|
+
async function handler(ctx) {
|
|
24
24
|
const link = 'https://www.anthropic.com/news';
|
|
25
25
|
const response = await ofetch(link);
|
|
26
26
|
const $ = load(response);
|
|
27
|
+
const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 20;
|
|
27
28
|
|
|
28
|
-
const list = $('.contentFadeUp a')
|
|
29
|
+
const list: DataItem[] = $('.contentFadeUp a')
|
|
29
30
|
.toArray()
|
|
30
|
-
.
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
const
|
|
31
|
+
.slice(0, limit)
|
|
32
|
+
.map((el) => {
|
|
33
|
+
const $el = $(el);
|
|
34
|
+
const title = $el.find('h3').text().trim();
|
|
35
|
+
const href = $el.attr('href') ?? '';
|
|
36
|
+
const pubDate = $el.find('p.detail-m.agate').text().trim() || $el.find('div[class^="PostList_post-date__"]').text().trim(); // legacy selector used roughly before Jan 2025
|
|
35
37
|
const fullLink = href.startsWith('http') ? href : `https://www.anthropic.com${href}`;
|
|
36
38
|
return {
|
|
37
39
|
title,
|
|
@@ -43,8 +45,8 @@ async function handler() {
|
|
|
43
45
|
const out = await pMap(
|
|
44
46
|
list,
|
|
45
47
|
(item) =>
|
|
46
|
-
cache.tryGet(item.link
|
|
47
|
-
const response = await ofetch(item.link);
|
|
48
|
+
cache.tryGet(item.link!, async () => {
|
|
49
|
+
const response = await ofetch(item.link!);
|
|
48
50
|
const $ = load(response);
|
|
49
51
|
|
|
50
52
|
$('div[class^="PostDetail_b-social-share"]').remove();
|
|
@@ -61,7 +63,7 @@ async function handler() {
|
|
|
61
63
|
}
|
|
62
64
|
});
|
|
63
65
|
|
|
64
|
-
item.description = content.html();
|
|
66
|
+
item.description = content.html() ?? undefined;
|
|
65
67
|
|
|
66
68
|
return item;
|
|
67
69
|
}),
|
|
@@ -81,7 +81,7 @@ async function handler(ctx) {
|
|
|
81
81
|
}
|
|
82
82
|
})
|
|
83
83
|
.filter(Boolean)
|
|
84
|
-
.
|
|
84
|
+
.toSorted((a, b) => b.pubDate - a.pubDate)
|
|
85
85
|
.slice(0, ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 20);
|
|
86
86
|
|
|
87
87
|
const items = ctx.req.query('fulltext') === 'true' ? await pMap(list, (item) => fetchArticle(item), { concurrency: 10 }) : list;
|
|
@@ -79,7 +79,7 @@ async function handler(ctx) {
|
|
|
79
79
|
return res;
|
|
80
80
|
})
|
|
81
81
|
.filter((e) => Boolean(e.link) && !new URL(e.link).pathname.split('/').includes('hub'))
|
|
82
|
-
.
|
|
82
|
+
.toSorted((a, b) => (a.pubDate && b.pubDate ? b.pubDate - a.pubDate : b.lastmod - a.lastmod))
|
|
83
83
|
.slice(0, ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 20);
|
|
84
84
|
|
|
85
85
|
const items = ctx.req.query('fulltext') === 'true' ? await pMap(list, (item) => fetchArticle(item), { concurrency: 20 }) : list;
|
package/lib/routes/apple/apps.ts
CHANGED
|
@@ -130,7 +130,7 @@ async function handler(ctx) {
|
|
|
130
130
|
return {
|
|
131
131
|
title: `${appName} ${item.versionDisplay} for ${p}`,
|
|
132
132
|
link: currentUrl,
|
|
133
|
-
description: item.releaseNotes?.
|
|
133
|
+
description: item.releaseNotes?.replaceAll('\n', '<br>'),
|
|
134
134
|
category: [p],
|
|
135
135
|
guid: `apple/apps/${country}/${id}/${pid}#${item.versionDisplay}`,
|
|
136
136
|
pubDate: parseDate(item.releaseTimestamp),
|
|
@@ -147,7 +147,7 @@ async function handler(ctx) {
|
|
|
147
147
|
item: items,
|
|
148
148
|
title: `${title} - Apple App Store`,
|
|
149
149
|
link: currentUrl,
|
|
150
|
-
description: description?.
|
|
150
|
+
description: description?.replaceAll('\n', ' '),
|
|
151
151
|
language: $('html').prop('lang'),
|
|
152
152
|
image: $('meta[property="og:image"]').prop('content'),
|
|
153
153
|
icon,
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { Route } from '@/types';
|
|
2
|
-
import
|
|
2
|
+
import ofetch from '@/utils/ofetch';
|
|
3
3
|
import { load } from 'cheerio';
|
|
4
4
|
import { parseDate } from '@/utils/parse-date';
|
|
5
|
+
import cache from '@/utils/cache';
|
|
6
|
+
import { config } from '@/config';
|
|
5
7
|
|
|
6
8
|
export const route: Route = {
|
|
7
9
|
path: '/podcast/:id/:region?',
|
|
@@ -21,7 +23,7 @@ export const route: Route = {
|
|
|
21
23
|
},
|
|
22
24
|
radar: [
|
|
23
25
|
{
|
|
24
|
-
source: ['podcasts.apple.com/:region/podcast/:id'],
|
|
26
|
+
source: ['podcasts.apple.com/:region/podcast/:showName/:id', 'podcasts.apple.com/:region/podcast/:id'],
|
|
25
27
|
},
|
|
26
28
|
],
|
|
27
29
|
name: '播客',
|
|
@@ -32,42 +34,73 @@ export const route: Route = {
|
|
|
32
34
|
|
|
33
35
|
async function handler(ctx) {
|
|
34
36
|
const { id, region } = ctx.req.param();
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
url: link,
|
|
39
|
-
});
|
|
37
|
+
const numericId = id.match(/id(\d+)/)?.[1];
|
|
38
|
+
const baseUrl = 'https://podcasts.apple.com';
|
|
39
|
+
const link = `${baseUrl}/${region || `cn`}/podcast/${id}`;
|
|
40
40
|
|
|
41
|
-
const
|
|
41
|
+
const response = await ofetch(link);
|
|
42
42
|
|
|
43
|
-
const
|
|
44
|
-
const serializedServerData = JSON.parse($('#serialized-server-data').text());
|
|
43
|
+
const $ = load(response);
|
|
45
44
|
|
|
46
|
-
const
|
|
47
|
-
const originEpisodes = serializedServerData[0].data.shelves.find((item) => item.contentType === 'episode').items;
|
|
45
|
+
const serializedServerData = JSON.parse($('#serialized-server-data').text());
|
|
48
46
|
const header = serializedServerData[0].data.shelves.find((item) => item.contentType === 'showHeaderRegular').items[0];
|
|
49
47
|
|
|
50
|
-
const
|
|
48
|
+
const bearerToken = await cache.tryGet(
|
|
49
|
+
'apple:podcast:bearer',
|
|
50
|
+
async () => {
|
|
51
|
+
const moduleAddress = new URL($('head script[type="module"]').attr('src'), baseUrl).href;
|
|
52
|
+
const modulesResponse = await ofetch(moduleAddress, {
|
|
53
|
+
parseResponse: (txt) => txt,
|
|
54
|
+
});
|
|
55
|
+
const bearerToken = modulesResponse.match(/="(eyJhbGci.*?)",/)[1];
|
|
56
|
+
|
|
57
|
+
return bearerToken as string;
|
|
58
|
+
},
|
|
59
|
+
config.cache.contentExpire,
|
|
60
|
+
false
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const episodeReponse = await ofetch(`https://amp-api.podcasts.apple.com/v1/catalog/us/podcasts/${numericId}/episodes`, {
|
|
64
|
+
query: {
|
|
65
|
+
'extend[podcast-channels]': 'editorialArtwork,subscriptionArtwork,subscriptionOffers',
|
|
66
|
+
include: 'channel',
|
|
67
|
+
limit: 25,
|
|
68
|
+
with: 'entitlements',
|
|
69
|
+
l: 'en-US',
|
|
70
|
+
},
|
|
71
|
+
headers: {
|
|
72
|
+
Authorization: `Bearer ${bearerToken}`,
|
|
73
|
+
Origin: baseUrl,
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const episodes = episodeReponse.data.map(({ attributes: item }) => {
|
|
51
78
|
// Try to keep line breaks in the description
|
|
52
|
-
const
|
|
53
|
-
const episodeDescription = (matchedSeoEpisode ? matchedSeoEpisode.description : item.summary).replaceAll('\n', '<br>');
|
|
79
|
+
const offer = item.offers[0];
|
|
54
80
|
|
|
55
81
|
return {
|
|
56
|
-
title: item.
|
|
57
|
-
enclosure_url: item.
|
|
58
|
-
enclosure_type: 'audio/mp4',
|
|
59
|
-
itunes_duration: item.
|
|
60
|
-
link: item.
|
|
61
|
-
pubDate: parseDate(item.
|
|
62
|
-
description:
|
|
82
|
+
title: item.name,
|
|
83
|
+
enclosure_url: item.assetUrl || offer.hlsUrl,
|
|
84
|
+
enclosure_type: item.assetUrl ? 'audio/mp4' : 'application/vnd.apple.mpegurl',
|
|
85
|
+
itunes_duration: (item.durationInMilliseconds || offer.durationInMilliseconds) / 1000,
|
|
86
|
+
link: item.url,
|
|
87
|
+
pubDate: parseDate(item.releaseDateTime),
|
|
88
|
+
description: item.description.standard.replaceAll('\n', '<br>'),
|
|
89
|
+
author: item.artistName,
|
|
90
|
+
itunes_item_image: item.artwork.url.replace(/\{w\}x\{h\}(?:\{c\}|bb)\.\{f\}/, '3000x3000bb.webp'),
|
|
91
|
+
category: item.genreNames,
|
|
63
92
|
};
|
|
64
93
|
});
|
|
65
94
|
|
|
95
|
+
const channel = episodeReponse.data.find((d) => d.type === 'podcast-episodes').relationships.channel.data.find((d) => d.type === 'podcast-channels').attributes;
|
|
96
|
+
|
|
66
97
|
return {
|
|
67
|
-
title:
|
|
68
|
-
link:
|
|
98
|
+
title: channel.name,
|
|
99
|
+
link: channel.url,
|
|
69
100
|
itunes_author: header.contextAction.podcastOffer.author,
|
|
70
101
|
item: episodes,
|
|
71
|
-
description: header.description.replaceAll('\n', ' '),
|
|
102
|
+
description: (header.description || channel.description.standard).replaceAll('\n', ' '),
|
|
103
|
+
image: (channel.logoArtwork || channel.subscriptionArtwork).url.replace(/\{w\}x\{h\}(?:\{c\}|bb)\.\{f\}/, '3000x3000bb.webp'),
|
|
104
|
+
itunes_category: header.metadata.find((d) => Object.hasOwn(d, 'category')).category?.title || header.metadata.find((d) => Object.hasOwn(d, 'category')).category,
|
|
72
105
|
};
|
|
73
106
|
}
|
|
@@ -56,7 +56,7 @@ async function handler(ctx) {
|
|
|
56
56
|
date: $el.children().first().find('small').children().remove().end().text().slice(3),
|
|
57
57
|
};
|
|
58
58
|
});
|
|
59
|
-
const finalLatestReplies = [...latestReplies, ...latestSubReplies].
|
|
59
|
+
const finalLatestReplies = [...latestReplies, ...latestSubReplies].toSorted((a, b) => (a.id < b.id ? 1 : -1));
|
|
60
60
|
|
|
61
61
|
const postTopic = {
|
|
62
62
|
title,
|
|
@@ -6,6 +6,17 @@ import { config } from '@/config';
|
|
|
6
6
|
import logger from '@/utils/logger';
|
|
7
7
|
import { getPuppeteerPage } from '@/utils/puppeteer';
|
|
8
8
|
import { JSDOM } from 'jsdom';
|
|
9
|
+
import { RateLimiterMemory, RateLimiterQueue } from 'rate-limiter-flexible';
|
|
10
|
+
|
|
11
|
+
const subtitleLimiter = new RateLimiterMemory({
|
|
12
|
+
points: 5,
|
|
13
|
+
duration: 1,
|
|
14
|
+
execEvenly: true,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const subtitleLimiterQueue = new RateLimiterQueue(subtitleLimiter, {
|
|
18
|
+
maxQueueSize: 4800,
|
|
19
|
+
});
|
|
9
20
|
|
|
10
21
|
const getCookie = (disableConfig = false) => {
|
|
11
22
|
if (Object.keys(config.bilibili.cookies).length > 0 && !disableConfig) {
|
|
@@ -200,7 +211,7 @@ const getCidFromId = (aid, pid, bvid) => {
|
|
|
200
211
|
const { data } = await got(`https://api.bilibili.com/x/web-interface/view?${bvid ? `bvid=${bvid}` : `aid=${aid}`}`, {
|
|
201
212
|
referer: `https://www.bilibili.com/video/${bvid || `av${aid}`}`,
|
|
202
213
|
});
|
|
203
|
-
return data
|
|
214
|
+
return data?.data?.pages[pid - 1]?.cid;
|
|
204
215
|
});
|
|
205
216
|
};
|
|
206
217
|
|
|
@@ -244,8 +255,13 @@ const getVideoSubtitle = async (
|
|
|
244
255
|
}
|
|
245
256
|
|
|
246
257
|
const cid = await getCidFromId(undefined, 1, bvid);
|
|
258
|
+
if (!cid) {
|
|
259
|
+
return [];
|
|
260
|
+
}
|
|
261
|
+
|
|
247
262
|
const cookie = await getCookie();
|
|
248
263
|
return cache.tryGet(`bili-video-subtitle-${bvid}`, async () => {
|
|
264
|
+
await subtitleLimiterQueue.removeTokens(1);
|
|
249
265
|
const response = await got(`https://api.bilibili.com/x/player/wbi/v2?bvid=${bvid}&cid=${cid}`, {
|
|
250
266
|
headers: {
|
|
251
267
|
Referer: `https://www.bilibili.com/video/${bvid}`,
|
|
@@ -274,7 +290,7 @@ const getVideoSubtitle = async (
|
|
|
274
290
|
const getVideoSubtitleAttachment = async (bvid: string) => {
|
|
275
291
|
const subtitles = await getVideoSubtitle(bvid);
|
|
276
292
|
return subtitles.map((subtitle) => ({
|
|
277
|
-
url: `data:text/plain;charset=utf-8,${subtitle.content}`,
|
|
293
|
+
url: `data:text/plain;charset=utf-8,${encodeURIComponent(subtitle.content)}`,
|
|
278
294
|
mime_type: 'text/srt',
|
|
279
295
|
title: `字幕 - ${subtitle.lan_doc}`,
|
|
280
296
|
}));
|
|
@@ -126,34 +126,58 @@ const getIframe = (data?: Modules, embed: boolean = true) => {
|
|
|
126
126
|
};
|
|
127
127
|
|
|
128
128
|
const getImgs = (data?: Modules) => {
|
|
129
|
-
const imgUrls:
|
|
129
|
+
const imgUrls: {
|
|
130
|
+
url: string;
|
|
131
|
+
width?: number;
|
|
132
|
+
height?: number;
|
|
133
|
+
}[] = [];
|
|
130
134
|
const major = data?.module_dynamic?.major;
|
|
131
135
|
if (!major) {
|
|
132
136
|
return '';
|
|
133
137
|
}
|
|
134
138
|
// 动态图片
|
|
135
139
|
if (major.opus?.pics?.length) {
|
|
136
|
-
imgUrls.push(
|
|
140
|
+
imgUrls.push(
|
|
141
|
+
...major.opus.pics.map((e) => ({
|
|
142
|
+
url: e.url,
|
|
143
|
+
width: e.width,
|
|
144
|
+
height: e.height,
|
|
145
|
+
}))
|
|
146
|
+
);
|
|
137
147
|
}
|
|
138
148
|
// 专栏封面
|
|
139
149
|
if (major.article?.covers?.length) {
|
|
140
|
-
imgUrls.push(
|
|
150
|
+
imgUrls.push(
|
|
151
|
+
...major.article.covers.map((e) => ({
|
|
152
|
+
url: e,
|
|
153
|
+
}))
|
|
154
|
+
);
|
|
141
155
|
}
|
|
142
156
|
// 相簿
|
|
143
157
|
if (major.draw?.items?.length) {
|
|
144
|
-
imgUrls.push(
|
|
158
|
+
imgUrls.push(
|
|
159
|
+
...major.draw.items.map((e) => ({
|
|
160
|
+
url: e.src,
|
|
161
|
+
width: e.width,
|
|
162
|
+
height: e.height,
|
|
163
|
+
}))
|
|
164
|
+
);
|
|
145
165
|
}
|
|
146
166
|
// 正在直播的动态
|
|
147
167
|
if (major.live_rcmd?.content) {
|
|
148
|
-
imgUrls.push(
|
|
168
|
+
imgUrls.push({
|
|
169
|
+
url: JSON.parse(major.live_rcmd.content)?.live_play_info?.cover,
|
|
170
|
+
});
|
|
149
171
|
}
|
|
150
172
|
const type = major.type.replace('MAJOR_TYPE_', '').toLowerCase();
|
|
151
173
|
if (major[type]?.cover) {
|
|
152
|
-
imgUrls.push(
|
|
174
|
+
imgUrls.push({
|
|
175
|
+
url: major[type]?.cover,
|
|
176
|
+
});
|
|
153
177
|
}
|
|
154
178
|
return imgUrls
|
|
155
179
|
.filter(Boolean)
|
|
156
|
-
.map((
|
|
180
|
+
.map((img) => `<img src="${img.url}" ${img.width ? `width="${img.width}"` : ''} ${img.height ? `height="${img.height}"` : ''}>`)
|
|
157
181
|
.join('');
|
|
158
182
|
};
|
|
159
183
|
|
|
@@ -45,7 +45,7 @@ async function handler(ctx) {
|
|
|
45
45
|
link,
|
|
46
46
|
description: `视频 ${name} 的视频选集列表`,
|
|
47
47
|
item: data
|
|
48
|
-
.
|
|
48
|
+
.toSorted((a, b) => b.page - a.page)
|
|
49
49
|
.slice(0, ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 10)
|
|
50
50
|
.map((item) => ({
|
|
51
51
|
title: item.part,
|
|
@@ -2,6 +2,8 @@ import { Route, ViewType } from '@/types';
|
|
|
2
2
|
import got from '@/utils/got';
|
|
3
3
|
import utils, { getVideoUrl } from './utils';
|
|
4
4
|
import { parseDuration } from '@/utils/helpers';
|
|
5
|
+
import { config } from '@/config';
|
|
6
|
+
import cache from './cache';
|
|
5
7
|
|
|
6
8
|
// https://www.bilibili.com/v/popular/rank/all
|
|
7
9
|
|
|
@@ -212,22 +214,26 @@ async function handler(ctx) {
|
|
|
212
214
|
return {
|
|
213
215
|
title: `bilibili 排行榜-${ridChinese}`,
|
|
214
216
|
link,
|
|
215
|
-
item: list.map((item) =>
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
217
|
+
item: list.map(async (item) => {
|
|
218
|
+
const subtitles = !config.bilibili.excludeSubtitles && item.bvid ? await cache.getVideoSubtitleAttachment(item.bvid) : [];
|
|
219
|
+
return {
|
|
220
|
+
title: item.title,
|
|
221
|
+
description: utils.renderUGCDescription(embed, item.pic, item.description || item.title, item.aid, undefined, item.bvid),
|
|
222
|
+
pubDate: item.create && new Date(item.create).toUTCString(),
|
|
223
|
+
author: item.author,
|
|
224
|
+
link: !item.create || (new Date(item.create).getTime() / 1000 > utils.bvidTime && item.bvid) ? `https://www.bilibili.com/video/${item.bvid}` : `https://www.bilibili.com/video/av${item.aid}`,
|
|
225
|
+
image: item.pic,
|
|
226
|
+
attachments: item.bvid
|
|
227
|
+
? [
|
|
228
|
+
{
|
|
229
|
+
url: getVideoUrl(item.bvid),
|
|
230
|
+
mime_type: 'text/html',
|
|
231
|
+
duration_in_seconds: parseDuration(item.duration),
|
|
232
|
+
},
|
|
233
|
+
...subtitles,
|
|
234
|
+
]
|
|
235
|
+
: undefined,
|
|
236
|
+
};
|
|
237
|
+
}),
|
|
232
238
|
};
|
|
233
239
|
}
|