feat: rename routes .js to .ts

This commit is contained in:
DIYgod
2024-03-04 00:35:37 +08:00
parent e8d13857a5
commit 8065ededf5
7634 changed files with 146255 additions and 143902 deletions
+12 -16
View File
@@ -14,23 +14,19 @@ type Root = {
const routes: Record<string, (root: Root) => void> = {};
if (process.env.NODE_ENV === 'test') {
routes.test = (await import('./routes/test/router')).default;
} else {
const imports = directoryImport({
targetDirectoryPath: path.join(__dirname, './routes'),
importPattern: /router\.ts$/,
});
const imports = directoryImport({
targetDirectoryPath: path.join(__dirname, './routes'),
importPattern: /router\.ts$/,
});
for (const path in imports) {
const name = path.split('/').find(Boolean);
if (name) {
routes[name] = (
imports[path] as {
default: (root: Root) => void;
}
).default;
}
for (const path in imports) {
const name = path.split('/').find(Boolean);
if (name) {
routes[name] = (
imports[path] as {
default: (root: Root) => void;
}
).default;
}
}
-48
View File
@@ -1,48 +0,0 @@
import cache from '@/utils/cache';
import got from '@/utils/got';
import { load } from 'cheerio';
import { parseDate } from '@/utils/parse-date';
import timezone from '@/utils/timezone';
export default async (ctx) => {
const baseUrl = 'http://www.0818tuan.com';
const listId = ctx.req.param('listId') || '1';
const url = `${baseUrl}/list-${listId}-0.html`;
const { data: response } = await got(url);
const $ = load(response);
const list = $(listId === '3' ? '.col-xs-12 .thumbnail > a' : '.col-md-8 .list-group > a')
.toArray()
.map((item) => {
item = $(item);
return {
title: item.attr('title'),
link: `${baseUrl}${item.attr('href')}`,
};
})
.filter((i) => !i.link.includes('m.0818tuan.com/tb1111.php'));
const items = await Promise.all(
list.map((item) =>
cache.tryGet(item.link, async () => {
const { data: response } = await got(item.link);
const $ = load(response);
$('.pageLink, .alert, p[style="margin:15px;"]').remove();
item.description = $('.post-content').html();
item.pubDate = timezone(parseDate($('.panel-body > .text-center').text().replace('时间:', ''), 'YYYY-MM-DD HH:mm:ss'), +8);
return item;
})
)
);
ctx.set('data', {
title: $('head title').text(),
link: url,
image: 'http://www.0818tuan.com/favicon.ico',
item: items,
});
};
+49
View File
@@ -0,0 +1,49 @@
// @ts-nocheck
import cache from '@/utils/cache';
import got from '@/utils/got';
import { load } from 'cheerio';
import { parseDate } from '@/utils/parse-date';
import timezone from '@/utils/timezone';
export default async (ctx) => {
const baseUrl = 'http://www.0818tuan.com';
const listId = ctx.req.param('listId') || '1';
const url = `${baseUrl}/list-${listId}-0.html`;
const { data: response } = await got(url);
const $ = load(response);
const list = $(listId === '3' ? '.col-xs-12 .thumbnail > a' : '.col-md-8 .list-group > a')
.toArray()
.map((item) => {
item = $(item);
return {
title: item.attr('title'),
link: `${baseUrl}${item.attr('href')}`,
};
})
.filter((i) => !i.link.includes('m.0818tuan.com/tb1111.php'));
const items = await Promise.all(
list.map((item) =>
cache.tryGet(item.link, async () => {
const { data: response } = await got(item.link);
const $ = load(response);
$('.pageLink, .alert, p[style="margin:15px;"]').remove();
item.description = $('.post-content').html();
item.pubDate = timezone(parseDate($('.panel-body > .text-center').text().replace('时间:', ''), 'YYYY-MM-DD HH:mm:ss'), +8);
return item;
})
)
);
ctx.set('data', {
title: $('head title').text(),
link: url,
image: 'http://www.0818tuan.com/favicon.ico',
item: items,
});
};
-109
View File
@@ -1,109 +0,0 @@
import cache from '@/utils/cache';
import got from '@/utils/got';
import { art } from '@/utils/render';
import * as path from 'node:path';
import { config } from '@/config';
const rootUrl = 'https://kyfw.12306.cn';
async function getJSESSIONID(linkUrl) {
const res = await got({
method: 'get',
url: linkUrl,
headers: {
UserAgent: config.ua,
Referer: 'https://www.12306.cn/index/index.html',
},
});
return res.headers['set-cookie'].join(',').match(/JSESSIONID=([^;]+);/)[0];
}
function getStationInfo(stationName) {
return cache.tryGet(stationName, async () => {
const res = await got({
method: 'get',
url: `${rootUrl}/otn/resources/js/framework/station_name.js`,
headers: {
UserAgent: config.ua,
Referer: 'https://kyfw.12306.cn/otn/leftTicket/init',
},
});
return res.data
.split('@')
.map((item) => {
const itemData = item.split('|');
return itemData.includes(stationName)
? {
code: itemData[2],
name: itemData[1],
}
: null;
})
.find(Boolean);
});
}
export default async (ctx) => {
const date = ctx.req.param('date');
const fromStationInfo = await getStationInfo(ctx.req.param('from'));
const toStationInfo = await getStationInfo(ctx.req.param('to'));
const type = ctx.req.param('type') ?? 'ADULT';
const apiUrl = `${rootUrl}/otn/leftTicket/queryA?leftTicketDTO.train_date=${date}&leftTicketDTO.from_station=${fromStationInfo.code}&leftTicketDTO.to_station=${toStationInfo.code}&purpose_codes=${type}`;
const linkUrl = `${rootUrl}/otn/leftTicket/init?linktypeid=dc&fs=${fromStationInfo.code}&ts=${toStationInfo.code}&date=${date}&flag=N,N,Y`;
const response = await got.get(apiUrl, {
headers: {
UserAgent: config.ua,
Referer: 'https://kyfw.12306.cn/otn/leftTicket/init',
Cookie: await getJSESSIONID(linkUrl),
},
});
if (response.data.data === undefined || response.data.data.length === 0) {
throw new Error('没有找到相关车次,请检查参数是否正确');
}
const data = response.data.data.result;
const map = response.data.data.map;
const items = data.map((item) => {
const itemData = item.split('|');
const trainInfo = {
trainNo: itemData[3],
fromStation: map[itemData[6]],
toStation: map[itemData[7]],
startTime: itemData[8],
arriveTime: itemData[9],
duration: itemData[10],
today: itemData[11],
A9: itemData[32],
M: itemData[31],
O: itemData[30],
A6: itemData[29],
A4: itemData[28],
F: itemData[27],
A3: itemData[26],
A2: itemData[25],
A1: itemData[24],
WZ: itemData[23],
QT: itemData[22],
};
return {
title: `${trainInfo.fromStation}${trainInfo.toStation} ${trainInfo.startTime} ${trainInfo.arriveTime}`,
description: art(path.join(__dirname, 'templates/train.art'), {
trainInfo,
}),
link: linkUrl,
guid: Object.values(trainInfo).join('|'),
};
});
ctx.set('data', {
title: `${fromStationInfo.name}${toStationInfo.name} ${date}`,
link: linkUrl,
item: items,
});
};
+110
View File
@@ -0,0 +1,110 @@
// @ts-nocheck
import cache from '@/utils/cache';
import got from '@/utils/got';
import { art } from '@/utils/render';
import * as path from 'node:path';
import { config } from '@/config';
const rootUrl = 'https://kyfw.12306.cn';
async function getJSESSIONID(linkUrl) {
const res = await got({
method: 'get',
url: linkUrl,
headers: {
UserAgent: config.ua,
Referer: 'https://www.12306.cn/index/index.html',
},
});
return res.headers['set-cookie'].join(',').match(/JSESSIONID=([^;]+);/)[0];
}
function getStationInfo(stationName) {
return cache.tryGet(stationName, async () => {
const res = await got({
method: 'get',
url: `${rootUrl}/otn/resources/js/framework/station_name.js`,
headers: {
UserAgent: config.ua,
Referer: 'https://kyfw.12306.cn/otn/leftTicket/init',
},
});
return res.data
.split('@')
.map((item) => {
const itemData = item.split('|');
return itemData.includes(stationName)
? {
code: itemData[2],
name: itemData[1],
}
: null;
})
.find(Boolean);
});
}
export default async (ctx) => {
const date = ctx.req.param('date');
const fromStationInfo = await getStationInfo(ctx.req.param('from'));
const toStationInfo = await getStationInfo(ctx.req.param('to'));
const type = ctx.req.param('type') ?? 'ADULT';
const apiUrl = `${rootUrl}/otn/leftTicket/queryA?leftTicketDTO.train_date=${date}&leftTicketDTO.from_station=${fromStationInfo.code}&leftTicketDTO.to_station=${toStationInfo.code}&purpose_codes=${type}`;
const linkUrl = `${rootUrl}/otn/leftTicket/init?linktypeid=dc&fs=${fromStationInfo.code}&ts=${toStationInfo.code}&date=${date}&flag=N,N,Y`;
const response = await got.get(apiUrl, {
headers: {
UserAgent: config.ua,
Referer: 'https://kyfw.12306.cn/otn/leftTicket/init',
Cookie: await getJSESSIONID(linkUrl),
},
});
if (response.data.data === undefined || response.data.data.length === 0) {
throw new Error('没有找到相关车次,请检查参数是否正确');
}
const data = response.data.data.result;
const map = response.data.data.map;
const items = data.map((item) => {
const itemData = item.split('|');
const trainInfo = {
trainNo: itemData[3],
fromStation: map[itemData[6]],
toStation: map[itemData[7]],
startTime: itemData[8],
arriveTime: itemData[9],
duration: itemData[10],
today: itemData[11],
A9: itemData[32],
M: itemData[31],
O: itemData[30],
A6: itemData[29],
A4: itemData[28],
F: itemData[27],
A3: itemData[26],
A2: itemData[25],
A1: itemData[24],
WZ: itemData[23],
QT: itemData[22],
};
return {
title: `${trainInfo.fromStation}${trainInfo.toStation} ${trainInfo.startTime} ${trainInfo.arriveTime}`,
description: art(path.join(__dirname, 'templates/train.art'), {
trainInfo,
}),
link: linkUrl,
guid: Object.values(trainInfo).join('|'),
};
});
ctx.set('data', {
title: `${fromStationInfo.name}${toStationInfo.name} ${date}`,
link: linkUrl,
item: items,
});
};
-59
View File
@@ -1,59 +0,0 @@
import cache from '@/utils/cache';
import got from '@/utils/got';
import { load } from 'cheerio';
const url = require('url');
export default async (ctx) => {
const id = ctx.req.param('id') || -1;
const link = id === -1 ? 'https://www.12306.cn/mormhweb/zxdt/index_zxdt.html' : `https://www.12306.cn/mormhweb/1/${id}/index_fl.html`;
const response = await got.get(link);
const data = response.data;
const $ = load(data);
const name = $('div.nav_center > a:nth-child(4)').text();
const list = $('#newList > ul > li')
.map(function () {
const info = {
title: $(this).find('a').text(),
link: $(this).find('a').attr('href'),
date: $(this).find('span').text().slice(1, -1),
};
return info;
})
.get();
const out = await Promise.all(
list.map(async (info) => {
const title = info.title;
const date = info.date;
const itemUrl = url.resolve(link, info.link);
const cacheIn = await cache.get(itemUrl);
if (cacheIn) {
return JSON.parse(cacheIn);
}
const response = await got.get(itemUrl);
const $ = load(response.data);
let description = $('.article-box').html();
description = description ? description.replaceAll('src="', `src="${url.resolve(itemUrl, '.')}`).trim() : $('.content_text').html() || '文章已被删除';
const single = {
title,
link: itemUrl,
description,
pubDate: new Date(date).toUTCString(),
};
cache.set(itemUrl, JSON.stringify(single));
return single;
})
);
ctx.set('data', {
title: `${name}最新动态`,
link,
item: out,
});
};
+60
View File
@@ -0,0 +1,60 @@
// @ts-nocheck
import cache from '@/utils/cache';
import got from '@/utils/got';
import { load } from 'cheerio';
const url = require('url');
export default async (ctx) => {
const id = ctx.req.param('id') || -1;
const link = id === -1 ? 'https://www.12306.cn/mormhweb/zxdt/index_zxdt.html' : `https://www.12306.cn/mormhweb/1/${id}/index_fl.html`;
const response = await got.get(link);
const data = response.data;
const $ = load(data);
const name = $('div.nav_center > a:nth-child(4)').text();
const list = $('#newList > ul > li')
.map(function () {
const info = {
title: $(this).find('a').text(),
link: $(this).find('a').attr('href'),
date: $(this).find('span').text().slice(1, -1),
};
return info;
})
.get();
const out = await Promise.all(
list.map(async (info) => {
const title = info.title;
const date = info.date;
const itemUrl = url.resolve(link, info.link);
const cacheIn = await cache.get(itemUrl);
if (cacheIn) {
return JSON.parse(cacheIn);
}
const response = await got.get(itemUrl);
const $ = load(response.data);
let description = $('.article-box').html();
description = description ? description.replaceAll('src="', `src="${url.resolve(itemUrl, '.')}`).trim() : $('.content_text').html() || '文章已被删除';
const single = {
title,
link: itemUrl,
description,
pubDate: new Date(date).toUTCString(),
};
cache.set(itemUrl, JSON.stringify(single));
return single;
})
);
ctx.set('data', {
title: `${name}最新动态`,
link,
item: out,
});
};
-72
View File
@@ -1,72 +0,0 @@
import { getSubPath } from '@/utils/common-utils';
import got from '@/utils/got';
import { load } from 'cheerio';
import { parseDate } from '@/utils/parse-date';
import { art } from '@/utils/render';
import * as path from 'node:path';
export default async (ctx) => {
const rootUrl = 'https://www.141jav.com';
const currentUrl = `${rootUrl}${getSubPath(ctx)}`;
const response = await got({
method: 'get',
url: currentUrl,
});
const $ = load(response.data);
if (getSubPath(ctx) === '/') {
ctx.redirect(`/141jav${$('.overview').first().attr('href')}`);
return;
}
const items = $('.columns')
.toArray()
.map((item) => {
item = $(item);
const id = item.find('.title a').text();
const size = item.find('.title span').text();
const pubDate = item.find('.subtitle a').attr('href').split('/date/').pop();
const description = item.find('.has-text-grey-dark').text();
const actresses = item
.find('.panel-block')
.toArray()
.map((a) => $(a).text().trim());
const tags = item
.find('.tag')
.toArray()
.map((t) => $(t).text().trim());
const magnet = item.find('a[title="Magnet torrent"]').attr('href');
const link = item.find('a[title="Download .torrent"]').attr('href');
const image = item.find('.image').attr('src');
return {
title: `${id} ${size}`,
pubDate: parseDate(pubDate, 'YYYY/MM/DD'),
link: new URL(item.find('a').first().attr('href'), rootUrl).href,
description: art(path.join(__dirname, 'templates/description.art'), {
image,
id,
size,
pubDate,
description,
actresses,
tags,
magnet,
link,
}),
author: actresses.join(', '),
category: [...tags, ...actresses],
enclosure_type: 'application/x-bittorrent',
enclosure_url: magnet,
};
});
ctx.set('data', {
title: `141JAV - ${$('title').text().split('-')[0].trim()}`,
link: currentUrl,
item: items,
});
};
+73
View File
@@ -0,0 +1,73 @@
// @ts-nocheck
import { getSubPath } from '@/utils/common-utils';
import got from '@/utils/got';
import { load } from 'cheerio';
import { parseDate } from '@/utils/parse-date';
import { art } from '@/utils/render';
import * as path from 'node:path';
export default async (ctx) => {
const rootUrl = 'https://www.141jav.com';
const currentUrl = `${rootUrl}${getSubPath(ctx)}`;
const response = await got({
method: 'get',
url: currentUrl,
});
const $ = load(response.data);
if (getSubPath(ctx) === '/') {
ctx.redirect(`/141jav${$('.overview').first().attr('href')}`);
return;
}
const items = $('.columns')
.toArray()
.map((item) => {
item = $(item);
const id = item.find('.title a').text();
const size = item.find('.title span').text();
const pubDate = item.find('.subtitle a').attr('href').split('/date/').pop();
const description = item.find('.has-text-grey-dark').text();
const actresses = item
.find('.panel-block')
.toArray()
.map((a) => $(a).text().trim());
const tags = item
.find('.tag')
.toArray()
.map((t) => $(t).text().trim());
const magnet = item.find('a[title="Magnet torrent"]').attr('href');
const link = item.find('a[title="Download .torrent"]').attr('href');
const image = item.find('.image').attr('src');
return {
title: `${id} ${size}`,
pubDate: parseDate(pubDate, 'YYYY/MM/DD'),
link: new URL(item.find('a').first().attr('href'), rootUrl).href,
description: art(path.join(__dirname, 'templates/description.art'), {
image,
id,
size,
pubDate,
description,
actresses,
tags,
magnet,
link,
}),
author: actresses.join(', '),
category: [...tags, ...actresses],
enclosure_type: 'application/x-bittorrent',
enclosure_url: magnet,
};
});
ctx.set('data', {
title: `141JAV - ${$('title').text().split('-')[0].trim()}`,
link: currentUrl,
item: items,
});
};
-75
View File
@@ -1,75 +0,0 @@
import { getSubPath } from '@/utils/common-utils';
import got from '@/utils/got';
import { load } from 'cheerio';
import { parseDate } from '@/utils/parse-date';
import { art } from '@/utils/render';
import * as path from 'node:path';
export default async (ctx) => {
const rootUrl = 'https://www.141ppv.com';
const currentUrl = `${rootUrl}${getSubPath(ctx)}`;
const response = await got({
method: 'get',
url: currentUrl,
});
const $ = load(response.data);
if (getSubPath(ctx) === '/') {
ctx.redirect(`/141ppv${$('.overview').first().attr('href')}`);
return;
}
const items = $('.columns')
.toArray()
.map((item) => {
item = $(item);
const id = item.find('.title a').text();
const size = item.find('.title span').text();
const pubDate = item.find('.subtitle a').attr('href').split('/date/').pop();
const description = item.find('.has-text-grey-dark').text();
const actresses = item
.find('.panel-block')
.toArray()
.map((a) => $(a).text().trim());
const tags = item
.find('.tag')
.toArray()
.map((t) => $(t).text().trim());
const magnet = item.find('a[title="Magnet torrent"]').attr('href');
const link = item.find('a[title="Download .torrent"]').attr('href');
const onErrorAttr = item.find('.image').attr('onerror');
const backupImageRegex = /this\.src='(.*?)'/;
const match = backupImageRegex.exec(onErrorAttr);
const image = match ? match[1] : item.find('.image').attr('src');
return {
title: `${id} ${size}`,
pubDate: parseDate(pubDate, 'YYYY/MM/DD'),
link: new URL(item.find('a').first().attr('href'), rootUrl).href,
description: art(path.join(__dirname, 'templates/description.art'), {
image,
id,
size,
pubDate,
description,
actresses,
tags,
magnet,
link,
}),
author: actresses.join(', '),
category: [...tags, ...actresses],
enclosure_type: 'application/x-bittorrent',
enclosure_url: magnet,
};
});
ctx.set('data', {
title: `141PPV - ${$('title').text().split('-')[0].trim()}`,
link: currentUrl,
item: items,
});
};
+76
View File
@@ -0,0 +1,76 @@
// @ts-nocheck
import { getSubPath } from '@/utils/common-utils';
import got from '@/utils/got';
import { load } from 'cheerio';
import { parseDate } from '@/utils/parse-date';
import { art } from '@/utils/render';
import * as path from 'node:path';
export default async (ctx) => {
const rootUrl = 'https://www.141ppv.com';
const currentUrl = `${rootUrl}${getSubPath(ctx)}`;
const response = await got({
method: 'get',
url: currentUrl,
});
const $ = load(response.data);
if (getSubPath(ctx) === '/') {
ctx.redirect(`/141ppv${$('.overview').first().attr('href')}`);
return;
}
const items = $('.columns')
.toArray()
.map((item) => {
item = $(item);
const id = item.find('.title a').text();
const size = item.find('.title span').text();
const pubDate = item.find('.subtitle a').attr('href').split('/date/').pop();
const description = item.find('.has-text-grey-dark').text();
const actresses = item
.find('.panel-block')
.toArray()
.map((a) => $(a).text().trim());
const tags = item
.find('.tag')
.toArray()
.map((t) => $(t).text().trim());
const magnet = item.find('a[title="Magnet torrent"]').attr('href');
const link = item.find('a[title="Download .torrent"]').attr('href');
const onErrorAttr = item.find('.image').attr('onerror');
const backupImageRegex = /this\.src='(.*?)'/;
const match = backupImageRegex.exec(onErrorAttr);
const image = match ? match[1] : item.find('.image').attr('src');
return {
title: `${id} ${size}`,
pubDate: parseDate(pubDate, 'YYYY/MM/DD'),
link: new URL(item.find('a').first().attr('href'), rootUrl).href,
description: art(path.join(__dirname, 'templates/description.art'), {
image,
id,
size,
pubDate,
description,
actresses,
tags,
magnet,
link,
}),
author: actresses.join(', '),
category: [...tags, ...actresses],
enclosure_type: 'application/x-bittorrent',
enclosure_url: magnet,
};
});
ctx.set('data', {
title: `141PPV - ${$('title').text().split('-')[0].trim()}`,
link: currentUrl,
item: items,
});
};
-33
View File
@@ -1,33 +0,0 @@
import got from '@/utils/got';
import { parseDate } from '@/utils/parse-date';
import { art } from '@/utils/render';
import * as path from 'node:path';
const root_url = 'https://inf.ds.163.com';
export default async (ctx) => {
const id = ctx.req.param('id');
const current_url = `${root_url}/v1/web/feed/basic/getSomeOneFeeds?feedTypes=1,2,3,4,6,7,10,11&someOneUid=${id}`;
const response = await got({
method: 'get',
url: current_url,
});
const data = response.data.result.feeds;
const list = data.map((feed) => ({
title: JSON.parse(feed.content).body.text,
link: `https://ds.163.com/feed/${feed.id}`,
description: art(path.resolve(__dirname, 'templates/ds.art'), {
text: JSON.parse(feed.content).body.text,
medias: JSON.parse(feed.content).body.media,
}),
pubDate: parseDate(feed.updateTime),
}));
ctx.set('data', {
title: `${response.data.result.userInfos[0].user.nick} 的动态`,
link: `https://ds.163.com/user/${id}`,
item: list,
});
};
+34
View File
@@ -0,0 +1,34 @@
// @ts-nocheck
import got from '@/utils/got';
import { parseDate } from '@/utils/parse-date';
import { art } from '@/utils/render';
import * as path from 'node:path';
const root_url = 'https://inf.ds.163.com';
export default async (ctx) => {
const id = ctx.req.param('id');
const current_url = `${root_url}/v1/web/feed/basic/getSomeOneFeeds?feedTypes=1,2,3,4,6,7,10,11&someOneUid=${id}`;
const response = await got({
method: 'get',
url: current_url,
});
const data = response.data.result.feeds;
const list = data.map((feed) => ({
title: JSON.parse(feed.content).body.text,
link: `https://ds.163.com/feed/${feed.id}`,
description: art(path.resolve(__dirname, 'templates/ds.art'), {
text: JSON.parse(feed.content).body.text,
medias: JSON.parse(feed.content).body.media,
}),
pubDate: parseDate(feed.updateTime),
}));
ctx.set('data', {
title: `${response.data.result.userInfos[0].user.nick} 的动态`,
link: `https://ds.163.com/user/${id}`,
item: list,
});
};
-30
View File
@@ -1,30 +0,0 @@
import cache from '@/utils/cache';
import got from '@/utils/got';
import { parseDate } from '@/utils/parse-date';
import timezone from '@/utils/timezone';
const { parseDyArticle } = require('./utils');
export default async (ctx) => {
const id = ctx.req.param('id');
const response = await got(`https://dy.163.com/v2/article/list.do?pageNo=1&wemediaId=${id}&size=10`);
const charset = response.headers['content-type'].split('=')[1];
const list = response.data.data.list.map((e) => ({
title: e.title,
link: 'https://www.163.com/dy/article/' + e.docid + '.html',
pubDate: timezone(parseDate(e.ptime), 8),
author: e.source,
imgsrc: e.imgsrc,
}));
const items = await Promise.all(list.map((e) => parseDyArticle(charset, e, cache.tryGet)));
ctx.set('data', {
title: `网易号 - ${list[0].author}`,
link: items[0].feedLink,
description: items[0].feedDescription,
image: items[0].feedImage,
item: items,
});
};
+31
View File
@@ -0,0 +1,31 @@
// @ts-nocheck
import cache from '@/utils/cache';
import got from '@/utils/got';
import { parseDate } from '@/utils/parse-date';
import timezone from '@/utils/timezone';
const { parseDyArticle } = require('./utils');
export default async (ctx) => {
const id = ctx.req.param('id');
const response = await got(`https://dy.163.com/v2/article/list.do?pageNo=1&wemediaId=${id}&size=10`);
const charset = response.headers['content-type'].split('=')[1];
const list = response.data.data.list.map((e) => ({
title: e.title,
link: 'https://www.163.com/dy/article/' + e.docid + '.html',
pubDate: timezone(parseDate(e.ptime), 8),
author: e.source,
imgsrc: e.imgsrc,
}));
const items = await Promise.all(list.map((e) => parseDyArticle(charset, e, cache.tryGet)));
ctx.set('data', {
title: `网易号 - ${list[0].author}`,
link: items[0].feedLink,
description: items[0].feedDescription,
image: items[0].feedImage,
item: items,
});
};
-43
View File
@@ -1,43 +0,0 @@
import cache from '@/utils/cache';
import got from '@/utils/got';
import { load } from 'cheerio';
const iconv = require('iconv-lite');
import { parseDate } from '@/utils/parse-date';
import timezone from '@/utils/timezone';
const { parseDyArticle } = require('./utils');
export default async (ctx) => {
const id = ctx.req.param('id');
const limit = ctx.req.query('limit') ?? 30;
const url = `https://www.163.com/dy/media/${id}.html`;
const response = await got(url, { responseType: 'buffer' });
const charset = response.headers['content-type'].split('=')[1];
const data = iconv.decode(response.data, charset);
const $ = load(data);
const list = $('.tab_content ul li')
.slice(0, limit)
.toArray()
.map((item) => {
item = $(item);
return {
title: item.find('h4 a').text(),
link: item.find('a').first().attr('href'),
pubDate: timezone(parseDate(item.find('.time').text()), 8),
imgsrc: item.find('a img').attr('src'),
};
});
const items = await Promise.all(list.map((item) => parseDyArticle(charset, item, cache.tryGet)));
ctx.set('data', {
title: `${$('head title').text()} - 网易号`,
link: url,
description: $('.icon_line.desc').text(),
image: $('.head_img').attr('src'),
item: items,
author: $('h2').text(),
});
};
+44
View File
@@ -0,0 +1,44 @@
// @ts-nocheck
import cache from '@/utils/cache';
import got from '@/utils/got';
import { load } from 'cheerio';
const iconv = require('iconv-lite');
import { parseDate } from '@/utils/parse-date';
import timezone from '@/utils/timezone';
const { parseDyArticle } = require('./utils');
export default async (ctx) => {
const id = ctx.req.param('id');
const limit = ctx.req.query('limit') ?? 30;
const url = `https://www.163.com/dy/media/${id}.html`;
const response = await got(url, { responseType: 'buffer' });
const charset = response.headers['content-type'].split('=')[1];
const data = iconv.decode(response.data, charset);
const $ = load(data);
const list = $('.tab_content ul li')
.slice(0, limit)
.toArray()
.map((item) => {
item = $(item);
return {
title: item.find('h4 a').text(),
link: item.find('a').first().attr('href'),
pubDate: timezone(parseDate(item.find('.time').text()), 8),
imgsrc: item.find('a img').attr('src'),
};
});
const items = await Promise.all(list.map((item) => parseDyArticle(charset, item, cache.tryGet)));
ctx.set('data', {
title: `${$('head title').text()} - 网易号`,
link: url,
description: $('.icon_line.desc').text(),
image: $('.head_img').attr('src'),
item: items,
author: $('h2').text(),
});
};
-149
View File
@@ -1,149 +0,0 @@
import cache from '@/utils/cache';
import got from '@/utils/got';
import { load } from 'cheerio';
import timezone from '@/utils/timezone';
import { parseDate } from '@/utils/parse-date';
import { art } from '@/utils/render';
import * as path from 'node:path';
const ids = {
'': {
id: 'BAI5E21O',
title: '首页',
},
qsyk: {
id: 'BD21K0DL',
title: '轻松一刻',
},
cz: {
id: 'CICMICLU',
title: '槽值',
},
rj: {
id: 'CICMOMBL',
title: '人间',
},
dgxm: {
id: 'CICMPVC5',
title: '大国小民',
},
ssyg: {
id: 'CICMLCOU',
title: '三三有梗',
},
sd: {
id: 'D551V75C',
title: '数读',
},
kk: {
id: 'D55253RH',
title: '看客',
},
xhx: {
id: 'D553A53L',
title: '下划线',
},
txs: {
id: 'D553PGHQ',
title: '谈心社',
},
dd: {
id: 'CICMS5BI',
title: '哒哒',
},
pbgl: {
id: 'CQ9UDVKO',
title: '胖编怪聊',
},
qyd: {
id: 'CQ9UJIJN',
title: '曲一刀',
},
jrzs: {
id: 'BD284UM8',
title: '今日之声',
},
lc: {
id: 'CICMMGBH',
title: '浪潮',
},
fd: {
id: 'D5543R68',
title: '沸点',
},
};
export default async (ctx) => {
const id = ctx.req.param('id') ?? '';
const rootUrl = 'https://3g.163.com';
const currentUrl = `${rootUrl}/touch/exclusive${id ? `/sub/${id}` : ''}`;
const apiUrl = `${rootUrl}/touch/reconstruct/article/list/${ids[id].id}wangning/0-20.html`;
const response = await got({
method: 'get',
url: apiUrl,
});
const data = JSON.parse(response.data.match(/^artiList\((.*)\)$/)[1])[`${ids[id].id}wangning`];
let items = data.map((item) => ({
title: item.title,
author: item.source,
link: item.skipURL || item.url || `${rootUrl}/dy/article/${item.docid}.html`,
pubDate: timezone(parseDate(item.ptime), +8),
videoId: item.skipType === 'video' ? item.stitle : '',
}));
items = await Promise.all(
items.map((item) =>
cache.tryGet(item.link, async () => {
try {
if (item.videoId) {
const detailResponse = await got({
method: 'get',
url: `${rootUrl}/touch/video/detail/jsonp/VIA8K0PTB.html?callback=videoList`,
});
const video = JSON.parse(detailResponse.data.match(/^videoList\((.*)\)$/)[1])?.mp4_url;
item.description = art(path.join(__dirname, 'templates/exclusive.art'), {
video,
});
} else {
const detailResponse = await got({
method: 'get',
url: item.link,
});
const content = load(detailResponse.data);
content('.m-linkCard').remove();
content('.m-photo').each(function () {
content(this).html(
art(path.join(__dirname, 'templates/exclusive.art'), {
image: content(this).find('img').attr('data-src'),
})
);
});
item.description = content('.article-body').html();
}
} catch {
// no-empty
}
delete item.videoId;
return item;
})
)
);
ctx.set('data', {
title: `网易独家 - ${ids[id].title}`,
link: currentUrl,
item: items,
});
};
+150
View File
@@ -0,0 +1,150 @@
// @ts-nocheck
import cache from '@/utils/cache';
import got from '@/utils/got';
import { load } from 'cheerio';
import timezone from '@/utils/timezone';
import { parseDate } from '@/utils/parse-date';
import { art } from '@/utils/render';
import * as path from 'node:path';
const ids = {
'': {
id: 'BAI5E21O',
title: '首页',
},
qsyk: {
id: 'BD21K0DL',
title: '轻松一刻',
},
cz: {
id: 'CICMICLU',
title: '槽值',
},
rj: {
id: 'CICMOMBL',
title: '人间',
},
dgxm: {
id: 'CICMPVC5',
title: '大国小民',
},
ssyg: {
id: 'CICMLCOU',
title: '三三有梗',
},
sd: {
id: 'D551V75C',
title: '数读',
},
kk: {
id: 'D55253RH',
title: '看客',
},
xhx: {
id: 'D553A53L',
title: '下划线',
},
txs: {
id: 'D553PGHQ',
title: '谈心社',
},
dd: {
id: 'CICMS5BI',
title: '哒哒',
},
pbgl: {
id: 'CQ9UDVKO',
title: '胖编怪聊',
},
qyd: {
id: 'CQ9UJIJN',
title: '曲一刀',
},
jrzs: {
id: 'BD284UM8',
title: '今日之声',
},
lc: {
id: 'CICMMGBH',
title: '浪潮',
},
fd: {
id: 'D5543R68',
title: '沸点',
},
};
export default async (ctx) => {
const id = ctx.req.param('id') ?? '';
const rootUrl = 'https://3g.163.com';
const currentUrl = `${rootUrl}/touch/exclusive${id ? `/sub/${id}` : ''}`;
const apiUrl = `${rootUrl}/touch/reconstruct/article/list/${ids[id].id}wangning/0-20.html`;
const response = await got({
method: 'get',
url: apiUrl,
});
const data = JSON.parse(response.data.match(/^artiList\((.*)\)$/)[1])[`${ids[id].id}wangning`];
let items = data.map((item) => ({
title: item.title,
author: item.source,
link: item.skipURL || item.url || `${rootUrl}/dy/article/${item.docid}.html`,
pubDate: timezone(parseDate(item.ptime), +8),
videoId: item.skipType === 'video' ? item.stitle : '',
}));
items = await Promise.all(
items.map((item) =>
cache.tryGet(item.link, async () => {
try {
if (item.videoId) {
const detailResponse = await got({
method: 'get',
url: `${rootUrl}/touch/video/detail/jsonp/VIA8K0PTB.html?callback=videoList`,
});
const video = JSON.parse(detailResponse.data.match(/^videoList\((.*)\)$/)[1])?.mp4_url;
item.description = art(path.join(__dirname, 'templates/exclusive.art'), {
video,
});
} else {
const detailResponse = await got({
method: 'get',
url: item.link,
});
const content = load(detailResponse.data);
content('.m-linkCard').remove();
content('.m-photo').each(function () {
content(this).html(
art(path.join(__dirname, 'templates/exclusive.art'), {
image: content(this).find('img').attr('data-src'),
})
);
});
item.description = content('.article-body').html();
}
} catch {
// no-empty
}
delete item.videoId;
return item;
})
)
);
ctx.set('data', {
title: `网易独家 - ${ids[id].title}`,
link: currentUrl,
item: items,
});
};
-39
View File
@@ -1,39 +0,0 @@
import got from '@/utils/got';
import { art } from '@/utils/render';
import * as path from 'node:path';
export default async (ctx) => {
const id = ctx.req.param('id');
const { data } = await got(`https://music.163.com/api/v1/artist/songs`, {
headers: {
Referer: 'https://music.163.com/',
},
searchParams: {
id,
private_cloud: 'true',
work_type: 1,
order: 'time',
offset: 0,
limit: 100,
},
});
const artist = data.songs.find(({ ar }) => ar[0].id === Number.parseInt(id)).ar[0];
const items = data.songs.map((song) => ({
title: `${song.name} - ${song.ar.map(({ name }) => name).join(' / ')}`,
description: art(path.join(__dirname, '../templates/music/playlist.art'), {
singer: song.ar.map(({ name }) => name).join(' / '),
album: song.al.name,
picUrl: song.al.picUrl,
}),
link: `https://music.163.com/#/song?id=${song.id}`,
}));
ctx.set('data', {
title: `${artist.name} - 歌手歌曲`,
link: `https://music.163.com/#/artist?id=${id}`,
description: `网易云音乐 - 歌手歌曲 - ${artist.name}`,
item: items,
});
};
+40
View File
@@ -0,0 +1,40 @@
// @ts-nocheck
import got from '@/utils/got';
import { art } from '@/utils/render';
import * as path from 'node:path';
export default async (ctx) => {
const id = ctx.req.param('id');
const { data } = await got(`https://music.163.com/api/v1/artist/songs`, {
headers: {
Referer: 'https://music.163.com/',
},
searchParams: {
id,
private_cloud: 'true',
work_type: 1,
order: 'time',
offset: 0,
limit: 100,
},
});
const artist = data.songs.find(({ ar }) => ar[0].id === Number.parseInt(id)).ar[0];
const items = data.songs.map((song) => ({
title: `${song.name} - ${song.ar.map(({ name }) => name).join(' / ')}`,
description: art(path.join(__dirname, '../templates/music/playlist.art'), {
singer: song.ar.map(({ name }) => name).join(' / '),
album: song.al.name,
picUrl: song.al.picUrl,
}),
link: `https://music.163.com/#/song?id=${song.id}`,
}));
ctx.set('data', {
title: `${artist.name} - 歌手歌曲`,
link: `https://music.163.com/#/artist?id=${id}`,
description: `网易云音乐 - 歌手歌曲 - ${artist.name}`,
item: items,
});
};
-39
View File
@@ -1,39 +0,0 @@
import got from '@/utils/got';
import { art } from '@/utils/render';
import * as path from 'node:path';
export default async (ctx) => {
const id = ctx.req.param('id');
const response = await got(`https://music.163.com/api/artist/albums/${id}`, {
headers: {
Referer: 'https://music.163.com/',
},
});
const data = response.data;
ctx.set('data', {
title: data.artist.name,
link: `https://music.163.com/#/artist/album?id=${id}`,
description: `网易云音乐歌手专辑 - ${data.artist.name}`,
image: data.artist.img1v1Url || data.artist.picUrl,
item: data.hotAlbums.map((item) => {
const singer = item.artists.length === 1 ? item.artists[0].name : item.artists.reduce((prev, cur) => (prev.name || prev) + '/' + cur.name);
return {
title: `${item.name} - ${singer}`,
description: art(path.join(__dirname, '../templates/music/playlist.art'), {
singer,
album: item.name,
date: new Date(item.publishTime).toLocaleDateString(),
picUrl: item.picUrl,
}),
link: `https://music.163.com/#/album?id=${item.id}`,
pubDate: new Date(item.publishTime),
published: new Date(item.publishTime),
category: item.subType,
author: singer,
};
}),
});
};
+40
View File
@@ -0,0 +1,40 @@
// @ts-nocheck
import got from '@/utils/got';
import { art } from '@/utils/render';
import * as path from 'node:path';
export default async (ctx) => {
const id = ctx.req.param('id');
const response = await got(`https://music.163.com/api/artist/albums/${id}`, {
headers: {
Referer: 'https://music.163.com/',
},
});
const data = response.data;
ctx.set('data', {
title: data.artist.name,
link: `https://music.163.com/#/artist/album?id=${id}`,
description: `网易云音乐歌手专辑 - ${data.artist.name}`,
image: data.artist.img1v1Url || data.artist.picUrl,
item: data.hotAlbums.map((item) => {
const singer = item.artists.length === 1 ? item.artists[0].name : item.artists.reduce((prev, cur) => (prev.name || prev) + '/' + cur.name);
return {
title: `${item.name} - ${singer}`,
description: art(path.join(__dirname, '../templates/music/playlist.art'), {
singer,
album: item.name,
date: new Date(item.publishTime).toLocaleDateString(),
picUrl: item.picUrl,
}),
link: `https://music.163.com/#/album?id=${item.id}`,
pubDate: new Date(item.publishTime),
published: new Date(item.publishTime),
category: item.subType,
author: singer,
};
}),
});
};
-78
View File
@@ -1,78 +0,0 @@
import got from '@/utils/got';
import { parseDate } from '@/utils/parse-date';
import { art } from '@/utils/render';
import * as path from 'node:path';
export default async (ctx) => {
const id = ctx.req.param('id');
const ProcessFeed = (limit, offset) =>
got.post('https://music.163.com/api/dj/program/byradio', {
headers: {
Referer: 'https://music.163.com/',
},
form: {
radioId: id,
limit,
offset,
},
});
const response = await ProcessFeed(1, 0);
const programs = response.data.programs || [];
const { radio, dj } = programs[0] || { radio: {}, dj: {} };
const count = response.data.count || 0;
const countPage = [];
for (let i = 0; i < Math.ceil(count / 500); i++) {
countPage.push(i);
}
const items = await Promise.all(
countPage.map(async (item) => {
const response = await ProcessFeed(500, item * 500);
const programs = response.data.programs || [];
const list = programs.map((pg) => {
const description = (pg.description || '').split('\n').map((p) => p);
const duration = Math.trunc(pg.duration / 1000);
const mm_ss_duration = `${(duration / 60).toFixed(0).padStart(2, '0')}:${(duration % 60).toFixed(0).padStart(2, '0')}`;
const html = art(path.join(__dirname, '../templates/music/djradio-content.art'), {
pg,
description,
itunes_duration: mm_ss_duration,
});
return {
title: pg.name,
link: 'https://music.163.com/program/' + pg.id,
pubDate: parseDate(pg.createTime),
published: parseDate(pg.createTime),
author: pg.dj.nickname,
description: html,
content: { html },
itunes_item_image: pg.coverUrl,
enclosure_url: `https://music.163.com/song/media/outer/url?id=${pg.mainTrackId}.mp3`,
enclosure_type: 'audio/mpeg',
itunes_duration: duration,
};
});
return list;
})
);
ctx.set('data', {
title: radio.name,
link: `https://music.163.com/djradio?id=${id}`,
subtitle: radio.desc,
description: radio.desc,
author: dj.nickname,
updated: radio.lastProgramCreateTime,
icon: radio.picUrl,
image: radio.picUrl,
itunes_author: dj.nickname,
itunes_category: radio.category,
item: items.flat(),
});
};
+79
View File
@@ -0,0 +1,79 @@
// @ts-nocheck
import got from '@/utils/got';
import { parseDate } from '@/utils/parse-date';
import { art } from '@/utils/render';
import * as path from 'node:path';
export default async (ctx) => {
const id = ctx.req.param('id');
const ProcessFeed = (limit, offset) =>
got.post('https://music.163.com/api/dj/program/byradio', {
headers: {
Referer: 'https://music.163.com/',
},
form: {
radioId: id,
limit,
offset,
},
});
const response = await ProcessFeed(1, 0);
const programs = response.data.programs || [];
const { radio, dj } = programs[0] || { radio: {}, dj: {} };
const count = response.data.count || 0;
const countPage = [];
for (let i = 0; i < Math.ceil(count / 500); i++) {
countPage.push(i);
}
const items = await Promise.all(
countPage.map(async (item) => {
const response = await ProcessFeed(500, item * 500);
const programs = response.data.programs || [];
const list = programs.map((pg) => {
const description = (pg.description || '').split('\n').map((p) => p);
const duration = Math.trunc(pg.duration / 1000);
const mm_ss_duration = `${(duration / 60).toFixed(0).padStart(2, '0')}:${(duration % 60).toFixed(0).padStart(2, '0')}`;
const html = art(path.join(__dirname, '../templates/music/djradio-content.art'), {
pg,
description,
itunes_duration: mm_ss_duration,
});
return {
title: pg.name,
link: 'https://music.163.com/program/' + pg.id,
pubDate: parseDate(pg.createTime),
published: parseDate(pg.createTime),
author: pg.dj.nickname,
description: html,
content: { html },
itunes_item_image: pg.coverUrl,
enclosure_url: `https://music.163.com/song/media/outer/url?id=${pg.mainTrackId}.mp3`,
enclosure_type: 'audio/mpeg',
itunes_duration: duration,
};
});
return list;
})
);
ctx.set('data', {
title: radio.name,
link: `https://music.163.com/djradio?id=${id}`,
subtitle: radio.desc,
description: radio.desc,
author: dj.nickname,
updated: radio.lastProgramCreateTime,
icon: radio.picUrl,
image: radio.picUrl,
itunes_author: dj.nickname,
itunes_category: radio.category,
item: items.flat(),
});
};
-55
View File
@@ -1,55 +0,0 @@
import got from '@/utils/got';
import { config } from '@/config';
import { art } from '@/utils/render';
import * as path from 'node:path';
export default async (ctx) => {
if (!config.ncm || !config.ncm.cookies) {
throw new Error('163 Music RSS is disabled due to the lack of <a href="https://docs.rsshub.app/install/#pei-zhi-bu-fen-rss-mo-kuai-pei-zhi">relevant config</a>');
}
const id = ctx.req.param('id');
const response = await got.post('https://music.163.com/api/v3/playlist/detail', {
headers: {
Referer: 'https://music.163.com/',
Cookie: config.ncm.cookies,
},
form: {
id,
},
});
const data = response.data.playlist;
const songinfo = await got('https://music.163.com/api/song/detail', {
headers: {
Referer: 'https://music.163.com',
},
searchParams: {
ids: `[${data.trackIds.slice(0, 201).map((item) => item.id)}]`,
},
});
const songs = songinfo.data.songs;
ctx.set('data', {
title: data.name,
link: `https://music.163.com/#/playlist?id=${id}`,
description: `网易云音乐歌单 - ${data.name}`,
item: data.trackIds.slice(0, 201).map((item) => {
const thisSong = songs.find((element) => element.id === item.id);
const singer = thisSong.artists.length === 1 ? thisSong.artists[0].name : thisSong.artists.reduce((prev, cur) => (prev.name || prev) + '/' + cur.name);
return {
title: `${thisSong.name} - ${singer}`,
description: art(path.join(__dirname, '../templates/music/playlist.art'), {
singer,
album: thisSong.album.name,
date: new Date(thisSong.album.publishTime).toLocaleDateString(),
picUrl: thisSong.album.picUrl,
}),
link: `https://music.163.com/#/song?id=${item.id}`,
pubDate: new Date(item.at),
author: singer,
};
}),
});
};
+56
View File
@@ -0,0 +1,56 @@
// @ts-nocheck
import got from '@/utils/got';
import { config } from '@/config';
import { art } from '@/utils/render';
import * as path from 'node:path';
export default async (ctx) => {
if (!config.ncm || !config.ncm.cookies) {
throw new Error('163 Music RSS is disabled due to the lack of <a href="https://docs.rsshub.app/install/#pei-zhi-bu-fen-rss-mo-kuai-pei-zhi">relevant config</a>');
}
const id = ctx.req.param('id');
const response = await got.post('https://music.163.com/api/v3/playlist/detail', {
headers: {
Referer: 'https://music.163.com/',
Cookie: config.ncm.cookies,
},
form: {
id,
},
});
const data = response.data.playlist;
const songinfo = await got('https://music.163.com/api/song/detail', {
headers: {
Referer: 'https://music.163.com',
},
searchParams: {
ids: `[${data.trackIds.slice(0, 201).map((item) => item.id)}]`,
},
});
const songs = songinfo.data.songs;
ctx.set('data', {
title: data.name,
link: `https://music.163.com/#/playlist?id=${id}`,
description: `网易云音乐歌单 - ${data.name}`,
item: data.trackIds.slice(0, 201).map((item) => {
const thisSong = songs.find((element) => element.id === item.id);
const singer = thisSong.artists.length === 1 ? thisSong.artists[0].name : thisSong.artists.reduce((prev, cur) => (prev.name || prev) + '/' + cur.name);
return {
title: `${thisSong.name} - ${singer}`,
description: art(path.join(__dirname, '../templates/music/playlist.art'), {
singer,
album: thisSong.album.name,
date: new Date(thisSong.album.publishTime).toLocaleDateString(),
picUrl: thisSong.album.picUrl,
}),
link: `https://music.163.com/#/song?id=${item.id}`,
pubDate: new Date(item.at),
author: singer,
};
}),
});
};
-52
View File
@@ -1,52 +0,0 @@
import * as path from 'node:path';
import got from '@/utils/got';
import { art } from '@/utils/render';
const renderDescription = (info) => art(path.join(__dirname, '../templates/music/userevents.art'), info);
export default async (ctx) => {
const id = ctx.req.param('id');
const response = await got(`https://music.163.com/api/event/get/${id}`, {
headers: {
Referer: 'https://music.163.com/',
},
});
const { data } = response;
const { nickname, signature, avatarUrl } = data.events[0].user;
ctx.set('data', {
title: `${nickname}的云村动态`,
link: `https://music.163.com/#/user/event?id=${id}`,
description: `网易云音乐用户动态 - ${signature}`,
icon: avatarUrl,
image: avatarUrl,
item: data.events.map((item) => {
const title = item.info.commentThread.resourceTitle;
const userId = item.user.userId;
const description = JSON.parse(item.json).msg;
const pics = item.pics.map(({ originUrl }) => originUrl);
const eventId = item.id;
/**
* @todo 根据 `item.info.commentThread.resourceInfo.eventType` 生成 Media
* 17 分享节目
* 18 分享单曲
* 19 分享专辑
* 35 空
* 因为我不需要,我就不写了。
* 因为 api 并没有 mp3 URL,生成 `media` 字段会有困难。
*/
return {
title,
description: renderDescription({ description, pics }),
link: `https://music.163.com/#/event?id=${eventId}&uid=${userId}`,
pubDate: new Date(item.eventTime),
published: new Date(item.eventTime),
author: nickname,
upvotes: item.info.likedCount,
comments: item.info.commentCount,
};
}),
});
};
+53
View File
@@ -0,0 +1,53 @@
// @ts-nocheck
import * as path from 'node:path';
import got from '@/utils/got';
import { art } from '@/utils/render';
const renderDescription = (info) => art(path.join(__dirname, '../templates/music/userevents.art'), info);
export default async (ctx) => {
const id = ctx.req.param('id');
const response = await got(`https://music.163.com/api/event/get/${id}`, {
headers: {
Referer: 'https://music.163.com/',
},
});
const { data } = response;
const { nickname, signature, avatarUrl } = data.events[0].user;
ctx.set('data', {
title: `${nickname}的云村动态`,
link: `https://music.163.com/#/user/event?id=${id}`,
description: `网易云音乐用户动态 - ${signature}`,
icon: avatarUrl,
image: avatarUrl,
item: data.events.map((item) => {
const title = item.info.commentThread.resourceTitle;
const userId = item.user.userId;
const description = JSON.parse(item.json).msg;
const pics = item.pics.map(({ originUrl }) => originUrl);
const eventId = item.id;
/**
* @todo 根据 `item.info.commentThread.resourceInfo.eventType` 生成 Media
* 17 分享节目
* 18 分享单曲
* 19 分享专辑
* 35 空
* 因为我不需要,我就不写了。
* 因为 api 并没有 mp3 URL,生成 `media` 字段会有困难。
*/
return {
title,
description: renderDescription({ description, pics }),
link: `https://music.163.com/#/event?id=${eventId}&uid=${userId}`,
pubDate: new Date(item.eventTime),
published: new Date(item.eventTime),
author: nickname,
upvotes: item.info.likedCount,
comments: item.info.commentCount,
};
}),
});
};
-56
View File
@@ -1,56 +0,0 @@
import got from '@/utils/got';
import { art } from '@/utils/render';
import * as path from 'node:path';
export default async (ctx) => {
const uid = ctx.req.param('uid');
const response = await got.post('https://music.163.com/api/user/playlist', {
headers: {
Referer: 'https://music.163.com/',
},
form: {
uid,
limit: 1000,
offset: 0,
},
});
const playlist = response.data.playlist || [];
const creator = (playlist[0] || {}).creator;
const { nickname, signature, avatarUrl } = creator;
ctx.set('data', {
title: `${nickname} 的所有歌单`,
link: `https://music.163.com/user/home?id=${uid}`,
subtitle: signature,
description: signature,
author: nickname,
updated: response.headers.date,
icon: avatarUrl,
image: avatarUrl,
item: playlist.map((pl) => {
const src = `http://music.163.com/playlist/${pl.id}`;
const html = art(path.join(__dirname, '../templates/music/userplaylist.art'), {
image: pl.coverImgUrl,
description: (pl.description || '').split('\n'),
src,
});
return {
title: pl.name,
link: src,
pubDate: new Date(pl.createTime).toUTCString(),
published: new Date(pl.createTime).toISOString(),
updated: new Date(pl.updateTime).toISOString(),
author: pl.creator.nickname,
description: html,
content: { html },
category: pl.tags,
};
}),
});
};
+57
View File
@@ -0,0 +1,57 @@
// @ts-nocheck
import got from '@/utils/got';
import { art } from '@/utils/render';
import * as path from 'node:path';
export default async (ctx) => {
const uid = ctx.req.param('uid');
const response = await got.post('https://music.163.com/api/user/playlist', {
headers: {
Referer: 'https://music.163.com/',
},
form: {
uid,
limit: 1000,
offset: 0,
},
});
const playlist = response.data.playlist || [];
const creator = (playlist[0] || {}).creator;
const { nickname, signature, avatarUrl } = creator;
ctx.set('data', {
title: `${nickname} 的所有歌单`,
link: `https://music.163.com/user/home?id=${uid}`,
subtitle: signature,
description: signature,
author: nickname,
updated: response.headers.date,
icon: avatarUrl,
image: avatarUrl,
item: playlist.map((pl) => {
const src = `http://music.163.com/playlist/${pl.id}`;
const html = art(path.join(__dirname, '../templates/music/userplaylist.art'), {
image: pl.coverImgUrl,
description: (pl.description || '').split('\n'),
src,
});
return {
title: pl.name,
link: src,
pubDate: new Date(pl.createTime).toUTCString(),
published: new Date(pl.createTime).toISOString(),
updated: new Date(pl.updateTime).toISOString(),
author: pl.creator.nickname,
description: html,
content: { html },
category: pl.tags,
};
}),
});
};
-55
View File
@@ -1,55 +0,0 @@
import got from '@/utils/got';
import { config } from '@/config';
import { art } from '@/utils/render';
import * as path from 'node:path';
const headers = {
cookie: config.ncm.cookies,
Referer: 'https://music.163.com/',
};
function getItem(records) {
if (!records || records.length === 0) {
return [
{
title: '暂无听歌排行',
},
];
}
return records.map((record, index) => {
const song = record.song;
const artists_paintext = song.ar.map((a) => a.name).join('/');
const html = art(path.join(__dirname, '../templates/music/userplayrecords.art'), {
index,
record,
song,
});
return {
title: `[${index + 1}] ${song.name} - ${artists_paintext}`,
link: `http://music.163.com/song?id=${song.id}`,
author: artists_paintext,
description: html,
};
});
}
export default async (ctx) => {
const uid = ctx.req.param('uid');
const type = Number.parseInt(ctx.req.param('type')) || 0;
const url = `https://music.163.com/api/v1/play/record?uid=${uid}&type=${type}`;
const response = await got(url, { headers });
const records = type === 1 ? response.data.weekData : response.data.allData;
ctx.set('data', {
title: `${type === 1 ? '听歌榜单(最近一周)' : '听歌榜单(所有时间}'} - ${uid}}`,
link: `https://music.163.com/user/home?id=${uid}`,
updated: response.headers.date,
item: getItem(records),
});
};
+56
View File
@@ -0,0 +1,56 @@
// @ts-nocheck
import got from '@/utils/got';
import { config } from '@/config';
import { art } from '@/utils/render';
import * as path from 'node:path';
const headers = {
cookie: config.ncm.cookies,
Referer: 'https://music.163.com/',
};
function getItem(records) {
if (!records || records.length === 0) {
return [
{
title: '暂无听歌排行',
},
];
}
return records.map((record, index) => {
const song = record.song;
const artists_paintext = song.ar.map((a) => a.name).join('/');
const html = art(path.join(__dirname, '../templates/music/userplayrecords.art'), {
index,
record,
song,
});
return {
title: `[${index + 1}] ${song.name} - ${artists_paintext}`,
link: `http://music.163.com/song?id=${song.id}`,
author: artists_paintext,
description: html,
};
});
}
export default async (ctx) => {
const uid = ctx.req.param('uid');
const type = Number.parseInt(ctx.req.param('type')) || 0;
const url = `https://music.163.com/api/v1/play/record?uid=${uid}&type=${type}`;
const response = await got(url, { headers });
const records = type === 1 ? response.data.weekData : response.data.allData;
ctx.set('data', {
title: `${type === 1 ? '听歌榜单(最近一周)' : '听歌榜单(所有时间}'} - ${uid}}`,
link: `https://music.163.com/user/home?id=${uid}`,
updated: response.headers.date,
item: getItem(records),
});
};
-156
View File
@@ -1,156 +0,0 @@
import cache from '@/utils/cache';
import got from '@/utils/got';
import { load } from 'cheerio';
const iconv = require('iconv-lite');
import { parseDate } from '@/utils/parse-date';
const rootUrl = 'https://news.163.com';
const config = {
whole: {
link: '/special/0001386F/rank_whole.html',
title: '全站',
},
news: {
link: '/special/0001386F/rank_news.html',
title: '新闻',
},
entertainment: {
link: '/special/0001386F/rank_ent.html',
title: '娱乐',
},
sports: {
link: '/special/0001386F/rank_sports.html',
title: '体育',
},
money: {
link: 'https://money.163.com/special/002526BH/rank.html',
title: '财经',
},
tech: {
link: '/special/0001386F/rank_tech.html',
title: '科技',
},
auto: {
link: '/special/0001386F/rank_auto.html',
title: '汽车',
},
lady: {
link: '/special/0001386F/rank_lady.html',
title: '女人',
},
house: {
link: '/special/0001386F/rank_house.html',
title: '房产',
},
game: {
link: '/special/0001386F/game_rank.html',
title: '游戏',
},
travel: {
link: '/special/0001386F/rank_travel.html',
title: '旅游',
},
edu: {
link: '/special/0001386F/rank_edu.html',
title: '教育',
},
};
const timeRange = {
hour: {
index: 0,
title: '1小时',
},
day: {
index: 1,
title: '24小时',
},
week: {
index: 2,
title: '本周',
},
month: {
index: 3,
title: '本月',
},
};
export default async (ctx) => {
const category = ctx.req.param('category') || 'whole';
const type = ctx.req.param('type') || 'click';
const time = ctx.req.param('time') || 'day';
const cfg = config[category];
if (!cfg) {
throw new Error('Bad category. See <a href="https://docs.rsshub.app/routes/new-media#wang-yi-xin-wen-pai-hang-bang">docs</a>');
} else if ((category !== 'whole' && type === 'click' && time === 'month') || (category === 'whole' && type === 'click' && time === 'hour') || (type === 'follow' && time === 'hour')) {
throw new Error('Bad timeRange range. See <a href="https://docs.rsshub.app/routes/new-media#wang-yi-xin-wen-pai-hang-bang">docs</a>');
}
const currentUrl = category === 'money' ? cfg.link : `${rootUrl}${cfg.link}`;
const response = await got({
method: 'get',
url: currentUrl,
responseType: 'buffer',
});
const $ = load(iconv.decode(response.data, 'gbk'));
const list = $('div.tabContents')
.eq(timeRange[time].index + (category === 'whole' ? (type === 'click' ? -1 : 2) : type === 'click' ? 0 : 2))
.find('table tbody tr td a')
.toArray()
.map((item) => {
item = $(item);
return {
link: item.attr('href'),
};
});
const items = await Promise.all(
list.map((item) =>
cache.tryGet(item.link, async () => {
try {
let link;
if (category === 'auto' || category === 'house' || category === 'travel') {
const category = item.link.split('.163.com')[0].split('//').pop().split('.').pop();
link = `https://3g.163.com/${category}/article/${item.link.split('/').pop()}`;
} else {
const pathname = new URL(item.link).pathname;
link = `https://3g.163.com${pathname}`;
}
const detailResponse = await got({
method: 'get',
url: link,
});
const content = load(detailResponse.data);
content('.bot_word, .js-open-app, .s-img').remove();
content('video').each(function () {
content(this).attr('src', content(this).attr('data-src'));
});
content('.article-body .image-lazy').each((_, elem) => {
elem.attribs.src = elem.attribs['data-src'] ?? elem.attribs.src;
});
item.title = content('meta[property="og:title"]').attr('content').replace('_手机网易网', '');
item.pubDate = parseDate(content('meta[property="og:release_date"]').attr('content'));
item.description = content('.article-body').html();
} catch {
return '';
}
return item;
})
)
);
ctx.set('data', {
title: `网易新闻${timeRange[time].title}${type === 'click' ? '点击' : '跟帖'}榜 - ${cfg.title}`,
link: currentUrl,
item: items.filter(Boolean),
});
};
+157
View File
@@ -0,0 +1,157 @@
// @ts-nocheck
import cache from '@/utils/cache';
import got from '@/utils/got';
import { load } from 'cheerio';
const iconv = require('iconv-lite');
import { parseDate } from '@/utils/parse-date';
const rootUrl = 'https://news.163.com';
const config = {
whole: {
link: '/special/0001386F/rank_whole.html',
title: '全站',
},
news: {
link: '/special/0001386F/rank_news.html',
title: '新闻',
},
entertainment: {
link: '/special/0001386F/rank_ent.html',
title: '娱乐',
},
sports: {
link: '/special/0001386F/rank_sports.html',
title: '体育',
},
money: {
link: 'https://money.163.com/special/002526BH/rank.html',
title: '财经',
},
tech: {
link: '/special/0001386F/rank_tech.html',
title: '科技',
},
auto: {
link: '/special/0001386F/rank_auto.html',
title: '汽车',
},
lady: {
link: '/special/0001386F/rank_lady.html',
title: '女人',
},
house: {
link: '/special/0001386F/rank_house.html',
title: '房产',
},
game: {
link: '/special/0001386F/game_rank.html',
title: '游戏',
},
travel: {
link: '/special/0001386F/rank_travel.html',
title: '旅游',
},
edu: {
link: '/special/0001386F/rank_edu.html',
title: '教育',
},
};
const timeRange = {
hour: {
index: 0,
title: '1小时',
},
day: {
index: 1,
title: '24小时',
},
week: {
index: 2,
title: '本周',
},
month: {
index: 3,
title: '本月',
},
};
export default async (ctx) => {
const category = ctx.req.param('category') || 'whole';
const type = ctx.req.param('type') || 'click';
const time = ctx.req.param('time') || 'day';
const cfg = config[category];
if (!cfg) {
throw new Error('Bad category. See <a href="https://docs.rsshub.app/routes/new-media#wang-yi-xin-wen-pai-hang-bang">docs</a>');
} else if ((category !== 'whole' && type === 'click' && time === 'month') || (category === 'whole' && type === 'click' && time === 'hour') || (type === 'follow' && time === 'hour')) {
throw new Error('Bad timeRange range. See <a href="https://docs.rsshub.app/routes/new-media#wang-yi-xin-wen-pai-hang-bang">docs</a>');
}
const currentUrl = category === 'money' ? cfg.link : `${rootUrl}${cfg.link}`;
const response = await got({
method: 'get',
url: currentUrl,
responseType: 'buffer',
});
const $ = load(iconv.decode(response.data, 'gbk'));
const list = $('div.tabContents')
.eq(timeRange[time].index + (category === 'whole' ? (type === 'click' ? -1 : 2) : type === 'click' ? 0 : 2))
.find('table tbody tr td a')
.toArray()
.map((item) => {
item = $(item);
return {
link: item.attr('href'),
};
});
const items = await Promise.all(
list.map((item) =>
cache.tryGet(item.link, async () => {
try {
let link;
if (category === 'auto' || category === 'house' || category === 'travel') {
const category = item.link.split('.163.com')[0].split('//').pop().split('.').pop();
link = `https://3g.163.com/${category}/article/${item.link.split('/').pop()}`;
} else {
const pathname = new URL(item.link).pathname;
link = `https://3g.163.com${pathname}`;
}
const detailResponse = await got({
method: 'get',
url: link,
});
const content = load(detailResponse.data);
content('.bot_word, .js-open-app, .s-img').remove();
content('video').each(function () {
content(this).attr('src', content(this).attr('data-src'));
});
content('.article-body .image-lazy').each((_, elem) => {
elem.attribs.src = elem.attribs['data-src'] ?? elem.attribs.src;
});
item.title = content('meta[property="og:title"]').attr('content').replace('_手机网易网', '');
item.pubDate = parseDate(content('meta[property="og:release_date"]').attr('content'));
item.description = content('.article-body').html();
} catch {
return '';
}
return item;
})
)
);
ctx.set('data', {
title: `网易新闻${timeRange[time].title}${type === 'click' ? '点击' : '跟帖'}榜 - ${cfg.title}`,
link: currentUrl,
item: items.filter(Boolean),
});
};
-115
View File
@@ -1,115 +0,0 @@
import cache from '@/utils/cache';
import got from '@/utils/got';
import { parseDate } from '@/utils/parse-date';
import { load } from 'cheerio';
const typeMap = {
1: '轻松一刻',
2: '槽值',
3: '人间',
4: '大国小民',
5: '三三有梗',
6: '数读',
7: '看客',
8: '下划线',
9: '谈心社',
10: '哒哒',
11: '胖编怪聊',
12: '曲一刀',
13: '今日之声',
14: '浪潮',
15: '沸点',
};
export default async (ctx) => {
if (!ctx.req.param('type')) {
throw new Error('Bad parameter. See <a href="https://docs.rsshub.app/routes/game#wang-yi-da-shen">https://docs.rsshub.app/routes/game#wang-yi-da-shen</a>');
}
const selectedType = Number.parseInt(ctx.req.param('type'));
let type;
switch (selectedType) {
case 1:
type = `BD21K0DLwangning`; // 轻松一刻
break;
case 2:
type = `CICMICLUwangning`; // 槽值
break;
case 3:
type = `CICMOMBLwangning`; // 人间
break;
case 4:
type = `CICMPVC5wangning`; // 大国小民
break;
case 5:
type = `CICMLCOUwangning`; // 三三有梗
break;
case 6:
type = `D551V75Cwangning`; // 数读
break;
case 7:
type = `D55253RHwangning`; // 看客
break;
case 8:
type = `D553A53Lwangning`; // 下划线
break;
case 9:
type = `D553PGHQwangning`; // 谈心社
break;
case 10:
type = `CICMS5BIwangning`; // 哒哒
break;
case 11:
type = `CQ9UDVKOwangning`; // 胖编怪聊
break;
case 12:
type = `CQ9UJIJNwangning`; // 曲一刀
break;
case 13:
type = `BD284UM8wangning`; // 今日之声
break;
case 14:
type = `CICMMGBHwangning`; // 浪潮
break;
case 15:
type = `D5543R68wangning`; // 沸点
break;
default:
break;
}
const url = `https://3g.163.com/touch/reconstruct/article/list/${type}/0-20.html`;
const response = await got(url);
const data = response.data;
const matches = data.replaceAll(/\s/g, '').match(/artiList\((.*?)]}\)/);
const articlelist0 = matches[1].replace(/".*?wangning/, '"articles') + ']}';
const articlelist = JSON.parse(articlelist0);
const articles = articlelist.articles;
const items = await Promise.all(
articles.map((article) => {
let url = article.url;
if (url === null || article.skipType === 'video') {
const skipurl = article.skipURL;
const vid = skipurl.match(/vid=(.*?)$/);
if (vid !== null) {
url = `https://3g.163.com/exclusive/video/${vid[1]}.html`;
}
}
return cache.tryGet(url, async () => {
const article_response = await got(url);
const $ = load(article_response.data);
article.link = url;
article.description = $('.article-body').html() || $('div[class="video"]').html();
article.pubDate = parseDate(article.ptime);
return article;
});
})
);
const selectedTypeName = typeMap[selectedType];
ctx.set('data', {
title: selectedTypeName ? `${selectedTypeName} - 网易专栏` : '网易专栏',
link: 'https://3g.163.com/touch/exclusive/?referFrom=163',
item: items,
});
};
+116
View File
@@ -0,0 +1,116 @@
// @ts-nocheck
import cache from '@/utils/cache';
import got from '@/utils/got';
import { parseDate } from '@/utils/parse-date';
import { load } from 'cheerio';
const typeMap = {
1: '轻松一刻',
2: '槽值',
3: '人间',
4: '大国小民',
5: '三三有梗',
6: '数读',
7: '看客',
8: '下划线',
9: '谈心社',
10: '哒哒',
11: '胖编怪聊',
12: '曲一刀',
13: '今日之声',
14: '浪潮',
15: '沸点',
};
export default async (ctx) => {
if (!ctx.req.param('type')) {
throw new Error('Bad parameter. See <a href="https://docs.rsshub.app/routes/game#wang-yi-da-shen">https://docs.rsshub.app/routes/game#wang-yi-da-shen</a>');
}
const selectedType = Number.parseInt(ctx.req.param('type'));
let type;
switch (selectedType) {
case 1:
type = `BD21K0DLwangning`; // 轻松一刻
break;
case 2:
type = `CICMICLUwangning`; // 槽值
break;
case 3:
type = `CICMOMBLwangning`; // 人间
break;
case 4:
type = `CICMPVC5wangning`; // 大国小民
break;
case 5:
type = `CICMLCOUwangning`; // 三三有梗
break;
case 6:
type = `D551V75Cwangning`; // 数读
break;
case 7:
type = `D55253RHwangning`; // 看客
break;
case 8:
type = `D553A53Lwangning`; // 下划线
break;
case 9:
type = `D553PGHQwangning`; // 谈心社
break;
case 10:
type = `CICMS5BIwangning`; // 哒哒
break;
case 11:
type = `CQ9UDVKOwangning`; // 胖编怪聊
break;
case 12:
type = `CQ9UJIJNwangning`; // 曲一刀
break;
case 13:
type = `BD284UM8wangning`; // 今日之声
break;
case 14:
type = `CICMMGBHwangning`; // 浪潮
break;
case 15:
type = `D5543R68wangning`; // 沸点
break;
default:
break;
}
const url = `https://3g.163.com/touch/reconstruct/article/list/${type}/0-20.html`;
const response = await got(url);
const data = response.data;
const matches = data.replaceAll(/\s/g, '').match(/artiList\((.*?)]}\)/);
const articlelist0 = matches[1].replace(/".*?wangning/, '"articles') + ']}';
const articlelist = JSON.parse(articlelist0);
const articles = articlelist.articles;
const items = await Promise.all(
articles.map((article) => {
let url = article.url;
if (url === null || article.skipType === 'video') {
const skipurl = article.skipURL;
const vid = skipurl.match(/vid=(.*?)$/);
if (vid !== null) {
url = `https://3g.163.com/exclusive/video/${vid[1]}.html`;
}
}
return cache.tryGet(url, async () => {
const article_response = await got(url);
const $ = load(article_response.data);
article.link = url;
article.description = $('.article-body').html() || $('div[class="video"]').html();
article.pubDate = parseDate(article.ptime);
return article;
});
})
);
const selectedTypeName = typeMap[selectedType];
ctx.set('data', {
title: selectedTypeName ? `${selectedTypeName} - 网易专栏` : '网易专栏',
link: 'https://3g.163.com/touch/exclusive/?referFrom=163',
item: items,
});
};
-64
View File
@@ -1,64 +0,0 @@
import cache from '@/utils/cache';
import got from '@/utils/got';
import { load } from 'cheerio';
import { parseDate } from '@/utils/parse-date';
import { art } from '@/utils/render';
import * as path from 'node:path';
export default async (ctx) => {
const url = 'https://vip.open.163.com';
const list_response = await got(url);
const $ = load(list_response.data);
const initialState = JSON.parse(
$('script')
.text()
.match(/window\.__INITIAL_STATE__=(.*);\(function\(\){var/)[1]
);
const list = Object.values(initialState.courseindex.myModules).flatMap((mod) =>
mod.contents.map((item) => ({
title: `${item.title} - ${item.subtitle}`,
author: item.authorName,
pubDate: parseDate(item.publishTime, 'x'),
link: `${url}/courses/${item.courseUid}/`,
courseUid: item.courseUid,
category: mod.name,
}))
);
const items = await Promise.all(
list.map((item) =>
cache.tryGet(item.link, async () => {
const {
data: { data },
} = await got.post(`${url}/open/trade/pc/course/getCourseInfo.do`, {
form: {
courseUid: item.courseUid,
version: 1,
},
});
const $ = load(data.courseInfo.description, null, false);
$('img').each((_, img) => {
img.attribs.src = img.attribs.src.split('?')[0];
delete img.attribs.width;
});
item.category = [item.category, data.courseInfo.firstClassifyName, data.courseInfo.secondClassifyName];
item.description = art(path.join(__dirname, '../templates/open.art'), {
data,
description: $.html(),
});
return item;
})
)
);
ctx.set('data', {
title: '网易公开课 - 精品课程',
link: url,
item: items,
});
};
+65
View File
@@ -0,0 +1,65 @@
// @ts-nocheck
import cache from '@/utils/cache';
import got from '@/utils/got';
import { load } from 'cheerio';
import { parseDate } from '@/utils/parse-date';
import { art } from '@/utils/render';
import * as path from 'node:path';
export default async (ctx) => {
const url = 'https://vip.open.163.com';
const list_response = await got(url);
const $ = load(list_response.data);
const initialState = JSON.parse(
$('script')
.text()
.match(/window\.__INITIAL_STATE__=(.*);\(function\(\){var/)[1]
);
const list = Object.values(initialState.courseindex.myModules).flatMap((mod) =>
mod.contents.map((item) => ({
title: `${item.title} - ${item.subtitle}`,
author: item.authorName,
pubDate: parseDate(item.publishTime, 'x'),
link: `${url}/courses/${item.courseUid}/`,
courseUid: item.courseUid,
category: mod.name,
}))
);
const items = await Promise.all(
list.map((item) =>
cache.tryGet(item.link, async () => {
const {
data: { data },
} = await got.post(`${url}/open/trade/pc/course/getCourseInfo.do`, {
form: {
courseUid: item.courseUid,
version: 1,
},
});
const $ = load(data.courseInfo.description, null, false);
$('img').each((_, img) => {
img.attribs.src = img.attribs.src.split('?')[0];
delete img.attribs.width;
});
item.category = [item.category, data.courseInfo.firstClassifyName, data.courseInfo.secondClassifyName];
item.description = art(path.join(__dirname, '../templates/open.art'), {
data,
description: $.html(),
});
return item;
})
)
);
ctx.set('data', {
title: '网易公开课 - 精品课程',
link: url,
item: items,
});
};
-78
View File
@@ -1,78 +0,0 @@
import cache from '@/utils/cache';
import got from '@/utils/got';
import { load } from 'cheerio';
const iconv = require('iconv-lite');
import { parseDate } from '@/utils/parse-date';
import timezone from '@/utils/timezone';
const titles = {
texie: '特写',
jishi: '记事',
daxie: '大写',
haodu: '好读',
kanke: '看客',
};
export default async (ctx) => {
const category = ctx.req.param('category') ?? 'texie';
const rootUrl = 'https://renjian.163.com';
const currentUrl = `${rootUrl}/special/renjian_${category}/`;
const response = await got({
method: 'get',
url: currentUrl,
responseType: 'buffer',
});
const data = iconv.decode(response.data, 'gbk');
let items = {};
const urls = data.match(/url:"(.*)",/g);
if (urls) {
items = urls.slice(0, ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 50).map((item) => ({
link: item.match(/url:"(.*)",/)[1],
}));
} else {
const $ = load(data);
items = $('.article h3 a')
.slice(0, ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 50)
.toArray()
.map((_, item) => {
item = $(item);
return {
link: item.attr('href'),
};
});
}
items = await Promise.all(
items.map((item) =>
cache.tryGet(item.link, async () => {
const detailResponse = await got({
method: 'get',
url: item.link,
});
const content = load(detailResponse.data);
item.title = content('h1').text();
item.author = content('script')
.text()
.match(/renjian_author = '(.*)'/)[1];
item.description = content('#endText').html() ?? content('#content').html();
item.pubDate = timezone(parseDate(content('.pub_time').text() ?? content('.post_info').text().split('来源:')[0].trim()), 8);
return item;
})
)
);
ctx.set('data', {
title: `人间 - ${titles[category]} - 网易新闻`,
link: currentUrl,
item: items,
});
};
+79
View File
@@ -0,0 +1,79 @@
// @ts-nocheck
import cache from '@/utils/cache';
import got from '@/utils/got';
import { load } from 'cheerio';
const iconv = require('iconv-lite');
import { parseDate } from '@/utils/parse-date';
import timezone from '@/utils/timezone';
const titles = {
texie: '特写',
jishi: '记事',
daxie: '大写',
haodu: '好读',
kanke: '看客',
};
export default async (ctx) => {
const category = ctx.req.param('category') ?? 'texie';
const rootUrl = 'https://renjian.163.com';
const currentUrl = `${rootUrl}/special/renjian_${category}/`;
const response = await got({
method: 'get',
url: currentUrl,
responseType: 'buffer',
});
const data = iconv.decode(response.data, 'gbk');
let items = {};
const urls = data.match(/url:"(.*)",/g);
if (urls) {
items = urls.slice(0, ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 50).map((item) => ({
link: item.match(/url:"(.*)",/)[1],
}));
} else {
const $ = load(data);
items = $('.article h3 a')
.slice(0, ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 50)
.toArray()
.map((_, item) => {
item = $(item);
return {
link: item.attr('href'),
};
});
}
items = await Promise.all(
items.map((item) =>
cache.tryGet(item.link, async () => {
const detailResponse = await got({
method: 'get',
url: item.link,
});
const content = load(detailResponse.data);
item.title = content('h1').text();
item.author = content('script')
.text()
.match(/renjian_author = '(.*)'/)[1];
item.description = content('#endText').html() ?? content('#content').html();
item.pubDate = timezone(parseDate(content('.pub_time').text() ?? content('.post_info').text().split('来源:')[0].trim()), 8);
return item;
})
)
);
ctx.set('data', {
title: `人间 - ${titles[category]} - 网易新闻`,
link: currentUrl,
item: items,
});
};
-56
View File
@@ -1,56 +0,0 @@
import cache from '@/utils/cache';
import got from '@/utils/got';
import { load } from 'cheerio';
import timezone from '@/utils/timezone';
import { parseDate } from '@/utils/parse-date';
export default async (ctx) => {
const needContent = /t|y/i.test(ctx.req.param('need_content') ?? 'true');
const rootUrl = 'https://gw.m.163.com';
const currentUrl = `${rootUrl}/nc/api/v1/feed/static/normal-list?start=0&tid=T1573700340788&size=${ctx.req.query('limit') ?? (needContent ? 30 : 200)}`;
const response = await got({
method: 'get',
url: currentUrl,
});
let items = response.data.data.items.map((item) => ({
title: item.title,
author: item.source,
pubDate: timezone(parseDate(item.ptime), +8),
description: `<p>${item.digest}</p><img src="${item.imgsrc}">`,
link: item.url || `https://c.m.163.com/news/a/${item.docid}.html`,
}));
if (needContent) {
items = await Promise.all(
items.map((item) =>
cache.tryGet(item.link, async () => {
const detailResponse = await got({
method: 'get',
url: item.link,
});
const content = load(detailResponse.data);
content('.bot_word').remove();
content('img').each((_, img) => {
img.attribs.src = img.attribs['data-src'] ?? img.attribs.src;
});
item.description = content('.content, article').html();
return item;
})
)
);
}
ctx.set('data', {
title: '今日关注 - 网易新闻',
link: 'https://wp.m.163.com/163/html/newsapp/todayFocus/index.html',
item: items,
});
};
+57
View File
@@ -0,0 +1,57 @@
// @ts-nocheck
import cache from '@/utils/cache';
import got from '@/utils/got';
import { load } from 'cheerio';
import timezone from '@/utils/timezone';
import { parseDate } from '@/utils/parse-date';
export default async (ctx) => {
const needContent = /t|y/i.test(ctx.req.param('need_content') ?? 'true');
const rootUrl = 'https://gw.m.163.com';
const currentUrl = `${rootUrl}/nc/api/v1/feed/static/normal-list?start=0&tid=T1573700340788&size=${ctx.req.query('limit') ?? (needContent ? 30 : 200)}`;
const response = await got({
method: 'get',
url: currentUrl,
});
let items = response.data.data.items.map((item) => ({
title: item.title,
author: item.source,
pubDate: timezone(parseDate(item.ptime), +8),
description: `<p>${item.digest}</p><img src="${item.imgsrc}">`,
link: item.url || `https://c.m.163.com/news/a/${item.docid}.html`,
}));
if (needContent) {
items = await Promise.all(
items.map((item) =>
cache.tryGet(item.link, async () => {
const detailResponse = await got({
method: 'get',
url: item.link,
});
const content = load(detailResponse.data);
content('.bot_word').remove();
content('img').each((_, img) => {
img.attribs.src = img.attribs['data-src'] ?? img.attribs.src;
});
item.description = content('.content, article').html();
return item;
})
)
);
}
ctx.set('data', {
title: '今日关注 - 网易新闻',
link: 'https://wp.m.163.com/163/html/newsapp/todayFocus/index.html',
item: items,
});
};
-40
View File
@@ -1,40 +0,0 @@
import got from '@/utils/got';
import { load } from 'cheerio';
const iconv = require('iconv-lite');
import { art } from '@/utils/render';
import * as path from 'node:path';
const parseDyArticle = (charset, item, tryGet) =>
tryGet(item.link, async () => {
const response = await got(item.link, {
responseType: 'buffer',
});
const html = iconv.decode(response.data, charset);
const $ = load(html);
$('.post_main img').each((_, i) => {
if (!i.attribs.src) {
return;
}
const url = new URL(i.attribs.src);
if (url.host === 'nimg.ws.126.net') {
i.attribs.src = url.searchParams.get('url');
}
});
item.description = art(path.join(__dirname, 'templates/dy.art'), {
imgsrc: item.imgsrc?.split('?')[0],
postBody: $('.post_body').html(),
});
item.feedLink = $('.post_wemedia_name a').attr('href');
item.feedDescription = $('.post_wemedia_title').text();
item.feedImage = $('.post_wemedia_avatar img').attr('src');
return item;
});
module.exports = {
parseDyArticle,
};
+41
View File
@@ -0,0 +1,41 @@
// @ts-nocheck
import got from '@/utils/got';
import { load } from 'cheerio';
const iconv = require('iconv-lite');
import { art } from '@/utils/render';
import * as path from 'node:path';
const parseDyArticle = (charset, item, tryGet) =>
tryGet(item.link, async () => {
const response = await got(item.link, {
responseType: 'buffer',
});
const html = iconv.decode(response.data, charset);
const $ = load(html);
$('.post_main img').each((_, i) => {
if (!i.attribs.src) {
return;
}
const url = new URL(i.attribs.src);
if (url.host === 'nimg.ws.126.net') {
i.attribs.src = url.searchParams.get('url');
}
});
item.description = art(path.join(__dirname, 'templates/dy.art'), {
imgsrc: item.imgsrc?.split('?')[0],
postBody: $('.post_body').html(),
});
item.feedLink = $('.post_wemedia_name a').attr('href');
item.feedDescription = $('.post_wemedia_title').text();
item.feedImage = $('.post_wemedia_avatar img').attr('src');
return item;
});
module.exports = {
parseDyArticle,
};
-79
View File
@@ -1,79 +0,0 @@
import cache from '@/utils/cache';
import got from '@/utils/got';
import { load } from 'cheerio';
import { parseDate } from '@/utils/parse-date';
const { defaultDomain, getRootUrl } = require('./utils');
export default async (ctx) => {
const id = ctx.req.param('id');
const { domain = defaultDomain } = ctx.req.query();
const rootUrl = getRootUrl(domain);
const currentUrl = `${rootUrl}/album/${id}`;
const response = await got({
method: 'get',
url: currentUrl,
});
const $ = load(response.data);
const category = $('span[data-type="tags"]')
.first()
.find('a')
.toArray()
.map((c) => $(c).text());
const author = $('span[data-type="author"]')
.first()
.find('a')
.toArray()
.map((a) => $(a).text())
.join(', ');
let items = $('.btn-toolbar')
.first()
.find('a')
.toArray()
.map((item) => {
item = $(item);
return {
title: item.text(),
link: `${rootUrl}${item.attr('href')}`,
guid: `https://18comic.org${item.attr('href')}`,
pubDate: parseDate(item.find('.hidden-xs').text()),
};
});
items = await Promise.all(
items.map((item) =>
cache.tryGet(item.guid, async () => {
const detailResponse = await got({
method: 'get',
url: item.link,
});
const content = load(detailResponse.data);
content('.tab-content').remove();
item.author = author;
item.category = category;
item.description = `<img src="${content('.thumb-overlay-albums img[data-original]')
.toArray()
.map((image) => content(image).attr('data-original'))
.join('"><img src="')}">`;
return item;
})
)
);
ctx.set('data', {
title: $('title').text(),
link: currentUrl,
item: items,
description: $('meta[property="og:description"]').attr('content'),
});
};
+80
View File
@@ -0,0 +1,80 @@
// @ts-nocheck
import cache from '@/utils/cache';
import got from '@/utils/got';
import { load } from 'cheerio';
import { parseDate } from '@/utils/parse-date';
const { defaultDomain, getRootUrl } = require('./utils');
export default async (ctx) => {
const id = ctx.req.param('id');
const { domain = defaultDomain } = ctx.req.query();
const rootUrl = getRootUrl(domain);
const currentUrl = `${rootUrl}/album/${id}`;
const response = await got({
method: 'get',
url: currentUrl,
});
const $ = load(response.data);
const category = $('span[data-type="tags"]')
.first()
.find('a')
.toArray()
.map((c) => $(c).text());
const author = $('span[data-type="author"]')
.first()
.find('a')
.toArray()
.map((a) => $(a).text())
.join(', ');
let items = $('.btn-toolbar')
.first()
.find('a')
.toArray()
.map((item) => {
item = $(item);
return {
title: item.text(),
link: `${rootUrl}${item.attr('href')}`,
guid: `https://18comic.org${item.attr('href')}`,
pubDate: parseDate(item.find('.hidden-xs').text()),
};
});
items = await Promise.all(
items.map((item) =>
cache.tryGet(item.guid, async () => {
const detailResponse = await got({
method: 'get',
url: item.link,
});
const content = load(detailResponse.data);
content('.tab-content').remove();
item.author = author;
item.category = category;
item.description = `<img src="${content('.thumb-overlay-albums img[data-original]')
.toArray()
.map((image) => content(image).attr('data-original'))
.join('"><img src="')}">`;
return item;
})
)
);
ctx.set('data', {
title: $('title').text(),
link: currentUrl,
item: items,
description: $('meta[property="og:description"]').attr('content'),
});
};
-67
View File
@@ -1,67 +0,0 @@
import cache from '@/utils/cache';
import got from '@/utils/got';
import { load } from 'cheerio';
import { parseDate } from '@/utils/parse-date';
const { defaultDomain, getRootUrl } = require('./utils');
export default async (ctx) => {
const category = ctx.req.param('category') ?? '';
const { domain = defaultDomain } = ctx.req.query();
const rootUrl = getRootUrl(domain);
const currentUrl = `${rootUrl}/blogs${category ? `/${category}` : ''}`;
const response = await got({
method: 'get',
url: currentUrl,
});
const $ = load(response.data);
let items = $('.title')
.toArray()
.map((item) => {
item = $(item);
return {
title: item.text(),
link: `${rootUrl}${item.parent().attr('href')}`,
guid: `https://18comic.org${item.parent().attr('href')}`,
};
});
items = await Promise.all(
items.map((item) =>
cache.tryGet(item.guid, async () => {
const detailResponse = await got({
method: 'get',
url: item.link,
});
const content = load(detailResponse.data);
item.pubDate = parseDate(content('.date').first().text());
content('.d-flex').remove();
item.author = content('.blog_name_id').first().text();
item.description = content('.blog_content').html();
item.category = content('.panel-heading dropdown-toggle')
.toArray()
.map((c) => $(c).text());
return item;
})
)
);
ctx.set('data', {
title: $('title')
.text()
.replace(/最新的/, $('.article-nav .active').text()),
link: currentUrl,
item: items,
description: $('meta[property="og:description"]').attr('content'),
});
};
+68
View File
@@ -0,0 +1,68 @@
// @ts-nocheck
import cache from '@/utils/cache';
import got from '@/utils/got';
import { load } from 'cheerio';
import { parseDate } from '@/utils/parse-date';
const { defaultDomain, getRootUrl } = require('./utils');
export default async (ctx) => {
const category = ctx.req.param('category') ?? '';
const { domain = defaultDomain } = ctx.req.query();
const rootUrl = getRootUrl(domain);
const currentUrl = `${rootUrl}/blogs${category ? `/${category}` : ''}`;
const response = await got({
method: 'get',
url: currentUrl,
});
const $ = load(response.data);
let items = $('.title')
.toArray()
.map((item) => {
item = $(item);
return {
title: item.text(),
link: `${rootUrl}${item.parent().attr('href')}`,
guid: `https://18comic.org${item.parent().attr('href')}`,
};
});
items = await Promise.all(
items.map((item) =>
cache.tryGet(item.guid, async () => {
const detailResponse = await got({
method: 'get',
url: item.link,
});
const content = load(detailResponse.data);
item.pubDate = parseDate(content('.date').first().text());
content('.d-flex').remove();
item.author = content('.blog_name_id').first().text();
item.description = content('.blog_content').html();
item.category = content('.panel-heading dropdown-toggle')
.toArray()
.map((c) => $(c).text());
return item;
})
)
);
ctx.set('data', {
title: $('title')
.text()
.replace(/最新的/, $('.article-nav .active').text()),
link: currentUrl,
item: items,
description: $('meta[property="og:description"]').attr('content'),
});
};
-14
View File
@@ -1,14 +0,0 @@
const { defaultDomain, getRootUrl, ProcessItems } = require('./utils');
export default async (ctx) => {
const category = ctx.req.param('category') ?? 'all';
const keyword = ctx.req.param('keyword') ?? '';
const time = ctx.req.param('time') ?? 'a';
const order = ctx.req.param('order') ?? 'mr';
const { domain = defaultDomain } = ctx.req.query();
const rootUrl = getRootUrl(domain);
const currentUrl = `${rootUrl}/albums${category === 'all' ? '' : `/${category}`}${keyword ? `?screen=${keyword}` : '?'}${time === 'a' ? '' : `&t=${time}`}${order === 'mr' ? '' : `&o=${order}`}`;
ctx.set('data', await ProcessItems(ctx, currentUrl, rootUrl));
};
+15
View File
@@ -0,0 +1,15 @@
// @ts-nocheck
const { defaultDomain, getRootUrl, ProcessItems } = require('./utils');
export default async (ctx) => {
const category = ctx.req.param('category') ?? 'all';
const keyword = ctx.req.param('keyword') ?? '';
const time = ctx.req.param('time') ?? 'a';
const order = ctx.req.param('order') ?? 'mr';
const { domain = defaultDomain } = ctx.req.query();
const rootUrl = getRootUrl(domain);
const currentUrl = `${rootUrl}/albums${category === 'all' ? '' : `/${category}`}${keyword ? `?screen=${keyword}` : '?'}${time === 'a' ? '' : `&t=${time}`}${order === 'mr' ? '' : `&o=${order}`}`;
ctx.set('data', await ProcessItems(ctx, currentUrl, rootUrl));
};
-15
View File
@@ -1,15 +0,0 @@
const { defaultDomain, getRootUrl, ProcessItems } = require('./utils');
export default async (ctx) => {
const option = ctx.req.param('option') ?? 'photos';
const category = ctx.req.param('category') ?? 'all';
const keyword = ctx.req.param('keyword') ?? '';
const time = ctx.req.param('time') ?? 'a';
const order = ctx.req.param('order') ?? 'mr';
const { domain = defaultDomain } = ctx.req.query();
const rootUrl = getRootUrl(domain);
const currentUrl = `${rootUrl}/search/${option}${category === 'all' ? '' : `/${category}`}${keyword ? `?search_query=${keyword}` : '?'}${time === 'a' ? '' : `&t=${time}`}${order === 'mr' ? '' : `&o=${order}`}`;
ctx.set('data', await ProcessItems(ctx, currentUrl, rootUrl));
};
+16
View File
@@ -0,0 +1,16 @@
// @ts-nocheck
const { defaultDomain, getRootUrl, ProcessItems } = require('./utils');
export default async (ctx) => {
const option = ctx.req.param('option') ?? 'photos';
const category = ctx.req.param('category') ?? 'all';
const keyword = ctx.req.param('keyword') ?? '';
const time = ctx.req.param('time') ?? 'a';
const order = ctx.req.param('order') ?? 'mr';
const { domain = defaultDomain } = ctx.req.query();
const rootUrl = getRootUrl(domain);
const currentUrl = `${rootUrl}/search/${option}${category === 'all' ? '' : `/${category}`}${keyword ? `?search_query=${keyword}` : '?'}${time === 'a' ? '' : `&t=${time}`}${order === 'mr' ? '' : `&o=${order}`}`;
ctx.set('data', await ProcessItems(ctx, currentUrl, rootUrl));
};
-83
View File
@@ -1,83 +0,0 @@
import cache from '@/utils/cache';
import got from '@/utils/got';
import { load } from 'cheerio';
import { parseDate } from '@/utils/parse-date';
import { art } from '@/utils/render';
import * as path from 'node:path';
import { config } from '@/config';
const defaultDomain = 'jmcomic1.me';
// list of address: https://jmcomic2.bet
const allowDomain = new Set(['18comic.vip', '18comic.org', 'jmcomic.me', 'jmcomic1.me', 'jm-comic3.art', 'jm-comic.club', 'jm-comic2.ark']);
const getRootUrl = (domain) => {
if (!config.feature.allow_user_supply_unsafe_domain && !allowDomain.has(domain)) {
throw new Error(`This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`);
}
return `https://${domain}`;
};
module.exports = {
defaultDomain,
getRootUrl,
ProcessItems: async (ctx, currentUrl, rootUrl) => {
currentUrl = currentUrl.replace(/\?$/, '');
const response = await got(currentUrl);
const $ = load(response.data);
let items = $('.video-title')
.slice(0, ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 20)
.toArray()
.map((item) => {
item = $(item);
return {
title: item.text().trim(),
link: `${rootUrl}${item.prev().find('a').attr('href')}`,
guid: `18comic:${item.prev().find('a').attr('href')}`,
};
});
items = await Promise.all(
items.map((item) =>
cache.tryGet(item.guid, async () => {
const detailResponse = await got(item.link);
const content = load(detailResponse.data);
item.pubDate = parseDate(content('div[itemprop="datePublished"]').first().attr('content'));
item.category = content('span[data-type="tags"]')
.first()
.find('a')
.toArray()
.map((c) => $(c).text());
item.author = content('span[data-type="author"]')
.first()
.find('a')
.toArray()
.map((a) => $(a).text())
.join(', ');
item.description = art(path.join(__dirname, 'templates/description.art'), {
introduction: content('#intro-block .p-t-5').text(),
images: content('.img_zoom_img img')
.toArray()
.map((image) => content(image).attr('data-original')),
});
return item;
})
)
);
return {
title: $('title').text(),
link: currentUrl,
item: items,
description: $('meta[property="og:description"]').attr('content'),
allowEmpty: true,
};
},
};
+84
View File
@@ -0,0 +1,84 @@
// @ts-nocheck
import cache from '@/utils/cache';
import got from '@/utils/got';
import { load } from 'cheerio';
import { parseDate } from '@/utils/parse-date';
import { art } from '@/utils/render';
import * as path from 'node:path';
import { config } from '@/config';
const defaultDomain = 'jmcomic1.me';
// list of address: https://jmcomic2.bet
const allowDomain = new Set(['18comic.vip', '18comic.org', 'jmcomic.me', 'jmcomic1.me', 'jm-comic3.art', 'jm-comic.club', 'jm-comic2.ark']);
const getRootUrl = (domain) => {
if (!config.feature.allow_user_supply_unsafe_domain && !allowDomain.has(domain)) {
throw new Error(`This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`);
}
return `https://${domain}`;
};
module.exports = {
defaultDomain,
getRootUrl,
ProcessItems: async (ctx, currentUrl, rootUrl) => {
currentUrl = currentUrl.replace(/\?$/, '');
const response = await got(currentUrl);
const $ = load(response.data);
let items = $('.video-title')
.slice(0, ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 20)
.toArray()
.map((item) => {
item = $(item);
return {
title: item.text().trim(),
link: `${rootUrl}${item.prev().find('a').attr('href')}`,
guid: `18comic:${item.prev().find('a').attr('href')}`,
};
});
items = await Promise.all(
items.map((item) =>
cache.tryGet(item.guid, async () => {
const detailResponse = await got(item.link);
const content = load(detailResponse.data);
item.pubDate = parseDate(content('div[itemprop="datePublished"]').first().attr('content'));
item.category = content('span[data-type="tags"]')
.first()
.find('a')
.toArray()
.map((c) => $(c).text());
item.author = content('span[data-type="author"]')
.first()
.find('a')
.toArray()
.map((a) => $(a).text())
.join(', ');
item.description = art(path.join(__dirname, 'templates/description.art'), {
introduction: content('#intro-block .p-t-5').text(),
images: content('.img_zoom_img img')
.toArray()
.map((image) => content(image).attr('data-original')),
});
return item;
})
)
);
return {
title: $('title').text(),
link: currentUrl,
item: items,
description: $('meta[property="og:description"]').attr('content'),
allowEmpty: true,
};
},
};
-79
View File
@@ -1,79 +0,0 @@
import cache from '@/utils/cache';
import got from '@/utils/got';
import { load } from 'cheerio';
import timezone from '@/utils/timezone';
import { parseDate } from '@/utils/parse-date';
const iconv = require('iconv-lite');
import { isValidHost } from '@/utils/valid-host';
const setCookie = function (cookieName, cookieValue, seconds, path, domain, secure) {
let expires = null;
if (seconds !== -1) {
expires = new Date();
expires.setTime(expires.getTime() + seconds);
}
return [encodeURI(cookieName), '=', encodeURI(cookieValue), expires ? '; expires=' + expires.toGMTString() : '', path ? '; path=' + path : '/', domain ? '; domain=' + domain : '', secure ? '; secure' : ''].join('');
};
export default async (ctx) => {
const city = ctx.req.param('city') ?? 'www';
if (!isValidHost(city)) {
throw new Error('Invalid city');
}
const rootUrl = `https://${city}.19lou.com`;
const response = await got({
method: 'get',
url: rootUrl,
responseType: 'buffer',
});
const $ = load(iconv.decode(response.data, 'gbk'));
$('.title-more').remove();
let items = $('.center-center-jiazi')
.find('a[title]')
.toArray()
.map((item) => {
item = $(item);
return {
title: item.attr('title'),
link: `https:${item.attr('href')}`,
};
});
items = await Promise.all(
items.map((item) =>
cache.tryGet(item.link, async () => {
const detailResponse = await got({
method: 'get',
url: item.link,
responseType: 'buffer',
headers: {
cookie: setCookie('_Z3nY0d4C_', '37XgPK9h', 365, '/', '19lou.com'),
referer: rootUrl,
},
});
const content = load(iconv.decode(detailResponse.data, 'gbk'));
content('.name-lz, .postView-pk-mod').remove();
item.author = content('.uname, .user-name').first().text();
item.description = content('.post-cont').first().html() || content('.thread-cont').html();
item.pubDate = timezone(parseDate(content('.cont-top-left meta').first().attr('content')), +8);
return item;
})
)
);
ctx.set('data', {
title: $('title').text().split('-')[0],
link: rootUrl,
item: items,
});
};
+80
View File
@@ -0,0 +1,80 @@
// @ts-nocheck
import cache from '@/utils/cache';
import got from '@/utils/got';
import { load } from 'cheerio';
import timezone from '@/utils/timezone';
import { parseDate } from '@/utils/parse-date';
const iconv = require('iconv-lite');
import { isValidHost } from '@/utils/valid-host';
const setCookie = function (cookieName, cookieValue, seconds, path, domain, secure) {
let expires = null;
if (seconds !== -1) {
expires = new Date();
expires.setTime(expires.getTime() + seconds);
}
return [encodeURI(cookieName), '=', encodeURI(cookieValue), expires ? '; expires=' + expires.toGMTString() : '', path ? '; path=' + path : '/', domain ? '; domain=' + domain : '', secure ? '; secure' : ''].join('');
};
export default async (ctx) => {
const city = ctx.req.param('city') ?? 'www';
if (!isValidHost(city)) {
throw new Error('Invalid city');
}
const rootUrl = `https://${city}.19lou.com`;
const response = await got({
method: 'get',
url: rootUrl,
responseType: 'buffer',
});
const $ = load(iconv.decode(response.data, 'gbk'));
$('.title-more').remove();
let items = $('.center-center-jiazi')
.find('a[title]')
.toArray()
.map((item) => {
item = $(item);
return {
title: item.attr('title'),
link: `https:${item.attr('href')}`,
};
});
items = await Promise.all(
items.map((item) =>
cache.tryGet(item.link, async () => {
const detailResponse = await got({
method: 'get',
url: item.link,
responseType: 'buffer',
headers: {
cookie: setCookie('_Z3nY0d4C_', '37XgPK9h', 365, '/', '19lou.com'),
referer: rootUrl,
},
});
const content = load(iconv.decode(detailResponse.data, 'gbk'));
content('.name-lz, .postView-pk-mod').remove();
item.author = content('.uname, .user-name').first().text();
item.description = content('.post-cont').first().html() || content('.thread-cont').html();
item.pubDate = timezone(parseDate(content('.cont-top-left meta').first().attr('content')), +8);
return item;
})
)
);
ctx.set('data', {
title: $('title').text().split('-')[0],
link: rootUrl,
item: items,
});
};
-54
View File
@@ -1,54 +0,0 @@
import cache from '@/utils/cache';
import got from '@/utils/got';
import { load } from 'cheerio';
import timezone from '@/utils/timezone';
import { parseDate } from '@/utils/parse-date';
export default async (ctx) => {
const path = ctx.req.param('path') ?? '';
const rootUrl = `https://www.1lou.me`;
const currentUrl = `${rootUrl}/${path}`;
const response = await got({
method: 'get',
url: currentUrl,
});
const $ = 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) =>
cache.tryGet(item.link, async () => {
const detailResponse = await got({
method: 'get',
url: item.link,
});
const content = 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.set('data', {
title: '1Lou',
link: currentUrl,
item: items,
});
};
+55
View File
@@ -0,0 +1,55 @@
// @ts-nocheck
import cache from '@/utils/cache';
import got from '@/utils/got';
import { load } from 'cheerio';
import timezone from '@/utils/timezone';
import { parseDate } from '@/utils/parse-date';
export default async (ctx) => {
const path = ctx.req.param('path') ?? '';
const rootUrl = `https://www.1lou.me`;
const currentUrl = `${rootUrl}/${path}`;
const response = await got({
method: 'get',
url: currentUrl,
});
const $ = 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) =>
cache.tryGet(item.link, async () => {
const detailResponse = await got({
method: 'get',
url: item.link,
});
const content = 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.set('data', {
title: '1Lou',
link: currentUrl,
item: items,
});
};
-67
View File
@@ -1,67 +0,0 @@
import got from '@/utils/got';
import { load } from 'cheerio';
import { parseDate } from '@/utils/parse-date';
export default async (ctx) => {
const categoryMap = {
studyinusa: {
title: '留学申请',
id: 18,
},
career: {
title: '找工求职',
id: 200,
},
lifestyle: {
title: '生活攻略',
id: 370,
},
invest: {
title: '投资理财',
id: 371,
},
visa: {
title: '签证移民',
id: 194,
},
news: {
title: '时政要闻',
id: 366,
},
};
const category = ctx.req.param('category');
const rootUrl = 'https://blog.1point3acres.com';
const currentUrl = `${rootUrl}/${category}/`;
const { data } = await got(`${rootUrl}/wp-json/wp/v2/posts`, {
searchParams: {
categories: category ? categoryMap[category].id : undefined,
per_page: ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 100,
},
});
const items = data.map((item) => {
const $ = load(item.content.rendered, null, false);
$('h2').nextAll().remove();
$('[powered-by="1p3a"], h2').remove();
$('img').each((_, img) => {
if (/wp-content\/uploads/.test(img.attribs.src)) {
img.attribs.src = img.attribs.src.replace(/(-\d+x\d+)/, '');
}
});
return {
title: item.title.rendered,
description: $.html(),
link: item.link,
pubDate: parseDate(item.date_gmt),
};
});
ctx.set('data', {
title: `${category ? `${categoryMap[category].title} | ` : ''}美国留学就业生活攻略`,
link: currentUrl,
item: items,
});
};
+68
View File
@@ -0,0 +1,68 @@
// @ts-nocheck
import got from '@/utils/got';
import { load } from 'cheerio';
import { parseDate } from '@/utils/parse-date';
export default async (ctx) => {
const categoryMap = {
studyinusa: {
title: '留学申请',
id: 18,
},
career: {
title: '找工求职',
id: 200,
},
lifestyle: {
title: '生活攻略',
id: 370,
},
invest: {
title: '投资理财',
id: 371,
},
visa: {
title: '签证移民',
id: 194,
},
news: {
title: '时政要闻',
id: 366,
},
};
const category = ctx.req.param('category');
const rootUrl = 'https://blog.1point3acres.com';
const currentUrl = `${rootUrl}/${category}/`;
const { data } = await got(`${rootUrl}/wp-json/wp/v2/posts`, {
searchParams: {
categories: category ? categoryMap[category].id : undefined,
per_page: ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 100,
},
});
const items = data.map((item) => {
const $ = load(item.content.rendered, null, false);
$('h2').nextAll().remove();
$('[powered-by="1p3a"], h2').remove();
$('img').each((_, img) => {
if (/wp-content\/uploads/.test(img.attribs.src)) {
img.attribs.src = img.attribs.src.replace(/(-\d+x\d+)/, '');
}
});
return {
title: item.title.rendered,
description: $.html(),
link: item.link,
pubDate: parseDate(item.date_gmt),
};
});
ctx.set('data', {
title: `${category ? `${categoryMap[category].title} | ` : ''}美国留学就业生活攻略`,
link: currentUrl,
item: items,
});
};
-18
View File
@@ -1,18 +0,0 @@
import cache from '@/utils/cache';
const { rootUrl, apiRootUrl, types, ProcessThreads } = require('./utils');
export default async (ctx) => {
const id = ctx.req.param('id') ?? '';
const type = ctx.req.param('type') ?? 'hot';
const order = ctx.req.param('order') ?? '';
const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 10;
const currentUrl = `${rootUrl}${id ? `/category/${id}` : ''}`;
const apiUrl = `${apiRootUrl}/api${id ? `/tags/${id}/` : ''}threads?type=${type}&includes=tags,forum_name,summary&ps=${limit}&pg=1&order=${order === '' ? '' : 'time_desc'}&is_groupid=1`;
ctx.set('data', {
title: `一亩三分地 - ${id}${types[type]}`,
link: currentUrl,
item: await ProcessThreads(cache.tryGet, apiUrl, order),
});
};
+19
View File
@@ -0,0 +1,19 @@
// @ts-nocheck
import cache from '@/utils/cache';
const { rootUrl, apiRootUrl, types, ProcessThreads } = require('./utils');
export default async (ctx) => {
const id = ctx.req.param('id') ?? '';
const type = ctx.req.param('type') ?? 'hot';
const order = ctx.req.param('order') ?? '';
const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 10;
const currentUrl = `${rootUrl}${id ? `/category/${id}` : ''}`;
const apiUrl = `${apiRootUrl}/api${id ? `/tags/${id}/` : ''}threads?type=${type}&includes=tags,forum_name,summary&ps=${limit}&pg=1&order=${order === '' ? '' : 'time_desc'}&is_groupid=1`;
ctx.set('data', {
title: `一亩三分地 - ${id}${types[type]}`,
link: currentUrl,
item: await ProcessThreads(cache.tryGet, apiUrl, order),
});
};
-69
View File
@@ -1,69 +0,0 @@
import got from '@/utils/got';
import { art } from '@/utils/render';
import { parseDate } from '@/utils/parse-date';
import * as path from 'node:path';
export default async (ctx) => {
// year 2017-2022
// 2017:6 2018:11 2019:12 2020:13 2021:14 2022:15
// CS:1 MIS:2
const { year = 'null', major = 'null', school = 'null' } = ctx.req.param();
// const filter = 'filters: {planyr: "12", planmajor: "1", outname_w: "CMU"}';
const responseBasic = await got.post('https://api.1point3acres.com/offer/results', {
searchParams: {
ps: 15,
pg: 1,
},
json: {
filters: {
planyr: year === 'null' ? undefined : year,
planmajor: major === 'null' ? undefined : major,
outname_w: school === 'null' ? undefined : school,
},
},
});
const data = responseBasic.data.results;
// data.id-> 访问offer具体信息->获取 data.tid
// if (data.id !== 0) {
// out = await Promise.all(
// data.map(async (item) => {
// var gettidresponse = await got({
// method: 'get',
// url: 'https://api.1point3acres.com/offer/results/'+ item.id + '/backgrounds',
// headers: {
// authorization: 'eyJhbGciOiJIUzUxMiIsImlhdCI6MTU3Njk5Njc5OSwiZXhwIjoxNTg0ODU5MTk5fQ.eyJ1aWQiOjQ1NzQyN30.0ei5UE6OgLBzN2_IS7xUIbIfW_S1Wzl42q2UeusbboxuzvctO_4Mz6YRr6f0PBLUVZMETxt8F0_4-yqIJ3_kUQ',
// },
// });
// var tid = gettidresponse.data.background.tid;
// //https: //www.1point3acres.com/bbs/thread-581177-1-1.html
// console.log(tid);
// const threadlink = 'https://www.1point3acres.com/bbs/thread-' + tid + '-1-1.html';
// console.log(threadlink);
// return threadlink;
// })
// );
// }
// let responseBasic_1;
// responseBasic_1 = await got({
// method: 'get',
// url: `https://api.1point3acres.com/offer/results/A7m20e4g/backgrounds`,
// headers: {
// authorization: `eyJhbGciOiJIUzUxMiIsImlhdCI6MTU3Njk5Njc5OSwiZXhwIjoxNTg0ODU5MTk5fQ.eyJ1aWQiOjQ1NzQyN30.0ei5UE6OgLBzN2_IS7xUIbIfW_S1Wzl42q2UeusbboxuzvctO_4Mz6YRr6f0PBLUVZMETxt8F0_4-yqIJ3_kUQ`,
// },
// });
// const tid = responseBasic_1.data.background.tid;
ctx.set('data', {
title: '录取结果 - 一亩三分地',
link: 'https://offer.1point3acres.com',
item: data.map((item) => ({
title: `${item.planyr}${item.planmajor}@${item.outname_w}${item.result} - 一亩三分地`,
description: art(path.join(__dirname, 'templates/offer.art'), {
item,
}),
pubDate: parseDate(item.dateline, 'X'),
link: 'https://offer.1point3acres.com',
guid: `1point3acres:offer:${year}:${major}:${school}:${item.id}`,
})),
});
};
+70
View File
@@ -0,0 +1,70 @@
// @ts-nocheck
import got from '@/utils/got';
import { art } from '@/utils/render';
import { parseDate } from '@/utils/parse-date';
import * as path from 'node:path';
export default async (ctx) => {
// year 2017-2022
// 2017:6 2018:11 2019:12 2020:13 2021:14 2022:15
// CS:1 MIS:2
const { year = 'null', major = 'null', school = 'null' } = ctx.req.param();
// const filter = 'filters: {planyr: "12", planmajor: "1", outname_w: "CMU"}';
const responseBasic = await got.post('https://api.1point3acres.com/offer/results', {
searchParams: {
ps: 15,
pg: 1,
},
json: {
filters: {
planyr: year === 'null' ? undefined : year,
planmajor: major === 'null' ? undefined : major,
outname_w: school === 'null' ? undefined : school,
},
},
});
const data = responseBasic.data.results;
// data.id-> 访问offer具体信息->获取 data.tid
// if (data.id !== 0) {
// out = await Promise.all(
// data.map(async (item) => {
// var gettidresponse = await got({
// method: 'get',
// url: 'https://api.1point3acres.com/offer/results/'+ item.id + '/backgrounds',
// headers: {
// authorization: 'eyJhbGciOiJIUzUxMiIsImlhdCI6MTU3Njk5Njc5OSwiZXhwIjoxNTg0ODU5MTk5fQ.eyJ1aWQiOjQ1NzQyN30.0ei5UE6OgLBzN2_IS7xUIbIfW_S1Wzl42q2UeusbboxuzvctO_4Mz6YRr6f0PBLUVZMETxt8F0_4-yqIJ3_kUQ',
// },
// });
// var tid = gettidresponse.data.background.tid;
// //https: //www.1point3acres.com/bbs/thread-581177-1-1.html
// console.log(tid);
// const threadlink = 'https://www.1point3acres.com/bbs/thread-' + tid + '-1-1.html';
// console.log(threadlink);
// return threadlink;
// })
// );
// }
// let responseBasic_1;
// responseBasic_1 = await got({
// method: 'get',
// url: `https://api.1point3acres.com/offer/results/A7m20e4g/backgrounds`,
// headers: {
// authorization: `eyJhbGciOiJIUzUxMiIsImlhdCI6MTU3Njk5Njc5OSwiZXhwIjoxNTg0ODU5MTk5fQ.eyJ1aWQiOjQ1NzQyN30.0ei5UE6OgLBzN2_IS7xUIbIfW_S1Wzl42q2UeusbboxuzvctO_4Mz6YRr6f0PBLUVZMETxt8F0_4-yqIJ3_kUQ`,
// },
// });
// const tid = responseBasic_1.data.background.tid;
ctx.set('data', {
title: '录取结果 - 一亩三分地',
link: 'https://offer.1point3acres.com',
item: data.map((item) => ({
title: `${item.planyr}${item.planmajor}@${item.outname_w}${item.result} - 一亩三分地`,
description: art(path.join(__dirname, 'templates/offer.art'), {
item,
}),
pubDate: parseDate(item.dateline, 'X'),
link: 'https://offer.1point3acres.com',
guid: `1point3acres:offer:${year}:${major}:${school}:${item.id}`,
})),
});
};
-29
View File
@@ -1,29 +0,0 @@
import cache from '@/utils/cache';
const { rootUrl, apiRootUrl, types, ProcessThreads } = require('./utils');
const sections = {
257: '留学申请',
379: '世界公民',
400: '投资理财',
31: '生活干货',
345: '职场达人',
391: '人际关系',
38: '海外求职',
265: '签证移民',
};
export default async (ctx) => {
const id = ctx.req.param('id') ?? '';
const type = ctx.req.param('type') ?? 'hot';
const order = ctx.req.param('order') ?? '';
const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 10;
const currentUrl = `${rootUrl}${id ? (isNaN(id) ? `/category/${id}` : `/section/${id}`) : ''}`;
const apiUrl = `${apiRootUrl}/api${id ? (isNaN(id) ? `/tags/${id}/` : `/forums/${id}/`) : ''}threads?type=${type}&includes=tags,forum_name,summary&ps=${limit}&pg=1&order=${order === '' ? '' : 'time_desc'}&is_groupid=1`;
ctx.set('data', {
title: `一亩三分地 - ${Object.hasOwn(sections, id) ? sections[id] : id}${types[type]}`,
link: currentUrl,
item: await ProcessThreads(cache.tryGet, apiUrl, order),
});
};
+30
View File
@@ -0,0 +1,30 @@
// @ts-nocheck
import cache from '@/utils/cache';
const { rootUrl, apiRootUrl, types, ProcessThreads } = require('./utils');
const sections = {
257: '留学申请',
379: '世界公民',
400: '投资理财',
31: '生活干货',
345: '职场达人',
391: '人际关系',
38: '海外求职',
265: '签证移民',
};
export default async (ctx) => {
const id = ctx.req.param('id') ?? '';
const type = ctx.req.param('type') ?? 'hot';
const order = ctx.req.param('order') ?? '';
const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 10;
const currentUrl = `${rootUrl}${id ? (isNaN(id) ? `/category/${id}` : `/section/${id}`) : ''}`;
const apiUrl = `${apiRootUrl}/api${id ? (isNaN(id) ? `/tags/${id}/` : `/forums/${id}/`) : ''}threads?type=${type}&includes=tags,forum_name,summary&ps=${limit}&pg=1&order=${order === '' ? '' : 'time_desc'}&is_groupid=1`;
ctx.set('data', {
title: `一亩三分地 - ${Object.hasOwn(sections, id) ? sections[id] : id}${types[type]}`,
link: currentUrl,
item: await ProcessThreads(cache.tryGet, apiUrl, order),
});
};
-16
View File
@@ -1,16 +0,0 @@
import cache from '@/utils/cache';
const { rootUrl, apiRootUrl, types, ProcessThreads } = require('./utils');
export default async (ctx) => {
const type = ctx.req.param('type') ?? 'hot';
const order = ctx.req.param('order') ?? '';
const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 10;
const apiUrl = `${apiRootUrl}/api/threads?type=${type}&includes=tags,forum_name,summary&ps=${limit}&pg=1&order=${order === '' ? '' : 'time_desc'}&is_groupid=1`;
ctx.set('data', {
title: `一亩三分地 - ${types[type]}`,
link: rootUrl,
item: await ProcessThreads(cache.tryGet, apiUrl, order),
});
};
+17
View File
@@ -0,0 +1,17 @@
// @ts-nocheck
import cache from '@/utils/cache';
const { rootUrl, apiRootUrl, types, ProcessThreads } = require('./utils');
export default async (ctx) => {
const type = ctx.req.param('type') ?? 'hot';
const order = ctx.req.param('order') ?? '';
const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 10;
const apiUrl = `${apiRootUrl}/api/threads?type=${type}&includes=tags,forum_name,summary&ps=${limit}&pg=1&order=${order === '' ? '' : 'time_desc'}&is_groupid=1`;
ctx.set('data', {
title: `一亩三分地 - ${types[type]}`,
link: rootUrl,
item: await ProcessThreads(cache.tryGet, apiUrl, order),
});
};
-21
View File
@@ -1,21 +0,0 @@
import got from '@/utils/got';
export default async (ctx) => {
const id = ctx.req.param('id');
const { posts } = (await got.get(`https://instant.1point3acres.com/v2/api/user/post?pg=1&ps=10&user_id=${id}`)).data;
const [{ author_name: author }] = posts;
ctx.set('data', {
title: `${author}的回复 - 一亩三分地`,
link: `https://instant.1point3acres.com/profile/${id}`,
description: `${author}的回复 - 一亩三分地`,
item: posts.map((item) => ({
title: item.message,
author,
description: item.message,
pubDate: new Date(item.create_time + ' GMT+8').toUTCString(),
link: `https://instant.1point3acres.com/thread/${item.thread_id}/post/${item.id}`,
})),
});
};
+22
View File
@@ -0,0 +1,22 @@
// @ts-nocheck
import got from '@/utils/got';
export default async (ctx) => {
const id = ctx.req.param('id');
const { posts } = (await got.get(`https://instant.1point3acres.com/v2/api/user/post?pg=1&ps=10&user_id=${id}`)).data;
const [{ author_name: author }] = posts;
ctx.set('data', {
title: `${author}的回复 - 一亩三分地`,
link: `https://instant.1point3acres.com/profile/${id}`,
description: `${author}的回复 - 一亩三分地`,
item: posts.map((item) => ({
title: item.message,
author,
description: item.message,
pubDate: new Date(item.create_time + ' GMT+8').toUTCString(),
link: `https://instant.1point3acres.com/thread/${item.thread_id}/post/${item.id}`,
})),
});
};
-21
View File
@@ -1,21 +0,0 @@
import got from '@/utils/got';
export default async (ctx) => {
const id = ctx.req.param('id');
const { threads } = (await got.get(`https://instant.1point3acres.com/v2/api/user/thread?pg=1&ps=10&user_id=${id}`)).data;
const [{ author_name: author }] = threads;
ctx.set('data', {
title: `${author}的主题帖 - 一亩三分地`,
link: `https://instant.1point3acres.com/profile/${id}`,
description: `${author}的主题帖 - 一亩三分地`,
item: threads.map((item) => ({
title: item.title,
author,
description: item.description,
pubDate: new Date(item.update_time + ' GMT+8').toUTCString(),
link: `https://instant.1point3acres.com/thread/${item.id}`,
})),
});
};

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