lemon-bot
Version:
a qq bot framework
364 lines • 18.6 kB
JavaScript
;
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