From 168e26217ea7f72a2b25f5c1272dc83ae34ce0ab Mon Sep 17 00:00:00 2001 From: "Lain." Date: Thu, 16 May 2024 08:27:57 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=B0=81=E8=A3=85=E4=B8=BB=E5=8A=A8?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E6=8E=A5=E5=8F=A3=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E8=BD=AC=E6=8D=A2=E7=BB=9D=E5=AF=B9=E8=B7=AF=E5=BE=84=E5=8F=82?= =?UTF-8?q?=E6=95=B0=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/Renderer/Renderer.js | 37 +++++++++++-- lib/bot/bot.js | 74 ++++++++++++++++++++++++- lib/common/common.js | 114 +++++++++++++++++++++++++++++++++++---- lib/event/event.js | 100 +--------------------------------- lib/utils/handler.js | 2 +- 5 files changed, 213 insertions(+), 114 deletions(-) diff --git a/lib/Renderer/Renderer.js b/lib/Renderer/Renderer.js index f56aea0..77a18cc 100644 --- a/lib/Renderer/Renderer.js +++ b/lib/Renderer/Renderer.js @@ -67,14 +67,45 @@ class Renderer { * @param {{ * id: string, * name: string, - * data: any - * }} + * data: { + * tplFile: string, + * saveId?: string, + * imgType?: "jpeg" | "png", + * quality?: number, + * omitBackground?: boolean, + * path?: string, + * multiPage?: boolean, + * multiPageHeight?: number, + * setViewport?: { width: number, height: number, deviceScaleFactor: number }, + * pageGotoParams?: any + * } + * }} options + * @param options.data.tplFile 模板路径或http地址,必传 + * @param options.data.saveId 生成html名称,为空name代替 + * @param options.data.imgType screenshot参数,生成图片类型:jpeg,png + * @param options.data.quality screenshot参数,图片质量 0-100,jpeg是可传,默认90 + * @param options.data.omitBackground screenshot参数,隐藏默认的白色背景,背景透明。默认不透明 + * @param options.data.path screenshot参数,截图保存路径。截图图片类型将从文件扩展名推断出来。如果是相对路径,则从当前路径解析。如果没有指定路径,图片将不会保存到硬盘。 + * @param options.data.multiPage 是否分页截图,默认false + * @param options.data.multiPageHeight 分页状态下页面高度,默认4000 + * @param options.data.setViewport 页面视窗大小和设备像素比 默认1920*1080、1 + * @param options.data.pageGotoParams 页面goto时的参数 + * @return {Promise} - 分片返回数组,单页返回base64:// */ - render ({ id, name, data }) { + async render ({ id, name, data }) { const app = this.App(id) if (!app) throw new Error(`[调用渲染器失败] 未找到渲染器:${id}`) return app.render(name, data) } + + /** + * 快速渲染 + * @param {string} file html路径或者http地址 + */ + async renderHtml (file) { + const app = this.App() + return app.render('renderHtml', { tplFile: file }) + } } export default new Renderer() diff --git a/lib/bot/bot.js b/lib/bot/bot.js index 8bc07c9..62d17ef 100644 --- a/lib/bot/bot.js +++ b/lib/bot/bot.js @@ -1,10 +1,9 @@ import '../adapter/kritor/index.js' import { EventEmitter } from 'events' -import logger from '../config/log.js' import message from '../event/message.js' import notice from '../event/notice.js' import request from '../event/request.js' -import common from '../common/common.js' +import { segment, common, logger } from '#Karin' class Bot extends EventEmitter { constructor () { @@ -41,6 +40,77 @@ class Bot extends EventEmitter { logger.mark(`[适配器][WebSocket][注册][${data.url}]`) }) } + + /** + * 获取bot + * @param {string} [id] - Bot的id 未传入则返回第一个Bot + * @returns {KarinAdapter} + */ + getBot (id = '') { + if (id === '') { + /** 如果没有任何Bot 产生错误 */ + if (Object.keys(this.adapter).length === 0) { + throw new Error('找不到Bot,请先注册Bot') + } + return Object.values(this.adapter)[0] + } + return this.adapter[id] + } + + /** + * 发送主动消息 + * @param {string} [id] - Bot的id + * @param {{ + * scene: 'private' | 'group', + * peer: string, + * }} contact - 目标信息 + * @param {string} contact.scene - 场景 + * @param {string} contact.peer - 目标id + * @param {string} elements - 消息内容 + * @returns {Promise<{message_id}>} + */ + async sendMsg (id, contact, elements, options = { recallMsg: 0, button: false, retry_count: 1 }) { + /** 将msg格式化为数组 */ + if (!Array.isArray(elements)) { + elements = [elements] + } + elements = elements.map(element => { + if (typeof element == 'string') { + return segment.text(element) + } + return element + }) + let { recallMsg, button, retry_count } = options + logger.debug(button) // 后续处理 + const bot = this.getBot(id) + + /** 先发 提升速度 */ + let msgRes = bot.SendMessage(contact, elements, retry_count) + + const reply_log = common.reply_log(elements) + const self_id = bot.account.uid || bot.account.uin + if (contact.scene === 'group') { + logger.info(common.logger(self_id, `${logger.green(`Send Proactive Group ${contact.peer}: `)}${reply_log}`)) + } else { + logger.info(common.logger(self_id, `${logger.green(`Send Proactive private ${contact.peer}: `)}${reply_log}`)) + } + + try { + this.emit('karin:count:send', 1) + /** 取结果 */ + msgRes = await msgRes + logger.debug(common.logger(self_id, `回复消息结果:${JSON.stringify(msgRes, null, 2)}`)) + } catch (err) { + logger.error(common.logger(self_id, `回复消息失败:${reply_log}`)) + logger.error(common.logger(self_id), err) + } + + /** 快速撤回 */ + if (bot.RecallMessage && recallMsg > 0 && msgRes?.message_id) { + setTimeout(() => bot.RecallMessage(null, msgRes.message_id), recallMsg * 1000) + } + return msgRes + } } export default new Bot() diff --git a/lib/common/common.js b/lib/common/common.js index 9c6ff63..b612c69 100644 --- a/lib/common/common.js +++ b/lib/common/common.js @@ -1,10 +1,10 @@ -import { logger } from '#Karin' -import axios from 'axios' import fs from 'fs' import path from 'path' -import { pipeline, Readable } from 'stream' +import axios from 'axios' +import lodash from 'lodash' import { promisify } from 'util' -import segment from '../utils/segment.js' +import { logger, segment } from '#Karin' +import { pipeline, Readable } from 'stream' class Common { /** @@ -25,10 +25,7 @@ class Common { try { this.mkdir(path.dirname(savePath)) logger.debug(`[下载文件] ${fileUrl}`) - const response = await axios.get(fileUrl, { - ...param, - responseType: 'stream' // 设置响应类型为流 - }) + const response = await axios.get(fileUrl, { ...param, responseType: 'stream' }) const streamPipeline = promisify(pipeline) await streamPipeline(response.data, fs.createWriteStream(savePath)) return true @@ -73,7 +70,7 @@ class Common { */ absPath (_path) { _path = _path.replace('file://', '') - return path.resolve(_path) + return path.resolve(_path).replace(/\\/g, '/') } /** @@ -203,6 +200,105 @@ class Common { return segment.node(fakeUin, fakeNick, element) }) } + + /** 构建消息体日志 */ + reply_log (message) { + const logs = [] + + for (let val of message) { + switch (val.type) { + case 'text': + logs.push(val.text) + break + case 'face': + logs.push(`[face:${val.id}]`) + break + case 'video': + case 'image': + case 'voice': + case 'record': + case 'file': { + let file + if (Buffer.isBuffer(val.file)) { + file = 'Buffer://...' + } else if (/^http|^file/.test(val.file)) { + file = val.file + } else { + file = 'base64://...' + } + logs.push(`[${val.type}:${file}]`) + break + } + case 'at': + logs.push(`[at:${val.uid}]`) + break + case 'rps': + logs.push(`[rps:${val.id}]`) + break + case 'dice': + logs.push(`[dice:${val.id}]`) + break + case 'shake': + logs.push('[shake:窗口抖动]') + break + case 'poke': + logs.push(`[poke:${val.id}]`) + break + case 'anonymous': + logs.push(`[anonymous:${val.ignore}]`) + break + case 'share': + logs.push(`[share:${JSON.stringify(val)}]`) + break + case 'contact': + logs.push(`[contact:${JSON.stringify(val)}]`) + break + case 'location': + logs.push(`[location:${JSON.stringify(val)}]`) + break + case 'music': + logs.push(`[music:${JSON.stringify(val)}]`) + break + case 'reply': + logs.push(`[reply:${val.message_id}]`) + break + case 'forward': + logs.push(`[forward:${val.id}]`) + break + case 'node': + logs.push(`[node:${JSON.stringify(val)}]`) + break + case 'xml': + logs.push(`[xml:${val.data}]`) + break + case 'json': + logs.push(`[json:${val.data}]`) + break + case 'markdown': { + if (val.content) { + logs.push(`[markdown:${val.content}]`) + } else { + let content = { id: val.custom_template_id } + for (let v of val.params) content[v.key] = v.values[0] + logs.push(`[markdown:${JSON.stringify(content)}]`) + } + break + } + case 'rows': { + const rows = [] + for (let v of val.rows) rows.push(v.log) + logs.push(`[rows:${JSON.stringify(rows)}]`) + break + } + case 'button': + logs.push(`[button:${val.log}]`) + break + default: + logs.push(`[未知:${JSON.stringify(val)}]`) + } + } + return lodash.truncate(logs.join(''), { length: 500 }) + } } export default new Common() diff --git a/lib/event/event.js b/lib/event/event.js index 6fe7b33..f6dbe90 100644 --- a/lib/event/event.js +++ b/lib/event/event.js @@ -122,7 +122,7 @@ export default class Event { /** 先发 提升速度 */ let msgRes = e.replyCallback(elements, retry_count) - const reply_log = this.reply_log(elements) + const reply_log = common.reply_log(elements) if (e.isGroup) { logger.info(common.logger(e.self_id, `${logger.green(`Send Group ${e.group_id}: `)}${reply_log}`)) @@ -149,102 +149,4 @@ export default class Event { } Object.freeze(e.reply) } - - reply_log (message) { - const logs = [] - - for (let val of message) { - switch (val.type) { - case 'text': - logs.push(val.text) - break - case 'face': - logs.push(`[face:${val.id}]`) - break - case 'video': - case 'image': - case 'voice': - case 'record': - case 'file': { - let file - if (Buffer.isBuffer(val.file)) { - file = 'Buffer://...' - } else if (/^http|^file/.test(val.file)) { - file = val.file - } else { - file = 'base64://...' - } - logs.push(`[${val.type}:${file}]`) - break - } - case 'at': - logs.push(`[at:${val.uid}]`) - break - case 'rps': - logs.push(`[rps:${val.id}]`) - break - case 'dice': - logs.push(`[dice:${val.id}]`) - break - case 'shake': - logs.push('[shake:窗口抖动]') - break - case 'poke': - logs.push(`[poke:${val.id}]`) - break - case 'anonymous': - logs.push(`[anonymous:${val.ignore}]`) - break - case 'share': - logs.push(`[share:${JSON.stringify(val)}]`) - break - case 'contact': - logs.push(`[contact:${JSON.stringify(val)}]`) - break - case 'location': - logs.push(`[location:${JSON.stringify(val)}]`) - break - case 'music': - logs.push(`[music:${JSON.stringify(val)}]`) - break - case 'reply': - logs.push(`[reply:${val.message_id}]`) - break - case 'forward': - logs.push(`[forward:${val.id}]`) - break - case 'node': - logs.push(`[node:${JSON.stringify(val)}]`) - break - case 'xml': - logs.push(`[xml:${val.data}]`) - break - case 'json': - logs.push(`[json:${val.data}]`) - break - case 'markdown': { - if (val.content) { - logs.push(`[markdown:${val.content}]`) - } else { - let content = { id: val.custom_template_id } - for (let v of val.params) content[v.key] = v.values[0] - logs.push(`[markdown:${JSON.stringify(content)}]`) - } - break - } - case 'rows': { - const rows = [] - for (let v of val.rows) rows.push(v.log) - logs.push(`[rows:${JSON.stringify(rows)}]`) - break - } - case 'button': - logs.push(`[button:${val.log}]`) - break - default: - logs.push(`[未知:${JSON.stringify(val)}]`) - } - } - return lodash.truncate(logs.join(''), { length: 500 }) - } } diff --git a/lib/utils/handler.js b/lib/utils/handler.js index 6ca3af2..cc13c00 100644 --- a/lib/utils/handler.js +++ b/lib/utils/handler.js @@ -82,7 +82,7 @@ class EventHandler { * @param {Object} args 参数数组 * @returns {Promise} 处理结果 */ - async call (key, args) { + async call (key, args = {}) { let ret for (const v of this.events[key] || []) { const App = new v.App()