|
|
|
@ -1,253 +1,174 @@
|
|
|
|
|
import EventEmitter from "events";
|
|
|
|
|
import { debounce } from "throttle-debounce";
|
|
|
|
|
import App from "./App";
|
|
|
|
|
import { CommonReceivedMessage } from "./message/Message";
|
|
|
|
|
import { CommonReceivedMessage, CommonSendMessage } from "./message/Message";
|
|
|
|
|
import { GroupSender, UserSender } from "./message/Sender";
|
|
|
|
|
import { ControllerSubscribeSource, MessageEventOptions, MessagePriority, PluginController } from "./PluginManager";
|
|
|
|
|
import { Robot } from "./RobotManager";
|
|
|
|
|
|
|
|
|
|
export type PluginControllerListenerInfo = {
|
|
|
|
|
priority: number;
|
|
|
|
|
callback: CallableFunction;
|
|
|
|
|
controller: PluginController;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export class EventManager {
|
|
|
|
|
private app: App;
|
|
|
|
|
private eventEmitter: EventEmitter;
|
|
|
|
|
private eventGroup: Record<string, EventGroup> = {};
|
|
|
|
|
private eventHandlerList: Record<string, EventGroup[]> = {};
|
|
|
|
|
private eventSortDebounce: Record<string, NodeJS.Timeout> = {};
|
|
|
|
|
private eventList: Record<string, PluginControllerListenerInfo[]> = {};
|
|
|
|
|
|
|
|
|
|
constructor(app: App) {
|
|
|
|
|
this.app = app;
|
|
|
|
|
this.eventEmitter = new EventEmitter;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async initialize() {
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public getEventGroup() {
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
on(event: string, controller: PluginController, callback: CallableFunction, options?: MessageEventOptions) {
|
|
|
|
|
if (!(event in this.eventList)) {
|
|
|
|
|
this.eventList[event] = [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const MessagePriority = {
|
|
|
|
|
LOWEST: 0,
|
|
|
|
|
LOW: 20,
|
|
|
|
|
DEFAULT: 40,
|
|
|
|
|
/**
|
|
|
|
|
* 在控制器中添加的临时会话处理器。
|
|
|
|
|
* 用于处理深层会话,会比一般指令优先级高。
|
|
|
|
|
*/
|
|
|
|
|
TEMP_HANDLER: 60,
|
|
|
|
|
HIGH: 80,
|
|
|
|
|
/**
|
|
|
|
|
* 一些系统自带的事件处理,如:记录消息
|
|
|
|
|
*/
|
|
|
|
|
SYSTEM: 90,
|
|
|
|
|
/**
|
|
|
|
|
* 最高优先级,可以覆盖系统原本的处理
|
|
|
|
|
*/
|
|
|
|
|
HIGHEST: 100
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export type MessageEventOptions = {
|
|
|
|
|
priority?: number,
|
|
|
|
|
/** Base sources: private, group, channel */
|
|
|
|
|
source?: string[],
|
|
|
|
|
robotApi?: string[],
|
|
|
|
|
let defaultOptions: MessageEventOptions = {
|
|
|
|
|
priority: MessagePriority.DEFAULT
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export type CommandInfo = {
|
|
|
|
|
command: string,
|
|
|
|
|
name: string,
|
|
|
|
|
alias?: string[],
|
|
|
|
|
help?: string,
|
|
|
|
|
if (!options) {
|
|
|
|
|
options = defaultOptions;
|
|
|
|
|
} else {
|
|
|
|
|
options = {
|
|
|
|
|
...defaultOptions,
|
|
|
|
|
...options
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export type EventListenerInfo = {
|
|
|
|
|
priority: number;
|
|
|
|
|
callback: CallableFunction;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export type MessageCallback = (message: CommonReceivedMessage, resolved: VoidFunction) => any;
|
|
|
|
|
export type CommandCallback = (argv: string[], message: CommonReceivedMessage, resolved: VoidFunction) => any;
|
|
|
|
|
export type RawEventCallback = (robot: Robot, event: any, resolved: VoidFunction) => any;
|
|
|
|
|
|
|
|
|
|
export type AllowedList = string[] | '*';
|
|
|
|
|
|
|
|
|
|
export class EventGroup {
|
|
|
|
|
readonly id: string;
|
|
|
|
|
|
|
|
|
|
public allowPrivate = true;
|
|
|
|
|
public allowGroup = true;
|
|
|
|
|
|
|
|
|
|
public allowedGroupList: AllowedList = '*';
|
|
|
|
|
const eventInfo = {
|
|
|
|
|
callback: callback,
|
|
|
|
|
priority: options.priority!,
|
|
|
|
|
controller: controller
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private commandList: CommandInfo[] = [];
|
|
|
|
|
private eventList: Record<string, EventListenerInfo[]> = {};
|
|
|
|
|
this.eventList[event].push(eventInfo);
|
|
|
|
|
|
|
|
|
|
constructor(id: string) {
|
|
|
|
|
this.id = id;
|
|
|
|
|
this.sortEvent(event);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public shouldAllowSource: (sender: any) => boolean = (sender) => {
|
|
|
|
|
if (sender instanceof UserSender) {
|
|
|
|
|
if (this.allowPrivate) {
|
|
|
|
|
return true;
|
|
|
|
|
off(event: string, controller: PluginController, callback: CallableFunction): void
|
|
|
|
|
off(controller: PluginController): void
|
|
|
|
|
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.controller !== controller);
|
|
|
|
|
}
|
|
|
|
|
} else if (sender instanceof GroupSender) {
|
|
|
|
|
if (this.allowedGroupList === '*') {
|
|
|
|
|
return true;
|
|
|
|
|
} else if (this.allowedGroupList.includes(sender.groupId)) {
|
|
|
|
|
return true;
|
|
|
|
|
} 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.controller !== controller);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public emit(eventName: string, ...args: any[]) {
|
|
|
|
|
public async emit(eventName: string, senderInfo: ControllerSubscribeSource, ...args: any[]) {
|
|
|
|
|
const eventList = this.eventList[eventName];
|
|
|
|
|
if (!eventList) return false;
|
|
|
|
|
|
|
|
|
|
let isResolved = false;
|
|
|
|
|
let isBreakAll = false;
|
|
|
|
|
|
|
|
|
|
const resolved = (breakAll: boolean = false) => {
|
|
|
|
|
const resolved = () => {
|
|
|
|
|
isResolved = true;
|
|
|
|
|
if (breakAll) {
|
|
|
|
|
isBreakAll = true;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for (let eventInfo of eventList) {
|
|
|
|
|
eventInfo.callback(...args, resolved);
|
|
|
|
|
if (eventInfo.controller.autoSubscribe) {
|
|
|
|
|
if (!eventInfo.controller.isAllowSubscribe(senderInfo)) {
|
|
|
|
|
continue;
|
|
|
|
|
} else {
|
|
|
|
|
// 需要添加订阅检测
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// 需要添加订阅检测
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await eventInfo.callback(...args, resolved);
|
|
|
|
|
if (isResolved) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
} catch(err: any) {
|
|
|
|
|
console.error(`事件 ${eventName} 处理失败`);
|
|
|
|
|
console.error(err);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return !isBreakAll;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
on(event: string, callback: CallableFunction, options?: MessageEventOptions) {
|
|
|
|
|
if (!(event in this.eventList)) {
|
|
|
|
|
this.eventList[event] = [];
|
|
|
|
|
return isResolved;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let defaultOptions: MessageEventOptions = {
|
|
|
|
|
priority: MessagePriority.DEFAULT
|
|
|
|
|
};
|
|
|
|
|
if (!options) {
|
|
|
|
|
options = defaultOptions;
|
|
|
|
|
} else {
|
|
|
|
|
options = {
|
|
|
|
|
...defaultOptions,
|
|
|
|
|
...options
|
|
|
|
|
};
|
|
|
|
|
public async emitMessage(message: CommonReceivedMessage) {
|
|
|
|
|
let isResolved = false;
|
|
|
|
|
|
|
|
|
|
if (message.origin === 'private' || (message.origin === 'group' && message.mentionedReceiver)) {
|
|
|
|
|
isResolved = await this.emit(`message/focused`, this.getSenderInfo(message), message);
|
|
|
|
|
if (isResolved) return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const eventInfo = {
|
|
|
|
|
callback: callback,
|
|
|
|
|
priority: options.priority!
|
|
|
|
|
};
|
|
|
|
|
const singleEventList = this.eventList[event];
|
|
|
|
|
const priority = options.priority!;
|
|
|
|
|
isResolved = await this.emit(`message/${message.origin}`, this.getSenderInfo(message), message);
|
|
|
|
|
if (isResolved) return true;
|
|
|
|
|
|
|
|
|
|
// Add event to specified position
|
|
|
|
|
if (singleEventList.length === 0) {
|
|
|
|
|
singleEventList.push(eventInfo);
|
|
|
|
|
} else {
|
|
|
|
|
for (let i = 0; i < singleEventList.length; i++) {
|
|
|
|
|
if (singleEventList[i].priority < priority) {
|
|
|
|
|
const target = i - 1;
|
|
|
|
|
if (target === 0) {
|
|
|
|
|
singleEventList.unshift(eventInfo);
|
|
|
|
|
} else {
|
|
|
|
|
this.eventList[event] = [
|
|
|
|
|
...singleEventList.slice(0, target),
|
|
|
|
|
eventInfo,
|
|
|
|
|
...singleEventList.slice(target)
|
|
|
|
|
];
|
|
|
|
|
isResolved = await this.emit('message', this.getSenderInfo(message), message);
|
|
|
|
|
if (isResolved) return true;
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async emitCommand(command: string, args: string, message: CommonReceivedMessage) {
|
|
|
|
|
return await this.emit(`command/${command}`, this.getSenderInfo(message), args, 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: CommonReceivedMessage) {
|
|
|
|
|
let isResolved = false;
|
|
|
|
|
isResolved = await this.emit(`raw/${message.receiver.type}/message`, this.getSenderInfo(message), message);
|
|
|
|
|
return await this.emit('raw/message', this.getSenderInfo(message), message);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async emitFilterSendMessage(message: CommonSendMessage) {
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
addCommand(command: string, name: string, callback: MessageCallback, options?: MessageEventOptions): void
|
|
|
|
|
addCommand(commandInfo: CommandInfo, callback: MessageCallback, options?: MessageEventOptions): void
|
|
|
|
|
addCommand(...args: any[]): void {
|
|
|
|
|
// 处理传入参数
|
|
|
|
|
let commandInfo: Partial<CommandInfo> = {};
|
|
|
|
|
let callback: MessageCallback;
|
|
|
|
|
let options: MessageEventOptions;
|
|
|
|
|
if (typeof args[0] === 'string' && typeof args[1] === 'string') {
|
|
|
|
|
commandInfo = {
|
|
|
|
|
command: args[0],
|
|
|
|
|
name: args[1]
|
|
|
|
|
public getSenderInfo(message: CommonReceivedMessage): ControllerSubscribeSource {
|
|
|
|
|
if (message.origin === 'private') {
|
|
|
|
|
return {
|
|
|
|
|
type: 'private',
|
|
|
|
|
robot: message.receiver,
|
|
|
|
|
userId: message.sender.uid
|
|
|
|
|
};
|
|
|
|
|
callback = args[2];
|
|
|
|
|
options = args[3] ?? {};
|
|
|
|
|
} else {
|
|
|
|
|
commandInfo = args[0];
|
|
|
|
|
callback = args[1];
|
|
|
|
|
options = args[2] ?? {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 注册消息
|
|
|
|
|
this.commandList.push(commandInfo as any);
|
|
|
|
|
this.on(`command/${commandInfo.command}`, callback, options);
|
|
|
|
|
if (Array.isArray(commandInfo.alias)) { // Add event for alias
|
|
|
|
|
commandInfo.alias.forEach((cmd) => {
|
|
|
|
|
this.on(`command/${cmd}`, callback, options);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Add message handle.
|
|
|
|
|
* will be trigger on private message or group message with mentions to robot
|
|
|
|
|
* @param callback
|
|
|
|
|
* @param options
|
|
|
|
|
*/
|
|
|
|
|
onMentionedMessage(callback: MessageCallback, options?: MessageEventOptions) {
|
|
|
|
|
this.on('mentionedMessage', callback, options);
|
|
|
|
|
this.on('privateMessage', callback, options);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Add private message handle.
|
|
|
|
|
* @param callback
|
|
|
|
|
* @param options
|
|
|
|
|
*/
|
|
|
|
|
onPrivateMessage(callback: MessageCallback, options?: MessageEventOptions) {
|
|
|
|
|
this.on('privateMessage', callback, options);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Add group message handle.
|
|
|
|
|
* @param callback
|
|
|
|
|
* @param options
|
|
|
|
|
*/
|
|
|
|
|
onGroupMessage(callback: MessageCallback, options?: MessageEventOptions) {
|
|
|
|
|
this.on('groupMessage', callback, options);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Add message handle.
|
|
|
|
|
* Will handle all messages in group
|
|
|
|
|
* @param callback
|
|
|
|
|
* @param options
|
|
|
|
|
*/
|
|
|
|
|
onMessage(callback: MessageCallback, options?: MessageEventOptions) {
|
|
|
|
|
this.on('message', callback, options);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Add raw message handle.
|
|
|
|
|
* Will be triggered even when the message is a command.
|
|
|
|
|
* @param callback
|
|
|
|
|
* @param options
|
|
|
|
|
*/
|
|
|
|
|
onRawMessage(callback: MessageCallback, options?: MessageEventOptions) {
|
|
|
|
|
this.on('rawMessage', callback, options);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onRawEvent(callback: RawEventCallback, options?: MessageEventOptions) {
|
|
|
|
|
this.on('rawEvent', callback, options);
|
|
|
|
|
} else if (message.origin === 'group') {
|
|
|
|
|
return {
|
|
|
|
|
type: 'group',
|
|
|
|
|
robot: message.receiver,
|
|
|
|
|
groupId: message.sender.groupId,
|
|
|
|
|
userId: message.sender.uid
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
type: 'unknown',
|
|
|
|
|
robot: message.receiver
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|