feat(route): pixiv novels (#10647)

* feat(route): pixiv novels

* fix: radar

* fix: cache rename

* docs: fix tag closing

* fix: sort
This commit is contained in:
Tony
2022-08-29 09:40:16 -06:00
committed by GitHub
parent b4f341fef0
commit 256387bd25
28 changed files with 230 additions and 175 deletions
-15
View File
@@ -34,21 +34,6 @@
s: [{ title: '热搜榜', docs: 'https://docs.rsshub.app/social-media.html#wei-bo', source: '/top/summary', target: '/weibo/search/hot' }],
},
'weibo.cn': { _name: '微博', m: [{ title: '博主', docs: 'https://docs.rsshub.app/social-media.html#wei-bo', source: ['/u/:uid', '/profile/:uid'], target: '/weibo/user/:uid' }] },
'pixiv.net': {
_name: 'Pixiv',
www: [
{ title: '用户收藏', docs: 'https://docs.rsshub.app/social-media.html#pixiv', source: '/users/:id/bookmarks/artworks', target: '/pixiv/user/bookmarks/:id' },
{ title: '用户动态', docs: 'https://docs.rsshub.app/social-media.html#pixiv', source: '/users/:id', target: '/pixiv/user/:id' },
{ title: '排行榜', docs: 'https://docs.rsshub.app/social-media.html#pixiv', source: '/ranking.php', target: (params, url) => `/pixiv/ranking/${new URL(url).searchParams.get('mode') || 'daily'}` },
{
title: '关键词',
docs: 'https://docs.rsshub.app/social-media.html#pixiv',
source: ['/tags/:keyword', '/tags/:keyword/:type?'],
target: (params, url) => `/pixiv/search/:keyword/${new URL(url).searchParams.get('order')}/${new URL(url).searchParams.get('mode')}`,
},
{ title: '关注的新作品', docs: 'https://docs.rsshub.app/social-media.html#pixiv', source: '/bookmark_new_illust.php', target: '/pixiv/user/illustfollows' },
],
},
'github.com': {
_name: 'GitHub',
'.': [
+4
View File
@@ -214,6 +214,10 @@ Though, every Story expires after 24 hours, so it may be not so serious.
<RouteEn author="EYHN" path="/pixiv/user/:id" example="/pixiv/user/15288095" :paramsDesc="['user id, available in user\'s homepage URL']" radar="1" rssbud="1"/>
### User Novels
<RouteEn author="TonyRL" example="/pixiv/user/novels/27104704" path="/pixiv/user/novels/:id" :paramsDesc="['User id, available in user\'s homepage URL']" radar="1" rssbud="1"/>
### Rankings
<RouteEn author="EYHN" path="/pixiv/ranking/:mode/:date?" example="/pixiv/ranking/week" :paramsDesc="['rank type', 'format: `2018-4-25`']" radar="1" rssbud="1">
+4
View File
@@ -562,6 +562,10 @@ Instagram Stories 没有可靠的 guid,你的 RSS 阅读器可能将同一条
<Route author="DIYgod" example="/pixiv/user/11" path="/pixiv/user/:id" :paramsDesc="['用户 id, 可在用户主页 URL 中找到']" radar="1" rssbud="1"/>
### 用户小说
<Route author="TonyRL" example="/pixiv/user/novels/27104704" path="/pixiv/user/novels/:id" :paramsDesc="['用户 id, 可在用户主页 URL 中找到']" radar="1" rssbud="1"/>
### 排行榜
<Route author="EYHN" example="/pixiv/ranking/week" path="/pixiv/ranking/:mode/:date?" :paramsDesc="['排行榜类型' ,'日期, 取值形如 `2018-4-25`']" radar="1" rssbud="1">
-35
View File
@@ -45,41 +45,6 @@ module.exports = {
},
],
},
'pixiv.net': {
_name: 'Pixiv',
www: [
{
title: '用户收藏',
docs: 'https://docs.rsshub.app/social-media.html#pixiv',
source: '/users/:id/bookmarks/artworks',
target: '/pixiv/user/bookmarks/:id',
},
{
title: '用户动态',
docs: 'https://docs.rsshub.app/social-media.html#pixiv',
source: '/users/:id',
target: '/pixiv/user/:id',
},
{
title: '排行榜',
docs: 'https://docs.rsshub.app/social-media.html#pixiv',
source: '/ranking.php',
target: (params, url) => `/pixiv/ranking/${new URL(url).searchParams.get('mode') || 'daily'}`,
},
{
title: '关键词',
docs: 'https://docs.rsshub.app/social-media.html#pixiv',
source: ['/tags/:keyword', '/tags/:keyword/:type?'],
target: (params, url) => `/pixiv/search/:keyword/${new URL(url).searchParams.get('order')}/${new URL(url).searchParams.get('mode')}`,
},
{
title: '关注的新作品',
docs: 'https://docs.rsshub.app/social-media.html#pixiv',
source: '/bookmark_new_illust.php',
target: '/pixiv/user/illustfollows',
},
],
},
'smzdm.com': {
_name: '什么值得买',
www: [
+6 -6
View File
@@ -116,12 +116,12 @@ router.get('/mzitu/category/:category', lazyloadRouteHandler('./routes/mzitu/cat
router.get('/mzitu/post/:id', lazyloadRouteHandler('./routes/mzitu/post'));
router.get('/mzitu/tag/:tag', lazyloadRouteHandler('./routes/mzitu/tag'));
// pixiv
router.get('/pixiv/user/bookmarks/:id', lazyloadRouteHandler('./routes/pixiv/bookmarks'));
router.get('/pixiv/user/illustfollows', lazyloadRouteHandler('./routes/pixiv/illustfollow'));
router.get('/pixiv/user/:id', lazyloadRouteHandler('./routes/pixiv/user'));
router.get('/pixiv/ranking/:mode/:date?', lazyloadRouteHandler('./routes/pixiv/ranking'));
router.get('/pixiv/search/:keyword/:order?/:mode?', lazyloadRouteHandler('./routes/pixiv/search'));
// pixiv migrated to v2
// router.get('/pixiv/user/bookmarks/:id', lazyloadRouteHandler('./routes/pixiv/bookmarks'));
// router.get('/pixiv/user/illustfollows', lazyloadRouteHandler('./routes/pixiv/illustfollow'));
// router.get('/pixiv/user/:id', lazyloadRouteHandler('./routes/pixiv/user'));
// router.get('/pixiv/ranking/:mode/:date?', lazyloadRouteHandler('./routes/pixiv/ranking'));
// router.get('/pixiv/search/:keyword/:order?/:mode?', lazyloadRouteHandler('./routes/pixiv/search'));
// pixiv-fanbox
router.get('/fanbox/:user?', lazyloadRouteHandler('./routes/fanbox/main'));
-74
View File
@@ -1,74 +0,0 @@
const config = require('@/config').value;
const logger = require('@/utils/logger');
const got = require('./pixiv-got');
const { maskHeader } = require('./constants');
let token = null;
const authorizationInfo = {
client_id: 'MOBrBDS8blbauoSck0ZfDbtuzpyT',
client_secret: 'lsACyCD94FhDUtGTXi3QzcFE2uU1hqtDaKeqrdwj',
hash_secret: '28c1fdd170a5204386cb1313c7077b34f83e4aaf4aa829ce78c231e05b0bae2c',
};
const refreshToken = () =>
got({
method: 'post',
url: 'https://oauth.secure.pixiv.net/auth/token',
form: {
...authorizationInfo,
get_secure_url: 1,
grant_type: 'refresh_token',
refresh_token: config.pixiv.refreshToken,
},
headers: {
...maskHeader,
},
responseType: 'json',
resolveBodyOnly: true,
}).catch((e) => {
logger.error('Pixiv refresh token failed.');
logger.debug(e);
});
async function tickToken() {
const result = await refreshToken();
let expireTime;
if (result && result.access_token) {
logger.debug('Pixiv refresh token success.');
token = result.access_token;
expireTime = result.expires_in;
} else {
expireTime = 30;
logger.error(`Pixiv refresh token failed, retry in ${expireTime} seconds.`);
}
// If is package, the mock server should be closed after request, needn't to refresh
if (!config.isPackage) {
setTimeout(tickToken, expireTime);
}
}
let tickTokenStarted = false;
async function startTickToken() {
if (!tickTokenStarted) {
// 如果tickToken没启动
tickTokenStarted = true;
await tickToken(); // 启动tickToken
}
}
async function waitForToken() {
while (!token) {
// eslint-disable-next-line no-await-in-loop
await new Promise((resolve) => setTimeout(resolve, 0));
}
return token;
}
module.exports = async () => {
await startTickToken();
return waitForToken();
};
module.exports.tickToken = tickToken;
+1 -1
View File
@@ -1,6 +1,6 @@
module.exports = {
'/cs/:type/:getDescription?': ['LogicJake', 'Seiry', 'qrzbing'],
'/cae/:type/:getDescription?': ['Seiry', 'qrzbing', 'Xm798'],
'/cs/:type/:getDescription?': ['LogicJake', 'Seiry', 'qrzbing'],
'/jwc/:type/:getDescription?': ['arcosx', 'Seiry', 'qrzbing', 'Xm798'],
'/yjsy/:type/:getDescription?': ['junfengP', 'Seiry', 'Xm798'],
};
+6 -6
View File
@@ -7,18 +7,18 @@ module.exports = {
docs: 'https://docs.rsshub.app/university.html#nan-jing-hang-kong-hang-tian-da-xue',
},
],
cs: [
{
title: '计算机科学与技术学院',
docs: 'https://docs.rsshub.app/university.html#nan-jing-hang-kong-hang-tian-da-xue',
},
],
cae: [
{
title: '自动化学院',
docs: 'https://docs.rsshub.app/university.html#nan-jing-hang-kong-hang-tian-da-xue',
},
],
cs: [
{
title: '计算机科学与技术学院',
docs: 'https://docs.rsshub.app/university.html#nan-jing-hang-kong-hang-tian-da-xue',
},
],
'www.graduate': [
{
title: '研究生院',
@@ -10,9 +10,7 @@ const queryString = require('query-string');
* @returns {Promise<got.AxiosResponse<{illusts: illust[]}>>}
*/
module.exports = function getBookmarks(user_id, token) {
return got({
method: 'get',
url: 'https://app-api.pixiv.net/v1/user/bookmarks/illust',
return got('https://app-api.pixiv.net/v1/user/bookmarks/illust', {
headers: {
...maskHeader,
Authorization: 'Bearer ' + token,
@@ -8,9 +8,7 @@ const queryString = require('query-string');
* @returns {Promise<got.AxiosResponse<{illusts: illust[]}>>}
*/
module.exports = function getUserIllustFollows(token) {
return got({
method: 'get',
url: 'https://app-api.pixiv.net/v2/illust/follow',
return got('https://app-api.pixiv.net/v2/illust/follow', {
headers: {
...maskHeader,
Authorization: 'Bearer ' + token,
@@ -9,9 +9,7 @@ const queryString = require('query-string');
* @returns {Promise<got.AxiosResponse<{illusts: illust[]}>>}
*/
module.exports = function getIllusts(user_id, token) {
return got({
method: 'get',
url: 'https://app-api.pixiv.net/v1/user/illusts',
return got('https://app-api.pixiv.net/v1/user/illusts', {
headers: {
...maskHeader,
Authorization: 'Bearer ' + token,
@@ -14,9 +14,7 @@ const allowMode = ['day', 'week', 'month', 'day_male', 'day_female', 'week_origi
*/
module.exports = function getRanking(mode, date, token) {
assert(allowMode.includes(mode), 'Mode not allow.');
return got({
method: 'get',
url: 'https://app-api.pixiv.net/v1/illust/ranking',
return got('https://app-api.pixiv.net/v1/illust/ranking', {
headers: {
...maskHeader,
Authorization: 'Bearer ' + token,
@@ -15,9 +15,7 @@ const queryString = require('query-string');
* @returns {Promise<got.AxiosResponse<userDetail>>}
*/
module.exports = function getUserDetail(user_id, token) {
return got({
method: 'get',
url: 'https://app-api.pixiv.net/v1/user/detail',
return got('https://app-api.pixiv.net/v1/user/detail', {
headers: {
...maskHeader,
Authorization: 'Bearer ' + token,
@@ -9,9 +9,7 @@ const queryString = require('query-string');
* @returns {Promise<got.AxiosResponse<{illusts: illust[]}>>}
*/
module.exports = function searchIllust(keyword, token) {
return got({
method: 'get',
url: 'https://app-api.pixiv.net/v1/search/illust',
return got('https://app-api.pixiv.net/v1/search/illust', {
headers: {
...maskHeader,
Authorization: 'Bearer ' + token,
@@ -9,9 +9,7 @@ const queryString = require('query-string');
* @returns {Promise<got.AxiosResponse<{illusts: illust[]}>>}
*/
module.exports = function searchPopularIllust(keyword, token) {
return got({
method: 'get',
url: 'https://app-api.pixiv.net/v1/search/popular-preview/illust',
return got('https://app-api.pixiv.net/v1/search/popular-preview/illust', {
headers: {
...maskHeader,
Authorization: 'Bearer ' + token,
@@ -1,8 +1,9 @@
const getToken = require('./token');
const { getToken } = require('./token');
const getBookmarks = require('./api/getBookmarks');
const getUserDetail = require('./api/getUserDetail');
const config = require('@/config').value;
const pixivUtils = require('./utils');
const { parseDate } = require('@/utils/parse-date');
module.exports = async (ctx) => {
if (!config.pixiv || !config.pixiv.refreshToken) {
@@ -11,7 +12,7 @@ module.exports = async (ctx) => {
const id = ctx.params.id;
const token = await getToken();
const token = await getToken(ctx.cache.tryGet);
if (!token) {
throw 'pixiv not login';
}
@@ -30,7 +31,7 @@ module.exports = async (ctx) => {
return {
title: illust.title,
author: illust.user.name,
pubDate: new Date(illust.create_date).toUTCString(),
pubDate: parseDate(illust.create_date),
description: `<p>画师:${illust.user.name} - 阅览数:${illust.total_view} - 收藏数:${illust.total_bookmarks}</p>${images.join('')}`,
link: `https://www.pixiv.net/artworks/${illust.id}`,
};
@@ -1,15 +1,16 @@
const getToken = require('./token');
const { getToken } = require('./token');
const getIllustFollows = require('./api/getIllustFollows');
const config = require('@/config').value;
const pixivUtils = require('./utils');
const { parseDate } = require('@/utils/parse-date');
module.exports = async (ctx) => {
if (!config.pixiv || !config.pixiv.refreshToken) {
throw 'pixiv 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 token = await getToken();
if (!(await getToken())) {
const token = await getToken(ctx.cache.tryGet);
if (!token) {
throw 'pixiv not login';
}
@@ -24,7 +25,7 @@ module.exports = async (ctx) => {
return {
title: illust.title,
author: illust.user.name,
pubDate: new Date(illust.create_date).toUTCString(),
pubDate: parseDate(illust.create_date),
description: `<p>画师:${illust.user.name} - 阅览数:${illust.total_view} - 收藏数:${illust.total_bookmarks}</p>${images.join('')}`,
link: `https://www.pixiv.net/artworks/${illust.id}`,
};
+8
View File
@@ -0,0 +1,8 @@
module.exports = {
'/ranking/:mode/:date?': ['EYHN'],
'/search/:keyword/:order?/:mode?': ['DIYgod'],
'/user/bookmarks/:id': ['EYHN'],
'/user/illustfollows': ['ClarkeCheng'],
'/user/novels/:id': ['TonyRL'],
'/user/:id': ['DIYgod'],
};
+35
View File
@@ -0,0 +1,35 @@
const got = require('@/utils/got');
const { parseDate } = require('@/utils/parse-date');
const baseUrl = 'https://www.pixiv.net';
module.exports = async (ctx) => {
const { id } = ctx.params;
const { data } = await got(`${baseUrl}/ajax/user/${id}/profile/all`);
const items = [
...data.body.novelSeries.map((item) => ({
title: item.title,
description: item.caption,
link: `https://www.pixiv.net/novel/series/${item.id}`,
author: item.userName,
pubDate: parseDate(item.createDate),
updated: parseDate(item.updateDate),
category: item.tags,
})),
...data.body.pickup.map((item) => ({
title: item.title,
description: item.description,
link: item.contentUrl,
author: item.userName,
pubDate: parseDate(item.createDate),
updated: parseDate(item.updateDate),
category: item.tags,
})),
];
ctx.state.data = {
title: `${data.body.novelSeries[0].userName || data.body.pickup[0].userName} 的小说`,
link: `${baseUrl}/users/${id}/novels`,
item: items,
};
};
@@ -6,7 +6,7 @@ const pixivConfig = require('@/config').value.pixiv;
async function dohResolve(name, jsonDohEndpoint) {
try {
const response = await got.get(jsonDohEndpoint, {
const response = await got(jsonDohEndpoint, {
searchParams: {
name,
type: 'A',
+43
View File
@@ -0,0 +1,43 @@
module.exports = {
'pixiv.net': {
_name: 'Pixiv',
www: [
{
title: '用户收藏',
docs: 'https://docs.rsshub.app/social-media.html#pixiv',
source: '/users/:id/bookmarks/artworks',
target: '/pixiv/user/bookmarks/:id',
},
{
title: '用户动态',
docs: 'https://docs.rsshub.app/social-media.html#pixiv',
source: '/users/:id',
target: '/pixiv/user/:id',
},
{
title: '用户小说',
docs: 'https://docs.rsshub.app/social-media.html#pixiv',
source: '/users/:id/novels',
target: '/pixiv/user/novels/:id',
},
{
title: '排行榜',
docs: 'https://docs.rsshub.app/social-media.html#pixiv',
source: '/ranking.php',
target: (params, url) => `/pixiv/ranking/${new URL(url).searchParams.get('mode') || 'daily'}`,
},
{
title: '关键词',
docs: 'https://docs.rsshub.app/social-media.html#pixiv',
source: ['/tags/:keyword', '/tags/:keyword/:type?'],
target: (params, url) => `/pixiv/search/:keyword/${new URL(url).searchParams.get('order')}/${new URL(url).searchParams.get('mode')}`,
},
{
title: '关注的新作品',
docs: 'https://docs.rsshub.app/social-media.html#pixiv',
source: '/bookmark_new_illust.php',
target: '/pixiv/user/illustfollows',
},
],
},
};
@@ -1,7 +1,8 @@
const getToken = require('./token');
const { getToken } = require('./token');
const getRanking = require('./api/getRanking');
const config = require('@/config').value;
const pixivUtils = require('./utils');
const { parseDate } = require('@/utils/parse-date');
const titles = {
day: 'pixiv 日排行',
@@ -56,7 +57,7 @@ module.exports = async (ctx) => {
const mode = alias[ctx.params.mode] ? alias[ctx.params.mode] : ctx.params.mode;
const date = ctx.params.date ? new Date(ctx.params.date) : new Date();
const token = await getToken();
const token = await getToken(ctx.cache.tryGet);
if (!token) {
throw 'pixiv not login';
}
@@ -75,7 +76,7 @@ module.exports = async (ctx) => {
const images = pixivUtils.getImgs(illust);
return {
title: `#${index + 1} ${illust.title}`,
pubDate: new Date(illust.create_date).toUTCString(),
pubDate: parseDate(illust.create_date),
description: `<p>画师:${illust.user.name} - 阅览数:${illust.total_view} - 收藏数:${illust.total_bookmarks}</p><br>${images.join('')}`,
link: `https://www.pixiv.net/artworks/${illust.id}`,
author: illust.user.name,
+8
View File
@@ -0,0 +1,8 @@
module.exports = (router) => {
router.get('/ranking/:mode/:date?', require('./ranking'));
router.get('/search/:keyword/:order?/:mode?', require('./search'));
router.get('/user/bookmarks/:id', require('./bookmarks'));
router.get('/user/illustfollows', require('./illustfollow'));
router.get('/user/novels/:id', require('./novels'));
router.get('/user/:id', require('./user'));
};
@@ -1,8 +1,9 @@
const getToken = require('./token');
const { getToken } = require('./token');
const searchPopularIllust = require('./api/searchPopularIllust');
const searchIllust = require('./api/searchIllust');
const config = require('@/config').value;
const pixivUtils = require('./utils');
const { parseDate } = require('@/utils/parse-date');
module.exports = async (ctx) => {
if (!config.pixiv || !config.pixiv.refreshToken) {
@@ -13,7 +14,7 @@ module.exports = async (ctx) => {
const order = ctx.params.order || 'date';
const mode = ctx.params.mode;
const token = await getToken();
const token = await getToken(ctx.cache.tryGet);
if (!token) {
throw 'pixiv not login';
}
@@ -40,7 +41,7 @@ module.exports = async (ctx) => {
return {
title: illust.title,
author: illust.user.name,
pubDate: new Date(illust.create_date).toUTCString(),
pubDate: parseDate(illust.create_date),
description: `<p>画师:${illust.user.name} - 阅览数:${illust.total_view} - 收藏数:${illust.total_bookmarks}</p>${images.join('')}`,
link: `https://www.pixiv.net/artworks/${illust.id}`,
};
+86
View File
@@ -0,0 +1,86 @@
const config = require('@/config').value;
const logger = require('@/utils/logger');
const got = require('./pixiv-got');
const { maskHeader } = require('./constants');
let token = null;
const authorizationInfo = {
client_id: 'MOBrBDS8blbauoSck0ZfDbtuzpyT',
client_secret: 'lsACyCD94FhDUtGTXi3QzcFE2uU1hqtDaKeqrdwj',
hash_secret: '28c1fdd170a5204386cb1313c7077b34f83e4aaf4aa829ce78c231e05b0bae2c',
};
const refreshToken = (tryGet) =>
tryGet(
'pixiv:accessToken',
() =>
got
.post('https://oauth.secure.pixiv.net/auth/token', {
form: {
...authorizationInfo,
get_secure_url: 1,
grant_type: 'refresh_token',
refresh_token: config.pixiv.refreshToken,
},
headers: {
...maskHeader,
},
responseType: 'json',
resolveBodyOnly: true,
})
.catch((e) => {
logger.error('Pixiv refresh token failed.');
logger.error(e);
}),
3600,
false
);
async function getToken(tryGet) {
const result = await refreshToken(tryGet);
// let expireTime;
if (result && result.access_token) {
logger.debug('Pixiv refresh token success.');
token = result.access_token;
// expireTime = result.expires_in;
}
// } else {
// expireTime = 30;
// logger.error(`Pixiv refresh token failed, retry in ${expireTime} seconds.`);
// }
// If is package, the mock server should be closed after request, needn't to refresh
// if (!config.isPackage) {
// setTimeout(tickToken, expireTime * 1000);
// }
return token;
}
module.exports = {
getToken,
};
// let tickTokenStarted = false;
// async function startTickToken(tryGet) {
// if (!tickTokenStarted) {
// // 如果tickToken没启动
// tickTokenStarted = true;
// await tickToken(tryGet); // 启动tickToken
// }
// }
// async function waitForToken() {
// while (!token) {
// // eslint-disable-next-line no-await-in-loop
// await new Promise((resolve) => setTimeout(resolve, 0));
// }
// return token;
// }
// module.exports = async (tryGet) => {
// await startTickToken(tryGet);
// return waitForToken();
// };
// module.exports.tickToken = tickToken;
@@ -1,7 +1,8 @@
const getToken = require('./token');
const { getToken } = require('./token');
const getIllusts = require('./api/getIllusts');
const config = require('@/config').value;
const pixivUtils = require('./utils');
const { parseDate } = require('@/utils/parse-date');
module.exports = async (ctx) => {
if (!config.pixiv || !config.pixiv.refreshToken) {
@@ -9,7 +10,7 @@ module.exports = async (ctx) => {
}
const id = ctx.params.id;
const token = await getToken();
const token = await getToken(ctx.cache.tryGet);
if (!token) {
throw 'pixiv not login';
}
@@ -28,7 +29,7 @@ module.exports = async (ctx) => {
return {
title: illust.title,
author: username,
pubDate: new Date(illust.create_date).toUTCString(),
pubDate: parseDate(illust.create_date),
description: `<p>画师:${username} - 阅览数:${illust.total_view} - 收藏数:${illust.total_bookmarks}</p>${images.join('')}`,
link: `https://www.pixiv.net/artworks/${illust.id}`,
};