diff --git a/package.json b/package.json index 43b5961..c101966 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@squeebot/core", - "version": "3.0.1", + "version": "3.1.0", "description": "Squeebot v3 core for the execution environment", "main": "lib/index.js", "module": "lib/", diff --git a/src/channel/index.ts b/src/channel/index.ts index 606ce52..141e9aa 100644 --- a/src/channel/index.ts +++ b/src/channel/index.ts @@ -1,5 +1,5 @@ import { IPlugin } from '../plugin'; -import { IMessage, Protocol } from '../types'; +import { IMessage, MessageResolver, Protocol } from '../types'; import { ScopedEventEmitter } from '../util/events'; export interface IChannel { @@ -32,20 +32,28 @@ export class ChannelManager { for (const event of ['message', 'event', 'special']) { this.stream.on('channel', event, (data: IMessage) => { + let msr; + if (event === 'message') { + msr = new MessageResolver(data); + } + const plugin = ChannelManager.determinePlugin(data.source); if (!plugin) { return; } + const source = plugin.manifest.name; const emitTo = this.getChannelsByPluginName(source, data.source); + for (const chan of emitTo) { if (chan.plugins.length < 2) { continue; } + for (const pl of chan.plugins) { if (pl !== source && !(pl.indexOf('/') !== -1 && pl.split('/')[0] === source)) { - this.stream.emitTo(pl, event, data, chan); + this.stream.emitTo(pl, event, data, chan, msr); } } } diff --git a/src/types/index.ts b/src/types/index.ts index a1edc47..e3fd5e1 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -5,3 +5,4 @@ export * from './message'; export * from './message-format'; export * from './protocol'; export * from './service'; +export * from './staged-handler'; diff --git a/src/types/message.ts b/src/types/message.ts index a13d2cb..5d8a695 100644 --- a/src/types/message.ts +++ b/src/types/message.ts @@ -85,7 +85,12 @@ export interface IMessage { * @param args Message data */ resolve(...args: any[]): void; - + /** + * Bot's error response to this message. Will be passed to the protocol, + * which usually sends `error.message` directly to the target of the message + * @param error Error + */ + reject(error: Error): void; /** * Insert a mention into the response * @param target UserTarget to mention (i.e. `msg.sender`) diff --git a/src/types/staged-handler.ts b/src/types/staged-handler.ts new file mode 100644 index 0000000..a533ebc --- /dev/null +++ b/src/types/staged-handler.ts @@ -0,0 +1,94 @@ +import { IMessage } from './message'; + +/** + * Message data object for staged handling + * Response value is always mandatory. + */ +export interface IMessageData { + text?: string; + data?: any; + compose?: any[]; + value: any; +} + +export type IMessageHandler = (data: IMessageData | string, reject: Function, next: IMessageHandler) => void; + +/** + * Staged messaging handler + */ +export class MessageResolver { + private handlers: IMessageHandler[] = []; + + public static textFormatter(text: string | undefined, value: any): string { + let stri = text || ''; + if (Array.isArray(value) || typeof value === 'object') { + stri = stri.replace(/%([a-z]+)?(\d+)?/ig, (substr: string, word?: string, nr?: number): string => { + if (nr && typeof nr === 'string') { + nr = parseInt(nr as 'string', 10); + } + + if (Array.isArray(value) && nr != null) { + return value[nr]; + } + + if (!word) { + return substr; + } + + const val = value[word]; + if (Array.isArray(val) && nr) { + return val[nr]; + } + + return val; + }); + } else if (value === 'string') { + if (!text) { + stri = value; + } else { + text += ' ' + value; + } + } + return stri; + } + + constructor(public msg: IMessage) {} + + /** + * Inject a middleware message handler + * @param fn Message handler function + */ + use(fn: IMessageHandler): void { + this.handlers.push(fn); + } + + /** + * Resolve the message. Call this only from the last plugin in the chain. + * @param data Message response data + */ + resolve(data: IMessageData | string, ...rest: any[]): void { + const ist = this.handlers.pop(); + if (!this.handlers.length || !ist) { + if (typeof data === 'string') { + this.msg.resolve(data, ...rest); + return; + } + if (data.compose && Array.isArray(data.compose)) { + this.msg.resolve(data.compose); + return; + } + this.msg.resolve(MessageResolver.textFormatter(data.text, data.value)); + return; + } + + ist.call(this, data, this.msg.reject, this.resolve); + } + + /** + * Reject the message. You can call this from anywhere. + * @param error Error + */ + reject(error: Error): void { + this.msg.reject(error); + } +}