改番茄,开放使用

This commit is contained in:
Jerry 2025-06-29 13:28:41 +08:00
parent 862e081780
commit c32abc7a47

View File

@ -5,95 +5,101 @@ import ConfigControl from '../lib/config/configControl.js';
import Fanqie from '../models/apps/fanqie/fanqie.js'; import Fanqie from '../models/apps/fanqie/fanqie.js';
/** /**
* 本功能由y68(github@yeqiu6080)提供技术支持 * 本功能由 y68(github@yeqiu6080) 提供技术支持
*/ */
let redis = global.redis; export default class FanqiePlugin extends plugin {
export default class xzq extends plugin {
constructor() { constructor() {
super({ super({
name: 'fanqie', name: 'crystelf-fanqie',
dsc: 'fq下载器', dsc: '番茄小说下载器',
event: 'message', event: 'message',
priority: -114, priority: -114,
rule: [ rule: [
{ {
reg: '(changdunovel.com/wap/share-v2.html|fanqienovel.com/page)', reg: '(changdunovel.com/wap/share-v2.html|fanqienovel.com/page)',
fnc: 'jx', fnc: 'handleFanqieLink',
}, },
{ {
reg: '#?fq下载(.*)', reg: '#?fq下载(.*)',
fnc: 'byid', fnc: 'downloadByBookId',
},
{
reg: '^fq(允许|禁止)(群|用户)使用',
fnc: 'control',
}, },
{ {
reg: '^fq清(理|除|空)缓存$', reg: '^fq清(理|除|空)缓存$',
fnc: 'clearCache', fnc: 'clearFanqieCache',
}, },
], ],
}); });
this.initConfigPromise = this.initConfig();
this.fq = null; this.initPromise = this.initFanqieConfig();
this.fanqieClient = null;
// 注册计划任务
this.task = { this.task = {
cron: '0 0 16 * * ?', cron: '0 0 16 * * ?',
name: 'fq自动清理缓存', name: '定时清理番茄缓存',
fnc: () => this.clearCache(false, true), fnc: () => this.clearFanqieCache(false, true),
}; };
} }
async initConfig() { async initFanqieConfig() {
this.outDir = await ConfigControl.get('fanqieConfig')?.outDir; this.outDir = await ConfigControl.get('fanqieConfig')?.outDir;
this.apiurl = await ConfigControl.get('fanqieConfig')?.url; this.apiUrl = await ConfigControl.get('fanqieConfig')?.url;
this.fq = new Fanqie(this.apiurl); this.fanqieClient = new Fanqie(this.apiUrl);
} }
async listen_outdir(dir, timeout = 30000) { /**
* 监听下载输出目录
*/
async waitForOutputFile(dir, timeout = 30000) {
if (!dir) return false; if (!dir) return false;
return new Promise((resolve) => { return new Promise((resolve) => {
const watcher = chokidar.watch(dir, { const watcher = chokidar.watch(dir, {
persistent: true, persistent: true,
ignoreInitial: true, ignoreInitial: true,
}); });
const timer = setTimeout(() => { const timer = setTimeout(() => {
watcher.close(); watcher.close();
resolve(false); resolve(false);
}, timeout); }, timeout);
watcher.on('add', (filePath) => { watcher.on('add', (filePath) => {
clearTimeout(timer); clearTimeout(timer);
watcher.close(); watcher.close();
resolve(filePath); resolve(filePath);
}); });
console.log(`已开始监听目录: ${dir}`);
}); });
} }
async clearCache(e, is_task = false, zd = false) { /**
if (!is_task && (!e || !e.isMaster)) { * 清理缓存
if (e) e.reply('你没有权限使用此功能', true); */
async clearFanqieCache(e, isScheduled = false, specificId = false) {
if (!isScheduled && e && !e.isMaster) {
e.reply('你没有权限使用此功能', true);
return false; return false;
} }
if (!this.outDir) { if (!this.outDir) {
await this.initConfigPromise; await this.initPromise;
if (!this.outDir) { if (!this.outDir) {
if (e) e.reply('缓存目录未初始化,无法清理缓存', true); if (e) e.reply('缓存目录未初始化,无法清理', true);
return false; return false;
} }
} }
if (zd) { if (specificId) {
const targetDir = path.join(this.outDir, 'files', zd); const specificPath = path.join(this.outDir, 'files', specificId);
if (fs.existsSync(targetDir)) { if (fs.existsSync(specificPath)) {
fs.rmSync(targetDir, { recursive: true, force: true }); fs.rmSync(specificPath, { recursive: true, force: true });
} }
} }
const fanqieDir = path.join(this.outDir, 'fanqie'); const mainCachePath = path.join(this.outDir, 'fanqie');
if (fs.existsSync(fanqieDir)) { if (fs.existsSync(mainCachePath)) {
fs.readdirSync(fanqieDir).forEach((file) => { fs.readdirSync(mainCachePath).forEach((file) => {
const fullPath = path.join(fanqieDir, file); const fullPath = path.join(mainCachePath, file);
const stat = fs.statSync(fullPath); const stat = fs.statSync(fullPath);
if (stat.isDirectory()) { if (stat.isDirectory()) {
fs.rmSync(fullPath, { recursive: true, force: true }); fs.rmSync(fullPath, { recursive: true, force: true });
@ -103,126 +109,116 @@ export default class xzq extends plugin {
}); });
} }
if (!is_task && e) e.reply('缓存清理', true); if (!isScheduled && e) e.reply('缓存清理完成', true);
return true; return true;
} }
async control(e) { /**
if (!e.isMaster) return e.reply('你没有权限使用此功能', true); * 解析网页链接中的 book_id
*/
async handleFanqieLink(e) {
const message = e.msg.trim();
let bookId = null;
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 { try {
if (msg.includes('changdunovel.com')) { if (message.includes('changdunovel.com')) {
book_id = msg.match(/book_id=(\d+)/)[1]; bookId = message.match(/book_id=(\d+)/)[1];
} else { } else {
book_id = msg.match(/page\/(\d+)/)[1]; bookId = message.match(/page\/(\d+)/)[1];
} }
} catch { } catch {
return e.reply('链接解析失败,请检查链接是否正确', true); return e.reply('解析失败,请检查链接是否正确', true);
} }
return this.xz(e, book_id);
return this.downloadFanqieBook(e, bookId);
} }
async xz(e, id) { /**
await this.initConfigPromise; * 使用 #fq下载 命令下载
let book_info; */
async downloadByBookId(e) {
const bookId = e.msg.replace(/^#?fq下载/, '').trim();
return this.downloadFanqieBook(e, bookId);
}
/**
* 执行下载并上传文件
*/
async downloadFanqieBook(e, bookId) {
await this.initPromise;
let bookInfo;
try { try {
book_info = await this.fq.get_info(id); bookInfo = await this.fanqieClient.get_info(bookId);
} catch (err) { } catch (err) {
logger.error(err); logger.error(err);
return e.reply('获取信息失败', true); return e.reply('获取小说信息失败', true);
} }
if (!book_info) return e.reply('获取信息失败', true); if (!bookInfo) return e.reply('获取失败,请稍后再试', true);
e.reply( e.reply(
`识别:[番茄小说]《${book_info.book_name}\n作者:${book_info.author}\n原名:${book_info.original_book_name}`, `识别小说:[番茄小说]《${bookInfo.book_name}\n作者:${bookInfo.author}\n原名:${bookInfo.original_book_name}`,
true true
); );
if (!e.isMaster) { e.reply('开始下载,请稍等片刻...', true);
const allowGroup = e.isGroup ? await redis.get(`fqxzq:g:${e.group_id}`) : null;
const allowUser = await redis.get(`fqxzq:u:${e.user_id}`);
//return e.reply(`暂未开放下载功能,请等待功能更新..`, true);
if (!allowGroup && !allowUser) return false;
}
e.reply('开始下载,请稍等', true);
const startTime = Date.now(); const startTime = Date.now();
try { try {
await this.fq.down(id, e.message_id); await this.fanqieClient.down(bookId, e.message_id);
} catch (err) { } catch (err) {
logger.error(err); logger.error(err);
return e.reply('下载失败', true); return e.reply('下载失败,请稍后重试', true);
} }
const outPath = path.join(this.outDir, 'files', String(e.message_id)); const outPath = path.join(this.outDir, 'files', String(e.message_id));
let filePath = await this.listen_outdir(outPath); let finalFilePath = await this.waitForOutputFile(outPath);
if (!filePath) return e.reply('下载超时', true); if (!finalFilePath) return e.reply('下载超时', true);
const safeFilePath = filePath.replace(/ /g, '_'); // 文件重命名防止空格
if (filePath !== safeFilePath) { const safePath = finalFilePath.replace(/ /g, '_');
if (finalFilePath !== safePath) {
try { try {
fs.renameSync(filePath, safeFilePath); fs.renameSync(finalFilePath, safePath);
filePath = safeFilePath; finalFilePath = safePath;
} catch (err) { } catch (err) {
logger.error(`重命名文件失败:${err.stack}`); logger.error(`重命名失败:${err.stack}`);
return e.reply('文件重命名失败', true); return e.reply('重命名失败', true);
} }
} }
const uploadReturn = await this.upload(e, filePath); const uploaded = await this.sendFileToUser(e, finalFilePath);
await this.clearCache(false, true, String(e.message_id)); await this.clearFanqieCache(false, true, String(e.message_id));
if (!uploadReturn) return e.reply('上传失败', true);
e.reply(`${book_info.book_name}》上传成功,用时 ${(Date.now() - startTime) / 1000}s`); if (!uploaded) return e.reply('上传失败', true);
e.reply(`${bookInfo.book_name}》上传成功,耗时 ${(Date.now() - startTime) / 1000}s`);
return true; return true;
} }
async upload(e, filePath) { /**
* 上传文件至群或私聊
*/
async sendFileToUser(e, filePath) {
try { try {
let res; const fileName = path.basename(filePath);
if (e.isGroup) { if (e.isGroup) {
res = await e.bot.sendApi('upload_group_file', { return await e.bot.sendApi('upload_group_file', {
group_id: e.group_id, group_id: e.group_id,
file: filePath, file: filePath,
name: path.basename(filePath), name: fileName,
}); });
} else if (e.friend) { } else if (e.friend) {
res = await e.bot.sendApi('upload_private_file', { return await e.bot.sendApi('upload_private_file', {
user_id: e.user_id, user_id: e.user_id,
file: filePath, file: filePath,
name: path.basename(filePath), name: fileName,
}); });
} }
return res;
} catch (err) { } catch (err) {
logger.error(`文件上传错误${logger.red(err.stack)}`); logger.error(`文件上传失败${logger.red(err.stack)}`);
e.reply(`文件上传失败:${err.message}`, true); e.reply(`上传失败:${err.message}`, true);
return null; return null;
} }
} }