UNPKG

koishi-plugin-ehentai-comics

Version:

一个用于在 E-Hentai/ExHentai 上搜索和下载漫画的 Koishi 插件

561 lines (559 loc) 29.2 kB
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 });