rsshub 1.0.0-master.f72af1b → 1.0.0-master.f7347d9
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 +18 -0
- package/lib/middleware/template.tsx +10 -2
- package/lib/routes/163/music/playlist.ts +2 -1
- package/lib/routes/5eplay/index.ts +1 -51
- package/lib/routes/a9vg/index.ts +214 -0
- package/lib/routes/a9vg/namespace.ts +1 -0
- package/lib/routes/a9vg/templates/description.art +17 -0
- package/lib/routes/aibase/discover.ts +388 -0
- package/lib/routes/aibase/namespace.ts +8 -0
- package/lib/routes/aibase/news.ts +118 -0
- package/lib/routes/aibase/templates/description.art +100 -0
- package/lib/routes/aibase/topic.ts +614 -0
- package/lib/routes/aibase/util.ts +114 -0
- package/lib/routes/apnews/api.ts +18 -3
- package/lib/routes/apnews/rss.ts +1 -1
- package/lib/routes/apnews/topics.ts +4 -3
- package/lib/routes/apple/podcast.ts +19 -15
- package/lib/routes/bangumi/templates/tv/subject.art +6 -0
- package/lib/routes/bangumi/tv/other/followrank.ts +19 -22
- package/lib/routes/bangumi/tv/user/collections.ts +172 -0
- package/lib/routes/bilibili/cache.ts +16 -8
- package/lib/routes/bilibili/utils.ts +85 -3
- package/lib/routes/bilibili/video.ts +7 -9
- package/lib/routes/bilibili/vsearch.ts +1 -1
- package/lib/routes/bjnews/cat.ts +9 -1
- package/lib/routes/cbpanet/index.ts +380 -0
- package/lib/routes/cbpanet/namespace.ts +8 -0
- package/lib/routes/ceph/blog.ts +72 -0
- package/lib/routes/ceph/namespace.ts +7 -0
- package/lib/routes/chinanews/index.ts +1 -1
- package/lib/routes/chinaventure/index.ts +1 -1
- package/lib/routes/cisia/index.ts +264 -0
- package/lib/routes/cisia/namespace.ts +8 -0
- package/lib/routes/cjlu/namespace.ts +10 -0
- package/lib/routes/cjlu/yjsy/index.ts +107 -0
- package/lib/routes/cnki/journals.ts +31 -2
- package/lib/routes/cohere/index.ts +54 -0
- package/lib/routes/cohere/namespace.ts +6 -0
- package/lib/routes/coolapk/dyh.ts +7 -1
- package/lib/routes/coolapk/hot.ts +7 -1
- package/lib/routes/coolapk/huati.ts +7 -1
- package/lib/routes/coolapk/namespace.ts +6 -0
- package/lib/routes/coolapk/toutiao.ts +7 -1
- package/lib/routes/coolapk/tuwen.ts +10 -5
- package/lib/routes/coolapk/user-dynamic.ts +7 -1
- package/lib/routes/damai/activity.ts +3 -2
- package/lib/routes/dcfever/trading.ts +3 -5
- package/lib/routes/dcfever/utils.ts +13 -4
- package/lib/routes/dealstreetasia/home.ts +72 -0
- package/lib/routes/dealstreetasia/namespace.ts +6 -0
- package/lib/routes/dealstreetasia/section.ts +57 -0
- package/lib/routes/dlsite/campaign.ts +1 -2
- package/lib/routes/dlsite/new.ts +1 -2
- package/lib/routes/dlsite/{index.ts → z-index/index.ts} +1 -1
- package/lib/routes/douban/other/topic.ts +10 -4
- package/lib/routes/douyin/types.ts +795 -0
- package/lib/routes/douyin/user.ts +51 -23
- package/lib/routes/douyin/utils.ts +1 -87
- package/lib/routes/dribbble/keyword.ts +1 -1
- package/lib/routes/dribbble/popular.ts +1 -1
- package/lib/routes/dribbble/user.ts +1 -1
- package/lib/routes/dribbble/utils.ts +16 -18
- package/lib/routes/famitsu/category.ts +1 -1
- package/lib/routes/fanbox/index.ts +1 -1
- package/lib/routes/fanbox/types.ts +1 -5
- package/lib/routes/fediverse/timeline.ts +21 -3
- package/lib/routes/follow/profile.ts +4 -2
- package/lib/routes/follow/types.ts +11 -1
- package/lib/routes/github/advisor.ts +97 -0
- package/lib/routes/github/discussions.ts +194 -0
- package/lib/routes/github/issue.ts +1 -1
- package/lib/routes/github/pulls.ts +1 -1
- package/lib/routes/gmcmonline/chinacustoms.ts +130 -0
- package/lib/routes/gmcmonline/namespace.ts +8 -0
- package/lib/routes/gov/csrc/csrc.ts +541 -0
- package/lib/routes/gov/customs/list.ts +5 -2
- package/lib/routes/gov/customs/namespace.ts +7 -0
- package/lib/routes/gov/jgjcndrc/index.ts +95 -66
- package/lib/routes/gov/moa/moa.ts +3 -3
- package/lib/routes/gov/moa/szcpxx.ts +115 -0
- package/lib/routes/gov/moa/zdscxx.ts +71 -21
- package/lib/routes/gov/ndrc/zfxxgk.ts +106 -0
- package/lib/routes/gov/pudong/zwgk.ts +65 -0
- package/lib/routes/gov/zj/search.ts +17 -12
- package/lib/routes/guancha/member.ts +1 -1
- package/lib/routes/hellogithub/index.ts +17 -67
- package/lib/routes/hellogithub/report.ts +1 -1
- package/lib/routes/hex-rays/index.ts +23 -29
- package/lib/routes/hfut/hf/notice.ts +43 -0
- package/lib/routes/hfut/hf/utils.ts +80 -0
- package/lib/routes/hfut/namespace.ts +6 -0
- package/lib/routes/hfut/xc/notice.ts +43 -0
- package/lib/routes/hfut/xc/utils.ts +73 -0
- package/lib/routes/hko/earthquake.ts +56 -0
- package/lib/routes/hko/namespace.ts +8 -0
- package/lib/routes/hko/weather.ts +56 -0
- package/lib/routes/huggingface/blog-zh.ts +1 -1
- package/lib/routes/hust/mse.ts +575 -0
- package/lib/routes/i-cable/namespace.ts +6 -0
- package/lib/routes/i-cable/news.ts +77 -0
- package/lib/routes/i-cable/templates/description.art +8 -0
- package/lib/routes/infoq/presentations.ts +203 -0
- package/lib/routes/infoq/templates/description.art +41 -0
- package/lib/routes/ipsw.dev/index.ts +64 -0
- package/lib/routes/ipsw.dev/namespace.ts +7 -0
- package/lib/routes/ipsw.dev/templates/description.art +20 -0
- package/lib/routes/ixigua/user-video.ts +18 -7
- package/lib/routes/javtiful/actress.ts +37 -0
- package/lib/routes/javtiful/channel.ts +37 -0
- package/lib/routes/javtiful/namespace.ts +6 -0
- package/lib/routes/javtiful/templates/description.art +7 -0
- package/lib/routes/javtiful/utils.ts +18 -0
- package/lib/routes/kisskiss/blog.ts +74 -0
- package/lib/routes/kisskiss/namespace.ts +6 -0
- package/lib/routes/ktown4u/artist-brandlist.ts +61 -0
- package/lib/routes/ktown4u/namespace.ts +6 -0
- package/lib/routes/lorientlejour/index.ts +186 -0
- package/lib/routes/lorientlejour/namespace.ts +7 -0
- package/lib/routes/lorientlejour/templates/description.art +18 -0
- package/lib/routes/lovelive-anime/namespace.ts +1 -1
- package/lib/routes/lovelive-anime/news.ts +56 -26
- package/lib/routes/lovelive-anime/schedules.ts +18 -6
- package/lib/routes/lovelive-anime/templates/description.art +1 -1
- package/lib/routes/lovelive-anime/templates/scheduleDesc.art +0 -1
- package/lib/routes/lovelive-anime/topics.ts +1 -1
- package/lib/routes/manhuagui/comic.ts +1 -1
- package/lib/routes/mi/crowdfunding.ts +43 -22
- package/lib/routes/mi/templates/crowdfunding.art +28 -0
- package/lib/routes/mi/types.ts +40 -0
- package/lib/routes/mi/utils.ts +80 -0
- package/lib/routes/misskey/utils.ts +1 -0
- package/lib/routes/misskon/namespace.ts +6 -0
- package/lib/routes/misskon/posts.ts +33 -0
- package/lib/routes/misskon/tag.ts +38 -0
- package/lib/routes/misskon/top.ts +67 -0
- package/lib/routes/misskon/utils.ts +41 -0
- package/lib/routes/natgeo/dailyphoto.ts +1 -1
- package/lib/routes/natgeo/dailyselection.ts +1 -1
- package/lib/routes/ncku/namespace.ts +1 -1
- package/lib/routes/ncku/phys.ts +95 -0
- package/lib/routes/news/namespace.ts +1 -1
- package/lib/routes/nikkei/cn/index.ts +12 -1
- package/lib/routes/nudt/yjszs.ts +33 -13
- package/lib/routes/nytimes/book.ts +11 -11
- package/lib/routes/nytimes/daily-briefing-chinese.ts +4 -4
- package/lib/routes/nytimes/namespace.ts +1 -1
- package/lib/routes/oncc/templates/article.art +1 -1
- package/lib/routes/papers/index.ts +7 -1
- package/lib/routes/parliament.uk/commonslibrary.ts +55 -0
- package/lib/routes/parliament.uk/lordslibrary.ts +55 -0
- package/lib/routes/parliament.uk/namespace.ts +6 -0
- package/lib/routes/picuki/profile.ts +50 -39
- package/lib/routes/pornhub/model.ts +3 -5
- package/lib/routes/pornhub/pornstar.ts +3 -5
- package/lib/routes/pornhub/users.ts +3 -5
- package/lib/routes/resonac/namespace.ts +6 -0
- package/lib/routes/resonac/products.ts +85 -0
- package/lib/routes/rsshub/transform/html.ts +114 -75
- package/lib/routes/rsshub/transform/json.ts +14 -8
- package/lib/routes/sciencenet/user.ts +3 -1
- package/lib/routes/scmp/utils.ts +2 -2
- package/lib/routes/skeb/following-creators.ts +52 -0
- package/lib/routes/skeb/following-works.ts +52 -0
- package/lib/routes/skeb/friend-works.ts +52 -0
- package/lib/routes/skeb/index.ts +131 -0
- package/lib/routes/skeb/namespace.ts +6 -0
- package/lib/routes/skeb/search.ts +77 -0
- package/lib/routes/skeb/templates/creator.art +8 -0
- package/lib/routes/skeb/templates/work.art +10 -0
- package/lib/routes/skeb/utils.ts +155 -0
- package/lib/routes/skeb/works.ts +88 -0
- package/lib/routes/skebetter/illust.ts +83 -0
- package/lib/routes/skebetter/index.ts +83 -0
- package/lib/routes/skebetter/manga.ts +65 -0
- package/lib/routes/skebetter/namespace.ts +6 -0
- package/lib/routes/skebetter/utils.ts +72 -0
- package/lib/routes/spankbang/namespace.ts +6 -0
- package/lib/routes/spankbang/new-videos.ts +90 -0
- package/lib/routes/spankbang/templates/video.art +7 -0
- package/lib/routes/straitstimes/index.ts +135 -0
- package/lib/routes/straitstimes/namespace.ts +7 -0
- package/lib/routes/straitstimes/templates/description.art +27 -0
- package/lib/routes/szftedu/dongtai.ts +62 -0
- package/lib/routes/szftedu/gonggao.ts +62 -0
- package/lib/routes/szftedu/namespace.ts +6 -0
- package/lib/routes/the/index.ts +1 -1
- package/lib/routes/tkww/index.ts +83 -0
- package/lib/routes/tkww/namespace.ts +6 -0
- package/lib/routes/ttv/index.ts +1 -1
- package/lib/routes/tvb/news.ts +2 -1
- package/lib/routes/twitter/api/mobile-api/login.ts +5 -0
- package/lib/routes/twitter/api/web-api/api.ts +16 -10
- package/lib/routes/twitter/api/web-api/constants.ts +1 -1
- package/lib/routes/twitter/api/web-api/utils.ts +94 -56
- package/lib/routes/twitter/list.ts +4 -12
- package/lib/routes/twitter/media.ts +13 -4
- package/lib/routes/twitter/user.ts +15 -6
- package/lib/routes/udn/breaking-news.ts +2 -2
- package/lib/routes/uestc/gr.ts +65 -37
- package/lib/routes/uestc/jwc.ts +49 -28
- package/lib/routes/wechat/ershcimi.ts +4 -2
- package/lib/routes/weibo/user.ts +10 -2
- package/lib/routes/xaut/index.ts +13 -20
- package/lib/routes/xaut/namespace.ts +1 -1
- package/lib/routes/xbookcn/blog.ts +66 -0
- package/lib/routes/xbookcn/namespace.ts +6 -0
- package/lib/routes/xiaoyuzhou/podcast.ts +35 -7
- package/lib/routes/xueqiu/cookies.ts +5 -3
- package/lib/routes/xueqiu/stock-info.ts +6 -12
- package/lib/routes/yande/namespace.ts +1 -1
- package/lib/routes/yande/post.ts +6 -6
- package/lib/routes/zaobao/util.ts +41 -43
- package/lib/routes/zhihu/activities.ts +2 -1
- package/lib/routes/zhihu/timeline.ts +6 -1
- package/lib/routes/zhihu/utils.ts +1 -1
- package/lib/routes/zjut/cs/index.ts +105 -0
- package/lib/routes/zjut/jwc/index.ts +117 -0
- package/lib/utils/cache/redis.ts +1 -1
- package/package.json +35 -36
- package/lib/routes/a9vg/a9vg.ts +0 -45
- package/lib/routes/gov/ndrc/zfxxgk/articles.ts +0 -73
- package/lib/routes-deprecated/hko/weather.js +0 -44
|
@@ -4,8 +4,11 @@ import { parseDate } from '@/utils/parse-date';
|
|
|
4
4
|
import { art } from '@/utils/render';
|
|
5
5
|
import { config } from '@/config';
|
|
6
6
|
import { fallback, queryToBoolean } from '@/utils/readable-social';
|
|
7
|
-
import { templates, resolveUrl, proxyVideo, getOriginAvatar
|
|
7
|
+
import { templates, resolveUrl, proxyVideo, getOriginAvatar } from './utils';
|
|
8
8
|
import InvalidParameterError from '@/errors/types/invalid-parameter';
|
|
9
|
+
import puppeteer from '@/utils/puppeteer';
|
|
10
|
+
import logger from '@/utils/logger';
|
|
11
|
+
import { PostData } from './types';
|
|
9
12
|
|
|
10
13
|
export const route: Route = {
|
|
11
14
|
path: '/user/:uid/:routeParams?',
|
|
@@ -43,29 +46,56 @@ async function handler(ctx) {
|
|
|
43
46
|
|
|
44
47
|
const pageUrl = `https://www.douyin.com/user/${uid}`;
|
|
45
48
|
|
|
46
|
-
const pageData = await cache.tryGet(
|
|
49
|
+
const pageData = (await cache.tryGet(
|
|
47
50
|
`douyin:user:${uid}`,
|
|
48
51
|
async () => {
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
+
let postData;
|
|
53
|
+
const browser = await puppeteer();
|
|
54
|
+
const page = await browser.newPage();
|
|
55
|
+
await page.setRequestInterception(true);
|
|
56
|
+
|
|
57
|
+
page.on('request', (request) => {
|
|
58
|
+
request.resourceType() === 'document' || request.resourceType() === 'script' || request.resourceType() === 'xhr' ? request.continue() : request.abort();
|
|
59
|
+
});
|
|
60
|
+
page.on('response', async (response) => {
|
|
61
|
+
const request = response.request();
|
|
62
|
+
if (request.url().includes('/web/aweme/post') && !postData) {
|
|
63
|
+
postData = await response.json();
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
logger.http(`Requesting ${pageUrl}`);
|
|
68
|
+
await page.goto(pageUrl, {
|
|
69
|
+
waitUntil: 'networkidle2',
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
browser.close();
|
|
73
|
+
|
|
74
|
+
if (!postData) {
|
|
75
|
+
throw new Error('Empty post data. The request may be filtered by WAF.');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return postData;
|
|
52
79
|
},
|
|
53
80
|
config.cache.routeExpire,
|
|
54
81
|
false
|
|
55
|
-
);
|
|
56
|
-
|
|
82
|
+
)) as PostData;
|
|
83
|
+
|
|
84
|
+
if (!pageData.aweme_list?.length) {
|
|
85
|
+
throw new Error('Empty post data. The request may be filtered by WAF.');
|
|
86
|
+
}
|
|
87
|
+
const userInfo = pageData.aweme_list[0].author;
|
|
57
88
|
const userNickName = userInfo.nickname;
|
|
58
|
-
const userDescription = userInfo.desc;
|
|
59
|
-
const userAvatar = getOriginAvatar(userInfo.
|
|
89
|
+
// const userDescription = userInfo.desc;
|
|
90
|
+
const userAvatar = getOriginAvatar(userInfo.avatar_thumb.url_list.at(-1));
|
|
60
91
|
|
|
61
|
-
const
|
|
62
|
-
const items = posts.map((post) => {
|
|
92
|
+
const items = pageData.aweme_list.map((post) => {
|
|
63
93
|
// parse video
|
|
64
|
-
let videoList = post.video
|
|
94
|
+
let videoList = post.video?.bit_rate?.map((item) => resolveUrl(item.play_addr.url_list.at(-1)));
|
|
65
95
|
if (relay) {
|
|
66
96
|
videoList = videoList.map((item) => proxyVideo(item, relay));
|
|
67
97
|
}
|
|
68
|
-
let duration = post.video
|
|
98
|
+
let duration = post.video?.duration;
|
|
69
99
|
duration = duration && duration / 1000;
|
|
70
100
|
let img;
|
|
71
101
|
// if (!embed) {
|
|
@@ -73,30 +103,28 @@ async function handler(ctx) {
|
|
|
73
103
|
// }
|
|
74
104
|
img =
|
|
75
105
|
img ||
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
post.video.originCover || // LD
|
|
79
|
-
post.video.cover)); // even more LD
|
|
106
|
+
post.video?.cover?.url_list.at(-1) || // HD
|
|
107
|
+
post.video?.origin_cover?.url_list.at(-1); // LD
|
|
80
108
|
img = img && resolveUrl(img);
|
|
81
109
|
|
|
82
110
|
// render description
|
|
83
|
-
const desc = post.desc
|
|
111
|
+
const desc = post.desc?.replaceAll('\n', '<br>');
|
|
84
112
|
let media = art(embed && videoList ? templates.embed : templates.cover, { img, videoList, duration });
|
|
85
113
|
media = embed && videoList && iframe ? art(templates.iframe, { content: media }) : media; // warp in iframe
|
|
86
114
|
const description = art(templates.desc, { desc, media });
|
|
87
115
|
|
|
88
116
|
return {
|
|
89
|
-
title: post.desc,
|
|
117
|
+
title: post.desc.split('\n')[0],
|
|
90
118
|
description,
|
|
91
|
-
link: `https://www.douyin.com/video/${post.
|
|
92
|
-
pubDate: parseDate(post.
|
|
93
|
-
category: post.
|
|
119
|
+
link: `https://www.douyin.com/video/${post.aweme_id}`,
|
|
120
|
+
pubDate: parseDate(post.create_time * 1000),
|
|
121
|
+
category: post.video_tag.map((t) => t.tag_name),
|
|
94
122
|
};
|
|
95
123
|
});
|
|
96
124
|
|
|
97
125
|
return {
|
|
98
126
|
title: userNickName,
|
|
99
|
-
description: userDescription,
|
|
127
|
+
// description: userDescription,
|
|
100
128
|
image: userAvatar,
|
|
101
129
|
link: pageUrl,
|
|
102
130
|
item: items,
|
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
import { getCurrentPath } from '@/utils/helpers';
|
|
2
2
|
const __dirname = getCurrentPath(import.meta.url);
|
|
3
3
|
|
|
4
|
-
import cache from '@/utils/cache';
|
|
5
4
|
import path from 'node:path';
|
|
6
|
-
import got from '@/utils/got';
|
|
7
|
-
import { load } from 'cheerio';
|
|
8
|
-
import logger from '@/utils/logger';
|
|
9
|
-
import { config } from '@/config';
|
|
10
|
-
import puppeteer from '@/utils/puppeteer';
|
|
11
5
|
|
|
12
6
|
const templates = {
|
|
13
7
|
desc: path.join(__dirname, 'templates/desc.art'),
|
|
@@ -51,84 +45,4 @@ const getOriginAvatar = (url) =>
|
|
|
51
45
|
.replace(/^(.*\.douyinpic\.com\/).*(\/aweme-avatar\/)([^?]*)(\?.*)?$/, '$1origin$2$3')
|
|
52
46
|
.replaceAll(/~\w+_\d+x\d+/g, '');
|
|
53
47
|
|
|
54
|
-
|
|
55
|
-
if (!html) {
|
|
56
|
-
throw new Error('Empty html. The request may be filtered by WAF.');
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const $ = load(html);
|
|
60
|
-
const renderData = JSON.parse(decodeURIComponent($('script#RENDER_DATA').html()));
|
|
61
|
-
if (!renderData) {
|
|
62
|
-
const title = $('title').text();
|
|
63
|
-
if (title.includes('验证')) {
|
|
64
|
-
throw new Error(title);
|
|
65
|
-
} else if ($('#captcha_container').length > 0) {
|
|
66
|
-
throw new Error('Captcha required.');
|
|
67
|
-
}
|
|
68
|
-
throw new Error('Failed to get render data.');
|
|
69
|
-
} else if (renderData.isSpider) {
|
|
70
|
-
throw new Error('The request was considered to be from a spider.');
|
|
71
|
-
} else {
|
|
72
|
-
return renderData;
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
const gotGet = async (url, ua) => {
|
|
77
|
-
const response = await got(url, {
|
|
78
|
-
headers: {
|
|
79
|
-
'User-Agent': ua, // magic!
|
|
80
|
-
},
|
|
81
|
-
});
|
|
82
|
-
return extractRenderData(response.data);
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
const puppeteerGet = async (pageUrl) => {
|
|
86
|
-
const browser = await puppeteer();
|
|
87
|
-
const page = await browser.newPage();
|
|
88
|
-
await page.setRequestInterception(true);
|
|
89
|
-
page.on('request', (request) => {
|
|
90
|
-
request.resourceType() === 'document' ? request.continue() : request.abort();
|
|
91
|
-
});
|
|
92
|
-
await page.goto(pageUrl, {
|
|
93
|
-
waitUntil: 'domcontentloaded',
|
|
94
|
-
});
|
|
95
|
-
const html = await page.evaluate(() => document.documentElement.innerHTML);
|
|
96
|
-
await browser.close();
|
|
97
|
-
return extractRenderData(html);
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
const universalGet = async (url, route) => {
|
|
101
|
-
const cacheKey = `douyin:utils:universalGet:gotFirst:${route}`;
|
|
102
|
-
const gotFirst = await cache.tryGet(
|
|
103
|
-
cacheKey,
|
|
104
|
-
() => '1',
|
|
105
|
-
config.cache.contentExpire,
|
|
106
|
-
false // no refresh now
|
|
107
|
-
);
|
|
108
|
-
|
|
109
|
-
const ua = route === 'live' ? 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)' : 'curl/7.86.0';
|
|
110
|
-
|
|
111
|
-
let data;
|
|
112
|
-
if (gotFirst === '1') {
|
|
113
|
-
try {
|
|
114
|
-
data = await gotGet(url, ua);
|
|
115
|
-
cache.set(cacheKey, '1', config.cache.contentExpire); // refresh if success
|
|
116
|
-
} catch (error) {
|
|
117
|
-
logger.warn('Failed to get data with got, trying puppeteer: ' + error.message);
|
|
118
|
-
data = await puppeteerGet(url);
|
|
119
|
-
cache.set(cacheKey, '0', config.cache.contentExpire); // only set if success
|
|
120
|
-
}
|
|
121
|
-
} else {
|
|
122
|
-
try {
|
|
123
|
-
data = await puppeteerGet(url);
|
|
124
|
-
cache.set(cacheKey, '0', config.cache.contentExpire); // refresh if success
|
|
125
|
-
} catch (error) {
|
|
126
|
-
logger.warn('Failed to get data with puppeteer, trying got: ' + error.message);
|
|
127
|
-
data = await gotGet(url, ua);
|
|
128
|
-
cache.set(cacheKey, '1', config.cache.contentExpire); // only set if success
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
return data;
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
export { templates, resolveUrl, proxyVideo, getOriginAvatar, universalGet };
|
|
48
|
+
export { templates, resolveUrl, proxyVideo, getOriginAvatar };
|
|
@@ -2,22 +2,24 @@ import { getCurrentPath } from '@/utils/helpers';
|
|
|
2
2
|
const __dirname = getCurrentPath(import.meta.url);
|
|
3
3
|
|
|
4
4
|
import cache from '@/utils/cache';
|
|
5
|
-
import
|
|
5
|
+
import ofetch from '@/utils/ofetch';
|
|
6
6
|
import { load } from 'cheerio';
|
|
7
7
|
import { parseDate } from '@/utils/parse-date';
|
|
8
8
|
import { art } from '@/utils/render';
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
|
|
11
|
+
const host = 'https://dribbble.com';
|
|
12
|
+
|
|
11
13
|
// Refactored function to load a link asynchronously
|
|
12
14
|
async function loadContent(link) {
|
|
13
15
|
// Make a GET request to the specified and retrieve the response
|
|
14
|
-
const response = await
|
|
15
|
-
const $ = load(response
|
|
16
|
+
const response = await ofetch(link);
|
|
17
|
+
const $ = load(response);
|
|
16
18
|
|
|
17
19
|
const shotData = JSON.parse(
|
|
18
20
|
$('script')
|
|
19
21
|
.text()
|
|
20
|
-
.match(/shotData:\s({.+?}),\n/)[1]
|
|
22
|
+
.match(/shotData:\s({.+?}),\n/)?.[1] ?? '{}'
|
|
21
23
|
);
|
|
22
24
|
|
|
23
25
|
// Join multiple shots together by selecting elements with class 'media-shot' or 'main-shot' or 'block-media-wrapper'
|
|
@@ -91,9 +93,7 @@ async function loadContent(link) {
|
|
|
91
93
|
|
|
92
94
|
// Refactored code with comments for clarity
|
|
93
95
|
|
|
94
|
-
function ProcessFeed(list
|
|
95
|
-
const host = 'https://dribbble.com';
|
|
96
|
-
|
|
96
|
+
function ProcessFeed(list) {
|
|
97
97
|
// Use Promise.all to process all items in the list asynchronously
|
|
98
98
|
return Promise.all(
|
|
99
99
|
list.map((item) => {
|
|
@@ -101,18 +101,20 @@ function ProcessFeed(list, caches) {
|
|
|
101
101
|
|
|
102
102
|
// The link of item is "/signup/new" when access "https://dribbble.com/search/something"
|
|
103
103
|
// So get url by id
|
|
104
|
-
const itemId = $(item).
|
|
104
|
+
const itemId = $(item).data('thumbnail-id');
|
|
105
105
|
|
|
106
106
|
// Construct the full item URL using the host and the item ID
|
|
107
|
-
const
|
|
107
|
+
const guid = new URL(`/shots/${itemId}`, host).href;
|
|
108
|
+
const itemUrl = new URL($(item).find('.shot-thumbnail-link').attr('href')!, host).href;
|
|
108
109
|
|
|
109
110
|
// Return a Promise that resolves to an object combining the single item data and the additional data
|
|
110
|
-
return
|
|
111
|
+
return cache.tryGet(guid, async () => {
|
|
111
112
|
const { description, pubDate, author, category } = await loadContent(itemUrl);
|
|
112
113
|
|
|
113
114
|
return {
|
|
114
115
|
title: $('.shot-title').text(),
|
|
115
116
|
link: itemUrl,
|
|
117
|
+
guid,
|
|
116
118
|
description,
|
|
117
119
|
pubDate,
|
|
118
120
|
author,
|
|
@@ -126,25 +128,21 @@ function ProcessFeed(list, caches) {
|
|
|
126
128
|
/**
|
|
127
129
|
* Retrieves data from a given URL and processes it.
|
|
128
130
|
*
|
|
129
|
-
* @param {Object} ctx - The context object.
|
|
130
131
|
* @param {string} url - The URL to retrieve data from.
|
|
131
132
|
* @param {string} title - The title of the data.
|
|
132
133
|
* @return {Object} - An object containing the retrieved data and metadata.
|
|
133
134
|
*/
|
|
134
|
-
const getData = async (
|
|
135
|
+
const getData = async (url, title) => {
|
|
135
136
|
// Make a GET request to the specified URL
|
|
136
|
-
const response = await
|
|
137
|
-
|
|
138
|
-
// Extract the response data
|
|
139
|
-
const data = response.data;
|
|
137
|
+
const response = await ofetch(url);
|
|
140
138
|
|
|
141
139
|
// Load the response data into a cheerio object
|
|
142
|
-
const $ = load(
|
|
140
|
+
const $ = load(response);
|
|
143
141
|
// Get all the list items under the 'ol.dribbbles.group' element
|
|
144
142
|
const list = $('ol.dribbbles.group > li').toArray();
|
|
145
143
|
|
|
146
144
|
// Process the list items using the ProcessFeed function
|
|
147
|
-
const result = await ProcessFeed(list
|
|
145
|
+
const result = await ProcessFeed(list);
|
|
148
146
|
|
|
149
147
|
// Return an object containing the retrieved data and metadata
|
|
150
148
|
return {
|
|
@@ -106,7 +106,7 @@ async function handler(ctx) {
|
|
|
106
106
|
},
|
|
107
107
|
});
|
|
108
108
|
|
|
109
|
-
const list = (data.pageProps.
|
|
109
|
+
const list = (data.pageProps.categoryArticleDataForPc as CategoryArticle[])
|
|
110
110
|
.filter((item) => !item.advertiserName)
|
|
111
111
|
.map((item) => {
|
|
112
112
|
const publicationDate = item.publishedAt?.slice(0, 7).replace('-', '');
|
|
@@ -50,7 +50,7 @@ async function handler(ctx: Context): Promise<Data> {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
const postListResponse = (await ofetch(`https://api.fanbox.cc/post.listCreator?creatorId=${creator}&limit=20`, { headers: getHeaders() })) as PostListResponse;
|
|
53
|
-
const items = await Promise.all(postListResponse.body.
|
|
53
|
+
const items = await Promise.all(postListResponse.body.map((i) => parseItem(i)));
|
|
54
54
|
|
|
55
55
|
return {
|
|
56
56
|
title,
|
|
@@ -25,10 +25,7 @@ export interface UserInfoResponse {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export interface PostListResponse {
|
|
28
|
-
body:
|
|
29
|
-
items: PostItem[];
|
|
30
|
-
nextUrl: string | null;
|
|
31
|
-
};
|
|
28
|
+
body: PostItem[];
|
|
32
29
|
}
|
|
33
30
|
|
|
34
31
|
export interface PostDetailResponse {
|
|
@@ -88,7 +85,6 @@ interface BasicPost {
|
|
|
88
85
|
userId: string;
|
|
89
86
|
};
|
|
90
87
|
}[];
|
|
91
|
-
nextUrl: string | null;
|
|
92
88
|
};
|
|
93
89
|
coverImageUrl: string | null;
|
|
94
90
|
creatorId: string;
|
|
@@ -5,6 +5,7 @@ import { parseDate } from '@/utils/parse-date';
|
|
|
5
5
|
import ofetch from '@/utils/ofetch';
|
|
6
6
|
import { config } from '@/config';
|
|
7
7
|
import ConfigNotFoundError from '@/errors/types/config-not-found';
|
|
8
|
+
import parser from '@/utils/rss-parser';
|
|
8
9
|
|
|
9
10
|
export const route: Route = {
|
|
10
11
|
path: '/timeline/:account',
|
|
@@ -21,7 +22,7 @@ export const route: Route = {
|
|
|
21
22
|
supportScihub: false,
|
|
22
23
|
},
|
|
23
24
|
name: 'Timeline',
|
|
24
|
-
maintainers: ['DIYgod'],
|
|
25
|
+
maintainers: ['DIYgod', 'pseudoyu'],
|
|
25
26
|
handler,
|
|
26
27
|
};
|
|
27
28
|
|
|
@@ -51,12 +52,29 @@ async function handler(ctx) {
|
|
|
51
52
|
Accept: 'application/jrd+json',
|
|
52
53
|
},
|
|
53
54
|
});
|
|
54
|
-
|
|
55
55
|
const jsonLink = acc.links.find((link) => link.rel === 'self' && activityPubTypes.has(link.type))?.href;
|
|
56
56
|
const link = acc.links.find((link) => link.rel === 'http://webfinger.net/rel/profile-page')?.href;
|
|
57
|
+
const officialFeed = await parser.parseURL(`${link}.rss`);
|
|
58
|
+
|
|
59
|
+
if (officialFeed) {
|
|
60
|
+
return {
|
|
61
|
+
title: `${officialFeed.title} (Fediverse@${account})`,
|
|
62
|
+
description: officialFeed.description,
|
|
63
|
+
image: officialFeed.image?.url,
|
|
64
|
+
link: officialFeed.link,
|
|
65
|
+
item: officialFeed.items.map((item) => ({
|
|
66
|
+
title: item.title,
|
|
67
|
+
description: item.content,
|
|
68
|
+
link: item.link,
|
|
69
|
+
pubDate: item.pubDate ? parseDate(item.pubDate) : null,
|
|
70
|
+
guid: item.guid,
|
|
71
|
+
})),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
57
74
|
|
|
58
75
|
const self = await ofetch(jsonLink, requestOptions);
|
|
59
76
|
|
|
77
|
+
// If RSS feed is not available, fallback to original method
|
|
60
78
|
const outbox = await ofetch(self.outbox, requestOptions);
|
|
61
79
|
const firstOutbox = await ofetch(outbox.first, requestOptions);
|
|
62
80
|
|
|
@@ -88,7 +106,7 @@ async function handler(ctx) {
|
|
|
88
106
|
image: self.icon?.url || self.image?.url,
|
|
89
107
|
link,
|
|
90
108
|
item: resolvedItems.map((item) => ({
|
|
91
|
-
title: item.object.content,
|
|
109
|
+
title: item.object.content.replaceAll(/<[^<]*>/g, ''),
|
|
92
110
|
description: `${item.object.content}\n${item.object.attachment?.map((attachment) => `<img src="${attachment.url}" width="${attachment.width}" height="${attachment.height}" />`).join('\n') || ''}`,
|
|
93
111
|
link: item.object.url,
|
|
94
112
|
pubDate: parseDate(item.published),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ViewType, type Data, type Route } from '@/types';
|
|
2
2
|
import type { Context } from 'hono';
|
|
3
3
|
import ofetch from '@/utils/ofetch';
|
|
4
|
-
import type { FollowResponse, ListSubscription, Profile, Subscription } from './types';
|
|
4
|
+
import type { FollowResponse, InboxSubscription, ListSubscription, Profile, Subscription } from './types';
|
|
5
5
|
import { parse } from 'tldts';
|
|
6
6
|
|
|
7
7
|
export const route: Route = {
|
|
@@ -28,6 +28,8 @@ export const route: Route = {
|
|
|
28
28
|
|
|
29
29
|
const isList = (subscription: Subscription): subscription is ListSubscription => 'lists' in subscription;
|
|
30
30
|
|
|
31
|
+
const isInbox = (subscription: Subscription): subscription is InboxSubscription => 'inboxId' in subscription;
|
|
32
|
+
|
|
31
33
|
async function handler(ctx: Context): Promise<Data> {
|
|
32
34
|
const uid = ctx.req.param('uid');
|
|
33
35
|
const host = 'https://api.follow.is';
|
|
@@ -37,7 +39,7 @@ async function handler(ctx: Context): Promise<Data> {
|
|
|
37
39
|
|
|
38
40
|
return {
|
|
39
41
|
title: `${profile.data.name}'s subscriptions`,
|
|
40
|
-
item: subscriptions.data.map((subscription) => {
|
|
42
|
+
item: (<Exclude<Subscription, InboxSubscription>[]>subscriptions.data.filter((i) => !isInbox(i))).map((subscription) => {
|
|
41
43
|
if (isList(subscription)) {
|
|
42
44
|
return {
|
|
43
45
|
title: subscription.lists.title,
|
|
@@ -3,7 +3,7 @@ export interface FollowResponse<T> {
|
|
|
3
3
|
data: T;
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
-
export type Subscription = FeedSubscription | ListSubscription;
|
|
6
|
+
export type Subscription = FeedSubscription | ListSubscription | InboxSubscription;
|
|
7
7
|
|
|
8
8
|
export interface Profile {
|
|
9
9
|
id: string;
|
|
@@ -66,3 +66,13 @@ export interface ListSubscription extends BaseSubscription {
|
|
|
66
66
|
view: number;
|
|
67
67
|
};
|
|
68
68
|
}
|
|
69
|
+
|
|
70
|
+
export interface InboxSubscription extends BaseSubscription {
|
|
71
|
+
inboxes: {
|
|
72
|
+
type: 'inbox';
|
|
73
|
+
id: string;
|
|
74
|
+
secret: string;
|
|
75
|
+
title: string;
|
|
76
|
+
};
|
|
77
|
+
inboxId: string;
|
|
78
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Route } from '@/types';
|
|
2
|
+
import got from '@/utils/got';
|
|
3
|
+
import { load } from 'cheerio';
|
|
4
|
+
import { parseDate } from '@/utils/parse-date';
|
|
5
|
+
import ofetch from '@/utils/ofetch';
|
|
6
|
+
import cache from '@/utils/cache';
|
|
7
|
+
|
|
8
|
+
export const route: Route = {
|
|
9
|
+
path: '/advisor/data/:type?/:category?',
|
|
10
|
+
categories: ['programming'],
|
|
11
|
+
example: '/github/advisor/data/reviewed/composer',
|
|
12
|
+
features: {
|
|
13
|
+
requireConfig: false,
|
|
14
|
+
requirePuppeteer: false,
|
|
15
|
+
antiCrawler: false,
|
|
16
|
+
supportBT: false,
|
|
17
|
+
supportPodcast: false,
|
|
18
|
+
supportScihub: false,
|
|
19
|
+
},
|
|
20
|
+
radar: [
|
|
21
|
+
{
|
|
22
|
+
source: ['github.com/advisories', 'github.com'],
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
name: 'Github Advisory Database RSS',
|
|
26
|
+
maintainers: ['sd0ric4'],
|
|
27
|
+
handler,
|
|
28
|
+
description: `
|
|
29
|
+
| Type | Description | Explanation |
|
|
30
|
+
| --- | --- | --- |
|
|
31
|
+
| reviewed | Reviewed | 已审核 |
|
|
32
|
+
| unreviewed | Unreviewed | 未审核 |
|
|
33
|
+
|
|
34
|
+
| Category | Description | Explanation |
|
|
35
|
+
| --- | --- | --- |
|
|
36
|
+
| composer | Composer | PHP 依赖管理工具 |
|
|
37
|
+
| go | Go | Go 语言包管理工具 |
|
|
38
|
+
| maven | Maven | Java 项目管理工具 |
|
|
39
|
+
| npm | NPM | Node.js 包管理工具 |
|
|
40
|
+
| nuget | NuGet | .NET 包管理工具 |
|
|
41
|
+
| pip | Pip | Python 包管理工具 |
|
|
42
|
+
| pub | Pub | Dart 包管理工具 |
|
|
43
|
+
| rubygems | RubyGems | Ruby 包管理工具 |
|
|
44
|
+
| rust | Rust | Rust 包管理工具 |
|
|
45
|
+
| erlang | Erlang | Erlang 包管理工具 |
|
|
46
|
+
| actions | Actions | GitHub Actions |
|
|
47
|
+
| swift | Swift | Swift 包管理工具 |`,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
async function handler(ctx) {
|
|
51
|
+
const { category, type } = ctx.req.param();
|
|
52
|
+
|
|
53
|
+
const apiRootUrl = 'https://github.com/advisories';
|
|
54
|
+
const apiUrl = `${apiRootUrl}?query=type%3A${type}+ecosystem%3A${category}`;
|
|
55
|
+
const currentUrl = `https://github.com/advisories`;
|
|
56
|
+
|
|
57
|
+
const response = await got({
|
|
58
|
+
method: 'get',
|
|
59
|
+
url: apiUrl,
|
|
60
|
+
});
|
|
61
|
+
const $ = load(response.data);
|
|
62
|
+
|
|
63
|
+
const list = $('div.Box-row')
|
|
64
|
+
.toArray()
|
|
65
|
+
.map((item) => {
|
|
66
|
+
item = $(item);
|
|
67
|
+
const a = item.find('a.Link--primary');
|
|
68
|
+
const b = item.find('relative-time').attr('datetime');
|
|
69
|
+
const title = a.text() || 'No title';
|
|
70
|
+
const link = a.attr('href') || '#';
|
|
71
|
+
const pubDate = parseDate(b || '');
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
title,
|
|
75
|
+
link: `https://github.com${link}`,
|
|
76
|
+
pubDate,
|
|
77
|
+
description: '',
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
const items = await Promise.all(
|
|
81
|
+
list.map((item) =>
|
|
82
|
+
cache.tryGet(item.link, async () => {
|
|
83
|
+
const response = await ofetch(item.link);
|
|
84
|
+
const $ = load(response);
|
|
85
|
+
|
|
86
|
+
item.description = $('.comment-body').first().html() || '';
|
|
87
|
+
|
|
88
|
+
return item;
|
|
89
|
+
})
|
|
90
|
+
)
|
|
91
|
+
);
|
|
92
|
+
return {
|
|
93
|
+
title: `GitHub Advisory Database RSS - ${category} - ${type}`,
|
|
94
|
+
link: currentUrl,
|
|
95
|
+
item: items,
|
|
96
|
+
};
|
|
97
|
+
}
|