|
1 | | -import TelegramBot = require('node-telegram-bot-api'); |
2 | | -import Command from './interfaces/Command'; |
3 | | -import CommandSet from './interfaces/CommandSet'; |
| 1 | +import { IBotEngine, IBotApiAdapter, BotSpeechApi, Command, Feature, Messenger } from './interfaces'; |
| 2 | +import * as re from './re'; |
| 3 | +import TelegramApiAdapter from './adapters/TelegramApiAdapter'; |
4 | 4 |
|
5 | | -export default class BotEngine { |
6 | | - private registeredCommands = new Set<string>([]); |
| 5 | +const ADAPTER_MAP: Record<Messenger, any> = { |
| 6 | + [Messenger.Telegram]: TelegramApiAdapter, |
| 7 | +} |
7 | 8 |
|
8 | | - constructor(private api: TelegramBot) {} |
| 9 | +export default class BotEngine implements IBotEngine { |
| 10 | + private commandsRegistry = new Set<string>([]); |
| 11 | + private apiAdapter: IBotApiAdapter; |
9 | 12 |
|
10 | | - public registerCommand(this: BotEngine, command: Command) { |
11 | | - if (this.registeredCommands.has(command.name)) { |
12 | | - throw new Error(`Command names collision occured: ${command.name}. Commands should have unique names.`); |
13 | | - } |
| 13 | + constructor(messenger: Messenger, secret: string) { |
| 14 | + this.apiAdapter = new ADAPTER_MAP[messenger](secret); |
| 15 | + } |
14 | 16 |
|
15 | | - if (!command.trigger) { |
16 | | - throw new Error(`Command ${command.name} has no trigger`); |
| 17 | + public registerCommand(command: Command) { |
| 18 | + const validationError = this.validateCommand(command); |
| 19 | + if (validationError) { |
| 20 | + throw new Error(validationError); |
17 | 21 | } |
18 | 22 |
|
19 | | - this.api.onText(command.trigger, command.reaction(this.api)); |
20 | | - this.registeredCommands.add(command.name); |
| 23 | + const { triggers, reaction, name } = command; |
| 24 | + triggers.forEach((trigger) => this.apiAdapter.onText(trigger, reaction)); |
| 25 | + this.commandsRegistry.add(name); |
| 26 | + console.log('Registered command: ' + name); |
21 | 27 |
|
22 | | - console.log('Registered command: ' + command.name); |
23 | 28 | return this; |
24 | 29 | } |
25 | 30 |
|
26 | | - public registerCommandSet(this: BotEngine, commandSet: CommandSet) { |
27 | | - const { name: setName, commands, fallback } = commandSet; |
28 | | - const commandNames = Object.keys(commands); |
29 | | - |
30 | | - if (this.registeredCommands.has(setName)) { |
31 | | - throw new Error(`Command names collision occured: ${setName}. Commands should have unique names.`); |
| 31 | + public registerFeature(feature: Feature) { |
| 32 | + const validationResult = this.validateFeature(feature); |
| 33 | + if (validationResult) { |
| 34 | + throw new Error(validationResult); |
32 | 35 | } |
33 | | - this.registeredCommands.add(setName); |
34 | 36 |
|
35 | | - console.log(`Command set: ${setName} ---`); |
| 37 | + const { name, commands, fallback } = feature; |
| 38 | + const commandNames = Object.keys(commands); |
| 39 | + |
| 40 | + this.commandsRegistry.add(name); |
| 41 | + console.log(`Command set: ${name} ---`); |
36 | 42 |
|
37 | | - commandNames.forEach(commandName => { |
38 | | - // matches "setName commandName..." |
39 | | - const reKnownCmd = `^${setName}[ \\t]+${commandName}([ \\t]+.*)?$`; |
40 | | - commands[commandName].trigger = new RegExp(reKnownCmd); |
| 43 | + commandNames.forEach((commandName) => { |
| 44 | + commands[commandName].triggers = commands[commandName].triggers || []; |
| 45 | + commands[commandName].triggers.push(re.featureCommand(name, commandName)); |
41 | 46 | this.registerCommand(commands[commandName]); |
42 | 47 | }); |
43 | 48 |
|
44 | | - // matches "setName" and "setName not(one|of|commandNames)..." |
45 | | - const reFallbackCmd = `^${setName}(?!\\S|([ \\t]+)?(${commandNames.join('|')}))`; |
46 | | - fallback.trigger = new RegExp(reFallbackCmd); |
| 49 | + fallback.triggers = fallback.triggers || []; |
| 50 | + fallback.triggers.push(re.featureFallback(name, commandNames)); |
47 | 51 | this.registerCommand(fallback); |
48 | 52 |
|
49 | 53 | console.log(`---`); |
50 | 54 |
|
51 | 55 | return this; |
52 | 56 | } |
53 | 57 |
|
54 | | - public registerFallback(this: BotEngine) { |
55 | | - // matches minus command |
56 | | - const reMinusSum = '-\\d+(?:[.,]\\d+)?(?:[+\\-*\\/]\\d+(?:[.,]\\d+)?)*'; |
57 | | - const commands = [...this.registeredCommands]; |
58 | | - const commandsJoined = commands.join('|'); |
| 58 | + public registerFallback() { |
| 59 | + const commandNames = [...this.commandsRegistry]; |
| 60 | + |
| 61 | + const triggers = [re.globalFallback(commandNames)]; |
| 62 | + const reaction = (bot: BotSpeechApi) => { |
| 63 | + bot.sendMessage(`commands:\n${commandNames.join('\n')}`); |
| 64 | + }; |
| 65 | + |
| 66 | + this.registerCommand({ name: 'global fallback', triggers, reaction }); |
| 67 | + } |
59 | 68 |
|
60 | | - // matches all but valid commands |
61 | | - const reFallbackGlobal = `^(?!(${reMinusSum}|${commandsJoined})).+|^(${commandsJoined})\\w+.*`; |
| 69 | + private validateCommand(command: Command): string | undefined { |
| 70 | + if (this.commandsRegistry.has(command.name)) { |
| 71 | + return `Command names collision occured: ${command.name}. Commands should have unique names.`; |
| 72 | + } |
62 | 73 |
|
63 | | - this.registerCommand({ |
64 | | - name: 'global fallback', |
| 74 | + if (!command.triggers?.length) { |
| 75 | + return `Command ${command.name} has no triggers`; |
| 76 | + } |
65 | 77 |
|
66 | | - trigger: new RegExp(reFallbackGlobal), |
| 78 | + if (command.triggers.map((t) => t instanceof RegExp).includes(false)) { |
| 79 | + return `Command ${command.name} triggers array contains invalid element`; |
| 80 | + } |
| 81 | + } |
67 | 82 |
|
68 | | - reaction: botApi => (msg, match) => { |
69 | | - botApi.sendMessage(msg.chat.id, `commands: \n${commands.join('\n')}`); |
70 | | - }, |
71 | | - }); |
| 83 | + private validateFeature(feature: Feature): string | undefined { |
| 84 | + if (this.commandsRegistry.has(feature.name)) { |
| 85 | + return `Command names collision occured: ${name}. Commands should have unique names.`; |
| 86 | + } |
72 | 87 |
|
73 | | - return this; |
| 88 | + if (!Object.keys(feature.commands).length) { |
| 89 | + return `Feature ${feature.name} has no commands`; |
| 90 | + } |
74 | 91 | } |
75 | 92 | } |
0 commit comments