mirror of
https://github.com/yml2213/javascript.git
synced 2026-10-22 09:34:00 +08:00
364 lines
18 KiB
JavaScript
364 lines
18 KiB
JavaScript
"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
|