@wahaha216/koishi-plugin-jmcomic
Version:
下载JM本子,无需python。支持pdf、zip加密。
1,531 lines (1,504 loc) • 68.4 kB
JavaScript
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var __commonJS = (cb, mod) => function __require() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var __export = (target, all) => {
for (var name2 in all)
__defProp(target, name2, { get: all[name2], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/locales/zh-CN.yml
var require_zh_CN = __commonJS({
"src/locales/zh-CN.yml"(exports2, module2) {
module2.exports = { commands: { jm: { description: "下载JM漫画,无需python!", examples: "jm album 本子数字ID\njm album info 本子数字ID\njm photo 本子章节数字ID", album: { examples: "jm album 数字ID", messages: { addedToQueue: "已将 {id} 添加到处理队列,请稍候。", queueFirst: "已将 {id} 添加到处理队列,即将开始处理", queuePosition: "已将 {id} 添加到处理队列\n前面还有 {ahead} 个任务等待或正在处理", queueProcessing: "已将 {id} 添加到处理队列\n当前任务状态:{status}", notExistError: "找不到该ID对应的本子", mysqlError: "已尝试所有备用地址,但是JM坏掉了" }, info: { examples: "jm album info 本子数字ID", messages: { notExistError: "找不到该ID对应的本子", mysqlError: "已尝试所有备用地址,但是JM坏掉了" } } }, photo: { examples: "jm photo 本子章节数字ID", messages: { addedToQueue: "已将 {id} 添加到处理队列,请稍候。", queueFirst: "已将 {id} 添加到处理队列,即将开始处理", queuePosition: "已将 {id} 添加到处理队列\n前面还有 {ahead} 个任务等待或正在处理", queueProcessing: "已将 {id} 添加到处理队列\n当前任务状态:{status}", notExistError: "找不到该ID对应的章节", mysqlError: "已尝试所有备用地址,但是JM坏掉了" } }, queue: { examples: "jm queue", messages: { emptyQueue: "当前没有正在处理或者等待处理的任务", msgFormat: "ID: {id}, 类型: {type}, 状态: {status}\n", task: { pending: "等待中...", processing: "处理中...", failed: "发生未知错误", completed: "已完成", unknown: "未定义状态" }, type: { album: "本子", photo: "章节" } } }, blog: { examples: "jm album 数字ID", messages: { addedToQueue: "已将 {id} 添加到处理队列,请稍候。", queueFirst: "已将 {id} 添加到处理队列,即将开始处理", queuePosition: "已将 {id} 添加到处理队列\n前面还有 {ahead} 个任务等待或正在处理", queueProcessing: "已将 {id} 添加到处理队列\n当前任务状态:{status}", notExistError: "找不到该ID对应的本子", mysqlError: "已尝试所有备用地址,但是JM坏掉了" } }, search: { example: "jm search <关键词>", messages: { emptyKeywordError: "请输入搜索关键词", id: "JMID", name: "名称", author: "作者", category: "分类", description: "描述", pagination: "共 {total} 条, 当前第 {page} 页, 每页 {pageSize} 条" } } } }, _config: [{ $desc: "基础设置", sendMethod: "发送方式", fileMethod: "文件获取方式<br>`buffer`: 读取成buffer后发送给bot实现端<br>`file`: 以`file:///` 本地路径形式发送,如docker环境,请在bot实现端同时映射/koishi目录", password: "密码,留空则不加密", fileName: "文件名定义<br>`{{name}}`:标题<br>`{{id}}`:章节或者本子ID<br>`{{index}}`:多章节本子自动填充`1` 、 `2`" }, { level: "压缩级别,0~9,0为仅存储" }, { listeningJMId: "监听 `JM ID` 关键词,当出现JMxxxxx的关键词时,发送第一张图片" }, { $desc: "限制相关设置", retryCount: "重试次数限制", concurrentDownloadLimit: "同时下载数量限制", concurrentDecodeLimit: "同时解密数量限制", concurrentQueueLimit: "同时处理数量限制" }, { $desc: "缓存设置", cache: "缓存文件" }, { autoDelete: "自动删除缓存,**依赖cron服务**" }, { cron: "5位cron表达式", deleteInStart: "启动时检测", keepDays: "缓存保留时间(天)" }, { $desc: "开发者选项", debug: "调试模式,输出更多信息" }] };
}
});
// src/locales/en-US.yml
var require_en_US = __commonJS({
"src/locales/en-US.yml"(exports2, module2) {
module2.exports = { commands: { jm: { description: "Download JM comics without python!", examples: "jm album albumID\njm album info albumID\njm photo photoID", album: { examples: "jm album albumID", messages: { addedToQueue: "Album {id} has been added to the processing queue. Please wait.", queueFirst: "Added {id} to the processing queue, starting shortly.", queuePosition: "Added {id} to the processing queue.\nThere are {ahead} tasks ahead or currently processing.", queueProcessing: "Added {id} to the processing queue.\nCurrent task status: {status}", notExistError: "albumID not found", mysqlError: "All alternate addresses have been tried. But no response." }, info: { examples: "jm album info albumID", messages: { notExistError: "albumID not found", mysqlError: "All alternate addresses have been tried. But no response." } } }, photo: { examples: "jm photo photoID", messages: { addedToQueue: "Photo {id} has been added to the processing queue. Please wait.", queueFirst: "Added {id} to the processing queue, starting shortly.", queuePosition: "Added {id} to the processing queue.\nThere are {ahead} tasks ahead or currently processing.", queueProcessing: "Added {id} to the processing queue.\nCurrent task status: {status}", notExistError: "photoID not found", mysqlError: "All alternate addresses have been tried. But no response." } }, queue: { examples: "jm queue", messages: { emptyQueue: "There are currently no tasks being processed or waiting.", msgFormat: "ID: {id}, type: {type}, status: {status}\n", task: { Pending: "Pending...", Processing: "Processing...", failed: "Failed (Unknown Error)", completed: "Completed", unknown: "Unknown Status" }, type: { album: "Album", photo: "Photo" } } }, search: { example: "jm search <keyword>", messages: { emptyKeywordError: "Please enter search keywords", id: "JMID", name: "name", author: "author", category: "category", description: "description", pagination: "Total {total}, current {page} page, each page {pageSize}" } }, blog: { examples: "jm blog blogID", messages: { addedToQueue: "Blog {id} has been added to the processing queue. Please wait.", queueFirst: "Added {id} to the processing queue, starting shortly.", queuePosition: "Added {id} to the processing queue.\nThere are {ahead} tasks ahead or currently processing.", queueProcessing: "Added {id} to the processing queue.\nCurrent task status: {status}", notExistError: "blogID not found", mysqlError: "All alternate addresses have been tried. But no response." } } } }, _config: [{ $desc: "Basic settings", sendMethod: "Send method", fileMethod: "File acquisition method<br>`buffer`: Read as buffer and send it to the bot implementation.<br>`file`: Send it in the local path of `file:///`. For example, if in the docker environment, please map the `/koishi` directory at the bot implementation.", password: "Password, leave blank without encryption", fileName: "File name definition<br>`{{name}}`: Title<br>`{{id}}`: Chapter or Book ID<br>`{{index}}`: Multi-chapter book auto-filling `_1` `_2`" }, { level: "Compression level, 0~9, 0 is only stores" }, { listeningJMId: "Monitors for the `JM ID` keyword. When the 'JMxxxxx' keyword is detected, the first image will be sent." }, { $desc: "Limit settings", retryCount: "Retry limit", concurrentDownloadLimit: "Concurrent Download Limit", concurrentDecodeLimit: "Concurrent Decode Limit", concurrentQueueLimit: "Concurrent Queue Limit" }, { $desc: "Cache settings", cache: "Cache files" }, { autoDelete: "Automatically delete cache, **need cron services**" }, { cron: "5-bit cron expression", deleteInStart: "Detection on startup", keepDays: "Cache retention time (days)" }, { $desc: "Developer Options", debug: "Debug mode, output more information" }] };
}
});
// src/index.ts
var src_exports = {};
__export(src_exports, {
Config: () => Config,
apply: () => apply,
inject: () => inject,
name: () => name
});
module.exports = __toCommonJS(src_exports);
var import_koishi2 = require("koishi");
var import_path4 = require("path");
// src/utils/Utils.ts
var import_fs = require("fs");
var import_promises = require("fs/promises");
// src/utils/Regexp.ts
var JM_SCRAMBLE_ID = /var scramble_id = (\d+);/;
// src/utils/Const.ts
var JM_CLIENT_URL_LIST = [
"www.cdnaspa.vip",
"www.cdnaspa.club",
"www.cdnplaystation6.vip",
"www.cdnplaystation6.cc"
];
var JM_IMAGE_URL_LIST = [
"cdn-msp.jmapiproxy1.cc",
"cdn-msp.jmapiproxy2.cc",
"cdn-msp2.jmapiproxy2.cc",
"cdn-msp3.jmapiproxy2.cc",
"cdn-msp.jmapinodeudzn.net",
"cdn-msp3.jmapinodeudzn.net"
];
var JM_API_URL_DOMAIN_SERVER_LIST = [
"https://rup4a04-c01.tos-ap-southeast-1.bytepluses.com/newsvr-2025.txt",
"https://rup4a04-c02.tos-cn-hongkong.bytepluses.com/newsvr-2025.txt"
];
var JM_APP_VERSION = "1.7.9";
var JM_APP_TOKEN_SECRET = "18comicAPP";
var JM_APP_TOKEN_SECRET_2 = "18comicAPPContent";
var JM_APP_DATA_SECRET = "185Hcomic3PAPP7R";
var JM_API_DOMAIN_SERVER_SECRET = "diosfjckwpqpdfjkvnqQjsik";
// src/utils/Utils.ts
var import_path = require("path");
// src/error/albumNotExist.error.ts
var AlbumNotExistError = class extends Error {
static {
__name(this, "AlbumNotExistError");
}
constructor(message) {
super(message);
this.name = "AlbumNotExistError";
}
};
// src/error/photoNotExist.error.ts
var PhotoNotExistError = class extends Error {
static {
__name(this, "PhotoNotExistError");
}
constructor(message) {
super(message);
this.name = "PhotoNotExistError";
}
};
// src/error/mysql.error.ts
var MySqlError = class extends Error {
static {
__name(this, "MySqlError");
}
constructor(message) {
super(message);
this.name = "MySqlError";
}
};
// src/error/emptybuffer.error.ts
var EmptyBufferError = class extends Error {
static {
__name(this, "EmptyBufferError");
}
constructor(message) {
super(message);
this.name = "EmptyBufferError";
}
};
// src/error/overRetry.error.ts
var OverRetryError = class extends Error {
static {
__name(this, "OverRetryError");
}
constructor(message) {
super(message);
this.name = "OverRetryError";
}
};
// src/error/allDomainFailed.error.ts
var AllDomainFailedError = class extends Error {
static {
__name(this, "AllDomainFailedError");
}
constructor(message) {
super(message);
this.name = "AllDomainFailedError";
}
};
// src/utils/Utils.ts
var import_crypto = require("crypto");
function fileExistsAsync(path) {
try {
(0, import_fs.accessSync)(path, import_fs.constants.F_OK);
return true;
} catch (err) {
return false;
}
}
__name(fileExistsAsync, "fileExistsAsync");
function fileSizeAsync(path) {
try {
const stats = (0, import_fs.statSync)(path);
return stats.size;
} catch (err) {
return 0;
}
}
__name(fileSizeAsync, "fileSizeAsync");
function sanitizeFileName(fileName) {
const forbiddenAndShellSpecialChars = /[<>:"/\\|?*()[\]{}!#$&;'`~]/g;
let sanitizedFileName = fileName.replace(forbiddenAndShellSpecialChars, "_");
sanitizedFileName = sanitizedFileName.replace(/\s+/g, "_");
sanitizedFileName = sanitizedFileName.replace(/_+/g, "_");
sanitizedFileName = sanitizedFileName.replace(/^[._]+|[._]+$/g, "");
let byteLength = Buffer.byteLength(sanitizedFileName, "utf-8");
while (byteLength > 200) {
const length = sanitizedFileName.length;
sanitizedFileName = sanitizedFileName.substring(0, length - 1);
byteLength = Buffer.byteLength(sanitizedFileName, "utf8");
}
if (sanitizedFileName.trim() === "") {
return "untitled_document";
}
return sanitizedFileName;
}
__name(sanitizeFileName, "sanitizeFileName");
async function limitPromiseAll(promises, limit) {
const results = new Array(promises.length);
const executing = [];
for (let i = 0; i < promises.length; i++) {
const promiseFn = promises[i];
while (executing.length >= limit) {
await Promise.race(executing);
}
const p = promiseFn().then((res) => {
results[i] = res;
}).finally(() => {
const index = executing.indexOf(p);
if (index !== -1) {
executing.splice(index, 1);
}
});
executing.push(p);
}
await Promise.all(executing);
return results;
}
__name(limitPromiseAll, "limitPromiseAll");
async function requestWithRetry(url, method, config = {}, http, pluginsConfig, logger, retryIndex = 0) {
try {
const res = await http(method, url, config);
if (typeof res.data === "string" && res.data.includes("Could not connect to mysql")) {
throw new MySqlError();
}
return res.data;
} catch (error) {
if (error instanceof MySqlError) {
throw new MySqlError();
} else if (retryIndex < pluginsConfig.retryCount) {
logger.info(
`${url} 请求失败,正在重试... ${retryIndex + 1}/${pluginsConfig.retryCount}`
);
return await requestWithRetry(
url,
method,
config,
http,
pluginsConfig,
logger,
retryIndex + 1
);
} else {
throw new OverRetryError(`请求失败,超过最大重试次数: ${url}`);
}
}
}
__name(requestWithRetry, "requestWithRetry");
async function requestWithUrlSwitch(url, method, config = {}, http, pluginsConfig, logger, type = "CLIENT", urlIndex = 0, isUpdate = false) {
const list = type === "CLIENT" ? JM_CLIENT_URL_LIST : JM_IMAGE_URL_LIST;
console.log(list);
const urlCount = list.length;
const url_bak = url;
if (url.startsWith("/")) {
url = `https://${list[urlIndex]}${url}`;
}
try {
if (urlIndex < urlCount) {
const res = await requestWithRetry(
url,
method,
config,
http,
pluginsConfig,
logger
);
if (res instanceof ArrayBuffer && res.byteLength === 0) {
throw new EmptyBufferError();
}
return res;
} else {
throw new AllDomainFailedError();
}
} catch (error) {
const isMysqlError = error instanceof MySqlError;
const isEmptyBuffer = error instanceof EmptyBufferError;
const isOverRetryError = error instanceof OverRetryError;
const isAllDomainFailedError = error instanceof AllDomainFailedError;
if (isMysqlError || isEmptyBuffer || isOverRetryError) {
logger.info(`请求失败,尝试切换域名... ${urlIndex + 1}/${urlCount}`);
return await requestWithUrlSwitch(
url_bak,
method,
config,
http,
pluginsConfig,
logger,
type,
urlIndex + 1,
isUpdate
);
} else if (isAllDomainFailedError && !isUpdate) {
logger.info(`请求失败,尝试更新域名...`);
const res = await updateApiDomain(http, pluginsConfig, logger);
if (!res) throw new AllDomainFailedError();
return await requestWithUrlSwitch(
url_bak,
method,
config,
http,
pluginsConfig,
logger,
type,
0,
true
);
}
throw new Error(error);
}
}
__name(requestWithUrlSwitch, "requestWithUrlSwitch");
function getFileInfo(filePath) {
const normalizedPath = (0, import_path.normalize)(filePath);
const parsePath = (0, import_path.parse)(normalizedPath);
const ext = parsePath.ext.slice(1);
const fileName = parsePath.name;
const dir = parsePath.dir;
return { fileName, ext, dir };
}
__name(getFileInfo, "getFileInfo");
async function deleteFewDaysAgoFolders(path, days) {
const dirEntries = await (0, import_promises.readdir)(path, {
withFileTypes: true
});
const subfolderNames = dirEntries.filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
for (const folder of subfolderNames) {
const s = await (0, import_promises.stat)(`${path}/${folder}`);
const creationTime = s.birthtime || s.ctime;
const now = /* @__PURE__ */ new Date();
const diffTime = Math.abs(now.getTime() - creationTime.getTime());
const diffDays = Math.floor(diffTime / (1e3 * 3600 * 24));
if (diffDays >= days) {
(0, import_promises.rm)(`${path}/${folder}`, { recursive: true });
}
}
}
__name(deleteFewDaysAgoFolders, "deleteFewDaysAgoFolders");
function formatFileName(originName, name2, id, index) {
return originName.replaceAll("{{name}}", name2).replaceAll("{{id}}", id).replaceAll("{{index}}", index ? `${index}` : "").trim();
}
__name(formatFileName, "formatFileName");
function md5Hex(key, inputEncoding = "utf-8") {
return (0, import_crypto.createHash)("md5").update(key, inputEncoding).digest("hex");
}
__name(md5Hex, "md5Hex");
function decodeBase64(base64, timestamp, secret = JM_APP_DATA_SECRET) {
const dataB64 = Buffer.from(base64, "base64");
const md5 = md5Hex(`${timestamp}${secret}`);
const key = Buffer.from(md5);
const decipher = (0, import_crypto.createDecipheriv)("aes-256-ecb", key, null);
let dataAES = decipher.update(dataB64);
let decrypted = Buffer.concat([dataAES, decipher.final()]);
const decodedString = decrypted.toString("utf-8");
return JSON.parse(decodedString);
}
__name(decodeBase64, "decodeBase64");
async function updateApiDomain(http, config, logger) {
for (const url of JM_API_URL_DOMAIN_SERVER_LIST) {
try {
const encodeText = await requestWithRetry(
url,
"GET",
{ responseType: "text" },
http,
config,
logger
);
const domainJson = decodeBase64(
encodeText,
"",
JM_API_DOMAIN_SERVER_SECRET
);
JM_CLIENT_URL_LIST.splice(0, JM_CLIENT_URL_LIST.length);
JM_CLIENT_URL_LIST.push(...domainJson.Server);
console.log(domainJson);
console.log(JM_CLIENT_URL_LIST);
return true;
} catch (error) {
console.log(error);
}
}
return false;
}
__name(updateApiDomain, "updateApiDomain");
// src/entity/JMAppClient.ts
var import_crypto3 = require("crypto");
var import_form_data = __toESM(require("form-data"));
// src/abstract/JMClientAbstract.ts
var JMClientAbstract = class {
static {
__name(this, "JMClientAbstract");
}
root;
constructor(root) {
this.root = root;
}
setRoot(root) {
this.root = root;
}
getRoot() {
return this.root;
}
};
// src/abstract/JMPhotoAbstract.ts
var import_crypto2 = __toESM(require("crypto"));
var JMPhotoAbstract = class _JMPhotoAbstract {
static {
__name(this, "JMPhotoAbstract");
}
static SCRAMBLE_268850 = 268850;
static SCRAMBLE_421926 = 421926;
id;
images;
image_names;
splitNumbers;
setId(id) {
this.id = id;
}
getId() {
return this.id;
}
setImages(images) {
this.images = images;
}
getImages() {
return this.images;
}
setImageNames(imageNames) {
this.image_names = imageNames;
}
getImageNames() {
return this.image_names;
}
setSplitNumbers(splitNumbers) {
this.splitNumbers = splitNumbers;
}
getSplitNumbers() {
return this.splitNumbers;
}
/**
* 生成图片分割数
*/
generateSplitNumbers(scramble_id) {
const splitNumbers = this.image_names.map((name2) => {
if (this.id < scramble_id) {
return 0;
} else if (this.id < _JMPhotoAbstract.SCRAMBLE_268850) {
return 10;
} else {
const x = this.id < _JMPhotoAbstract.SCRAMBLE_421926 ? 10 : 8;
const s = `${this.id}${name2}`;
const md5 = import_crypto2.default.createHash("md5");
const hash = md5.update(s).digest("hex");
const lastChar = hash[hash.length - 1];
let num = lastChar.charCodeAt(0) % x;
num = num * 2 + 2;
return num;
}
});
this.splitNumbers = splitNumbers;
}
};
// src/entity/JMAppPhoto.ts
var JMAppPhoto = class _JMAppPhoto extends JMPhotoAbstract {
static {
__name(this, "JMAppPhoto");
}
series;
tags;
name;
addtime;
series_id;
is_favorite;
liked;
constructor(json) {
super();
for (const key in json) {
if (Object.prototype.hasOwnProperty.call(this, key)) {
this[key] = json[key];
}
}
}
setSeries(series) {
this.series = series;
}
getSeries() {
return this.series;
}
setTags(tags) {
this.tags = tags;
}
getTags() {
return this.tags;
}
setName(name2) {
this.name = name2;
}
getName() {
return this.name;
}
setAddtime(addtime) {
this.addtime = addtime;
}
getAddtime() {
return this.addtime;
}
setSeriesId(seriesId) {
this.series_id = seriesId;
}
getSeriesId() {
return this.series_id;
}
setIsFavorite(isFavorite) {
this.is_favorite = isFavorite;
}
getIsFavorite() {
return this.is_favorite;
}
setLiked(liked) {
this.liked = liked;
}
getLiked() {
return this.liked;
}
/**
* 从JSON数据返回JMPhoto实体类
* @param json JMPhoto JSON数据
* @returns
*/
static fromJson(json) {
return new _JMAppPhoto(json);
}
};
// src/abstract/JMAlbumAbstract.ts
var JMAlbumAbstract = class {
static {
__name(this, "JMAlbumAbstract");
}
/**
* 本子ID
*/
id;
/**
* 名称
*/
name;
/**
* 章节列表
*/
series = [];
/**
* 作品
*/
works;
/**
* 登场人物
*/
actors;
/**
* 标签
*/
tags;
/**
* 作者
*/
authors;
/**
* 描述
*/
description;
/**
* 点赞数
*/
likes;
/**
* 观看次数
*/
total_views;
/**
* 章节信息
*/
photos;
setId(id) {
this.id = id;
}
getId() {
return this.id;
}
setName(name2) {
this.name = name2;
}
getName() {
return this.name;
}
setSeries(series) {
this.series = series;
}
getSeries() {
return this.series;
}
setWorks(works) {
this.works = works;
}
getWorks() {
return this.works;
}
setActors(actors) {
this.actors = actors;
}
getActors() {
return this.actors;
}
setTags(tags) {
this.tags = tags;
}
getTags() {
return this.tags;
}
setAuthors(authors) {
this.authors = authors;
}
getAuthors() {
return this.authors;
}
setDescription(description) {
this.description = description;
}
getDescription() {
return this.description;
}
setLikes(likes) {
this.likes = likes;
}
getLikes() {
return this.likes;
}
setTotalViews(totalViews) {
this.total_views = totalViews;
}
getTotalViews() {
return this.total_views;
}
setPhotos(photos) {
this.photos = photos;
}
getPhotos() {
return this.photos;
}
};
// src/entity/JMAppAlbum.ts
var JMAppAlbum = class _JMAppAlbum extends JMAlbumAbstract {
static {
__name(this, "JMAppAlbum");
}
images;
addtime;
series_id;
comment_total;
related_list;
liked;
is_favorite;
is_aids;
price;
purchased;
constructor(json) {
super();
for (const key in json) {
if (Object.prototype.hasOwnProperty.call(this, key)) {
this[key] = json[key];
}
}
}
setImages(images) {
this.images = images;
}
getImages() {
return this.images;
}
setAddtime(addtime) {
this.addtime = addtime;
}
getAddtime() {
return this.addtime;
}
setSeriesId(seriesId) {
this.series_id = seriesId;
}
getSeriesId() {
return this.series_id;
}
setCommentTotal(commentTotal) {
this.comment_total = commentTotal;
}
getCommentTotal() {
return this.comment_total;
}
setRelatedList(relatedList) {
this.related_list = relatedList;
}
getRelatedList() {
return this.related_list;
}
setLiked(liked) {
this.liked = liked;
}
getLiked() {
return this.liked;
}
setIsFavorite(isFavorite) {
this.is_favorite = isFavorite;
}
getIsFavorite() {
return this.is_favorite;
}
setIsAids(isAids) {
this.is_aids = isAids;
}
getIsAids() {
return this.is_aids;
}
setPrice(price) {
this.price = price;
}
getPrice() {
return this.price;
}
setPurchased(purchased) {
this.purchased = purchased;
}
getPurchased() {
return this.purchased;
}
getPhotos() {
return super.getPhotos();
}
static fromJson(json) {
return new _JMAppAlbum(json);
}
};
// src/entity/JMAppClient.ts
var import_promises2 = require("fs/promises");
// src/utils/Image.ts
var import_fs2 = __toESM(require("fs"));
var import_sharp = __toESM(require("sharp"));
var import_archiver = __toESM(require("archiver"));
var import_archiver_zip_encrypted = __toESM(require("archiver-zip-encrypted"));
var import_path2 = require("path");
async function decodeImage(imageBuffer, num, path) {
if (num > 0) {
const metadata = await (0, import_sharp.default)(Buffer.from(imageBuffer)).metadata();
const height = metadata.height || 0;
const width = metadata.width || 0;
if (height < num) {
await (0, import_sharp.default)(Buffer.from(imageBuffer)).toFile(path);
return;
}
const over = height % num;
const move = Math.floor(height / num);
let decodedImageInstance = (0, import_sharp.default)({
create: {
width,
height,
channels: 3,
background: { r: 0, g: 0, b: 0 }
}
}).webp();
const croppeds = [];
for (let i = 0; i < num; i++) {
let currentMove = move;
let ySrc = height - move * (i + 1) - over;
let yDst = move * i;
if (i === 0) {
currentMove += over;
} else {
yDst += over;
}
const cropped = await (0, import_sharp.default)(imageBuffer).extract({ left: 0, top: ySrc, width, height: currentMove }).toBuffer();
croppeds.push({ top: yDst, cropped });
}
decodedImageInstance = decodedImageInstance.composite(
croppeds.map((c) => ({ input: c.cropped, top: c.top, left: 0 }))
);
await decodedImageInstance.toFile(path);
} else {
await (0, import_sharp.default)(Buffer.from(imageBuffer)).toFile(path);
}
}
__name(decodeImage, "decodeImage");
async function saveImage(imageBuffer, path) {
if (!imageBuffer.byteLength) return;
const buffer = Buffer.from(imageBuffer);
await (0, import_sharp.default)(buffer).toFile(path);
}
__name(saveImage, "saveImage");
async function archiverImage(directorys, outputPath, password, level = 9) {
if (!import_archiver.default.isRegisteredFormat("zip-encrypted")) {
import_archiver.default.registerFormat("zip-encrypted", import_archiver_zip_encrypted.default);
}
const output = import_fs2.default.createWriteStream(outputPath);
const options = {
zlib: { level }
// 压缩级别
};
if (password) {
options["encryptionMethod"] = "aes256";
options["password"] = password;
}
const archive = import_archiver.default.create(password ? "zip-encrypted" : "zip", options);
archive.pipe(output);
directorys.forEach(({ directory, destpath }) => {
archive.directory(directory, destpath);
});
await archive.finalize();
}
__name(archiverImage, "archiverImage");
async function archiverFile(path, outputPath, password, level = 9) {
if (!import_archiver.default.isRegisteredFormat("zip-encrypted")) {
import_archiver.default.registerFormat("zip-encrypted", import_archiver_zip_encrypted.default);
}
const output = import_fs2.default.createWriteStream(outputPath);
const options = {
zlib: { level }
// 压缩级别
};
if (password) {
options["encryptionMethod"] = "aes256";
options["password"] = password;
}
const archive = import_archiver.default.create(password ? "zip-encrypted" : "zip", options);
archive.pipe(output);
archive.file(path, { name: (0, import_path2.basename)(path) });
await archive.finalize();
}
__name(archiverFile, "archiverFile");
// src/entity/JMAppClient.ts
var import_path3 = require("path");
var import_sharp2 = __toESM(require("sharp"));
var import_muhammara = require("muhammara");
// src/abstract/JMBlogAbstract.ts
var JMBlogAbstract = class {
static {
__name(this, "JMBlogAbstract");
}
/**
* 文库信息
*/
info;
/**
* 关联的本子
*/
related_comics;
/**
* 相关文库
*/
related_blogs;
// --- Info 的 Getter 和 Setter ---
get Info() {
return this.info;
}
set Info(value) {
this.info = value;
}
// --- RelatedComics 的 Getter 和 Setter ---
get RelatedComics() {
return this.related_comics;
}
set RelatedComics(value) {
this.related_comics = value;
}
// --- RelatedBlogs 的 Getter 和 Setter ---
get RelatedBlogs() {
return this.related_blogs;
}
set RelatedBlogs(value) {
this.related_blogs = value;
}
};
// src/entity/JMAppBlog.ts
var JMAppBlog = class _JMAppBlog extends JMBlogAbstract {
static {
__name(this, "JMAppBlog");
}
constructor(json) {
super();
for (const key in json) {
if (Object.prototype.hasOwnProperty.call(this, key)) {
this[key] = json[key];
}
}
}
static fromJson(json) {
return new _JMAppBlog(json);
}
};
// src/entity/JMAppClient.ts
var JMAppClient = class extends JMClientAbstract {
static {
__name(this, "JMAppClient");
}
/**
* koishi 配置项
*/
config;
/**
* koishi 日志
*/
logger;
/**
* koishi http
*/
http;
puppeteer;
constructor(root, http, config, logger, puppeteer) {
super(root);
this.config = config;
this.logger = logger;
this.http = http;
this.puppeteer = puppeteer;
}
/**
* 登录,未完成
* @param username 用户名
* @param password 密码
* @returns 用户信息
*/
async login(username, password) {
const timestamp = this.getTimeStamp();
const { token, tokenparam } = this.getTokenAndTokenParam(timestamp);
const headers = {
"Accept-Encoding": "gzip, deflate",
"User-Agent": "Mozilla/5.0 (Linux; Android 9; V1938CT Build/PQ3A.190705.11211812; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/91.0.4472.114 Safari/537.36",
token,
tokenparam
};
const formData = new import_form_data.default();
formData.append("username", username);
formData.append("password", password);
const res = await this.http.post(
"https://www.cdnmhws.cc/login",
formData,
{ headers, responseType: "json" }
);
return decodeBase64(res.data, timestamp);
}
async search(keyword) {
if (this.config.debug) this.logger.info(`搜索本子: ${keyword}`);
const timestamp = this.getTimeStamp();
const { token, tokenparam } = this.getTokenAndTokenParam(timestamp);
const searchRes = await requestWithUrlSwitch(
"/search",
"POST",
{
params: { search_query: keyword },
headers: { token, tokenparam },
responseType: "json"
},
this.http,
this.config,
this.logger
);
return decodeBase64(searchRes.data, timestamp);
}
async getAlbumById(id) {
if (this.config.debug) this.logger.info(`获取本子(${id})信息`);
const timestamp = this.getTimeStamp();
const { token, tokenparam } = this.getTokenAndTokenParam(timestamp);
const res = await requestWithUrlSwitch(
"/album",
"POST",
{
params: { id },
headers: { token, tokenparam },
responseType: "json"
},
this.http,
this.config,
this.logger
);
const album_json = decodeBase64(res.data, timestamp);
if (!album_json.name) throw new AlbumNotExistError();
const album = JMAppAlbum.fromJson(album_json);
const series = album.getSeries();
const photos = [];
if (series.length) {
for (const s of series) {
const photo = await this.getPhotoById(s.id);
photos.push(photo);
}
} else {
const photo = await this.getPhotoById(id);
photos.push(photo);
}
if (this.config.debug)
this.logger.info(`本子 ${id} 章节数:${photos.length}`);
album.setPhotos(photos);
return album;
}
async getPhotoById(id) {
if (this.config.debug) this.logger.info(`获取章节(${id})信息`);
const timestamp = this.getTimeStamp();
const { token, tokenparam } = this.getTokenAndTokenParam(timestamp);
const res = await requestWithUrlSwitch(
"/chapter",
"POST",
{
params: { id },
headers: { token, tokenparam },
responseType: "json"
},
this.http,
this.config,
this.logger
);
const photo_json = decodeBase64(res.data, timestamp);
if (!photo_json.name) throw new PhotoNotExistError();
const photo = JMAppPhoto.fromJson(photo_json);
const images = photo.getImages();
const image_ids = images.map((image) => image.split(".")[0]);
photo.setImageNames(image_ids);
return photo;
}
async getBlogById(id) {
if (this.config.debug) this.logger.info(`获取文库(${id})信息`);
const timestamp = this.getTimeStamp();
const { token, tokenparam } = this.getTokenAndTokenParam(timestamp);
const res = await requestWithUrlSwitch(
"/blog",
"POST",
{
params: { id },
headers: { token, tokenparam },
responseType: "json"
},
this.http,
this.config,
this.logger
);
const blog_json = decodeBase64(res.data, timestamp);
if (!blog_json?.info?.title) throw new AlbumNotExistError();
return JMAppBlog.fromJson(blog_json);
}
async downloadByAlbum(album) {
const id = album.getId();
const path = `${this.root}/album/${id}`;
await (0, import_promises2.mkdir)(path, { recursive: true });
const photos = album.getPhotos();
for (const photo of photos) {
await this.downloadByPhoto(photo, "album", id, photos.length === 1);
}
}
async downloadByPhoto(photo, type = "photo", albumId = "", single = false) {
const images = photo.getImages();
const id = photo.getId();
let path = `${this.root}/${type}/${id}/origin`;
if (this.config.debug) {
this.logger.info(`开始下载: ${id}`);
if (type === "album") {
this.logger.info(`单章节: ${single ? "是" : "否"}`);
this.logger.info(`子章节: ${albumId ? "是" : "否"}`);
this.logger.info(`本子ID: ${albumId}`);
}
}
if (type === "album") {
if (single) {
path = `${this.root}/${type}/${albumId}/origin`;
} else {
path = `${this.root}/${type}/${albumId}/origin/${id}`;
}
}
if (this.config.debug) this.logger.info(`存储目录:${path}`);
await (0, import_promises2.mkdir)(path, { recursive: true });
await limitPromiseAll(
images.filter((image) => {
const imagePath = `${path}/${image}`;
const fileExists = fileExistsAsync(imagePath);
const fileSize = fileSizeAsync(imagePath);
return !fileExists || !fileSize;
}).map((image) => async () => {
const url = `/media/photos/${id}/${image}`;
if (this.config.debug) this.logger.info(`下载图片:${url}`);
const res = await requestWithUrlSwitch(
url,
"GET",
{ responseType: "arraybuffer" },
this.http,
this.config,
this.logger,
"IMAGE"
);
await saveImage(res, `${path}/${image}`);
}),
this.config.concurrentDownloadLimit
);
if (this.config.debug) this.logger.info(`${id} 下载完成,开始解密图片`);
await this.decodeByPhoto(photo, type, albumId, single);
}
async downloadFirstImageByAlbum(album) {
const id = album.getId();
const path = `${this.root}/album/${id}`;
await (0, import_promises2.mkdir)(path, { recursive: true });
const photos = album.getPhotos();
return await this.downloadAndDecodeFirstImageByPhoto(
photos[0],
"album",
id,
photos.length === 1
);
}
async downloadAndDecodeFirstImageByPhoto(photo, type = "photo", albumId = "", single = false) {
const image = photo.getImages()[0];
const id = photo.getId();
let path = `${this.root}/${type}/${id}/origin`;
if (type === "album") {
if (single) {
path = `${this.root}/${type}/${albumId}/origin`;
} else {
path = `${this.root}/${type}/${albumId}/origin/${id}`;
}
}
if (this.config.debug) this.logger.info(`存储目录:${path}`);
await (0, import_promises2.mkdir)(path, { recursive: true });
const url = `/media/photos/${id}/${image}`;
if (this.config.debug) this.logger.info(`下载图片:${url}`);
const res = await requestWithUrlSwitch(
url,
"GET",
{ responseType: "arraybuffer" },
this.http,
this.config,
this.logger,
"IMAGE"
);
await saveImage(res, `${path}/${image}`);
if (this.config.debug) this.logger.info(`${id} 下载完成,开始解密图片`);
let decodedPath = `${this.root}/${type}/${id}/decoded`;
if (type === "album" && !single) {
path = `${this.root}/${type}/${albumId}/origin/${id}`;
decodedPath = `${this.root}/${type}/${albumId}/decoded/${id}`;
}
await (0, import_promises2.mkdir)(decodedPath, { recursive: true });
const scramble_id = await this.requestScrambleId(id);
photo.generateSplitNumbers(scramble_id);
const splitNumber = photo.getSplitNumbers()[0];
const imagePath = `${path}/${image}`;
if (this.config.debug) this.logger.info(`解密图片:${imagePath}`);
const decodedImagePath = `${decodedPath}/${image}`;
const imageBuffer = await (0, import_promises2.readFile)(imagePath);
await decodeImage(imageBuffer, splitNumber, decodedImagePath);
return decodedImagePath;
}
async decodeByPhoto(photo, type = "photo", albumId = "", single = false) {
const images = photo.getImages();
const id = photo.getId();
const scramble_id = await this.requestScrambleId(id);
photo.generateSplitNumbers(scramble_id);
const splitNumbers = photo.getSplitNumbers();
let path = `${this.root}/${type}/${id}/origin`;
let decodedPath = `${this.root}/${type}/${id}/decoded`;
if (type === "album" && !single) {
path = `${this.root}/${type}/${albumId}/origin/${id}`;
decodedPath = `${this.root}/${type}/${albumId}/decoded/${id}`;
}
await (0, import_promises2.mkdir)(path, { recursive: true });
await (0, import_promises2.mkdir)(decodedPath, { recursive: true });
await limitPromiseAll(
images.filter((image) => {
const imagePath = `${decodedPath}/${image}`;
const fileExists = fileExistsAsync(imagePath);
const fileSize = fileSizeAsync(imagePath);
return !fileExists || !fileSize;
}).map((image, index) => async () => {
const imagePath = `${path}/${image}`;
if (this.config.debug) this.logger.info(`解密图片:${imagePath}`);
const decodedImagePath = `${decodedPath}/${image}`;
const imageBuffer = await (0, import_promises2.readFile)(imagePath);
await decodeImage(imageBuffer, splitNumbers[index], decodedImagePath);
}),
this.config.concurrentDecodeLimit
);
this.logger.info(`${id} 解密完成`);
}
async albumToPdf(album, password) {
const id = album.getId();
const photos = album.getPhotos();
if (photos.length === 1) {
const photo = photos[0];
return await this.photoToPdf(
photo,
`${photo.getName()}`,
"album",
id,
true,
password
);
} else {
let paths = [];
for (const [i, photo] of photos.entries()) {
const path = await this.photoToPdf(
photo,
`${photo.getName()}_${i + 1}`,
"album",
id,
false,
password
);
paths.push(path);
}
return paths;
}
}
async photoToPdf(photo, pdfName, type = "photo", albumId = "", single = false, password) {
const images = photo.getImages();
const id = photo.getId();
pdfName = sanitizeFileName(pdfName);
if (this.config.debug) this.logger.info(`开始生成PDF ${pdfName}.pdf`);
let path = (0, import_path3.join)(this.root, type, `${id}`);
if (type === "album") {
path = (0, import_path3.join)(this.root, type, `${albumId}`);
}
const pdfPath = (0, import_path3.join)(path, `${pdfName}.pdf`);
let pdfDoc;
try {
pdfDoc = new import_muhammara.Recipe("new", pdfPath, {
version: 1.6
});
} catch (error) {
throw new Error(error);
}
const tempPath = (0, import_path3.join)(path, `temp_${id}`);
await (0, import_promises2.mkdir)(tempPath);
for (const image of images) {
let imagePath = (0, import_path3.join)(path, "decoded", image);
if (type === "album" && !single) {
imagePath = (0, import_path3.join)(path, "decoded", `${id}`, image);
}
const buffer = await (0, import_promises2.readFile)(imagePath);
const ext = (0, import_path3.extname)(imagePath);
const jpgName = image.replace(ext, ".jpg");
const jpgPath = (0, import_path3.join)(tempPath, jpgName);
const sharpInstance = (0, import_sharp2.default)(buffer);
await sharpInstance.jpeg().toFile(jpgPath);
const metadata = await sharpInstance.metadata();
pdfDoc.createPage(metadata.width, metadata.height).image(jpgPath, 0, 0).endPage();
}
if (password) {
pdfDoc.encrypt({
userPassword: password,
ownerPassword: password,
userProtectionFlag: 4
});
}
try {
pdfDoc.endPDF(() => {
if (this.config.debug) this.logger.info(`PDF ${pdfName}.pdf 生成完成`);
});
} catch (error) {
throw new Error(error);
} finally {
await (0, import_promises2.rm)(tempPath, { recursive: true });
}
return pdfPath;
}
async blogToPdf(blog, password) {
const pdfName = sanitizeFileName(blog.Info.title);
if (this.config.debug) this.logger.info(`开始生成PDF ${pdfName}.pdf`);
const path = (0, import_path3.join)(this.root, "blog", `${blog.Info.id}`);
await (0, import_promises2.mkdir)(path, { recursive: true });
const pdfPath = (0, import_path3.join)(path, `${pdfName}.pdf`);
const pdfTempPath = (0, import_path3.join)(path, `temp.pdf`);
await this.writeBlogPdf(blog, pdfTempPath);
try {
const recipe = new import_muhammara.Recipe(pdfTempPath, pdfPath);
if (password) {
recipe.encrypt({
userPassword: password,
ownerPassword: password,
userProtectionFlag: 4
});
}
recipe.endPDF(() => {
if (this.config.debug) this.logger.info(`PDF ${pdfName}.pdf 生成完成`);
});
} catch (error) {
throw new Error(error);
} finally {
await (0, import_promises2.rm)(pdfTempPath, { recursive: true, force: true });
}
return pdfPath;
}
async writeBlogPdf(blog, pdfPath) {
const context = await this.puppeteer.browser.createBrowserContext();
const page = await context.newPage();
await page.setViewport({
width: 0,
height: 0,
deviceScaleFactor: 1
});
await page.evaluate((title) => {
document.title = title;
}, blog.Info.title);
await page.setContent(blog.Info.content, {
waitUntil: "networkidle0",
timeout: 60 * 1e3
});
const pdfBuffer = await page.pdf({
format: "A4",
printBackground: true,
// 确保背景颜色和图片被打印
margin: {
top: "25mm",
right: "25mm",
bottom: "25mm",
left: "25mm"
}
});
await (0, import_promises2.writeFile)(pdfPath, pdfBuffer);
context.close();
page.close();
}
async albumToZip(album, password, level = 6) {
const id = album.getId();
const series = album.getSeries();
const zipName = sanitizeFileName(album.getName());
if (this.config.debug) this.logger.info(`开始生成ZIP ${zipName}.zip`);
const path = (0, import_path3.join)(this.root, "album", `${id}`);
const directorys = [];
if (series.length > 1) {
for (const s of series) {
directorys.push({
directory: (0, import_path3.join)(path, "decoded", s.id),
destpath: `第${s.sort}章`
});
}
} else {
directorys.push({ directory: (0, import_path3.join)(path, "decoded"), destpath: false });
}
const zipPath = (0, import_path3.join)(path, `${zipName}.zip`);
await archiverImage(directorys, zipPath, password, level);
if (this.config.debug) this.logger.info(`ZIP ${zipName}.zip 生成完成`);
return zipPath;
}
async photoToZip(photo, zipName, password, level = 6) {
const id = photo.getId();
zipName = sanitizeFileName(zipName);
if (this.config.debug) this.logger.info(`开始生成ZIP ${zipName}.zip`);
const path = (0, import_path3.join)(this.root, "photo", `${id}`);
const zipPath = (0, import_path3.join)(path, `${zipName}.zip`);
await archiverImage(
[{ directory: (0, import_path3.join)(path, "decoded"), destpath: false }],
zipPath,
password,
level
);
if (this.config.debug) this.logger.info(`ZIP ${zipName}.zip 生成完成`);
return zipPath;
}
async blogToZip(blog, password, level = 6) {
const zipName = sanitizeFileName(blog.Info.title);
if (this.config.debug) this.logger.info(`开始生成ZIP ${zipName}.zip`);
const path = (0, import_path3.join)(this.root, "blog", `${blog.Info.id}`);
await (0, import_promises2.mkdir)(path, { recursive: true });
const pdfPath = (0, import_path3.join)(path, `${zipName}.pdf`);
const zipPath = (0, import_path3.join)(path, `${zipName}.zip`);
await this.writeBlogPdf(blog, pdfPath);
await archiverFile(pdfPath, zipPath, password, level);
if (this.config.debug) this.logger.info(`ZIP ${zipName}.zip 生成完成`);
return zipPath;
}
/**
* 获取时间戳
* @returns 时间戳
*/
getTimeStamp() {
const date = /* @__PURE__ */ new Date();
return date.getTime();
}
/**
* 获取Scramble ID
* @param id JM本子ID
*/
async requestScrambleId(id) {
const timestamp = this.getTimeStamp();
const { token, tokenparam } = this.getTokenAndTokenParam(
timestamp,
JM_APP_TOKEN_SECRET_2
);
const html = await requestWithUrlSwitch(
"/chapter_view_template",
"POST",
{ params: { id }, headers: { token, tokenparam }, responseType: "text" },
this.http,
this.config,
this.logger
);
return parseInt(html.match(JM_SCRAMBLE_ID)[1]);
}
/**
* 获取请求时所需的token和tokenparam
* @param timestamp 时间戳
* @param version APP版本
* @param secret 密钥
* @returns 请求时所需的token和tokenparam
*/
getTokenAndTokenParam(timestamp, secret = JM_APP_TOKEN_SECRET, version = JM_APP_VERSION) {
const key = `${timestamp}${secret}`;
const token = (0, import_crypto3.createHash)("md5").update(key).digest("hex");
const tokenparam = `${timestamp},${version}`;
return { token, tokenparam };
}
};
// src/processors/jmProcessor.ts
var import_koishi = require("koishi");
var import_promises3 = require("node:fs/promises");
var createJmProcessor = /* @__PURE__ */ __name((processorConfig, http, config, logger, puppeteer) => {
const {
root,
sendMethod,
password,
level,
fileName,
fileMethod,
cache,
debug
} = processorConfig;
return async (payload) => {
const { id, session, messageId, scope } = payload;
try {
const jmClient = new JMAppClient(root, http, config, logger, puppeteer);
let filePath;
switch (payload.type) {
case "album": {
const album = await jmClient.getAlbumById(id);
await jmClient.downloadByAlbum(album);
if (sendMethod === "zip") {
filePath = await jmClient.albumToZip(album, password, level);
} else {
filePath = await jmClient.albumToPdf(album, password);
}
break;
}
case "photo": {
const photo = await jmClient.getPhotoById(id);
await jmClient.downloadByPhoto(photo);
const photoName = photo.getName();
if (sendMethod === "zip") {
filePath = await jmClient.photoToZip(
photo,
photoName,
password,
level
);
} else {
filePath = await jmClient.photoToPdf(photo, photoName);
}
break;
}
case "blog": {
const blog = await jmClient.getBlogById(id);
if (sendMethod === "zip") {
filePath = await jmClient.blogToZip(blog, password, level);
} else {
filePath = await jmClient.blogToPdf(blog, password);
}
return;
}
// 理论上不会走到这里,但为了类型安全和健壮性,可以抛出错误
default:
throw new Error(`未知任务类型: ${payload.type}`);
}
if (typeof filePath === "string") {
const {
fileName: baseFileName,
ext: fileExt,
dir: fileDir
} = getFileInfo(filePath);
const finalName = formatFileName(fileName, baseFileName, id);
if (debug) logger.info(`文件名:${finalName}.${fileExt}`);
if (fileMethod === "buffer") {
const buffer = await (0, import_promises3.readFile)(filePath);
await session.send([
import_koishi.h.file(buffer, fileExt, { title: `${finalName}.${fileExt}` })
]);
} else {
await session.send([
import_koishi.h.file(`file:///${filePath}`, { title: `${finalName}.${fileExt}` })
]);
}
if (!cache) (0, import_promises3.rm)(fileDir, { recursive: true });
} else {
let currentFileDir = "";
for (const [index, p] of filePath.entries()) {
const {
fileName: baseFileName,
ext: fileExt,
dir: fileDir
} = getFileInfo(p);
const finalName = formatFileName(
fileName,
baseFileName,
id,
index + 1
);
if (debug) logger.info(`文件名:${finalName}.${fileExt}`);
if (fileMethod === "buffer") {
const buffer = await (0, import_promises3.readFile)(p);
await session.send([
import_koishi.h.file(buffer, fileExt, { title: `${finalName}.${fileExt}` })
]);
} else {
await session.send([
import_koishi.h.file(`file:///${p}`, { title: `${finalName}.${fileExt}` })
]);
}
currentFileDir = fileDir;
}
if (!cache) {
if (currentFileDir) {
(0, import_promises3.rm)(currentFileDir, { recu