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
package/lib/routes/scmp/utils.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { load } from 'cheerio';
|
|
2
|
-
import got from '@/utils/got';
|
|
3
2
|
import { parseDate } from '@/utils/parse-date';
|
|
3
|
+
import ofetch from '@/utils/ofetch';
|
|
4
4
|
|
|
5
5
|
export const renderHTML = (node) => {
|
|
6
6
|
if (!node) {
|
|
@@ -60,7 +60,7 @@ export const renderHTML = (node) => {
|
|
|
60
60
|
};
|
|
61
61
|
|
|
62
62
|
export const parseItem = async (item) => {
|
|
63
|
-
const {
|
|
63
|
+
const { _data: response, url } = await ofetch.raw(item.link);
|
|
64
64
|
|
|
65
65
|
if (new URL(url).hostname !== 'www.scmp.com') {
|
|
66
66
|
// e.g., https://multimedia.scmp.com/
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Data, DataItem, Route } from '@/types';
|
|
2
|
+
import { config } from '@/config';
|
|
3
|
+
import ConfigNotFoundError from '@/errors/types/config-not-found';
|
|
4
|
+
import { getFollowingsItems } from './utils';
|
|
5
|
+
|
|
6
|
+
export const route: Route = {
|
|
7
|
+
path: '/following_creators/:username',
|
|
8
|
+
categories: ['picture'],
|
|
9
|
+
example: '/skeb/following_creators/@brm2_1925',
|
|
10
|
+
parameters: { username: 'Skeb Username with @' },
|
|
11
|
+
features: {
|
|
12
|
+
requireConfig: [
|
|
13
|
+
{
|
|
14
|
+
name: 'SKEB_BEARER_TOKEN',
|
|
15
|
+
optional: false,
|
|
16
|
+
description: '在瀏覽器開發者工具(F12)的主控台中輸入 `localStorage.getItem("token")` 獲取',
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
requirePuppeteer: false,
|
|
20
|
+
antiCrawler: false,
|
|
21
|
+
supportBT: false,
|
|
22
|
+
supportPodcast: false,
|
|
23
|
+
supportScihub: false,
|
|
24
|
+
},
|
|
25
|
+
name: 'Following Creators',
|
|
26
|
+
maintainers: ['SnowAgar25'],
|
|
27
|
+
handler,
|
|
28
|
+
radar: [
|
|
29
|
+
{
|
|
30
|
+
title: 'Following Creators',
|
|
31
|
+
source: ['skeb.jp/:username'],
|
|
32
|
+
target: '/following_creators/:username',
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
description: 'Get the list of creators the specified user is following on Skeb.',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
async function handler(ctx): Promise<Data> {
|
|
39
|
+
const username = ctx.req.param('username');
|
|
40
|
+
|
|
41
|
+
if (!config.skeb || !config.skeb.bearerToken) {
|
|
42
|
+
throw new ConfigNotFoundError('Skeb followings RSS is disabled due to the lack of relevant config');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const items = await getFollowingsItems(username, 'following_creators');
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
title: `Skeb - ${username} - フォロー中のクリエイター`,
|
|
49
|
+
link: `https://skeb.jp/${username}`,
|
|
50
|
+
item: items as DataItem[],
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Data, DataItem, Route } from '@/types';
|
|
2
|
+
import { config } from '@/config';
|
|
3
|
+
import ConfigNotFoundError from '@/errors/types/config-not-found';
|
|
4
|
+
import { getFollowingsItems } from './utils';
|
|
5
|
+
|
|
6
|
+
export const route: Route = {
|
|
7
|
+
path: '/following_works/:username',
|
|
8
|
+
categories: ['picture'],
|
|
9
|
+
example: '/skeb/following_works/@brm2_1925',
|
|
10
|
+
parameters: { username: 'Skeb Username with @' },
|
|
11
|
+
features: {
|
|
12
|
+
requireConfig: [
|
|
13
|
+
{
|
|
14
|
+
name: 'SKEB_BEARER_TOKEN',
|
|
15
|
+
optional: false,
|
|
16
|
+
description: '在瀏覽器開發者工具(F12)的主控台中輸入 `localStorage.getItem("token")` 獲取',
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
requirePuppeteer: false,
|
|
20
|
+
antiCrawler: false,
|
|
21
|
+
supportBT: false,
|
|
22
|
+
supportPodcast: false,
|
|
23
|
+
supportScihub: false,
|
|
24
|
+
},
|
|
25
|
+
name: 'Following Works',
|
|
26
|
+
maintainers: ['SnowAgar25'],
|
|
27
|
+
handler,
|
|
28
|
+
radar: [
|
|
29
|
+
{
|
|
30
|
+
title: 'Following Works',
|
|
31
|
+
source: ['skeb.jp/:username'],
|
|
32
|
+
target: '/following_works/:username',
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
description: "Get the latest works for the specified user's followings on Skeb.",
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
async function handler(ctx): Promise<Data> {
|
|
39
|
+
const username = ctx.req.param('username');
|
|
40
|
+
|
|
41
|
+
if (!config.skeb || !config.skeb.bearerToken) {
|
|
42
|
+
throw new ConfigNotFoundError('Skeb followings RSS is disabled due to the lack of relevant config');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const items = await getFollowingsItems(username, 'following_works');
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
title: `Skeb - ${username} - フォロー中のクリエイターの新着作品`,
|
|
49
|
+
link: `https://skeb.jp/${username}`,
|
|
50
|
+
item: items as DataItem[],
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Data, DataItem, Route } from '@/types';
|
|
2
|
+
import { config } from '@/config';
|
|
3
|
+
import ConfigNotFoundError from '@/errors/types/config-not-found';
|
|
4
|
+
import { getFollowingsItems } from './utils';
|
|
5
|
+
|
|
6
|
+
export const route: Route = {
|
|
7
|
+
path: '/friend_works/:username',
|
|
8
|
+
categories: ['picture'],
|
|
9
|
+
example: '/skeb/friend_works/@brm2_1925',
|
|
10
|
+
parameters: { username: 'Skeb Username with @' },
|
|
11
|
+
features: {
|
|
12
|
+
requireConfig: [
|
|
13
|
+
{
|
|
14
|
+
name: 'SKEB_BEARER_TOKEN',
|
|
15
|
+
optional: false,
|
|
16
|
+
description: '在瀏覽器開發者工具(F12)的主控台中輸入 `localStorage.getItem("token")` 獲取',
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
requirePuppeteer: false,
|
|
20
|
+
antiCrawler: false,
|
|
21
|
+
supportBT: false,
|
|
22
|
+
supportPodcast: false,
|
|
23
|
+
supportScihub: false,
|
|
24
|
+
},
|
|
25
|
+
name: 'Friend Works',
|
|
26
|
+
maintainers: ['SnowAgar25'],
|
|
27
|
+
handler,
|
|
28
|
+
radar: [
|
|
29
|
+
{
|
|
30
|
+
title: 'Friend Works',
|
|
31
|
+
source: ['skeb.jp/:username'],
|
|
32
|
+
target: '/friend_works/:username',
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
description: "Get the latest requests for the specified user's followings on Skeb.",
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
async function handler(ctx): Promise<Data> {
|
|
39
|
+
const username = ctx.req.param('username');
|
|
40
|
+
|
|
41
|
+
if (!config.skeb || !config.skeb.bearerToken) {
|
|
42
|
+
throw new ConfigNotFoundError('Skeb followings RSS is disabled due to the lack of relevant config');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const items = await getFollowingsItems(username, 'friend_works');
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
title: `Skeb - ${username} - フォロー中のクライアントの新着リクエスト`,
|
|
49
|
+
link: `https://skeb.jp/${username}`,
|
|
50
|
+
item: items as DataItem[],
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { Route, Data, DataItem } from '@/types';
|
|
2
|
+
import ofetch from '@/utils/ofetch';
|
|
3
|
+
import cache from '@/utils/cache';
|
|
4
|
+
import { baseUrl, processWork, processCreator } from './utils';
|
|
5
|
+
import { config } from '@/config';
|
|
6
|
+
|
|
7
|
+
const categoryMap = {
|
|
8
|
+
// Works categories
|
|
9
|
+
new_art_works: '新着作品 (Illust)',
|
|
10
|
+
new_voice_works: '新着作品 (Voice)',
|
|
11
|
+
new_novel_works: '新着作品 (Novel)',
|
|
12
|
+
new_video_works: '新着作品 (Video)',
|
|
13
|
+
new_music_works: '新着作品 (Music)',
|
|
14
|
+
new_correction_works: '新着作品 (Advice)',
|
|
15
|
+
new_comic_works: '新着作品 (Comic)',
|
|
16
|
+
popular_works: '人気の作品 (Popular)',
|
|
17
|
+
// Creators categories
|
|
18
|
+
popular_creators: '人気クリエイター',
|
|
19
|
+
new_creators: '新着クリエイター',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const workCategories = new Set(['new_art_works', 'new_voice_works', 'new_novel_works', 'new_video_works', 'new_music_works', 'new_correction_works', 'new_comic_works', 'popular_works']);
|
|
23
|
+
|
|
24
|
+
export const route: Route = {
|
|
25
|
+
path: '/:category',
|
|
26
|
+
categories: ['picture'],
|
|
27
|
+
example: '/skeb/new_art_works',
|
|
28
|
+
parameters: { category: 'Category, the div id of the section title on the homepage.' },
|
|
29
|
+
features: {
|
|
30
|
+
requireConfig: false,
|
|
31
|
+
requirePuppeteer: false,
|
|
32
|
+
antiCrawler: false,
|
|
33
|
+
supportBT: false,
|
|
34
|
+
supportPodcast: false,
|
|
35
|
+
supportScihub: false,
|
|
36
|
+
},
|
|
37
|
+
name: 'Skeb',
|
|
38
|
+
maintainers: ['SnowAgar25'],
|
|
39
|
+
handler,
|
|
40
|
+
radar: [
|
|
41
|
+
{
|
|
42
|
+
title: '新着作品 (Illust)',
|
|
43
|
+
source: ['skeb.jp'],
|
|
44
|
+
target: '/new_art_works',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
title: '新着作品 (Voice)',
|
|
48
|
+
source: ['skeb.jp'],
|
|
49
|
+
target: '/new_voice_works',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
title: '新着作品 (Novel)',
|
|
53
|
+
source: ['skeb.jp'],
|
|
54
|
+
target: '/new_novel_works',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
title: '新着作品 (Video)',
|
|
58
|
+
source: ['skeb.jp'],
|
|
59
|
+
target: '/new_video_works',
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
title: '新着作品 (Music)',
|
|
63
|
+
source: ['skeb.jp'],
|
|
64
|
+
target: '/new_music_works',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
title: '新着作品 (Advice)',
|
|
68
|
+
source: ['skeb.jp'],
|
|
69
|
+
target: '/new_correction_works',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
title: '新着作品 (Comic)',
|
|
73
|
+
source: ['skeb.jp'],
|
|
74
|
+
target: '/new_comic_works',
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
title: '人気の作品 (Popular)',
|
|
78
|
+
source: ['skeb.jp'],
|
|
79
|
+
target: '/popular_works',
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
title: '人気クリエイター',
|
|
83
|
+
source: ['skeb.jp'],
|
|
84
|
+
target: '/popular_creators',
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
title: '新着クリエイター',
|
|
88
|
+
source: ['skeb.jp'],
|
|
89
|
+
target: '/new_creators',
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
async function handler(ctx): Promise<Data> {
|
|
95
|
+
const category = ctx.req.param('category') || 'new_art_works';
|
|
96
|
+
|
|
97
|
+
if (!(category in categoryMap)) {
|
|
98
|
+
throw new Error('Invalid category');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const url = `${baseUrl}/api`;
|
|
102
|
+
|
|
103
|
+
const apiData = await cache.tryGet(
|
|
104
|
+
url,
|
|
105
|
+
async () => {
|
|
106
|
+
const data = await ofetch(url);
|
|
107
|
+
return data;
|
|
108
|
+
},
|
|
109
|
+
config.cache.routeExpire,
|
|
110
|
+
false
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
if (!apiData || typeof apiData !== 'object') {
|
|
114
|
+
throw new Error('Invalid data received from API');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const items = await cache.tryGet(category, async () => {
|
|
118
|
+
if (!(category in apiData) || !Array.isArray(apiData[category])) {
|
|
119
|
+
return [];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const processItem = workCategories.has(category) ? processWork : processCreator;
|
|
123
|
+
return (await Promise.all(apiData[category].map(async (item) => await processItem(item)).filter(Boolean))) as DataItem[];
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
title: `Skeb - ${categoryMap[category]}`,
|
|
128
|
+
link: `${baseUrl}/#${category}`,
|
|
129
|
+
item: items as DataItem[],
|
|
130
|
+
};
|
|
131
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Data, DataItem, Route } from '@/types';
|
|
2
|
+
import cache from '@/utils/cache';
|
|
3
|
+
import ofetch from '@/utils/ofetch';
|
|
4
|
+
import { processWork, baseUrl } from './utils';
|
|
5
|
+
import InvalidParameterError from '@/errors/types/invalid-parameter';
|
|
6
|
+
|
|
7
|
+
export const route: Route = {
|
|
8
|
+
path: '/search/:keyword',
|
|
9
|
+
categories: ['picture'],
|
|
10
|
+
example: '/skeb/search/初音ミク',
|
|
11
|
+
parameters: { keyword: 'Search keyword' },
|
|
12
|
+
features: {
|
|
13
|
+
requireConfig: false,
|
|
14
|
+
requirePuppeteer: false,
|
|
15
|
+
antiCrawler: false,
|
|
16
|
+
supportBT: false,
|
|
17
|
+
supportPodcast: false,
|
|
18
|
+
supportScihub: false,
|
|
19
|
+
},
|
|
20
|
+
name: 'Search Results',
|
|
21
|
+
maintainers: ['SnowAgar25'],
|
|
22
|
+
handler,
|
|
23
|
+
description: 'Get the search results for works on Skeb',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
async function handler(ctx): Promise<Data> {
|
|
27
|
+
const keyword = ctx.req.param('keyword');
|
|
28
|
+
|
|
29
|
+
if (!keyword) {
|
|
30
|
+
throw new InvalidParameterError('Invalid search keyword');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const url = 'https://hb1jt3kre9-dsn.algolia.net/1/indexes/*/queries';
|
|
34
|
+
|
|
35
|
+
const items = await cache.tryGet(`skeb:search:${keyword}`, async () => {
|
|
36
|
+
const data = await ofetch(url, {
|
|
37
|
+
method: 'POST',
|
|
38
|
+
headers: {
|
|
39
|
+
'x-algolia-application-id': 'HB1JT3KRE9',
|
|
40
|
+
'x-algolia-api-key': '9a4ce7d609e71bf29e977925e4c6740c',
|
|
41
|
+
},
|
|
42
|
+
body: {
|
|
43
|
+
requests: [
|
|
44
|
+
{
|
|
45
|
+
indexName: 'User',
|
|
46
|
+
query: keyword,
|
|
47
|
+
params: 'hitsPerPage=40',
|
|
48
|
+
filters: 'genres:art OR genres:comic OR genres:voice OR genres:novel OR genres:video OR genres:music OR genres:correction',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
indexName: 'Request',
|
|
52
|
+
query: keyword,
|
|
53
|
+
params: 'hitsPerPage=40&filters=genre%3Aart%20OR%20genre%3Acomic%20OR%20genre%3Avoice%20OR%20genre%3Anovel%20OR%20genre%3Avideo%20OR%20genre%3Amusic%20OR%20genre%3Acorrection',
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
if (!data || !data.results || !Array.isArray(data.results) || data.results.length < 2) {
|
|
60
|
+
throw new Error('Invalid data received from API');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const works = data.results[1].hits;
|
|
64
|
+
|
|
65
|
+
if (!Array.isArray(works)) {
|
|
66
|
+
throw new TypeError('Invalid hits data received from API');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return works.map((item) => processWork(item)).filter(Boolean);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
title: `Skeb - Search Results for "${keyword}"`,
|
|
74
|
+
link: `${baseUrl}/search?q=${encodeURIComponent(keyword)}`,
|
|
75
|
+
item: items as DataItem[],
|
|
76
|
+
};
|
|
77
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { config } from '@/config';
|
|
2
|
+
import { DataItem } from '@/types';
|
|
3
|
+
import cache from '@/utils/cache';
|
|
4
|
+
import ofetch from '@/utils/ofetch';
|
|
5
|
+
import { art } from '@/utils/render';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { getCurrentPath } from '@/utils/helpers';
|
|
8
|
+
|
|
9
|
+
const __dirname = getCurrentPath(import.meta.url);
|
|
10
|
+
|
|
11
|
+
export const baseUrl = 'https://skeb.jp';
|
|
12
|
+
|
|
13
|
+
interface Work {
|
|
14
|
+
path: string;
|
|
15
|
+
private_thumbnail_image_urls: null | string;
|
|
16
|
+
private: boolean;
|
|
17
|
+
genre: string;
|
|
18
|
+
tipped: boolean;
|
|
19
|
+
creator_id: number;
|
|
20
|
+
client_id: number;
|
|
21
|
+
vtt_url: null | string;
|
|
22
|
+
thumbnail_image_urls: {
|
|
23
|
+
src: string;
|
|
24
|
+
srcset: string;
|
|
25
|
+
};
|
|
26
|
+
preview_url: null | string;
|
|
27
|
+
duration: null | number;
|
|
28
|
+
nsfw: boolean;
|
|
29
|
+
hardcore: boolean;
|
|
30
|
+
consored_thumbnail_image_urls: {
|
|
31
|
+
src: string;
|
|
32
|
+
srcset: string;
|
|
33
|
+
};
|
|
34
|
+
body: string;
|
|
35
|
+
nc: number;
|
|
36
|
+
word_count: number;
|
|
37
|
+
transcoder: string;
|
|
38
|
+
creator_acceptable_same_genre: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface Creator {
|
|
42
|
+
id: number;
|
|
43
|
+
creator: boolean;
|
|
44
|
+
nsfw_acceptable: boolean;
|
|
45
|
+
acceptable: boolean;
|
|
46
|
+
name: string;
|
|
47
|
+
screen_name: string;
|
|
48
|
+
avatar_url: string;
|
|
49
|
+
header_url: string | null;
|
|
50
|
+
appeal_receivable: boolean;
|
|
51
|
+
popular_creator_rank: number | null;
|
|
52
|
+
request_master_rank: number | null;
|
|
53
|
+
first_requester_rank: number | null;
|
|
54
|
+
deleted_at: string | null;
|
|
55
|
+
tip_acceptable_by: string;
|
|
56
|
+
accept_expiration_days: number;
|
|
57
|
+
skills: { genre: string }[];
|
|
58
|
+
nc: number;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function processWork(work: Work): DataItem | null {
|
|
62
|
+
if (!work || typeof work !== 'object' || work.private === true) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const imageUrl = work.thumbnail_image_urls?.srcset?.split(',').pop()?.trim().split(' ')[0] || '';
|
|
67
|
+
const body = work.body || '';
|
|
68
|
+
|
|
69
|
+
const audioUrl = work.genre === 'music' || work.genre === 'voice' ? work.preview_url : null;
|
|
70
|
+
|
|
71
|
+
const renderedHtml = art(path.join(__dirname, 'templates/work.art'), {
|
|
72
|
+
imageUrl,
|
|
73
|
+
body,
|
|
74
|
+
audioUrl,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
title: work.path || '',
|
|
79
|
+
link: `${baseUrl}${work.path || ''}`,
|
|
80
|
+
description: renderedHtml,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const skillMap = {
|
|
85
|
+
art: 'Illust',
|
|
86
|
+
voice: 'Voice',
|
|
87
|
+
novel: 'Novel',
|
|
88
|
+
video: 'Video',
|
|
89
|
+
music: 'Music',
|
|
90
|
+
correction: 'Advice',
|
|
91
|
+
comic: 'Comic',
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export function processCreator(creator: Creator): DataItem | null {
|
|
95
|
+
if (!creator || typeof creator !== 'object') {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const avatarUrl = creator.avatar_url || '';
|
|
100
|
+
|
|
101
|
+
let renderedHtml;
|
|
102
|
+
|
|
103
|
+
if (creator.creator) {
|
|
104
|
+
const acceptingCommissions = creator.acceptable ? 'Yes' : 'No';
|
|
105
|
+
const nsfwAcceptable = creator.nsfw_acceptable ? 'Yes' : 'No';
|
|
106
|
+
|
|
107
|
+
let skills = '';
|
|
108
|
+
if (Array.isArray(creator.skills) && creator.skills.length > 0) {
|
|
109
|
+
skills = creator.skills
|
|
110
|
+
.map((skill) => skillMap[skill.genre] || skill.genre)
|
|
111
|
+
.filter(Boolean)
|
|
112
|
+
.join(', ');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
renderedHtml = art(path.join(__dirname, 'templates/creator.art'), {
|
|
116
|
+
avatarUrl,
|
|
117
|
+
acceptingCommissions,
|
|
118
|
+
nsfwAcceptable,
|
|
119
|
+
skills,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
title: creator.name || '',
|
|
125
|
+
link: `${baseUrl}/@${creator.screen_name || ''}`,
|
|
126
|
+
description: renderedHtml,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export async function getFollowingsItems(username: string, path: 'friend_works' | 'following_works' | 'following_creators'): Promise<DataItem[]> {
|
|
131
|
+
const url = `${baseUrl}/api/users/${username.replace('@', '')}/followings`;
|
|
132
|
+
|
|
133
|
+
const followings_data = await cache.tryGet(
|
|
134
|
+
`skeb:followings_data:${username}`,
|
|
135
|
+
async () => {
|
|
136
|
+
const data = await ofetch(url, {
|
|
137
|
+
headers: {
|
|
138
|
+
Authorization: `Bearer ${config.skeb.bearerToken}`,
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
return data;
|
|
142
|
+
},
|
|
143
|
+
config.cache.routeExpire,
|
|
144
|
+
false
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
if (!followings_data || typeof followings_data !== 'object') {
|
|
148
|
+
throw new Error('Failed to fetch followings data');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (path === 'following_creators') {
|
|
152
|
+
return followings_data[path].map((item) => processCreator(item)).filter(Boolean) as DataItem[];
|
|
153
|
+
}
|
|
154
|
+
return followings_data[path].map((item) => processWork(item)).filter(Boolean) as DataItem[];
|
|
155
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { Data, DataItem, Route } from '@/types';
|
|
2
|
+
import { config } from '@/config';
|
|
3
|
+
import ConfigNotFoundError from '@/errors/types/config-not-found';
|
|
4
|
+
import { baseUrl, processWork } from './utils';
|
|
5
|
+
import cache from '@/utils/cache';
|
|
6
|
+
import ofetch from '@/utils/ofetch';
|
|
7
|
+
|
|
8
|
+
export const route: Route = {
|
|
9
|
+
path: '/works/:username',
|
|
10
|
+
categories: ['picture'],
|
|
11
|
+
example: '/skeb/works/@brm2_1925',
|
|
12
|
+
parameters: { username: 'Skeb Username with @' },
|
|
13
|
+
features: {
|
|
14
|
+
requireConfig: [
|
|
15
|
+
{
|
|
16
|
+
name: 'SKEB_BEARER_TOKEN',
|
|
17
|
+
optional: false,
|
|
18
|
+
description: '在瀏覽器開發者工具(F12)的主控台中輸入 `localStorage.getItem("token")` 獲取',
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
requirePuppeteer: false,
|
|
22
|
+
antiCrawler: false,
|
|
23
|
+
supportBT: false,
|
|
24
|
+
supportPodcast: false,
|
|
25
|
+
supportScihub: false,
|
|
26
|
+
},
|
|
27
|
+
name: 'Creator Works',
|
|
28
|
+
maintainers: ['SnowAgar25'],
|
|
29
|
+
handler,
|
|
30
|
+
radar: [
|
|
31
|
+
{
|
|
32
|
+
title: 'Creator Works',
|
|
33
|
+
source: ['skeb.jp/:username'],
|
|
34
|
+
target: '/works/:username',
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
description: 'Get the latest works of a specific creator on Skeb',
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
async function handler(ctx): Promise<Data> {
|
|
41
|
+
const username = ctx.req.param('username');
|
|
42
|
+
|
|
43
|
+
if (!config.skeb || !config.skeb.bearerToken) {
|
|
44
|
+
throw new ConfigNotFoundError('Skeb works RSS is disabled due to the lack of <a href="https://docs.rsshub.app/deploy/config#route-specific-configurations">relevant config</a>');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const url = `${baseUrl}/api/users/${username.replace('@', '')}/works`;
|
|
48
|
+
|
|
49
|
+
const items = await cache.tryGet(url, async () => {
|
|
50
|
+
const fetchData = async (retryCount = 0, maxRetries = 3) => {
|
|
51
|
+
const data = await ofetch(url, {
|
|
52
|
+
retry: 0,
|
|
53
|
+
method: 'GET',
|
|
54
|
+
query: { role: 'creator', sort: 'date', offset: '0' },
|
|
55
|
+
headers: {
|
|
56
|
+
'User-Agent': config.ua,
|
|
57
|
+
Cookie: `request_key=${cache.get('skeb:request_key')}`,
|
|
58
|
+
Authorization: `Bearer ${config.skeb.bearerToken}`,
|
|
59
|
+
},
|
|
60
|
+
}).catch((error) => {
|
|
61
|
+
if (retryCount >= maxRetries) {
|
|
62
|
+
throw new Error('Max retries reached');
|
|
63
|
+
}
|
|
64
|
+
const newRequestKey = error.response?._data?.match(/request_key=(.*?);/)?.[1];
|
|
65
|
+
if (newRequestKey) {
|
|
66
|
+
cache.set('skeb:request_key', newRequestKey);
|
|
67
|
+
return fetchData(retryCount + 1, maxRetries);
|
|
68
|
+
}
|
|
69
|
+
throw error;
|
|
70
|
+
});
|
|
71
|
+
return data;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const data = await fetchData();
|
|
75
|
+
|
|
76
|
+
if (!data || !Array.isArray(data)) {
|
|
77
|
+
throw new Error('Invalid data received from API');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return data.map((item) => processWork(item)).filter(Boolean);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
title: `Skeb - ${username}'s Works`,
|
|
85
|
+
link: `${baseUrl}/${username}`,
|
|
86
|
+
item: items as DataItem[],
|
|
87
|
+
};
|
|
88
|
+
}
|