pxder
Version:
Download illusts from pixiv.net P站插画批量下载器
419 lines (381 loc) • 10.4 kB
JavaScript
require('colors');
const PixivApi = require('./pixiv-api-client-mod');
const Downloader = require('./downloader');
const Illust = require('./illust');
const Illustrator = require('./illustrator');
const Fse = require('fs-extra');
const Path = require('path');
const Tools = require('./tools');
const { getProxyAgent, delSysProxy } = require('./proxy');
const { Agent } = require('https');
const CONFIG_FILE_DIR = require('appdata-path').getAppDataPath('pxder');
const CONFIG_FILE = Path.resolve(CONFIG_FILE_DIR, 'config.json');
const defaultConfig = {
download: {
thread: 5,
timeout: 30,
},
};
Object.freeze(defaultConfig);
let __config;
class PixivFunc {
constructor() {
this.followNextUrl = null;
}
/**
* 初始化配置文件
*
* @static
* @param {boolean} [forceInit=false] 是否强制初始化
* @memberof PixivFunc
*/
static initConfig(forceInit = false) {
Fse.ensureDirSync(CONFIG_FILE_DIR);
if (!Fse.existsSync(CONFIG_FILE) || forceInit) Fse.writeJSONSync(CONFIG_FILE, defaultConfig);
}
/**
* 读取配置
*
* @static
* @returns 配置
* @memberof PixivFunc
*/
static readConfig() {
PixivFunc.initConfig();
const config = (() => {
try {
return Fse.readJsonSync(CONFIG_FILE);
} catch (error) {}
return defaultConfig;
})();
// check
Object.keys(defaultConfig.download).forEach(key => {
if (typeof config.download[key] === 'undefined') config.download[key] = defaultConfig.download[key];
});
return config;
}
/**
* 写入配置
*
* @static
* @param {*} config 配置
* @memberof PixivFunc
*/
static writeConfig(config) {
Fse.ensureDirSync(CONFIG_FILE_DIR);
Fse.writeJsonSync(CONFIG_FILE, config);
}
/**
* 检查配置
*
* @static
* @param {*} [config=PixivFunc.readConfig()]
* @returns 是否通过
* @memberof PixivFunc
*/
static checkConfig(config = PixivFunc.readConfig()) {
let check = true;
if (!config.refresh_token) {
console.error('\nYou must login first!'.red + '\n Try ' + 'pxder --login'.yellow);
check = false;
}
if (!config.download.path) {
check = false;
console.error('\nYou must set download path first!'.red + '\n Try ' + 'pxder --setting'.yellow);
}
return check;
}
/**
* 应用配置
*
* @static
* @param {*} config 配置
* @memberof PixivFunc
*/
static applyConfig(config = PixivFunc.readConfig()) {
__config = config;
config.download.tmp = Path.join(CONFIG_FILE_DIR, 'tmp');
Downloader.setConfig(config.download);
PixivFunc.applyProxyConfig(config);
}
/**
* 应用代理配置
*
* @static
* @param {*} config 配置
* @memberof PixivFunc
*/
static applyProxyConfig(config = PixivFunc.readConfig()) {
const agent = getProxyAgent(config.proxy);
// fix OAuth may fail
delSysProxy();
if (agent) {
Downloader.setAgent(agent);
PixivApi.setAgent(agent);
global.proxyAgent = agent;
}
}
/**
* 登录
*
* @static
* @param {string} code
* @param {string} code_verifier
* @memberof PixivFunc
*/
static async login(code, code_verifier) {
// 登录
const pixiv = new PixivApi();
await pixiv.tokenRequest(code, code_verifier);
// 获取 refresh_token
const refresh_token = pixiv.authInfo().refresh_token;
// 更新配置
const conf = PixivFunc.readConfig();
conf.refresh_token = refresh_token;
PixivFunc.writeConfig(conf);
}
/**
* 使用 refreshToken 登录
*
* @static
* @param {string} token refreshToken
* @memberof PixivFunc
*/
static async loginByToken(token) {
// 测试登录
const pixiv = new PixivApi();
await pixiv.refreshAccessToken(token);
// 更新配置
const conf = PixivFunc.readConfig();
conf.refresh_token = token;
PixivFunc.writeConfig(conf);
}
/**
* 重登陆
*
* @static
* @returns 成功或失败
* @memberof PixivFunc
*/
async relogin() {
// 检查配置
const refresh_token = PixivFunc.readConfig().refresh_token;
if (!refresh_token) return false;
// 刷新 token
this.pixiv = new PixivApi();
await this.pixiv.refreshAccessToken(refresh_token);
Illustrator.setPixiv(this.pixiv);
Illust.setPixiv(this.pixiv);
// 定时刷新 token
const p = this.pixiv;
this.reloginInterval = setInterval(() => {
p.refreshAccessToken(refresh_token);
}, 40 * 60 * 1000);
return true;
}
/**
* 清除定时重登陆
*
* @memberof PixivFunc
*/
clearReloginInterval() {
clearInterval(this.reloginInterval);
}
/**
* 登出
*
* @static
* @memberof PixivFunc
*/
static logout() {
const config = PixivFunc.readConfig();
config.refresh_token = null;
PixivFunc.writeConfig(config);
}
/**
* 取得我的关注(一次30个)
*
* @param {boolean} [isPrivate=false] 是否是私密关注
* @returns 关注列表
* @memberof PixivFunc
*/
async getMyFollow(isPrivate = false) {
const follows = [];
let next = this.followNextUrl;
// 加入画师信息
async function addToFollows(data) {
next = data.next_url;
for (const preview of data.user_previews) {
if (preview.user.id != 11) {
// 除去“pixiv事務局”
const tmp = new Illustrator(preview.user.id, preview.user.name);
await tmp.setExampleIllusts(preview.illusts);
follows.push(tmp);
}
}
}
// 开始收集
if (next) {
await this.pixiv.requestUrl(next).then(addToFollows);
} else
await this.pixiv
.userFollowing(this.pixiv.authInfo().user.id, {
restrict: isPrivate ? 'private' : 'public',
})
.then(addToFollows);
this.followNextUrl = next;
return follows;
}
/**
* 是否还有关注画师可以取得
*
* @returns 是或否
* @memberof PixivFunc
*/
hasNextMyFollow() {
return !!this.followNextUrl;
}
/**
* 取得我的所有关注
*
* @param {boolean} [isPrivate=false] 是否是私密关注
* @returns 关注列表
* @memberof PixivFunc
*/
async getAllMyFollow(isPrivate = false) {
const follows = [];
const processDisplay = Tools.showProgress(() => follows.length);
do {
follows.push(...(await this.getMyFollow(isPrivate)));
} while (this.followNextUrl);
Tools.clearProgress(processDisplay);
return follows;
}
/**
* 根据UID下载插画
*
* @param {*} uids 画师UID(可数组)
* @memberof PixivFunc
*/
async downloadByUIDs(uids) {
const uidArray = Array.isArray(uids) ? uids : [uids];
for (const uid of uidArray) {
await Downloader.downloadByIllustrators([new Illustrator(uid)]).catch(Tools.logError);
}
}
/**
* 根据收藏下载插画
*
* @param {boolean} [isPrivate=false] 是否私密
* @memberof PixivFunc
*/
async downloadBookmark(isPrivate = false) {
const me = new Illustrator(this.pixiv.authInfo().user.id);
await Downloader.downloadByBookmark(me, isPrivate);
}
/**
* 下载关注画师的所有插画
*
* @param {boolean} isPrivate 是否是私密关注
* @param {boolean} force 是否忽略上次进度
* @memberof PixivFunc
*/
async downloadFollowAll(isPrivate, force) {
let follows = null;
let illustrators = null;
// 临时文件
const tmpJson = Path.join(CONFIG_FILE_DIR, (isPrivate ? 'private' : 'public') + '.json');
const tmpJsonExist = Fse.existsSync(tmpJson);
Fse.ensureDirSync(__config.download.path);
// 取得关注列表
if (!tmpJsonExist || force || (tmpJsonExist && !(follows = Tools.readJsonSafely(tmpJson, null)))) {
console.log('\nCollecting your follows');
follows = [];
await this.getAllMyFollow(isPrivate).then(ret => {
illustrators = ret;
ret.forEach(illustrator =>
follows.push({
id: illustrator.id,
name: illustrator.name,
illusts: illustrator.exampleIllusts,
})
);
});
Fse.ensureDirSync(CONFIG_FILE_DIR);
Fse.writeJSONSync(tmpJson, follows);
}
// 数据恢复
if (!illustrators) {
illustrators = [];
for (const follow of follows) {
const tempI = new Illustrator(follow.id, follow.name);
tempI.exampleIllusts = follow.illusts;
illustrators.push(tempI);
}
}
// 开始下载
await Downloader.downloadByIllustrators(illustrators, () => {
follows.shift();
Fse.ensureDirSync(CONFIG_FILE_DIR);
Fse.writeJSONSync(tmpJson, follows);
});
// 清除临时文件
Fse.unlinkSync(tmpJson);
}
/**
* 对下载目录内的所有画师更新画作
*
* @memberof PixivFunc
*/
async downloadUpdate() {
const uids = [];
// 得到文件夹内所有UID
Fse.ensureDirSync(__config.download.path);
const files = Fse.readdirSync(__config.download.path);
for (const file of files) {
const search = /^\(([0-9]+)\)/.exec(file);
if (search) uids.push(search[1]);
}
// 下载
const illustrators = [];
uids.forEach(uid => illustrators.push(new Illustrator(uid)));
await Downloader.downloadByIllustrators(illustrators);
}
/**
* 获取工具
*
* @static
* @returns 工具
* @memberof PixivFunc
*/
static tools() {
return require('./tools');
}
/**
* 根据PID下载插画
* @param {Array} pids 作品PID
* @memberof PixivFunc
*/
async downloadByPIDs(pids) {
const jsons = [];
const dirPath = Path.join(__config.download.path, 'PID');
Fse.ensureDirSync(dirPath);
const exists = Fse.readdirSync(dirPath)
.map(file => {
const search = /^\(([0-9]+)\)/.exec(file);
if (search && search[1]) return search[1];
return null;
})
.filter(pid => pid);
for (const pid of pids) {
if (exists.includes(pid)) continue;
try {
jsons.push(await this.pixiv.illustDetail(pid).then(json => json.illust));
} catch (error) {
console.log(`${pid} does not exist`.gray);
}
}
await Downloader.downloadByIllusts(jsons);
}
}
module.exports = PixivFunc;