crystelf-plugin/apps/fanqie.js
2025-05-16 23:43:27 +08:00

257 lines
7.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import fs from 'node:fs';
import path from 'path';
import chokidar from 'chokidar';
import ConfigControl from '../lib/config/configControl.js';
import Fanqie from '../models/apps/fanqie/fanqie.js';
let redis = global.redis;
export class xzq extends plugin {
constructor() {
super({
name: 'fanqie',
dsc: 'fq下载器',
event: 'message',
priority: -114,
rule: [
{
reg: '(changdunovel.com/wap/share-v2.html|fanqienovel.com/page)',
fnc: 'jx',
},
{
reg: '#?fq下载(.*)',
fnc: 'byid',
},
{
reg: '^fq(允许|禁止)(群|用户)使用',
fnc: 'control',
},
{
reg: '^fq清(理|除|空)缓存$',
fnc: 'clearCache',
},
],
});
this.initConfigPromise = this.initConfig();
this.fq = null;
this.task = {
cron: '0 0 16 * * ?',
name: 'fq自动清理缓存',
fnc: () => this.clearCache(false, true),
};
}
async initConfig() {
this.outDir = await ConfigControl.get('fanqieConfig')?.outDir;
this.apiurl = await ConfigControl.get('fanqieConfig')?.url;
this.fq = new Fanqie(this.apiurl);
}
async listen_outdir(dir, timeout = 30000) {
if (!dir) return false;
return new Promise((resolve) => {
const watcher = chokidar.watch(dir, {
persistent: true,
ignoreInitial: true,
});
const timer = setTimeout(() => {
watcher.close();
resolve(false);
}, timeout);
watcher.on('add', (filePath) => {
clearTimeout(timer);
watcher.close();
resolve(filePath);
});
console.log(`已开始监听目录: ${dir}`);
});
}
async clearCache(e, is_task = false, zd = false) {
if (!is_task && (!e || !e.isMaster)) {
if (e) e.reply('你没有权限使用此功能', true);
return false;
}
if (!this.outDir) {
await this.initConfigPromise;
if (!this.outDir) {
if (e) e.reply('缓存目录未初始化,无法清理缓存', true);
return false;
}
}
if (zd) {
const targetDir = path.join(this.outDir, 'files', zd);
if (fs.existsSync(targetDir)) {
fs.rmSync(targetDir, { recursive: true, force: true });
}
}
const fanqieDir = path.join(this.outDir, 'fanqie');
if (fs.existsSync(fanqieDir)) {
fs.readdirSync(fanqieDir).forEach((file) => {
const fullPath = path.join(fanqieDir, file);
const stat = fs.statSync(fullPath);
if (stat.isDirectory()) {
fs.rmSync(fullPath, { recursive: true, force: true });
} else {
fs.unlinkSync(fullPath);
}
});
}
if (!is_task && e) e.reply('缓存已清理', true);
return true;
}
async control(e) {
if (!e.isMaster) return e.reply('你没有权限使用此功能', true);
let id = e.msg.trim().replace(/^fq(允许|禁止)(群|用户)使用/, '') || e.group_id;
const keyPrefix = e.msg.includes('群') ? 'g' : 'u';
const key = `fqxzq:${keyPrefix}:${id}`;
const allow = e.msg.includes('允许');
await redis.set(key, allow);
e.reply(
`${allow ? '允许' : '禁止'}${keyPrefix === 'g' ? '群' : '用户'}${id}使用此功能`,
true
);
return true;
}
async byid(e) {
const book_id = e.msg
.trim()
.replace(/^#?fq下载/, '')
.trim();
return this.xz(e, book_id);
}
async jx(e) {
const msg = e.msg.trim();
let book_id = null;
try {
if (msg.includes('changdunovel.com')) {
book_id = msg.match(/book_id=(\d+)/)[1];
} else {
book_id = msg.match(/page\/(\d+)/)[1];
}
} catch {
return e.reply('链接解析失败,请检查链接是否正确', true);
}
return this.xz(e, book_id);
}
async xz(e, id) {
await this.initConfigPromise;
let book_info;
try {
book_info = await this.fq.get_info(id);
} catch (err) {
logger.error(err);
return e.reply('获取信息失败', true);
}
if (!book_info) return e.reply('获取信息失败', true);
e.reply(
`识别:[番茄小说]《${book_info.book_name}\n作者:${book_info.author}\n原名:${book_info.original_book_name}`,
true
);
if (!e.isMaster) {
const allowGroup = e.isGroup ? await redis.get(`fqxzq:g:${e.group_id}`) : null;
const allowUser = await redis.get(`fqxzq:u:${e.user_id}`);
if (!allowGroup && !allowUser) return false;
}
e.reply('开始下载,请稍等', true);
const startTime = Date.now();
try {
await this.fq.down(id, e.message_id);
} catch (err) {
logger.error(err);
return e.reply('下载失败', true);
}
const outPath = path.join(this.outDir, 'files', String(e.message_id));
let filePath = await this.listen_outdir(outPath);
if (!filePath) return e.reply('下载超时', true);
const safeFilePath = filePath.replace(/ /g, '_');
if (filePath !== safeFilePath) {
try {
fs.renameSync(filePath, safeFilePath);
filePath = safeFilePath;
} catch (err) {
logger.error(`重命名文件失败:${err.stack}`);
return e.reply('文件重命名失败', true);
}
}
const uploadReturn = await this.upload(e, filePath);
await this.clearCache(false, true, String(e.message_id));
if (!uploadReturn) return e.reply('上传失败', true);
e.reply(
`${book_info.book_name}》上传成功,用时 ${(Date.now() - startTime) / 1000}s\n下载链接为${uploadReturn.url},将在10分钟后删除请及时下载`
);
return true;
}
async upload(e, filePath) {
// TODO 修好这发文件的bug
try {
let res;
if (e.isGroup) {
res = e.bot.sendApi('upload_group_file', {
group_id: e.group_id,
file: filePath,
name: path.basename(filePath),
folder: 'fanqie',
});
} else if (e.friend) {
res = e.bot.sendApi('upload_private_file', {
user_id: e.user_id,
file: filePath,
name: path.basename(filePath),
});
}
return res;
/*
//另外一种解决方法
const form = new FormData();
if (!fs.existsSync(filePath)) {
logger.error(`文件不存在:${filePath}`);
e.reply(`文件上传失败`, true);
return;
}
const fileStream = fs.createReadStream(filePath);
fileStream.on('error', (err) => {
logger.error('文件读取错误:', err);
});
form.append('file', fileStream);
form.append('token', configControl.get('coreConfig')?.token);
await finished(fileStream); //这文件读取也太慢了
const uploadUrl = `${configControl.get('coreConfig')?.coreUrl}/public/upload?dir=fanqie&expire=600`;
const response = await axios.post(uploadUrl, form, {
headers: form.getHeaders(),
maxContentLength: Infinity,
maxBodyLength: Infinity,
});
if (response?.success) {
const url = response.data?.url;
const message = response.data?.message;
return { url, message };
}*/
//return null;
} catch (err) {
logger.error(`文件上传错误:${logger.red(err.stack)}`);
e.reply(`文件上传失败:${err.message}`, true);
return null;
}
}
}