mirror of
https://github.com/DIYgod/RSSHub.git
synced 2026-03-13 10:30:18 +08:00
Merge remote-tracking branch 'origin/master' into feature/cloudflare-workers
This commit is contained in:
3
.github/dependabot.yml
vendored
3
.github/dependabot.yml
vendored
@@ -11,9 +11,6 @@ updates:
|
||||
ignore:
|
||||
- dependency-name: jsrsasign
|
||||
versions: ['>=11.0.0'] # no longer includes KJUR.crypto.Cipher for RSA
|
||||
# ESM only packages
|
||||
- dependency-name: got
|
||||
versions: ['>=12.0.0']
|
||||
- dependency-name: unified
|
||||
versions: ['>=10.0.0']
|
||||
|
||||
|
||||
35
.github/workflows/build-assets.yml
vendored
35
.github/workflows/build-assets.yml
vendored
@@ -47,23 +47,20 @@ jobs:
|
||||
publish_dir: ./assets
|
||||
user_name: 'github-actions[bot]'
|
||||
user_email: '41898282+github-actions[bot]@users.noreply.github.com'
|
||||
- name: Pushes to docs repository - en
|
||||
uses: cpina/github-action-push-to-another-repository@main
|
||||
env:
|
||||
API_TOKEN_GITHUB: ${{ secrets.DOCS_API_TOKEN }}
|
||||
- name: Checkout docs
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
source-directory: './assets/build/docs/en'
|
||||
target-directory: './src/routes'
|
||||
destination-github-username: 'RSSNext'
|
||||
destination-repository-name: 'rsshub-docs'
|
||||
target-branch: 'main'
|
||||
- name: Pushes to docs repository - zh
|
||||
uses: cpina/github-action-push-to-another-repository@main
|
||||
env:
|
||||
API_TOKEN_GITHUB: ${{ secrets.DOCS_API_TOKEN }}
|
||||
with:
|
||||
source-directory: './assets/build/docs/zh'
|
||||
target-directory: './src/zh/routes'
|
||||
destination-github-username: 'RSSNext'
|
||||
destination-repository-name: 'rsshub-docs'
|
||||
target-branch: 'main'
|
||||
repository: 'RSSNext/rsshub-docs'
|
||||
token: ${{ secrets.DOCS_API_TOKEN }}
|
||||
path: rsshub-docs
|
||||
- name: Update docs
|
||||
run: |
|
||||
cp -r ./assets/build/docs/en/* ./rsshub-docs/src/routes
|
||||
cp -r ./assets/build/docs/zh/* ./rsshub-docs/src/zh/routes
|
||||
- name: Commit docs
|
||||
run: |
|
||||
cd rsshub-docs
|
||||
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git config --local user.name "github-actions[bot]"
|
||||
git status
|
||||
git diff-index --quiet HEAD || (git commit -m "chore: auto build https://github.com/$GITHUB_REPOSITORY/commit/$GITHUB_SHA" -a --no-verify && git push "https://${GITHUB_ACTOR}:${{ secrets.DOCS_API_TOKEN }}@github.com/RSSNext/rsshub-docs.git" HEAD:main)
|
||||
|
||||
4
.github/workflows/format.yml
vendored
4
.github/workflows/format.yml
vendored
@@ -4,10 +4,6 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
workflow_run:
|
||||
workflows: ['Build assets']
|
||||
types:
|
||||
- completed
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
4
.github/workflows/issue-command.yml
vendored
4
.github/workflows/issue-command.yml
vendored
@@ -80,6 +80,10 @@ jobs:
|
||||
const script = require(`${process.env.GITHUB_WORKSPACE}/scripts/workflow/test-route/identify.js`)
|
||||
return script({ github, context, core }, body, number, sender)
|
||||
|
||||
- name: Build RSSHub
|
||||
if: env.TEST_CONTINUE
|
||||
run: pnpm build
|
||||
|
||||
- name: Start RSSHub
|
||||
if: env.TEST_CONTINUE
|
||||
run: pnpm start &
|
||||
|
||||
@@ -756,8 +756,8 @@ router.get('/nosetime/home', lazyloadRouteHandler('./routes/nosetime/home'));
|
||||
router.get('/daxiaamu/home', lazyloadRouteHandler('./routes/daxiaamu/home'));
|
||||
|
||||
// 爱发电
|
||||
router.get('/afdian/explore/:type?/:category?', lazyloadRouteHandler('./routes/afdian/explore'));
|
||||
router.get('/afdian/dynamic/:uid', lazyloadRouteHandler('./routes/afdian/dynamic'));
|
||||
// router.get('/afdian/explore/:type?/:category?', lazyloadRouteHandler('./routes/afdian/explore'));
|
||||
// router.get('/afdian/dynamic/:uid', lazyloadRouteHandler('./routes/afdian/dynamic'));
|
||||
|
||||
// Simons Foundation
|
||||
router.get('/simonsfoundation/articles', lazyloadRouteHandler('./routes/simonsfoundation/articles'));
|
||||
|
||||
@@ -26,9 +26,9 @@ export const route: Route = {
|
||||
url: 'www.2023game.com/',
|
||||
description: `分类
|
||||
|
||||
| PS4游戏 | switch游戏 | 3DS游戏 | PSV游戏 | Xbox360 | PS3游戏 | 世嘉MD/SS | PSP游戏 | PC周边 | 怀旧掌机 | 怀旧主机 | PS4教程 | PS4金手指 | switch金手指 | switch教程 | switch补丁 | switch主题 | switch存档 |
|
||||
| -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
|
||||
| ps4 | sgame | 3ds | psv | jiaocheng | ps3yx | zhuji.md | zhangji.psp | pcgame | zhangji | zhuji | ps4.psjc | ps41.ps4pkg | nsaita.cundang | nsaita.pojie | nsaita.buding | nsaita.zhutie | nsaita.zhuti |`,
|
||||
| PS4游戏 | switch游戏 | 3DS游戏 | PSV游戏 | Xbox360 | PS3游戏 | 世嘉MD/SS | PSP游戏 | PC周边 | 怀旧掌机 | 怀旧主机 | PS4教程 | PS4金手指 | switch金手指 | switch教程 | switch补丁 | switch主题 | switch存档 |
|
||||
| -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
|
||||
| ps4 | sgame | 3ds | psv | jiaocheng | ps3yx | zhuji.md | zhangji.psp | pcgame | zhangji | zhuji | ps4.psjc | ps41.ps4pkg | nsaita.cundang | nsaita.pojie | nsaita.buding | nsaita.zhutie | nsaita.zhuti |`,
|
||||
};
|
||||
|
||||
async function handler(ctx?: Context): Promise<Data> {
|
||||
|
||||
@@ -17,10 +17,19 @@ const shortcuts = {
|
||||
};
|
||||
|
||||
export const route: Route = {
|
||||
path: '/{.*}?',
|
||||
path: '/:category/:subCategory?/:keyword?',
|
||||
categories: ['new-media'],
|
||||
example: '/36kr/newsflashes',
|
||||
parameters: {
|
||||
category: '分类,必填项',
|
||||
subCategory: '子分类,选填项,目的是为了兼容老逻辑',
|
||||
keyword: '关键词,选填项,仅搜索文章/快讯时有效',
|
||||
},
|
||||
name: '资讯, 快讯, 用户文章, 主题文章, 专题文章, 搜索文章, 搜索快讯',
|
||||
maintainers: ['nczitzk'],
|
||||
maintainers: ['nczitzk', 'fashioncj'],
|
||||
description: `| 最新资讯频道 | 快讯 | 推荐资讯|生活|房产|职场|搜索文章|搜索快讯|
|
||||
| ------- | -------- | -------- | -------- | -------- | --------| -------- | -------- |
|
||||
| news | newsflashes | recommend | life | estate | workplace | search/articles/关键词 | search/articles/关键词 |`,
|
||||
handler,
|
||||
};
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import { parseDate } from '@/utils/parse-date';
|
||||
import { parseArticle } from './utils';
|
||||
|
||||
export const route: Route = {
|
||||
path: '/:name/:type?',
|
||||
path: '/games/:name/:type?',
|
||||
radar: [
|
||||
{
|
||||
source: ['3dmgame.com/games/:name/:type'],
|
||||
@@ -14,7 +14,7 @@ export const route: Route = {
|
||||
],
|
||||
name: '游戏资讯',
|
||||
categories: ['game'],
|
||||
maintainers: ['sinchang', 'jacky2001114', 'HenryQW'],
|
||||
maintainers: ['sinchang', 'jacky2001114', 'HenryQW', 'lyqluis'],
|
||||
handler,
|
||||
};
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ export const route: Route = {
|
||||
},
|
||||
],
|
||||
name: '新闻中心',
|
||||
maintainers: ['zhboner'],
|
||||
maintainers: ['zhboner', 'lyqluis'],
|
||||
handler,
|
||||
description: `| 新闻推荐 | 游戏新闻 | 动漫影视 | 智能数码 | 时事焦点 |
|
||||
| -------- | -------- | -------- | -------- | ----------- |
|
||||
@@ -35,7 +35,7 @@ export const route: Route = {
|
||||
async function handler(ctx) {
|
||||
const { category = '' } = ctx.req.param();
|
||||
const isArcPost = category && !isNaN(category); // https://www.3dmgame.com/news/\d+/
|
||||
const url = `https://www.3dmgame.com/${category && category !== 'news_36_1' ? 'news/' : ''}${category ?? 'news'}/`;
|
||||
const url = `https://www.3dmgame.com/${category === 'news_36_1' ? category : 'news/' + category}`;
|
||||
const res = await got(url);
|
||||
const $ = load(res.data);
|
||||
const list = $(isArcPost ? '.selectarcpost' : '.selectpost')
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
const got = require('@/utils/got');
|
||||
import got from '@/utils/got';
|
||||
|
||||
module.exports = async (ctx) => {
|
||||
const url_slug = ctx.params.uid.replace('@', '');
|
||||
export const route: Route = {
|
||||
path: '/dynamic/:uid?',
|
||||
categories: ['other'],
|
||||
example: '/afdian/dynamic/@afdian',
|
||||
parameters: { uid: '用户id,用户动态页面url里可找到' },
|
||||
name: '用户动态',
|
||||
maintainers: ['sanmmm'],
|
||||
handler,
|
||||
};
|
||||
|
||||
async function handler(ctx) {
|
||||
const url_slug = ctx.req.param('uid').replace('@', '');
|
||||
const baseUrl = 'https://afdian.net';
|
||||
const userInfoRes = await got(`${baseUrl}/api/user/get-profile-by-slug`, {
|
||||
searchParams: {
|
||||
@@ -26,11 +36,11 @@ module.exports = async (ctx) => {
|
||||
pubDate: new Date(Number(publish_time) * 1000).toUTCString(),
|
||||
};
|
||||
});
|
||||
ctx.state.data = {
|
||||
return {
|
||||
title: `${name}的爱发电动态`,
|
||||
description: `${name}的爱发电动态`,
|
||||
image: avatar,
|
||||
link: `${baseUrl}/@${url_slug}`,
|
||||
item: list,
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
const got = require('@/utils/got');
|
||||
import got from '@/utils/got';
|
||||
|
||||
const categoryMap = {
|
||||
所有: '',
|
||||
@@ -26,8 +26,29 @@ const typeToLabel = {
|
||||
hot: '人气',
|
||||
};
|
||||
|
||||
module.exports = async (ctx) => {
|
||||
const { type = 'rec', category = '所有' } = ctx.params;
|
||||
export const route: Route = {
|
||||
path: '/explore/:type/:category?',
|
||||
categories: ['other'],
|
||||
example: '/afdian/explore/hot/所有',
|
||||
parameters: { type: '分类', category: '目录类型,默认为 `所有`' },
|
||||
name: '发现用户',
|
||||
maintainers: ['sanmmm'],
|
||||
description: `分类
|
||||
|
||||
| 推荐 | 最热 |
|
||||
| ---- | ---- |
|
||||
| rec | hot |
|
||||
|
||||
目录类型
|
||||
|
||||
| 所有 | 绘画 | 视频 | 写作 | 游戏 | 音乐 | 播客 | 摄影 | 技术 | Vtuber | 舞蹈 | 体育 | 旅游 | 美食 | 时尚 | 数码 | 动画 | 其他 |
|
||||
| ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ------ | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- |
|
||||
| 所有 | 绘画 | 视频 | 写作 | 游戏 | 音乐 | 播客 | 摄影 | 技术 | Vtuber | 舞蹈 | 体育 | 旅游 | 美食 | 时尚 | 数码 | 动画 | 其他 |`,
|
||||
handler,
|
||||
};
|
||||
|
||||
async function handler(ctx) {
|
||||
const { type = 'rec', category = '所有' } = ctx.req.param();
|
||||
const baseUrl = 'https://afdian.net';
|
||||
const link = `${baseUrl}/api/creator/list`;
|
||||
const res = await got(link, {
|
||||
@@ -48,10 +69,10 @@ module.exports = async (ctx) => {
|
||||
link: `${baseUrl}/@${item.url_slug}`,
|
||||
};
|
||||
});
|
||||
ctx.state.data = {
|
||||
return {
|
||||
title: `爱发电-创作者 (按 ${category}/${typeToLabel[type]})`,
|
||||
description: `爱发电-发现创作者 (按 ${category}/${typeToLabel[type]})`,
|
||||
link: `${baseUrl}/explore`,
|
||||
item: list,
|
||||
};
|
||||
};
|
||||
}
|
||||
6
lib/routes/afdian/namespace.ts
Normal file
6
lib/routes/afdian/namespace.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import type { Namespace } from '@/types';
|
||||
|
||||
export const namespace: Namespace = {
|
||||
name: '爱发电',
|
||||
url: 'afdian.net',
|
||||
};
|
||||
@@ -20,8 +20,7 @@ export const route: Route = {
|
||||
},
|
||||
name: '新书速递',
|
||||
maintainers: ['fengkx', 'lyqluis'],
|
||||
description: `
|
||||
| 文学 | 小说 | 历史文化 | 社会纪实 | 科学新知 | 艺术设计 | 商业经管 | 绘本漫画 |
|
||||
description: `| 文学 | 小说 | 历史文化 | 社会纪实 | 科学新知 | 艺术设计 | 商业经管 | 绘本漫画 |
|
||||
| ------------ | ------- | -------- | --------- | -------- | -------- | -------- | -------- |
|
||||
| prose_poetry | fiction | history | biography | science | art | business | comics |`,
|
||||
handler,
|
||||
|
||||
6
lib/routes/ippa/namespace.ts
Normal file
6
lib/routes/ippa/namespace.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import type { Namespace } from '@/types';
|
||||
|
||||
export const namespace: Namespace = {
|
||||
name: '子方有料',
|
||||
url: 'ippa.top',
|
||||
};
|
||||
35
lib/routes/ippa/rss.ts
Normal file
35
lib/routes/ippa/rss.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Route } from '@/types';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import got from '@/utils/got';
|
||||
|
||||
export const route: Route = {
|
||||
path: '/',
|
||||
categories: ['blog'],
|
||||
example: '/ippa',
|
||||
radar: [
|
||||
{
|
||||
source: ['ippa.top/'],
|
||||
},
|
||||
],
|
||||
name: '最新文章',
|
||||
maintainers: ['cnkmmk'],
|
||||
handler,
|
||||
url: 'ippa.top/',
|
||||
};
|
||||
|
||||
async function handler() {
|
||||
const url = 'https://www.ippa.top';
|
||||
const response = await got(`${url}/wp-json/wp/v2/posts`);
|
||||
const list = response.data;
|
||||
return {
|
||||
title: '子方有料',
|
||||
link: url,
|
||||
description: '子方有料 - 最新文章',
|
||||
item: list.map((item) => ({
|
||||
title: item.title.rendered,
|
||||
link: item.link,
|
||||
pubDate: parseDate(item.date_gmt),
|
||||
description: item.content.rendered,
|
||||
})),
|
||||
};
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import { parseDate } from '@/utils/parse-date';
|
||||
export const route: Route = {
|
||||
path: '/news/:language?',
|
||||
categories: ['new-media'],
|
||||
example: '/news/zh-hans, /news/zh-hant',
|
||||
example: '/news/zh-hans',
|
||||
parameters: {
|
||||
language: '语言',
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Namespace } from '@/types';
|
||||
|
||||
export const namespace: Namespace = {
|
||||
name: '哩哔轻小说',
|
||||
name: '哔哩轻小说',
|
||||
url: 'linovelib.com',
|
||||
};
|
||||
|
||||
36
lib/routes/linovelib/volume.ts
Normal file
36
lib/routes/linovelib/volume.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import type { Route, Data } from '@/types';
|
||||
import type { Context } from 'hono';
|
||||
import got from '@/utils/got';
|
||||
import { load } from 'cheerio';
|
||||
|
||||
export const route: Route = {
|
||||
path: '/volume/:id',
|
||||
categories: ['reading'],
|
||||
example: '/linovelib/volume/8',
|
||||
parameters: { id: '小说 ID,可在小说页 URL 中找到' },
|
||||
radar: [
|
||||
{
|
||||
source: ['www.linovelib.com/novel/:id/catalog'],
|
||||
},
|
||||
],
|
||||
name: '卷',
|
||||
maintainers: ['rkscv'],
|
||||
handler,
|
||||
};
|
||||
|
||||
async function handler(ctx: Context): Promise<Data> {
|
||||
const { id } = ctx.req.param();
|
||||
const link = `https://www.linovelib.com/novel/${id}/catalog`;
|
||||
const $ = load(await got(link).text());
|
||||
return {
|
||||
title: `${$('.book-meta h1').text()} - 哔哩轻小说`,
|
||||
link,
|
||||
item: $('.volume')
|
||||
.toArray()
|
||||
.map((elem) => ({
|
||||
title: $(elem).find('h2').text(),
|
||||
link: $(elem).find('.volume-cover').attr('href'),
|
||||
}))
|
||||
.toReversed(),
|
||||
};
|
||||
}
|
||||
6
lib/routes/loongarch/namespace.ts
Normal file
6
lib/routes/loongarch/namespace.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import type { Namespace } from '@/types';
|
||||
|
||||
export const namespace: Namespace = {
|
||||
name: 'LA UOSC社区',
|
||||
url: 'loongarch.org',
|
||||
};
|
||||
59
lib/routes/loongarch/post.ts
Normal file
59
lib/routes/loongarch/post.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import got from '@/utils/got';
|
||||
|
||||
export const route: Route = {
|
||||
path: '/post/:type?',
|
||||
categories: ['bbs'],
|
||||
example: '/loongarch/post/newest',
|
||||
parameters: { type: 'top 或 newest' },
|
||||
radar: [
|
||||
{
|
||||
source: ['bbs.loongarch.org'],
|
||||
},
|
||||
],
|
||||
name: '最热 / 最新帖子',
|
||||
maintainers: ['ladeng07', '3401797899'],
|
||||
handler,
|
||||
url: 'bbs.loongarch.org/',
|
||||
};
|
||||
|
||||
async function handler(ctx) {
|
||||
const type = ctx.req.param('type');
|
||||
const link = 'https://bbs.loongarch.org/api/discussions';
|
||||
|
||||
let title = '最新帖子';
|
||||
let sortValue = '-createdAt';
|
||||
if (type === 'top') {
|
||||
title = '最热帖子';
|
||||
sortValue = '-commentCount';
|
||||
}
|
||||
|
||||
const { data: response } = await got('https://bbs.loongarch.org/api/discussions', {
|
||||
searchParams: {
|
||||
include: 'user,tags,tags.parent,firstPost',
|
||||
sort: sortValue,
|
||||
'page[offset]': 0,
|
||||
},
|
||||
});
|
||||
|
||||
const users = response.included.filter((i) => i.type === 'users');
|
||||
const tags = response.included.filter((i) => i.type === 'tags');
|
||||
const posts = response.included.filter((i) => i.type === 'posts');
|
||||
|
||||
const out = response.data.map(({ attributes, relationships }) => ({
|
||||
title: attributes.title,
|
||||
link: `https://bbs.loongarch.org/d/${attributes.slug}`,
|
||||
author: users.find((i) => i.id === relationships.user.data.id).attributes.displayName,
|
||||
description: posts.find((i) => i.id === relationships.firstPost.data.id).attributes.contentHtml,
|
||||
pubDate: parseDate(attributes.createdAt),
|
||||
updated: parseDate(attributes.lastPostedAt),
|
||||
category: relationships.tags.data.map((tag) => tags.find((i) => i.id === tag.id).attributes.name),
|
||||
}));
|
||||
|
||||
return {
|
||||
title: `LA UOSC-${title}`,
|
||||
link,
|
||||
description: `LA UOSC-${title}`,
|
||||
item: out,
|
||||
};
|
||||
}
|
||||
@@ -1,28 +1,19 @@
|
||||
import { Route } from '@/types';
|
||||
|
||||
import cache from '@/utils/cache';
|
||||
import got from '@/utils/got';
|
||||
import { load } from 'cheerio';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import timezone from '@/utils/timezone';
|
||||
|
||||
const baseUrl = 'https://xd.x6d.com';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
|
||||
export const route: Route = {
|
||||
path: '/:id?',
|
||||
categories: ['new-media'],
|
||||
example: '/x6d/34',
|
||||
parameters: { id: '分类 id,可在对应分类页面的 URL 中找到,默认为首页最近更新' },
|
||||
features: {
|
||||
requireConfig: false,
|
||||
requirePuppeteer: false,
|
||||
antiCrawler: false,
|
||||
supportBT: false,
|
||||
supportPodcast: false,
|
||||
supportScihub: false,
|
||||
},
|
||||
name: '分类',
|
||||
url: 'xd.x6d.com',
|
||||
maintainers: ['nczitzk'],
|
||||
handler,
|
||||
example: '/x6d/34',
|
||||
parameters: { id: '分类 id,可在对应分类页面的 URL 中找到,默认为首页最近更新' },
|
||||
description: `| 技巧分享 | QQ 技巧 | 微信技巧 | 其他教程 | 其他分享 |
|
||||
| -------- | ------- | -------- | -------- | -------- |
|
||||
| 31 | 55 | 112 | 33 | 88 |
|
||||
@@ -46,54 +37,105 @@ export const route: Route = {
|
||||
| 资源宝库 | 书籍资料 | 设计资源 | 剪辑资源 | 办公资源 | 壁纸资源 | 编程资源 |
|
||||
| -------- | -------- | -------- | -------- | -------- | -------- | -------- |
|
||||
| 106 | 107 | 108 | 109 | 110 | 111 | 113 |`,
|
||||
categories: ['new-media'],
|
||||
|
||||
features: {
|
||||
requireConfig: false,
|
||||
requirePuppeteer: false,
|
||||
antiCrawler: false,
|
||||
supportBT: false,
|
||||
supportPodcast: false,
|
||||
supportScihub: false,
|
||||
},
|
||||
};
|
||||
|
||||
async function handler(ctx) {
|
||||
export async function handler(ctx) {
|
||||
const { id = 'latest' } = ctx.req.param();
|
||||
const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 22;
|
||||
|
||||
const currentUrl = id === 'latest' ? baseUrl : `${baseUrl}/html/${id}.html`;
|
||||
const rootUrl = 'https://xd.x6d.com';
|
||||
|
||||
const { data: response } = await got(currentUrl);
|
||||
let currentUrl = new URL(id === 'latest' ? '' : `html/${id}.html`, rootUrl).href;
|
||||
|
||||
const $ = load(response);
|
||||
const { data: firstResponse } = await got(currentUrl);
|
||||
|
||||
let $;
|
||||
|
||||
if (/<meta\s/.test(firstResponse)) {
|
||||
$ = load(firstResponse);
|
||||
} else {
|
||||
currentUrl = new URL(
|
||||
id === 'latest'
|
||||
? ''
|
||||
: firstResponse
|
||||
.match(/'([\w./=?]+)'/g)
|
||||
.reverse()
|
||||
.join('')
|
||||
.replaceAll("'", ''),
|
||||
rootUrl
|
||||
).href;
|
||||
|
||||
const { data: response } = await got(currentUrl);
|
||||
|
||||
$ = load(response);
|
||||
}
|
||||
|
||||
$('i.rj').remove();
|
||||
|
||||
const query =
|
||||
id === 'latest'
|
||||
? $('#newslist ul')
|
||||
.eq(0)
|
||||
.find('li')
|
||||
.not('.addd')
|
||||
.find('a')
|
||||
.slice(0, ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 22)
|
||||
: $('a.soft-title').slice(0, ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 10);
|
||||
const language = 'zh';
|
||||
|
||||
const list = query.toArray().map((item) => {
|
||||
const query = id === 'latest' ? $('#newslist ul').first().find('li').not('li.addd').find('a').slice(0, limit) : $('a.soft-title').slice(0, limit);
|
||||
|
||||
let items = query.toArray().map((item) => {
|
||||
item = $(item);
|
||||
|
||||
return {
|
||||
title: item.text(),
|
||||
link: `${baseUrl}${item.attr('href')}`,
|
||||
title: item.prop('title') ?? item.text(),
|
||||
link: new URL(item.prop('href'), rootUrl).href,
|
||||
language,
|
||||
};
|
||||
});
|
||||
|
||||
const items = await Promise.all(
|
||||
list.map((item) =>
|
||||
items = await Promise.all(
|
||||
items.map((item) =>
|
||||
cache.tryGet(item.link, async () => {
|
||||
const { data: detailResponse } = await got(item.link);
|
||||
const content = load(detailResponse);
|
||||
|
||||
item.description = content('div.article-content').html();
|
||||
item.pubDate = timezone(parseDate(content('time').text()), 8);
|
||||
const $$ = load(detailResponse);
|
||||
|
||||
const title = $$('h1.article-title').text();
|
||||
const description = $$('div.article-content').html();
|
||||
const image = new URL($$('div.article-content img').first().prop('src'), rootUrl).href;
|
||||
|
||||
item.title = title;
|
||||
item.description = description;
|
||||
item.pubDate = timezone(parseDate($$('time').text()), +8);
|
||||
item.category = $$('b.bq-wg')
|
||||
.toArray()
|
||||
.map((c) => $$(c).text());
|
||||
item.author = $$('span.bq-zz').text();
|
||||
item.content = {
|
||||
html: description,
|
||||
text: $$('div.article-content').text(),
|
||||
};
|
||||
item.image = image;
|
||||
item.banner = image;
|
||||
item.language = language;
|
||||
|
||||
return item;
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
const image = new URL($('div.header-logo img').prop('src'), rootUrl).href;
|
||||
|
||||
return {
|
||||
title: `小刀娱乐网 - ${$('title').text().split('-')[0]}`,
|
||||
title: $('title').text().split(/\s-/)[0],
|
||||
description: $('meta[name="description"]').prop('content'),
|
||||
link: currentUrl,
|
||||
item: items,
|
||||
allowEmpty: true,
|
||||
image,
|
||||
language,
|
||||
};
|
||||
}
|
||||
|
||||
6
lib/routes/ygkkk/namespace.ts
Normal file
6
lib/routes/ygkkk/namespace.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import type { Namespace } from '@/types';
|
||||
|
||||
export const namespace: Namespace = {
|
||||
name: '甬哥侃侃侃YouTube教程摘要随笔',
|
||||
url: 'ygkkk.blogspot.com',
|
||||
};
|
||||
48
lib/routes/ygkkk/rss.ts
Normal file
48
lib/routes/ygkkk/rss.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { Route } from '@/types';
|
||||
import got from '@/utils/got';
|
||||
import { load } from 'cheerio';
|
||||
|
||||
export const route: Route = {
|
||||
path: '/',
|
||||
categories: ['blog'],
|
||||
example: '/ygkkk',
|
||||
radar: [
|
||||
{
|
||||
source: ['ygkkk.blogspot.com/'],
|
||||
},
|
||||
],
|
||||
name: '最新发表',
|
||||
maintainers: ['cnkmmk'],
|
||||
handler,
|
||||
url: 'ygkkk.blogspot.com/',
|
||||
};
|
||||
|
||||
async function handler() {
|
||||
const currentUrl = 'https://ygkkk.blogspot.com';
|
||||
const response = await got(`${currentUrl}/feeds/posts/default?alt=rss`);
|
||||
const $ = load(response.data, { xmlMode: true });
|
||||
const title_main = $('channel > title').text();
|
||||
const description_main = $('channel > description').text();
|
||||
const items = $('channel > item')
|
||||
.map((_, item) => {
|
||||
const $item = $(item);
|
||||
const link = $item.find('link').text();
|
||||
const title = $item.find('title').text();
|
||||
const description = $item.find('description').text();
|
||||
const pubDate = $item.find('pubDate').text();
|
||||
return {
|
||||
link,
|
||||
pubDate, // no need to normalize because it's from a valid RSS feed
|
||||
title,
|
||||
description,
|
||||
};
|
||||
})
|
||||
.get();
|
||||
|
||||
return {
|
||||
title: title_main,
|
||||
description: description_main,
|
||||
link: currentUrl,
|
||||
item: items,
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import logger from '@/utils/logger';
|
||||
import { config } from '@/config';
|
||||
import got, { CancelableRequest, Response as GotResponse, NormalizedOptions, Options, Got } from 'got';
|
||||
import got, { CancelableRequest, Response as GotResponse, OptionsInit, Options, Got } from 'got';
|
||||
|
||||
type Response<T> = GotResponse<string> & {
|
||||
data: T;
|
||||
@@ -25,22 +25,14 @@ const custom: {
|
||||
delete: GotRequestFunction;
|
||||
} & GotRequestFunction &
|
||||
Got = got.extend({
|
||||
get: got.get,
|
||||
retry: {
|
||||
limit: config.requestRetry,
|
||||
statusCodes: [400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 421, 422, 423, 424, 426, 428, 429, 431, 451, 500, 501, 502, 503, 504, 505, 506, 507, 508, 510, 511, 521, 522, 524],
|
||||
},
|
||||
hooks: {
|
||||
beforeRetry: [
|
||||
(
|
||||
options: NormalizedOptions & {
|
||||
retryCount?: number;
|
||||
},
|
||||
err,
|
||||
count
|
||||
) => {
|
||||
logger.error(`Request ${options.url} fail, retry attempt #${count}: ${err}`);
|
||||
options.retryCount = count;
|
||||
(err, count) => {
|
||||
logger.error(`Request ${err.options.url} fail, retry attempt #${count}: ${err}`);
|
||||
},
|
||||
],
|
||||
beforeRedirect: [
|
||||
@@ -63,7 +55,7 @@ const custom: {
|
||||
],
|
||||
init: [
|
||||
(
|
||||
options: Options & {
|
||||
options: OptionsInit & {
|
||||
data?: string;
|
||||
}
|
||||
) => {
|
||||
@@ -84,4 +76,4 @@ const custom: {
|
||||
custom.all = (list) => Promise.all(list);
|
||||
|
||||
export default custom;
|
||||
export type { Response, NormalizedOptions, Options } from 'got';
|
||||
export type { Response, Options } from 'got';
|
||||
|
||||
@@ -45,6 +45,7 @@ const requestWrapper = (url: string, options: http.RequestOptions = {}) => {
|
||||
if (config.proxyStrategy === 'all') {
|
||||
prxied = proxyWrapper(url, optionsWithHeaders);
|
||||
} else if (config.proxyStrategy === 'on_retry' && (optionsWithHeaders as any).retryCount) {
|
||||
// TODO
|
||||
prxied = proxyWrapper(url, optionsWithHeaders);
|
||||
}
|
||||
if (prxied) {
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
"fanfou-sdk": "5.0.0",
|
||||
"git-rev-sync": "3.0.2",
|
||||
"googleapis": "134.0.0",
|
||||
"got": "11.8.6",
|
||||
"got": "14.2.1",
|
||||
"hono": "4.1.3",
|
||||
"html-to-text": "9.0.5",
|
||||
"https-proxy-agent": "7.0.4",
|
||||
@@ -157,7 +157,7 @@
|
||||
"eslint-plugin-n": "16.6.2",
|
||||
"eslint-plugin-prettier": "5.1.3",
|
||||
"eslint-plugin-unicorn": "51.0.1",
|
||||
"eslint-plugin-yml": "1.13.1",
|
||||
"eslint-plugin-yml": "1.13.2",
|
||||
"fs-extra": "11.2.0",
|
||||
"husky": "9.0.11",
|
||||
"lint-staged": "15.2.2",
|
||||
|
||||
815
pnpm-lock.yaml
generated
815
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user