import App from "./App"; import { CommandOverrideConfig } from "./Config"; import { PermissionDeniedError, RateLimitError } from "./error/errors"; import { CommonReceivedMessage } from "./message/Message"; import { ChatIdentity } from "./message/Sender"; import { CommandInfo, CommandInputArgs, EventScope, MessageEventOptions, MessagePriority, PluginEvent } from "./PluginManager"; import { Robot } from "./robot/Robot"; import { SubscribeItem } from "./SubscribeManager"; import { Reactive } from "./utils/reactive"; export type ControllerEventInfo = { priority: number; callback: CallableFunction; eventScope: PluginEvent; } export type SessionEventInfo = { activeTime: Date; eventScope: EventScope; } export type ControllerCommandInfo = { commandInfo: CommandInfo; eventScope: PluginEvent; } export class EventManager { private app: App; /** 事件排序的debounce */ private eventSortDebounce: Record = {}; /** 全局事件列表 */ private eventList: Record = {}; /** 会话事件列表 */ private sessionEventList: Record = {}; /** 全局指令列表 */ private commandList: Record = {}; /** 指令信息 */ private commandInfoList: ControllerCommandInfo[] = []; /** 指令信息覆盖配置 */ private commandOverride: CommandOverrideConfig; constructor(app: App) { this.app = app; this.commandOverride = app.config.command_override ?? {}; } public async initialize() { } public on(event: string, eventScope: PluginEvent, callback: CallableFunction, options?: MessageEventOptions) { if (!(event in this.eventList)) { this.eventList[event] = []; } let defaultOptions: MessageEventOptions = { priority: MessagePriority.DEFAULT }; if (!options) { options = defaultOptions; } else { options = { ...defaultOptions, ...options }; } const eventInfo = { callback: callback, priority: options.priority!, eventScope }; this.eventList[event].push(eventInfo); this.sortEvent(event); } public off(event: string, eventScope: PluginEvent, callback: CallableFunction): void public off(eventScope: PluginEvent): void public off(...args: any): void { if (typeof args[0] === 'string') { let [event, controller, callback] = args; if (Array.isArray(this.eventList[event])) { this.eventList[event] = this.eventList[event].filter((eventInfo) => eventInfo.callback !== callback || eventInfo.eventScope !== controller); } } else if (typeof args[0] !== 'undefined') { let controller = args[0]; for (let event in this.eventList) { this.eventList[event] = this.eventList[event].filter((eventInfo) => eventInfo.eventScope !== controller); } } } public addCommand(commandInfo: CommandInfo, eventScope: PluginEvent) { // 如果配置了Command覆盖,则覆盖原本的指令设置 if (commandInfo.command in this.commandOverride) { commandInfo = { ...commandInfo, ...this.commandOverride[commandInfo.command] }; } let data = { commandInfo, eventScope: eventScope }; this.commandInfoList.push(data); this.commandList[commandInfo.command.toLocaleLowerCase()] = data; if (Array.isArray(commandInfo.alias)) { commandInfo.alias.forEach((alias) => { this.commandList[alias.toLocaleLowerCase()] = data; }); } } public removeCommand(commandInfo: CommandInfo): void public removeCommand(eventScope: PluginEvent): void public removeCommand(...args: any): void { if ('command' in args[0]) { let commandInfo: CommandInfo = args[0]; this.commandInfoList = this.commandInfoList.filter((commandInfoItem) => commandInfoItem.commandInfo !== commandInfo); delete this.commandList[commandInfo.command.toLocaleLowerCase()]; if (Array.isArray(commandInfo.alias)) { commandInfo.alias.forEach((alias) => { delete this.commandList[alias.toLocaleLowerCase()]; }); } } else if (typeof args[0] !== 'undefined' && args[0].pluginId) { let eventScope: PluginEvent = args[0]; this.commandInfoList = this.commandInfoList.filter((commandInfoItem) => commandInfoItem.eventScope !== eventScope); for (let command in this.commandList) { if (this.commandList[command].eventScope.pluginId === eventScope.pluginId) { delete this.commandList[command]; } } } } public async emit(eventName: string, senderInfo?: ChatIdentity | null, ...args: any[]) { if (this.app.debug) { if (typeof args[0] === 'object' && args[0].chatType) { this.app.logger.debug(`触发事件 ${eventName} ${args[0].contentText}`); } else { this.app.logger.debug(`触发事件 ${eventName}`); } } const eventList = this.eventList[eventName]; if (!eventList) return false; const isFilter = eventName.startsWith('filter/'); let isResolved = false; const resolved = () => { isResolved = true; }; const buildOnError = (eventInfo: ControllerEventInfo) => (error: Error) => { this.app.logger.error(`[${eventInfo.eventScope.pluginId}/${eventInfo.eventScope.scopeName}] 处理事件 ${eventName} 时出错`, error); console.error(error); for (let arg of args) { if (typeof arg === 'object' && arg.chatType) { const msg: CommonReceivedMessage = arg; if (error instanceof RateLimitError) { const retryAfterMinutes = Math.ceil(error.retryAfter / 60); msg.sendReply(`使用太多了,${retryAfterMinutes}分钟后再试吧`); } else if (error instanceof PermissionDeniedError) { msg.sendReply(`使用此功能需要${error.requiredPermission}权限`); } break; } } } let [subscribedPlugins, disabledPlugins] = this.getPluginSubscribe(senderInfo); for (let eventInfo of eventList) { if (!isFilter && senderInfo) { switch (senderInfo.type) { case 'private': if (!eventInfo.eventScope.allowPrivate) { continue; } if (!eventInfo.eventScope.isAllowSubscribe(senderInfo)) { continue; } break; case 'group': if (!eventInfo.eventScope.allowGroup) { continue; } break; case 'channel': if (!eventInfo.eventScope.allowChannel) { continue; } break; } if (senderInfo.type !== 'private') { // 私聊消息不存在订阅,只判断群消息和频道消息 const eventScope = eventInfo.eventScope; if (eventScope.autoSubscribe) { if (!eventScope.isAllowSubscribe(senderInfo)) { continue; } else { // 检测控制器是否已禁用 if (this.isPluginScopeInList(eventScope.pluginId, eventScope.scopeName, disabledPlugins)) { continue; } } } else { // 检测控制器是否已启用 if (!this.isPluginScopeInList(eventScope.pluginId, eventScope.scopeName, subscribedPlugins)) { continue; } } } } try { const ret = await eventInfo.callback(...args, resolved); if (isResolved) { break; } // detect ret is promise if (ret && typeof ret.catch === 'function') { ret.catch(buildOnError(eventInfo)); } } catch(err: any) { buildOnError(eventInfo)(err); } } return isResolved; } public async emitMessage(message: Reactive) { let isResolved = false; if (message.chatType === 'private' || (message.chatType === 'group' && message.mentionedReceiver)) { if (this.app.config.focused_as_command) { isResolved = await this.emitCommand(message.contentText, message); if (isResolved) return true; } isResolved = await this.emit(`message/focused`, this.getSenderInfo(message), message); if (isResolved) return true; } isResolved = await this.emit(`message/${message.chatType}`, this.getSenderInfo(message), message); if (isResolved) return true; isResolved = await this.emit('message', this.getSenderInfo(message), message); if (isResolved) return true; return false; } public async emitCommand(contentText: string, message: Reactive) { let command = ''; let param = ''; // 尝试识别空格分隔的指令 if (contentText.includes(' ')) { command = contentText.split(' ')[0].toLocaleLowerCase(); param = contentText.substring(command.length + 1); if (!(command in this.commandList)) { command = ''; } } // 尝试使用最长匹配查找指令 if (command.length === 0) { for (let registeredCommand in this.commandList) { if (contentText.startsWith(registeredCommand)) { if (registeredCommand.length > command.length) { command = registeredCommand; } } } if (command.length === 0) { return false; } param = contentText.substring(command.length); } if (this.app.debug) { this.app.logger.debug('指令识别结果', command, param); } let commandArgs: CommandInputArgs = { command, param }; return await this.emit(`command/${command}`, this.getSenderInfo(message), commandArgs, message); } public async emitRawEvent(robot: Robot, event: string, ...args: any[]) { return await this.emit(`raw/${robot.type}/${event}`, { type: 'raw', robot: robot }, event); } public async emitRawMessage(message: Reactive) { let isResolved = false; await this.emit(`filter/message`, null, message); isResolved = await this.emit(`raw/${message.receiver.type}/message`, this.getSenderInfo(message), message); if (isResolved) return true; return await this.emit('raw/message', this.getSenderInfo(message), message); } public async emitFilterSendMessage(message: Reactive) { } public getSenderInfo(message: CommonReceivedMessage): ChatIdentity { if (message.chatType === 'private') { return { type: 'private', robot: message.receiver, userId: message.sender.userId }; } else if (message.chatType === 'group') { return { type: 'group', robot: message.receiver, groupId: message.sender.groupId, userId: message.sender.userId }; } return { type: 'unknown', robot: message.receiver } } public getPluginSubscribe(senderInfo?: ChatIdentity | null): [SubscribeItem[], SubscribeItem[]] { let subscribedCommands: SubscribeItem[] = []; let disabledCommands: SubscribeItem[] = []; if (senderInfo) { let targetType = ''; let targetId = ''; switch (senderInfo.type) { case 'private': targetType = 'user'; targetId = senderInfo.userId!; break; case 'group': targetType = 'group'; targetId = senderInfo.groupId!; break; case 'channel': targetType = 'channel'; targetId = senderInfo.channelId!; break; } subscribedCommands = this.app.subscribe.getSubscribedList(senderInfo.robot.robotId!, targetType, targetId, 'controller'); disabledCommands = this.app.subscribe.getSubscribedList(senderInfo.robot.robotId!, targetType, targetId, 'disable_controller'); } return [ subscribedCommands, disabledCommands ]; } public isPluginScopeInList(pluginId: string, scopeName: string, scopeList: SubscribeItem[]): boolean { return scopeList.some((scope) => scope[0] === pluginId && (scope[1] === "*" || scope[1] === scopeName) ); } private sortEvent(eventName: string) { if (this.eventSortDebounce[eventName]) { return; } this.eventSortDebounce[eventName] = setTimeout(() => { this.eventList[eventName] = this.eventList[eventName].sort((a, b) => b.priority - a.priority); delete this.eventSortDebounce[eventName]; }, 200); } }