UNPKG

miraipie

Version:

the most powerful nodejs development kit for mirai-api-http

459 lines (418 loc) 18.3 kB
import EventEmitter from 'events'; import { Configuration, configure, getLogger, Logger } from 'log4js'; import path from 'path'; import { MiraiApiHttpAdapter } from './adapter'; import { Chat, FriendChat, GroupChat, TempChat } from './chat'; import { ApplicationConfig, checkUserConfig } from './config'; import { MessageChain } from './message'; import { ChatMessage, ChatMessageMap, ChatMessageType, Event, EventMap, EventType, Friend, GroupMember, SyncMessage, SyncMessageMap, SyncMessageType, } from './mirai'; import { Pie } from './pie'; import { makeReadonly } from './utils'; type MessageReceivedListener<T extends ChatMessage> = (chatMessage: T) => any; type SyncMessageReceivedListener<T extends SyncMessage> = (syncMessage: T) => any; type EventReceivedListener<T extends Event> = (event: T) => any; type LifecycleHookListener = () => any; /** MiraiPie 应用程序 */ export class MiraiPieApplication extends EventEmitter { /** 应用程序单例 */ static instance: MiraiPieApplication = null; /** logger */ logger: Logger = getLogger('miraipie'); /** 机器人服务的QQ号 */ readonly qq: number; /** 用于与 mirai-api-http 对接的客户端 adapter */ api: MiraiApiHttpAdapter; /** 可用的 mirai-api-http 客户端 adapter 映射 */ private __apiAdapterMap: Map<string, MiraiApiHttpAdapter>; /** 已安装的 pie 映射 */ private __piesMap: Map<string, Pie>; /** pie 启用状态映射 */ private __piesEnabledMap: Map<string, boolean>; private constructor(private config: ApplicationConfig, loggerOptions?: Configuration) { super(); MiraiPieApplication.instance = this; // 注册全局模块 process.env.MIRAIPIE_MODULE = module.id; this.qq = config.qq; // 初始化log4js configure(loggerOptions || { appenders: { console: { type: 'console' }, file: { type: 'dateFile', filename: config.logDirectory + '/miraipie.log' } }, categories: { default: { appenders: config.logDirectory ? ['console', 'file'] : ['console'], level: config.logLevel || 'debug' } } }); this.setMaxListeners(0); this.__apiAdapterMap = new Map(); this.__piesMap = new Map(); this.__piesEnabledMap = new Map(); // 加载内置 adapter const BuiltinApiAdapters = [ require('./builtin/HttpApiAdapter'), require('./builtin/WebsocketApiAdapter'), require('./builtin/MixedApiAdapter') ]; for (const adapter of BuiltinApiAdapters) { adapter.configs.qq = this.qq; this.adapter(adapter); } // 加载配置文件中的各项拓展 const modules = new Set([ ...this.config.extensions, ...Object.keys(this.config.adapters).map((id) => this.config.adapters[id].module), ...Object.keys(this.config.pies).map((id) => this.config.pies[id].module) ]); for (const module of modules) { if (module) { try { // 执行拓展文件 require(path.join(process.cwd(), module))(this); } catch (err) { this.logger.error(`加载拓展模块 '${module}' 时出错:`, err); } } } if (this.config.verbose) { this.on('message', (chatMessage) => { const displayString = (chatMessage.messageChain as MessageChain).toDisplayString() if (chatMessage.type === 'FriendMessage') { const sender = chatMessage.sender as Friend; this.logger.info(`${sender.nickname}(${sender.id}) -> ${displayString}`); } else if (chatMessage.type === 'GroupMessage') { const sender = chatMessage.sender as GroupMember; this.logger.info(`[${sender.group.name}(${sender.group.id})] ${sender.memberName}(${sender.id}) -> ${displayString}`); } else if (chatMessage.type === 'TempMessage') { const sender = chatMessage.sender as GroupMember; this.logger.info(`${sender.memberName}(${sender.id}) -> ${displayString}`); } }); this.on('event', (event) => { this.logger.info(`${event.type}: ${JSON.stringify(event)}`); }); } // 选用默认 adapter this.useAdapter(config.adapterInUse); // 捕获未处理的异常 process.on('uncaughtException', (error) => { this.logger.debug('捕获到未处理的异常:', error); }); process.on('unhandledRejection', (reason) => { this.logger.debug('捕获到未处理的rejection:', reason); }); // 捕捉Ctrl-C process.on('SIGINT', () => { this.logger.info('已手动停止运行 (Ctrl-C)'); process.exit(); }); } /** * 创建 MiraiPie 应用程序实例 * @param appConfigs 应用程序配置 * @param loggerOptions log4js实例配置 */ static createApp(appConfigs: ApplicationConfig, loggerOptions?: Configuration): MiraiPieApplication { return new MiraiPieApplication(appConfigs, loggerOptions); } /** * 安装拓展 * @param extension 拓展对象, 可以为 adapter 和 pie */ install<Instance extends Pie | MiraiApiHttpAdapter>(extension: Instance): this { if ((extension as Pie).__isPie) this.pie(extension as Pie); else if ((extension as MiraiApiHttpAdapter).__isApiAdapter) this.adapter(extension as MiraiApiHttpAdapter); else this.logger.error(`未知的拓展类型: ${extension}`); return this; } /** * 安装 adapter * @param adapter adapter 对象 */ adapter<MiraiApiHttpAdapterInstance extends MiraiApiHttpAdapter>(adapter: MiraiApiHttpAdapterInstance): this { if (!adapter.__isApiAdapter) { this.logger.error(`安装 adapter 失败, ${adapter} 不是有效的 adapter`); return this; } // 安装 adapter this.__apiAdapterMap.set(adapter.id, adapter); // 注册时事件 adapter.on('message', async (chatMessage) => { chatMessage.messageChain = MessageChain.from(chatMessage.messageChain); this.emit('message', chatMessage); this.emit(chatMessage.type, chatMessage); await this.__messageDispatcher(chatMessage); }); adapter.on('sync', async (syncMessage) => { syncMessage.messageChain = MessageChain.from(syncMessage.messageChain); this.emit('sync', syncMessage); this.emit(syncMessage.type, syncMessage); }); adapter.on('event', (event) => { this.emit('event', event); this.emit(event.type, event); }); adapter.on('listen', () => { this.emit('listen'); }); adapter.on('stop', () => { this.emit('stop'); }); adapter.once('listen', async () => { const resp = await adapter.getAbout(); if (resp.data.version !== adapter.supportVersion) { this.logger.warn(`当前使用的adapter: '${adapter.id}' 支持 mirai-api-http V${adapter.supportVersion}, 而服务端运行的版本为 V${resp.data.version}, 使用中可能产生兼容性问题`); } }); // 配置 adapter if (adapter.id in this.config.adapters) { adapter.configs = Object.assign(adapter.configs, this.config.adapters[adapter.id].configs); checkUserConfig(adapter.configMeta, adapter.configs, adapter.logger); } this.logger.info(`已安装 adapter '${adapter.id}'`); adapter.emit('installed'); return this; } /** * 切换服务 adapter * @param id adapter id */ useAdapter(id: string): this { if (this.__apiAdapterMap.has(id)) { if (this.api) { this.api.emit('unused'); this.api.stop(); } this.api = this.__apiAdapterMap.get(id); this.__apiAdapterMap.get(id).emit('used'); } else { this.logger.error(`使用 adapter 失败, 指定的 adapter '${id}' 未安装`); } return this; } /** * 卸载 adapter * @param id adapter id */ uninstallAdapter(id: string): MiraiApiHttpAdapter { if (this.api && this.api.id === id) { this.logger.error(`卸载 adapter '${id}' 失败, 当前正在使用`); } if (this.__apiAdapterMap.has(id)) { const adapter = this.__apiAdapterMap.get(id); this.__apiAdapterMap.delete(id); this.logger.info(`已卸载 adapter '${id}'`); return adapter; } else { this.logger.error(`卸载 adapter 失败, 指定的 adapter '${id}' 未安装`); } } /** * 安装 pie * @param pie pie 对象 */ pie<PieInstance extends Pie>(pie: PieInstance): this { if (!pie.__isPie) { this.logger.error(`安装 pie 失败, ${pie} 不是有效的 pie`); return this; } // 安装 pie this.__piesMap.set(pie.id, pie); // 配置 pie if (pie.id in this.config.pies) { const pieConfig = this.config.pies[pie.id]; pie.configs = Object.assign(pie.configs, pieConfig.configs); checkUserConfig(pie.configMeta, pie.configs, pie.logger); // 是否启用 this.__piesEnabledMap.set(pie.id, pieConfig.enabled); } else { // 自动启用 this.__piesEnabledMap.set(pie.id, true); } this.logger.info(`已安装 pie '${pie.id}'`); pie.emit('installed'); if (this.__piesEnabledMap.get(pie.id)) this.enable(pie.id); return this; } /** * 启用 pie * @param id pie id */ enable(id: string): this { if (this.__piesMap.has(id)) { this.__piesEnabledMap.set(id, true); this.__piesMap.get(id).emit('enabled'); this.logger.info(`已启用 pie '${id}'`); } return this; } /** * 禁用 pie * @param id pie id */ disable(id: string): this { if (this.__piesMap.has(id)) { this.__piesEnabledMap.set(id, false); this.__piesMap.get(id).emit('disabled'); this.logger.info(`已禁用 pie '${id}'`); } return this; } /** * 卸载 pie * @param id pie id */ uninstallPie(id: string): Pie { if (this.__piesMap.has(id)) { const pie = this.__piesMap.get(id); this.disable(id); this.__piesEnabledMap.delete(id); this.__piesMap.delete(id); pie.emit('uninstalled'); this.logger.info(`已卸载 pie '${id}'`); return pie; } else { this.logger.error(`卸载 pie 失败, 指定的 pie '${id}' 未安装`); } } /** 启动 adapter 的事件监听 */ async listen() { if (this.api) { if (!this.api.listening) await this.api.listen(); } else { this.logger.error('未指定使用的 adapter, 无法启动监听'); } } /** 停止 adapter 的事件监听 */ stop() { if (this.api && this.api.listening) this.api.stop(); } addListener(e: 'message', listener: MessageReceivedListener<ChatMessage>): this; addListener<T extends ChatMessageType>(e: T, listener: MessageReceivedListener<ChatMessageMap[T]>): this; addListener(e: 'sync', listener: SyncMessageReceivedListener<SyncMessage>): this; addListener<T extends SyncMessageType>(e: T, listener: SyncMessageReceivedListener<SyncMessageMap[T]>): this; addListener(e: 'event', listener: EventReceivedListener<Event>): this; addListener<T extends EventType>(e: T, listener: EventReceivedListener<EventMap[T]>): this; addListener(e: 'listen', listener: LifecycleHookListener): this; addListener(e: 'stop', listener: LifecycleHookListener): this; addListener(event, listener): this { return super.addListener(event, listener); } on(e: 'message', listener: MessageReceivedListener<ChatMessage>): this; on<T extends ChatMessageType>(e: T, listener: MessageReceivedListener<ChatMessageMap[T]>): this; on(e: 'sync', listener: SyncMessageReceivedListener<SyncMessage>): this; on<T extends SyncMessageType>(e: T, listener: SyncMessageReceivedListener<SyncMessageMap[T]>): this; on(e: 'event', listener: EventReceivedListener<Event>): this; on<T extends EventType>(e: T, listener: EventReceivedListener<EventMap[T]>): this; on(e: 'listen', listener: LifecycleHookListener): this; on(e: 'stop', listener: LifecycleHookListener): this; on(event, listener): this { return super.on(event, listener); } once(e: 'message', listener: MessageReceivedListener<ChatMessage>): this; once<T extends ChatMessageType>(e: T, listener: MessageReceivedListener<ChatMessageMap[T]>): this; once(e: 'sync', listener: SyncMessageReceivedListener<SyncMessage>): this; once<T extends SyncMessageType>(e: T, listener: SyncMessageReceivedListener<SyncMessageMap[T]>): this; once(e: 'event', listener: EventReceivedListener<Event>): this; once<T extends EventType>(e: T, listener: EventReceivedListener<EventMap[T]>): this; once(e: 'listen', listener: LifecycleHookListener): this; once(e: 'stop', listener: LifecycleHookListener): this; once(event, listener): this { return super.once(event, listener); } listeners(e: 'message'): MessageReceivedListener<ChatMessage>[]; listeners<T extends ChatMessageType>(e: T): MessageReceivedListener<ChatMessageMap[T]>[]; listeners(e: 'sync'): SyncMessageReceivedListener<SyncMessage>[]; listeners<T extends SyncMessageType>(e: T): SyncMessageReceivedListener<SyncMessageMap[T]>[]; listeners(e: 'event'): EventReceivedListener<Event>[]; listeners<T extends EventType>(e: T): EventReceivedListener<EventMap[T]>[]; listeners(e: 'listen'): LifecycleHookListener[]; listeners(e: 'stop'): LifecycleHookListener[]; listeners(event) { return super.listeners(event); } emit(e: 'message', chatMessage: ChatMessage); emit(e: ChatMessageType, chatMessage: ChatMessage); emit(e: 'sync', syncMessage: SyncMessage); emit(e: SyncMessageType, syncMessage: SyncMessage); emit(e: 'event', event: Event); emit(e: EventType, event: Event); emit(e: 'listen'); emit(e: 'stop'); emit(event, ...args) { return super.emit(event, ...args); } prependListener(e: 'message', listener: MessageReceivedListener<ChatMessage>): this; prependListener<T extends ChatMessageType>(e: T, listener: MessageReceivedListener<ChatMessageMap[T]>): this; prependListener(e: 'sync', listener: SyncMessageReceivedListener<SyncMessage>): this; prependListener<T extends SyncMessageType>(e: T, listener: SyncMessageReceivedListener<SyncMessageMap[T]>): this; prependListener(e: 'event', listener: EventReceivedListener<Event>): this; prependListener<T extends EventType>(e: T, listener: EventReceivedListener<EventMap[T]>): this; prependListener(e: 'listen', listener: LifecycleHookListener): this; prependListener(e: 'stop', listener: LifecycleHookListener): this; prependListener(event, listener): this { return super.prependListener(event, listener); } prependOnceListener(e: 'message', listener: MessageReceivedListener<ChatMessage>): this; prependOnceListener<T extends ChatMessageType>(e: T, listener: MessageReceivedListener<ChatMessageMap[T]>): this; prependOnceListener(e: 'sync', listener: SyncMessageReceivedListener<SyncMessage>): this; prependOnceListener<T extends SyncMessageType>(e: T, listener: SyncMessageReceivedListener<SyncMessageMap[T]>): this; prependOnceListener(e: 'event', listener: EventReceivedListener<Event>): this; prependOnceListener<T extends EventType>(e: T, listener: EventReceivedListener<EventMap[T]>): this; prependOnceListener(e: 'listen', listener: LifecycleHookListener): this; prependOnceListener(e: 'stop', listener: LifecycleHookListener): this; prependOnceListener(event, listener): this { return super.prependOnceListener(event, listener); } /** 用于 pie 的消息分发器 */ private async __messageDispatcher(chatMessage: ChatMessage) { let chat: Chat = null; if (chatMessage.type === 'FriendMessage') chat = new FriendChat(chatMessage.sender as Friend); else if (chatMessage.type === 'GroupMessage') chat = new GroupChat(chatMessage.sender as GroupMember); else if (chatMessage.type === 'TempMessage') chat = new TempChat(chatMessage.sender as GroupMember); else return; chat = makeReadonly(chat); const chain = makeReadonly(MessageChain.from(chatMessage.messageChain)); for (const [id, enabled] of this.__piesEnabledMap.entries()) { if (enabled) { const pie = this.__piesMap.get(id); if (pie.filters.map((filter) => filter.handler(chat, chain, pie)).some((v) => !v)) continue; pie.emit('received', chat, chain); } } } } export {ResponseCode, FaceType} from './mirai'; export * as Mirai from './mirai'; export * from './adapter'; export * from './pie'; export * from './message'; export * from './chat'; export * from './config'; export const createApp = MiraiPieApplication.createApp; export const makeAppConfig = ApplicationConfig.make; export default createApp; // 当存在全局 miraipie 应用程序时, 使用全局模块 if (process.env.MIRAIPIE_MODULE) { module.exports = require(process.env.MIRAIPIE_MODULE); }