mirror of
https://github.com/DIYgod/RSSHub.git
synced 2025-12-19 06:38:55 +08:00
feat: pixiv ranking
This commit is contained in:
@@ -59,6 +59,9 @@ router.get('/mzitu/tag/:tag', require('./routes/mzitu/tag'));
|
|||||||
|
|
||||||
// // Pixiv
|
// // Pixiv
|
||||||
router.get('/pixiv/user/:id', require('./routes/pixiv/user'));
|
router.get('/pixiv/user/:id', require('./routes/pixiv/user'));
|
||||||
|
// mode: [ day, week, month, day_male, day_female, week_original, week_rookie, day_r18, day_male_r18, day_female_r18, week_r18, week_r18g]
|
||||||
|
// date: 2018-4-25
|
||||||
|
router.get('/pixiv/ranking/:mode/:date?', require('./routes/pixiv/ranking'));
|
||||||
|
|
||||||
// 豆瓣
|
// 豆瓣
|
||||||
router.get('/douban/movie/playing', require('./routes/douban/playing'));
|
router.get('/douban/movie/playing', require('./routes/douban/playing'));
|
||||||
|
|||||||
8
routes/pixiv/constants.js
Normal file
8
routes/pixiv/constants.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
exports.modules = {
|
||||||
|
maskHeader: {
|
||||||
|
'App-OS': 'ios',
|
||||||
|
'App-OS-Version': '10.3.1',
|
||||||
|
'App-Version': '6.7.1',
|
||||||
|
'User-Agent': 'PixivIOSApp/6.7.1 (iOS 10.3.1; iPhone8,1)'
|
||||||
|
}
|
||||||
|
};
|
||||||
106
routes/pixiv/ranking.js
Normal file
106
routes/pixiv/ranking.js
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
const config = require('../../config');
|
||||||
|
|
||||||
|
const pixivConfig = config.pixiv;
|
||||||
|
|
||||||
|
if (!pixivConfig) {
|
||||||
|
logger.info('Pixiv RSS disable.');
|
||||||
|
module.exports = () => {};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const axios = require('axios');
|
||||||
|
const art = require('art-template');
|
||||||
|
const path = require('path');
|
||||||
|
const FormData = require('form-data');
|
||||||
|
const getToken = require('./token');
|
||||||
|
const maskHeader = require('./constants').maskHeader;
|
||||||
|
const assert = require('assert');
|
||||||
|
const util = require('util');
|
||||||
|
|
||||||
|
const allowMode = [
|
||||||
|
'day', 'week', 'month', 'day_male', 'day_female', 'week_original', 'week_rookie', 'day_r18', 'day_male_r18', 'day_female_r18', 'week_r18', 'week_r18g'
|
||||||
|
];
|
||||||
|
|
||||||
|
const titles = {
|
||||||
|
'day': 'pixiv 日排行',
|
||||||
|
'week': 'pixiv 周排行',
|
||||||
|
'month': 'pixiv 月排行',
|
||||||
|
'day_male': 'pixiv 受男性欢迎排行',
|
||||||
|
'day_female': 'pixiv 受女性欢迎排行',
|
||||||
|
'week_original': 'pixiv 原创作品排行',
|
||||||
|
'week_rookie': 'pixiv 新人排行',
|
||||||
|
'day_r18': 'pixiv R-18 日排行',
|
||||||
|
'day_male_r18': 'pixiv R-18 受男性欢迎排行',
|
||||||
|
'day_female_r18': 'pixiv R-18 受女性欢迎排行',
|
||||||
|
'week_r18': 'pixiv R-18 周排行',
|
||||||
|
'week_r18g': 'pixiv R-18G 排行'
|
||||||
|
};
|
||||||
|
|
||||||
|
const links = {
|
||||||
|
'day': 'https://www.pixiv.net/ranking.php?mode=daily',
|
||||||
|
'week': 'https://www.pixiv.net/ranking.php?mode=weekly',
|
||||||
|
'month': 'https://www.pixiv.net/ranking.php?mode=monthly',
|
||||||
|
'day_male': 'https://www.pixiv.net/ranking.php?mode=male',
|
||||||
|
'day_female': 'https://www.pixiv.net/ranking.php?mode=female',
|
||||||
|
'week_original': 'https://www.pixiv.net/ranking.php?mode=original',
|
||||||
|
'week_rookie': 'https://www.pixiv.net/ranking.php?mode=rookie',
|
||||||
|
'day_r18': 'https://www.pixiv.net/ranking.php?mode=daily_r18',
|
||||||
|
'day_male_r18': 'https://www.pixiv.net/ranking.php?mode=male_r18',
|
||||||
|
'day_female_r18': 'https://www.pixiv.net/ranking.php?mode=female_r18',
|
||||||
|
'week_r18': 'https://www.pixiv.net/ranking.php?mode=weekly_r18',
|
||||||
|
'week_r18g': 'https://www.pixiv.net/ranking.php?mode=r18g'
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = async (ctx) => {
|
||||||
|
const mode = ctx.params.mode;
|
||||||
|
const date = ctx.params.date ? new Date(ctx.params.date) : new Date();
|
||||||
|
|
||||||
|
assert(allowMode.includes(mode), 'Mode not allow.');
|
||||||
|
|
||||||
|
if (typeof getToken() === 'null') {
|
||||||
|
ctx.throw(500);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await axios({
|
||||||
|
method: 'get',
|
||||||
|
url: 'https://app-api.pixiv.net/v1/illust/ranking',
|
||||||
|
headers: {
|
||||||
|
...maskHeader,
|
||||||
|
'Authorization': 'Bearer ' + getToken()
|
||||||
|
},
|
||||||
|
params: {
|
||||||
|
mode: mode,
|
||||||
|
filter: 'for_ios',
|
||||||
|
...(ctx.params.date && {
|
||||||
|
date: `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`
|
||||||
|
})
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const illusts = response.data.illusts;
|
||||||
|
|
||||||
|
const dateStr = `${date.getFullYear()}年${date.getMonth() + 1}月${date.getDate()}日 `;
|
||||||
|
|
||||||
|
ctx.body = art(path.resolve(__dirname, '../../views/rss.art'), {
|
||||||
|
title: (ctx.params.date ? dateStr : '') + titles[mode],
|
||||||
|
link: links[mode],
|
||||||
|
description: dateStr + titles[mode],
|
||||||
|
lastBuildDate: new Date().toUTCString(),
|
||||||
|
item: illusts.map((illust, index) => {
|
||||||
|
const images = [];
|
||||||
|
if (illust.page_count === 1) {
|
||||||
|
images.push(`<p><img referrerpolicy="no-referrer" src="https://pixiv.cat/${illust.id}.jpg"/></p>`);
|
||||||
|
} else {
|
||||||
|
for (let i = 0; i < illust.page_count; i++) {
|
||||||
|
images.push(`<p><img referrerpolicy="no-referrer" src="https://pixiv.cat/${illust.id}-${i+1}.jpg"/></p>`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
title: `#${index + 1} ${illust.title}`,
|
||||||
|
description: `<p>画师:${illust.user.name} - 上传于:${new Date(illust.create_date).toLocaleString('zh-cn')} - 阅览数:${illust.total_view} - 收藏数:${illust.total_bookmarks}</p>${images.join('')}`,
|
||||||
|
link: `https://www.pixiv.net/member_illust.php?mode=medium&illust_id=${illust.id}`
|
||||||
|
};
|
||||||
|
})
|
||||||
|
});
|
||||||
|
};
|
||||||
89
routes/pixiv/token.js
Normal file
89
routes/pixiv/token.js
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
const config = require('../../config');
|
||||||
|
const logger = require('../../utils/logger');
|
||||||
|
const wait = require('../../utils/wait');
|
||||||
|
const FormData = require('form-data');
|
||||||
|
const axios = require('axios');
|
||||||
|
const maskHeader = require('./constants').maskHeader;
|
||||||
|
|
||||||
|
const pixivConfig = config.pixiv;
|
||||||
|
|
||||||
|
let token = null;
|
||||||
|
|
||||||
|
const authorizationInfo = {
|
||||||
|
client_id: pixivConfig.client_id,
|
||||||
|
client_secret: pixivConfig.client_secret,
|
||||||
|
username: pixivConfig.username,
|
||||||
|
password: pixivConfig.password
|
||||||
|
};
|
||||||
|
|
||||||
|
async function getToken() {
|
||||||
|
const data = new FormData();
|
||||||
|
const jsonData = {
|
||||||
|
...authorizationInfo,
|
||||||
|
get_secure_url: 1,
|
||||||
|
grant_type: 'password'
|
||||||
|
};
|
||||||
|
for (const key in jsonData) {
|
||||||
|
if (jsonData.hasOwnProperty(key)) {
|
||||||
|
const element = jsonData[key];
|
||||||
|
data.append(key,element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const response = await axios.post('https://oauth.secure.pixiv.net/auth/token', data, {
|
||||||
|
headers: {
|
||||||
|
...maskHeader,
|
||||||
|
...data.getHeaders()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return response.data.response;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshToken(refresh_token) {
|
||||||
|
const data = new FormData();
|
||||||
|
const jsonData = {
|
||||||
|
...authorizationInfo,
|
||||||
|
get_secure_url: 1,
|
||||||
|
grant_type: 'refresh_token',
|
||||||
|
refresh_token: refresh_token
|
||||||
|
};
|
||||||
|
for (const key in jsonData) {
|
||||||
|
if (jsonData.hasOwnProperty(key)) {
|
||||||
|
const element = jsonData[key];
|
||||||
|
data.append(key,element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const response = await axios.post('https://oauth.secure.pixiv.net/auth/token', data, {
|
||||||
|
headers: {
|
||||||
|
...maskHeader,
|
||||||
|
...data.getHeaders()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return response.data.response;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function tokenLoop() {
|
||||||
|
const res = await getToken();
|
||||||
|
logger.info('Pixiv login success.');
|
||||||
|
token = res.access_token;
|
||||||
|
let refresh_token = res.refresh_token;
|
||||||
|
let expires_in = res.expires_in * 0.9;
|
||||||
|
while (true) {
|
||||||
|
await wait(expires_in * 1000);
|
||||||
|
try {
|
||||||
|
const refresh_res = await refreshToken(refresh_token);
|
||||||
|
logger.debug('Pixiv refresh token success.');
|
||||||
|
token = refresh_res.access_token;
|
||||||
|
refresh_token = refresh_res.refresh_token;
|
||||||
|
expires_in = refresh_res.expires_in * 0.9;
|
||||||
|
} catch (err) {
|
||||||
|
expires_in = 30;
|
||||||
|
logger.err('Pixiv refresh token failed, retry in ${expires_in} seconds.', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenLoop();
|
||||||
|
|
||||||
|
module.exports = function getToken() {
|
||||||
|
return token;
|
||||||
|
};
|
||||||
@@ -1,9 +1,4 @@
|
|||||||
const axios = require('axios');
|
|
||||||
const art = require('art-template');
|
|
||||||
const path = require('path');
|
|
||||||
const config = require('../../config');
|
const config = require('../../config');
|
||||||
const logger = require('../../utils/logger');
|
|
||||||
const FormData = require('form-data');
|
|
||||||
|
|
||||||
const pixivConfig = config.pixiv;
|
const pixivConfig = config.pixiv;
|
||||||
|
|
||||||
@@ -13,100 +8,17 @@ if (!pixivConfig) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const authorizationInfo = {
|
const axios = require('axios');
|
||||||
client_id: pixivConfig.client_id,
|
const art = require('art-template');
|
||||||
client_secret: pixivConfig.client_secret,
|
const path = require('path');
|
||||||
username: pixivConfig.username,
|
const FormData = require('form-data');
|
||||||
password: pixivConfig.password
|
const getToken = require('./token');
|
||||||
};
|
const maskHeader = require('./constants').maskHeader;
|
||||||
|
|
||||||
const maskHeader = {
|
|
||||||
'App-OS': 'ios',
|
|
||||||
'App-OS-Version': '10.3.1',
|
|
||||||
'App-Version': '6.7.1',
|
|
||||||
'User-Agent': 'PixivIOSApp/6.7.1 (iOS 10.3.1; iPhone8,1)'
|
|
||||||
};
|
|
||||||
|
|
||||||
let token = null;
|
|
||||||
|
|
||||||
async function getToken() {
|
|
||||||
const data = new FormData();
|
|
||||||
const jsonData = {
|
|
||||||
...authorizationInfo,
|
|
||||||
get_secure_url: 1,
|
|
||||||
grant_type: 'password'
|
|
||||||
};
|
|
||||||
for (const key in jsonData) {
|
|
||||||
if (jsonData.hasOwnProperty(key)) {
|
|
||||||
const element = jsonData[key];
|
|
||||||
data.append(key,element);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const response = await axios.post('https://oauth.secure.pixiv.net/auth/token', data, {
|
|
||||||
headers: {
|
|
||||||
...maskHeader,
|
|
||||||
...data.getHeaders()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return response.data.response;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function refreshToken(refresh_token) {
|
|
||||||
const data = new FormData();
|
|
||||||
const jsonData = {
|
|
||||||
...authorizationInfo,
|
|
||||||
get_secure_url: 1,
|
|
||||||
grant_type: 'refresh_token',
|
|
||||||
refresh_token: refresh_token
|
|
||||||
};
|
|
||||||
for (const key in jsonData) {
|
|
||||||
if (jsonData.hasOwnProperty(key)) {
|
|
||||||
const element = jsonData[key];
|
|
||||||
data.append(key,element);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const response = await axios.post('https://oauth.secure.pixiv.net/auth/token', data, {
|
|
||||||
headers: {
|
|
||||||
...maskHeader,
|
|
||||||
...data.getHeaders()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return response.data.response;
|
|
||||||
}
|
|
||||||
|
|
||||||
function wait(ms) {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
setTimeout(resolve, ms);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function tokenLoop() {
|
|
||||||
const res = await getToken();
|
|
||||||
logger.info('Pixiv login success.');
|
|
||||||
token = res.access_token;
|
|
||||||
let refresh_token = res.refresh_token;
|
|
||||||
let expires_in = res.expires_in * 0.9;
|
|
||||||
while (true) {
|
|
||||||
await wait(expires_in * 1000);
|
|
||||||
try {
|
|
||||||
const refresh_res = await refreshToken(refresh_token);
|
|
||||||
logger.debug('Pixiv refresh token success.');
|
|
||||||
token = refresh_res.access_token;
|
|
||||||
refresh_token = refresh_res.refresh_token;
|
|
||||||
expires_in = refresh_res.expires_in * 0.9;
|
|
||||||
} catch (err) {
|
|
||||||
expires_in = 30;
|
|
||||||
logger.err('Pixiv refresh token failed, retry in ${expires_in} seconds.', err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tokenLoop();
|
|
||||||
|
|
||||||
module.exports = async (ctx) => {
|
module.exports = async (ctx) => {
|
||||||
const id = ctx.params.id;
|
const id = ctx.params.id;
|
||||||
|
|
||||||
if (typeof token === 'null') {
|
if (typeof getToken() === 'null') {
|
||||||
ctx.throw(500);
|
ctx.throw(500);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -116,7 +28,7 @@ module.exports = async (ctx) => {
|
|||||||
url: 'https://app-api.pixiv.net/v1/user/illusts',
|
url: 'https://app-api.pixiv.net/v1/user/illusts',
|
||||||
headers: {
|
headers: {
|
||||||
...maskHeader,
|
...maskHeader,
|
||||||
'Authorization': 'Bearer ' + token
|
'Authorization': 'Bearer ' + getToken()
|
||||||
},
|
},
|
||||||
params: {
|
params: {
|
||||||
user_id: id,
|
user_id: id,
|
||||||
|
|||||||
5
utils/wait.js
Normal file
5
utils/wait.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
module.exports = function wait(ms) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, ms);
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user