mirror of
https://github.com/DIYgod/RSSHub.git
synced 2026-09-24 23:12:34 +08:00
feat: v3 migration - 8/n
This commit is contained in:
+2
-2
@@ -3,8 +3,8 @@ import type { Hono, Handler } from 'hono';
|
||||
import { serveStatic } from '@hono/node-server/serve-static';
|
||||
import * as path from 'node:path';
|
||||
|
||||
import index from '@/v3/index';
|
||||
import robotstxt from '@/v3/robots.txt';
|
||||
import index from '@/v2/index';
|
||||
import robotstxt from '@/v2/robots.txt';
|
||||
|
||||
type Root = {
|
||||
get: (routePath: string, filePath: string) => void;
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = function (router) {
|
||||
router.get('/:id', './index');
|
||||
};
|
||||
@@ -1,5 +0,0 @@
|
||||
module.exports = {
|
||||
'/topic/text/:id': ['HenryQW'],
|
||||
'/topic/:id/:showUid?': ['DIYgod', 'prnake'],
|
||||
'/user/:id': ['DIYgod', 'prnake'],
|
||||
};
|
||||
@@ -1,54 +0,0 @@
|
||||
module.exports = {
|
||||
'okjike.com': {
|
||||
_name: '即刻',
|
||||
m: [
|
||||
{
|
||||
title: '用户动态',
|
||||
docs: 'https://docs.rsshub.app/routes/social-media#ji-ke',
|
||||
source: '/users/:uid',
|
||||
target: '/jike/user/:uid',
|
||||
},
|
||||
{
|
||||
title: '用户动态',
|
||||
docs: 'https://docs.rsshub.app/routes/social-media#ji-ke',
|
||||
source: '/reposts/:repostId',
|
||||
target: (params, url, document) => {
|
||||
const uid = document.querySelector('.avatar').getAttribute('href').replace('/users/', '');
|
||||
return uid ? `/jike/user/${uid}` : '';
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '圈子',
|
||||
docs: 'https://docs.rsshub.app/routes/social-media#ji-ke',
|
||||
source: '/topics/:id',
|
||||
target: '/jike/topic/:id',
|
||||
},
|
||||
{
|
||||
title: '圈子 - 纯文字',
|
||||
docs: 'https://docs.rsshub.app/routes/social-media#ji-ke',
|
||||
source: '/topics/:id',
|
||||
target: '/jike/topic/text/:id',
|
||||
},
|
||||
],
|
||||
web: [
|
||||
{
|
||||
title: '用户动态',
|
||||
docs: 'https://docs.rsshub.app/routes/social-media#ji-ke',
|
||||
source: '/u/:uid',
|
||||
target: '/jike/user/:uid',
|
||||
},
|
||||
{
|
||||
title: '圈子',
|
||||
docs: 'https://docs.rsshub.app/routes/social-media#ji-ke',
|
||||
source: '/topic/:id',
|
||||
target: '/jike/topic/:id',
|
||||
},
|
||||
{
|
||||
title: '圈子 - 纯文字',
|
||||
docs: 'https://docs.rsshub.app/routes/social-media#ji-ke',
|
||||
source: '/topic/:id',
|
||||
target: '/jike/topic/text/:id',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -1,5 +0,0 @@
|
||||
module.exports = (router) => {
|
||||
router.get('/topic/text/:id', require('./topic-text'));
|
||||
router.get('/topic/:id/:showUid?', require('./topic'));
|
||||
router.get('/user/:id', require('./user'));
|
||||
};
|
||||
@@ -1,21 +0,0 @@
|
||||
const dayjs = require('dayjs');
|
||||
const { constructTopicEntry } = require('./utils');
|
||||
|
||||
module.exports = async (ctx) => {
|
||||
const id = ctx.req.param('id');
|
||||
const topicUrl = `https://m.okjike.com/topics/${id}`;
|
||||
|
||||
const data = await constructTopicEntry(ctx, topicUrl);
|
||||
|
||||
if (data) {
|
||||
ctx.state.data.item = data.posts.map((item) => {
|
||||
const date = dayjs(item.createdAt);
|
||||
return {
|
||||
title: `${data.topic.content} ${date.format('MM月DD日')}`,
|
||||
description: item.content.replaceAll('\n', '<br>'),
|
||||
pubDate: date.toDate(),
|
||||
link: `https://m.okjike.com/originalPosts/${item.id}`,
|
||||
};
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,48 +0,0 @@
|
||||
const got = require('@/utils/got');
|
||||
const { topicDataHanding } = require('./utils');
|
||||
const cheerio = require('cheerio');
|
||||
const dayjs = require('dayjs');
|
||||
const { constructTopicEntry } = require('@/v3/jike/utils');
|
||||
|
||||
module.exports = async (ctx) => {
|
||||
const id = ctx.req.param('id');
|
||||
const topicUrl = `https://m.okjike.com/topics/${id}`;
|
||||
|
||||
const data = await constructTopicEntry(ctx, topicUrl);
|
||||
|
||||
if (data) {
|
||||
let item = topicDataHanding(data, ctx);
|
||||
if (id === '553870e8e4b0cafb0a1bef68' || id === '55963702e4b0d84d2c30ce6f') {
|
||||
item = await Promise.all(
|
||||
item.map(async (one) => {
|
||||
const item = { ...one };
|
||||
const regResult = /https:\/\/www\.okjike\.com\/medium\/[\dA-Za-z]*/.exec(item.description);
|
||||
if (regResult) {
|
||||
const newsUrl = regResult[0];
|
||||
item.description = await ctx.cache.tryGet(newsUrl, async () => {
|
||||
const { data } = await got(newsUrl);
|
||||
const $ = cheerio.load(data);
|
||||
const upper = $('ul.main > li.item');
|
||||
const links = upper.find('a').map((_, ele) => $(ele).attr('href'));
|
||||
const texts = upper.find('span.text').map((_, ele) => $(ele).text());
|
||||
let description = '';
|
||||
for (const [i, link] of links.entries()) {
|
||||
description += `${i + 1}、<a href="${link}">${texts[i]}</a><br>`;
|
||||
}
|
||||
description = description.replace(/<br>$/, '');
|
||||
return description;
|
||||
});
|
||||
}
|
||||
item.title = `${data.topic.content} ${dayjs(one.pubDate).format('MM月DD日')}`;
|
||||
return item;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
ctx.set('data', {
|
||||
...ctx.get('data'),
|
||||
item,
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,158 +0,0 @@
|
||||
const got = require('@/utils/got');
|
||||
const cheerio = require('cheerio');
|
||||
const { parseDate } = require('@/utils/parse-date');
|
||||
|
||||
module.exports = async (ctx) => {
|
||||
const id = ctx.req.param('id');
|
||||
|
||||
const response = await got({
|
||||
method: 'get',
|
||||
url: `https://m.okjike.com/users/${id}`,
|
||||
headers: {
|
||||
Referer: `https://m.okjike.com/users/${id}`,
|
||||
},
|
||||
});
|
||||
|
||||
const html = response.data;
|
||||
const $ = cheerio.load(html);
|
||||
const raw = $('[type = "application/json"]').html();
|
||||
const data = JSON.parse(raw).props.pageProps;
|
||||
|
||||
const getLink = (id, type) => {
|
||||
switch (type) {
|
||||
case 'REPOST':
|
||||
return `https://m.okjike.com/reposts/${id}`;
|
||||
case 'MEDIUM':
|
||||
return `https://www.okjike.com/medium/${id}`;
|
||||
default:
|
||||
return `https://m.okjike.com/originalPosts/${id}`;
|
||||
}
|
||||
};
|
||||
|
||||
const items = await Promise.all(
|
||||
data.posts.map(async (item) => {
|
||||
const typeMap = {
|
||||
ORIGINAL_POST: '发布',
|
||||
REPOST: '转发',
|
||||
ANSWER: '回答',
|
||||
QUESTION: '提问',
|
||||
PERSONAL_UPDATE: '创建新主题',
|
||||
};
|
||||
|
||||
let linkTemplate = '';
|
||||
if (item.linkInfo && item.linkInfo.linkUrl) {
|
||||
linkTemplate = `<a href="${item.linkInfo.linkUrl}">${item.linkInfo.title}</a><br>`;
|
||||
}
|
||||
|
||||
let imgTemplate = '';
|
||||
if (item.pictures) {
|
||||
for (const picture of item.pictures) {
|
||||
imgTemplate += `<br><img src="${picture.picUrl}">`;
|
||||
}
|
||||
}
|
||||
|
||||
let content = item.content || (item.linkInfo && item.linkInfo.title) || (item.question && item.question.title) || item.title || '';
|
||||
content = content.replaceAll(/\r\n|\n|\r/g, '<br>');
|
||||
|
||||
let shortenTitle = '一条动态';
|
||||
if (content) {
|
||||
shortenTitle = content.replaceAll(/(<br>)+/g, ' ');
|
||||
content = `${content}<br><br>`;
|
||||
}
|
||||
|
||||
let repostContent;
|
||||
if (item.type === 'REPOST') {
|
||||
const screenNameTemplate = item.target.user ? `<a href="https://m.okjike.com/users/${item.target.user.username}" target="_blank">@${item.target.user.screenName}</a>` : '';
|
||||
|
||||
let repostImgTemplate = '';
|
||||
if (item.target.pictures) {
|
||||
for (const picture of item.target.pictures) {
|
||||
repostImgTemplate += `<br><img src="${picture.thumbnailUrl}">`;
|
||||
}
|
||||
}
|
||||
|
||||
repostContent = `<div class="rsshub-quote">转发 ${screenNameTemplate}: ${item.target.content}${repostImgTemplate}</div>`.replaceAll(/\r\n|\n|\r/g, '<br>');
|
||||
content = `${content}${repostContent}`;
|
||||
}
|
||||
// 部分功能未知
|
||||
/* else if (item.type === 'ANSWER') {
|
||||
let answerTextTemplate = '';
|
||||
let answerImgTemplate = '';
|
||||
let answerImgKeys = [];
|
||||
item.richtextContent.blocks &&
|
||||
item.richtextContent.blocks.forEach((item) => {
|
||||
if (item.entityRanges.length && item.text === '[图片]') {
|
||||
answerImgKeys = [...answerImgKeys, ...Object.keys(item.entityRanges)];
|
||||
} else {
|
||||
answerTextTemplate += item.text;
|
||||
}
|
||||
});
|
||||
|
||||
if (answerImgKeys.length) {
|
||||
answerImgKeys.forEach((key) => {
|
||||
const entity = item.richtextContent.entityMap[key];
|
||||
if (entity.type.toUpperCase() === 'IMAGE') {
|
||||
answerImgTemplate += `<br><img src="${entity.data.pictureUrl.middlePicUrl}">`;
|
||||
}
|
||||
});
|
||||
}
|
||||
const answerContent = `回答: ${answerTextTemplate}${answerImgTemplate}`;
|
||||
content = `${content}${answerContent}`.replace(/\n|\r/g, '<br>');
|
||||
} else if (item.type === 'QUESTION') {
|
||||
content = `在主题 <a href="https://web.okjike.com/topic/${item.topic.id}/official" target="_blank">${item.topic.content}</a> 提出了一个问题:<br><br>${content}`;
|
||||
} else if (item.type === 'PERSONAL_UPDATE') {
|
||||
shortenTitle = item.topic.content;
|
||||
content = `<img src="${item.topic.squarePicture.picUrl}"> 主题简介:<br>${item.topic.briefIntro.replace(/(?:\r\n|\r|\n)/g, '<br>')}`;
|
||||
}*/
|
||||
|
||||
const single = {
|
||||
title: `${typeMap[item.type]}了: ${shortenTitle}`,
|
||||
description: `${content}${linkTemplate}${imgTemplate}`.replace(/(<br>|\s)+$/, ''),
|
||||
pubDate: parseDate(item.createdAt),
|
||||
link: getLink(item.id, item.type),
|
||||
_extra: repostContent && {
|
||||
links: [
|
||||
{
|
||||
url: getLink(item.target.id, item.target.type),
|
||||
type: 'quote',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
if (id === 'wenhao1996' && item.topic.id === '553870e8e4b0cafb0a1bef68') {
|
||||
single.link = item.urlsInText[0].url;
|
||||
|
||||
const { data } = await got({
|
||||
method: 'get',
|
||||
url: single.link,
|
||||
headers: {
|
||||
Referer: `https://m.okjike.com/users/${id}`,
|
||||
},
|
||||
});
|
||||
const $$ = cheerio.load(data);
|
||||
$$('span.num,span.arrow').remove();
|
||||
|
||||
single.title = `一觉醒来世界发生了什么 ${$$('title').text()}`;
|
||||
|
||||
single.description = '';
|
||||
$$('div.container')
|
||||
.find('li.item')
|
||||
// eslint-disable-next-line array-callback-return
|
||||
.map((i, j) => {
|
||||
single.description += `<a href="${$$(j).find('a').attr('href')}">${$$(j).find('a').text()}</a><br>`;
|
||||
});
|
||||
}
|
||||
|
||||
return single;
|
||||
})
|
||||
);
|
||||
|
||||
ctx.set('data', {
|
||||
title: `${data.user.screenName}的即刻动态`,
|
||||
description: data.user.bio,
|
||||
link: `https://m.okjike.com/users/${id}`,
|
||||
image: data.user.avatarImage.picUrl,
|
||||
item: items,
|
||||
});
|
||||
};
|
||||
@@ -1,198 +0,0 @@
|
||||
const cheerio = require('cheerio');
|
||||
import parseDate from '@/utils/parse-date';
|
||||
import got from '@/utils/got';
|
||||
import { config } from '@/config';
|
||||
import cache from '@/utils/cache';
|
||||
|
||||
const videoAPI = 'https://api.ruguoapp.com/1.0/mediaMeta/play?type=ORIGINAL_POST';
|
||||
|
||||
module.exports = {
|
||||
topicDataHanding: (data, ctx) =>
|
||||
data.posts.map((item) => {
|
||||
let audioName, videoName, linkName;
|
||||
|
||||
// 获取纯文字内容 和 即刻原文链接
|
||||
let content, link;
|
||||
switch (item.type) {
|
||||
case 'ORIGINAL_POST':
|
||||
content = item.content;
|
||||
link = `https://m.okjike.com/originalPosts/${item.id}`;
|
||||
break;
|
||||
|
||||
/* case 'QUESTION':
|
||||
content = item.title;
|
||||
link = `https://m.okjike.com/questions/${item.id}`;
|
||||
break;
|
||||
case 'OFFICIAL_MESSAGE':
|
||||
content = item.content;
|
||||
link = `https://web.okjike.com/message-detail/${item.id}/officialMessage`;
|
||||
break;*/
|
||||
default:
|
||||
content = '未知类型,请前往GitHub提交issue';
|
||||
link = 'https://github.com/DIYgod/RSSHub/issues';
|
||||
}
|
||||
|
||||
// rss内容
|
||||
let description = '';
|
||||
// 作者昵称
|
||||
let author = '';
|
||||
|
||||
// 添加内容作者信息
|
||||
if (item.user) {
|
||||
author = item.user.screenName;
|
||||
if (ctx.req.param('showUid')) {
|
||||
description += `<span>用户昵称:${author} <br> Username:${item.user.username}</span><br>`;
|
||||
}
|
||||
}
|
||||
|
||||
if (item.linkInfo) {
|
||||
const linkUrl = item.linkInfo.originalLinkUrl || item.linkInfo.linkUrl;
|
||||
|
||||
// 对于即刻抓取的微信公众号文章 特殊处理
|
||||
// 此时 Rss原文链接 变为 微信公众号链接
|
||||
if (new URL(linkUrl).host === 'mp.weixin.qq.com') {
|
||||
link = linkUrl;
|
||||
}
|
||||
|
||||
// 1. 音频
|
||||
const audioObject = item.linkInfo.audio || item.audio;
|
||||
if (audioObject) {
|
||||
const audioImage = audioObject.image.picUrl || audioObject.image.thumbnailUrl;
|
||||
const audioLink = linkUrl;
|
||||
const audioTitle = audioObject.title;
|
||||
const audioAuthor = audioObject.author;
|
||||
audioName = `${audioTitle} - ${audioAuthor}`;
|
||||
description += `
|
||||
<img src="${audioImage}">
|
||||
<a href="${audioLink}">${audioName}</a>
|
||||
`;
|
||||
}
|
||||
|
||||
// 2. 视频
|
||||
const videoObject = item.linkInfo.video || item.video;
|
||||
if (videoObject) {
|
||||
const videoImage = videoObject.image.picUrl || videoObject.image.thumbnailUrl;
|
||||
const videoLink = linkUrl;
|
||||
const videoDuration = Math.floor(videoObject.duration / 60000);
|
||||
videoName = item.linkInfo.title;
|
||||
description += `
|
||||
<img src="${videoImage}">
|
||||
<a href="${videoLink}">${videoName || '观看视频'} - 约${videoDuration}分钟</a>
|
||||
`;
|
||||
}
|
||||
|
||||
// 3. 链接
|
||||
if (!audioObject && !videoObject && linkUrl) {
|
||||
// 部分链接有标题
|
||||
linkName = item.linkInfo.title;
|
||||
const linkTitle = linkName || '访问原文';
|
||||
// 部分链接有缩略图
|
||||
const linkImage = item.linkInfo.pictureUrl;
|
||||
const imageTag = `<img src="${linkImage}">`;
|
||||
description += `
|
||||
${linkImage ? imageTag : ''}
|
||||
<a href="${linkUrl}">${linkTitle}</a>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 文字内容
|
||||
description += description ? `<br>${content}` : content;
|
||||
|
||||
// 5. 图片
|
||||
if (item.pictures) {
|
||||
for (const pic of item.pictures) {
|
||||
if (pic.format === 'gif') {
|
||||
description += `<img src="${pic.picUrl.split('?imageMogr2/')[0]}">`;
|
||||
} else {
|
||||
// jpeg, bmp, png, gif, webp
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types
|
||||
// let type = 'jpeg';
|
||||
// switch (pic.format) {
|
||||
// case 'bmp':
|
||||
// type = 'bmp';
|
||||
// break;
|
||||
// case 'png':
|
||||
// type = 'png';
|
||||
// break;
|
||||
// default:
|
||||
// break;
|
||||
// }
|
||||
const imgUrl = /\.[\da-z]+?\?imageMogr2/.test(pic.picUrl) ? pic.picUrl.split('?imageMogr2/')[0] : pic.picUrl.replace(/thumbnail\/.+/, '');
|
||||
description += `<br><img src="${imgUrl}">`;
|
||||
// description += `<br><picture><source srcset="${
|
||||
// pic.picUrl.split('/thumbnail/')[0]
|
||||
// }/strip/format/webp" type="image/webp"><source srcset="${imgUrl}" type="image/${type}"><img src="${imgUrl}"></picture>`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 6. 视频
|
||||
if (item.video) {
|
||||
description += `<br><video src="${item.video.url}" controls></video>`;
|
||||
}
|
||||
|
||||
// rss标题
|
||||
// 优先将音频和视频名作为标题
|
||||
// 其次将正文内容作为标题
|
||||
// 若都没有 则是推送型消息,将连接标题作为主题
|
||||
// “无题” fallback
|
||||
const title = audioName || videoName || content || linkName || '无题';
|
||||
|
||||
return {
|
||||
title,
|
||||
description: description.trim().replaceAll('\n', '<br>'),
|
||||
pubDate: parseDate.parse(item.createdAt),
|
||||
author,
|
||||
link,
|
||||
};
|
||||
}),
|
||||
constructTopicEntry: async (ctx, url) => {
|
||||
const data = await cache.tryGet(
|
||||
url,
|
||||
async () => {
|
||||
const resp = await got(url);
|
||||
const html = resp.data;
|
||||
const $ = cheerio.load(html);
|
||||
const raw = $('[type = "application/json"]').html();
|
||||
const data = JSON.parse(raw).props.pageProps;
|
||||
data.posts = await Promise.all(
|
||||
data.posts.map(async (item) => {
|
||||
if (!item.video) {
|
||||
return item;
|
||||
}
|
||||
|
||||
const videoUrl = `${videoAPI}&id=${item.id}`;
|
||||
const videoRes = await got(videoUrl);
|
||||
item.video = videoRes.data;
|
||||
|
||||
return item;
|
||||
})
|
||||
);
|
||||
|
||||
return data;
|
||||
},
|
||||
false,
|
||||
config.cache.routeExpire
|
||||
);
|
||||
|
||||
if (data.length === 0) {
|
||||
ctx.set('data', {
|
||||
title: '主题 ID 不存在,或该主题暂无内容',
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
const topic = data.topic;
|
||||
|
||||
ctx.set('data', {
|
||||
title: `${topic.content} - 即刻圈子`,
|
||||
link: url,
|
||||
description: topic.briefIntro,
|
||||
image: topic.squarePicture.picUrl || topic.squarePicture.middlePicUrl || topic.squarePicture.thumbnailUrl,
|
||||
// item: topicDataHanding(data),
|
||||
});
|
||||
|
||||
return data;
|
||||
},
|
||||
};
|
||||
@@ -1,6 +0,0 @@
|
||||
module.exports = {
|
||||
'/routes/:lang?': ['DIYgod'],
|
||||
'/transform/html/:url/:routeParams': ['ttttmr'],
|
||||
'/transform/json/:url/:routeParams': ['ttttmr'],
|
||||
'/transform/sitemap/:url/:routeParams?': ['flrngel'],
|
||||
};
|
||||
@@ -1,13 +0,0 @@
|
||||
module.exports = {
|
||||
'rsshub.app': {
|
||||
_name: 'RSSHub',
|
||||
docs: [
|
||||
{
|
||||
title: '有新路由啦',
|
||||
docs: 'https://docs.rsshub.app/routes/program-update#rsshub',
|
||||
source: ['/*'],
|
||||
target: '/rsshub/routes',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -1,6 +0,0 @@
|
||||
module.exports = (router) => {
|
||||
router.get('/routes/:lang?', require('./routes'));
|
||||
router.get('/transform/html/:url/:routeParams', require('./transform/html'));
|
||||
router.get('/transform/json/:url/:routeParams', require('./transform/json'));
|
||||
router.get('/transform/sitemap/:url/:routeParams?', require('./transform/sitemap'));
|
||||
};
|
||||
@@ -1,65 +0,0 @@
|
||||
import got from '@/utils/got';
|
||||
const cheerio = require('cheerio');
|
||||
|
||||
module.exports = async (ctx) => {
|
||||
const isEnglish = ctx.req.param('lang') !== 'zh';
|
||||
|
||||
const lang = isEnglish ? '' : 'zh/';
|
||||
const types = [
|
||||
'social-media',
|
||||
'new-media',
|
||||
'traditional-media',
|
||||
'bbs',
|
||||
'blog',
|
||||
'programming',
|
||||
'design',
|
||||
'live',
|
||||
'multimedia',
|
||||
'picture',
|
||||
'anime',
|
||||
'program-update',
|
||||
'university',
|
||||
'forecast',
|
||||
'travel',
|
||||
'shopping',
|
||||
'game',
|
||||
'reading',
|
||||
'government',
|
||||
'study',
|
||||
'journal',
|
||||
'finance',
|
||||
'other',
|
||||
];
|
||||
const all = await Promise.all(
|
||||
types.map(async (type) => {
|
||||
const response = await got(`https://docs.rsshub.app/${lang}routes/${type}`);
|
||||
|
||||
const data = response.data;
|
||||
|
||||
const $ = cheerio.load(data);
|
||||
const page = $('.page').toArray();
|
||||
const item = $('.routeBlock').toArray();
|
||||
return { page, item, type };
|
||||
})
|
||||
);
|
||||
const list = all.flatMap(({ page, item, type }) => item.map((item) => ({ page, item, type })));
|
||||
|
||||
ctx.set('data', {
|
||||
title: isEnglish ? 'RSSHub has new routes' : 'RSSHub 有新路由啦',
|
||||
link: 'https://docs.rsshub.app',
|
||||
description: isEnglish ? 'Everything is RSSible' : '万物皆可 RSS',
|
||||
language: isEnglish ? 'en-us' : 'zh-cn',
|
||||
item: list.map(({ page, item, type }) => {
|
||||
const $ = cheerio.load(page);
|
||||
item = $(item);
|
||||
const h2Title = item.prevAll('h2').eq(0);
|
||||
const h3Title = item.prevAll('h3').eq(0);
|
||||
return {
|
||||
title: `${h2Title.text().trim()} - ${h3Title.text().trim()}`,
|
||||
description: item.html(),
|
||||
link: `https://docs.rsshub.app/${lang}routes/${type}#${encodeURIComponent(h2Title.find('.hash-link').attr('href') && h3Title.find('.hash-link').attr('href')?.substring(1))}`,
|
||||
guid: item.attr('id'),
|
||||
};
|
||||
}),
|
||||
});
|
||||
};
|
||||
@@ -1,65 +0,0 @@
|
||||
import got from '@/utils/got';
|
||||
const cheerio = require('cheerio');
|
||||
import { config } from '@/config';
|
||||
|
||||
module.exports = async (ctx) => {
|
||||
if (!config.feature.allow_user_supply_unsafe_domain) {
|
||||
throw new Error(`This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`);
|
||||
}
|
||||
const url = ctx.req.param('url');
|
||||
const response = await got({
|
||||
method: 'get',
|
||||
url,
|
||||
});
|
||||
|
||||
const routeParams = new URLSearchParams(ctx.req.param('routeParams'));
|
||||
const $ = cheerio.load(response.data);
|
||||
const rssTitle = routeParams.get('title') || $('title').text();
|
||||
const item = routeParams.get('item') || 'html';
|
||||
const items = $(item)
|
||||
.toArray()
|
||||
.map((item) => {
|
||||
try {
|
||||
item = $(item);
|
||||
|
||||
const titleEle = routeParams.get('itemTitle') ? item.find(routeParams.get('itemTitle')) : item;
|
||||
const title = routeParams.get('itemTitleAttr') ? titleEle.attr(routeParams.get('itemTitleAttr')) : titleEle.text();
|
||||
|
||||
let link;
|
||||
const linkEle = routeParams.get('itemLink') ? item.find(routeParams.get('itemLink')) : item;
|
||||
if (routeParams.get('itemLinkAttr')) {
|
||||
link = linkEle.attr(routeParams.get('itemLinkAttr'));
|
||||
} else {
|
||||
link = linkEle.is('a') ? linkEle.attr('href') : linkEle.find('a').attr('href');
|
||||
}
|
||||
// 补全绝对链接
|
||||
link = link.trim();
|
||||
if (link && !link.startsWith('http')) {
|
||||
link = `${new URL(url).origin}${link}`;
|
||||
}
|
||||
|
||||
const descEle = routeParams.get('itemDesc') ? item.find(routeParams.get('itemDesc')) : item;
|
||||
const desc = routeParams.get('itemDescAttr') ? descEle.attr(routeParams.get('itemDescAttr')) : descEle.html();
|
||||
|
||||
const pubDateEle = routeParams.get('itemPubDate') ? item.find(routeParams.get('itemPubDate')) : item;
|
||||
const pubDate = routeParams.get('itemPubDateAttr') ? pubDateEle.attr(routeParams.get('itemPubDateAttr')) : pubDateEle.html();
|
||||
|
||||
return {
|
||||
title,
|
||||
link,
|
||||
description: desc,
|
||||
pubDate,
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
ctx.set('data', {
|
||||
title: rssTitle,
|
||||
link: url,
|
||||
description: `Proxy ${url}`,
|
||||
item: items,
|
||||
});
|
||||
};
|
||||
@@ -1,58 +0,0 @@
|
||||
import got from '@/utils/got';
|
||||
const cheerio = require('cheerio');
|
||||
import { config } from '@/config';
|
||||
|
||||
function jsonGet(obj, attr) {
|
||||
if (typeof attr !== 'string') {
|
||||
return obj;
|
||||
}
|
||||
// a.b.c
|
||||
// a.b[0].c => a.b.0.c
|
||||
for (const key of attr.split('.')) {
|
||||
obj = obj[key];
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
module.exports = async (ctx) => {
|
||||
if (!config.feature.allow_user_supply_unsafe_domain) {
|
||||
throw new Error(`This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`);
|
||||
}
|
||||
const url = ctx.req.param('url');
|
||||
const response = await got({
|
||||
method: 'get',
|
||||
url,
|
||||
});
|
||||
|
||||
const routeParams = new URLSearchParams(ctx.req.param('routeParams'));
|
||||
let rssTitle = routeParams.get('title');
|
||||
if (!rssTitle) {
|
||||
const resp = await got({
|
||||
method: 'get',
|
||||
url: new URL(url).origin,
|
||||
});
|
||||
const $ = cheerio.load(resp.data);
|
||||
rssTitle = $('title').text();
|
||||
}
|
||||
|
||||
const items = jsonGet(response.data, routeParams.get('item')).map((item) => {
|
||||
let link = jsonGet(item, routeParams.get('itemLink')).trim();
|
||||
// 补全绝对链接
|
||||
if (link && !link.startsWith('http')) {
|
||||
link = `${new URL(url).origin}${link}`;
|
||||
}
|
||||
return {
|
||||
title: jsonGet(item, routeParams.get('itemTitle')),
|
||||
link,
|
||||
description: routeParams.get('itemDesc') ? jsonGet(item, routeParams.get('itemDesc')) : '',
|
||||
pubDate: routeParams.get('itemPubDate') ? jsonGet(item, routeParams.get('itemPubDate')) : '',
|
||||
};
|
||||
});
|
||||
|
||||
ctx.set('data', {
|
||||
title: rssTitle,
|
||||
link: url,
|
||||
description: `Proxy ${url}`,
|
||||
item: items,
|
||||
});
|
||||
};
|
||||
@@ -1,50 +0,0 @@
|
||||
import got from '@/utils/got';
|
||||
const cheerio = require('cheerio');
|
||||
import { config } from '@/config';
|
||||
|
||||
module.exports = async (ctx) => {
|
||||
if (!config.feature.allow_user_supply_unsafe_domain) {
|
||||
throw new Error(`This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`);
|
||||
}
|
||||
const url = ctx.req.param('url');
|
||||
const response = await got({
|
||||
method: 'get',
|
||||
url,
|
||||
});
|
||||
|
||||
const routeParams = new URLSearchParams(ctx.req.param('routeParams'));
|
||||
const $ = cheerio.load(response.data, { xmlMode: true });
|
||||
|
||||
const rssTitle = routeParams.get('title') || ($('urlset url').length && $('urlset url').first().find('loc').text() ? $('urlset url').first().find('loc').text() : 'Sitemap');
|
||||
|
||||
const urls = $('urlset url').toArray();
|
||||
const items =
|
||||
urls && urls.length
|
||||
? urls
|
||||
.map((item) => {
|
||||
try {
|
||||
const title = $(item).find('loc').text() || '';
|
||||
const link = $(item).find('loc').text() || '';
|
||||
const description = $(item).find('loc').text() || '';
|
||||
const pubDate = $(item).find('lastmod').text() || undefined;
|
||||
|
||||
return {
|
||||
title,
|
||||
link,
|
||||
description,
|
||||
pubDate,
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(Boolean)
|
||||
: [];
|
||||
|
||||
ctx.set('data', {
|
||||
title: rssTitle,
|
||||
link: url,
|
||||
description: `Proxy ${url}`,
|
||||
item: items,
|
||||
});
|
||||
};
|
||||
@@ -1,3 +0,0 @@
|
||||
module.exports = function (router) {
|
||||
router.get('/:id', require('./index'));
|
||||
};
|
||||
Reference in New Issue
Block a user