Merge branch 'master' into pr/justjustCC/14123
This commit is contained in:
commit
9dc7e52db9
|
|
@ -76,6 +76,8 @@ const calculateValue = () => {
|
|||
},
|
||||
proxyStrategy: envs.PROXY_STRATEGY || 'all', // all / on_retry
|
||||
reverseProxyUrl: envs.REVERSE_PROXY_URL,
|
||||
pacUri: envs.PAC_URI,
|
||||
pacScript: envs.PAC_SCRIPT,
|
||||
// auth
|
||||
authentication: {
|
||||
name: envs.HTTP_BASIC_AUTH_NAME || 'usernam3',
|
||||
|
|
@ -121,6 +123,7 @@ const calculateValue = () => {
|
|||
// Route-specific Configurations
|
||||
bilibili: {
|
||||
cookies: bilibili_cookies,
|
||||
dmImgList: envs.BILIBILI_DM_IMG_LIST,
|
||||
},
|
||||
bitbucket: {
|
||||
username: envs.BITBUCKET_USERNAME,
|
||||
|
|
|
|||
|
|
@ -905,7 +905,7 @@ router.get('/4gamers/tag/:tag', lazyloadRouteHandler('./routes/4gamers/tag'));
|
|||
router.get('/4gamers/topic/:topic', lazyloadRouteHandler('./routes/4gamers/topic'));
|
||||
|
||||
// 大麦网
|
||||
router.get('/damai/activity/:city/:category/:subcategory/:keyword?', lazyloadRouteHandler('./routes/damai/activity'));
|
||||
// router.get('/damai/activity/:city/:category/:subcategory/:keyword?', lazyloadRouteHandler('./routes/damai/activity'));
|
||||
|
||||
// 桂林电子科技大学新闻资讯
|
||||
router.get('/guet/xwzx/:type?', lazyloadRouteHandler('./routes/guet/news'));
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
const got = require('@/utils/got');
|
||||
|
||||
module.exports = async (ctx) => {
|
||||
const city = ctx.params.city === '全部' ? '' : ctx.params.city;
|
||||
const category = ctx.params.category === '全部' ? '' : ctx.params.category;
|
||||
const subcategory = ctx.params.subcategory === '全部' ? '' : ctx.params.subcategory;
|
||||
const keyword = ctx.params.keyword ? ctx.params.keyword : '';
|
||||
|
||||
const url = `https://search.damai.cn/searchajax.html?keyword=${encodeURIComponent(keyword)}&cty=${encodeURIComponent(city)}&ctl=${encodeURIComponent(category)}&sctl=${encodeURIComponent(
|
||||
subcategory
|
||||
)}&tsg=0&st=&et=&order=3&pageSize=30&currPage=1&tn=`;
|
||||
|
||||
const response = await got.get(url);
|
||||
const data = response.data;
|
||||
const list = data.pageData.resultData;
|
||||
|
||||
ctx.state.data = {
|
||||
title: `大麦网票务 - ${city ? city : '全国'} - ${category ? category : '全部分类'}${subcategory ? ' - ' + subcategory : ''}${keyword ? ' - ' + keyword : ''}`,
|
||||
link: 'https://search.damai.cn/search.htm',
|
||||
item: list.map((item) => ({
|
||||
title: item.nameNoHtml,
|
||||
author: item.actors ? item.actors.replace(/<[^<>]*>/, '') : '大麦网',
|
||||
description: `<img src="${item.verticalPic}" /><p>${item.description}</p><p>地点:${item.venuecity} | ${item.venue}</p><p>时间:${item.showtime}</p><p>票价:${item.price_str}</p>`,
|
||||
pubDate: new Date(),
|
||||
link: `https://detail.damai.cn/item.htm?id=${item.projectid}`,
|
||||
})),
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
const config = require('@/config').value;
|
||||
const logger = require('./logger');
|
||||
|
||||
const possibleProtocol = ['http', 'https', 'ftp', 'file', 'data'];
|
||||
|
||||
const pacProxy = (pacUri, pacScript, proxyObj) => {
|
||||
let pacUrlHandler = null;
|
||||
|
||||
// Validate PAC_URI / PAC_SCRIPT
|
||||
if (pacScript) {
|
||||
if (typeof pacScript === 'string') {
|
||||
pacUri = 'data:text/javascript;charset=utf-8,' + encodeURIComponent(pacScript);
|
||||
} else {
|
||||
logger.error('Invalid PAC_SCRIPT, use PAC_URI instead');
|
||||
}
|
||||
}
|
||||
if (pacUri && typeof pacUri === 'string') {
|
||||
try {
|
||||
pacUrlHandler = new URL(pacUri);
|
||||
} catch (e) {
|
||||
pacUri = null;
|
||||
pacUrlHandler = null;
|
||||
logger.error(`Parse PAC_URI error: ${e.stack}`);
|
||||
}
|
||||
} else {
|
||||
pacUri = null;
|
||||
}
|
||||
|
||||
// Check if PAC_URI has the right protocol
|
||||
if (pacUri && !possibleProtocol.includes(pacUrlHandler?.protocol?.replace(':', ''))) {
|
||||
logger.error(`Unsupported PAC protocol: ${pacUrlHandler?.protocol?.replace(':', '')}, expect one of ${possibleProtocol.join(', ')}`);
|
||||
pacUri = null;
|
||||
pacUrlHandler = null;
|
||||
}
|
||||
|
||||
// Validate proxyObj
|
||||
if (pacUrlHandler) {
|
||||
proxyObj.host = pacUrlHandler.hostname;
|
||||
proxyObj.port = parseInt(pacUrlHandler.port) || undefined;
|
||||
proxyObj.protocol = pacUrlHandler.protocol.replace(':', '');
|
||||
} else {
|
||||
proxyObj.protocol = proxyObj.host = proxyObj.port = proxyObj.auth = undefined;
|
||||
}
|
||||
|
||||
// Validate PROXY_AUTH
|
||||
if (proxyObj.auth && pacUrlHandler) {
|
||||
let promptProxyUri = false;
|
||||
if (pacUrlHandler.username || pacUrlHandler.password) {
|
||||
logger.warn('PAC_URI contains username and/or password, ignoring PROXY_AUTH');
|
||||
proxyObj.auth = undefined;
|
||||
} else if (!['http:', 'https:'].includes(pacUrlHandler.protocol)) {
|
||||
logger.warn(`PROXY_AUTH is only supported by HTTP(S) proxies, but got ${pacUrlHandler.protocol}, ignoring`);
|
||||
proxyObj.auth = undefined;
|
||||
promptProxyUri = true;
|
||||
} else {
|
||||
logger.info('PROXY_AUTH is set and will be used for requests from Node.js. However, requests from puppeteer will not use it');
|
||||
promptProxyUri = true;
|
||||
}
|
||||
if (promptProxyUri) {
|
||||
logger.info('To get rid of this, set PAC_URI like protocol://username:password@host:port and clear PROXY_{AUTH,PROTOCOL,HOST,PORT}');
|
||||
}
|
||||
}
|
||||
|
||||
// Compatible with unify-proxy
|
||||
return {
|
||||
proxyUri: pacUri,
|
||||
proxyObj,
|
||||
proxyUrlHandler: pacUrlHandler,
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
pacProxy,
|
||||
...pacProxy(config.pacUri, config.pacScript, config.proxy),
|
||||
};
|
||||
|
|
@ -1,11 +1,15 @@
|
|||
const config = require('@/config').value;
|
||||
const { proxyUri, proxyObj, proxyUrlHandler } = require('./unify-proxy');
|
||||
const proxyIsPAC = config.pacUri || config.pacScript;
|
||||
const { proxyUri, proxyObj, proxyUrlHandler } = proxyIsPAC ? require('./pac-proxy') : require('./unify-proxy');
|
||||
const logger = require('./logger');
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
|
||||
let agent = null;
|
||||
if (proxyUri) {
|
||||
if (proxyIsPAC) {
|
||||
const { PacProxyAgent } = require('pac-proxy-agent');
|
||||
agent = new PacProxyAgent(`pac+${proxyUri}`);
|
||||
} else if (proxyUri) {
|
||||
if (proxyUri.startsWith('http')) {
|
||||
const { HttpsProxyAgent } = require('https-proxy-agent');
|
||||
agent = new HttpsProxyAgent(proxyUri);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
const got = require('@/utils/got');
|
||||
const cheerio = require('cheerio');
|
||||
const timezone = require('@/utils/timezone');
|
||||
const { parseDate } = require('@/utils/parse-date');
|
||||
|
||||
module.exports = async (ctx) => {
|
||||
const path = ctx.params.path ?? '';
|
||||
const rootUrl = `https://www.1lou.me`;
|
||||
const currentUrl = `${rootUrl}/${path}`;
|
||||
const response = await got({
|
||||
method: 'get',
|
||||
url: currentUrl,
|
||||
});
|
||||
const $ = cheerio.load(response.data);
|
||||
|
||||
let items = $('li.media.thread.tap:not(.hidden-sm)')
|
||||
.toArray()
|
||||
.map((item) => {
|
||||
const title = $(item).find('.subject.break-all').children('a').first();
|
||||
const author = $(item).find('.username.text-grey.mr-1').text();
|
||||
const pubDate = $(item).find('.date.text-grey').text();
|
||||
return {
|
||||
title: title.text(),
|
||||
link: `${rootUrl}/${title.attr('href')}`,
|
||||
author,
|
||||
pubDate: timezone(parseDate(pubDate), +8),
|
||||
};
|
||||
});
|
||||
items = await Promise.all(
|
||||
items.map((item) =>
|
||||
ctx.cache.tryGet(item.link, async () => {
|
||||
const detailResponse = await got({
|
||||
method: 'get',
|
||||
url: item.link,
|
||||
});
|
||||
const content = cheerio.load(detailResponse.data);
|
||||
item.description = content('.message.break-all').html();
|
||||
const torrents = content('.attachlist').find('a');
|
||||
if (torrents.length > 0) {
|
||||
item.enclosure_type = 'application/x-bittorrent';
|
||||
item.enclosure_url = `${rootUrl}/${torrents.first().attr('href')}`;
|
||||
}
|
||||
|
||||
return item;
|
||||
})
|
||||
)
|
||||
);
|
||||
ctx.state.data = {
|
||||
title: '1Lou',
|
||||
link: currentUrl,
|
||||
item: items,
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = {
|
||||
'/:path?': ['falling'],
|
||||
};
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
module.exports = {
|
||||
'1lou.me': {
|
||||
_name: 'BT之家 1LOU站',
|
||||
'.': [
|
||||
{
|
||||
title: '搜索',
|
||||
docs: 'https://docs.rsshub.app/routes/multimedia#bt-zhi-jia-1lou-zhan',
|
||||
source: ['/:path'],
|
||||
target: '/1lou/:path',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = function (router) {
|
||||
router.get('/:path?', require('./index'));
|
||||
};
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
const got = require('@/utils/got');
|
||||
const cheerio = require('cheerio');
|
||||
const timezone = require('@/utils/timezone');
|
||||
const { parseDate } = require('@/utils/parse-date');
|
||||
|
||||
module.exports = async (ctx) => {
|
||||
const { id = '1', name = '重要通知' } = ctx.params;
|
||||
const limit = ctx.query.limit ? parseInt(ctx.query.limit, 10) : 30;
|
||||
|
||||
const rootUrl = 'http://www.acpaa.cn';
|
||||
const currentUrl = new URL(`article/taglist.jhtml?tagIds=${id}&tagname=${name}`, rootUrl).href;
|
||||
|
||||
const { data: response } = await got(currentUrl);
|
||||
|
||||
const $ = cheerio.load(response);
|
||||
|
||||
let items = $('div.text01 ul li a[title]')
|
||||
.slice(0, limit)
|
||||
.toArray()
|
||||
.map((item) => {
|
||||
item = $(item);
|
||||
|
||||
return {
|
||||
title: item.prop('title'),
|
||||
link: new URL(item.prop('href'), rootUrl).href,
|
||||
pubDate: timezone(parseDate(item.find('span[title]').prop('title')), +8),
|
||||
};
|
||||
});
|
||||
|
||||
items = await Promise.all(
|
||||
items.map((item) =>
|
||||
ctx.cache.tryGet(item.link, async () => {
|
||||
const { data: detailResponse } = await got(item.link);
|
||||
|
||||
const content = cheerio.load(detailResponse);
|
||||
|
||||
item.title = content('div.xhjj_head01').text();
|
||||
item.description = content('div.text01').html();
|
||||
|
||||
return item;
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
const author = $('title').text().replace(/-/g, '');
|
||||
const subtitle = $('span.myTitle').text().trim();
|
||||
|
||||
ctx.state.data = {
|
||||
item: items,
|
||||
title: `${author} - ${subtitle}`,
|
||||
link: currentUrl,
|
||||
description: $('meta[property="og:description"]').prop('content'),
|
||||
language: 'zh',
|
||||
subtitle,
|
||||
author,
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = {
|
||||
'/:id?/:name?': ['nczitzk'],
|
||||
};
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
module.exports = {
|
||||
'acpaa.cn': {
|
||||
_name: '中华全国专利代理师协会',
|
||||
'.': [
|
||||
{
|
||||
title: '文章',
|
||||
docs: 'https://docs.rsshub.app/routes/other#zhong-hua-quan-guo-zhuan-li-dai-li-shi-xie-hui',
|
||||
source: ['/article/taglist.jhtml'],
|
||||
target: (url) => {
|
||||
url = new URL(url);
|
||||
const id = url.searchParams.get('id');
|
||||
const name = url.searchParams.get('name');
|
||||
|
||||
return `/acpaa${id ? `/${id}${name ? `/${name}` : ''}` : ''}`;
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = (router) => {
|
||||
router.get('/:id?/:name?', require('./'));
|
||||
};
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
const got = require('@/utils/got');
|
||||
const cheerio = require('cheerio');
|
||||
const { parseDate } = require('@/utils/parse-date');
|
||||
|
||||
module.exports = async (ctx) => {
|
||||
const baseUrl = 'https://backlinko.com';
|
||||
const { data: response, url: link } = await got(`${baseUrl}/blog`);
|
||||
|
||||
const $ = cheerio.load(response);
|
||||
const nextData = JSON.parse($('#__NEXT_DATA__').text());
|
||||
const {
|
||||
buildId,
|
||||
props: { pageProps },
|
||||
} = nextData;
|
||||
|
||||
const posts = pageProps.posts.nodes.concat(pageProps.backlinkoLockedPosts.nodes).map((post) => ({
|
||||
title: post.title,
|
||||
link: `${baseUrl}/${post.slug}`,
|
||||
pubDate: parseDate(post.modified),
|
||||
author: post.author.node.name,
|
||||
apiUrl: `${baseUrl}/_next/data/${buildId}/${post.slug}.json`,
|
||||
}));
|
||||
|
||||
const items = await Promise.all(
|
||||
posts.map((item) =>
|
||||
ctx.cache.tryGet(item.link, async () => {
|
||||
const { data } = await got(item.apiUrl);
|
||||
const post = data.pageProps.post || data.pageProps.lockedPost;
|
||||
|
||||
item.description = post.content;
|
||||
|
||||
return item;
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
ctx.state.data = {
|
||||
title: pageProps.page.seo.title,
|
||||
description: pageProps.page.seo.metaDesc,
|
||||
link,
|
||||
language: 'en',
|
||||
item: items,
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = {
|
||||
'/blog': ['TonyRL'],
|
||||
};
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
module.exports = {
|
||||
'backlinko.com': {
|
||||
_name: 'Backlinko',
|
||||
'.': [
|
||||
{
|
||||
title: 'Blog',
|
||||
docs: 'https://docs.rsshub.app/routes/blog#backlinko',
|
||||
source: ['/blog', '/'],
|
||||
target: '/backlinko/blog',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = (router) => {
|
||||
router.get('/blog', require('./blog'));
|
||||
};
|
||||
|
|
@ -55,8 +55,8 @@ module.exports = {
|
|||
return cookie.join('; ');
|
||||
});
|
||||
},
|
||||
getVerifyString: (ctx) => {
|
||||
const key = 'bili-verify-string';
|
||||
getWbiVerifyString: (ctx) => {
|
||||
const key = 'bili-wbi-verify-string';
|
||||
return ctx.cache.tryGet(key, async () => {
|
||||
const cookie = await module.exports.getCookie(ctx);
|
||||
const { data: navResponse } = await got('https://api.bilibili.com/x/web-interface/nav', {
|
||||
|
|
@ -97,14 +97,14 @@ module.exports = {
|
|||
const key = 'bili-username-from-uid-' + uid;
|
||||
return ctx.cache.tryGet(key, async () => {
|
||||
const cookie = await module.exports.getCookie(ctx);
|
||||
const verifyString = await module.exports.getVerifyString(ctx);
|
||||
const wbiVerifyString = await module.exports.getWbiVerifyString(ctx);
|
||||
// await got(`https://space.bilibili.com/${uid}/`, {
|
||||
// headers: {
|
||||
// Referer: 'https://www.bilibili.com/',
|
||||
// Cookie: cookie,
|
||||
// },
|
||||
// });
|
||||
const params = utils.addVerifyInfo(`mid=${uid}&token=&platform=web&web_location=1550101`, verifyString);
|
||||
const params = utils.addWbiVerifyInfo(`mid=${uid}&token=&platform=web&web_location=1550101`, wbiVerifyString);
|
||||
const { data: nameResponse } = await got(`https://api.bilibili.com/x/space/wbi/acc/info?${params}`, {
|
||||
headers: {
|
||||
Referer: `https://space.bilibili.com/${uid}/`,
|
||||
|
|
@ -121,14 +121,14 @@ module.exports = {
|
|||
let face = await ctx.cache.get(faceKey);
|
||||
if (!name || !face) {
|
||||
const cookie = await module.exports.getCookie(ctx);
|
||||
const verifyString = await module.exports.getVerifyString(ctx);
|
||||
const wbiVerifyString = await module.exports.getWbiVerifyString(ctx);
|
||||
// await got(`https://space.bilibili.com/${uid}/`, {
|
||||
// headers: {
|
||||
// Referer: `https://www.bilibili.com/`,
|
||||
// Cookie: cookie,
|
||||
// },
|
||||
// });
|
||||
const params = utils.addVerifyInfo(`mid=${uid}&token=&platform=web&web_location=1550101`, verifyString);
|
||||
const params = utils.addWbiVerifyInfo(`mid=${uid}&token=&platform=web&web_location=1550101`, wbiVerifyString);
|
||||
const { data: nameResponse } = await got(`https://api.bilibili.com/x/space/wbi/acc/info?${params}`, {
|
||||
headers: {
|
||||
Referer: `https://space.bilibili.com/${uid}/`,
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ const cache = require('./cache');
|
|||
const utils = require('./utils');
|
||||
|
||||
module.exports = async (ctx) => {
|
||||
const verifyString = await cache.getVerifyString(ctx);
|
||||
const params = utils.addVerifyInfo('limit=10&platform=web', verifyString);
|
||||
const wbiVerifyString = await cache.getWbiVerifyString(ctx);
|
||||
const params = utils.addWbiVerifyInfo('limit=10&platform=web', wbiVerifyString);
|
||||
const url = `https://api.bilibili.com/x/web-interface/wbi/search/square?${params}`;
|
||||
const response = await got({
|
||||
method: 'get',
|
||||
|
|
|
|||
|
|
@ -17,9 +17,9 @@ module.exports = async (ctx) => {
|
|||
orderTitle = '人气直播';
|
||||
break;
|
||||
}
|
||||
const verifyString = await cache.getVerifyString(ctx);
|
||||
const wbiVerifyString = await cache.getWbiVerifyString(ctx);
|
||||
let params = `__refresh__=true&_extra=&context=&page=1&page_size=42&order=${order}&duration=&from_source=&from_spmid=333.337&platform=pc&highlight=1&single_column=0&keyword=${urlEncodedKey}&ad_resource=&source_tag=3&gaia_vtoken=&category_id=&search_type=live&dynamic_offset=0&web_location=1430654`;
|
||||
params = utils.addVerifyInfo(params, verifyString);
|
||||
params = utils.addWbiVerifyInfo(params, wbiVerifyString);
|
||||
|
||||
const response = await got({
|
||||
method: 'get',
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ module.exports = (router) => {
|
|||
router.get('/user/followers/:uid/:loginUid', require('./followers'));
|
||||
router.get('/user/followings/:uid/:loginUid', require('./followings'));
|
||||
router.get('/user/video/:uid/:disableEmbed?', require('./video'));
|
||||
router.get('/user/video-all/:uid/:disableEmbed?', require('./video-all'));
|
||||
router.get('/video/danmaku/:bvid/:pid?', require('./danmaku'));
|
||||
router.get('/video/page/:bvid/:disableEmbed?', require('./page'));
|
||||
router.get('/video/reply/:bvid', require('./reply'));
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
const config = require('@/config').value;
|
||||
const md5 = require('@/utils/md5');
|
||||
const CryptoJS = require('crypto-js');
|
||||
|
||||
|
|
@ -65,20 +66,58 @@ function hexsign(e) {
|
|||
return o;
|
||||
}
|
||||
|
||||
function addVerifyInfo(params, verifyString) {
|
||||
function addWbiVerifyInfo(params, wbiVerifyString) {
|
||||
const searchParams = new URLSearchParams(params);
|
||||
searchParams.sort();
|
||||
const verifyParam = searchParams.toString();
|
||||
const wts = Math.round(Date.now() / 1000);
|
||||
const w_rid = md5(`${verifyParam}&wts=${wts}${verifyString}`);
|
||||
const w_rid = md5(`${verifyParam}&wts=${wts}${wbiVerifyString}`);
|
||||
return `${params}&w_rid=${w_rid}&wts=${wts}`;
|
||||
}
|
||||
|
||||
// https://github.com/errcw/gaussian/blob/master/lib/box-muller.js
|
||||
function generateGaussianInteger(mean, std) {
|
||||
const _2PI = Math.PI * 2;
|
||||
const u1 = Math.random();
|
||||
const u2 = Math.random();
|
||||
|
||||
const z0 = Math.sqrt(-2.0 * Math.log(u1)) * Math.cos(_2PI * u2);
|
||||
|
||||
return Math.round(z0 * std + mean);
|
||||
}
|
||||
|
||||
function getDmImgList() {
|
||||
if (typeof config.bilibili.dmImgList !== 'undefined') {
|
||||
const dmImgList = JSON.parse(config.bilibili.dmImgList);
|
||||
return JSON.stringify([dmImgList[Math.floor(Math.random() * dmImgList.length)]]);
|
||||
}
|
||||
const x = Math.max(generateGaussianInteger(650, 5), 0);
|
||||
const y = Math.max(generateGaussianInteger(400, 5), 0);
|
||||
const path = [
|
||||
{
|
||||
x: 3 * x + 2 * y,
|
||||
y: 4 * x - 5 * y,
|
||||
z: 0,
|
||||
timestamp: Math.max(generateGaussianInteger(30, 5), 0),
|
||||
type: 0,
|
||||
},
|
||||
];
|
||||
return JSON.stringify(path);
|
||||
}
|
||||
|
||||
function addDmVerifyInfo(params, dmImgList) {
|
||||
const dmImgStr = Buffer.from('no webgl').toString('base64').slice(0, -2);
|
||||
const dmCoverImgStr = Buffer.from('no webgl').toString('base64').slice(0, -2);
|
||||
return `${params}&dm_img_list=${dmImgList}&dm_img_str=${dmImgStr}&dm_cover_img_str=${dmCoverImgStr}`;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
iframe,
|
||||
lsid,
|
||||
_uuid,
|
||||
hexsign,
|
||||
addVerifyInfo,
|
||||
addWbiVerifyInfo,
|
||||
getDmImgList,
|
||||
addDmVerifyInfo,
|
||||
bvidTime: 1589990400,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,75 @@
|
|||
const got = require('@/utils/got');
|
||||
const cache = require('./cache');
|
||||
const utils = require('./utils');
|
||||
const { parseDate } = require('@/utils/parse-date');
|
||||
|
||||
module.exports = async (ctx) => {
|
||||
const uid = ctx.params.uid;
|
||||
const disableEmbed = ctx.params.disableEmbed;
|
||||
const cookie = await cache.getCookie(ctx);
|
||||
const wbiVerifyString = await cache.getWbiVerifyString(ctx);
|
||||
const dmImgList = utils.getDmImgList();
|
||||
const [name, face] = await cache.getUsernameAndFaceFromUID(ctx, uid);
|
||||
|
||||
await got(`https://space.bilibili.com/${uid}/video?tid=0&page=1&keyword=&order=pubdate`, {
|
||||
headers: {
|
||||
Referer: `https://space.bilibili.com/${uid}/`,
|
||||
Cookie: cookie,
|
||||
},
|
||||
});
|
||||
const params = utils.addWbiVerifyInfo(utils.addDmVerifyInfo(`mid=${uid}&ps=30&tid=0&pn=1&keyword=&order=pubdate&platform=web&web_location=1550101&order_avoided=true`, dmImgList), wbiVerifyString);
|
||||
const response = await got(`https://api.bilibili.com/x/space/wbi/arc/search?${params}`, {
|
||||
headers: {
|
||||
Referer: `https://space.bilibili.com/${uid}/video?tid=0&page=1&keyword=&order=pubdate`,
|
||||
Cookie: cookie,
|
||||
},
|
||||
});
|
||||
|
||||
const vlist = [...response.data.data.list.vlist];
|
||||
const pageTotal = Math.ceil(response.data.data.page.count / response.data.data.page.ps);
|
||||
|
||||
const getPage = async (pageId) => {
|
||||
const cookie = await cache.getCookie(ctx);
|
||||
await got(`https://space.bilibili.com/${uid}/video?tid=0&page=${pageId}&keyword=&order=pubdate`, {
|
||||
headers: {
|
||||
Referer: `https://space.bilibili.com/${uid}/`,
|
||||
Cookie: cookie,
|
||||
},
|
||||
});
|
||||
const params = utils.addWbiVerifyInfo(utils.addDmVerifyInfo(`mid=${uid}&ps=30&tid=0&pn=${pageId}&keyword=&order=pubdate&platform=web&web_location=1550101&order_avoided=true`, dmImgList), wbiVerifyString);
|
||||
return got(`https://api.bilibili.com/x/space/wbi/arc/search?${params}`, {
|
||||
headers: {
|
||||
Referer: `https://space.bilibili.com/${uid}/video?tid=0&page=${pageId}&keyword=&order=pubdate`,
|
||||
Cookie: cookie,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const promises = [];
|
||||
|
||||
if (pageTotal > 1) {
|
||||
for (let i = 2; i <= pageTotal; i++) {
|
||||
promises.push(getPage(i));
|
||||
}
|
||||
const rets = await Promise.all(promises);
|
||||
rets.forEach((ret) => {
|
||||
vlist.push(...ret.data.data.list.vlist);
|
||||
});
|
||||
}
|
||||
|
||||
ctx.state.data = {
|
||||
title: name,
|
||||
link: `https://space.bilibili.com/${uid}/video`,
|
||||
description: `${name} 的 bilibili 所有视频`,
|
||||
logo: face,
|
||||
icon: face,
|
||||
item: vlist.map((item) => ({
|
||||
title: item.title,
|
||||
description: `${item.description}${!disableEmbed ? `<br><br>${utils.iframe(item.aid)}` : ''}<br><img src="${item.pic}">`,
|
||||
pubDate: parseDate(item.created, 'X'),
|
||||
link: item.created > utils.bvidTime && item.bvid ? `https://www.bilibili.com/video/${item.bvid}` : `https://www.bilibili.com/video/av${item.aid}`,
|
||||
author: name,
|
||||
comments: item.comment,
|
||||
})),
|
||||
};
|
||||
};
|
||||
|
|
@ -7,7 +7,8 @@ module.exports = async (ctx) => {
|
|||
const uid = ctx.params.uid;
|
||||
const disableEmbed = ctx.params.disableEmbed;
|
||||
const cookie = await cache.getCookie(ctx);
|
||||
const verifyString = await cache.getVerifyString(ctx);
|
||||
const wbiVerifyString = await cache.getWbiVerifyString(ctx);
|
||||
const dmImgList = utils.getDmImgList();
|
||||
const [name, face] = await cache.getUsernameAndFaceFromUID(ctx, uid);
|
||||
|
||||
// await got(`https://space.bilibili.com/${uid}/video?tid=0&page=1&keyword=&order=pubdate`, {
|
||||
|
|
@ -16,14 +17,7 @@ module.exports = async (ctx) => {
|
|||
// Cookie: cookie,
|
||||
// },
|
||||
// });
|
||||
const params = utils.addVerifyInfo(
|
||||
`mid=${uid}&ps=30&tid=0&pn=1&keyword=&order=pubdate&platform=web&web_location=1550101&order_avoided=true&dm_img_list=[]&dm_img_str=${Buffer.from('no webgl').toString('base64').slice(0, -2)}&dm_cover_img_str=${Buffer.from(
|
||||
'no webgl'
|
||||
)
|
||||
.toString('base64')
|
||||
.slice(0, -2)}`,
|
||||
verifyString
|
||||
);
|
||||
const params = utils.addWbiVerifyInfo(utils.addDmVerifyInfo(`mid=${uid}&ps=30&tid=0&pn=1&keyword=&order=pubdate&platform=web&web_location=1550101&order_avoided=true`, dmImgList), wbiVerifyString);
|
||||
const response = await got(`https://api.bilibili.com/x/space/wbi/arc/search?${params}`, {
|
||||
headers: {
|
||||
Referer: `https://space.bilibili.com/${uid}/video?tid=0&page=1&keyword=&order=pubdate`,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
module.exports = {
|
||||
'/keyword/:keyword': ['untitaker'],
|
||||
'/profile/:handle': ['TonyRL'],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
const { parseDate } = require('@/utils/parse-date');
|
||||
const { resolveHandle, getProfile, getAuthorFeed } = require('./utils');
|
||||
const { art } = require('@/utils/render');
|
||||
const { join } = require('path');
|
||||
|
||||
module.exports = async (ctx) => {
|
||||
const { handle } = ctx.params;
|
||||
const DID = await resolveHandle(handle, ctx.cache.tryGet);
|
||||
const profile = await getProfile(DID, ctx.cache.tryGet);
|
||||
const authorFeed = await getAuthorFeed(DID, ctx.cache.tryGet);
|
||||
|
||||
const items = authorFeed.feed.map(({ post }) => ({
|
||||
title: post.record.text.split('\n')[0],
|
||||
description: art(join(__dirname, 'templates/post.art'), {
|
||||
text: post.record.text.replace(/\n/g, '<br>'),
|
||||
embed: post.embed,
|
||||
// embed.$type "app.bsky.embed.record#view" and "app.bsky.embed.recordWithMedia#view"
|
||||
// are not handled
|
||||
}),
|
||||
author: post.author.displayName,
|
||||
pubDate: parseDate(post.record.createdAt),
|
||||
link: `https://bsky.app/profile/${post.author.handle}/post/${post.uri.split('app.bsky.feed.post/')[1]}`,
|
||||
upvotes: post.likeCount,
|
||||
comments: post.replyCount,
|
||||
}));
|
||||
|
||||
ctx.state.data = {
|
||||
title: `${profile.displayName} (@${profile.handle}) — Bluesky`,
|
||||
description: profile.description.replace(/\n/g, ' '),
|
||||
link: `https://bsky.app/profile/${profile.handle}`,
|
||||
image: profile.banner,
|
||||
icon: profile.avatar,
|
||||
logo: profile.avatar,
|
||||
item: items,
|
||||
};
|
||||
|
||||
ctx.state.json = {
|
||||
DID,
|
||||
profile,
|
||||
authorFeed,
|
||||
};
|
||||
};
|
||||
|
|
@ -8,6 +8,12 @@ module.exports = {
|
|||
source: '/search',
|
||||
target: (params, url) => `/bsky/keyword/${new URL(url).searchParams.get('q')}`,
|
||||
},
|
||||
{
|
||||
title: 'Post',
|
||||
docs: 'https://docs.rsshub.app/routes/social-media#bluesky-bsky',
|
||||
source: '/profile/:handle',
|
||||
target: '/bsky/profile/:handle',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
module.exports = (router) => {
|
||||
router.get('/keyword/:keyword', require('./keyword'));
|
||||
router.get('/profile/:handle', require('./posts'));
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
{{ if text }}
|
||||
{{@ text }}<br>
|
||||
{{ /if }}
|
||||
|
||||
{{ if embed }}
|
||||
{{ if embed.$type == 'app.bsky.embed.images#view'}}
|
||||
{{ each embed.images i }}
|
||||
<img src="{{ i.fullsize }}" alt="{{ i.alt }}"><br>
|
||||
{{ /each }}
|
||||
{{ else if embed.$type == 'app.bsky.embed.external#view' }}
|
||||
<a href="{{ embed.external.uri }}"><b>{{ embed.external.title }}</b><br>
|
||||
{{ embed.external.description }}
|
||||
</a>
|
||||
{{ /if }}
|
||||
{{ /if }}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
const got = require('@/utils/got');
|
||||
const config = require('@/config').value;
|
||||
|
||||
/**
|
||||
* docs: https://atproto.com/lexicons/app-bsky
|
||||
*/
|
||||
|
||||
// https://github.com/bluesky-social/atproto/blob/main/lexicons/com/atproto/identity/resolveHandle.json
|
||||
const resolveHandle = (handle, tryGet) =>
|
||||
tryGet(`bsky:${handle}`, async () => {
|
||||
const { data } = await got('https://public.api.bsky.app/xrpc/com.atproto.identity.resolveHandle', {
|
||||
searchParams: {
|
||||
handle,
|
||||
},
|
||||
});
|
||||
return data.did;
|
||||
});
|
||||
|
||||
// https://github.com/bluesky-social/atproto/blob/main/lexicons/app/bsky/actor/getProfile.json
|
||||
const getProfile = (did, tryGet) =>
|
||||
tryGet(`bsky:profile:${did}`, async () => {
|
||||
const { data } = await got('https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile', {
|
||||
searchParams: {
|
||||
actor: did,
|
||||
},
|
||||
});
|
||||
return data;
|
||||
});
|
||||
|
||||
// https://github.com/bluesky-social/atproto/blob/main/lexicons/app/bsky/feed/getAuthorFeed.json
|
||||
const getAuthorFeed = (did, tryGet) =>
|
||||
tryGet(
|
||||
`bsky:authorFeed:${did}`,
|
||||
async () => {
|
||||
const { data } = await got('https://public.api.bsky.app/xrpc/app.bsky.feed.getAuthorFeed', {
|
||||
searchParams: {
|
||||
actor: did,
|
||||
filter: 'posts_and_author_threads',
|
||||
limit: 30,
|
||||
},
|
||||
});
|
||||
return data;
|
||||
},
|
||||
config.cache.routeExpire,
|
||||
false
|
||||
);
|
||||
|
||||
module.exports = {
|
||||
resolveHandle,
|
||||
getProfile,
|
||||
getAuthorFeed,
|
||||
};
|
||||
|
|
@ -100,7 +100,8 @@ const processItems = async (apiUrl, limit, tryGet, ...searchParams) => {
|
|||
});
|
||||
|
||||
item.title = data.title;
|
||||
item.link = /^\/\//.test(data.link_url) ? `https:${data.link_url}` : data.link_url;
|
||||
// api 返回的 link_url 数据可能是空, 所有最好自己通过 id 拼接 url
|
||||
item.link = `${rootUrl}/article/${item.guid}.html`;
|
||||
item.description = content.html();
|
||||
item.author = data.author_name ?? data.author;
|
||||
item.category = [...new Set(categories)].filter((c) => c);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
const got = require('@/utils/got');
|
||||
const cheerio = require('cheerio');
|
||||
const { art } = require('@/utils/render');
|
||||
const { join } = require('path');
|
||||
|
||||
module.exports = async (ctx) => {
|
||||
const city = ctx.params.city === '全部' ? '' : ctx.params.city;
|
||||
const category = ctx.params.category === '全部' ? '' : ctx.params.category;
|
||||
const subcategory = ctx.params.subcategory === '全部' ? '' : ctx.params.subcategory;
|
||||
const keyword = ctx.params.keyword ? ctx.params.keyword : '';
|
||||
|
||||
const url = 'https://search.damai.cn/searchajax.html';
|
||||
|
||||
const response = await got(url, {
|
||||
searchParams: {
|
||||
keyword,
|
||||
cty: city,
|
||||
ctl: category,
|
||||
sctl: subcategory,
|
||||
tsg: 0,
|
||||
st: '',
|
||||
et: '',
|
||||
order: 3,
|
||||
pageSize: 30,
|
||||
currPage: 1,
|
||||
tn: '',
|
||||
},
|
||||
});
|
||||
const data = response.data;
|
||||
const list = data.pageData.resultData || [];
|
||||
|
||||
ctx.state.data = {
|
||||
title: `大麦网票务 - ${city || '全国'} - ${category || '全部分类'}${subcategory ? ' - ' + subcategory : ''}${keyword ? ' - ' + keyword : ''}`,
|
||||
link: 'https://search.damai.cn/search.htm',
|
||||
item: list.map((item) => ({
|
||||
title: item.nameNoHtml,
|
||||
author: item.actors ? cheerio.load(item.actors, null, false).text() : '大麦网',
|
||||
description: art(join(__dirname, 'templates/activity.art'), {
|
||||
item,
|
||||
}),
|
||||
link: `https://detail.damai.cn/item.htm?id=${item.projectid}`,
|
||||
})),
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = {
|
||||
'/activity/:city/:category/:subcategory/:keyword?': ['hoilc'],
|
||||
};
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
module.exports = {
|
||||
'damai.cn': {
|
||||
_name: '大麦网',
|
||||
search: [
|
||||
{
|
||||
title: '票务更新',
|
||||
docs: 'https://docs.rsshub.app/routes/shopping#da-mai-wang',
|
||||
source: ['/search.html'],
|
||||
target: (_params, url) => `/damai/activity/全部/全部/全部/${new URL(url).searchParams.get('keyword') || ''}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = (router) => {
|
||||
router.get('/activity/:city/:category/:subcategory/:keyword?', require('./activity'));
|
||||
};
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<img src="{{ item.verticalPic }}">
|
||||
<p>{{@ item.description }}</p>
|
||||
<p>地点:{{ item.venuecity }} | {{ item.venue }}</p>
|
||||
<p>时间:{{ item.showtime }}</p>
|
||||
<p>票价:{{ item.price_str }}</p>
|
||||
|
|
@ -25,7 +25,7 @@ function getDomList($, detailUrl) {
|
|||
return list;
|
||||
}
|
||||
|
||||
function getItemList($, detailUrl) {
|
||||
function getItemList($, detailUrl, second) {
|
||||
const encoded = $('.article script[type]')
|
||||
.text()
|
||||
.match(/return p}\('(.*)',(\d+),(\d+),'(.*)'.split\(/);
|
||||
|
|
@ -39,7 +39,9 @@ function getItemList($, detailUrl) {
|
|||
.replace(/\\\\"/g, '"')
|
||||
.replace(/\\\\\\/g, '')
|
||||
);
|
||||
const { downurls } = data.Data[0];
|
||||
// support secondary download address
|
||||
const { downurls } = second && data.Data.length > 1 ? data.Data[1] : data.Data[0];
|
||||
|
||||
return downurls.map((item) => {
|
||||
const [title, downurl] = item.split('$');
|
||||
const urlType = getUrlType(downurl);
|
||||
|
|
@ -69,7 +71,7 @@ function getMetaInfo($) {
|
|||
|
||||
module.exports = async (ctx) => {
|
||||
const { id } = ctx.params;
|
||||
const { domain } = ctx.query;
|
||||
const { domain, second } = ctx.query;
|
||||
let pureId = id;
|
||||
let detailType = 'html';
|
||||
// compatible for .html suffix in radar
|
||||
|
|
@ -84,7 +86,7 @@ module.exports = async (ctx) => {
|
|||
|
||||
const res = await got(detailUrl);
|
||||
const $ = cheerio.load(res.data);
|
||||
const list = getItemList($, detailUrl);
|
||||
const list = getItemList($, detailUrl, second);
|
||||
const meta = getMetaInfo($);
|
||||
|
||||
ctx.state.data = {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ const config = require('@/config').value;
|
|||
|
||||
const defaultDomain = 'mp4us.com';
|
||||
|
||||
const allowedDomains = ['domp4.cc', 'mp4kan.com', 'mp4us.com', 'wemp4.com', 'dbmp4.com'];
|
||||
const allowedDomains = ['domp4.cc', 'mp4us.com', 'wemp4.com', 'dbmp4.com'];
|
||||
|
||||
/**
|
||||
* trackers from https://www.domp4.cc/Style/2020/js/base.js?v=2
|
||||
|
|
|
|||
|
|
@ -18,7 +18,9 @@ module.exports = async (ctx) => {
|
|||
|
||||
const date = new Date();
|
||||
const year = date.getFullYear();
|
||||
const mon = date.getMonth() + 1;
|
||||
const month = date.getMonth() + 1;
|
||||
const mon = month < 10 ? '0' + month : month.toString();
|
||||
|
||||
let items = response.data.data[0].items;
|
||||
|
||||
const subjectCollectionId = items.find((item) => item.title.startsWith(`${year}年${mon}月`)).id;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
// Require necessary modules
|
||||
const got = require('@/utils/got'); // a customised got
|
||||
const cheerio = require('cheerio'); // an HTML parser with a jQuery-like API
|
||||
|
||||
module.exports = async (ctx) => {
|
||||
// Your logic here
|
||||
// Defining base URL
|
||||
const baseUrl = 'https://ekantipur.com';
|
||||
|
||||
// Retrive the channel parameter
|
||||
const { channel = 'news' } = ctx.params;
|
||||
|
||||
// Fetches content of the requested channel
|
||||
const { data: response } = await got(`${baseUrl}/${channel}`);
|
||||
const $ = cheerio.load(response);
|
||||
|
||||
// Retrive articles
|
||||
const list = $('article.normal')
|
||||
// We use the `toArray()` method to retrieve all the DOM elements selected as an array.
|
||||
.toArray()
|
||||
// We use the `map()` method to traverse the array and parse the data we need from each element.
|
||||
.map((item) => {
|
||||
item = $(item);
|
||||
const a = item.find('a').first();
|
||||
return {
|
||||
title: a.text(),
|
||||
// We need an absolute URL for `link`, but `a.attr('href')` returns a relative URL.
|
||||
link: `${baseUrl}${a.attr('href')}`,
|
||||
author: item.find('div.author').text(),
|
||||
category: channel,
|
||||
};
|
||||
});
|
||||
|
||||
const items = await Promise.all(
|
||||
list.map((item) =>
|
||||
ctx.cache.tryGet(item.link, async () => {
|
||||
const { data: response } = await got(item.link);
|
||||
const $ = cheerio.load(response);
|
||||
|
||||
// Remove sponsor elements
|
||||
$('a.static-sponsor').remove();
|
||||
$('div.ekans-wrapper').remove();
|
||||
|
||||
// Fetch title from the article page
|
||||
item.title = $('h1.eng-text-heading').text();
|
||||
// Fetch article content from the article page
|
||||
item.description = $('div.current-news-block').first().html();
|
||||
|
||||
// Every property of a list item defined above is reused here
|
||||
// and we add a new property 'description'
|
||||
return item;
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
ctx.state.data = {
|
||||
// channel title
|
||||
title: `Ekantipur - ${channel}`,
|
||||
// channel link
|
||||
link: `${baseUrl}/${channel}`,
|
||||
// each feed item
|
||||
item: items,
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = {
|
||||
'/:channel?': ['maniche04'],
|
||||
};
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
module.exports = {
|
||||
'ekantipur.com': {
|
||||
_name: 'Ekantipur',
|
||||
'.': [
|
||||
{
|
||||
title: 'Full Article RSS',
|
||||
docs: 'https://docs.rsshub.app/routes/traditional-media#ekantipur-%E0%A4%95%E0%A4%BE%E0%A4%A8%E0%A5%8D%E0%A4%A4%E0%A4%BF%E0%A4%AA%E0%A5%81%E0%A4%B0-nepal',
|
||||
source: ['/:channel'],
|
||||
target: '/ekantipur/:channel',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = (router) => {
|
||||
router.get('/:channel?', require('./issue'));
|
||||
};
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
module.exports = {
|
||||
'/myft/:key': ['HenryQW'],
|
||||
'/:language/:channel?': ['HenryQW', 'xyqfer'],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
const got = require('@/utils/got');
|
||||
const parser = require('@/utils/rss-parser');
|
||||
const cheerio = require('cheerio');
|
||||
|
||||
module.exports = async (ctx) => {
|
||||
const ProcessFeed = (content) => {
|
||||
// clean up the article
|
||||
content.find('div.o-share, aside, div.o-ads').remove();
|
||||
|
||||
return content.html();
|
||||
};
|
||||
|
||||
const link = `https://www.ft.com/myft/following/${ctx.params.key}.rss`;
|
||||
|
||||
const feed = await parser.parseURL(link);
|
||||
|
||||
const items = await Promise.all(
|
||||
feed.items.map((item) =>
|
||||
ctx.cache.tryGet(item.link, async () => {
|
||||
const response = await got({
|
||||
method: 'get',
|
||||
url: item.link,
|
||||
headers: {
|
||||
Referer: 'https://www.facebook.com',
|
||||
},
|
||||
});
|
||||
|
||||
const $ = cheerio.load(response.data);
|
||||
|
||||
item.description = ProcessFeed($('article.js-article__content-body'));
|
||||
item.category = [$('.n-content-tag--with-follow').text()].concat(
|
||||
$('.article__right-bottom a.concept-list__concept')
|
||||
.map((i, e) => $(e).text().trim())
|
||||
.get()
|
||||
);
|
||||
item.author = $('a.n-content-tag--author')
|
||||
.map((i, e) => $(e).text())
|
||||
.get();
|
||||
|
||||
return item;
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
ctx.state.data = {
|
||||
title: `FT.com - myFT`,
|
||||
link,
|
||||
description: `FT.com - myFT`,
|
||||
item: items,
|
||||
};
|
||||
};
|
||||
|
|
@ -6,6 +6,19 @@ module.exports = {
|
|||
title: 'FT 中文网',
|
||||
docs: 'https://docs.rsshub.app/routes/traditional-media#financial-times',
|
||||
},
|
||||
{
|
||||
title: 'myFT 个人 RSS',
|
||||
docs: 'https://docs.rsshub.app/routes/traditional-media#financial-times',
|
||||
},
|
||||
],
|
||||
},
|
||||
'ft.com': {
|
||||
_name: 'Financial Times',
|
||||
'.': [
|
||||
{
|
||||
title: 'myFT personal RSS',
|
||||
docs: 'https://docs.rsshub.app/routes/en/traditional-media#financial-times',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
module.exports = function (router) {
|
||||
router.get('/myft/:key', require('./myft'));
|
||||
router.get('/:language/:channel?', require('./channel'));
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
const got = require('@/utils/got');
|
||||
const { parseDate } = require('@/utils/parse-date');
|
||||
const { art } = require('@/utils/render');
|
||||
const { join } = require('path');
|
||||
|
||||
module.exports = async (ctx) => {
|
||||
const { kind = '' } = ctx.params;
|
||||
const baseUrl = 'https://gofans.cn';
|
||||
|
||||
const { data: response } = await got('https://api.gofans.cn/v1/web/app_records', {
|
||||
headers: {
|
||||
origin: baseUrl,
|
||||
},
|
||||
searchParams: {
|
||||
limit: 20,
|
||||
kind: kind && (kind === 'macos' ? 1 : 2),
|
||||
page: 1,
|
||||
},
|
||||
});
|
||||
|
||||
const items = response.data.map((item) => ({
|
||||
title: `「${item.price === '0.00' ? '免费' : '降价'}」-「${item.kind === 1 ? 'macOS' : 'iOS'}」${item.name}`,
|
||||
description: art(join(__dirname, 'templates/description.art'), {
|
||||
icon: item.icon,
|
||||
originalPrice: item.original_price,
|
||||
price: item.price,
|
||||
kind: item.kind,
|
||||
description: item.description.replaceAll('\n', '<br>'),
|
||||
}),
|
||||
pubDate: parseDate(item.updated_at, 'X'),
|
||||
link: new URL(`/app/${item.uuid}`, baseUrl).href,
|
||||
category: item.primary_genre_name,
|
||||
}));
|
||||
|
||||
ctx.state.data = {
|
||||
title: '最新限免 / 促销应用',
|
||||
link: baseUrl,
|
||||
description: 'GoFans:最新限免 / 促销应用',
|
||||
item: items,
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = {
|
||||
'/:kind?': ['HenryQW'],
|
||||
};
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
module.exports = {
|
||||
'gofans.cn': {
|
||||
_name: 'GoFans',
|
||||
'.': [
|
||||
{
|
||||
title: '最新限免 / 促销应用',
|
||||
docs: 'https://docs.rsshub.app/program-update#gofans',
|
||||
source: ['/limited/:kind?', '/'],
|
||||
target: (params) => `/gofans${(params.kind && (params.kind === 'macos' ? '/macos' : '/ios')) || ''}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = (router) => {
|
||||
router.get('/:kind?', require('./index'));
|
||||
};
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<img src="{{ icon }}">
|
||||
<br>
|
||||
原价:¥{{ originalPrice }} -> 现价:¥{{ price }}
|
||||
<br>
|
||||
平台:{{ kind === 1 ? 'macOS' : 'iOS' }}
|
||||
<br/>
|
||||
{{@ description }}
|
||||
|
|
@ -40,6 +40,7 @@ module.exports = {
|
|||
'/mof/bond/:category?': ['la3rence'],
|
||||
'/mofcom/article/:suffix+': ['LogicJake'],
|
||||
'/moj/aac/news/:type?': ['TonyRL'],
|
||||
'/moj/lfyjzj': ['la3rence'],
|
||||
'/mot/:category?': ['nczitzk'],
|
||||
'/ndrc/fggz/:category?': ['nczitzk'],
|
||||
'/ndrc/xwdt/:category?': ['nczitzk'],
|
||||
|
|
@ -57,7 +58,7 @@ module.exports = {
|
|||
'/pbc/tradeAnnouncement': ['nczitzk'],
|
||||
'/pbc/zcyj': ['Fatpandac'],
|
||||
'/samr/xgzlyhd/:category?/:department?': ['nczitzk'],
|
||||
'/safe/bussiness/:site?': ['nczitzk'],
|
||||
'/safe/business/:site?': ['nczitzk'],
|
||||
'/safe/complaint/:site?': ['nczitzk'],
|
||||
'/sasac/:path+': ['TonyRL'],
|
||||
'/stats/:path+': ['bigfei', 'nczitzk'],
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
const got = require('@/utils/got');
|
||||
const cheerio = require('cheerio');
|
||||
const timezone = require('@/utils/timezone');
|
||||
const { parseDate } = require('@/utils/parse-date');
|
||||
|
||||
const DOMAIN = 'www.moj.gov.cn';
|
||||
|
||||
module.exports = async (ctx) => {
|
||||
const rootUrl = `https://${DOMAIN}`;
|
||||
const currentUrl = `${rootUrl}/lfyjzj/lflfyjzj/index.html`;
|
||||
const { data: response } = await got(currentUrl);
|
||||
const $ = cheerio.load(response);
|
||||
const siteName = $('title:first').text();
|
||||
const theme = $('div.list_title').text();
|
||||
const description = `${siteName} - ${theme}`;
|
||||
const icon = new URL('/images/sfbgw_favicon.ico', rootUrl).href;
|
||||
const indexes = $('ul.newsMsgList_zzy li')
|
||||
.toArray()
|
||||
.map((li) => {
|
||||
const a = $(li).find('a');
|
||||
const pubDate = $(li).find('div.rightData').text();
|
||||
const href = a.prop('href');
|
||||
const link = href.startsWith('http') ? href : new URL(href, currentUrl).href;
|
||||
return {
|
||||
title: a.text(),
|
||||
link,
|
||||
pubDate: timezone(parseDate(pubDate), +8),
|
||||
};
|
||||
});
|
||||
|
||||
const items = await Promise.all(
|
||||
indexes.map((item) =>
|
||||
ctx.cache.tryGet(`gov:mof:${item.link}`, async () => {
|
||||
const { data: detailResponse } = await got(item.link);
|
||||
const content = cheerio.load(detailResponse);
|
||||
item.description = content('div.TRS_Editor').html();
|
||||
item.author = content('div.sT_left span:first').text().split(':')[1];
|
||||
const pubDate = content('div.sT_left span:last').text().split(':')[1];
|
||||
item.pubDate = pubDate ? timezone(parseDate(pubDate), +8) : item.pubDate;
|
||||
return item;
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
ctx.state.data = {
|
||||
item: items,
|
||||
title: theme,
|
||||
link: currentUrl,
|
||||
description,
|
||||
author: siteName,
|
||||
icon,
|
||||
};
|
||||
};
|
||||
|
|
@ -1273,6 +1273,17 @@ module.exports = {
|
|||
},
|
||||
],
|
||||
},
|
||||
'moj.gov.cn': {
|
||||
_name: '中华人民共和国司法部',
|
||||
www: [
|
||||
{
|
||||
title: '立法意见征集',
|
||||
docs: 'https://docs.rsshub.app/routes/government#zhong-hua-ren-min-gong-he-guo-si-fa-bu',
|
||||
source: ['/lfyjzj/lflfyjzj/*', '/pub/sfbgw/lfyjzj/lflfyjzj/*'],
|
||||
target: '/gov/moj/lfyjzj',
|
||||
},
|
||||
],
|
||||
},
|
||||
'moj.gov.tw': {
|
||||
_name: '台灣法務部廉政署',
|
||||
'www.aac': [
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ module.exports = function (router) {
|
|||
router.get('/mof/bond/:category?', require('./mof/bond'));
|
||||
router.get('/mofcom/article/:suffix+', require('./mofcom/article'));
|
||||
router.get('/moj/aac/news/:type?', require('./moj/aac/news'));
|
||||
router.get('/moj/lfyjzj', require('./moj/lfyjzj'));
|
||||
router.get('/ndrc/fggz/:category*', require('./ndrc/fggz'));
|
||||
router.get('/ndrc/xwdt/:category*', require('./ndrc/xwdt'));
|
||||
router.get('/nea/sjzz/ghs', require('./nea/ghs'));
|
||||
|
|
|
|||
|
|
@ -44,13 +44,13 @@ const processZxfkItems = async (site = 'beijing', category = 'ywzx', limit = '3'
|
|||
|
||||
return {
|
||||
title: `${message.author}: ${message.content}`,
|
||||
link: new URL(item.find('.title').prop('href'), rootUrl).href,
|
||||
link: currentUrl,
|
||||
description: art(path.join(__dirname, 'templates/message.art'), {
|
||||
message,
|
||||
reply,
|
||||
}),
|
||||
author: `${message.author}/${reply.author}`,
|
||||
guid: item.find('.id').text(),
|
||||
guid: `${currentUrl}#${message.author}(${message.date})/${reply.author}(${reply.date})`,
|
||||
pubDate: parseDate(message.date),
|
||||
updated: parseDate(reply.date),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
// 域名
|
||||
const HOST = 'https://bbs-api-os.hoyolab.com';
|
||||
// 活动列接口
|
||||
const EVENT_LIST = '/community/community_contribution/wapi/event/list';
|
||||
// 活动详情接口
|
||||
const POST_FULL = '/community/post/wapi/getPostFull';
|
||||
// 公告和资讯接口
|
||||
const NEW_LIST = '/community/post/wapi/getNewsList';
|
||||
|
||||
const ICON = 'https://img-os-static.hoyolab.com/favicon.ico';
|
||||
// 源网站链接
|
||||
const LINK = 'https://www.hoyolab.com';
|
||||
|
||||
// 部分图片使用了禁止公开访问的域名
|
||||
const PRIVATE_IMG = '<img src="https://hoyolab-upload-private.hoyolab.com/upload';
|
||||
// 使用以下域名可以替换
|
||||
const PUBLIC_IMG = '<img src="https://upload-os-bbs.hoyolab.com/upload';
|
||||
|
||||
// 游戏id
|
||||
const GIDS_MAP = {
|
||||
1: 'Honkai Impact 3rd',
|
||||
2: '原神',
|
||||
4: '未定事件簿',
|
||||
5: 'HoYoLAB',
|
||||
6: '崩坏:星穹铁道',
|
||||
8: '绝区零',
|
||||
};
|
||||
|
||||
// 公告类型
|
||||
const TYPE_MAP = {
|
||||
1: '公告',
|
||||
2: '活动',
|
||||
3: '资讯',
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
PRIVATE_IMG,
|
||||
PUBLIC_IMG,
|
||||
LINK,
|
||||
ICON,
|
||||
POST_FULL,
|
||||
HOST,
|
||||
EVENT_LIST,
|
||||
NEW_LIST,
|
||||
GIDS_MAP,
|
||||
TYPE_MAP,
|
||||
};
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = {
|
||||
'/news/:language/:gids/:type': ['ZenoTian'],
|
||||
};
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
const got = require('@/utils/got');
|
||||
const logger = require('@/utils/logger');
|
||||
const { parseDate } = require('@/utils/parse-date');
|
||||
const { HOST, NEW_LIST, TYPE_MAP, POST_FULL, GIDS_MAP, LINK, ICON, PUBLIC_IMG, PRIVATE_IMG } = require('./constant');
|
||||
|
||||
const getEventList = async ({ type, gids, size, language }) => {
|
||||
const query = new URLSearchParams({
|
||||
type,
|
||||
gids,
|
||||
page_size: size,
|
||||
}).toString();
|
||||
const url = `${HOST}${NEW_LIST}?${query}`;
|
||||
const res = await got({
|
||||
method: 'get',
|
||||
url,
|
||||
headers: {
|
||||
'X-Rpc-Language': language,
|
||||
},
|
||||
}).json();
|
||||
const list = res?.data?.list || [];
|
||||
return list;
|
||||
};
|
||||
|
||||
const replaceImgDomain = (content) => content.replaceAll(PRIVATE_IMG, PUBLIC_IMG);
|
||||
|
||||
const getPostContent = (ctx, list, { type, language }) =>
|
||||
Promise.all(
|
||||
list.map(async (row) => {
|
||||
const post = row.post;
|
||||
const post_id = post.post_id;
|
||||
const query = new URLSearchParams({
|
||||
post_id,
|
||||
language, // language为了区分缓存,对接口并无意义
|
||||
}).toString();
|
||||
const url = `${HOST}${POST_FULL}?${query}`;
|
||||
return await ctx.cache.tryGet(url, async () => {
|
||||
const res = await got({
|
||||
method: 'get',
|
||||
url,
|
||||
headers: {
|
||||
'X-Rpc-Language': language,
|
||||
},
|
||||
}).json();
|
||||
const author = res?.data?.post?.user?.nickname || '';
|
||||
let content = res?.data?.post?.post?.content || '';
|
||||
if (content === language || !content) {
|
||||
content = post.content;
|
||||
}
|
||||
const description = replaceImgDomain(content);
|
||||
return {
|
||||
// 文章标题
|
||||
title: post.subject,
|
||||
// 文章链接
|
||||
link: `${LINK}/article/${post_id}`,
|
||||
// 文章正文
|
||||
description,
|
||||
// 文章发布日期
|
||||
pubDate: parseDate(post.created_at * 1000),
|
||||
// 如果有的话,文章分类
|
||||
category: `${GIDS_MAP[post.game_id]}-${TYPE_MAP[type]}`,
|
||||
author,
|
||||
};
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
module.exports = async (ctx) => {
|
||||
try {
|
||||
const { type, gids, language } = ctx.params;
|
||||
const params = {
|
||||
type,
|
||||
gids,
|
||||
language,
|
||||
size: parseInt(ctx.query?.limit) || 15,
|
||||
};
|
||||
const list = await getEventList(params);
|
||||
const items = await getPostContent(ctx, list, params);
|
||||
ctx.state.data = {
|
||||
title: `HoYoLAB-${GIDS_MAP[gids]}-${TYPE_MAP[type]}`,
|
||||
link: LINK,
|
||||
item: items,
|
||||
image: ICON,
|
||||
icon: ICON,
|
||||
logo: ICON,
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
module.exports = {
|
||||
'hoyolab.com': {
|
||||
_name: 'HoYoLAB',
|
||||
'.': [
|
||||
{
|
||||
title: '活动公告资讯',
|
||||
docs: 'https://docs.rsshub.app/routes/game#hoyolab-news',
|
||||
source: ['/', '/circles/:gid/:unknow/official'],
|
||||
target: (params, url) => {
|
||||
const typeMap = {
|
||||
notices: '1',
|
||||
events: '2',
|
||||
news: '3',
|
||||
};
|
||||
const query = new URL(url).searchParams;
|
||||
const type = typeMap[query.get('page_sort')] || '2';
|
||||
return `/hoyolab/news/zh-cn/${params?.gid || 2}/${type}`;
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = (router) => {
|
||||
router.get('/news/:language/:gids/:type', require('./news'));
|
||||
};
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
const got = require('@/utils/got');
|
||||
const cheerio = require('cheerio');
|
||||
const { parseDate } = require('@/utils/parse-date');
|
||||
|
||||
module.exports = async (ctx) => {
|
||||
const { body: response } = await got('https://huggingface.co/blog/zh');
|
||||
const $ = cheerio.load(response);
|
||||
|
||||
/** @type {Array<{blog: {local: string, title: string, author: string, thumbnail: string, date: string, tags: Array<string>}, blogUrl: string, lang: 'zh', link: string}>} */
|
||||
const papers = $('div[data-target="BlogThumbnail"]')
|
||||
.toArray()
|
||||
.map((item) => {
|
||||
const props = $(item).data('props');
|
||||
const link = $(item).find('a').attr('href');
|
||||
return {
|
||||
...props,
|
||||
link,
|
||||
};
|
||||
});
|
||||
|
||||
const items = papers.map((item) => ({
|
||||
title: item.blog.title,
|
||||
link: `https://huggingface.co${item.link}`,
|
||||
category: item.blog.tags,
|
||||
pubDate: parseDate(item.blog.date),
|
||||
author: item.blog.author,
|
||||
}));
|
||||
|
||||
ctx.state.data = {
|
||||
title: 'Huggingface 中文博客',
|
||||
link: 'https://huggingface.co/blog/zh',
|
||||
item: items,
|
||||
};
|
||||
};
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
module.exports = {
|
||||
'/blog-zh': ['zcf0508'],
|
||||
'/daily-papers': ['zeyugao'],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,6 +8,12 @@ module.exports = {
|
|||
source: ['/papers', '/'],
|
||||
target: '/huggingface/daily-papers',
|
||||
},
|
||||
{
|
||||
title: '中文博客',
|
||||
docs: 'https://docs.rsshub.app/routes/programming#huggingface',
|
||||
source: ['/blog/zh', '/'],
|
||||
target: '/huggingface/blog-zh',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
module.exports = (router) => {
|
||||
router.get('/blog-zh', require('./blog-zh'));
|
||||
router.get('/daily-papers', require('./daily-papers'));
|
||||
};
|
||||
|
|
|
|||
|
|
@ -15,62 +15,69 @@ module.exports = async (ctx) => {
|
|||
|
||||
const $ = cheerio.load(response);
|
||||
|
||||
let items = $('a.logStore, a[aid], div.title a, div.news-header a')
|
||||
let items = $('a')
|
||||
.toArray()
|
||||
.map((item) => {
|
||||
.reduce((acc, item) => {
|
||||
item = $(item);
|
||||
|
||||
return {
|
||||
title: item.text(),
|
||||
link: item.prop('href'),
|
||||
};
|
||||
})
|
||||
.filter((item) => item.link && !item.link.startsWith(new URL('special', rootUrl).href));
|
||||
const href = item.prop('href');
|
||||
const link = href ? (href.startsWith('/') ? new URL(href, rootUrl).href : href) : undefined;
|
||||
|
||||
if (link && /\/(article|video)\/\w+\.html/.test(link)) {
|
||||
acc[link] = {
|
||||
title: item.text(),
|
||||
link,
|
||||
};
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
items = await Promise.all(
|
||||
items.slice(0, limit).map((item) =>
|
||||
ctx.cache.tryGet(item.link, async () => {
|
||||
const { data: detailResponse } = await got(item.link);
|
||||
Object.values(items)
|
||||
.slice(0, limit)
|
||||
.map((item) =>
|
||||
ctx.cache.tryGet(item.link, async () => {
|
||||
const { data: detailResponse } = await got(item.link);
|
||||
|
||||
const content = cheerio.load(detailResponse);
|
||||
const image = content('div.article-img img').first();
|
||||
const video = content('#video-player').first();
|
||||
const content = cheerio.load(detailResponse);
|
||||
const image = content('div.article-img img').first();
|
||||
const video = content('#video-player').first();
|
||||
|
||||
item.title = content('div.article-header h1').text();
|
||||
item.description = art(path.join(__dirname, 'templates/description.art'), {
|
||||
image: image
|
||||
? {
|
||||
src: image.prop('src'),
|
||||
alt: image.next('p').text() || item.title,
|
||||
}
|
||||
: undefined,
|
||||
video: video
|
||||
? {
|
||||
src: video.prop('data-url'),
|
||||
poster: video.prop('data-poster'),
|
||||
width: video.prop('width'),
|
||||
height: video.prop('height'),
|
||||
}
|
||||
: undefined,
|
||||
intro: content('div.article-header p').text(),
|
||||
description: content('div.article-content').html(),
|
||||
});
|
||||
item.author = content('span.author')
|
||||
.first()
|
||||
.find('a')
|
||||
.toArray()
|
||||
.map((a) => content(a).text())
|
||||
.join('/');
|
||||
item.category = content('meta.meta-container a')
|
||||
.toArray()
|
||||
.map((c) => content(c).text());
|
||||
item.pubDate = parseDate(content('div.article-info span[data-article-publish-time]').prop('data-article-publish-time'), 'X');
|
||||
item.upvotes = content('span.opt-praise__count').text() ? parseInt(content('span.opt-praise__count').text(), 10) : 0;
|
||||
item.comments = content('span.opt-comment__count').text() ? parseInt(content('span.opt-comment__count').text(), 10) : 0;
|
||||
item.title = content('div.article-header h1').text();
|
||||
item.description = art(path.join(__dirname, 'templates/description.art'), {
|
||||
image: image
|
||||
? {
|
||||
src: image.prop('src'),
|
||||
alt: image.next('p').text() || item.title,
|
||||
}
|
||||
: undefined,
|
||||
video: video
|
||||
? {
|
||||
src: video.prop('data-url'),
|
||||
poster: video.prop('data-poster'),
|
||||
width: video.prop('width'),
|
||||
height: video.prop('height'),
|
||||
}
|
||||
: undefined,
|
||||
intro: content('div.article-header p').text(),
|
||||
description: content('div.article-content').html(),
|
||||
});
|
||||
item.author = content('span.author')
|
||||
.first()
|
||||
.find('a')
|
||||
.toArray()
|
||||
.map((a) => content(a).text())
|
||||
.join('/');
|
||||
item.category = content('meta.meta-container a')
|
||||
.toArray()
|
||||
.map((c) => content(c).text());
|
||||
item.pubDate = parseDate(content('div.article-info span[data-article-publish-time]').prop('data-article-publish-time'), 'X');
|
||||
item.upvotes = content('span.opt-praise__count').text() ? parseInt(content('span.opt-praise__count').text(), 10) : 0;
|
||||
item.comments = content('span.opt-comment__count').text() ? parseInt(content('span.opt-comment__count').text(), 10) : 0;
|
||||
|
||||
return item;
|
||||
})
|
||||
)
|
||||
return item;
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
const title = $('title').text();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
const got = require('@/utils/got');
|
||||
const getRssItem = require('./utils');
|
||||
|
||||
const rootApiUrl = 'https://www.lifeweek.com.cn/api/userWebFollow/getFollowTagContentList?type=3&sort=2&tagId';
|
||||
const rootUrl = 'https://www.lifeweek.com.cn/column';
|
||||
const articleRootUrl = 'https://www.lifeweek.com.cn/article';
|
||||
|
||||
module.exports = async (ctx) => {
|
||||
const channel = ctx.params.id;
|
||||
const url = `${rootApiUrl}=${channel}`;
|
||||
const { data } = await got(url);
|
||||
const result = data.model.articleResponseList;
|
||||
const items = await Promise.all(
|
||||
result.map((item) => {
|
||||
const articleLink = `${articleRootUrl}/${item.id}`;
|
||||
return ctx.cache.tryGet(articleLink, () => getRssItem(item, articleLink));
|
||||
})
|
||||
);
|
||||
|
||||
ctx.state.data = {
|
||||
title: data.model.tagName,
|
||||
link: `${rootUrl}/${channel}`,
|
||||
item: items,
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
module.exports = {
|
||||
'/channel/:channel': ['changren-wcr'],
|
||||
'/tag/:tag': ['changren-wcr'],
|
||||
};
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
module.exports = {
|
||||
'lifeweek.com.cn': {
|
||||
_name: '三联生活周刊',
|
||||
'.': [
|
||||
{
|
||||
title: '栏目',
|
||||
docs: 'https://docs.rsshub.app/routes/traditional-media#san-lian-sheng-huo-zhou-kan',
|
||||
source: ['/column/:channel'],
|
||||
target: '/lifeweek/channel/:channel',
|
||||
},
|
||||
{
|
||||
title: '标签',
|
||||
docs: 'https://docs.rsshub.app/routes/traditional-media#san-lian-sheng-huo-zhou-kan',
|
||||
source: ['/articleList/:tag'],
|
||||
target: '/lifeweek/tag/:tag',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
module.exports = function (router) {
|
||||
router.get('/channel/:id', require('./channel'));
|
||||
router.get('/tag/:id', require('./tag'));
|
||||
};
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
const got = require('@/utils/got');
|
||||
const getRssItem = require('./utils');
|
||||
const rootApiUrl = 'https://www.lifeweek.com.cn/api/userWebFollow/getFollowTagContentList?type=4&sort=2&tagId';
|
||||
const rootUrl = 'https://www.lifeweek.com.cn/articleList';
|
||||
const articleRootUrl = 'https://www.lifeweek.com.cn/article';
|
||||
|
||||
module.exports = async (ctx) => {
|
||||
const tag = ctx.params.id;
|
||||
const url = `${rootApiUrl}=${tag}`;
|
||||
const { data } = await got(url);
|
||||
const result = data.model.articleResponseList;
|
||||
const items = await Promise.all(
|
||||
result.map((item) => {
|
||||
const articleLink = `${articleRootUrl}/${item.id}`;
|
||||
return ctx.cache.tryGet(articleLink, () => getRssItem(item, articleLink));
|
||||
})
|
||||
);
|
||||
|
||||
ctx.state.data = {
|
||||
title: data.model.tagName,
|
||||
link: `${rootUrl}/${tag}`,
|
||||
item: items,
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
const got = require('@/utils/got');
|
||||
const timezone = require('@/utils/timezone');
|
||||
const { parseDate } = require('@/utils/parse-date');
|
||||
const articleApiRootUrl = 'https://www.lifeweek.com.cn/api/article';
|
||||
|
||||
async function getRssItem(item, articleLink) {
|
||||
const articleApiLink = `${articleApiRootUrl}/${item.id}`;
|
||||
const { data } = await got(articleApiLink);
|
||||
const time = timezone(parseDate(item.pubTime), +8);
|
||||
return {
|
||||
title: item.title,
|
||||
description: data.model.content,
|
||||
link: articleLink,
|
||||
pubDate: time,
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = getRssItem;
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
const got = require('@/utils/got');
|
||||
const cheerio = require('cheerio');
|
||||
const { isValidHost } = require('@/utils/valid-host');
|
||||
|
||||
module.exports = async (ctx) => {
|
||||
let region = ctx.params.region ?? 'ukraine';
|
||||
const limit = ctx.query.limit ? parseInt(ctx.query.limit) : 50;
|
||||
if (!isValidHost(region)) {
|
||||
throw Error('Invalid region');
|
||||
}
|
||||
|
||||
let url = `https://${region}.liveuamap.com/`;
|
||||
if (region === undefined) {
|
||||
region = 'Default';
|
||||
url = 'https://liveuamap.com/';
|
||||
}
|
||||
|
||||
const response = await got({
|
||||
method: 'get',
|
||||
url,
|
||||
});
|
||||
const $ = cheerio.load(response.data);
|
||||
|
||||
const items = $('div#feedler > div')
|
||||
.slice(0, limit)
|
||||
.toArray()
|
||||
.map((item) => {
|
||||
item = $(item);
|
||||
return {
|
||||
title: item.find('div.title').text(),
|
||||
description: item.find('div.title').text(),
|
||||
link: item.attr('data-link'),
|
||||
};
|
||||
});
|
||||
|
||||
ctx.state.data = {
|
||||
title: `Liveuamap - ${region}`,
|
||||
link: url,
|
||||
item: items,
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = {
|
||||
'/:region?': ['CoderSherlock'],
|
||||
};
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
module.exports = {
|
||||
'liveuamap.com': {
|
||||
_name: 'Live Universal Awareness Map',
|
||||
'.': [
|
||||
{
|
||||
title: 'Region',
|
||||
docs: 'https://docs.rsshub.app/routes/new-media#live-universal-awareness-map',
|
||||
source: ['/:region*'],
|
||||
target: '/liveuamap/:region',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = (router) => {
|
||||
router.get('/:region?', require('./'));
|
||||
};
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = {
|
||||
'/': ['artefaritaKuniklo'],
|
||||
};
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
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 baseUrl = 'https://medieval-china.club';
|
||||
const { data: response } = await got(baseUrl);
|
||||
const $ = cheerio.load(response);
|
||||
const posts = JSON.parse(
|
||||
$('script:contains("window.localPosts")')
|
||||
.text()
|
||||
.match(/window\.localPosts = JSON\.parse\('(.*)'\);/)[1]
|
||||
)
|
||||
.slice(0, ctx.query.limit ? parseInt(ctx.query.limit) : 10)
|
||||
.map((item) => ({
|
||||
title: item.title,
|
||||
link: `${baseUrl}${item.path}`,
|
||||
pubDate: timezone(parseDate(item.date), +8),
|
||||
}));
|
||||
const items = await Promise.all(
|
||||
posts.map((item) =>
|
||||
ctx.cache.tryGet(item.link, async () => {
|
||||
const { data: response } = await got(item.link);
|
||||
const $ = cheerio.load(response);
|
||||
const imgSrc = $('img').attr('data-original');
|
||||
$('img').attr('src', `${baseUrl}${imgSrc}`);
|
||||
$('.head-mask').remove();
|
||||
$('div.lover-box').remove();
|
||||
item.description = $('article').first().html();
|
||||
return item;
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
ctx.state.data = {
|
||||
title: '中国的中古',
|
||||
link: baseUrl,
|
||||
item: items,
|
||||
image: 'https://medieval-china.club/images/icons/favicon-144x144.png',
|
||||
description:
|
||||
'世界那么大,你无法去到每一个地方,感受每一处风景;时间那么长,那些逝去的人你也终将无法与之谋面。而通过古人之文字,今人之分享,你可以领略以前风光之奇绝瑰玮,感受逝人之人情冷暖。中古就是这么一个地方,大家来自全球各地,不同时区,不同性别,不同身份,不同职业,但是大家都被中古的绚烂华章聚集在一起,哀其所哀,乐其所乐。这是一个虚拟的世界,但是我们仿佛跨越千里而来,谈一场绝世爱恋,今夕何夕!仅以此网站献给中古club的每一位成员,契阔谈宴,西园不芜!',
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
module.exports = {
|
||||
'medieval-china.club': {
|
||||
_name: '中国的中古',
|
||||
'.': [
|
||||
{
|
||||
title: '首页',
|
||||
docs: 'https://docs.rsshub.app/routes/reading#zhong-guo-de-zhong-shou-ye',
|
||||
source: '/',
|
||||
target: '/medieval-china',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = function (router) {
|
||||
router.get('/', require('./post'));
|
||||
};
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
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 iconv = require('iconv-lite');
|
||||
|
||||
module.exports = async (ctx) => {
|
||||
const { id } = ctx.params;
|
||||
const limit = ctx.query.limit ? parseInt(ctx.query.limit, 10) : 50;
|
||||
|
||||
const ids = id?.split(/\//) ?? [];
|
||||
const titles = [];
|
||||
|
||||
const rootUrl = 'http://cmdp.ncc-cma.net';
|
||||
const currentUrl = new URL('cn/index.htm', rootUrl).href;
|
||||
|
||||
const { data: response } = await got(currentUrl, {
|
||||
responseType: 'buffer',
|
||||
});
|
||||
|
||||
const $ = cheerio.load(iconv.decode(response, 'gbk'));
|
||||
|
||||
const author = '国家气候中心';
|
||||
|
||||
const items = $('ul.img-con-new-con li img[id]')
|
||||
.toArray()
|
||||
.filter((item) => ids.length === 0 || ids.includes($(item).prop('id')))
|
||||
.slice(0, limit)
|
||||
.map((item) => {
|
||||
item = $(item);
|
||||
|
||||
const id = item.prop('id');
|
||||
const title = $(`li[data-id="${id}"]`).text() || undefined;
|
||||
const src = new URL(item.prop('src'), currentUrl).href;
|
||||
const date =
|
||||
src
|
||||
.match(/_(\d{4})(\d{2})(\d{2})_/)
|
||||
?.slice(1, 4)
|
||||
.join('-') ?? new Date().toISOString().slice(0, 10);
|
||||
|
||||
if (ids.length !== 0 && title) {
|
||||
titles.push(title);
|
||||
}
|
||||
|
||||
return {
|
||||
title: `${title} ${date}`,
|
||||
link: currentUrl,
|
||||
description: art(path.join(__dirname, 'templates/description.art'), {
|
||||
image: {
|
||||
src,
|
||||
alt: `${title} ${date}`,
|
||||
},
|
||||
}),
|
||||
author,
|
||||
category: [title],
|
||||
guid: `ncc-cma#${id}#${date}`,
|
||||
pubDate: parseDate(date),
|
||||
enclosure_url: src,
|
||||
enclosure_type: `image/${src.split(/\./).pop()}`,
|
||||
};
|
||||
});
|
||||
|
||||
const subtitle = $('h1').last().text();
|
||||
const image = $('img.logo').prop('src');
|
||||
const icon = new URL('favicon.ico', rootUrl).href;
|
||||
|
||||
ctx.state.data = {
|
||||
item: items,
|
||||
title: `${author} - ${subtitle}${titles.length === 0 ? '' : ` - ${titles.join('|')}`}`,
|
||||
link: currentUrl,
|
||||
description: $('title').text(),
|
||||
language: 'zh',
|
||||
image,
|
||||
icon,
|
||||
logo: icon,
|
||||
subtitle,
|
||||
author,
|
||||
allowEmpty: true,
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = {
|
||||
'/cmdp/image/:id?': ['nczitzk'],
|
||||
};
|
||||
|
|
@ -0,0 +1,229 @@
|
|||
module.exports = {
|
||||
'ncc-cma.net': {
|
||||
_name: '国家气候中心',
|
||||
cmdp: [
|
||||
{
|
||||
title: '中国气温 - 日平均气温距平',
|
||||
docs: 'https://docs.rsshub.app/routes/forecast#guo-jia-qi-hou-zhong-xin-zui-xin-jian-ce',
|
||||
source: ['/cn/index.htm'],
|
||||
target: '/ncc-cma/cmdp/image/RPJQWQYZ',
|
||||
},
|
||||
{
|
||||
title: '中国气温 - 近5天平均气温距平',
|
||||
docs: 'https://docs.rsshub.app/routes/forecast#guo-jia-qi-hou-zhong-xin-zui-xin-jian-ce',
|
||||
source: ['/cn/index.htm'],
|
||||
target: '/ncc-cma/cmdp/image/ZJ5TPJQWJP',
|
||||
},
|
||||
{
|
||||
title: '中国气温 - 近10天平均气温距平',
|
||||
docs: 'https://docs.rsshub.app/routes/forecast#guo-jia-qi-hou-zhong-xin-zui-xin-jian-ce',
|
||||
source: ['/cn/index.htm'],
|
||||
target: '/ncc-cma/cmdp/image/ZJ10TQWJP',
|
||||
},
|
||||
{
|
||||
title: '中国气温 - 近20天平均气温距平',
|
||||
docs: 'https://docs.rsshub.app/routes/forecast#guo-jia-qi-hou-zhong-xin-zui-xin-jian-ce',
|
||||
source: ['/cn/index.htm'],
|
||||
target: '/ncc-cma/cmdp/image/ZJ20TQWJP',
|
||||
},
|
||||
{
|
||||
title: '中国气温 - 近30天平均气温距平',
|
||||
docs: 'https://docs.rsshub.app/routes/forecast#guo-jia-qi-hou-zhong-xin-zui-xin-jian-ce',
|
||||
source: ['/cn/index.htm'],
|
||||
target: '/ncc-cma/cmdp/image/ZJ30TQWJP',
|
||||
},
|
||||
{
|
||||
title: '中国气温 - 本月以来气温距平',
|
||||
docs: 'https://docs.rsshub.app/routes/forecast#guo-jia-qi-hou-zhong-xin-zui-xin-jian-ce',
|
||||
source: ['/cn/index.htm'],
|
||||
target: '/ncc-cma/cmdp/image/BYYLQWJP',
|
||||
},
|
||||
{
|
||||
title: '中国气温 - 本季以来气温距平',
|
||||
docs: 'https://docs.rsshub.app/routes/forecast#guo-jia-qi-hou-zhong-xin-zui-xin-jian-ce',
|
||||
source: ['/cn/index.htm'],
|
||||
target: '/ncc-cma/cmdp/image/BJYLQWJP',
|
||||
},
|
||||
{
|
||||
title: '中国气温 - 本年以来气温距平',
|
||||
docs: 'https://docs.rsshub.app/routes/forecast#guo-jia-qi-hou-zhong-xin-zui-xin-jian-ce',
|
||||
source: ['/cn/index.htm'],
|
||||
target: '/ncc-cma/cmdp/image/BNYLQWJP',
|
||||
},
|
||||
{
|
||||
title: '中国降水 - 日降水量分布',
|
||||
docs: 'https://docs.rsshub.app/routes/forecast#guo-jia-qi-hou-zhong-xin-zui-xin-jian-ce',
|
||||
source: ['/cn/index.htm'],
|
||||
target: '/ncc-cma/cmdp/image/QGRJSLFBT0808S',
|
||||
},
|
||||
{
|
||||
title: '中国降水 - 近5天降水量',
|
||||
docs: 'https://docs.rsshub.app/routes/forecast#guo-jia-qi-hou-zhong-xin-zui-xin-jian-ce',
|
||||
source: ['/cn/index.htm'],
|
||||
target: '/ncc-cma/cmdp/image/ZJ5TJSLFBT',
|
||||
},
|
||||
{
|
||||
title: '中国降水 - 近10天降水量',
|
||||
docs: 'https://docs.rsshub.app/routes/forecast#guo-jia-qi-hou-zhong-xin-zui-xin-jian-ce',
|
||||
source: ['/cn/index.htm'],
|
||||
target: '/ncc-cma/cmdp/image/ZJ10TJSL',
|
||||
},
|
||||
{
|
||||
title: '中国降水 - 近20天降水量',
|
||||
docs: 'https://docs.rsshub.app/routes/forecast#guo-jia-qi-hou-zhong-xin-zui-xin-jian-ce',
|
||||
source: ['/cn/index.htm'],
|
||||
target: '/ncc-cma/cmdp/image/ZJ20TJSL',
|
||||
},
|
||||
{
|
||||
title: '中国降水 - 近30天降水量',
|
||||
docs: 'https://docs.rsshub.app/routes/forecast#guo-jia-qi-hou-zhong-xin-zui-xin-jian-ce',
|
||||
source: ['/cn/index.htm'],
|
||||
target: '/ncc-cma/cmdp/image/ZJ30TJSL',
|
||||
},
|
||||
{
|
||||
title: '中国降水 - 本月以来降水量',
|
||||
docs: 'https://docs.rsshub.app/routes/forecast#guo-jia-qi-hou-zhong-xin-zui-xin-jian-ce',
|
||||
source: ['/cn/index.htm'],
|
||||
target: '/ncc-cma/cmdp/image/BYYLJSL',
|
||||
},
|
||||
{
|
||||
title: '中国降水 - 本季以来降水量',
|
||||
docs: 'https://docs.rsshub.app/routes/forecast#guo-jia-qi-hou-zhong-xin-zui-xin-jian-ce',
|
||||
source: ['/cn/index.htm'],
|
||||
target: '/ncc-cma/cmdp/image/BJYLJSL',
|
||||
},
|
||||
{
|
||||
title: '中国降水 - 近10天降水量距平百分率',
|
||||
docs: 'https://docs.rsshub.app/routes/forecast#guo-jia-qi-hou-zhong-xin-zui-xin-jian-ce',
|
||||
source: ['/cn/index.htm'],
|
||||
target: '/ncc-cma/cmdp/image/ZJ10TJSLJP',
|
||||
},
|
||||
{
|
||||
title: '中国降水 - 近20天降水量距平百分率',
|
||||
docs: 'https://docs.rsshub.app/routes/forecast#guo-jia-qi-hou-zhong-xin-zui-xin-jian-ce',
|
||||
source: ['/cn/index.htm'],
|
||||
target: '/ncc-cma/cmdp/image/ZJ20TJSLJP',
|
||||
},
|
||||
{
|
||||
title: '中国降水 - 近30天降水量距平百分率',
|
||||
docs: 'https://docs.rsshub.app/routes/forecast#guo-jia-qi-hou-zhong-xin-zui-xin-jian-ce',
|
||||
source: ['/cn/index.htm'],
|
||||
target: '/ncc-cma/cmdp/image/ZJ30TJSLJP',
|
||||
},
|
||||
{
|
||||
title: '中国降水 - 本月以来降水量距平百分率',
|
||||
docs: 'https://docs.rsshub.app/routes/forecast#guo-jia-qi-hou-zhong-xin-zui-xin-jian-ce',
|
||||
source: ['/cn/index.htm'],
|
||||
target: '/ncc-cma/cmdp/image/BYYLJSLJPZYQHZ',
|
||||
},
|
||||
{
|
||||
title: '中国降水 - 本季以来降水量距平百分率',
|
||||
docs: 'https://docs.rsshub.app/routes/forecast#guo-jia-qi-hou-zhong-xin-zui-xin-jian-ce',
|
||||
source: ['/cn/index.htm'],
|
||||
target: '/ncc-cma/cmdp/image/BJYLJSLJPZJQHZ',
|
||||
},
|
||||
{
|
||||
title: '中国降水 - 本年以来降水量距平百分率',
|
||||
docs: 'https://docs.rsshub.app/routes/forecast#guo-jia-qi-hou-zhong-xin-zui-xin-jian-ce',
|
||||
source: ['/cn/index.htm'],
|
||||
target: '/ncc-cma/cmdp/image/BNYLJSLJP',
|
||||
},
|
||||
{
|
||||
title: '全球气温 - 气温距平(最近10天)',
|
||||
docs: 'https://docs.rsshub.app/routes/forecast#guo-jia-qi-hou-zhong-xin-zui-xin-jian-ce',
|
||||
source: ['/cn/index.htm'],
|
||||
target: '/ncc-cma/cmdp/image/glbtmeana10_',
|
||||
},
|
||||
{
|
||||
title: '全球气温 - 气温距平(最近20天)',
|
||||
docs: 'https://docs.rsshub.app/routes/forecast#guo-jia-qi-hou-zhong-xin-zui-xin-jian-ce',
|
||||
source: ['/cn/index.htm'],
|
||||
target: '/ncc-cma/cmdp/image/glbtmeana20_',
|
||||
},
|
||||
{
|
||||
title: '全球气温 - 气温距平(最近30天)',
|
||||
docs: 'https://docs.rsshub.app/routes/forecast#guo-jia-qi-hou-zhong-xin-zui-xin-jian-ce',
|
||||
source: ['/cn/index.htm'],
|
||||
target: '/ncc-cma/cmdp/image/glbtmeana30_',
|
||||
},
|
||||
{
|
||||
title: '全球气温 - 气温距平(最近90天)',
|
||||
docs: 'https://docs.rsshub.app/routes/forecast#guo-jia-qi-hou-zhong-xin-zui-xin-jian-ce',
|
||||
source: ['/cn/index.htm'],
|
||||
target: '/ncc-cma/cmdp/image/glbtmeana90_',
|
||||
},
|
||||
{
|
||||
title: '全球气温 - 最低气温距平(最近30天)',
|
||||
docs: 'https://docs.rsshub.app/routes/forecast#guo-jia-qi-hou-zhong-xin-zui-xin-jian-ce',
|
||||
source: ['/cn/index.htm'],
|
||||
target: '/ncc-cma/cmdp/image/glbtmina30_',
|
||||
},
|
||||
{
|
||||
title: '全球气温 - 最低气温距平(最近90天)',
|
||||
docs: 'https://docs.rsshub.app/routes/forecast#guo-jia-qi-hou-zhong-xin-zui-xin-jian-ce',
|
||||
source: ['/cn/index.htm'],
|
||||
target: '/ncc-cma/cmdp/image/glbtmina90_',
|
||||
},
|
||||
{
|
||||
title: '全球气温 - 最高气温距平(最近30天)',
|
||||
docs: 'https://docs.rsshub.app/routes/forecast#guo-jia-qi-hou-zhong-xin-zui-xin-jian-ce',
|
||||
source: ['/cn/index.htm'],
|
||||
target: '/ncc-cma/cmdp/image/glbtmaxa30_',
|
||||
},
|
||||
{
|
||||
title: '全球气温 - 最高气温距平(最近90天)',
|
||||
docs: 'https://docs.rsshub.app/routes/forecast#guo-jia-qi-hou-zhong-xin-zui-xin-jian-ce',
|
||||
source: ['/cn/index.htm'],
|
||||
target: '/ncc-cma/cmdp/image/glbtmaxa90_',
|
||||
},
|
||||
{
|
||||
title: '全球降水 - 降水量(最近10天)',
|
||||
docs: 'https://docs.rsshub.app/routes/forecast#guo-jia-qi-hou-zhong-xin-zui-xin-jian-ce',
|
||||
source: ['/cn/index.htm'],
|
||||
target: '/ncc-cma/cmdp/image/glbrain10_',
|
||||
},
|
||||
{
|
||||
title: '全球降水 - 降水量(最近20天)',
|
||||
docs: 'https://docs.rsshub.app/routes/forecast#guo-jia-qi-hou-zhong-xin-zui-xin-jian-ce',
|
||||
source: ['/cn/index.htm'],
|
||||
target: '/ncc-cma/cmdp/image/glbrain20_',
|
||||
},
|
||||
{
|
||||
title: '全球降水 - 降水量(最近30天)',
|
||||
docs: 'https://docs.rsshub.app/routes/forecast#guo-jia-qi-hou-zhong-xin-zui-xin-jian-ce',
|
||||
source: ['/cn/index.htm'],
|
||||
target: '/ncc-cma/cmdp/image/glbrain30_',
|
||||
},
|
||||
{
|
||||
title: '全球降水 - 降水量(最近90天)',
|
||||
docs: 'https://docs.rsshub.app/routes/forecast#guo-jia-qi-hou-zhong-xin-zui-xin-jian-ce',
|
||||
source: ['/cn/index.htm'],
|
||||
target: '/ncc-cma/cmdp/image/glbrain90_',
|
||||
},
|
||||
{
|
||||
title: '全球降水 - 降水距平百分率(最近10天)',
|
||||
docs: 'https://docs.rsshub.app/routes/forecast#guo-jia-qi-hou-zhong-xin-zui-xin-jian-ce',
|
||||
source: ['/cn/index.htm'],
|
||||
target: '/ncc-cma/cmdp/image/glbraina10_',
|
||||
},
|
||||
{
|
||||
title: '全球降水 - 降水距平百分率(最近20天)',
|
||||
docs: 'https://docs.rsshub.app/routes/forecast#guo-jia-qi-hou-zhong-xin-zui-xin-jian-ce',
|
||||
source: ['/cn/index.htm'],
|
||||
target: '/ncc-cma/cmdp/image/glbraina20_',
|
||||
},
|
||||
{
|
||||
title: '全球降水 - 降水距平百分率(最近30天)',
|
||||
docs: 'https://docs.rsshub.app/routes/forecast#guo-jia-qi-hou-zhong-xin-zui-xin-jian-ce',
|
||||
source: ['/cn/index.htm'],
|
||||
target: '/ncc-cma/cmdp/image/glbraina30_',
|
||||
},
|
||||
{
|
||||
title: '全球降水 - 降水距平百分率(最近90天)',
|
||||
docs: 'https://docs.rsshub.app/routes/forecast#guo-jia-qi-hou-zhong-xin-zui-xin-jian-ce',
|
||||
source: ['/cn/index.htm'],
|
||||
target: '/ncc-cma/cmdp/image/glbraina90_',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = (router) => {
|
||||
router.get('/cmdp/image/:id*', require('./cmdp'));
|
||||
};
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{{ if image?.src }}
|
||||
<figure>
|
||||
<img
|
||||
{{ if image.alt }}
|
||||
alt="{{ image.alt }}"
|
||||
{{ /if }}
|
||||
src="{{ image.src }}">
|
||||
</figure>
|
||||
{{ /if }}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = {
|
||||
'/news': ['Vegann'],
|
||||
};
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
const parser = require('@/utils/rss-parser');
|
||||
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 { parseArticleContent, parseMainImage } = require('./utils');
|
||||
|
||||
module.exports = async (ctx) => {
|
||||
const rssUrl = 'https://wiadomosci.onet.pl/.feed';
|
||||
const feed = await parser.parseURL(rssUrl);
|
||||
const items = await Promise.all(
|
||||
feed.items.map(async (item) => {
|
||||
const { description, author, category } = await ctx.cache.tryGet(item.link, async () => {
|
||||
const { data: response } = await got(item.link, {
|
||||
headers: {
|
||||
referer: 'https://www.onet.pl/', // for some reason onet.pl will redirect to the main page if referer is not set
|
||||
},
|
||||
});
|
||||
|
||||
const $ = cheerio.load(response);
|
||||
const content = parseArticleContent($);
|
||||
|
||||
const mainImage = parseMainImage($);
|
||||
|
||||
const description = art(path.join(__dirname, 'templates/article.art'), {
|
||||
mainImage,
|
||||
lead: $('#lead').text()?.trim(),
|
||||
content: content.html()?.trim(),
|
||||
});
|
||||
|
||||
const author = $('.authorNameWrapper span[itemprop="name"]').text()?.trim();
|
||||
const category = $('span.relatedTopic').text()?.trim();
|
||||
|
||||
return { description, author, category };
|
||||
});
|
||||
return {
|
||||
title: item.title,
|
||||
link: item.link,
|
||||
description,
|
||||
author,
|
||||
category,
|
||||
pubDate: parseDate(item.pubDate),
|
||||
guid: item.id,
|
||||
};
|
||||
})
|
||||
);
|
||||
ctx.state.data = {
|
||||
title: feed.title,
|
||||
link: feed.link,
|
||||
description: feed.title,
|
||||
item: items,
|
||||
language: 'pl',
|
||||
image: 'https://ocdn.eu/wiadomosciucs/static/logo2017/onet2017big_dark.png',
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
module.exports = {
|
||||
'onet.pl': {
|
||||
_name: 'Onet',
|
||||
wiadomosci: [
|
||||
{
|
||||
title: 'News',
|
||||
docs: 'https://docs.rsshub.app/routes/new-media#onet',
|
||||
source: '/',
|
||||
target: '/onet/news',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = (router) => {
|
||||
router.get('/news', require('./news'));
|
||||
};
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
{{if lead }}
|
||||
<p><strong>{{ lead }}</strong></p>
|
||||
{{/if}}
|
||||
{{@ mainImage }}
|
||||
{{@ content }}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<figure>
|
||||
<img src="{{ url }}" alt="{{ alt }}">
|
||||
<figcation>
|
||||
{{if caption }}
|
||||
<cite>{{ caption }}</cite> -
|
||||
{{/if}}
|
||||
{{ author }}
|
||||
</figcation>
|
||||
</figure>
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
const { art } = require('@/utils/render');
|
||||
const path = require('path');
|
||||
|
||||
const parseMainImage = ($) => {
|
||||
const mainImage = $('figure.mainPhoto');
|
||||
const img = mainImage.find('img');
|
||||
const author = mainImage.find('span.copyright');
|
||||
const caption = mainImage.find('span.imageDescription');
|
||||
|
||||
return art(path.join(__dirname, 'templates/image.art'), {
|
||||
url: img.attr('src'),
|
||||
alt: img.attr('alt')?.trim(),
|
||||
author: author.text()?.trim(),
|
||||
caption: caption.text()?.trim(),
|
||||
});
|
||||
};
|
||||
|
||||
const parseArticleContent = ($) => {
|
||||
const content = $('[itemprop="articleBody"]');
|
||||
$('*')
|
||||
.contents()
|
||||
.filter(function () {
|
||||
return this.nodeType === 8;
|
||||
})
|
||||
.remove();
|
||||
content.find('aside').remove();
|
||||
content.find('.videoPlayerContainer').remove();
|
||||
content.find('.pulsevideo').remove();
|
||||
content.find('.adsContainer').remove();
|
||||
content.find('.placeholder').remove();
|
||||
content.find('.contentPremium').removeAttr('style');
|
||||
content.find('div.image').each((i, el) => {
|
||||
const img = $(el).find('img');
|
||||
const author = $(el).find('span.author');
|
||||
const caption = $(el).find('span.caption');
|
||||
const html = art(path.join(__dirname, 'templates/image.art'), {
|
||||
url: img.attr('src'),
|
||||
alt: img.attr('alt')?.trim(),
|
||||
caption: caption.text()?.trim(),
|
||||
author: author.text()?.trim(),
|
||||
});
|
||||
|
||||
$(el).replaceWith(html);
|
||||
});
|
||||
|
||||
return content;
|
||||
};
|
||||
module.exports = {
|
||||
parseArticleContent,
|
||||
parseMainImage,
|
||||
};
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
const got = require('@/utils/got');
|
||||
const { apiBase, baseUrl, getUserInfo, renderCast } = require('./utils');
|
||||
|
||||
module.exports = async (ctx) => {
|
||||
const { id } = ctx.params;
|
||||
|
||||
const userInfo = await getUserInfo(id, ctx.cache.tryGet);
|
||||
const { data: castData } = await got(`${apiBase}/users/${id}/casts/`);
|
||||
|
||||
const casts = castData.results.map((item) => renderCast(item));
|
||||
|
||||
ctx.state.data = {
|
||||
title: `${userInfo.name} (@${userInfo.username}) - 音声投稿 | OTOBANANA`,
|
||||
description: userInfo.bio.replace(/\n/g, ' '),
|
||||
link: `${baseUrl}/user/${id}`,
|
||||
image: userInfo.avatar_url,
|
||||
icon: userInfo.avatar_url,
|
||||
logo: userInfo.avatar_url,
|
||||
language: 'ja',
|
||||
author: userInfo.name,
|
||||
itunes_author: userInfo.name,
|
||||
item: casts,
|
||||
};
|
||||
|
||||
ctx.state.json = {
|
||||
userInfo,
|
||||
castData,
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
const got = require('@/utils/got');
|
||||
const { apiBase, baseUrl, getUserInfo, renderLive } = require('./utils');
|
||||
|
||||
module.exports = async (ctx) => {
|
||||
const { id } = ctx.params;
|
||||
|
||||
const userInfo = await getUserInfo(id, ctx.cache.tryGet);
|
||||
const { data: liveData } = await got(`${apiBase}/users/${id}/livestreams/`);
|
||||
|
||||
const casts = liveData.results.map((item) => renderLive(item));
|
||||
|
||||
ctx.state.data = {
|
||||
title: `${userInfo.name} (@${userInfo.username}) - ライブ配信 | OTOBANANA`,
|
||||
description: userInfo.bio.replace(/\n/g, ' '),
|
||||
link: `${baseUrl}/user/${id}`,
|
||||
image: userInfo.avatar_url,
|
||||
icon: userInfo.avatar_url,
|
||||
logo: userInfo.avatar_url,
|
||||
language: 'ja',
|
||||
author: userInfo.name,
|
||||
itunes_author: userInfo.name,
|
||||
item: casts,
|
||||
};
|
||||
|
||||
ctx.state.json = {
|
||||
userInfo,
|
||||
liveData,
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
'/user/:id': ['TonyRL'],
|
||||
'/user/:id/cast': ['TonyRL'],
|
||||
'/user/:id/livestream': ['TonyRL'],
|
||||
};
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
module.exports = {
|
||||
'otobanana.com': {
|
||||
_name: 'OTOBANANA',
|
||||
'.': [
|
||||
{
|
||||
title: 'Timeline タイムライン',
|
||||
docs: 'https://docs.rsshub.app/multimedia#otobanana',
|
||||
source: ['/user/:id'],
|
||||
target: '/otobanana/user/:id',
|
||||
},
|
||||
{
|
||||
title: 'Cast 音声投稿',
|
||||
docs: 'https://docs.rsshub.app/multimedia#otobanana',
|
||||
source: ['/user/:id/cast', '/user/:id'],
|
||||
target: '/otobanana/user/:id/cast',
|
||||
},
|
||||
{
|
||||
title: 'Livestream ライブ配信',
|
||||
docs: 'https://docs.rsshub.app/multimedia#otobanana',
|
||||
source: ['/user/:id/livestream', '/user/:id'],
|
||||
target: '/otobanana/user/:id/livestream',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
module.exports = (router) => {
|
||||
router.get('/user/:id', require('./timeline'));
|
||||
router.get('/user/:id/cast', require('./cast'));
|
||||
router.get('/user/:id/livestream', require('./livestream'));
|
||||
};
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
{{ if cast }}
|
||||
<img src="{{ cast.thumbnail_url }}">
|
||||
<br>
|
||||
<audio controls>
|
||||
<source src="{{ cast.audio_url }}" type="audio/x-m4a">
|
||||
</audio>
|
||||
<br>
|
||||
💬 {{ cast.comment_count }} ❤️ {{ cast.like_count }} 🍌 {{ cast.gift_banana }} {{ cast.play_count }} 再生
|
||||
<br>
|
||||
{{@ cast.text.replace(/\n/g, '<br>') }}
|
||||
{{ /if }}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
const got = require('@/utils/got');
|
||||
const { apiBase, baseUrl, getUserInfo, renderPost } = require('./utils');
|
||||
|
||||
module.exports = async (ctx) => {
|
||||
const { id } = ctx.params;
|
||||
|
||||
const userInfo = await getUserInfo(id, ctx.cache.tryGet);
|
||||
const { data: postData } = await got(`${apiBase}/users/${id}/posts/`);
|
||||
|
||||
const posts = postData.results.map((item) => renderPost(item));
|
||||
|
||||
ctx.state.data = {
|
||||
title: `${userInfo.name} (@${userInfo.username}) - タイムライン | OTOBANANA`,
|
||||
description: userInfo.bio.replace(/\n/g, ' '),
|
||||
link: `${baseUrl}/user/${id}`,
|
||||
image: userInfo.avatar_url,
|
||||
icon: userInfo.avatar_url,
|
||||
logo: userInfo.avatar_url,
|
||||
language: 'ja',
|
||||
author: userInfo.name,
|
||||
itunes_author: userInfo.name,
|
||||
item: posts,
|
||||
};
|
||||
|
||||
ctx.state.json = {
|
||||
userInfo,
|
||||
postData,
|
||||
};
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue