mirror of
https://github.com/DIYgod/RSSHub.git
synced 2026-10-25 10:14:31 +08:00
Merge branch 'master' into feature/hono
This commit is contained in:
+3
-3
@@ -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
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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'));
|
||||
|
||||
@@ -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(),
|
||||
})),
|
||||
};
|
||||
};
|
||||
@@ -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(),
|
||||
})),
|
||||
};
|
||||
};
|
||||
@@ -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(),
|
||||
})),
|
||||
};
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
'/nice': ['nczitzk'],
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
@@ -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',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = (router) => {
|
||||
router.get('/nice', require('./nice'));
|
||||
};
|
||||
@@ -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 }}
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
'/paper/:id?': ['nczitzk'],
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
@@ -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]}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = function (router) {
|
||||
router.get('/paper/:id?', require('./paper'));
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
{{@ resource }}
|
||||
{{@ description }}
|
||||
@@ -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',
|
||||
},
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
|
||||
@@ -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,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'));
|
||||
};
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
})
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
}));
|
||||
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
'/lives/:category?': ['nczitzk'],
|
||||
'/timeline/:category?': ['nczitzk'],
|
||||
'/:category?': ['nczitzk'],
|
||||
};
|
||||
@@ -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',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
module.exports = (router) => {
|
||||
router.get('/lives/:category?', require('./lives'));
|
||||
router.get('/timeline/:category?', require('./timeline'));
|
||||
router.get('/:category?', require('./catalogue'));
|
||||
};
|
||||
@@ -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 }}
|
||||
@@ -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
@@ -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) => {
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
'/': ['TonyRL'],
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
@@ -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',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = (router) => {
|
||||
router.get('/', require('./news'));
|
||||
};
|
||||
@@ -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',
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
'/:id': ['TonyRL'],
|
||||
};
|
||||
@@ -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',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = (router) => {
|
||||
router.get('/:id', require('./index'));
|
||||
};
|
||||
@@ -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',
|
||||
},
|
||||
|
||||
@@ -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,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',
|
||||
},
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
'/:category?': ['TonyRL'],
|
||||
};
|
||||
@@ -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', '')}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = (router) => {
|
||||
router.get('/:category?', require('./index'));
|
||||
};
|
||||
@@ -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',
|
||||
},
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
module.exports = {
|
||||
'/:category_id': ['proletarius101'],
|
||||
'/topics/:topic': ['TonyRL'],
|
||||
'/coronavirus': ['DIYgod'],
|
||||
};
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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'));
|
||||
};
|
||||
|
||||
@@ -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',
|
||||
};
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -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')}` : ''}`,
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -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(
|
||||
{
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
|
||||
@@ -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)}`,
|
||||
},
|
||||
|
||||
@@ -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
@@ -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 && {
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
|
||||
@@ -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}`,
|
||||
|
||||
@@ -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'));
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user