Merge branch 'master' into feature/hono

This commit is contained in:
DIYgod
2024-01-29 22:09:28 +08:00
119 changed files with 2719 additions and 2113 deletions
+3 -3
View File
@@ -10,9 +10,9 @@ Procfile
app-minimal
assets
coverage
docs
node_modules
test
website
# files
.codecov.yml
@@ -35,13 +35,13 @@ process.json
package-lock.json
vercel.json
#git but keep the git commit hash
# git but keep the git commit hash
.git/logs
.git/objects
.git/index
.git/info
.git/hooks
#rsshub auxiliary files
# rsshub auxiliary files
lib/radar-rules.js
lib/v2/**/radar.js
+1 -1
View File
@@ -136,7 +136,7 @@ jobs:
- uses: actions/checkout@v4
- name: Docker Hub Description
uses: peter-evans/dockerhub-description@v3
uses: peter-evans/dockerhub-description@v4
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
+2
View File
@@ -598,6 +598,8 @@ const calculateValue = () => {
twitter: {
oauthTokens: envs.TWITTER_OAUTH_TOKEN?.split(','),
oauthTokenSecrets: envs.TWITTER_OAUTH_TOKEN_SECRET?.split(','),
username: envs.TWITTER_USERNAME,
password: envs.TWITTER_PASSWORD,
},
weibo: {
app_key: envs.WEIBO_APP_KEY,
+2 -1
View File
@@ -6,6 +6,7 @@ import { config } from '@/config';
import { RE2JS } from 're2js';
import markdownit from 'markdown-it'
import htmlToText from 'html-to-text'
import sanitizeHtml from 'sanitize-html';
import { MiddlewareHandler } from 'hono';
import cache from '@/utils/cache';
import Parser from '@postlight/parser';
@@ -363,7 +364,7 @@ const middleware: MiddlewareHandler = async (ctx, next) => {
for (const item of data.item) {
let text;
if (item.description) {
text = item.description.replaceAll(/<\/?[^>]+(>|$)/g, '');
text = sanitizeHtml(item.description, { allowedTags: [], allowedAttributes: {} });
item.description = text.length > brief ? `<p>${text.substring(0, brief)}…</p>` : `<p>${text}</p>`;
}
}
+3 -3
View File
@@ -1170,9 +1170,9 @@ router.get('/ruby-china/jobs', lazyloadRouteHandler('./routes/ruby-china/jobs'))
router.get('/adquan/:type?', lazyloadRouteHandler('./routes/adquan/index'));
// 金色财经
router.get('/jinse/lives', lazyloadRouteHandler('./routes/jinse/lives'));
router.get('/jinse/timeline', lazyloadRouteHandler('./routes/jinse/timeline'));
router.get('/jinse/catalogue/:caty', lazyloadRouteHandler('./routes/jinse/catalogue'));
// router.get('/jinse/lives', lazyloadRouteHandler('./routes/jinse/lives'));
// router.get('/jinse/timeline', lazyloadRouteHandler('./routes/jinse/timeline'));
// router.get('/jinse/catalogue/:caty', lazyloadRouteHandler('./routes/jinse/catalogue'));
// deeplearning.ai
// router.get('/deeplearningai/thebatch', lazyloadRouteHandler('./routes/deeplearningai/thebatch'));
-20
View File
@@ -1,20 +0,0 @@
const got = require('@/utils/got');
module.exports = async (ctx) => {
const liveUrl = `https://api.jinse.com/v6/www/information/list?catelogue_key=${ctx.params.caty}&limit=23&flag=down`;
const response = await got({
method: 'get',
url: liveUrl,
});
ctx.state.data = {
title: `金色财经 - ${ctx.params.caty}`,
link: liveUrl,
item: response.data.list.map((item) => ({
title: item.title,
link: item.extra.topic_url,
description: item.extra.summary,
pubDate: new Date(item.extra.published_at * 1000).toUTCString(),
})),
};
};
-20
View File
@@ -1,20 +0,0 @@
const got = require('@/utils/got');
module.exports = async (ctx) => {
const liveUrl = `https://api.jinse.com/v4/live/list?limit=20`;
const response = await got({
method: 'get',
url: liveUrl,
});
ctx.state.data = {
title: '金色财经 - 快讯',
link: liveUrl,
item: response.data.list[0].lives.map((item) => ({
title: item.content,
link: `https://www.jinse.com/lives/${item.id}`,
description: item.content + (item.link ? `<br><a href="${item.link}" target="_blank">${item.link_name}</a>` : ''),
pubDate: new Date(`${new Date(response.data.list[0].date).toLocaleString().slice(0, 10)} ${new Date(item.created_at).toLocaleString().slice(-8, 0)}`).toUTCString(),
})),
};
};
-21
View File
@@ -1,21 +0,0 @@
const got = require('@/utils/got');
module.exports = async (ctx) => {
const liveUrl = `https://api.jinse.com/noah/v1/www/timelines?&limit=23&flag=down`;
const response = await got({
method: 'get',
url: liveUrl,
});
ctx.state.data = {
title: '金色财经 - 头条',
link: liveUrl,
item: response.data.data.list.map((item) => ({
title: item.title,
link: item.extra.topic_url,
author: item.extra.author,
description: item.extra.summary,
pubDate: new Date(item.extra.published_at * 1000).toUTCString(),
})),
};
};
+9 -6
View File
@@ -1,5 +1,7 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const { parseDate } = require('@/utils/parse-date');
const timezone = require('@/utils/timezone');
module.exports = async (ctx) => {
const year = ctx.params.year || new Date().getFullYear();
@@ -14,15 +16,15 @@ module.exports = async (ctx) => {
const $ = cheerio.load(response.data);
const list = $('.panel-body table tbody tr td span a')
.map((_, item) => {
const list = $('.card-body table tbody tr td span a')
.toArray()
.map((item) => {
item = $(item);
return {
title: item.text(),
link: `${rootUrl}/oral_arguments${item.attr('href').replace('..', '')}`,
link: new URL(item.attr('href'), currentUrl).href,
};
})
.get();
});
const items = await Promise.all(
list.map((item) =>
@@ -37,7 +39,7 @@ module.exports = async (ctx) => {
item.description = content('#pagemaindiv').html();
item.title += ` : ${content('#ctl00_ctl00_MainEditable_mainContent_lblCaseName').text()}`;
item.pubDate = new Date(content('#ctl00_ctl00_MainEditable_mainContent_lblDate').text() + ' GMT-5').toUTCString();
item.pubDate = timezone(parseDate(content('#ctl00_ctl00_MainEditable_mainContent_lblDate').text()), -5);
item.itunes_item_image = imageUrl;
item.enclosure_url = content('audio source').attr('src');
@@ -54,5 +56,6 @@ module.exports = async (ctx) => {
item: items,
itunes_author: 'Supreme Court of the United States',
image: imageUrl,
allowEmpty: true,
};
};
+2 -2
View File
@@ -4,13 +4,13 @@ module.exports = {
'.': [
{
title: 'Articles',
docs: 'https://docs.rsshub.app/routes/en/programming#a-list-apart',
docs: 'https://docs.rsshub.app/routes/programming#a-list-apart',
source: ['/articles/'],
target: '/alistapart',
},
{
title: 'Topics',
docs: 'https://docs.rsshub.app/routes/en/programming#a-list-apart',
docs: 'https://docs.rsshub.app/routes/programming#a-list-apart',
source: ['/blog/topic/:topic'],
target: '/alistapart/:topic',
},
+1 -1
View File
@@ -4,7 +4,7 @@ module.exports = {
www: [
{
title: 'Exhibitions',
docs: 'https://docs.rsshub.app/routes/en/travel#brooklyn-museum',
docs: 'https://docs.rsshub.app/routes/travel#brooklyn-museum',
},
],
},
+1 -1
View File
@@ -4,7 +4,7 @@ module.exports = {
'.': [
{
title: 'BYTES - Your weekly dose of JS',
docs: 'https://docs.rsshub.app/routes/en/programming#ui-dev-bytes',
docs: 'https://docs.rsshub.app/routes/programming#ui-dev-bytes',
source: ['/archives', '/'],
target: '/bytes',
},
+3
View File
@@ -0,0 +1,3 @@
module.exports = {
'/nice': ['nczitzk'],
};
+76
View File
@@ -0,0 +1,76 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const { parseDate } = require('@/utils/parse-date');
const { art } = require('@/utils/render');
const path = require('path');
const md = require('markdown-it')({
html: true,
});
module.exports = async (ctx) => {
const limit = ctx.query.limit ? Number.parseInt(ctx.query.limit, 10) : 100;
const rootUrl = 'https://chuanliu.org';
const apiRootUrl = 'https://s.chuanliu.org';
const currentUrl = new URL('nice/', rootUrl).href;
const apiUrl = new URL('api/dis/api/v1/memo', apiRootUrl).href;
const { data: response } = await got(apiUrl, {
searchParams: {
creatorId: 1,
offset: 0,
limit,
},
});
const items = response.slice(0, limit).map((item) => {
const contents = item.content.split(/\n/);
const category = contents?.[0].replace(/^#/, '') ?? undefined;
const title = contents?.[1] ?? undefined;
const link = contents?.[2] ?? undefined;
const author = contents?.[3] ?? undefined;
const isStar = (contents?.[5] && contents[5] === 'star') ?? false;
if (isStar) {
contents.splice(5, 1);
}
return {
title: `${isStar ? '[STAR] ' : ''}${title}`,
link,
description: art(path.join(__dirname, 'templates/description.art'), {
description: md.render(contents?.join('\n\n') ?? ''),
images: item.resourceList.map((i) => ({
src: i.externalLink,
alt: i.filename,
type: i.type,
})),
}),
author,
category: [category, isStar ? 'STAR' : undefined].filter(Boolean),
guid: `chuanliu-nice#${item.id}`,
pubDate: parseDate(item.createdTs, 'X'),
updated: parseDate(item.updatedTs, 'X'),
};
});
const { data: currentResponse } = await got(currentUrl);
const $ = cheerio.load(currentResponse);
const icon = new URL($('link[rel="shortcut icon"]').prop('href'), currentUrl).href;
ctx.state.data = {
item: items,
title: $('title').text(),
link: currentUrl,
description: $('span.rainbow-text').first().text(),
language: $('html').prop('lang'),
icon,
logo: icon,
subtitle: $('title').text(),
author: $('meta[name="author"]').prop('content'),
allowEmpty: true,
};
};
+13
View File
@@ -0,0 +1,13 @@
module.exports = {
'chuanliu.org': {
_name: '川流',
'.': [
{
title: '严选',
docs: 'https://docs.rsshub.app/routes/blog#chuan-liu-yan-xuan',
source: ['/nice'],
target: '/chuanliu/nice',
},
],
},
};
+3
View File
@@ -0,0 +1,3 @@
module.exports = (router) => {
router.get('/nice', require('./nice'));
};
+23
View File
@@ -0,0 +1,23 @@
{{ if images }}
{{ each images image }}
{{ if image?.src }}
<figure>
<img
{{ if image.alt }}
alt="{{ image.alt }}"
{{ /if }}
{{ if image.width }}
alt="{{ image.width }}"
{{ /if }}
{{ if image.height }}
alt="{{ image.height }}"
{{ /if }}
src="{{ image.src }}">
</figure>
{{ /if }}
{{ /each }}
{{ /if }}
{{ if description }}
{{@ description }}
{{ /if }}
+3
View File
@@ -0,0 +1,3 @@
module.exports = {
'/paper/:id?': ['nczitzk'],
};
+87
View File
@@ -0,0 +1,87 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const { parseDate } = require('@/utils/parse-date');
const { art } = require('@/utils/render');
const path = require('path');
module.exports = async (ctx) => {
const id = ctx.params.id;
const rootUrl = 'https://paper.cntheory.com';
let response = await got({
method: 'get',
url: rootUrl,
});
response = await got({
method: 'get',
url: `${rootUrl}/${response.data.match(/URL=(.*)"/)[1]}`,
});
const $ = cheerio.load(response.data);
const matches = response.data.match(/images\/(\d{4}-\d{2}\/\d{2})\/\w+\/\w+_brief/);
const link = `${rootUrl}/html/${matches[1]}`;
let items = [];
await Promise.all(
$('#pageLink')
.toArray()
.filter((p) => (id ? $(p).text().split(':').pop() === id : true))
.map((p) => `${link}/${$(p).attr('href').replace(/\.\//, '')}`)
.map(async (p) => {
const pageResponse = await got({
method: 'get',
url: p,
});
const page = cheerio.load(pageResponse.data);
items.push(
...page('table')
.last()
.find('a')
.toArray()
.map((a) => `${link}/${$(a).attr('href')}`)
);
})
);
items = await Promise.all(
items.map((item) =>
ctx.cache.tryGet(item, async () => {
const detailResponse = await got({
method: 'get',
url: item,
});
const content = cheerio.load(detailResponse.data);
return {
link: item,
title: content('h1').text(),
pubDate: parseDate(matches[1], 'YYYY-MM/DD'),
enclosure_url: `${rootUrl}${
content('.ban_t a')
.first()
.attr('href')
.match(/(\/images.*)/)[1]
}`,
description: art(path.join(__dirname, 'templates/description.art'), {
resource: content('#reslist').html().replaceAll('display:none;', ''),
description: content('founder-content').html(),
}),
};
})
)
);
ctx.state.data = {
title: `学习时报${id ? ` - ${id}` : ''}`,
link: rootUrl,
item: items,
allowEmpty: true,
};
};
+13
View File
@@ -0,0 +1,13 @@
module.exports = {
'cntheory.com': {
_name: '理论网',
paper: [
{
title: '学习时报',
docs: 'https://docs.rsshub.app/routes/traditional-media#li-lun-wang-xue-xi-shi-bao',
source: ['/'],
target: (params, url) => `/cntheory/paper/${new URL(url).toString().match(/-(\w+)\.htm/)[1]}`,
},
],
},
};
+3
View File
@@ -0,0 +1,3 @@
module.exports = function (router) {
router.get('/paper/:id?', require('./paper'));
};
@@ -0,0 +1,2 @@
{{@ resource }}
{{@ description }}
+1 -1
View File
@@ -4,7 +4,7 @@ module.exports = {
'.': [
{
title: 'User',
docs: 'https://docs.rsshub.app/routes/en/social-media#curiouscat',
docs: 'https://docs.rsshub.app/routes/social-media#curiouscat',
source: ['/:id'],
target: '/curiouscat/user/:id',
},
+3 -3
View File
@@ -4,19 +4,19 @@ module.exports = {
'.': [
{
title: 'Most Discussed',
docs: 'https://docs.rsshub.app/routes/en/social-media#daily.dev',
docs: 'https://docs.rsshub.app/routes/social-media#daily.dev',
source: ['/discussed'],
target: '/daily/discussed',
},
{
title: 'Most Upvoted',
docs: 'https://docs.rsshub.app/routes/en/social-media#daily.dev',
docs: 'https://docs.rsshub.app/routes/social-media#daily.dev',
source: ['/upvoted'],
target: '/daily/upvoted',
},
{
title: 'Popular',
docs: 'https://docs.rsshub.app/routes/en/social-media#daily.dev',
docs: 'https://docs.rsshub.app/routes/social-media#daily.dev',
source: ['/popular'],
target: '/daily',
},
+1 -1
View File
@@ -4,7 +4,7 @@ module.exports = {
'.': [
{
title: 'Latest Articles',
docs: 'https://docs.rsshub.app/routes/en/new-media#deadline',
docs: 'https://docs.rsshub.app/routes/new-media#deadline',
source: ['/'],
target: '/deadline',
},
+1 -1
View File
@@ -4,7 +4,7 @@ module.exports = {
'.': [
{
title: 'Channel Messages',
docs: 'https://docs.rsshub.app/routes/en/social-media#discord',
docs: 'https://docs.rsshub.app/routes/social-media#discord',
source: ['/channels/:guildId/:channelId/:messageID', '/channels/:guildId/:channelId'],
target: '/discord/channel/:channelId',
},
+2 -2
View File
@@ -4,13 +4,13 @@ module.exports = {
'.': [
{
title: 'News',
docs: 'https://docs.rsshub.app/routes/en/traditional-media#dna-india',
docs: 'https://docs.rsshub.app/routes/traditional-media#dna-india',
source: ['/:category'],
target: '/dnaindia/:category',
},
{
title: 'Topic',
docs: 'https://docs.rsshub.app/routes/en/traditional-media#dna-india',
docs: 'https://docs.rsshub.app/routes/traditional-media#dna-india',
source: ['/topic/:topic'],
target: '/dnaindia/topic/:topic',
},
+13 -7
View File
@@ -1,18 +1,24 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const { parseDate } = require('@/utils/parse-date');
const platformSlugs = {
desktop: 'releasenotes',
beta: 'beta/notes',
nightly: 'nightly/notes',
android: 'android/releasenotes',
ios: 'ios/notes',
};
module.exports = async (ctx) => {
const { platform } = ctx.params;
let devicePlatform = platform.replace('-', '/');
if (devicePlatform === 'desktop') {
devicePlatform = '';
}
const { platform = 'desktop' } = ctx.params;
const devicePlatform = platform.replace('-', '/');
const link = ['https://www.mozilla.org/en-US/firefox', devicePlatform, 'releasenotes/'].filter(Boolean).join('/');
const link = ['https://www.mozilla.org/en-US/firefox', Object.hasOwn(platformSlugs, devicePlatform) ? platformSlugs[devicePlatform] : devicePlatform].filter(Boolean).join('/');
const response = await got.get(link);
const $ = cheerio.load(response.data);
const version = $('.c-release-version').text();
const pubDate = new Date($('.c-release-date').text()).toUTCString();
const pubDate = parseDate($('.c-release-date').text(), 'MMMM D, YYYY');
ctx.state.data = {
title: `Firefox ${platform} release notes`,
+1 -1
View File
@@ -1,5 +1,5 @@
module.exports = function (router) {
router.get('/addons/:id', require('./addons'));
router.get('/breaches', require('./breaches'));
router.get('/release/:platform', require('./release'));
router.get('/release/:platform?', require('./release'));
};
+1 -1
View File
@@ -4,7 +4,7 @@ module.exports = {
'.': [
{
title: 'news',
docs: 'https://docs.rsshub.app/routes/en/other#fisher-spb',
docs: 'https://docs.rsshub.app/routes/other#fisher-spb',
source: ['/news'],
target: '/fisher-spb/news',
},
+1 -1
View File
@@ -47,7 +47,7 @@ const processItems = async (apiUrl, limit, ...searchParams) => {
let items = JSON.parse(String(zlib.inflateSync(Buffer.from(response.data?.list ?? response.data, 'base64'))));
items = (items?.list ?? items).slice(0, limit).map((item) => {
const sourceType = item.source_type ?? (item.source_link ? 'news' : item.event_type ? 'event' : constants.defaultType);
const sourceType = item.source_type ?? (item.source_link ? (item.column?.title ? 'article' : 'news') : item.event_type ? 'event' : constants.defaultType);
item = item.source_type ? item[item.source_type] : item;
+2 -2
View File
@@ -4,13 +4,13 @@ module.exports = {
'.': [
{
title: 'Selected New Books',
docs: 'https://docs.rsshub.app/routes/en/reading#free-computer-books',
docs: 'https://docs.rsshub.app/routes/reading#free-computer-books',
source: ['/', '/index.html'],
target: '/freecomputerbooks',
},
{
title: 'Current Book List',
docs: 'https://docs.rsshub.app/routes/en/reading#free-computer-books',
docs: 'https://docs.rsshub.app/routes/reading#free-computer-books',
source: ['/:category'],
target: (params, _, document) => {
const categoryId = params.category.replace('.html', '');
+1 -1
View File
@@ -17,7 +17,7 @@ module.exports = {
'.': [
{
title: 'myFT personal RSS',
docs: 'https://docs.rsshub.app/routes/en/traditional-media#financial-times',
docs: 'https://docs.rsshub.app/routes/traditional-media#financial-times',
},
],
},
+1 -1
View File
@@ -872,7 +872,7 @@ module.exports = {
immi: [
{
title: 'Immigration and Citizenship',
docs: 'https://docs.rsshub.app/routes/en/government#Australia-Department-of-Home-Affairs',
docs: 'https://docs.rsshub.app/routes/government#Australia-Department-of-Home-Affairs',
source: '/news-media/archive',
target: () => '/gov/immiau/news',
},
+15 -10
View File
@@ -7,12 +7,16 @@ module.exports = async (ctx) => {
const { data: response } = await got('https://www.guanhai.com.cn');
const $ = cheerio.load(response);
const imgBox = $('.img-box');
const recommand = {
title: imgBox.find('a').first().attr('title'),
link: imgBox.find('a').first().attr('href'),
pubDate: timezone(parseDate(imgBox.find('time').text()), 8),
};
const recommand = $('.img-box ul > a')
.toArray()
.map((item) => {
item = $(item);
return {
title: item.attr('title'),
link: item.attr('href'),
};
});
const list = [
...$('.pic-summary .title')
.toArray()
@@ -21,11 +25,11 @@ module.exports = async (ctx) => {
return {
title: item.find('a').attr('title'),
link: item.find('a').attr('href'),
pubDate: timezone(parseDate(item.find('time').text()), 8),
pubDate: timezone(parseDate(item.find('time').text(), 'YYYY-MM-DD HH:mm'), 8),
};
}),
recommand,
];
...recommand,
].filter((item) => item.link.startsWith('http'));
const items = await Promise.all(
list.map((item) =>
@@ -33,7 +37,8 @@ module.exports = async (ctx) => {
const { data: response } = await got(item.link);
const $ = cheerio.load(response);
item.author = $('.source').text();
item.description = $('.article-content').html();
item.description = $('.article-content').html() ?? $('.video-content').html();
item.pubDate = item.pubDate ?? timezone(parseDate($('.date').text(), 'YYYY-MM-DD HH:mm'), 8);
return item;
})
)
+5 -5
View File
@@ -20,26 +20,26 @@ module.exports = async (ctx) => {
throw new Error('Invalid category');
}
const host = 'https://' + category + '.huanqiu.com';
const host = `https://${category}.huanqiu.com`;
const resp = await got({
method: 'get',
url: [...host, '/api/channel_pc'],
url: `${host}/api/channel_pc`,
}).json();
const name = getKeysRecursive(resp.children, 'children', 'domain_name', [])[0];
const nodes = getKeysRecursive(resp.children, 'children', 'node', [])
.map((x) => '"' + x + '"')
.map((x) => `"${x}"`)
.join(',');
const req = await got({
method: 'get',
url: [...host, '/api/list?node='] + nodes + `&offset=0&limit=${ctx.query.limit ?? 20}`,
url: `${host}/api/list?node=${nodes}&offset=0&limit=${ctx.query.limit ?? 20}`,
}).json();
let items = req.list
.filter((item) => item.aid)
.map((item) => ({
link: host + '/article/' + item.aid,
link: `${host}/article/${item.aid}`,
title: item.title,
}));
+1 -1
View File
@@ -4,7 +4,7 @@ module.exports = {
'.': [
{
title: 'News',
docs: 'https://docs.rsshub.app/routes/en/new-media#indians-in-kuwait',
docs: 'https://docs.rsshub.app/routes/new-media#indians-in-kuwait',
source: ['/latest-news', '/'],
target: '/indiansinkuwait/latest',
},
+1 -1
View File
@@ -55,7 +55,7 @@ module.exports = {
content('.icn_edit, .btn_videoplayer, a[rel="bookmark"]').remove();
content('span').each(function () {
if (content(this).attr('class').startsWith('icn_')) {
if (content(this).attr('class')?.startsWith('icn_')) {
content(this).remove();
}
});
+1 -1
View File
@@ -4,7 +4,7 @@ module.exports = {
'.': [
{
title: 'Exhibitions',
docs: 'https://docs.rsshub.app/routes/en/travel#the-jewish-museum',
docs: 'https://docs.rsshub.app/routes/travel#the-jewish-museum',
},
],
},
+101
View File
@@ -0,0 +1,101 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const { parseDate } = require('@/utils/parse-date');
const { art } = require('@/utils/render');
const path = require('path');
const categories = {
zhengce: '政策',
fenxishishuo: '行情',
defi: 'DeFi',
kuang: '矿业',
'以太坊2.0': '以太坊 2.0',
industry: '产业',
IPFS: 'IPFS',
tech: '技术',
baike: '百科',
capitalmarket: '研报',
};
module.exports = async (ctx) => {
const { category = 'zhengce' } = ctx.params;
const limit = ctx.query.limit ? Number.parseInt(ctx.query.limit, 10) : 50;
const rootUrl = 'https://www.jinse.cn';
const rootApiUrl = 'https://api.jinse.cn';
const apiUrl = new URL('v6/www/information/list', rootApiUrl).href;
const currentUrl = rootUrl;
const { data: response } = await got(apiUrl, {
searchParams: {
catelogue_key: category,
limit,
flag: 'up',
},
});
let items = response.list.slice(0, limit).map((item) => ({
title: item.title,
link: item.extra.topic_url,
description: art(path.join(__dirname, 'templates/description.art'), {
images:
item.extra.thumbnails_pics.length > 0
? item.extra.thumbnails_pics.map((p) => ({
src: p.replace(/_[^\W_]+(\.\w+)$/, '_true$1'),
}))
: undefined,
intro: item.extra.summary,
}),
author: item.extra.nickname,
guid: `jinse${/\/lives\//.test(item.extra.topic_url) ? '-lives' : ''}-${item.extra.topic_id}`,
pubDate: parseDate(item.extra.published_at, 'X'),
upvotes: item.extra.up_counts ?? 0,
downvotes: item.extra.down_counts ?? 0,
comments: item.extra.comment_count ?? 0,
}));
items = await Promise.all(
items.map((item) =>
ctx.cache.tryGet(item.link, async () => {
if (/\/lives\//.test(item.link)) {
return item;
}
const { data: detailResponse } = await got(item.link);
const content = cheerio.load(detailResponse);
item.description += art(path.join(__dirname, 'templates/description.art'), {
description: content('section.js-article-content').html() || content('div.js-article').html(),
});
item.category = content('section.js-article-tag_state_1 a span')
.toArray()
.map((c) => content(c).text());
return item;
})
)
);
const { data: currentResponse } = await got(currentUrl);
const $ = cheerio.load(currentResponse);
const author = $('meta[name="author"]').prop('content');
const image = $('a.js-logoBox img').prop('src');
const icon = new URL($('link[rel="favicon"]').prop('href'), rootUrl).href;
ctx.state.data = {
item: items,
title: `${author} - ${Object.hasOwn(categories, category) ? categories[category] : category}`,
link: currentUrl,
description: $('meta[name="description"]').prop('content'),
language: $('html').prop('lang'),
image,
icon,
logo: icon,
subtitle: $('meta[name="keywords"]').prop('content'),
author,
allowEmpty: true,
};
};
+87
View File
@@ -0,0 +1,87 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const { parseDate } = require('@/utils/parse-date');
const { art } = require('@/utils/render');
const path = require('path');
const categories = {
0: '全部',
1: '精选',
2: '政策',
3: '数据',
4: 'NFT',
5: '项目',
};
module.exports = async (ctx) => {
const { category = '0' } = ctx.params;
const limit = ctx.query.limit ? Number.parseInt(ctx.query.limit, 10) : 100;
const rootUrl = 'https://jinse.cn';
const rootApiUrl = 'https://api.jinse.cn';
const apiUrl = new URL('noah/v2/lives', rootApiUrl).href;
const currentUrl = new URL('lives', rootUrl).href;
const { data: response } = await got(apiUrl, {
searchParams: {
limit,
reading: false,
source: 'web',
flag: 'up',
id: 0,
category,
},
});
const items =
response.list
.flatMap((l) => l.lives)
.slice(0, limit)
.map((item) => ({
title: item.content_prefix,
link: new URL(`lives/${item.id}.html`, rootUrl).href,
description: art(path.join(__dirname, 'templates/description.art'), {
images:
item.images?.map((i) => ({
src: i.url.replace(/_[^\W_]+(\.\w+)$/, '_true$1'),
width: i.width,
height: i.height,
})) ?? [],
description: item.content,
original: item.link
? {
link: item.link,
name: item.link_name,
}
: undefined,
}),
author: item.show_source_name,
guid: `jinse-lives-${item.id}`,
pubDate: parseDate(item.created_at, 'X'),
upvotes: item.up_counts ?? 0,
downvotes: item.down_counts ?? 0,
comments: item.comment_count ?? 0,
})) ?? [];
const { data: currentResponse } = await got(currentUrl);
const $ = cheerio.load(currentResponse);
const author = $('meta[name="author"]').prop('content');
const image = $('a.js-logoBox img').prop('src');
const icon = new URL($('link[rel="favicon"]').prop('href'), rootUrl).href;
ctx.state.data = {
item: items,
title: `${author} - ${Object.hasOwn(categories, category) ? categories[category] : category}`,
link: currentUrl,
description: $('meta[name="description"]').prop('content'),
language: $('html').prop('lang'),
image,
icon,
logo: icon,
subtitle: $('meta[name="keywords"]').prop('content'),
author,
allowEmpty: true,
};
};
+5
View File
@@ -0,0 +1,5 @@
module.exports = {
'/lives/:category?': ['nczitzk'],
'/timeline/:category?': ['nczitzk'],
'/:category?': ['nczitzk'],
};
+187
View File
@@ -0,0 +1,187 @@
module.exports = {
'jinse.cn': {
_name: '金色财经',
'.': [
{
title: '首页 - 头条',
docs: 'https://docs.rsshub.app/routes/finance#jin-se-cai-jing-shou-ye',
source: ['/'],
target: '/jinse/timeline/头条',
},
{
title: '首页 - 独家',
docs: 'https://docs.rsshub.app/routes/finance#jin-se-cai-jing-shou-ye',
source: ['/'],
target: '/jinse/timeline/独家',
},
{
title: '首页 - 铭文',
docs: 'https://docs.rsshub.app/routes/finance#jin-se-cai-jing-shou-ye',
source: ['/'],
target: '/jinse/timeline/铭文',
},
{
title: '首页 - 产业',
docs: 'https://docs.rsshub.app/routes/finance#jin-se-cai-jing-shou-ye',
source: ['/'],
target: '/jinse/timeline/产业',
},
{
title: '首页 - 项目',
docs: 'https://docs.rsshub.app/routes/finance#jin-se-cai-jing-shou-ye',
source: ['/'],
target: '/jinse/timeline/项目',
},
{
title: '首页 - 政策',
docs: 'https://docs.rsshub.app/routes/finance#jin-se-cai-jing-shou-ye',
source: ['/'],
target: '/jinse/timeline/政策',
},
{
title: '首页 - AI',
docs: 'https://docs.rsshub.app/routes/finance#jin-se-cai-jing-shou-ye',
source: ['/'],
target: '/jinse/timeline/AI',
},
{
title: '首页 - Web 3.0',
docs: 'https://docs.rsshub.app/routes/finance#jin-se-cai-jing-shou-ye',
source: ['/'],
target: '/jinse/timeline/Web 3.0',
},
{
title: '首页 - 以太坊2.0',
docs: 'https://docs.rsshub.app/routes/finance#jin-se-cai-jing-shou-ye',
source: ['/'],
target: '/jinse/timeline/以太坊2.0',
},
{
title: '首页 - DeFi',
docs: 'https://docs.rsshub.app/routes/finance#jin-se-cai-jing-shou-ye',
source: ['/'],
target: '/jinse/timeline/DeFi',
},
{
title: '首页 - Layer2',
docs: 'https://docs.rsshub.app/routes/finance#jin-se-cai-jing-shou-ye',
source: ['/'],
target: '/jinse/timeline/Layer2',
},
{
title: '首页 - NFT',
docs: 'https://docs.rsshub.app/routes/finance#jin-se-cai-jing-shou-ye',
source: ['/'],
target: '/jinse/timeline/NFT',
},
{
title: '首页 - DAO',
docs: 'https://docs.rsshub.app/routes/finance#jin-se-cai-jing-shou-ye',
source: ['/'],
target: '/jinse/timeline/DAO',
},
{
title: '首页 - 百科',
docs: 'https://docs.rsshub.app/routes/finance#jin-se-cai-jing-shou-ye',
source: ['/'],
target: '/jinse/timeline/百科',
},
{
title: '快讯 - 全部',
docs: 'https://docs.rsshub.app/routes/finance#jin-se-cai-jing-kuai-xun',
source: ['/lives'],
target: '/jinse/lives/0',
},
{
title: '快讯 - 精选',
docs: 'https://docs.rsshub.app/routes/finance#jin-se-cai-jing-kuai-xun',
source: ['/lives'],
target: '/jinse/lives/1',
},
{
title: '快讯 - 政策',
docs: 'https://docs.rsshub.app/routes/finance#jin-se-cai-jing-kuai-xun',
source: ['/lives'],
target: '/jinse/lives/2',
},
{
title: '快讯 - 数据',
docs: 'https://docs.rsshub.app/routes/finance#jin-se-cai-jing-kuai-xun',
source: ['/lives'],
target: '/jinse/lives/3',
},
{
title: '快讯 - NFT',
docs: 'https://docs.rsshub.app/routes/finance#jin-se-cai-jing-kuai-xun',
source: ['/lives'],
target: '/jinse/lives/4',
},
{
title: '快讯 - 项目',
docs: 'https://docs.rsshub.app/routes/finance#jin-se-cai-jing-kuai-xun',
source: ['/lives'],
target: '/jinse/lives/5',
},
{
title: '分类 - 政策',
docs: 'https://docs.rsshub.app/routes/finance#jin-se-cai-jing-fen-lei',
source: ['/'],
target: '/jinse/zhengce',
},
{
title: '分类 - 行情',
docs: 'https://docs.rsshub.app/routes/finance#jin-se-cai-jing-fen-lei',
source: ['/'],
target: '/jinse/fenxishishuo',
},
{
title: '分类 - DeFi',
docs: 'https://docs.rsshub.app/routes/finance#jin-se-cai-jing-fen-lei',
source: ['/'],
target: '/jinse/defi',
},
{
title: '分类 - 矿业',
docs: 'https://docs.rsshub.app/routes/finance#jin-se-cai-jing-fen-lei',
source: ['/'],
target: '/jinse/kuang',
},
{
title: '分类 - 以太坊 2.0',
docs: 'https://docs.rsshub.app/routes/finance#jin-se-cai-jing-fen-lei',
source: ['/'],
target: '/jinse/以太坊2.0',
},
{
title: '分类 - 产业',
docs: 'https://docs.rsshub.app/routes/finance#jin-se-cai-jing-fen-lei',
source: ['/'],
target: '/jinse/industry',
},
{
title: '分类 - IPFS',
docs: 'https://docs.rsshub.app/routes/finance#jin-se-cai-jing-fen-lei',
source: ['/'],
target: '/jinse/IPFS',
},
{
title: '分类 - 技术',
docs: 'https://docs.rsshub.app/routes/finance#jin-se-cai-jing-fen-lei',
source: ['/'],
target: '/jinse/tech',
},
{
title: '分类 - 百科',
docs: 'https://docs.rsshub.app/routes/finance#jin-se-cai-jing-fen-lei',
source: ['/'],
target: '/jinse/baike',
},
{
title: '分类 - 研报',
docs: 'https://docs.rsshub.app/routes/finance#jin-se-cai-jing-fen-lei',
source: ['/'],
target: '/jinse/capitalmarket',
},
],
},
};
+5
View File
@@ -0,0 +1,5 @@
module.exports = (router) => {
router.get('/lives/:category?', require('./lives'));
router.get('/timeline/:category?', require('./timeline'));
router.get('/:category?', require('./catalogue'));
};
+34
View File
@@ -0,0 +1,34 @@
{{ if images }}
{{ each images image }}
{{ if image?.src }}
<figure>
<img
{{ if image.alt }}
alt="{{ image.alt }}"
{{ /if }}
{{ if image.width }}
alt="{{ image.width }}"
{{ /if }}
{{ if image.height }}
alt="{{ image.height }}"
{{ /if }}
src="{{ image.src }}">
</figure>
{{ /if }}
{{ /each }}
{{ /if }}
{{ if intro }}
<blockquote>{{ intro }}</blockquote>
{{ /if }}
{{ if description }}
{{@ description }}
{{ /if }}
{{ if original?.link }}
<p>
{{ original.name }}:
<a href="{{ original.link }}">{{ original.link }}</a>
</p>
{{ /if }}
+96
View File
@@ -0,0 +1,96 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const { parseDate } = require('@/utils/parse-date');
const { art } = require('@/utils/render');
const path = require('path');
module.exports = async (ctx) => {
const { category = '头条' } = ctx.params;
const limit = ctx.query.limit ? Number.parseInt(ctx.query.limit, 10) : 50;
const rootUrl = 'https://www.jinse.cn';
const rootApiUrl = 'https://api.jinse.cn';
const apiUrl = new URL('noah/v3/timelines', rootApiUrl).href;
const currentUrl = rootUrl;
const { data: response } = await got(apiUrl, {
searchParams: {
catelogue_key: category === '头条' ? 'www' : category,
limit,
information_id: 0,
flag: 'up',
},
});
let items = response.data.list.slice(0, limit).map((item) => {
item = item.object_1 ?? item.object_2;
return {
title: item.title,
link: item.jump_url,
description: art(path.join(__dirname, 'templates/description.art'), {
images: item.cover
? [
{
src: item.cover.replace(/_[^\W_]+(\.\w+)$/, '_true$1'),
alt: item.title,
},
]
: undefined,
intro: item.summary,
description: item.content,
}),
author: item.author.nickname,
guid: `jinse${/\/lives\//.test(item.jump_url) ? '-lives' : ''}-${item.id}`,
pubDate: parseDate(item.published_at, 'X'),
upvotes: item.up_counts ?? 0,
downvotes: item.down_counts ?? 0,
comments: item.comment_count ?? 0,
};
});
items = await Promise.all(
items.map((item) =>
ctx.cache.tryGet(item.link, async () => {
if (/\/lives\//.test(item.link)) {
return item;
}
const { data: detailResponse } = await got(item.link);
const content = cheerio.load(detailResponse);
item.description += art(path.join(__dirname, 'templates/description.art'), {
description: content('section.js-article-content').html() || content('div.js-article').html(),
});
item.category = content('section.js-article-tag_state_1 a span')
.toArray()
.map((c) => content(c).text());
return item;
})
)
);
const { data: currentResponse } = await got(currentUrl);
const $ = cheerio.load(currentResponse);
const author = $('meta[name="author"]').prop('content');
const image = $('a.js-logoBox img').prop('src');
const icon = new URL($('link[rel="favicon"]').prop('href'), rootUrl).href;
ctx.state.data = {
item: items,
title: `${author} - ${category}`,
link: currentUrl,
description: $('meta[name="description"]').prop('content'),
language: $('html').prop('lang'),
image,
icon,
logo: icon,
subtitle: $('meta[name="keywords"]').prop('content'),
author,
allowEmpty: true,
};
};
+2 -3
View File
@@ -4,6 +4,7 @@ const asyncPool = require('tiny-async-pool');
const { art } = require('@/utils/render');
const { parseJucheDate, fixDesc, fetchPhoto, fetchVideo } = require('./utils');
const path = require('path');
const sanitizeHtml = require('sanitize-html');
module.exports = async (ctx) => {
const { lang, category = '1ee9bdb7186944f765208f34ecfb5407' } = ctx.params;
@@ -15,9 +16,7 @@ module.exports = async (ctx) => {
const $ = cheerio.load(response.data);
// fix <nobr><span class="fSpecCs">???</span></nobr>
const title = $('head > title')
.text()
.replaceAll(/<[^>]*>/g, '');
const title = sanitizeHtml($('head > title').text(), { allowedTags: [], allowedAttributes: {} });
const list = $('.article-link li a')
.map((_, item) => {
+3
View File
@@ -0,0 +1,3 @@
module.exports = {
'/': ['TonyRL'],
};
+46
View File
@@ -0,0 +1,46 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const { parseDate } = require('@/utils/parse-date');
module.exports = async (ctx) => {
const baseUrl = 'https://keepass.info/news/news_all.html';
const { data: response } = await got(baseUrl);
const $ = cheerio.load(response);
const list = $('p > a')
.toArray()
.map((elem) => {
elem = $(elem);
return {
title: elem.find('b').text(),
link: new URL(elem.attr('href'), baseUrl).href,
pubDate: parseDate(elem.next().next('small').text().split('.')[0]),
};
})
.slice(0, ctx.query.limit ? Number.parseInt(ctx.query.limit, 10) : 10);
const items = await Promise.all(
list.map((item) =>
ctx.cache.tryGet(item.link, async () => {
if (!item.link.startsWith('https://keepass.info/')) {
return item;
}
const { data } = await got(item.link);
const $ = cheerio.load(data);
$('.sectionheader').remove();
$('.laytablews > tbody> tr:nth-child(1) > td:nth-child(2) > p').first().remove();
item.description = $('.laytablews > tbody> tr:nth-child(1) > td:nth-child(2)').html();
return item;
})
)
);
ctx.state.data = {
title: $('head title').attr('content'),
link: baseUrl,
item: items,
};
};
+13
View File
@@ -0,0 +1,13 @@
module.exports = {
'keepass.info': {
_name: 'KeePass',
'.': [
{
title: 'News',
docs: 'https://docs.rsshub.app/routes/program-update.html#keepass',
source: ['/news/news_all.html', '/'],
target: '/keepass/news',
},
],
},
};
+3
View File
@@ -0,0 +1,3 @@
module.exports = (router) => {
router.get('/', require('./news'));
};
+2 -2
View File
@@ -4,13 +4,13 @@ module.exports = {
'.': [
{
title: 'Latest News',
docs: 'https://docs.rsshub.app/routes/en/new-media#kuwait-local',
docs: 'https://docs.rsshub.app/routes/new-media#kuwait-local',
source: ['/news/latest', '/news', '/'],
target: '/kuwaitlocal',
},
{
title: 'Categorised News',
docs: 'https://docs.rsshub.app/routes/en/new-media#kuwait-local',
docs: 'https://docs.rsshub.app/routes/new-media#kuwait-local',
source: ['/news/categories/:category'],
target: '/kuwaitlocal/:category',
},
+41
View File
@@ -0,0 +1,41 @@
const got = require('@/utils/got');
const iconv = require('iconv-lite');
const cheerio = require('cheerio');
const { parseDate } = require('@/utils/parse-date');
module.exports = async (ctx) => {
const { id } = ctx.params;
const baseUrl = `https://www.laimanhua8.com`;
const link = `${baseUrl}/kanmanhua/${id}/`;
const { data: response } = await got(link, {
responseType: 'buffer',
});
let $ = cheerio.load(iconv.decode(response, 'utf-8'));
const charset = $('meta[http-equiv="Content-Type"]')
.attr('content')
?.match(/charset=(.*)/)?.[1];
if (charset?.toLowerCase() !== 'utf-8') {
$ = cheerio.load(iconv.decode(response, charset ?? 'utf-8'));
}
const items = $('.plist a')
.toArray()
.map((item, index) => {
item = $(item);
return {
title: item.attr('title'),
link: `${baseUrl}${item.attr('href')}`,
pubDate: index === 0 ? parseDate($('head meta[property="og:novel:update_time"]').attr('content')) : null,
author: $('head meta[property="og:novel:author"]').attr('content'),
};
});
ctx.state.data = {
title: `${$('head meta[property="og:novel:book_name"]').attr('content')} - 来漫画`,
description: $('.introduction').text(),
image: $('head meta[property="og:image"]').attr('content'),
link,
item: items,
};
};
+3
View File
@@ -0,0 +1,3 @@
module.exports = {
'/:id': ['TonyRL'],
};
+13
View File
@@ -0,0 +1,13 @@
module.exports = {
'laimanhua8.com': {
_name: '来漫画',
www: [
{
title: '漫画列表',
docs: 'https://docs.rsshub.app/routes/anime#lai-man-hua',
source: '/kanmanhua/:id',
target: '/laimanhua/:id',
},
],
},
};
+3
View File
@@ -0,0 +1,3 @@
module.exports = (router) => {
router.get('/:id', require('./index'));
};
+1 -1
View File
@@ -4,7 +4,7 @@ module.exports = {
'.': [
{
title: 'Data Tracker Feed',
docs: 'https://docs.rsshub.app/routes/en/other#layoffs-fyi',
docs: 'https://docs.rsshub.app/routes/other#layoffs-fyi',
source: '/',
target: '/layoffs',
},
+1 -1
View File
@@ -4,7 +4,7 @@ module.exports = {
'.': [
{
title: 'Job Listing',
docs: 'https://docs.rsshub.app/routes/en/other#linkedin-jobs',
docs: 'https://docs.rsshub.app/routes/other#linkedin-jobs',
source: '/jobs/search/',
target: (params, url) => {
const searchParams = new URL(url).searchParams;
+4 -4
View File
@@ -4,13 +4,13 @@ module.exports = {
www: [
{
title: 'LiSA News',
docs: 'https://docs.rsshub.app/routes/en/live#lisa',
docs: 'https://docs.rsshub.app/routes/live#lisa',
source: ['/artist/lisa/', '/artist/lisa/info/'],
target: '/lxixsxa/info',
},
{
title: 'LiSA Albums',
docs: 'https://docs.rsshub.app/routes/en/live#lisa',
docs: 'https://docs.rsshub.app/routes/live#lisa',
source: ['/artist/lisa/', '/artist/lisa/discography/'],
target: '/lxixsxa/disco',
},
@@ -21,13 +21,13 @@ module.exports = {
www: [
{
title: 'News',
docs: 'https://docs.rsshub.app/routes/en/live#lisa',
docs: 'https://docs.rsshub.app/routes/live#lisa',
source: ['/', '/info'],
target: '/lxixsxa/info',
},
{
title: 'Albums',
docs: 'https://docs.rsshub.app/routes/en/live#lisa',
docs: 'https://docs.rsshub.app/routes/live#lisa',
source: ['/', '/discography'],
target: '/lxixsxa/disco',
},
+1 -1
View File
@@ -4,7 +4,7 @@ module.exports = {
www: [
{
title: 'Exhibitions',
docs: 'https://docs.rsshub.app/routes/en/travel#the-metropolitan-museum-of-art',
docs: 'https://docs.rsshub.app/routes/travel#the-metropolitan-museum-of-art',
},
],
},
+42
View File
@@ -0,0 +1,42 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const { parseDate } = require('@/utils/parse-date');
module.exports = async (ctx) => {
const { category = 'zonghezixun3' } = ctx.params;
const baseUrl = 'http://www.mrm.com.cn';
const link = `${baseUrl}/${category}.html`;
const { data: response } = await got(link);
const $ = cheerio.load(response);
const list = $('#datalist_wap .li')
.toArray()
.map((item) => {
item = $(item);
const a = item.find('a');
return {
title: a.text().trim(),
link: `${baseUrl}${a.attr('href')}`,
pubDate: parseDate(item.find('.d').text(), 'YYYY.MM.DD'),
};
});
const items = await Promise.all(
list.map((item) =>
ctx.cache.tryGet(item.link, async () => {
const { data } = await got(item.link);
const $ = cheerio.load(data);
item.description = $('.article-cont').html();
return item;
})
)
);
ctx.state.data = {
title: $('head title').text(),
link,
item: items,
};
};
+3
View File
@@ -0,0 +1,3 @@
module.exports = {
'/:category?': ['TonyRL'],
};
+13
View File
@@ -0,0 +1,13 @@
module.exports = {
'mrm.com.cn': {
_name: '华储网',
'.': [
{
title: '通知',
docs: 'https://docs.rsshub.app/finance#hua-chu-wang',
source: ['/:category'],
target: (params) => `/mrm/${params.category.replace('.html', '')}`,
},
],
},
};
+3
View File
@@ -0,0 +1,3 @@
module.exports = (router) => {
router.get('/:category?', require('./index'));
};
+1 -1
View File
@@ -4,7 +4,7 @@ module.exports = {
'.': [
{
title: 'Topics',
docs: 'https://docs.rsshub.app/routes/en/new-media#nautilus',
docs: 'https://docs.rsshub.app/routes/new-media#nautilus',
source: ['/topics/:tid'],
target: '/nautil/topic/:tid',
},
+2 -2
View File
@@ -4,13 +4,13 @@ module.exports = {
'.': [
{
title: 'New working paper',
docs: 'https://docs.rsshub.app/routes/en/journal#national-bureau-of-economic-research',
docs: 'https://docs.rsshub.app/routes/journal#national-bureau-of-economic-research',
source: ['/papers'],
target: '/nber/news',
},
{
title: 'All working paper',
docs: 'https://docs.rsshub.app/routes/en/journal#national-bureau-of-economic-research',
docs: 'https://docs.rsshub.app/routes/journal#national-bureau-of-economic-research',
source: ['/papers'],
target: '/nber/papers',
},
+1 -1
View File
@@ -4,7 +4,7 @@ module.exports = {
www: [
{
title: 'Exhibitions',
docs: 'https://docs.rsshub.app/routes/en/travel#new-museum',
docs: 'https://docs.rsshub.app/routes/travel#new-museum',
},
],
},
+3 -3
View File
@@ -4,7 +4,7 @@ module.exports = {
'.': [
{
title: 'Blog',
docs: 'https://docs.rsshub.app/routes/en/new-media#openai',
docs: 'https://docs.rsshub.app/routes/new-media#openai',
source: '/blog',
target: (_, url) => {
const topics = new URL(url).searchParams.get('topics');
@@ -18,7 +18,7 @@ module.exports = {
chat: [
{
title: 'ChatGPT - Release Notes',
docs: 'https://docs.rsshub.app/routes/en/new-media#openai',
docs: 'https://docs.rsshub.app/routes/new-media#openai',
source: '/chat',
target: () => '/openai/chatgpt/release-notes',
},
@@ -26,7 +26,7 @@ module.exports = {
research: [
{
title: 'Research',
docs: 'https://docs.rsshub.app/routes/en/new-media#openai',
docs: 'https://docs.rsshub.app/routes/new-media#openai',
source: '/research',
target: () => '/openai/research',
},
+1 -1
View File
@@ -4,7 +4,7 @@ module.exports = {
'.': [
{
title: 'User profile',
docs: 'https://docs.rsshub.app/routes/en/social-media#picnob',
docs: 'https://docs.rsshub.app/routes/social-media#picnob',
source: ['/profile/:id/*'],
target: '/picnob/user/:id',
},
+3 -3
View File
@@ -4,19 +4,19 @@ module.exports = {
'.': [
{
title: 'Community',
docs: 'https://docs.rsshub.app/routes/en/bbs#pikabu',
docs: 'https://docs.rsshub.app/routes/bbs#pikabu',
source: ['/community/:name'],
target: '/pikabu/community/:name',
},
{
title: 'Tag',
docs: 'https://docs.rsshub.app/routes/en/bbs#pikabu',
docs: 'https://docs.rsshub.app/routes/bbs#pikabu',
source: ['/tag/:name'],
target: '/pikabu/tag/:name',
},
{
title: 'User',
docs: 'https://docs.rsshub.app/routes/en/bbs#pikabu',
docs: 'https://docs.rsshub.app/routes/bbs#pikabu',
source: ['/:name'],
target: '/pikabu/user/:name',
},
+1 -1
View File
@@ -4,7 +4,7 @@ module.exports = {
'.': [
{
title: 'Search',
docs: 'https://docs.rsshub.app/routes/en/picture#pixabay',
docs: 'https://docs.rsshub.app/routes/picture#pixabay',
source: ['/:searchType/search/:q'],
target: '/pixabay/search/:q',
},
+1 -1
View File
@@ -4,7 +4,7 @@ module.exports = {
'.': [
{
title: 'Home',
docs: 'https://docs.rsshub.app/routes/en/picture#rare-historical-photos',
docs: 'https://docs.rsshub.app/routes/picture#rare-historical-photos',
source: ['/'],
target: '/rarehistoricalphotos',
},
+1 -1
View File
@@ -4,7 +4,7 @@ module.exports = {
'.': [
{
title: 'User Threads',
docs: 'https://docs.rsshub.app/routes/en/social-media#rattibha',
docs: 'https://docs.rsshub.app/routes/social-media#rattibha',
source: ['/:user'],
target: '/rattibha/user/:user',
},
+1 -1
View File
@@ -4,7 +4,7 @@ module.exports = {
'.': [
{
title: 'React Newsletter',
docs: 'https://docs.rsshub.app/routes/en/programming#ui-dev-bytes',
docs: 'https://docs.rsshub.app/routes/programming#ui-dev-bytes',
source: ['/issues', '/'],
target: '/reactnewsletter',
},
+2 -31
View File
@@ -1,7 +1,7 @@
const cheerio = require('cheerio');
const got = require('@/utils/got');
const { parseDate } = require('@/utils/parse-date');
const { renderHTML } = require('./utils');
const { parseItem } = require('./utils');
module.exports = async (ctx) => {
const categoryId = ctx.params.category_id;
@@ -42,36 +42,7 @@ module.exports = async (ctx) => {
};
});
const items = await Promise.all(
list.map((item) =>
ctx.cache.tryGet(item.link, async () => {
const { data: response, url } = await got(item.link);
if (new URL(url).hostname !== 'www.scmp.com') {
// e.g., https://multimedia.scmp.com/
return item;
}
const $ = cheerio.load(response);
const nextData = JSON.parse($('script#__NEXT_DATA__').text());
const { article } = nextData.props.pageProps.payload.data;
// item.nextData = article;
item.summary = renderHTML(article.summary.json);
item.description = renderHTML(article.subHeadline.json) + renderHTML(article.images.find((i) => i.type === 'leading')) + renderHTML(article.body.json);
item.updated = parseDate(article.updatedDate, 'x');
item.category = [...new Set([...article.topics.map((t) => t.name), ...article.sections.flatMap((t) => t.value.map((v) => v.name)), ...article.keywords.map((k) => k?.split(', '))])];
// N.B. gallery in article is not rendered
// e.g., { type: 'div', attribs: { class: 'scmp-photo-gallery', 'data-gallery-nid': '3239409' }}
// from https://www.scmp.com/news/china/politics/article/3239355/li-keqiang-former-premier-china-dead
return item;
})
)
);
const items = await Promise.all(list.map((item) => ctx.cache.tryGet(item.link, () => parseItem)));
ctx.state.json = {
items,
+2
View File
@@ -1,3 +1,5 @@
module.exports = {
'/:category_id': ['proletarius101'],
'/topics/:topic': ['TonyRL'],
'/coronavirus': ['DIYgod'],
};
+7 -1
View File
@@ -4,10 +4,16 @@ module.exports = {
'.': [
{
title: 'News',
docs: 'https://docs.rsshub.app/routes/en/traditional-media#south-china-morning-post',
docs: 'https://docs.rsshub.app/routes/traditional-media#south-china-morning-post',
source: ['/rss/:category_id/feed'],
target: '/scmp/:category_id',
},
{
title: 'Topics',
docs: 'https://docs.rsshub.app/routes/traditional-media#south-china-morning-post',
source: ['/topics/:topic'],
target: '/scmp/topics/:topic',
},
],
},
};
+2
View File
@@ -1,3 +1,5 @@
module.exports = (router) => {
router.redirect('/coronavirus', '/scmp/topics/coronavirus-pandemic-all-stories');
router.get('/:category_id', require('./index'));
router.get('/topics/:topic', require('./topic'));
};
+66
View File
@@ -0,0 +1,66 @@
const cheerio = require('cheerio');
const got = require('@/utils/got');
const { parseDate } = require('@/utils/parse-date');
const { parseItem } = require('./utils');
module.exports = async (ctx) => {
const { topic } = ctx.params;
const limit = Number.parseInt(ctx.query.limit, 10) || 30;
const pageUrl = `https://www.scmp.com/topics/${topic}`;
const { data: pageResponse } = await got(pageUrl);
const $ = cheerio.load(pageResponse);
const nextData = JSON.parse($('script#__NEXT_DATA__').text());
const topicData = nextData.props.pageProps.payload.data.topic;
const variables = nextData.props.pageProps.operationDescriptor.root.variables;
const { data: apiResponse } = await got('https://apigw.scmp.com/content-delivery/v2', {
headers: {
apikey: 'MyYvyg8M9RTaevVlcIRhN5yRIqqVssNY',
'content-type': 'application/json',
},
searchParams: {
// got v11 does not support nested object in searchParams
extensions: JSON.stringify({
persistedQuery: {
sha256Hash: '8c951c1c2d4e94bc37d06dd94571552da4c0440c744acd00f2af84d3d8b6e2cf',
version: 1,
},
}),
operationName: 'topicContentListPaginationQuery',
variables: JSON.stringify({
applicationIds: variables.applicationIds,
count: limit,
scmpPlusPaywallTypeIds: variables.scmpPlusPaywallTypeIds,
id: topicData.id,
}),
},
});
const list = apiResponse.data.node.contents.edges.map(({ node }) => ({
title: node.headline,
summary: node.summary.text,
link: `https://www.scmp.com${node.urlAlias}`,
author: node.authors.map((a) => a.name).join(', '),
pubDate: parseDate(node.publishedDate, 'x'),
updated: parseDate(node.updatedDate, 'x'),
}));
const items = await Promise.all(list.map((item) => ctx.cache.tryGet(item.link, () => parseItem(item))));
ctx.state.json = {
nextData,
apiResponse,
};
ctx.state.data = {
title: topicData.name,
link: pageUrl,
description: topicData.description.text,
item: items,
language: 'en-hk',
icon: 'https://assets.i-scmp.com/static/img/icons/scmp-icon-256x256.png',
logo: 'https://customerservice.scmp.com/img/logo_scmp@2x.png',
image: 'https://assets-v2.i-scmp.com/production/_next/static/media/default-image.d1be8967.png',
};
};
+32
View File
@@ -1,3 +1,7 @@
const cheerio = require('cheerio');
const got = require('@/utils/got');
const { parseDate } = require('@/utils/parse-date');
const renderHTML = (node) => {
if (!node) {
return '';
@@ -55,6 +59,34 @@ const renderHTML = (node) => {
}
};
const parseItem = async (item) => {
const { data: response, url } = await got(item.link);
if (new URL(url).hostname !== 'www.scmp.com') {
// e.g., https://multimedia.scmp.com/
return item;
}
const $ = cheerio.load(response);
const nextData = JSON.parse($('script#__NEXT_DATA__').text());
const { article } = nextData.props.pageProps.payload.data;
// item.nextData = article;
item.summary = renderHTML(article.summary.json);
item.description = renderHTML(article.subHeadline.json) + renderHTML(article.images.find((i) => i.type === 'leading')) + renderHTML(article.body.json);
item.updated = parseDate(article.updatedDate, 'x');
item.category = [...new Set([...article.topics.map((t) => t.name), ...article.sections.flatMap((t) => t.value.map((v) => v.name)), ...article.keywords.map((k) => k?.split(', '))])];
// N.B. gallery in article is not rendered
// e.g., { type: 'div', attribs: { class: 'scmp-photo-gallery', 'data-gallery-nid': '3239409' }}
// from https://www.scmp.com/news/china/politics/article/3239355/li-keqiang-former-premier-china-dead
return item;
};
module.exports = {
renderHTML,
parseItem,
};
+1 -1
View File
@@ -4,7 +4,7 @@ module.exports = {
'.': [
{
title: 'Summary',
docs: 'https://docs.rsshub.app/routes/en/finance#seeking-alpha',
docs: 'https://docs.rsshub.app/routes/finance#seeking-alpha',
source: ['/symbol/:symbol/:category', '/symbol/:symbol/earnings/:category'],
target: '/seekingalpha/:symbol/:category',
},
+2 -2
View File
@@ -4,13 +4,13 @@ module.exports = {
'.': [
{
title: 'Articles',
docs: 'https://docs.rsshub.app/routes/en/programming#a-list-apart',
docs: 'https://docs.rsshub.app/routes/programming#a-list-apart',
source: ['/articles/'],
target: '/smashingmagazine',
},
{
title: 'Category',
docs: 'https://docs.rsshub.app/routes/en/programming#a-list-apart',
docs: 'https://docs.rsshub.app/routes/programming#a-list-apart',
source: ['/category/:category'],
target: '/smashingmagazine/:category',
},
+1 -1
View File
@@ -4,7 +4,7 @@ module.exports = {
web: [
{
title: 'Daily Updates News',
docs: 'https://docs.rsshub.app/routes/en/finance#stock-edge',
docs: 'https://docs.rsshub.app/routes/finance#stock-edge',
source: ['/daily-updates/news'],
target: '/stockedge/daily-updates/news',
},
+1 -1
View File
@@ -4,7 +4,7 @@ module.exports = {
blog: [
{
title: 'Articles',
docs: 'https://docs.rsshub.app/routes/en/blog#stratechery-by-ben-thompson',
docs: 'https://docs.rsshub.app/routes/blog#stratechery-by-ben-thompson',
},
],
},
+2 -2
View File
@@ -4,13 +4,13 @@ module.exports = {
'.': [
{
title: 'Latest Content',
docs: 'https://docs.rsshub.app/routes/en/new-media#techpowerup',
docs: 'https://docs.rsshub.app/routes/new-media#techpowerup',
source: ['/'],
target: '/techpowerup',
},
{
title: 'Reviews',
docs: 'https://docs.rsshub.app/routes/en/new-media#techpowerup',
docs: 'https://docs.rsshub.app/routes/new-media#techpowerup',
source: ['/review/search', '/review'],
target: (_, url) => `/techpowerup/review${new URL(url).searchParams.has('q') ? `/${new URL(url).searchParams.get('q')}` : ''}`,
},
+34 -26
View File
@@ -24,34 +24,42 @@ module.exports = async (ctx) => {
const author = item.source;
const abstract = item.abstract;
return ctx.cache.tryGet(itemUrl, async () => {
const response = await got(itemUrl);
const $ = cheerio.load(response.data);
const data = JSON.parse(
$('script:contains("window.DATA")')
.text()
.match(/window\.DATA = ({.+});/)[1]
);
const $data = cheerio.load(data.originContent.text, null, false);
return item.articletype === '4'
? {
title,
description: abstract,
link: itemUrl,
author,
pubDate,
}
: ctx.cache.tryGet(itemUrl, async () => {
const response = await got(itemUrl);
const $ = cheerio.load(response.data);
const data = JSON.parse(
$('script:contains("window.DATA")')
.text()
.match(/window\.DATA = ({.+});/)[1]
);
const $data = cheerio.load(data.originContent.text, null, false);
$data('*')
.contents()
.filter((_, elem) => elem.type === 'comment')
.replaceWith((_, elem) =>
art(path.join(__dirname, '../templates/news/image.art'), {
attribute: elem.data.trim(),
originAttribute: data.originAttribute,
})
);
$data('*')
.contents()
.filter((_, elem) => elem.type === 'comment')
.replaceWith((_, elem) =>
art(path.join(__dirname, '../templates/news/image.art'), {
attribute: elem.data.trim(),
originAttribute: data.originAttribute,
})
);
return {
title,
description: $data.html() || abstract,
link: itemUrl,
author,
pubDate,
};
});
return {
title,
description: $data.html() || abstract,
link: itemUrl,
author,
pubDate,
};
});
})
);
+11
View File
@@ -223,6 +223,17 @@ module.exports = async (ctx) => {
break;
case 'brief':
item.push({
title: '小可愛',
description: '<p>宇宙無敵</p><br>'.repeat(1000),
link: `/DIYgod/RSSHub/issues/0`,
pubDate: new Date(1_546_272_000_000).toUTCString(),
author: `DIYgod0`,
});
break;
case 'json':
item.push(
{
+1 -1
View File
@@ -4,7 +4,7 @@ module.exports = {
'.': [
{
title: 'Topic',
docs: 'https://docs.rsshub.app/routes/en/traditional-media#the-hindu',
docs: 'https://docs.rsshub.app/routes/traditional-media#the-hindu',
source: ['/topic/:topic'],
target: '/thehindu/topic/:topic',
},
+1 -1
View File
@@ -4,7 +4,7 @@ module.exports = {
'.': [
{
title: 'User timeline',
docs: 'https://docs.rsshub.app/routes/en/social-media#threads',
docs: 'https://docs.rsshub.app/routes/social-media#threads',
source: ['/:user'],
target: (params) => `/threads/${params.user.substring(1)}`,
},
+1 -1
View File
@@ -4,7 +4,7 @@ module.exports = {
'.': [
{
title: 'User',
docs: 'https://docs.rsshub.app/routes/en/social-media#tiktok',
docs: 'https://docs.rsshub.app/routes/social-media#tiktok',
source: ['/:user'],
target: '/tiktok/user/:user',
},
+12 -3
View File
@@ -31,9 +31,11 @@ const replaceBreak = (text) => text.replaceAll(/<br><br>|<br>/g, ' ');
const formatText = (item) => {
let text = item.full_text;
const id_str = item.id_str || item.conversation_id_str;
const urls = item.entities.urls || [];
for (const url of urls) {
text = text.replaceAll(url.url, url.expanded_url);
// trim link pointing to the tweet itself (usually appears when the tweet is truncated)
text = text.replaceAll(url.url, url.expanded_url.endsWith(id_str) ? '' : url.expanded_url);
}
const media = item.extended_entities?.media || [];
for (const m of media) {
@@ -361,20 +363,27 @@ const ProcessFeed = (ctx, { data = [] }, params = {}) => {
description += quote;
if (readable) {
description += `<br clear='both' /><div style='clear: both'></div><hr>`;
description += `<br clear='both' /><div style='clear: both'></div>`;
}
if (showTimestampInDescription) {
if (readable) {
description += `<hr>`;
}
description += `<small>${parseDate(item.created_at)}</small>`;
}
const authorName = originalItem.user.name;
const link =
originalItem.user.screen_name && (originalItem.id_str || originalItem.conversation_id_str)
? `https://twitter.com/${originalItem.user.screen_name}/status/${originalItem.id_str || originalItem.conversation_id_str}`
: `https://twitter.com/${item.user.screen_name}/status/${item.id_str || item.conversation_id_str}`;
return {
title,
author: authorName,
description,
pubDate: parseDate(item.created_at),
link: `https://twitter.com/${item.user.screen_name}/status/${item.id_str || item.conversation_id_str}`,
link,
_extra:
(isRetweet && {
+6
View File
@@ -79,6 +79,10 @@ const timelineParams = {
simple_quoted_tweet: 1,
};
const bearerToken = 'Bearer AAAAAAAAAAAAAAAAAAAAAFXzAwAAAAAAMHCxpeSDG1gLNLghVe8d74hl6k4%3DRUMF4xAQLsbeBhTSRrCiQpJtxoGWeyHrDb5te2jpGskWDFW82F';
const guestActivateUrl = baseUrl + '/1.1/guest/activate.json';
module.exports = {
baseUrl,
consumerKey,
@@ -86,4 +90,6 @@ module.exports = {
gqlMap,
gqlFeatures,
timelineParams,
bearerToken,
guestActivateUrl,
};
+133
View File
@@ -0,0 +1,133 @@
// https://github.com/BANKA2017/twitter-monitor/blob/node/apps/open_account/scripts/login.mjs
const { bearerToken, guestActivateUrl } = require('./constants');
const got = require('@/utils/got');
const crypto = require('crypto');
const config = require('@/config').value;
const { v5: uuidv5 } = require('uuid');
const NAMESPACE = 'd41d092b-b007-48f7-9129-e9538d2d8fe9';
const username = config.twitter.username;
const password = config.twitter.password;
let authentication = null;
const headers = {
'User-Agent': 'TwitterAndroid/10.21.0-release.0 (310210000-r-0) ONEPLUS+A3010/9 (OnePlus;ONEPLUS+A3010;OnePlus;OnePlus3;0;;1;2016)',
'X-Twitter-API-Version': 5,
'X-Twitter-Client': 'TwitterAndroid',
'X-Twitter-Client-Version': '10.21.0-release.0',
'OS-Version': '28',
'System-User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 9; ONEPLUS A3010 Build/PKQ1.181203.001)',
'X-Twitter-Active-User': 'yes',
'Content-Type': 'application/json',
Authorization: bearerToken,
};
async function login() {
const android_id = uuidv5(username, NAMESPACE);
headers['X-Twitter-Client-DeviceID'] = android_id;
const ct0 = crypto.randomUUID().replaceAll('-', '');
const guestToken = await got(guestActivateUrl, {
headers: {
authorization: bearerToken,
'x-csrf-token': ct0,
cookie: 'ct0=' + ct0,
},
method: 'POST',
});
headers['x-guest-token'] = guestToken.data.guest_token;
const task1 = await got.post(
'https://api.twitter.com/1.1/onboarding/task.json?' +
new URLSearchParams({
flow_name: 'login',
api_version: '1',
known_device_token: `${ct0}${ct0}`.slice(0, 40),
sim_country_code: 'us',
}).toString(),
{
headers,
json: {
flow_token: null,
input_flow_data: {
country_code: null,
flow_context: {
referrer_context: {
referral_details: 'utm_source=google-play&utm_medium=organic',
referrer_url: '',
},
start_location: {
location: 'deeplink',
},
},
requested_variant: null,
target_user_id: 0,
},
},
}
);
headers.att = task1.headers.att;
const task2 = await got.post('https://api.twitter.com/1.1/onboarding/task.json', {
headers,
json: {
flow_token: task1.data.flow_token,
subtask_inputs: [
{
enter_text: {
suggestion_id: null,
text: username,
link: 'next_link',
},
subtask_id: 'LoginEnterUserIdentifier',
},
],
},
});
const task3 = await got.post('https://api.twitter.com/1.1/onboarding/task.json', {
headers,
json: {
flow_token: task2.data.flow_token,
subtask_inputs: [
{
enter_password: {
password,
link: 'next_link',
},
subtask_id: 'LoginEnterPassword',
},
],
},
});
const task4 = await got.post('https://api.twitter.com/1.1/onboarding/task.json', {
headers,
json: {
flow_token: task3.data.flow_token,
subtask_inputs: [
{
check_logged_in_account: {
link: 'AccountDuplicationCheck_false',
},
subtask_id: 'AccountDuplicationCheck',
},
],
},
});
for (const subtask of task4.data?.subtasks || []) {
if (subtask.open_account) {
authentication = subtask.open_account;
break;
}
}
return authentication;
}
module.exports = login;
+36 -14
View File
@@ -5,12 +5,35 @@ const got = require('@/utils/got');
const OAuth = require('oauth-1.0a');
const CryptoJS = require('crypto-js');
const queryString = require('query-string');
const login = require('./login');
let tokenIndex = 0;
let authentication = null;
const twitterGot = async (url, params) => {
if (!config.twitter.oauthTokens?.length || !config.twitter.oauthTokenSecrets?.length || config.twitter.oauthTokens.length !== config.twitter.oauthTokenSecrets.length) {
throw new Error('Invalid twitter oauth tokens');
let token;
if (config.twitter.username && config.twitter.password) {
if (!authentication) {
authentication = await login();
}
token = {
key: authentication.oauth_token,
secret: authentication.oauth_token_secret,
};
} else if (config.twitter.oauthTokens?.length && config.twitter.oauthTokenSecrets.length && config.twitter.oauthTokens.length === config.twitter.oauthTokenSecrets.length) {
token = {
key: config.twitter.oauthTokens[tokenIndex],
secret: config.twitter.oauthTokenSecrets[tokenIndex],
};
tokenIndex++;
if (tokenIndex >= config.twitter.oauthTokens.length) {
tokenIndex = 0;
}
}
if (!token) {
throw new Error('Invalid twitter configs');
}
const oauth = OAuth({
@@ -22,15 +45,6 @@ const twitterGot = async (url, params) => {
hash_function: (base_string, key) => CryptoJS.HmacSHA1(base_string, key).toString(CryptoJS.enc.Base64),
});
const token = {
key: config.twitter.oauthTokens[tokenIndex],
secret: config.twitter.oauthTokenSecrets[tokenIndex],
};
tokenIndex++;
if (tokenIndex >= config.twitter.oauthTokens.length) {
tokenIndex = 0;
}
const requestData = {
url: `${url}?${queryString.stringify(params)}`,
method: 'GET',
@@ -151,6 +165,7 @@ function gatherLegacyFromData(entries, filterNested, userId) {
continue;
}
t.legacy.user = t.core?.user_result?.result?.legacy || t.core?.user_results?.result?.legacy;
t.legacy.id_str = t.rest_id; // avoid falling back to conversation_id_str elsewhere
const quote = t.quoted_status_result?.result;
if (quote) {
t.legacy.quoted_status = quote.legacy;
@@ -257,10 +272,17 @@ const getUserTweets = async (cache, id, params = {}) => {
}
const idSet = new Set();
tweets = tweets
.filter((tweet) => !tweet.in_reply_to_screen_name) // exclude replies
.map((tweet) => (idSet.has(tweet.id_str) ? null : idSet.add(tweet.id_str)) && tweet) // deduplicate
.filter(
(tweet) =>
!tweet.in_reply_to_user_id_str || // exclude replies
tweet.in_reply_to_user_id_str === rest_id // but include replies to self (threads)
)
.map((tweet) => {
const id_str = tweet.id_str || tweet.conversation_id_str;
return !idSet.has(id_str) && idSet.add(id_str) && tweet;
}) // deduplicate
.filter(Boolean) // remove null
.sort((a, b) => b.id_str - a.id_str) // desc
.sort((a, b) => (b.id_str || b.conversation_id_str) - (a.id_str || a.conversation_id_str)) // desc
.slice(0, 20);
cache.set(cacheKey, JSON.stringify(tweets));
return tweets;
+1 -1
View File
@@ -4,7 +4,7 @@ module.exports = {
'.': [
{
title: 'News Flow',
docs: 'https://docs.rsshub.app/routes/en/finance#unusual-whales',
docs: 'https://docs.rsshub.app/routes/finance#unusual-whales',
source: ['/news', '/'],
target: '/unusualwhales/news',
},
+1 -1
View File
@@ -4,7 +4,7 @@ module.exports = {
'.': [
{
title: 'Random words',
docs: 'https://docs.rsshub.app/routes/en/other#urban-dictionary',
docs: 'https://docs.rsshub.app/routes/other#urban-dictionary',
source: ['/random.php', '/'],
target: '/urbandictionary/random',
},
+1 -1
View File
@@ -4,7 +4,7 @@ module.exports = {
verfgh: [
{
title: 'Press releases',
docs: 'https://docs.rsshub.app/routes/en/government#constitutional-court-of-baden-wurttemberg-germany',
docs: 'https://docs.rsshub.app/routes/government#constitutional-court-of-baden-wurttemberg-germany',
source: ['/de/presse-und-service/pressemitteilungen/'],
target: '/verfghbw/press',
},
+2 -1
View File
@@ -1,6 +1,7 @@
const got = require('@/utils/got');
const queryString = require('query-string');
const { parseDate } = require('@/utils/parse-date');
const sanitizeHtml = require('sanitize-html');
module.exports = async (ctx) => {
const res1 = await got({
@@ -34,7 +35,7 @@ module.exports = async (ctx) => {
item: data.map((item) => {
const description = item.text;
return {
title: item.title ?? description.replaceAll(/<[^>]+>/g, ''),
title: item.title ?? sanitizeHtml(description, { allowedTags: [], allowedAttributes: {} }),
description: item.text,
pubDate: parseDate(item.created_at),
link: `https://xueqiu.com${item.target}`,
+1 -1
View File
@@ -4,7 +4,7 @@ module.exports = (router) => {
router.get('/fund/:id', require('./fund'));
router.get('/hots', require('./hots'));
router.get('/snb/:id', require('./snb'));
router.get('/stock_comments/:id/:titleLength?', require('./stock-comments'));
router.get('/stock_comments/:id', require('./stock-comments'));
router.get('/stock_info/:id/:type?', require('./stock-info'));
router.get('/today', require('./today'));
router.get('/user/:id/:type?', require('./user'));
+2 -2
View File
@@ -3,10 +3,10 @@ const cheerio = require('cheerio');
const { art } = require('@/utils/render');
const path = require('path');
const { parseDate } = require('@/utils/parse-date');
const sanitizeHtml = require('sanitize-html');
module.exports = async (ctx) => {
const id = ctx.params.id;
const titleLength = ctx.params.titleLength ? Number.parseInt(ctx.params.titleLength) : 30;
const res = await got({
method: 'get',
@@ -35,7 +35,7 @@ module.exports = async (ctx) => {
}
const description = art(path.join(__dirname, 'templates/comments_description.art'), { item });
return {
title: item.title === '' ? item.text.replaceAll(/<[^>]+>/g, '').slice(0, titleLength) : item.title,
title: item.title || sanitizeHtml(item.text, { allowedTags: [], allowedAttributes: {} }),
description,
pubDate: parseDate(item.created_at),
link,

Some files were not shown because too many files have changed in this diff Show More