koishi-plugin-ehentai-comics
Version:
一个用于在 E-Hentai/ExHentai 上搜索和下载漫画的 Koishi 插件
561 lines (559 loc) • 29.2 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 __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/index.ts
var src_exports = {};
__export(src_exports, {
Config: () => Config,
apply: () => apply,
inject: () => inject,
name: () => name
});
module.exports = __toCommonJS(src_exports);
var import_koishi = require("koishi");
var path = __toESM(require("path"));
var import_promises = require("fs/promises");
var import_url = require("url");
var import_muhammara = require("muhammara");
var import_sharp = __toESM(require("sharp"));
var import_cheerio = require("cheerio");
var name = "ehentai-comics";
var inject = {
required: ["http"]
};
var logger = new import_koishi.Logger(name);
var galleryUrlRegex = /(e-hentai\.org|exhentai\.org)\/g\/(\d+)\/([a-f0-9]+)\/?/;
function bufferToDataURI(buffer, mime = "image/jpeg") {
return `data:${mime};base64,${buffer.toString("base64")}`;
}
__name(bufferToDataURI, "bufferToDataURI");
var Config = import_koishi.Schema.intersect([
import_koishi.Schema.object({
site: import_koishi.Schema.union([
import_koishi.Schema.const("e-hentai.org").description("E-Hentai (免费)"),
import_koishi.Schema.const("exhentai.org").description("ExHentai (需要登录)")
]).description("选择要使用的站点。").default("e-hentai.org"),
ipb_member_id: import_koishi.Schema.string().description("(可选)您的 `ipb_member_id` Cookie 值,用于登录 ExHentai。").role("secret"),
ipb_pass_hash: import_koishi.Schema.string().description("(可选)您的 `ipb_pass_hash` Cookie 值,用于登录 ExHentai。").role("secret"),
igneous: import_koishi.Schema.string().description("(可选)您的 `igneous` Cookie 值,登录 ExHentai 时可能需要。").role("secret")
}).description("站点与登录设置"),
import_koishi.Schema.object({
searchResultCount: import_koishi.Schema.number().min(1).max(25).step(1).role("slider").default(10).description("搜索结果显示的数量。"),
useForwardForSearch: import_koishi.Schema.boolean().description("【QQ平台】是否默认使用合并转发的形式发送【搜索结果】。").default(true),
useForwardForImages: import_koishi.Schema.boolean().description("【QQ平台】当以图片形式发送漫画时,是否默认使用【合并转发】。").default(true),
showImageInSearch: import_koishi.Schema.boolean().description("是否在【搜索结果】中显示封面图片。").default(true),
splitMessagesInSearch: import_koishi.Schema.boolean().description("【搜索结果】是否将文本和图片分开。").default(false)
// [!code ++]
}).description("消息发送设置"),
import_koishi.Schema.object({
downloadPath: import_koishi.Schema.string().description("PDF 文件和临时文件的保存目录。").default("./data/downloads/ehentai"),
defaultToPdf: import_koishi.Schema.boolean().description("是否默认将漫画下载为 PDF 文件。").default(true),
pdfPassword: import_koishi.Schema.string().role("secret").description("(可选)为生成的 PDF 文件设置一个打开密码。留空则不加密。"),
enableCompression: import_koishi.Schema.boolean().description("【PDF模式】是否启用图片压缩以减小 PDF 文件体积。").default(true),
compressionQuality: import_koishi.Schema.number().min(1).max(100).step(1).role("slider").default(80).description("【PDF模式】JPEG 图片质量 (1-100)。"),
pdfSendMethod: import_koishi.Schema.union([
import_koishi.Schema.const("buffer").description("Buffer (内存模式,最高兼容性)"),
import_koishi.Schema.const("file").description("File (文件路径模式,低兼容性)")
]).description("PDF 发送方式。如果 Koishi 与机器人客户端不在同一台设备或 Docker 环境中,必须选择“Buffer”。").default("buffer")
}).description("PDF 设置"),
import_koishi.Schema.object({
downloadConcurrency: import_koishi.Schema.number().min(1).max(10).step(1).description("【图片/PDF模式】下载漫画图片时的并行下载数量。数值越低越稳定。").default(5),
downloadTimeout: import_koishi.Schema.number().min(1).default(30).description("【高级】单张图片下载的超时时间(秒)。"),
downloadRetries: import_koishi.Schema.number().min(0).max(5).step(1).description("【高级】单张图片下载失败后的自动重试次数。").default(3),
scrapeDelay: import_koishi.Schema.number().min(0.2).default(1).description("【高级】每次抓取网页之间的延迟(秒),以防止IP被封禁。"),
debug: import_koishi.Schema.boolean().description("是否在控制台输出详细的调试日志。用于排查问题。").default(false)
}).description("下载与调试设置")
]);
function apply(ctx, config) {
if (config.site === "exhentai.org") {
if (config.ipb_member_id && config.ipb_pass_hash) {
logger.info(`ExHentai 模式已启用,并检测到 Cookie 配置${config.igneous ? " (包含 igneous)" : ""}。`);
} else {
logger.warn("ExHentai 模式已启用,但未完整配置 Cookie。访问受限内容可能会失败。");
}
}
const siteBase = `https://${config.site}`;
const apiBase = `https://api.e-hentai.org/api.php`;
function buildHeaders() {
const cookieParts = [];
if (config.ipb_member_id) cookieParts.push(`ipb_member_id=${config.ipb_member_id}`);
if (config.ipb_pass_hash) cookieParts.push(`ipb_pass_hash=${config.ipb_pass_hash}`);
if (config.igneous) cookieParts.push(`igneous=${config.igneous}`);
return {
"Cookie": cookieParts.join("; "),
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
"Accept-Language": "en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7"
};
}
__name(buildHeaders, "buildHeaders");
async function getGalleryMetadata(galleries) {
try {
const payload = { method: "gdata", gidlist: galleries.map((g) => [g.gid, g.token]), namespace: 1 };
const response = await ctx.http.post(apiBase, payload, { headers: buildHeaders() });
let data = response;
if (typeof data === "string") {
try {
data = JSON.parse(data);
} catch (e) {
logger.error("[API] 手动解析 JSON 失败", { rawResponse: data });
return [];
}
}
if (!data) {
logger.warn("[API] 响应为空或无效");
return [];
}
if (config.debug) {
logger.info("[API] 解析后的数据: %o", data);
}
if (data.error) {
logger.warn(`[API] E-Hentai API 返回明确错误: ${data.error}`);
return [];
}
return data.gmetadata || [];
} catch (error) {
logger.error(`[API] 请求元数据时发生网络错误`, { error: error.response?.data || error.message });
return [];
}
}
__name(getGalleryMetadata, "getGalleryMetadata");
async function searchGalleries(keyword) {
const searchUrl = `${siteBase}/?f_search=${encodeURIComponent(keyword)}`;
if (config.debug) logger.info(`[搜索] 正在抓取搜索页面: ${searchUrl}`);
try {
const html = await ctx.http.get(searchUrl, { headers: buildHeaders() });
const $ = (0, import_cheerio.load)(html);
const results = [];
$("table.gltc tbody tr").each((i, elem) => {
const linkElement = $(elem).find("td.gl3c a");
const imgElement = $(elem).find("td.gl2c img");
const url = linkElement.attr("href");
const title = linkElement.find(".glink").text();
let thumb = imgElement.attr("data-src") || imgElement.attr("src");
const tags = [];
$(elem).find(".gt, .gtl").each((_, tagElem) => {
tags.push($(tagElem).attr("title"));
});
if (url && title) results.push({ url, title, thumb, tags });
});
return results.slice(0, config.searchResultCount);
} catch (error) {
logger.error(`[搜索] 抓取或解析搜索页面失败: ${keyword}`, { error });
return [];
}
}
__name(searchGalleries, "searchGalleries");
async function scrapeWithRetry(url, parser) {
for (let i = 0; i <= config.downloadRetries; i++) {
try {
await (0, import_koishi.sleep)(config.scrapeDelay * 1e3);
const html = await ctx.http.get(url, { headers: buildHeaders() });
return parser(html);
} catch (error) {
if (i < config.downloadRetries) {
if (config.debug) logger.warn(`[抓取] 页面 ${url} 失败 (第 ${i + 1} 次), 2秒后重试...`);
await (0, import_koishi.sleep)(2e3);
} else {
logger.error(`[抓取] 页面 ${url} 在重试 ${config.downloadRetries} 次后最终失败。`);
return null;
}
}
}
}
__name(scrapeWithRetry, "scrapeWithRetry");
async function getImageUrlsFromGallery(gid, gtoken) {
const gdata = await getGalleryMetadata([{ gid, token: gtoken }]);
const [metadata] = gdata;
if (!metadata) throw new Error("无法获取画廊元数据,请检查Cookie或站点配置。");
const fileCount = parseInt(metadata.filecount, 10);
const galleryUrl = `${siteBase}/g/${gid}/${gtoken}/`;
const allImageUrls = new Array(fileCount).fill(null);
logger.info(`[抓取] 画廊共有 ${fileCount} 张图片。开始抓取图片链接...`);
const allDetailLinks = [];
const firstPageUrl = `${galleryUrl}?p=0`;
const firstPageLinks = await scrapeWithRetry(firstPageUrl, (html) => {
const $ = (0, import_cheerio.load)(html);
if ($("#gdt").length === 0) {
logger.error(`[抓取] 错误:页面 ${firstPageUrl} HTML内容中未找到画廊元素(#gdt)。`);
return [];
}
const links = [];
$("#gdt a").each((_, elem) => {
const href = $(elem).attr("href");
if (href && href.includes("/s/")) links.push(href);
});
return links;
});
if (!firstPageLinks || firstPageLinks.length === 0) {
logger.error(`[抓取] 无法从第一页获取任何图片链接,任务中止。请检查网络或 Cookie。`);
return [];
}
const thumbsPerPage = firstPageLinks.length;
const pageCount = Math.ceil(fileCount / thumbsPerPage);
logger.info(`[抓取] 动态检测到每页有 ${thumbsPerPage} 个缩略图。预计总页数: ${pageCount}。`);
allDetailLinks.push(...firstPageLinks);
if (pageCount > 1) {
const remainingPagePromises = Array.from({ length: pageCount - 1 }, (_, i) => {
const pageNum = i + 1;
const pageUrl = `${galleryUrl}?p=${pageNum}`;
return scrapeWithRetry(pageUrl, (html) => {
const $ = (0, import_cheerio.load)(html);
const links = [];
$("#gdt a").each((_2, elem) => {
const href = $(elem).attr("href");
if (href && href.includes("/s/")) links.push(href);
});
if (config.debug) logger.info(`[抓取] 页面 ${pageNum + 1}/${pageCount} (${pageUrl}) 找到 ${links.length} 个详情链接。`);
return links;
});
});
const resultsOfRemainingPages = await Promise.all(remainingPagePromises);
resultsOfRemainingPages.forEach((links) => {
if (links) allDetailLinks.push(...links);
});
}
logger.info(`[抓取] 阶段一完成:共收集到 ${allDetailLinks.length} 个有效的图片详情页链接。`);
const detailPagePromises = allDetailLinks.map((link) => {
return scrapeWithRetry(link, (html) => {
const $$ = (0, import_cheerio.load)(html);
const imageUrl = $$("#img").attr("src");
const linkIndexMatch = link.match(/-(\d+)$/);
if (imageUrl && linkIndexMatch) {
const index = parseInt(linkIndexMatch[1], 10) - 1;
return { index, imageUrl };
}
logger.warn(`[抓取] 在 ${link} 未找到图片URL或索引。`);
return null;
});
});
const resultsOfDetails = await Promise.all(detailPagePromises);
let foundCount = 0;
for (const result of resultsOfDetails) {
if (result && result.index < allImageUrls.length && !allImageUrls[result.index]) {
allImageUrls[result.index] = result.imageUrl;
foundCount++;
}
}
logger.info(`[抓取] 阶段二完成:成功提取 ${foundCount}/${fileCount} 个最终图片链接。`);
if (foundCount < fileCount) {
logger.warn(`[抓取] 警告:发现漏页现象,应有 ${fileCount} 张,实际找到 ${foundCount} 张。请检查网络或目标画廊。`);
}
return allImageUrls.filter((url) => !!url);
}
__name(getImageUrlsFromGallery, "getImageUrlsFromGallery");
async function downloadImage(url, index, referer) {
for (let i = 0; i <= config.downloadRetries; i++) {
try {
const headers = buildHeaders();
if (referer) {
headers["Referer"] = referer;
}
const arrayBuffer = await ctx.http.get(url, {
timeout: config.downloadTimeout * 1e3,
responseType: "arraybuffer",
headers
});
return { index, buffer: Buffer.from(arrayBuffer) };
} catch (error) {
if (i < config.downloadRetries) {
if (config.debug) logger.warn(`[下载] 图片 ${index + 1} (${url}) 下载失败 (第 ${i + 1} 次), 2秒后重试...`);
await (0, import_koishi.sleep)(2e3);
} else {
logger.error(`[下载] 图片 ${index + 1} (${url}) 在重试 ${config.downloadRetries} 次后最终失败。`);
return { index, error };
}
}
}
}
__name(downloadImage, "downloadImage");
ctx.command("ehsearch <keyword:text>", "E-Hentai 漫画搜索").action(async ({ session }, keyword) => {
if (!keyword) return "请输入关键词。";
const [statusMessageId] = await session.send((0, import_koishi.h)("quote", { id: session.messageId }) + "正在搜索...");
try {
const results = await searchGalleries(keyword);
if (results.length === 0) {
await session.send("未找到任何结果。");
return;
}
const useForward = config.useForwardForSearch && ["qq", "onebot"].includes(session.platform);
if (useForward) {
const forwardElements = [(0, import_koishi.h)("p", `搜索到 ${results.length} 个结果,为您展示前 ${Math.min(results.length, config.searchResultCount)} 个:`)];
for (const [index, gallery] of results.entries()) {
const textElements = [];
const parsedTags = { parody: [], character: [], group: [], artist: [], male: [], female: [], misc: [] };
for (const tag of gallery.tags) {
const parts = tag.split(":");
if (parts.length > 1) {
const namespace = parts[0];
const tagName = parts.slice(1).join(":");
if (parsedTags[namespace]) parsedTags[namespace].push(tagName);
else parsedTags.misc.push(tag);
} else {
parsedTags.misc.push(tag);
}
}
let tagInfo = `[标题] ${gallery.title}
`;
if (parsedTags.parody.length > 0) tagInfo += `[原作] ${parsedTags.parody.join(", ")}
`;
if (parsedTags.artist.length > 0) tagInfo += `[作者] ${parsedTags.artist.join(", ")}
`;
if (parsedTags.group.length > 0) tagInfo += `[团体] ${parsedTags.group.join(", ")}
`;
if (parsedTags.character.length > 0) tagInfo += `[角色] ${parsedTags.character.join(", ")}
`;
const otherTags = [...parsedTags.female, ...parsedTags.male, ...parsedTags.misc];
if (otherTags.length > 0) tagInfo += `[标签] ${otherTags.slice(0, 8).join(", ")}${otherTags.length > 8 ? "..." : ""}
`;
tagInfo += `[URL] ${gallery.url}`;
textElements.push((0, import_koishi.h)("p", "──────────"));
textElements.push((0, import_koishi.h)("p", tagInfo));
const imageElement = config.showImageInSearch && gallery.thumb ? await (async () => {
try {
const result = await downloadImage(gallery.thumb, index, siteBase + "/");
return "buffer" in result ? import_koishi.h.image(bufferToDataURI(result.buffer)) : null;
} catch (e) {
if (config.debug) logger.warn(`[搜索] 下载封面失败: ${gallery.thumb}`, e);
return null;
}
})() : null;
if (config.splitMessagesInSearch) {
forwardElements.push((0, import_koishi.h)("message", textElements));
if (imageElement) forwardElements.push((0, import_koishi.h)("message", imageElement));
} else {
if (imageElement) textElements.push(imageElement);
forwardElements.push((0, import_koishi.h)("message", textElements));
}
}
await session.send((0, import_koishi.h)("figure", {}, forwardElements));
} else {
await session.send(`搜索到 ${results.length} 个结果,为您展示前 ${Math.min(results.length, config.searchResultCount)} 个:`);
for (const [index, gallery] of results.entries()) {
const textElements = [];
const parsedTags = { parody: [], character: [], group: [], artist: [], male: [], female: [], misc: [] };
for (const tag of gallery.tags) {
const parts = tag.split(":");
if (parts.length > 1) {
const namespace = parts[0];
const tagName = parts.slice(1).join(":");
if (parsedTags[namespace]) parsedTags[namespace].push(tagName);
else parsedTags.misc.push(tag);
} else {
parsedTags.misc.push(tag);
}
}
let tagInfo = `[标题] ${gallery.title}
`;
if (parsedTags.parody.length > 0) tagInfo += `[原作] ${parsedTags.parody.join(", ")}
`;
if (parsedTags.artist.length > 0) tagInfo += `[作者] ${parsedTags.artist.join(", ")}
`;
if (parsedTags.group.length > 0) tagInfo += `[团体] ${parsedTags.group.join(", ")}
`;
if (parsedTags.character.length > 0) tagInfo += `[角色] ${parsedTags.character.join(", ")}
`;
const otherTags = [...parsedTags.female, ...parsedTags.male, ...parsedTags.misc];
if (otherTags.length > 0) tagInfo += `[标签] ${otherTags.slice(0, 8).join(", ")}${otherTags.length > 8 ? "..." : ""}
`;
tagInfo += `[URL] ${gallery.url}`;
textElements.push((0, import_koishi.h)("p", "──────────"));
textElements.push((0, import_koishi.h)("p", tagInfo));
const imageElement = config.showImageInSearch && gallery.thumb ? await (async () => {
try {
const result = await downloadImage(gallery.thumb, index, siteBase + "/");
return "buffer" in result ? import_koishi.h.image(bufferToDataURI(result.buffer)) : null;
} catch (e) {
if (config.debug) logger.warn(`[搜索] 下载封面失败: ${gallery.thumb}`, e);
return null;
}
})() : null;
if (config.splitMessagesInSearch) {
await session.send(textElements);
await (0, import_koishi.sleep)(500);
if (imageElement) await session.send(imageElement);
await (0, import_koishi.sleep)(500);
} else {
if (imageElement) textElements.push(imageElement);
await session.send(textElements);
await (0, import_koishi.sleep)(1e3);
}
}
}
} catch (error) {
logger.error(`[搜索] 命令执行失败。关键词: "${keyword}"`, { error });
return "搜索失败,请查看后台日志。";
} finally {
try {
await session.bot.deleteMessage(session.channelId, statusMessageId);
} catch (e) {
if (config.debug) logger.warn("撤回搜索状态消息失败", e);
}
}
});
ctx.command("ehdownload <url:string>", "E-Hentai 漫画下载").option("output", "-o <type:string>").action(async ({ session, options }, url) => {
if (!url) return "请输入画廊 URL。";
const match = url.match(galleryUrlRegex);
if (!match) return "URL 格式不正确。请输入一个有效的 E-Hentai/ExHentai 画廊链接。";
const [, , gid, gtoken] = match;
const [statusMessageId] = await session.send((0, import_koishi.h)("quote", { id: session.messageId }) + `收到请求,正在处理画廊 ${gid}...`);
try {
if (config.debug) logger.info(`[下载] 开始处理画廊 ${gid}/${gtoken}`);
const allImageUrls = await getImageUrlsFromGallery(gid, gtoken);
if (allImageUrls.length === 0) return "任务中止:未能从画廊页面中提取到任何图片链接,请检查后台日志以确认失败原因。";
logger.info(`[下载] 链接抓取成功,共 ${allImageUrls.length} 张图片,准备下载...`);
const reportFailures = /* @__PURE__ */ __name(async (failedIndexes) => {
if (failedIndexes.length > 0) {
const sortedIndexes = failedIndexes.map((i) => i + 1).sort((a, b) => a - b);
await session.send(`任务完成,但以下图片下载失败,已跳过:
第 ${sortedIndexes.join(", ")} 张。`);
}
}, "reportFailures");
const outputType = options.output || (config.defaultToPdf ? "pdf" : "image");
if (outputType === "pdf") {
const [metadata] = await getGalleryMetadata([{ gid, token: gtoken }]);
const galleryTitle = metadata?.title_jpn || metadata?.title || gid;
const safeFilename = galleryTitle.replace(/[\\/:\*\?"<>\|]/g, "_");
const downloadDir = path.resolve(ctx.app.baseDir, config.downloadPath);
const tempPdfPath = path.resolve(downloadDir, `${safeFilename}_${Date.now()}.pdf`);
const finalPdfPath = path.resolve(downloadDir, `${safeFilename}.pdf`);
const tempImageDir = path.resolve(downloadDir, `temp_${gid}_${Date.now()}`);
await (0, import_promises.mkdir)(tempImageDir, { recursive: true });
let recipe;
const failedImageIndexes = [];
try {
recipe = new import_muhammara.Recipe("new", tempPdfPath, { version: 1.6 });
const successfulDownloads = [];
for (let i = 0; i < allImageUrls.length; i += config.downloadConcurrency) {
const chunk = allImageUrls.slice(i, i + config.downloadConcurrency);
if (config.debug) logger.info(`[下载] [PDF] 正在下载批次 ${Math.floor(i / config.downloadConcurrency) + 1}...`);
const chunkPromises = chunk.map((url2, idx) => downloadImage(url2, i + idx));
const chunkResults = await Promise.all(chunkPromises);
for (const result of chunkResults) {
if ("buffer" in result) successfulDownloads.push(result);
else failedImageIndexes.push(result.index);
}
}
successfulDownloads.sort((a, b) => a.index - b.index);
for (const { index, buffer } of successfulDownloads) {
if (config.debug) logger.info(`[下载] [PDF] 正在处理第 ${index + 1}/${allImageUrls.length} 张图片并添加到PDF...`);
const imagePath = path.resolve(tempImageDir, `${index + 1}.jpg`);
const sharpInstance = (0, import_sharp.default)(buffer);
const jpegOptions = {};
if (config.enableCompression) {
jpegOptions.quality = config.compressionQuality;
}
await sharpInstance.jpeg(jpegOptions).toFile(imagePath);
const md = await (0, import_sharp.default)(imagePath).metadata();
recipe.createPage(md.width, md.height).image(imagePath, 0, 0).endPage();
}
if (config.pdfPassword) recipe.encrypt({ userPassword: config.pdfPassword, ownerPassword: config.pdfPassword });
recipe.endPDF();
await (0, import_promises.rename)(tempPdfPath, finalPdfPath);
logger.info(`[下载] [PDF] 正在发送 PDF 文件: ${finalPdfPath}`);
const fileAttributes = { filename: `${safeFilename}.pdf`, title: `${safeFilename}.pdf` };
if (config.pdfSendMethod === "buffer") {
const pdfBuffer = await (0, import_promises.readFile)(finalPdfPath);
await session.send(import_koishi.h.file(pdfBuffer, "application/pdf", fileAttributes));
} else {
await session.send(import_koishi.h.file((0, import_url.pathToFileURL)(finalPdfPath).href, fileAttributes));
}
} finally {
try {
await (0, import_promises.unlink)(finalPdfPath);
} catch (e) {
}
try {
await (0, import_promises.unlink)(tempPdfPath);
} catch (e) {
}
try {
await (0, import_promises.rm)(tempImageDir, { recursive: true, force: true });
} catch (e) {
}
await reportFailures(failedImageIndexes);
}
} else {
if (config.useForwardForImages && ["qq", "onebot"].includes(session.platform)) {
const forwardElements = [];
const failedImageIndexes = [];
for (let i = 0; i < allImageUrls.length; i += config.downloadConcurrency) {
const chunk = allImageUrls.slice(i, i + config.downloadConcurrency);
const chunkPromises = chunk.map((url2, idx) => downloadImage(url2, i + idx));
const chunkResults = await Promise.all(chunkPromises);
for (const result of chunkResults) {
if ("buffer" in result) {
forwardElements.push(import_koishi.h.image(bufferToDataURI(result.buffer)));
} else {
failedImageIndexes.push(result.index);
forwardElements.push((0, import_koishi.h)("p", `第 ${result.index + 1} 张图片下载失败`));
}
}
}
if (forwardElements.length > 0) await session.send((0, import_koishi.h)("figure", {}, forwardElements));
else await session.send("所有图片都下载失败了,无法发送。");
} else {
for (const [index, imageUrl] of allImageUrls.entries()) {
try {
const result = await downloadImage(imageUrl, index);
if ("buffer" in result) {
await session.send([
(0, import_koishi.h)("p", `第 ${index + 1} / ${allImageUrls.length} 张`),
import_koishi.h.image(bufferToDataURI(result.buffer))
]);
} else {
await session.send(`发送第 ${index + 1} 张图片失败(下载错误),已跳过。`);
}
} catch (error) {
logger.warn(`[下载] 发送单张图片失败。GID: ${gid}, 图片URL: ${imageUrl}`, { error });
await session.send(`发送第 ${index + 1} 张图片失败,已跳过。`);
}
await (0, import_koishi.sleep)(1500);
}
}
}
} catch (error) {
logger.error(`[下载] 任务失败。GID: ${gid}`, { error: error.message, stack: error.stack });
return (0, import_koishi.h)("quote", { id: session.messageId }) + `下载失败:${error.message}`;
} finally {
try {
await session.bot.deleteMessage(session.channelId, statusMessageId);
} catch (e) {
if (config.debug) logger.warn("撤回初始状态消息失败", e);
}
}
});
}
__name(apply, "apply");
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Config,
apply,
inject,
name
});