UNPKG

lemon-bot

Version:
364 lines 18.6 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("@xhmm/utils"); const debugMod = require("debug"); const Command_1 = require("./Command"); const CQHelper_1 = require("./CQHelper"); const logger_1 = require("./logger"); class RobotFactory { static create({ port, robot, httpPlugin, commands, session = null, secret = '', context, }) { const debug = debugMod(`lemon-bot[QQ:${robot}]`); const allDirectives = []; for (const command of commands) { Command_1.Command.validate(command); allDirectives.push(...command.directives); } if (utils_1.hasRepeat(allDirectives)) throw new Error('所有的Command对象间的指令不能重复'); if (Object.keys(RobotFactory.commandsMap).includes(robot + '')) throw new Error(`机器人${robot}已存在,不可重复创建`); if (session) debug(` - session函数处理已启用`); else debug(` - session函数处理未开启`); for (const [index, command] of Object.entries(commands)) { debug(` - [命令] 指令集:${command.directives.join(',')} 解析函数:${command.parse ? '有' : '无'} 作用域:${command.scope} ${command.scope === Command_1.Scope.user ? '' : `是否艾特:${command.triggerType ? command.triggerType : Command_1.TriggerType.at}`}`); command.context = context || null; command.httpPlugin = httpPlugin; } RobotFactory.commandsMap[robot + ''] = { commands: commands, port, session, qq: robot, secret, httpPlugin, }; if (!Object.keys(RobotFactory.appsMap).includes(port + '')) { const app = createServer(RobotFactory.commandsMap, port); RobotFactory.appsMap[port + ''] = [app, 'idle']; } commands.forEach((cmd, idx) => { commands[idx] = new Proxy(cmd, { set(target, key, value) { if (Command_1.Command.blackList.includes(key)) { logger_1.warn(`无法变更Command继承类的实例对象的"${key}"属性`); return false; } logger_1.warn(`检测到命令类${target.__proto__.constructor.name}的实例对象添加了"${key}"属性。强烈不建议给命令类添加任何的自定义属性,因为不同请求会共享同一实例,由于异步原因可能会造成数据不一致。`); target[key] = value; return true; }, deleteProperty(target, key) { if (Command_1.Command.blackList.includes(key)) { logger_1.warn(`无法删除Command继承类的实例对象的"${key}"属性`); return false; } delete target[key]; return true; }, defineProperty(target, property, descriptor) { logger_1.warn(`对Command继承类的实例对象使用defineProperty已被阻止,请使用dot语法赋值`); return false; }, }); }); function start() { return new Promise((resolve, reject) => { const [app, status] = RobotFactory.appsMap[port]; if (status === 'idle') { RobotFactory.appsMap[port][1] = 'listening'; app .listen(port, () => { debug(` - ${port} 端口开始监听运行在 ${httpPlugin.endpoint} 的HTTP插件的事件上报`); resolve(); }) .on('error', err => { logger_1.error(err); reject(err); }); } else { debug(` - ${port} 端口开始监听运行在 ${httpPlugin.endpoint} 的HTTP插件的事件上报`); resolve(); } }); } function stop() { delete RobotFactory.commandsMap[robot + '']; } return { start, stop, }; } } exports.RobotFactory = RobotFactory; RobotFactory.commandsMap = {}; RobotFactory.appsMap = {}; function createServer(commandsMap, port) { const express = require('express'); const crypto = require('crypto'); const debug = debugMod(`lemon-bot[Port:${port}]`); const app = express(); app.use(express.json()); app.post('/coolq', (req, res) => __awaiter(this, void 0, void 0, function* () { const robot = +req.header('X-Self-ID'); if (!robot) { debug('[请求终止] 该请求无机器人头信息(X-Self-ID),不做处理'); res.end(); return; } if (!(robot in commandsMap)) { debug(`[请求终止] 请求机器人${robot}不在已注册的的机器人列表,请检查create的robot参数和酷Q登录的机器人是否一致`); res.end(); return; } const serverPort = commandsMap[robot].port; const secret = commandsMap[robot].secret; const session = commandsMap[robot].session; const httpPlugin = commandsMap[robot].httpPlugin; if (serverPort !== port) { throw new Error(`端口号配置错误,请检查机器人${robot}的HTTP插件配置文件的post_url端口号为${serverPort}`); } if (secret) { let signature = req.header('X-Signature'); if (!signature) throw new Error('无X-Signature请求头,请确保HTTP插件的配置文件配置了secret选项'); signature = signature.split('=')[1]; const hmac = crypto.createHmac('sha1', secret); hmac.update(JSON.stringify(req.body)); const test = hmac.digest('hex'); if (test !== signature) { debug('[请求终止] 消息体与签名不符,结束'); res.end(); return; } } const commands = commandsMap[robot].commands; const message = req.body.message && CQHelper_1.CQMessageHelper.normalizeMessage(req.body.message); const rawMessage = req.body.raw_message && req.body.raw_message; const messageFromType = CQHelper_1.CQMessageFromTypeHelper.getMessageFromType({ message_type: req.body.message_type, sub_type: req.body.sub_type }); if (messageFromType === Command_1.MessageFromType.unknown) { debug('[请求终止] 暂不支持的消息类型,不做处理'); res.end(); return; } const isAt = CQHelper_1.CQMessageHelper.isAt(robot, message); const requestBody = req.body; const userRole = req.body.sender.role || 'member'; const userNumber = CQHelper_1.CQMessageFromTypeHelper.isQQGroupAnonymousMessage(messageFromType) ? req.body.anonymous : req.body.user_id; if (typeof userNumber === 'object' && ('flag' in userNumber)) { delete userNumber.flag; } const groupNumber = req.body.group_id; const robotNumber = robot; const requestIdentity = { messageFromType, fromGroup: groupNumber, fromUser: userNumber, robot: robotNumber, }; const noSessionError = () => { throw new Error('未设置session参数,无法使用该函数'); }; let sessionData = null; if (session) { sessionData = yield session.getSession(requestIdentity); } if (sessionData) { for (const command of commands) { const className = command.constructor.name; if (sessionData.className !== className) continue; const sessionNames = Object.getOwnPropertyNames(command.__proto__) .filter(item => { return item.startsWith('session') && typeof command.__proto__[item] === 'function'; }); if (sessionNames.includes(sessionData.sessionName)) { const storedHistoryMessage = sessionData.historyMessage; if (sessionData.sessionName in storedHistoryMessage) storedHistoryMessage[sessionData.sessionName].push(message); else storedHistoryMessage[sessionData.sessionName] = [message]; if (session) yield session.updateSession(requestIdentity, 'historyMessage', storedHistoryMessage); const setNext = session ? session.setSession.bind(session, requestIdentity, { className: sessionData.className, historyMessage: storedHistoryMessage, }) : noSessionError; const setEnd = session ? session.removeSession.bind(session, requestIdentity) : noSessionError; const sessionHandlerParams = Object.assign(Object.assign({ setNext, setEnd }, requestIdentity), { message, requestBody, rawMessage, historyMessage: sessionData.historyMessage }); const replyData = yield command[sessionData.sessionName].call(command, sessionHandlerParams); yield handleReplyData(res, replyData, { userNumber, groupNumber, httpPlugin, }); debug(`[消息处理] 使用${className}类的session${sessionData.sessionName}函数处理完毕`); return; } } res.end(); yield session.removeSession(requestIdentity); debug(`[消息处理] 未在${sessionData.className}类中找到与缓存匹配的${sessionData.sessionName}函数,当前会话已重置`); } else { for (const command of commands) { const className = command.constructor.name; const { includeGroup, excludeGroup, includeUser, excludeUser, scope, directives } = command; const parse = command.parse && command.parse.bind(command); const user = command.user && command.user.bind(command); const group = command.group && command.group.bind(command); const both = command.both && command.both.bind(command); const triggerType = command.triggerType || Command_1.TriggerType.both; const triggerScope = command.triggerScope || Command_1.TriggerScope.all; const matchGroupScope = (scope === Command_1.Scope.group || scope === Command_1.Scope.both) && CQHelper_1.CQMessageFromTypeHelper.isQQGroupMessage(messageFromType); const matchUserScope = (scope === Command_1.Scope.user || scope === Command_1.Scope.both) && CQHelper_1.CQMessageFromTypeHelper.isUserMessage(messageFromType); if (matchGroupScope || matchUserScope) { if (matchGroupScope) { if (triggerType === Command_1.TriggerType.at && !isAt) continue; if (triggerType === Command_1.TriggerType.noAt && isAt) continue; if (includeGroup && !includeGroup.includes(groupNumber)) continue; if (excludeGroup && excludeGroup.includes(groupNumber)) continue; if ((Command_1.TriggerScope[userRole] & triggerScope) === 0) continue; } if (matchUserScope) { if (includeUser && !includeUser.includes(userNumber)) continue; if (excludeUser && excludeUser.includes(userNumber)) continue; } let parsedData = null; const baseInfo = { requestBody, message, rawMessage, }; if (parse) { parsedData = yield parse(Object.assign(Object.assign({}, requestIdentity), baseInfo)); if (typeof parsedData === 'undefined') { continue; } debug(`[消息处理] 使用${className}类的parse函数处理通过`); } else { if (!directives.includes(CQHelper_1.CQRawMessageHelper.removeAt(rawMessage))) continue; debug(`[消息处理] 使用${className}类的指令集处理通过`); } let replyData; if ((matchUserScope || matchGroupScope) && both) { replyData = yield both(Object.assign(Object.assign(Object.assign({}, baseInfo), requestIdentity), { data: parsedData, setNext: session ? session.setSession.bind(session, requestIdentity, { className, historyMessage: { both: [message], }, }) : noSessionError })); debug(`[消息处理] 使用${className}类的both函数处理完毕${typeof replyData === 'undefined' ? '(无返回值)' : ''}`); } else { if (matchGroupScope && group) { replyData = yield group(Object.assign(Object.assign(Object.assign({}, baseInfo), requestIdentity), { messageFromType: requestIdentity.messageFromType, data: parsedData, isAt, setNext: session ? session.setSession.bind(session, requestIdentity, { className, historyMessage: { group: [message], }, }) : noSessionError })); debug(`[消息处理] 使用${className}类的group函数处理完毕${typeof replyData === 'undefined' ? '(无返回值)' : ''}`); } if (matchUserScope && user) { replyData = yield user(Object.assign(Object.assign(Object.assign({}, baseInfo), requestIdentity), { messageFromType: requestIdentity.messageFromType, data: parsedData, setNext: session ? session.setSession.bind(session, requestIdentity, { className, historyMessage: { user: [message], }, }) : noSessionError })); debug(`[消息处理] 使用${className}类的user函数处理完毕${typeof replyData === 'undefined' ? '(无返回值)' : ''}`); } } yield handleReplyData(res, replyData, { userNumber, groupNumber, httpPlugin, }); return; } } } res.end(); return; })); return app; } function handleReplyData(res, replyData, deps) { return __awaiter(this, void 0, void 0, function* () { const replyType = utils_1.getType(replyData); if (replyType === 'array') { for (const reply of replyData) { yield deps.httpPlugin.sendMsg({ user: deps.userNumber, group: deps.groupNumber, }, reply.toString()); } res.end(); return; } else if (replyType === 'object') { res.json({ at_sender: typeof replyData.atSender === 'boolean' ? replyData.atSender : false, reply: replyData.content || 'Hi', }); return; } else if (replyType === 'string') { res.json({ at_sender: false, reply: replyData, }); return; } else { try { const str = replyData.toString(); if (str) res.json({ at_sender: false, reply: str, }); else res.end(); return; } catch (e) { res.end(); } } }); } //# sourceMappingURL=RobotFactory.js.map