UNPKG

@shangxueink/koishi-plugin-puppeteer-without-canvas

Version:
836 lines (820 loc) 32.7 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 name in all) __defProp(target, name, { get: all[name], 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, { SVG: () => SVG, Tag: () => Tag, default: () => src_default }); module.exports = __toCommonJS(src_exports); var import_puppeteer_core = __toESM(require("puppeteer-core")); var import_puppeteer_finder = __toESM(require("puppeteer-finder")); var import_koishi3 = require("koishi"); // src/svg.ts var import_koishi = require("koishi"); function hyphenate(source) { const result = {}; for (const key in source) { result[key.replace(/[A-Z]/g, (str) => "-" + str.toLowerCase())] = source[key]; } return result; } __name(hyphenate, "hyphenate"); function escapeHtml(source) { return source.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;"); } __name(escapeHtml, "escapeHtml"); var Tag = class _Tag { constructor(tag) { this.tag = tag; } static { __name(this, "Tag"); } parent; children = []; attributes = {}; innerText = ""; child(tag) { const child = new _Tag(tag); child.parent = this; this.children.push(child); return child; } attr(attributes) { this.attributes = { ...this.attributes, ...attributes }; return this; } data(innerText) { this.innerText = innerText; return this; } line(x1, y1, x2, y2, attr = {}) { this.child("line").attr({ ...hyphenate(attr), x1, y1, x2, y2 }); return this; } circle(cx, cy, r, attr = {}) { this.child("circle").attr({ ...hyphenate(attr), cx, cy, r }); return this; } rect(x1, y1, x2, y2, attr = {}) { this.child("rect").attr({ ...hyphenate(attr), x: x1, y: y1, width: y2 - y1, height: x2 - x1 }); return this; } text(text, x, y, attr = {}) { this.child("text").attr({ ...hyphenate(attr), x, y }).data(text); return this; } g(attr = {}) { return this.child("g").attr(hyphenate(attr)); } get outer() { const attrText = Object.keys(this.attributes).map((key) => ` ${key}="${escapeHtml(String(this.attributes[key]))}"`).join(""); return `<${this.tag}${attrText}>${this.inner}</${this.tag}>`; } get inner() { return this.children.length ? this.children.map((child) => child.outer).join("") : this.innerText; } }; var SVG = class extends Tag { static { __name(this, "SVG"); } view; width; height; constructor(options = {}) { super("svg"); const { size = 200, viewSize = size, width = size, height = size } = options; this.width = width; this.height = height; const ratio = viewSize / size; const { left = 0, top = 0, bottom = height * ratio, right = width * ratio } = options.viewBox || {}; this.view = { left, bottom, top, right }; this.attr({ width, height, viewBox: `${left} ${top} ${right} ${bottom}`, xmlns: "http://www.w3.org/2000/svg", version: "1.1" }); } fill(color) { this.rect(this.view.top, this.view.left, this.view.bottom, this.view.right, { style: `fill: ${color}` }); return this; } async render(ctx) { const page = await ctx.puppeteer.page(); await page.setContent(this.outer); const buffer = await page.screenshot({ clip: { x: 0, y: 0, width: this.width, height: this.height } }); page.close(); return import_koishi.h.image(buffer, "image/png"); } }; // src/canvas.ts var import_canvas = __toESM(require("@koishijs/canvas")); var import_koishi2 = require("koishi"); var import_path = require("path"); var import_url = require("url"); var kElement = Symbol("element"); var BaseElement = class { constructor(page, id) { this.page = page; this.id = id; } static { __name(this, "BaseElement"); } [kElement] = true; get selector() { return `document.querySelector("#${this.id}")`; } async dispose() { await this.page.evaluate(`${this.selector}?.remove()`); this.id = null; } }; var CanvasElement = class extends BaseElement { constructor(page, id, width, height, fontFaceSet = [], styleHandles = []) { super(page, id); this.width = width; this.height = height; this.fontFaceSet = fontFaceSet; this.styleHandles = styleHandles; } static { __name(this, "CanvasElement"); } stmts = []; ctx = new Proxy({ canvas: this, direction: "inherit", fillStyle: "#000000", filter: "none", font: "10px sans-serif", fontKerning: "auto", fontStretch: "normal", fontVariantCaps: "normal", globalAlpha: 1, globalCompositeOperation: "source-over", imageSmoothingEnabled: true, imageSmoothingQuality: "low", letterSpacing: "0px", lineCap: "butt", lineDashOffset: 0, lineJoin: "miter", lineWidth: 1, miterLimit: 10, shadowBlur: 0, shadowColor: "rgba(0, 0, 0, 0)", shadowOffsetX: 0, shadowOffsetY: 0, strokeStyle: "#000000", textAlign: "start", textBaseline: "alphabetic", textRendering: "auto", wordSpacing: "0px" }, { get: /* @__PURE__ */ __name((target, prop, receiver) => { if (Reflect.has(target, prop) || typeof prop === "symbol") { return Reflect.get(target, prop, receiver); } return new Proxy(() => { }, { apply: /* @__PURE__ */ __name((target2, thisArg, argArray) => { this.stmts.push(`ctx.${prop}(${argArray.map((value) => { if (value[kElement]) return value.selector; return JSON.stringify(value); }).join(", ")});`); }, "apply") }); }, "get"), set: /* @__PURE__ */ __name((target, prop, value, receiver) => { if (Reflect.has(target, prop)) { if (typeof prop !== "symbol") { this.stmts.push(`ctx.${prop} = ${JSON.stringify(value)};`); } return Reflect.set(target, prop, value, receiver); } }, "set") }); getContext(type) { return this.ctx; } async toDataURL(type) { if (!this.id) throw new Error("canvas has been disposed"); try { this.stmts.unshift(`(async (ctx) => {`); const expr = this.stmts.join("\n ") + ` })(${this.selector}.getContext('2d'))`; this.stmts = []; await this.page.evaluate(expr); return await this.page.evaluate(`${this.selector}.toDataURL(${JSON.stringify(type)})`); } catch (err) { await this.dispose(); throw err; } } async toBuffer(type) { const url = await this.toDataURL(type); return Buffer.from(url.slice(url.indexOf(",") + 1), "base64"); } async dispose() { await super.dispose(); await Promise.all(this.fontFaceSet.map(async (fontFace) => { await this.page.evaluate((fontFace2) => { document.fonts.delete(fontFace2); }, fontFace); })); await Promise.all(this.styleHandles.map(async (handle) => { try { await handle.evaluate((node) => node.remove()); await handle.dispose(); } catch (e) { } })); } }; var ImageElement = class extends BaseElement { constructor(ctx, page, id, source, type) { super(page, id); this.ctx = ctx; this.source = source; this.type = type; } static { __name(this, "ImageElement"); } naturalHeight; naturalWidth; async initialize() { let base64; if (this.source instanceof URL) { this.source = this.source.href; } if (typeof this.source === "string") { const file = await this.ctx.http.file(this.source); base64 = import_koishi2.Binary.toBase64(file.data); } else if (Buffer.isBuffer(this.source)) { base64 = this.source.toString("base64"); } else { base64 = import_koishi2.Binary.toBase64(this.source); } const size = await this.page.evaluate(`loadImage(${JSON.stringify(this.id)}, ${JSON.stringify(base64)}, ${JSON.stringify(this.type)})`); this.naturalWidth = size.width; this.naturalHeight = size.height; } }; var canvas_default = class extends import_canvas.default { static { __name(this, "default"); } static inject = ["puppeteer", "http"]; page; counter = 0; async start() { const page = await this.ctx.puppeteer.page(); try { await page.goto((0, import_url.pathToFileURL)((0, import_path.resolve)(__dirname, "../index.html")).href); this.page = page; } catch (err) { await page.close(); throw err; } } async stop() { await this.page?.close(); this.page = null; } async createCanvas(width, height, options) { const fontFaceSet = []; const styleHandles = []; try { const name = `canvas_${++this.counter}`; if (options && options.families.length && this.ctx.fonts) { try { const fonts = await this.ctx.fonts.get(options.families); await Promise.all(fonts.map(async (font) => { if (font.format === "google") { const style = await this.page.addStyleTag({ url: font.path }); styleHandles.push(style); } else { await this.page.evaluate((font2, fontFaceSet2) => { const fontFace = new FontFace( font2.family, `url(${font2.path}) format('${font2.format}')`, font2.descriptors ); document.fonts.add(fontFace); fontFaceSet2.push(fontFace); }, font, fontFaceSet); } })); if (options?.text) { await this.page.evaluate(async (text, families) => { await document.fonts.load( `1px ${families.join(",")}`, text ); }, options.text, options.families); } } catch (e) { this.ctx.logger("puppeteer").warn("加载字体失败,将使用系统默认字体:", e.message); } } await this.page.evaluate([ `const ${name} = document.createElement('canvas');`, `${name}.width = ${width};`, `${name}.height = ${height};`, `${name}.id = ${JSON.stringify(name)};`, `document.body.appendChild(${name});` ].join("\n")); return new CanvasElement(this.page, name, width, height, fontFaceSet, styleHandles); } catch (err) { this.ctx.logger("puppeteer").warn(err); throw err; } } async render(width, height, callback, options) { let canvas; try { canvas = await this.createCanvas(width, height, options); await callback(canvas.getContext("2d")); const buffer = await canvas.toBuffer("image/png"); return import_koishi2.h.image(buffer, "image/png"); } catch (err) { this.ctx.logger("puppeteer").warn(err); throw err; } finally { try { await canvas.dispose(); } catch (err) { this.ctx.logger("puppeteer").warn(err); } } } async loadImage(source, type) { const id = `image_${++this.counter}`; const image = new ImageElement(this.ctx, this.page, id, source, type); await image.initialize(); return image; } }; // src/index.ts var import_path2 = require("path"); var import_url2 = require("url"); var Puppeteer = class extends import_koishi3.Service { constructor(ctx, config) { super(ctx, "puppeteer"); this.config = config; if (this.config.enableCanvas !== false) { ctx.plugin(canvas_default); } this.registerHtmlComponent(); if (this.config.enableRestartCommand !== false) { ctx.command("puppeteer.restart", "重启 Puppeteer 浏览器服务").action(async ({ session }) => { try { session?.send("正在重启 Puppeteer 服务..."); await this.stopBrowser(); await this.startBrowser(); return "✅ Puppeteer 服务重启成功"; } catch (error) { ctx.logger.error("Puppeteer 服务重启失败:", error); return `❌ Puppeteer 服务重启失败: ${error.message}`; } }); } } static { __name(this, "Puppeteer"); } static [import_koishi3.Service.provide] = "puppeteer"; static inject = ["http"]; browser; executable; browserWSEndpoint; registerHtmlComponent() { const transformStyle = /* @__PURE__ */ __name((source, base = {}) => { return Object.entries({ ...base, ...source }).map(([key, value]) => { return `${(0, import_koishi3.hyphenate)(key)}: ${Array.isArray(value) ? value.join(", ") : value}`; }).join("; "); }, "transformStyle"); this.ctx.component("html", async (attrs, children) => { const head = []; const transform = /* @__PURE__ */ __name((element) => { if (element.type === "head") { head.push(...element.children); return; } const attrs2 = { ...element.attrs }; if (typeof attrs2.style === "object") { attrs2.style = transformStyle(attrs2.style); } return (0, import_koishi3.h)(element.type, attrs2, element.children.map(transform).filter(Boolean)); }, "transform"); await this.ensureConnected(); const page = await this.page(); try { if (attrs.src) { await page.goto(attrs.src); } else { await page.goto((0, import_url2.pathToFileURL)((0, import_path2.resolve)(__dirname, "../index.html")).href); const bodyStyle = typeof attrs.style === "object" ? transformStyle({ display: "inline-block" }, attrs.style) : ["display: inline-block", attrs.style].filter(Boolean).join("; "); const content = children.map(transform).filter(Boolean).join(""); const lang = attrs.lang ? ` lang="${attrs.lang}"` : ""; await page.setContent(`<html${lang}> <head>${head.join("")}</head> <body style="${bodyStyle}">${content}</body> </html>`); } await page.waitForNetworkIdle({ timeout: attrs.timeout ? +attrs.timeout : void 0 }); const body = await page.$(attrs.selector || "body"); const clip = await body.boundingBox(); const screenshot = await page.screenshot({ clip }); return import_koishi3.h.image(screenshot, "image/png"); } finally { await page?.close(); } }); } async start() { await this.startBrowser(); } async startBrowser() { const { remote, endpoint, executablePath, headers, headless, args = [], ...config } = this.config; try { if (remote) { if (!endpoint) { throw new Error("远程浏览器模式下必须提供 endpoint 参数"); } const connectOptions = { headers, ...config }; try { const endpointURL = new URL(endpoint); if (["ws:", "wss:"].includes(endpointURL.protocol)) { connectOptions.browserWSEndpoint = endpoint; } else if (["http:", "https:"].includes(endpointURL.protocol)) { connectOptions.browserURL = endpoint; } else { throw new Error(`不支持的协议: ${endpointURL.protocol},endpoint 必须以 ws://, wss://, http:// 或 https:// 开头`); } } catch (e) { if (e instanceof TypeError) { throw new Error(`无效的 endpoint URL: ${endpoint},请检查格式是否正确`); } throw e; } try { this.ctx.logger.info("正在连接远程浏览器: %c", endpoint); this.browser = await import_puppeteer_core.default.connect(connectOptions); this.ctx.logger.info("远程浏览器连接成功。"); if (connectOptions.browserWSEndpoint) { this.browserWSEndpoint = connectOptions.browserWSEndpoint; } else if (this.browser.wsEndpoint) { this.browserWSEndpoint = this.browser.wsEndpoint(); this.ctx.logger.debug("从 HTTP URL 获取 WebSocket 端点: %c", this.browserWSEndpoint); } } catch (e) { if (e.message?.includes("ECONNREFUSED")) { throw new Error(`无法连接到远程浏览器 ${endpoint},请确保远程浏览器已启动并且端口可访问`); } else if (e.message?.includes("not opened")) { throw new Error(`远程浏览器连接被拒绝,请确保提供的 endpoint 是正确的并且浏览器已启动调试模式`); } else { throw new Error(`连接远程浏览器失败: ${e.message || e}`); } } } else { this.executable = executablePath || (0, import_puppeteer_finder.default)(); if (!this.executable) { throw new Error("未找到 Chrome 可执行文件,请手动指定 executablePath 参数"); } if (!executablePath) { this.ctx.logger.info("找到 Chrome 可执行文件: %c", this.executable); } const localArgs = [...args]; const { proxyAgent } = this.ctx.http.config; if (proxyAgent && !localArgs.some((arg) => arg.startsWith("--proxy-server"))) { localArgs.push(`--proxy-server=${proxyAgent}`); } try { this.ctx.logger.info("正在启动本地浏览器..."); this.browser = await import_puppeteer_core.default.launch({ executablePath: this.executable, headless, args: localArgs, ...config }); this.ctx.logger.info("本地浏览器启动成功。"); this.browserWSEndpoint = this.browser.wsEndpoint(); } catch (e) { if (e.message?.includes("Failed to launch")) { throw new Error(`启动浏览器失败,请检查 Chrome 是否已安装或路径是否正确: ${e.message}`); } else { throw new Error(`启动浏览器失败: ${e.message || e}`); } } } } catch (error) { this.ctx.logger.error(`Puppeteer 初始化失败: `, error); throw error; } } async stop() { await this.stopBrowser(); } async stopBrowser() { try { if (this.browser) { if (this.config.remote) { await this.browser.disconnect(); } else { await this.browser.close(); } this.browser = null; this.browserWSEndpoint = null; } } catch (error) { this.ctx.logger.warn("停止浏览器时出现错误:", error.message); } } // 检查浏览器连接状态并尝试重连 async ensureConnected() { if (this.browser.connected) return; if (this.config.enableReconnect === false) { throw new Error("浏览器连接已断开,且未启用自动重连"); } const hasReconnectEndpoint = this.browserWSEndpoint || this.config.endpoint; if (!hasReconnectEndpoint) { throw new Error("浏览器连接已断开,且没有可用的重连端点"); } let retryCount = 0; const maxRetries = this.config.maxReconnectRetries ?? 3; while (retryCount < maxRetries) { try { this.ctx.logger.info(`浏览器连接已断开,尝试重新连接... (尝试 ${retryCount + 1}/${maxRetries})`); if (this.config.reconnectInterval > 0) { await new Promise((resolve3) => setTimeout(resolve3, this.config.reconnectInterval)); } const connectOptions = { ...this.config }; if (this.browserWSEndpoint) { connectOptions.browserWSEndpoint = this.browserWSEndpoint; this.ctx.logger.debug("使用保存的 WebSocket 端点重连: %c", this.browserWSEndpoint); } else if (this.config.endpoint) { try { const endpointURL = new URL(this.config.endpoint); if (["ws:", "wss:"].includes(endpointURL.protocol)) { connectOptions.browserWSEndpoint = this.config.endpoint; this.ctx.logger.debug("使用配置的 WebSocket 端点重连: %c", this.config.endpoint); } else if (["http:", "https:"].includes(endpointURL.protocol)) { connectOptions.browserURL = this.config.endpoint; this.ctx.logger.debug("使用配置的 HTTP 端点重连: %c", this.config.endpoint); } } catch (e) { this.ctx.logger.warn("解析端点 URL 失败: %c", e.message); } } this.browser = await import_puppeteer_core.default.connect(connectOptions); if (this.browser.connected) { this.ctx.logger.info("浏览器重新连接成功"); if (this.browser.wsEndpoint) { this.browserWSEndpoint = this.browser.wsEndpoint(); this.ctx.logger.debug("保存新的 WebSocket 端点: %c", this.browserWSEndpoint); } return; } else { throw new Error("连接后浏览器状态仍为断开"); } } catch (e) { retryCount++; if (e.message?.includes("404") || e.message?.includes("Unexpected server response: 404")) { this.ctx.logger.warn("检测到 404 错误,尝试使用原始配置重新连接"); try { this.browserWSEndpoint = null; const originalOptions = { ...this.config }; if (this.config.endpoint) { const endpointURL = new URL(this.config.endpoint); if (["ws:", "wss:"].includes(endpointURL.protocol)) { originalOptions.browserWSEndpoint = this.config.endpoint; } else if (["http:", "https:"].includes(endpointURL.protocol)) { originalOptions.browserURL = this.config.endpoint; } } this.browser = await import_puppeteer_core.default.connect(originalOptions); if (this.browser.connected) { this.ctx.logger.info("使用原始配置重新连接成功"); if (this.browser.wsEndpoint) { this.browserWSEndpoint = this.browser.wsEndpoint(); this.ctx.logger.debug("保存新的 WebSocket 端点: %c", this.browserWSEndpoint); } return; } } catch (reconnectError) { this.ctx.logger.warn("使用原始配置重新连接失败:", reconnectError.message); } } if (retryCount >= maxRetries) { this.ctx.logger.error(`浏览器重新连接失败 (${retryCount}/${maxRetries}):`, e.message); throw new Error(`浏览器重新连接失败: ${e.message}`); } else { this.ctx.logger.warn(`浏览器重新连接失败,将重试 (${retryCount}/${maxRetries}):`, e.message); await new Promise((resolve3) => setTimeout(resolve3, this.config.reconnectInterval * retryCount)); } } } } page = /* @__PURE__ */ __name(async (options) => { let page; try { await this.ensureConnected(); page = await this.browser.newPage(); if (options) { if (options?.beforeGotoPage) { await options.beforeGotoPage(page); } await page.goto(`${(0, import_url2.pathToFileURL)(options.url)}`, options?.gotoOptions); if (options?.content) { await page.setContent(options.content); } if (options?.families?.length && this.ctx.fonts) { try { const fonts = await this.ctx.fonts.get(options.families); await Promise.all(fonts.map(async (font) => { if (font.format === "google") { await page.addStyleTag({ content: `@import url('${font.path}')` }); } else { await page.evaluate((font2) => { const fontFace = new FontFace( font2.family, `url(${font2.path}) format('${font2.format}')`, font2.descriptors ); document.fonts.add(fontFace); }, font); } })); } catch (e) { this.ctx.logger.warn("加载字体失败,将使用系统默认字体:", e.message); } } } } catch (err) { if (page) { await page.close(); } this.ctx.logger.error("failed to create page: %s", err); throw err; } return page; }, "page"); svg = /* @__PURE__ */ __name(async (options) => { await this.ensureConnected(); return new SVG(options); }, "svg"); render = /* @__PURE__ */ __name(async (content, callback, families) => { await this.ensureConnected(); const url = (0, import_path2.resolve)(__dirname, "../index.html"); const page = await this.page({ url, content, families }); const renderConfig = this.config.render || {}; if (families?.length) { try { await page.addStyleTag({ content: `* {font-family: ${families.map((f) => `'${f}'`).join(", ")};}` }); await page.evaluate(async () => { await document.fonts.ready; await new Promise((resolve3) => setTimeout(resolve3, 100)); }); } catch (e) { this.ctx.logger.warn("应用字体样式失败:", e.message); } } callback ||= /* @__PURE__ */ __name(async (_, next) => page.$("body").then(next), "callback"); const output = await callback(page, async (handle) => { const clip = handle ? await handle.boundingBox() : null; const screenshotOptions = { clip, ...renderConfig }; const buffer = await page.screenshot(screenshotOptions); const imageType = renderConfig.type || "png"; return import_koishi3.h.image(buffer, `image/${imageType}`).toString(); }); page.close(); return output; }, "render"); }; ((Puppeteer2) => { Puppeteer2.filter = false; Puppeteer2.usage = ` --- 本插件提供浏览器 API 服务,主要用于网页截图、生成图片等功能。 **重要提示:** 1. **浏览器环境要求:** 为确保插件正常运行,请确保您的系统已安装 Chromium 浏览器,或已配置远程浏览器服务。 2. **版本匹配:** 建议保持 Chromium/Chrome 浏览器与本插件同步更新,以避免因版本不匹配导致的功能异常。 3. **Windows 系统兼容性:** 如果您在 Windows 服务器上运行,为支持最新版 Puppeteer 及其捆绑的 Chromium,您的操作系统至少需要是 **Windows Server 2016 或更高版本**。 注意: Windows Server 2012 R2 及更早版本已无法满足最新 Chrome 的运行要求。 --- **服务依赖:** - **必需服务:** http(自带服务,无需额外安装) - **可选服务:** [fonts](/market?keyword=font+email:shigma10826@gmail.com+email:saarchaffee@qq.com) --- <p>➣ <a href="https://github.com/shangxueink/koishi-plugin-puppeteer-without-canvas" target="_blank">点我前往项目地址</a></p> --- `; Puppeteer2.Config = import_koishi3.Schema.intersect([ import_koishi3.Schema.object({ remote: import_koishi3.Schema.boolean().description("是否连接到远程浏览器。").default(false) }).description("连接设置"), import_koishi3.Schema.union([ import_koishi3.Schema.object({ remote: import_koishi3.Schema.const(true).required(), endpoint: import_koishi3.Schema.string().description( "远程浏览器的端点。<br>例:<br>WebSocket URL: `ws://localhost:14550/devtools/browser/[id]`<br>HTTP URL: `http://localhost:14550`<br>" ).required(), headers: import_koishi3.Schema.dict(String).role("table").description( "连接到远程浏览器时使用的 HTTP 请求头。<br>注意:这与本地模式的 `args` 不同,headers 只影响连接请求,不影响浏览器本身的行为。<br>常用于设置身份验证、API密钥或自定义标识等。详细示例请参考 README。" ) }), import_koishi3.Schema.object({ remote: import_koishi3.Schema.const(false).default(false), executablePath: import_koishi3.Schema.string().description( "Chrome/Chromium 可执行文件的路径。<br>如果不指定,将自动从系统中查找。<br>如果自动查找失败,请手动指定此路径。" ), headless: import_koishi3.Schema.boolean().description("是否开启[无头模式](https://developer.chrome.com/blog/headless-chrome/)。无头模式下浏览器不会显示界面。").default(true), args: import_koishi3.Schema.array(String).description( "启动 Chrome/Chromium 浏览器时传递的命令行参数。<br>常用参数:<br>`--no-sandbox`: 禁用沙箱(在 Docker 或 root 用户下常用)<br>`--disable-gpu`: 禁用 GPU 加速<br>更多 [Chromium 参数请参考这个页面](https://peter.sh/experiments/chromium-command-line-switches/)。" ).default(process.getuid?.() === 0 ? ["--no-sandbox", "--disable-gpu"] : []) }) ]), import_koishi3.Schema.object({ enablePuppeteer: import_koishi3.Schema.boolean().description("是否注册 puppeteer 服务。").default(true), enableCanvas: import_koishi3.Schema.boolean().description("是否注册 canvas 服务。(默认关闭。)<br>注意: 这与[`koishi-plugin-canvas`](/market?keyword=koishi-plugin-canvas+email:shigma10826@gmail.com+email:void@anillc.cn+email:i.dlist@outlook.com)的`canvas`服务同名 但API不一致。").default(false), enableRestartCommand: import_koishi3.Schema.boolean().description("是否注册 puppeteer.restart 重启指令。启用后可通过指令重启浏览器服务。").default(false), enableReconnect: import_koishi3.Schema.boolean().description("是否启用浏览器自动重连功能。当浏览器连接断开时,会尝试重新连接。").default(true), reconnectInterval: import_koishi3.Schema.number().description("浏览器重连尝试的间隔时间(毫秒)。").default(1e3), maxReconnectRetries: import_koishi3.Schema.number().description("浏览器重连最大尝试次数。").default(3) }).description("功能设置"), import_koishi3.Schema.object({ render: import_koishi3.Schema.intersect([ import_koishi3.Schema.object({ type: import_koishi3.Schema.union(["png", "jpeg", "webp"]).description("默认渲染的图片类型。").default("png") }), import_koishi3.Schema.union([ import_koishi3.Schema.object({ type: import_koishi3.Schema.const("png") }), import_koishi3.Schema.object({ quality: import_koishi3.Schema.number().min(0).max(100).step(1).description("默认渲染的图片质量。").default(80) }) ]) ]) }).description("渲染设置"), import_koishi3.Schema.object({ defaultViewport: import_koishi3.Schema.object({ width: import_koishi3.Schema.natural().description("默认的视图宽度。").default(1280), height: import_koishi3.Schema.natural().description("默认的视图高度。").default(768), deviceScaleFactor: import_koishi3.Schema.number().min(0).description("默认的设备缩放比率。").default(2) }), ignoreHTTPSErrors: import_koishi3.Schema.boolean().description("在导航时忽略 HTTPS 错误。").default(false) }).description("浏览器设置") ]); })(Puppeteer || (Puppeteer = {})); var src_default = Puppeteer; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { SVG, Tag });