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,10 +1,6 @@
|
|
|
1
1
|
import ofetch from '@/utils/ofetch';
|
|
2
2
|
import * as cheerio from 'cheerio';
|
|
3
3
|
import { parseDate } from '@/utils/parse-date';
|
|
4
|
-
// import MarkdownIt from 'markdown-it';
|
|
5
|
-
// const md = MarkdownIt({
|
|
6
|
-
// html: true,
|
|
7
|
-
// });
|
|
8
4
|
import crypto from 'node:crypto';
|
|
9
5
|
import cache from '@/utils/cache';
|
|
10
6
|
import { Category, Collection, Tag } from './types';
|
|
@@ -18,22 +14,6 @@ const s256 = (s1: Uint8Array, s2: string) => {
|
|
|
18
14
|
return sha.digest('hex');
|
|
19
15
|
};
|
|
20
16
|
|
|
21
|
-
// 加载文章页
|
|
22
|
-
// async function loadContent(id) {
|
|
23
|
-
// const response = await ofetch('https://api.juejin.cn/content_api/v1/article/detail', {
|
|
24
|
-
// method: 'post',
|
|
25
|
-
// body: {
|
|
26
|
-
// article_id: id,
|
|
27
|
-
// },
|
|
28
|
-
// });
|
|
29
|
-
// let description;
|
|
30
|
-
// if (response.data) {
|
|
31
|
-
// description = md.render(response.data.article_info.mark_content) || response.data.article_info.content;
|
|
32
|
-
// }
|
|
33
|
-
|
|
34
|
-
// return { description };
|
|
35
|
-
// }
|
|
36
|
-
|
|
37
17
|
const solveWafChallenge = (cs) => {
|
|
38
18
|
const c = JSON.parse(Buffer.from(cs, 'base64').toString());
|
|
39
19
|
const prefix = b64tou8a(c.v.a);
|
|
@@ -49,6 +29,11 @@ const solveWafChallenge = (cs) => {
|
|
|
49
29
|
return Buffer.from(JSON.stringify(c)).toString('base64');
|
|
50
30
|
};
|
|
51
31
|
|
|
32
|
+
export const generateUuid = () => {
|
|
33
|
+
const e = (t) => (t ? (t ^ ((16 * 0.5) >> (t / 4))).toString(10) : '10000000-1000-4000-8000-100000000000'.replaceAll(/[018]/g, e));
|
|
34
|
+
return e().replaceAll('-', '').slice(0, 19);
|
|
35
|
+
};
|
|
36
|
+
|
|
52
37
|
export const getArticle = async (link) => {
|
|
53
38
|
let response = await ofetch(link);
|
|
54
39
|
let $ = cheerio.load(response);
|
|
@@ -70,16 +55,23 @@ export const getArticle = async (link) => {
|
|
|
70
55
|
return $('.article-viewer').html();
|
|
71
56
|
};
|
|
72
57
|
|
|
73
|
-
// const loadNews = async (link) => {
|
|
74
|
-
// const response = await ofetch(link);
|
|
75
|
-
// const $ = cheerio.load(response);
|
|
76
|
-
// $('h1.title, .main-box .message').remove();
|
|
77
|
-
// return { description: $('.main-box .article').html() };
|
|
78
|
-
// };
|
|
79
|
-
|
|
80
58
|
export const parseList = (data) =>
|
|
81
59
|
data.map((item) => {
|
|
82
60
|
const isArticle = !!item.article_info;
|
|
61
|
+
const isShortMsg = !!item.msg_Info;
|
|
62
|
+
if (isShortMsg) {
|
|
63
|
+
const msg = item.msg_Info;
|
|
64
|
+
const contentMatches = msg.content.match(/\[(.*?)\]\s+(.*)/);
|
|
65
|
+
return {
|
|
66
|
+
title: contentMatches?.[1],
|
|
67
|
+
description: contentMatches?.[2]?.trim(),
|
|
68
|
+
pubDate: parseDate(msg.ctime, 'X'),
|
|
69
|
+
author: item.author_user_info.user_name,
|
|
70
|
+
link: `https://juejin.cn/pin/7548882523352186915${item.msg_id}`,
|
|
71
|
+
category: [item.topic.title],
|
|
72
|
+
isShortMsg: true,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
83
75
|
|
|
84
76
|
return {
|
|
85
77
|
title: isArticle ? item.article_info.title : item.content_info.title,
|
|
@@ -95,36 +87,13 @@ export const ProcessFeed = (list) =>
|
|
|
95
87
|
Promise.all(
|
|
96
88
|
list.map((item) =>
|
|
97
89
|
cache.tryGet(item.link, async () => {
|
|
98
|
-
item.description = (await getArticle(item.link)) || item.description;
|
|
90
|
+
item.description = item.isShortMsg ? item.description : (await getArticle(item.link)) || item.description;
|
|
99
91
|
|
|
100
92
|
return item;
|
|
101
93
|
})
|
|
102
94
|
)
|
|
103
95
|
);
|
|
104
96
|
|
|
105
|
-
// export const ProcessFeed = (list, caches) =>
|
|
106
|
-
// Promise.all(
|
|
107
|
-
// list.map(async (item) => {
|
|
108
|
-
// const isArticle = !!item.article_info;
|
|
109
|
-
// const pubDate = parseDate((isArticle ? item.article_info.ctime : item.content_info.ctime) * 1000);
|
|
110
|
-
// const link = `https://juejin.cn${isArticle ? '/post/' + item.article_id : '/news/' + item.content_id}`;
|
|
111
|
-
// // 列表上提取到的信息
|
|
112
|
-
// const single = {
|
|
113
|
-
// title: isArticle ? item.article_info.title : item.content_info.title,
|
|
114
|
-
// description: ((isArticle ? item.article_info.brief_content : item.content_info.brief) || '无描述').replaceAll(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, ''),
|
|
115
|
-
// pubDate,
|
|
116
|
-
// author: item.author_user_info.user_name,
|
|
117
|
-
// link,
|
|
118
|
-
// };
|
|
119
|
-
|
|
120
|
-
// // 使用tryGet方法从缓存获取内容。
|
|
121
|
-
// // 当缓存中无法获取到链接内容的时候,则使用load方法加载文章内容。
|
|
122
|
-
// const other = await caches.tryGet(link, () => (isArticle ? loadContent(item.article_id) : loadNews(link)));
|
|
123
|
-
// // 合并解析后的结果集作为该篇文章最终的输出结果
|
|
124
|
-
// return { ...single, ...other };
|
|
125
|
-
// })
|
|
126
|
-
// );
|
|
127
|
-
|
|
128
97
|
export const getCategoryBrief = () =>
|
|
129
98
|
cache.tryGet('juejin:categoryBriefs', async () => {
|
|
130
99
|
const response = await ofetch('https://api.juejin.cn/tag_api/v1/query_category_briefs');
|
|
@@ -152,3 +121,19 @@ export const getTag = (tag) =>
|
|
|
152
121
|
});
|
|
153
122
|
return response.data;
|
|
154
123
|
}) as Promise<{ tag_id: string; tag: Tag }>;
|
|
124
|
+
|
|
125
|
+
export const getTagList = () =>
|
|
126
|
+
cache.tryGet('juejin:tagList', async () => {
|
|
127
|
+
const response = await ofetch('https://api.juejin.cn/tag_api/v1/query_tag_list', {
|
|
128
|
+
method: 'POST',
|
|
129
|
+
body: {
|
|
130
|
+
key_word: '',
|
|
131
|
+
status: [0],
|
|
132
|
+
id_type: 1101,
|
|
133
|
+
sort_type: 0,
|
|
134
|
+
cursor: '0',
|
|
135
|
+
limit: 100,
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
return response.data;
|
|
139
|
+
}) as Promise<{ tag_id: string; tag: Tag }[]>;
|
|
@@ -49,7 +49,7 @@ async function handler(ctx: Context): Promise<Data> {
|
|
|
49
49
|
const episodes = values.filter((value) => value.__typename === 'Episode') as NextDataEpisode[];
|
|
50
50
|
const items = (await Promise.all(
|
|
51
51
|
episodes
|
|
52
|
-
.
|
|
52
|
+
.toSorted((a, b) => b.publishedAt.localeCompare(a.publishedAt))
|
|
53
53
|
.slice(0, limit)
|
|
54
54
|
.map((item) => {
|
|
55
55
|
const episodeUrl = `https://kakuyomu.jp/works/${id}/episodes/${item.id}`;
|
|
@@ -63,7 +63,7 @@ export const route: Route = {
|
|
|
63
63
|
::: tip
|
|
64
64
|
When \`posts\` is selected as the value of the parameter **source**, the parameter **id** does not take effect.
|
|
65
65
|
There is an optinal parameter **limit** which controls the number of posts to fetch, default value is 25.
|
|
66
|
-
|
|
66
|
+
|
|
67
67
|
Support for announcements and fancards:
|
|
68
68
|
- Use \`/:source/:id/announcements\` to get announcements
|
|
69
69
|
- Use \`/:source/:id/fancards\` to get fancards
|
|
@@ -201,7 +201,7 @@ async function processDiscordMessages(channels: any[], limit: number) {
|
|
|
201
201
|
|
|
202
202
|
return channelResponse.data
|
|
203
203
|
.filter((message: DiscordMessage) => message.content || message.attachments)
|
|
204
|
-
.
|
|
204
|
+
.toSorted((a, b) => b.id.localeCompare(a.id))
|
|
205
205
|
.slice(0, limit)
|
|
206
206
|
.map((message: DiscordMessage) => ({
|
|
207
207
|
title: message.content || 'Discord Message',
|
|
@@ -62,7 +62,7 @@ async function handler(ctx) {
|
|
|
62
62
|
},
|
|
63
63
|
});
|
|
64
64
|
|
|
65
|
-
const sortedChapters = chapterData.data.chaptersByComicId.
|
|
65
|
+
const sortedChapters = chapterData.data.chaptersByComicId.toSorted((a, b) => Date.parse(b.dateUpdated) - Date.parse(a.dateUpdated));
|
|
66
66
|
|
|
67
67
|
const chapterLimit = Number(limit) || sortedChapters.length;
|
|
68
68
|
const filteredChapters = sortedChapters.slice(0, chapterLimit);
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { type Data, type DataItem, type Route, ViewType } from '@/types';
|
|
2
|
+
|
|
3
|
+
import { art } from '@/utils/render';
|
|
4
|
+
import cache from '@/utils/cache';
|
|
5
|
+
import ofetch from '@/utils/ofetch';
|
|
6
|
+
|
|
7
|
+
import { type CheerioAPI, type Cheerio, load } from 'cheerio';
|
|
8
|
+
import type { Element } from 'domhandler';
|
|
9
|
+
import { type Context } from 'hono';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
|
|
12
|
+
export const handler = async (ctx: Context): Promise<Data> => {
|
|
13
|
+
const { category = '0', sort = 'latest' } = ctx.req.param();
|
|
14
|
+
const limit: number = Number.parseInt(ctx.req.query('limit') ?? '30', 10);
|
|
15
|
+
|
|
16
|
+
const baseUrl: string = 'https://koyso.to';
|
|
17
|
+
const targetUrl: string = new URL(`?${category === '0' ? '' : `category=${category}&`}sort=${sort}`, baseUrl).href;
|
|
18
|
+
|
|
19
|
+
const response = await ofetch(targetUrl);
|
|
20
|
+
const $: CheerioAPI = load(response);
|
|
21
|
+
const language = $('html').attr('lang') ?? 'en';
|
|
22
|
+
|
|
23
|
+
let items: DataItem[] = [];
|
|
24
|
+
|
|
25
|
+
items = $('a.game_item')
|
|
26
|
+
.slice(0, limit)
|
|
27
|
+
.toArray()
|
|
28
|
+
.map((el): Element => {
|
|
29
|
+
const $el: Cheerio<Element> = $(el);
|
|
30
|
+
|
|
31
|
+
const title: string = $el.find('div.game_info').text();
|
|
32
|
+
const image: string | undefined = $el.find('div.game_media img').attr('data-src');
|
|
33
|
+
const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), {
|
|
34
|
+
images: image
|
|
35
|
+
? [
|
|
36
|
+
{
|
|
37
|
+
src: image,
|
|
38
|
+
alt: title,
|
|
39
|
+
},
|
|
40
|
+
]
|
|
41
|
+
: undefined,
|
|
42
|
+
});
|
|
43
|
+
const linkUrl: string | undefined = $el.attr('href');
|
|
44
|
+
|
|
45
|
+
const processedItem: DataItem = {
|
|
46
|
+
title,
|
|
47
|
+
description,
|
|
48
|
+
link: linkUrl ? new URL(linkUrl, baseUrl).href : undefined,
|
|
49
|
+
content: {
|
|
50
|
+
html: description,
|
|
51
|
+
text: description,
|
|
52
|
+
},
|
|
53
|
+
image,
|
|
54
|
+
banner: image,
|
|
55
|
+
language,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
return processedItem;
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
items = await Promise.all(
|
|
62
|
+
items.map((item) => {
|
|
63
|
+
if (!item.link) {
|
|
64
|
+
return item;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return cache.tryGet(item.link, async (): Promise<DataItem> => {
|
|
68
|
+
const detailResponse = await ofetch(item.link);
|
|
69
|
+
const $$: CheerioAPI = load(detailResponse);
|
|
70
|
+
|
|
71
|
+
$$('div.ind').remove();
|
|
72
|
+
$$('div.download_div').remove();
|
|
73
|
+
|
|
74
|
+
const title: string = $$('h1.content_title').text();
|
|
75
|
+
|
|
76
|
+
$$('h1.content_title').remove();
|
|
77
|
+
|
|
78
|
+
const description: string | undefined = item.description + ($$('div.game_content').html() || '');
|
|
79
|
+
|
|
80
|
+
const processedItem: DataItem = {
|
|
81
|
+
title,
|
|
82
|
+
description,
|
|
83
|
+
content: {
|
|
84
|
+
html: description,
|
|
85
|
+
text: description,
|
|
86
|
+
},
|
|
87
|
+
language,
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
...item,
|
|
92
|
+
...processedItem,
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
})
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
const categoryName: string = $(`ul.category li#category_${category}`).text();
|
|
99
|
+
const sortName: string = $(`div.genres_content ul li.${sort}`).text();
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
title: `${$('title').text()} - ${categoryName} - ${sortName}`,
|
|
103
|
+
description: $('meta[name="description"]').attr('content'),
|
|
104
|
+
link: targetUrl,
|
|
105
|
+
item: items,
|
|
106
|
+
allowEmpty: true,
|
|
107
|
+
language,
|
|
108
|
+
id: targetUrl,
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export const route: Route = {
|
|
113
|
+
path: '/:category?/:sort?',
|
|
114
|
+
name: '游戏',
|
|
115
|
+
url: 'koyso.to',
|
|
116
|
+
maintainers: ['nczitzk'],
|
|
117
|
+
handler,
|
|
118
|
+
example: '/koyso/0/latest',
|
|
119
|
+
parameters: {
|
|
120
|
+
category: {
|
|
121
|
+
description: '排序,默认为 `0`,即全部,可在对应分类页 URL 中找到',
|
|
122
|
+
options: [
|
|
123
|
+
{
|
|
124
|
+
label: '全部游戏',
|
|
125
|
+
value: '0',
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
label: '动作游戏',
|
|
129
|
+
value: '3',
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
label: '冒险游戏',
|
|
133
|
+
value: '5',
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
label: '绅士游戏',
|
|
137
|
+
value: '7',
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
label: '射击游戏',
|
|
141
|
+
value: '1',
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
label: '休闲游戏',
|
|
145
|
+
value: '2',
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
label: '体育竞速',
|
|
149
|
+
value: '4',
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
label: '模拟经营',
|
|
153
|
+
value: '6',
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
label: '角色扮演',
|
|
157
|
+
value: '8',
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
label: '策略游戏',
|
|
161
|
+
value: '9',
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
label: '格斗游戏',
|
|
165
|
+
value: '10',
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
label: '恐怖游戏',
|
|
169
|
+
value: '11',
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
label: '即时战略',
|
|
173
|
+
value: '12',
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
label: '卡牌游戏',
|
|
177
|
+
value: '13',
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
label: '独立游戏',
|
|
181
|
+
value: '14',
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
label: '局域网联机',
|
|
185
|
+
value: '15',
|
|
186
|
+
},
|
|
187
|
+
],
|
|
188
|
+
},
|
|
189
|
+
sort: {
|
|
190
|
+
description: '排序,默认为 `latest`,即最新,可在对应页 URL 中找到',
|
|
191
|
+
options: [
|
|
192
|
+
{
|
|
193
|
+
label: '热度',
|
|
194
|
+
value: 'views',
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
label: '最新',
|
|
198
|
+
value: 'latest',
|
|
199
|
+
},
|
|
200
|
+
],
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
description: `:::tip
|
|
204
|
+
订阅 [最新动作游戏](https://koyso.to/?category=3&sort=latest),其源网址为 \`https://koyso.to/?category=3&sort=latest\`,请参考该 URL 指定部分构成参数,此时路由为 [\`/koyso/3/latest\`](https://koyso.to/?category=3&sort=latest)。
|
|
205
|
+
:::
|
|
206
|
+
|
|
207
|
+
#### 分类
|
|
208
|
+
|
|
209
|
+
| 分类 | ID |
|
|
210
|
+
| ------------------------------------------- | --------------------------------- |
|
|
211
|
+
| [全部游戏](https://koyso.to/) | [0](https://rsshub.app/koyso/0) |
|
|
212
|
+
| [动作游戏](https://koyso.to/?category=3) | [3](https://rsshub.app/koyso/3) |
|
|
213
|
+
| [冒险游戏](https://koyso.to/?category=5) | [5](https://rsshub.app/koyso/5) |
|
|
214
|
+
| [绅士游戏](https://koyso.to/?category=7) | [7](https://rsshub.app/koyso/7) |
|
|
215
|
+
| [射击游戏](https://koyso.to/?category=1) | [1](https://rsshub.app/koyso/1) |
|
|
216
|
+
| [休闲游戏](https://koyso.to/?category=2) | [2](https://rsshub.app/koyso/2) |
|
|
217
|
+
| [体育竞速](https://koyso.to/?category=4) | [4](https://rsshub.app/koyso/4) |
|
|
218
|
+
| [模拟经营](https://koyso.to/?category=6) | [6](https://rsshub.app/koyso/6) |
|
|
219
|
+
| [角色扮演](https://koyso.to/?category=8) | [8](https://rsshub.app/koyso/8) |
|
|
220
|
+
| [策略游戏](https://koyso.to/?category=9) | [9](https://rsshub.app/koyso/9) |
|
|
221
|
+
| [格斗游戏](https://koyso.to/?category=10) | [10](https://rsshub.app/koyso/10) |
|
|
222
|
+
| [恐怖游戏](https://koyso.to/?category=11) | [11](https://rsshub.app/koyso/11) |
|
|
223
|
+
| [即时战略](https://koyso.to/?category=12) | [12](https://rsshub.app/koyso/12) |
|
|
224
|
+
| [卡牌游戏](https://koyso.to/?category=13) | [13](https://rsshub.app/koyso/13) |
|
|
225
|
+
| [独立游戏](https://koyso.to/?category=14) | [14](https://rsshub.app/koyso/14) |
|
|
226
|
+
| [局域网联机](https://koyso.to/?category=15) | [15](https://rsshub.app/koyso/15) |
|
|
227
|
+
|
|
228
|
+
#### 排序
|
|
229
|
+
|
|
230
|
+
| 排序 | ID |
|
|
231
|
+
| ------------------------------------- | ------------------------------------------- |
|
|
232
|
+
| [热度](https://koyso.to/?sort=views) | [views](https://rsshub.app/koyso/0/views) |
|
|
233
|
+
| [最新](https://koyso.to/?sort=latest) | [latest](https://rsshub.app/koyso/0/latest) |
|
|
234
|
+
`,
|
|
235
|
+
categories: ['game'],
|
|
236
|
+
features: {
|
|
237
|
+
requireConfig: false,
|
|
238
|
+
requirePuppeteer: false,
|
|
239
|
+
antiCrawler: false,
|
|
240
|
+
supportRadar: true,
|
|
241
|
+
supportBT: false,
|
|
242
|
+
supportPodcast: false,
|
|
243
|
+
supportScihub: false,
|
|
244
|
+
},
|
|
245
|
+
radar: [
|
|
246
|
+
{
|
|
247
|
+
source: ['koyso.to'],
|
|
248
|
+
target: (_, url) => {
|
|
249
|
+
const urlObj: URL = new URL(url);
|
|
250
|
+
const category: string | undefined = urlObj.searchParams.get('category') ?? undefined;
|
|
251
|
+
const sort: string | undefined = urlObj.searchParams.get('sort') ?? undefined;
|
|
252
|
+
|
|
253
|
+
return `/koyso${category ? `/${category}` : '0'}${sort ? `/${sort}` : ''}`;
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
title: '全部游戏',
|
|
258
|
+
source: ['koyso.to'],
|
|
259
|
+
target: '/0',
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
title: '动作游戏',
|
|
263
|
+
source: ['koyso.to'],
|
|
264
|
+
target: '/3',
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
title: '冒险游戏',
|
|
268
|
+
source: ['koyso.to'],
|
|
269
|
+
target: '/5',
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
title: '绅士游戏',
|
|
273
|
+
source: ['koyso.to'],
|
|
274
|
+
target: '/7',
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
title: '射击游戏',
|
|
278
|
+
source: ['koyso.to'],
|
|
279
|
+
target: '/1',
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
title: '休闲游戏',
|
|
283
|
+
source: ['koyso.to'],
|
|
284
|
+
target: '/2',
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
title: '体育竞速',
|
|
288
|
+
source: ['koyso.to'],
|
|
289
|
+
target: '/4',
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
title: '模拟经营',
|
|
293
|
+
source: ['koyso.to'],
|
|
294
|
+
target: '/6',
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
title: '角色扮演',
|
|
298
|
+
source: ['koyso.to'],
|
|
299
|
+
target: '/8',
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
title: '策略游戏',
|
|
303
|
+
source: ['koyso.to'],
|
|
304
|
+
target: '/9',
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
title: '格斗游戏',
|
|
308
|
+
source: ['koyso.to'],
|
|
309
|
+
target: '/10',
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
title: '恐怖游戏',
|
|
313
|
+
source: ['koyso.to'],
|
|
314
|
+
target: '/11',
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
title: '即时战略',
|
|
318
|
+
source: ['koyso.to'],
|
|
319
|
+
target: '/12',
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
title: '卡牌游戏',
|
|
323
|
+
source: ['koyso.to'],
|
|
324
|
+
target: '/13',
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
title: '独立游戏',
|
|
328
|
+
source: ['koyso.to'],
|
|
329
|
+
target: '/14',
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
title: '局域网联机',
|
|
333
|
+
source: ['koyso.to'],
|
|
334
|
+
target: '/15',
|
|
335
|
+
},
|
|
336
|
+
],
|
|
337
|
+
view: ViewType.Articles,
|
|
338
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Route } from '@/types';
|
|
2
|
+
import { namespace } from './namespace';
|
|
3
|
+
import ofetch from '@/utils/ofetch';
|
|
4
|
+
import cache from '@/utils/cache';
|
|
5
|
+
import { load } from 'cheerio';
|
|
6
|
+
import type { Context } from 'hono';
|
|
7
|
+
|
|
8
|
+
const baseUrl = `https://${namespace.url}`;
|
|
9
|
+
|
|
10
|
+
export const route: Route = {
|
|
11
|
+
path: '/:username/watchlist',
|
|
12
|
+
categories: ['social-media'],
|
|
13
|
+
example: '/letterboxd/matthew/watchlist',
|
|
14
|
+
parameters: {
|
|
15
|
+
username: 'Letterboxd username',
|
|
16
|
+
},
|
|
17
|
+
radar: [
|
|
18
|
+
{
|
|
19
|
+
source: ['letterboxd.com/:username/watchlist/'],
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
name: 'User Watchlist',
|
|
23
|
+
maintainers: ['johan456789'],
|
|
24
|
+
handler,
|
|
25
|
+
url: 'letterboxd.com',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
async function handler(ctx: Context) {
|
|
29
|
+
const { username } = ctx.req.param();
|
|
30
|
+
const currentUrl = `${baseUrl}/${username}/watchlist/`;
|
|
31
|
+
|
|
32
|
+
const html = await ofetch(currentUrl);
|
|
33
|
+
const $ = load(html);
|
|
34
|
+
|
|
35
|
+
const wrappers = $('div.react-component[data-component-class*="LazyPoster"]').toArray();
|
|
36
|
+
|
|
37
|
+
const items = await Promise.all(
|
|
38
|
+
wrappers.map(async (el) => {
|
|
39
|
+
const wrapper = $(el);
|
|
40
|
+
const linkPath = wrapper.attr('data-item-link') || wrapper.attr('data-target-link') || '';
|
|
41
|
+
const link = linkPath ? new URL(linkPath, baseUrl).href : undefined;
|
|
42
|
+
const title = (wrapper.attr('data-item-full-display-name') || wrapper.attr('data-item-name') || '') as string;
|
|
43
|
+
let image: string | undefined;
|
|
44
|
+
if (link) {
|
|
45
|
+
const posterApiUrl = `${link}poster/std/125/`;
|
|
46
|
+
const cacheKey = `letterboxd:poster:${posterApiUrl}`;
|
|
47
|
+
const posterData = await cache.tryGet<Record<string, any>>(cacheKey, () => ofetch(posterApiUrl, { responseType: 'json' }));
|
|
48
|
+
image = posterData.url2x || posterData.url;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
title,
|
|
53
|
+
link,
|
|
54
|
+
image,
|
|
55
|
+
};
|
|
56
|
+
})
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
title: $('title').text() || `${username}'s Watchlist • Letterboxd`,
|
|
61
|
+
link: currentUrl,
|
|
62
|
+
item: items,
|
|
63
|
+
allowEmpty: true,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -53,7 +53,7 @@ export const route: Route = {
|
|
|
53
53
|
title: each.vod_name,
|
|
54
54
|
image: each.vod_pic,
|
|
55
55
|
link: `https://${domain}/vod/${each.vod_id}/`,
|
|
56
|
-
guid: each.vod_play_url?.match(/https:\/\/.+?\.m3u8/g)?.
|
|
56
|
+
guid: each.vod_play_url?.match(/https:\/\/.+?\.m3u8/g)?.at(-1),
|
|
57
57
|
pubDate: timezone(parseDate(each.vod_time, 'YYYY-MM-DD HH:mm:ss'), +8),
|
|
58
58
|
category: [each.type_name, ...each.vod_class!.split(',')],
|
|
59
59
|
description: render(each, `https://${domain}/vod/${each.vod_id}/`) + each.vod_content,
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import crypto from 'node:crypto';
|
|
2
2
|
import { Buffer } from 'node:buffer';
|
|
3
3
|
import ofetch from '@/utils/ofetch';
|
|
4
|
-
import { v4 as uuidv4 } from 'uuid';
|
|
5
4
|
import { SearchResponse, ItemDetail, ShopItemDetail } from './types';
|
|
6
5
|
import { parseDate } from '@/utils/parse-date';
|
|
7
6
|
import { art } from '@/utils/render';
|
|
@@ -14,6 +13,7 @@ const rootShopProductURL = 'https://jp.mercari.com/shops/product/';
|
|
|
14
13
|
const searchURL = `${rootURL}v2/entities:search`;
|
|
15
14
|
const itemInfoURL = `${rootURL}items/get`;
|
|
16
15
|
const shopItemInfoURL = `${rootURL}v1/marketplaces/shops/products/`;
|
|
16
|
+
const uuidv4 = () => crypto.randomUUID();
|
|
17
17
|
|
|
18
18
|
const MercariStatus = {
|
|
19
19
|
default: '',
|
|
@@ -137,7 +137,7 @@ async function handler(ctx) {
|
|
|
137
137
|
? $(script)
|
|
138
138
|
.text()
|
|
139
139
|
?.match(/\$\('#lower'\)\.prepend\('(.*)'\);/)?.[1]
|
|
140
|
-
?.replaceAll(
|
|
140
|
+
?.replaceAll(String.raw`\"`, '"')
|
|
141
141
|
: '';
|
|
142
142
|
if (lowerContent) {
|
|
143
143
|
const $ = cheerio.load(lowerContent, null, false);
|