diff --git a/lib/puppeteer/puppeteer.js b/lib/puppeteer/puppeteer.js index 47498bf..7c615c6 100644 --- a/lib/puppeteer/puppeteer.js +++ b/lib/puppeteer/puppeteer.js @@ -1,27 +1,24 @@ -import Renderer from '../renderer/Renderer.js' +import { segment } from 'oicq' +import Renderer from '../renderer/loader.js' /** * 暂时保留对手工引用puppeteer.js的兼容 * 后期会逐步废弃 * 只提供截图及分片截图功能 */ -export default { - // 截图 - async screenshot (name, data = {}) { - let renderer = Renderer.getRenderer() +let renderer = Renderer.getRenderer('puppeteer') +renderer.screenshot = async (name, data) => { let img = await renderer.render(name, data) return img ? segment.image(img) : img - }, - - // 分片截图 - async screenshots (name, data = {}) { - let renderer = Renderer.getRenderer() +} +renderer.screenshots = async (name, data) => { data.multiPage = true let imgs = await renderer.render(name, data) || [] let ret = [] for (let img of imgs) { - ret.push(img ? segment.image(img) : img) + ret.push(img ? segment.image(img) : img) } return ret.length > 0 ? ret : false - } } + +export default renderer \ No newline at end of file diff --git a/lib/renderer/Renderer.js b/lib/renderer/Renderer.js index d81c904..a6dfe14 100644 --- a/lib/renderer/Renderer.js +++ b/lib/renderer/Renderer.js @@ -1,39 +1,82 @@ +import template from 'art-template' +import chokidar from 'chokidar' +import path from 'node:path' import fs from 'node:fs' -import yaml from 'yaml' -import lodash from 'lodash' -import cfg from '../config/config.js' -import { Data } from '#miao' -let rendererBackends = {} +export default class Renderer { + /** + * 渲染器 + * @param data.id 渲染器ID + * @param data.type 渲染器类型 + * @param data.render 渲染器入口 + */ + constructor(data) { + /** 渲染器ID */ + this.id = data.id || 'renderer' + /** 渲染器类型 */ + this.type = data.type || 'image' + /** 渲染器入口 */ + this.render = this[data.render || 'render'] + this.dir = './temp/html' + this.html = {} + this.watcher = {} + this.createDir(this.dir) + } -async function registerRendererBackends () { - const subFolders = fs.readdirSync(`${process.cwd()}/renderers`, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()) - for (let subFolder of subFolders) { - let name = subFolder.name - const rendererFn = await Data.importDefault(`/renderers/${name}/index.js`) - let configFile = `./renderers/${name}/config.yaml` - let rendererCfg = {} - if (fs.existsSync(configFile)) { - try { - rendererCfg = yaml.parse(fs.readFileSync(configFile, 'utf8')) - } catch (e) { - rendererCfg = {} + /** 创建文件夹 */ + createDir(dirname) { + if (fs.existsSync(dirname)) { + return true + } else { + if (this.createDir(path.dirname(dirname))) { + fs.mkdirSync(dirname) + return true } } - let renderer = rendererFn(rendererCfg) - if (!renderer.id || !renderer.type || !renderer.render || !lodash.isFunction(renderer.render)) { - logger.warn('渲染后端 ' + (renderer.id || subFolder.name) + ' 不可用') + } + + /** 模板 */ + dealTpl(name, data) { + let { tplFile, saveId = name } = data + let savePath = `./temp/html/${name}/${saveId}.html` + + /** 读取html模板 */ + if (!this.html[tplFile]) { + this.createDir(`./temp/html/${name}`) + + try { + this.html[tplFile] = fs.readFileSync(tplFile, 'utf8') + } catch (error) { + logger.error(`加载html错误:${tplFile}`) + return false + } + + this.watch(tplFile) } - rendererBackends[renderer.id] = renderer - logger.info(`加载渲染后端 ${renderer.id}`) - } -} -await registerRendererBackends() + data.resPath = `./resources/` -export default { - getRenderer () { - // TODO 渲染器降级 - return rendererBackends[cfg.renderer?.name || 'puppeteer'] + /** 替换模板 */ + let tmpHtml = template.render(this.html[tplFile], data) + + /** 保存模板 */ + fs.writeFileSync(savePath, tmpHtml) + + logger.debug(`[图片生成][使用模板] ${savePath}`) + + return savePath } -} + + /** 监听配置文件 */ + watch(tplFile) { + if (this.watcher[tplFile]) return + + const watcher = chokidar.watch(tplFile) + watcher.on('change', path => { + delete this.html[tplFile] + logger.mark(`[修改html模板] ${tplFile}`) + }) + + this.watcher[tplFile] = watcher + } +} \ No newline at end of file diff --git a/lib/renderer/loader.js b/lib/renderer/loader.js new file mode 100644 index 0000000..f750611 --- /dev/null +++ b/lib/renderer/loader.js @@ -0,0 +1,56 @@ +import fs from 'node:fs' +import yaml from 'yaml' +import lodash from 'lodash' +import cfg from '../config/config.js' +import { Data } from '#miao' +import Renderer from './Renderer.js' + +/** 全局变量 Renderer */ +global.Renderer = Renderer + +/** + * 加载渲染器 + */ +class RendererLoader { + constructor() { + this.renderers = new Map() + this.dir = './renderers' + // TODO 渲染器热加载 + this.watcher = {} + } + + static async init() { + const render = new RendererLoader() + await render.load() + return render + } + + async load() { + const subFolders = fs.readdirSync(this.dir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()) + for (let subFolder of subFolders) { + let name = subFolder.name + try { + const rendererFn = await Data.importDefault(`${this.dir}/${name}/index.js`) + let configFile = `${this.dir}/${name}/config.yaml` + let rendererCfg = fs.existsSync(configFile) ? yaml.parse(fs.readFileSync(configFile, 'utf8')) : {} + let renderer = rendererFn(rendererCfg) + if (!renderer.id || !renderer.type || !renderer.render || !lodash.isFunction(renderer.render)) { + logger.warn('渲染后端 ' + (renderer.id || subFolder.name) + ' 不可用') + } + this.renderers.set(renderer.id, renderer) + logger.info(`加载渲染后端 ${renderer.id}`) + } catch (err) { + logger.error(`渲染后端 ${name} 加载失败`) + logger.error(err) + } + } + } + + getRenderer(name = cfg.renderer?.name || 'puppeteer') { + // TODO 渲染器降级 + return this.renderers.get(name) + } +} + + +export default await RendererLoader.init() \ No newline at end of file diff --git a/renderers/puppeteer/index.js b/renderers/puppeteer/index.js index ecb4dc3..1684f12 100644 --- a/renderers/puppeteer/index.js +++ b/renderers/puppeteer/index.js @@ -10,13 +10,5 @@ import Puppeteer from './lib/puppeteer.js' */ export default function (config) { // TODO Puppeteer待简化重构 - const PuppeteerRender = new Puppeteer(config) - - return { - id: 'puppeteer', - type: 'image', - async render (name, data) { - return await PuppeteerRender.screenshot(name, data) - } - } + return new Puppeteer(config) } \ No newline at end of file diff --git a/renderers/puppeteer/lib/puppeteer.js b/renderers/puppeteer/lib/puppeteer.js index 9990851..f2c5d25 100644 --- a/renderers/puppeteer/lib/puppeteer.js +++ b/renderers/puppeteer/lib/puppeteer.js @@ -1,20 +1,22 @@ -import fs from 'node:fs' +import Renderer from '../../../lib/renderer/Renderer.js' import os from 'node:os' import lodash from 'lodash' -import template from 'art-template' -import chokidar from 'chokidar' import puppeteer from 'puppeteer' // 暂时保留对原config的兼容 import cfg from '../../../lib/config/config.js' import { Data } from '#miao' - -const _path = process.cwd() +import path from 'path' // mac地址 let mac = '' -export default class PuppeteerRenderer { +export default class Puppeteer extends Renderer { constructor(config) { + super({ + id: 'puppeteer', + type: 'image', + render: 'screenshot' + }) this.browser = false this.lock = false this.shoting = [] @@ -39,22 +41,6 @@ export default class PuppeteerRenderer { /** chromium其他路径 */ this.config.wsEndpoint = config.puppeteerWS || cfg?.bot?.puppeteer_ws } - - this.html = {} - this.watcher = {} - this.createDir('./temp/html') - } - - createDir(dir) { - if (!fs.existsSync(dir)) { - let dirs = dir.split('/') - for (let idx = 1; idx <= dirs.length; idx++) { - let temp = dirs.slice(0, idx).join('/') - if (!fs.existsSync(temp)) { - fs.mkdirSync(temp) - } - } - } } /** @@ -190,7 +176,7 @@ export default class PuppeteerRenderer { try { const page = await this.browser.newPage() let pageGotoParams = lodash.extend({ timeout: 120000 }, data.pageGotoParams || {}) - await page.goto(`file://${_path}${lodash.trim(savePath, '.')}`, pageGotoParams) + await page.goto('file://' + path.resolve(savePath), pageGotoParams) let body = await page.$('#container') || await page.$('body') // 计算页面高度 @@ -282,51 +268,6 @@ export default class PuppeteerRenderer { return data.multiPage ? ret : ret[0] } - /** 模板 */ - dealTpl(name, data) { - let { tplFile, saveId = name } = data - let savePath = `./temp/html/${name}/${saveId}.html` - - /** 读取html模板 */ - if (!this.html[tplFile]) { - this.createDir(`./temp/html/${name}`) - - try { - this.html[tplFile] = fs.readFileSync(tplFile, 'utf8') - } catch (error) { - logger.error(`加载html错误:${tplFile}`) - return false - } - - this.watch(tplFile) - } - - data.resPath = `${_path}/resources/` - - /** 替换模板 */ - let tmpHtml = template.render(this.html[tplFile], data) - - /** 保存模板 */ - fs.writeFileSync(savePath, tmpHtml) - - logger.debug(`[图片生成][使用模板] ${savePath}`) - - return savePath - } - - /** 监听配置文件 */ - watch(tplFile) { - if (this.watcher[tplFile]) return - - const watcher = chokidar.watch(tplFile) - watcher.on('change', path => { - delete this.html[tplFile] - logger.mark(`[修改html模板] ${tplFile}`) - }) - - this.watcher[tplFile] = watcher - } - /** 重启 */ restart() { /** 截图超过重启数时,自动关闭重启浏览器,避免生成速度越来越慢 */