From fc2aaac231ff52b533a8eed1c8bcc1f808bc6883 Mon Sep 17 00:00:00 2001 From: yeyang <746659424@qq.com> Date: Sun, 12 Feb 2023 02:10:06 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9A=97=EF=B8=8F=20pixiv=E5=A2=9E=E5=8A=A0tok?= =?UTF-8?q?en=E7=99=BB=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/default_config/pixiv.yaml | 6 + ...roxyAgentMod.mjs => httpsProxyAgentMod.js} | 0 lib/request/request.js | 25 +-- model/Pixiv.js | 90 ++++++++--- model/Pixiv/api.js | 145 ++++++++++++++++++ 5 files changed, 238 insertions(+), 28 deletions(-) rename lib/request/{httpsProxyAgentMod.mjs => httpsProxyAgentMod.js} (100%) create mode 100644 model/Pixiv/api.js diff --git a/config/default_config/pixiv.yaml b/config/default_config/pixiv.yaml index 7d7e000..14979ee 100644 --- a/config/default_config/pixiv.yaml +++ b/config/default_config/pixiv.yaml @@ -6,3 +6,9 @@ pixivDirectConnection: false pixivImageProxy: i.pixiv.re #每名用户每日次数限制(0 则无限制) limit: 30 + +# Pixiv 登录凭证刷新令牌 (Refresh Token) +# 获取方法请参考: https://github.com/mixmoe/HibiAPI/issues/53 +refresh_token: + +language: zh-cn # 返回语言, 会影响标签的翻译 diff --git a/lib/request/httpsProxyAgentMod.mjs b/lib/request/httpsProxyAgentMod.js similarity index 100% rename from lib/request/httpsProxyAgentMod.mjs rename to lib/request/httpsProxyAgentMod.js diff --git a/lib/request/request.js b/lib/request/request.js index f41592e..1f3de71 100644 --- a/lib/request/request.js +++ b/lib/request/request.js @@ -2,7 +2,7 @@ import fetch from 'node-fetch' import { segment } from 'oicq' import { Config } from '../../components/index.js' import { Agent } from 'https' -import { HttpsProxyAgent } from './httpsProxyAgentMod.mjs' +import { HttpsProxyAgent } from './httpsProxyAgentMod.js' import _ from 'lodash' const CHROME_UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.79 Safari/537.36' @@ -24,6 +24,11 @@ const checkStatus = response => { } } +const qs = (obj) => { + let res = '' + for (const [k, v] of Object.entries(obj)) { res += `${k}=${encodeURIComponent(v)}&` } + return res.slice(0, res.length - 1) +} export default new class { /** * @description: Get请求 @@ -35,14 +40,7 @@ export default new class { async get (url, options = {}) { // 处理参数 if (options.params) { - const values = Object.values(options.params) - const keys = Object.keys(options.params) - const arr = [] - for (let i = 0; i < values.length; i++) { - arr.push(`${keys[i]}=${values[i]}`) - } - const str = arr.join('&') - url += `?${str}` + url = url + '?' + qs(options.params) } options.headers = { 'User-Agent': CHROME_UA, @@ -69,9 +67,16 @@ export default new class { 'User-Agent': CHROME_UA, ...options.headers } - if (typeof options.data === 'object') { + if (options.params) { + url = url + '?' + qs(options.params) + } + if (options.data) { options.body = JSON.stringify(options.data) + delete options.data options.headers['Content-Type'] = 'application/json' + } else if (options.body) { + options.headers['Content-Type'] = 'application/x-www-form-urlencoded' + options.body = qs(options.body) } if (!options.agent)options.agent = await this.getAgent() return await fetch(url, options) diff --git a/model/Pixiv.js b/model/Pixiv.js index c6ee0da..4bfa20f 100644 --- a/model/Pixiv.js +++ b/model/Pixiv.js @@ -5,12 +5,21 @@ import moment from 'moment' import { rankType, MSG } from '../tools/pixiv.js' import request from '../lib/request/request.js' import { Config } from '../components/index.js' +import PixivApi from './Pixiv/api.js' /** API请求错误文案 */ export default new class Pixiv { constructor () { this.ranktype = rankType this.domain = 'http://api.liaobiao.top/api/pixiv' + this.PixivApi = null + this.login() + } + + async login () { + if (Config.pixiv.refresh_token) { + this.PixivApi = new PixivApi(Config.pixiv.refresh_token) + } } get headers () { @@ -41,7 +50,12 @@ export default new class Pixiv { */ async illust (ids, filter = false) { const params = { id: ids } - let res = await request.get(`${this.domain}/illust`, { params }).then(res => res.json()) + let res = null + if (this.PixivApi) { + res = await this.PixivApi.illust(params) + } else { + res = await request.get(`${this.domain}/illust`, { params }).then(res => res.json()) + } if (res.error) throw Error(res.error?.user_message || '无法获取数据') let illust = this.format(res.illust) let { id, title, user, tags, total_bookmarks, total_view, url, create_date, x_restrict, illust_ai_type } = illust @@ -99,14 +113,17 @@ export default new class Pixiv { if (!date) date = moment().subtract(moment().utcOffset(9).hour() >= 12 ? 1 : 2, 'days').format('YYYY-MM-DD') - let params = { + const params = { mode: type, page, date } - // 请求api - let api = `${this.domain}/rank` - let res = await request.get(api, { params }).then(res => res.json()) + let res = null + if (this.PixivApi) { + res = await this.PixivApi.rank(type, date, page) + } else { + res = await request.get(`${this.domain}/rank`, { params }).then(res => res.json()) + } if (res.error) throw Error(res.error.message) if (_.isEmpty(res.illusts)) throw Error('暂无数据,请等待榜单更新哦(。-ω-)zzz') @@ -195,7 +212,12 @@ export default new class Pixiv { page, order: 'popular_desc' } - let res = await request.get(`${this.domain}/search`, { params }).then(res => res.json()) + let res = null + if (this.PixivApi) { + res = await this.PixivApi.search(params) + } else { + res = await request.get(`${this.domain}/search`, { params }).then(res => res.json()) + } if (res.error) throw Error(res.error.message) if (_.isEmpty(res.illusts)) throw Error('宝~没有数据了哦(๑>︶<)و') @@ -231,9 +253,13 @@ export default new class Pixiv { * @return {Array} */ async PopularTags () { - let api = `${this.domain}/tags` + let res = null + if (this.PixivApi) { + res = await this.PixivApi.tags() + } else { + res = await fetch(`${this.domain}/tags`).then(res => res.json()) + } - let res = await fetch(api).then(res => res.json()) if (!res.trend_tags) throw Error('呜呜呜,没有获取到数据(๑ १д१)') let list = [] @@ -262,15 +288,29 @@ export default new class Pixiv { async userIllust (keyword, page = 1, isfilter = true) { // 关键词搜索 if (!/^\d+$/.test(keyword)) { - let wordapi = `${this.domain}/search_user?word=${keyword}` - let wordlist = await request.get(wordapi).then(res => res.json()) - + let wordlist = null + if (this.PixivApi) { + wordlist = await this.PixivApi.search_user({ word: keyword }) + } else { + wordlist = await request.get(`${this.domain}/search_user`, { + params: { + word: keyword + } + }).then(res => res.json()) + } if (_.isEmpty(wordlist.user_previews)) throw Error('呜呜呜,人家没有找到这个淫d(ŐдŐ๑)') keyword = wordlist.user_previews[0].user.id } - // 作品 - let api = `${this.domain}/member_illust?id=${keyword}&page=${page}` - let res = await request.get(api).then(res => res.json()) + const params = { + id: keyword, + page + } + let res = null + if (this.PixivApi) { + res = await this.PixivApi.member_illust(params) + } else { + res = await request.get(`${this.domain}/member_illust`, { params }).then(res => res.json()) + } if (res.error) throw Error(res.error.message) // 没有作品直接返回信息 @@ -315,8 +355,17 @@ export default new class Pixiv { * @return {Array} 可直接发送的消息数组 */ async searchUser (word, page = 1, isfilter = true) { - let api = `${this.domain}/search_user?word=${word}&page=${page}&size=10` - let user = await request.get(api).then(res => res.json()) + let params = { + word, + page, + size: 10 + } + let user = null + if (this.PixivApi) { + user = await this.PixivApi.search_user(params) + } else { + user = await request.get(`${this.domain}/search_user`, { params }).then(res => res.json()) + } if (user.error) throw Error(user.error.message) if (_.isEmpty(user.user_previews)) throw Error('呜呜呜,人家没有找到这个淫d(ŐдŐ๑)') @@ -370,8 +419,13 @@ export default new class Pixiv { * @return {*} */ async related (pid, isfilter = true) { - let api = `${this.domain}/related?id=${pid}` - let res = await request.get(api).then(res => res.json()) + let params = { id: pid } + let res = null + if (this.PixivApi) { + res = await this.PixivApi.related(params) + } else { + res = await request.get(`${this.domain}/related`, { params }).then(res => res.json()) + } if (res.error) throw Error(res.error.user_message) if (_.isEmpty(res.illusts)) throw Error('呃...没有数据(•ิ_•ิ)') diff --git a/model/Pixiv/api.js b/model/Pixiv/api.js new file mode 100644 index 0000000..2d7fcdc --- /dev/null +++ b/model/Pixiv/api.js @@ -0,0 +1,145 @@ +import request from '../../lib/request/request.js' +import moment from 'moment' +import { Config } from '../../components/index.js' +export default class PixivApi { + constructor (refresh_token) { + this.access_token = null + this.baseUrl = 'https://app-api.pixiv.net/' + this.headers = { + 'User-Agent': 'PixivIOSApp/7.13.3 (iOS 14.6; iPhone13,2)', + 'Accept-Language': Config.pixiv.language, + 'App-OS': 'ios', + 'App-OS-Version': '14.6', + Accept: '*/*', + Connection: 'keep-alive' + } + this.login(refresh_token) + } + + async login (refresh_token) { + if (!refresh_token) { + throw Error('Pixiv 未配置refresh_token刷新令牌') + } + const body = { + client_id: 'MOBrBDS8blbauoSck0ZfDbtuzpyT', + client_secret: 'lsACyCD94FhDUtGTXi3QzcFE2uU1hqtDaKeqrdwj', + grant_type: 'refresh_token', + refresh_token + } + const { response, error } = await request.post('https://oauth.secure.pixiv.net/auth/token', { + body, + headers: this.headers + }).then(res => res.json()) + if (error) throw Error(`Pixiv login Error Response: ${error}`) + this.access_token = response.access_token + } + + async request (target, options = {}) { + const headers = { + ...this.headers, + Authorization: `Bearer ${this.access_token}` + } + return request[options.data ? 'post' : 'get'](this.baseUrl + target, { headers, ...options }).then(res => res.json()) + } + + async tags () { + return this.request('v1/trending-tags/illust') + } + + async rank ({ + mode = 'week', + date = moment().subtract(moment().utcOffset(9).hour() >= 12 ? 1 : 2, 'days').format('YYYY-MM-DD'), + page = 1, + size = 30 + }) { + return this.request('v1/illust/ranking', { + params: { + mode, + date, + offset: (page - 1) * size + } + }) + } + + async illust ({ id }) { + return this.request('v1/illust/detail', { + params: { + illust_id: id + } + }) + } + + async member ({ id }) { + return this.request('v1/user/detail', { + params: { + illust_id: id + } + }) + } + + async member_illust ({ + id, + page = 1, + size = 30, + illust_type = 'illust' + }) { + return this.request('v1/user/illusts', { + params: { + user_id: id, + type: illust_type, + offset: (page - 1) * size + } + }) + } + + async search ({ + word, + page = 1, + size = 30, + order = 'date_desc', + mode = 'partial_match_for_tags', + include_translated_tag_results = true + }) { + return this.request('v1/search/illust', { + params: { + word, + search_target: mode, + sort: order, + offset: (page - 1) * size, + include_translated_tag_results + } + }) + } + + async search_user ({ + word, + page = 1, + size = 30 + }) { + return await this.request( + 'v1/search/user', + { + params: { + word, + offset: (page - 1) * size + } + } + ) + } + + async related ({ + id, + page = 1, + size = 30 + }) { + return await this.request( + 'v2/illust/related', + { + params: { + illust_id: id, + offset: (page - 1) * size + } + } + ) + } +}