diff --git a/lib/plugins/loader.js b/lib/plugins/loader.js index 78f073d..cad4763 100644 --- a/lib/plugins/loader.js +++ b/lib/plugins/loader.js @@ -54,26 +54,9 @@ class PluginsLoader { let packageErr = [] for (let File of files) { - let { name: appName, path: appPath } = File try { let tmp = await import(File.path) - // 如果当前是一个插件 - if (tmp.apps) { - // 插件主目录,在这里this.watch里已经做了判断所以不用捕获异常 - appName = `${appName}/apps` - // 插件路径 - appPath = appPath.replace(/\/index\.js$/, ""); - // 逐个遍历插件app里的js - for (let app in tmp.apps) { - // 插件目录的功能 - const curPlugin = tmp.apps[app] - // 当前目录的功能名 (apps/hello.js) - const curAppName = curPlugin.name - // 监听插件 - this.watch(appName, `${curAppName}.js`) - } - tmp = { ...tmp.apps } - } + if (tmp.apps) tmp = { ...tmp.apps } let isAdd = false lodash.forEach(tmp, (p, i) => { if (!p.prototype) return @@ -87,7 +70,7 @@ class PluginsLoader { this.collectTask(plugin.task) this.priority.push({ class: p, - key: `${appName}/${p.name}.js`, + key: File.name, name: plugin.name, priority: plugin.priority }) diff --git a/lib/puppeteer/puppeteer.js b/lib/puppeteer/puppeteer.js index 9ca241a..e21b510 100644 --- a/lib/puppeteer/puppeteer.js +++ b/lib/puppeteer/puppeteer.js @@ -1,28 +1,23 @@ -import Renderer from '../renderer/Renderer.js' +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 4b64b00..a6dfe14 100644 --- a/lib/renderer/Renderer.js +++ b/lib/renderer/Renderer.js @@ -1,40 +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}`) + + data.resPath = `./resources/` + + /** 替换模板 */ + let tmpHtml = template.render(this.html[tplFile], data) + + /** 保存模板 */ + fs.writeFileSync(savePath, tmpHtml) + + logger.debug(`[图片生成][使用模板] ${savePath}`) + + return savePath } -} -await registerRendererBackends() + /** 监听配置文件 */ + watch(tplFile) { + if (this.watcher[tplFile]) return -export default { - getRenderer () { - // TODO 渲染器降级 - return rendererBackends[cfg.renderer?.name || 'puppeteer'] + 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/package.json b/package.json index b90f8ce..4c07914 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "node-xlsx": "^0.23.0", "oicq": "link:lib/modules/oicq", "pm2": "^5.3.0", - "puppeteer": "^21.1.1", + "puppeteer": "^21.2.1", "redis": "^4.6.8", "sequelize": "^6.33.0", "sqlite3": "^5.1.6", 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 8101cf0..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 = [] @@ -23,7 +25,7 @@ export default class PuppeteerRenderer { /** 截图次数 */ this.renderNum = 0 this.config = { - headless: Data.def(config.headless, "new"), + headless: Data.def(config.headless, 'new'), args: Data.def(config.args, [ '--disable-gpu', '--disable-setuid-sandbox', @@ -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) - } - } - } } /** @@ -123,7 +109,7 @@ export default class PuppeteerRenderer { } else { logger.info(`[Chromium] ${this.browser.wsEndpoint()}`) if (process.env.pm_id && this.browserMacKey) { - //缓存一下实例30天 + // 缓存一下实例30天 const expireTime = 60 * 60 * 24 * 30 await redis.set(this.browserMacKey, this.browser.wsEndpoint(), { EX: expireTime }) } @@ -140,15 +126,19 @@ export default class PuppeteerRenderer { } // 获取Mac地址 - getMac() { + getMac () { const mac = '00:00:00:00:00:00' try { const network = os.networkInterfaces() - for (const a in network) - for (const i of network[a]) - if (i.mac && i.mac != mac) + for (const a in network) { + for (const i of network[a]) { + if (i.mac && i.mac != mac) { return i.mac - } catch (e) {} + } + } + } + } catch (e) { + } return mac } @@ -173,7 +163,9 @@ export default class PuppeteerRenderer { const pageHeight = data.multiPageHeight || 4000 let savePath = this.dealTpl(name, data) - if (!savePath) return false + if (!savePath) { + return false + } let buff = '' let start = Date.now() @@ -184,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') // 计算页面高度 @@ -238,7 +230,9 @@ export default class PuppeteerRenderer { } else { buff = await page.screenshot(randData) } - if (num > 2) await Data.sleep(200) + if (num > 2) { + await Data.sleep(200) + } this.renderNum++ /** 计算图片大小 */ @@ -251,7 +245,6 @@ export default class PuppeteerRenderer { } } page.close().catch((err) => logger.error(err)) - } catch (error) { logger.error(`[图片生成][${name}] 图片生成失败:${error}`) /** 关闭浏览器 */ @@ -275,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() { /** 截图超过重启数时,自动关闭重启浏览器,避免生成速度越来越慢 */ @@ -335,4 +283,4 @@ export default class PuppeteerRenderer { } } } -} \ No newline at end of file +}