chz-telegram-bot 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/README.md +73 -11
  2. package/dist/dtos/incomingQuery.d.ts +9 -0
  3. package/dist/dtos/incomingQuery.d.ts.map +1 -0
  4. package/dist/dtos/incomingQuery.js +12 -0
  5. package/dist/dtos/responses/delay.d.ts +1 -1
  6. package/dist/dtos/responses/delay.d.ts.map +1 -1
  7. package/dist/dtos/responses/imageMessage.d.ts +1 -1
  8. package/dist/dtos/responses/imageMessage.d.ts.map +1 -1
  9. package/dist/dtos/responses/inlineQueryResponse.d.ts +13 -0
  10. package/dist/dtos/responses/inlineQueryResponse.d.ts.map +1 -0
  11. package/dist/dtos/responses/inlineQueryResponse.js +15 -0
  12. package/dist/dtos/responses/reaction.d.ts +1 -1
  13. package/dist/dtos/responses/reaction.d.ts.map +1 -1
  14. package/dist/dtos/responses/textMessage.d.ts +1 -1
  15. package/dist/dtos/responses/textMessage.d.ts.map +1 -1
  16. package/dist/dtos/responses/unpin.d.ts +1 -1
  17. package/dist/dtos/responses/unpin.d.ts.map +1 -1
  18. package/dist/dtos/responses/videoMessage.d.ts +1 -1
  19. package/dist/dtos/responses/videoMessage.d.ts.map +1 -1
  20. package/dist/entities/actions/commandAction.d.ts +1 -1
  21. package/dist/entities/actions/commandAction.d.ts.map +1 -1
  22. package/dist/entities/actions/inlineQueryAction.d.ts +14 -0
  23. package/dist/entities/actions/inlineQueryAction.d.ts.map +1 -0
  24. package/dist/entities/actions/inlineQueryAction.js +40 -0
  25. package/dist/entities/actions/scheduledAction.d.ts +1 -1
  26. package/dist/entities/actions/scheduledAction.d.ts.map +1 -1
  27. package/dist/entities/botInstance.d.ts +8 -2
  28. package/dist/entities/botInstance.d.ts.map +1 -1
  29. package/dist/entities/botInstance.js +34 -2
  30. package/dist/entities/context/chatContext.d.ts +1 -1
  31. package/dist/entities/context/chatContext.d.ts.map +1 -1
  32. package/dist/entities/context/inlineQueryContext.d.ts +38 -0
  33. package/dist/entities/context/inlineQueryContext.d.ts.map +1 -0
  34. package/dist/entities/context/inlineQueryContext.js +40 -0
  35. package/dist/entities/context/messageContext.d.ts +1 -1
  36. package/dist/entities/context/messageContext.d.ts.map +1 -1
  37. package/dist/helpers/builders/inlineQueryActionBuilder.d.ts +34 -0
  38. package/dist/helpers/builders/inlineQueryActionBuilder.d.ts.map +1 -0
  39. package/dist/helpers/builders/inlineQueryActionBuilder.js +48 -0
  40. package/dist/helpers/noop.d.ts +1 -0
  41. package/dist/helpers/noop.d.ts.map +1 -1
  42. package/dist/helpers/noop.js +1 -1
  43. package/dist/helpers/traceFactory.d.ts +1 -1
  44. package/dist/helpers/traceFactory.d.ts.map +1 -1
  45. package/dist/helpers/traceFactory.js +1 -1
  46. package/dist/index.d.ts +1 -1
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +2 -3
  49. package/dist/main.d.ts +44 -35
  50. package/dist/main.d.ts.map +1 -1
  51. package/dist/main.js +34 -31
  52. package/dist/services/jsonFileStorage.d.ts +1 -1
  53. package/dist/services/jsonFileStorage.d.ts.map +1 -1
  54. package/dist/services/telegramApi.d.ts.map +1 -1
  55. package/dist/services/telegramApi.js +6 -1
  56. package/dist/types/handlers.d.ts +4 -0
  57. package/dist/types/handlers.d.ts.map +1 -1
  58. package/dist/types/response.d.ts +4 -2
  59. package/dist/types/response.d.ts.map +1 -1
  60. package/dist/types/response.js +2 -1
  61. package/dist/types/statefulAction.d.ts +9 -0
  62. package/dist/types/statefulAction.d.ts.map +1 -0
  63. package/dist/types/statefulAction.js +2 -0
  64. package/dist/types/statelessAction.d.ts +5 -0
  65. package/dist/types/statelessAction.d.ts.map +1 -0
  66. package/dist/types/statelessAction.js +2 -0
  67. package/dist/types/storage.d.ts +1 -1
  68. package/dist/types/storage.d.ts.map +1 -1
  69. package/dtos/incomingQuery.ts +20 -0
  70. package/dtos/responses/delay.ts +1 -1
  71. package/dtos/responses/imageMessage.ts +1 -1
  72. package/dtos/responses/inlineQueryResponse.ts +26 -0
  73. package/dtos/responses/reaction.ts +1 -1
  74. package/dtos/responses/textMessage.ts +1 -1
  75. package/dtos/responses/unpin.ts +1 -1
  76. package/dtos/responses/videoMessage.ts +1 -1
  77. package/entities/actions/commandAction.ts +1 -1
  78. package/entities/actions/inlineQueryAction.ts +68 -0
  79. package/entities/actions/scheduledAction.ts +1 -1
  80. package/entities/botInstance.ts +86 -4
  81. package/entities/context/chatContext.ts +4 -4
  82. package/entities/context/inlineQueryContext.ts +83 -0
  83. package/entities/context/messageContext.ts +1 -1
  84. package/helpers/builders/inlineQueryActionBuilder.ts +62 -0
  85. package/helpers/noop.ts +3 -1
  86. package/helpers/traceFactory.ts +4 -2
  87. package/index.ts +1 -1
  88. package/main.ts +65 -58
  89. package/package.json +1 -1
  90. package/services/jsonFileStorage.ts +1 -1
  91. package/services/telegramApi.ts +10 -1
  92. package/types/handlers.ts +6 -0
  93. package/types/response.ts +5 -2
  94. package/types/statelessAction.ts +5 -0
  95. package/types/storage.ts +1 -1
  96. /package/types/{actionWithState.ts → statefulAction.ts} +0 -0
@@ -0,0 +1,68 @@
1
+ import { Noop } from '../../helpers/noop';
2
+ import { ActionKey } from '../../types/statefulAction';
3
+ import { InlineQueryContext } from '../context/inlineQueryContext';
4
+ import { InlineQueryHandler } from '../../types/handlers';
5
+ import { IActionWithoutState } from '../../types/statelessAction';
6
+
7
+ export class InlineQueryAction implements IActionWithoutState {
8
+ readonly key: ActionKey;
9
+ readonly active: boolean;
10
+ readonly handler: InlineQueryHandler;
11
+ readonly name: string;
12
+ readonly pattern: RegExp;
13
+
14
+ constructor(
15
+ handler: InlineQueryHandler,
16
+ name: string,
17
+ active: boolean,
18
+ pattern: RegExp
19
+ ) {
20
+ this.handler = handler;
21
+ this.name = name;
22
+ this.active = active;
23
+ this.pattern = pattern;
24
+
25
+ this.key = `inline:${this.name.replace('.', '-')}` as ActionKey;
26
+ }
27
+
28
+ async exec(ctx: InlineQueryContext) {
29
+ if (!ctx.isInitialized)
30
+ throw new Error(
31
+ `Context for ${this.key} is not initialized or already consumed`
32
+ );
33
+
34
+ if (!this.active) return Noop.NoResponse;
35
+
36
+ const matchResults: RegExpExecArray[] = [];
37
+
38
+ this.pattern.lastIndex = 0;
39
+
40
+ const execResult = this.pattern.exec(ctx.queryText);
41
+ if (execResult != null) {
42
+ matchResults.push(execResult);
43
+
44
+ if (this.pattern.global) {
45
+ while (true) {
46
+ const nextResult = this.pattern.exec(ctx.queryText);
47
+ if (nextResult == null) break;
48
+ matchResults.push(nextResult);
49
+ }
50
+ }
51
+ }
52
+
53
+ if (matchResults.length == 0) return Noop.NoResponse;
54
+
55
+ ctx.matchResults = matchResults;
56
+
57
+ ctx.logger.logWithTraceId(
58
+ ctx.botName,
59
+ ctx.traceId,
60
+ 'Unknown',
61
+ ` - Executing [${this.name}]`
62
+ );
63
+
64
+ await this.handler(ctx);
65
+
66
+ return ctx.responses;
67
+ }
68
+ }
@@ -4,7 +4,7 @@ import { ScheduledHandler } from '../../types/handlers';
4
4
  import { hoursToMilliseconds } from '../../helpers/timeConvertions';
5
5
  import { HoursOfDay } from '../../types/timeValues';
6
6
  import { IActionState } from '../../types/actionState';
7
- import { IActionWithState, ActionKey } from '../../types/actionWithState';
7
+ import { IActionWithState, ActionKey } from '../../types/statefulAction';
8
8
  import { CachedStateFactory } from '../cachedStateFactory';
9
9
  import { ChatContext } from '../context/chatContext';
10
10
  import { Noop } from '../../helpers/noop';
@@ -20,6 +20,9 @@ import { ILogger } from '../types/logger';
20
20
  import { IScheduler } from '../types/scheduler';
21
21
  import { NodeTimeoutScheduler } from '../services/nodeTimeoutScheduler';
22
22
  import { createTrace } from '../helpers/traceFactory';
23
+ import { InlineQueryAction } from './actions/inlineQueryAction';
24
+ import { IncomingInlineQuery } from '../dtos/incomingQuery';
25
+ import { InlineQueryContext } from './context/inlineQueryContext';
23
26
 
24
27
  export class BotInstance {
25
28
  private readonly api: TelegramApiService;
@@ -31,13 +34,17 @@ export class BotInstance {
31
34
  private readonly telegraf: Telegraf;
32
35
  private readonly commands: CommandAction<IActionState>[];
33
36
  private readonly scheduled: ScheduledAction<IActionState>[];
37
+ private readonly inlineQueries: InlineQueryAction[];
34
38
  private readonly chats: Record<string, number>;
35
39
 
36
40
  constructor(options: {
37
41
  name: string;
38
42
  token: string;
39
- commands: CommandAction<IActionState>[];
40
- scheduled: ScheduledAction<IActionState>[];
43
+ actions: {
44
+ commands: CommandAction<IActionState>[];
45
+ scheduled: ScheduledAction<IActionState>[];
46
+ inlineQueries: InlineQueryAction[];
47
+ };
41
48
  chats: Record<string, number>;
42
49
  storagePath?: string;
43
50
  scheduledPeriod?: Seconds;
@@ -49,8 +56,9 @@ export class BotInstance {
49
56
  };
50
57
  }) {
51
58
  this.name = options.name;
52
- this.commands = options.commands;
53
- this.scheduled = options.scheduled;
59
+ this.commands = options.actions.commands;
60
+ this.scheduled = options.actions.scheduled;
61
+ this.inlineQueries = options.actions.inlineQueries;
54
62
  this.chats = options.chats;
55
63
 
56
64
  const actions = [...this.commands, ...this.scheduled];
@@ -72,6 +80,7 @@ export class BotInstance {
72
80
  this.initializeMessageProcessing(
73
81
  options.verboseLoggingForIncomingMessage ?? false
74
82
  );
83
+ this.initializeInlineQueryProcessing(500 as Milliseconds);
75
84
  this.initializeScheduledProcessing(
76
85
  options.scheduledPeriod ?? hoursToSeconds(1 as Hours)
77
86
  );
@@ -130,6 +139,79 @@ export class BotInstance {
130
139
  );
131
140
  }
132
141
  }
142
+
143
+ private initializeInlineQueryProcessing(period: Milliseconds) {
144
+ let pendingInlineQueries: IncomingInlineQuery[] = [];
145
+
146
+ if (this.inlineQueries.length > 0) {
147
+ this.telegraf.on('inline_query', async (ctx) => {
148
+ const query = new IncomingInlineQuery(
149
+ ctx.inlineQuery.id,
150
+ ctx.inlineQuery.query,
151
+ ctx.inlineQuery.from.id,
152
+ createTrace('InlineQuery', this.name, ctx.inlineQuery.id)
153
+ );
154
+
155
+ this.logger.logWithTraceId(
156
+ this.name,
157
+ query.traceId,
158
+ 'Query',
159
+ `${ctx.inlineQuery.from.username} (${ctx.inlineQuery.from.id}): Query for ${ctx.inlineQuery.query}`
160
+ );
161
+
162
+ pendingInlineQueries = pendingInlineQueries.filter(
163
+ (q) => q.userId != query.userId
164
+ );
165
+
166
+ pendingInlineQueries.push(query);
167
+ });
168
+
169
+ this.scheduler.createTask(
170
+ 'InlineQueryProcessing',
171
+ async () => {
172
+ const ctx = new InlineQueryContext(
173
+ this.storage,
174
+ this.logger,
175
+ this.scheduler
176
+ );
177
+
178
+ for (const inlineQuery of pendingInlineQueries) {
179
+ for (const inlineQueryAction of this.inlineQueries) {
180
+ ctx.initializeContext(
181
+ inlineQuery.query,
182
+ inlineQuery.queryId,
183
+ this.name,
184
+ inlineQueryAction,
185
+ inlineQuery.traceId
186
+ );
187
+
188
+ try {
189
+ const responses = await inlineQueryAction.exec(
190
+ ctx
191
+ );
192
+ this.api.enqueueBatchedResponses(responses);
193
+ ctx.isInitialized = false;
194
+ } catch (error) {
195
+ this.logger.errorWithTraceId(
196
+ ctx.botName,
197
+ ctx.traceId,
198
+ 'Unknown',
199
+ error,
200
+ ctx
201
+ );
202
+ }
203
+ }
204
+ }
205
+
206
+ this.api.flushResponses();
207
+ },
208
+ period,
209
+ false,
210
+ this.name
211
+ );
212
+ }
213
+ }
214
+
133
215
  private initializeMessageProcessing(
134
216
  verboseLoggingForIncomingMessage: boolean
135
217
  ) {
@@ -8,7 +8,7 @@ import {
8
8
  MessageSendingOptions,
9
9
  TextMessageSendingOptions
10
10
  } from '../../types/messageSendingOptions';
11
- import { IActionWithState } from '../../types/actionWithState';
11
+ import { IActionWithState } from '../../types/statefulAction';
12
12
  import { IActionState } from '../../types/actionState';
13
13
  import { BotResponse } from '../../types/response';
14
14
  import { Milliseconds } from '../../types/timeValues';
@@ -25,11 +25,11 @@ export class ChatContext<TActionState extends IActionState> {
25
25
  protected action!: IActionWithState<TActionState>;
26
26
 
27
27
  /** Storage client instance for the bot executing this action. */
28
- readonly storage!: IStorageClient;
28
+ readonly storage: IStorageClient;
29
29
  /** Logger instance for the bot executing this action */
30
- readonly logger!: ILogger;
30
+ readonly logger: ILogger;
31
31
  /** Scheduler instance for the bot executing this action */
32
- readonly scheduler!: IScheduler;
32
+ readonly scheduler: IScheduler;
33
33
 
34
34
  /** Trace id of a action execution. */
35
35
  traceId!: TraceId;
@@ -0,0 +1,83 @@
1
+ import { InlineQueryResult } from 'telegraf/types';
2
+ import { ILogger } from '../../types/logger';
3
+ import { BotResponse } from '../../types/response';
4
+ import { IScheduler } from '../../types/scheduler';
5
+ import { IStorageClient } from '../../types/storage';
6
+ import { TraceId } from '../../types/trace';
7
+ import { InlineQueryAction } from '../actions/inlineQueryAction';
8
+ import { InlineQueryResponse } from '../../dtos/responses/inlineQueryResponse';
9
+
10
+ export class InlineQueryContext {
11
+ private action!: InlineQueryAction;
12
+ private queryResults: InlineQueryResult[] = [];
13
+
14
+ /** Storage client instance for the bot executing this action. */
15
+ readonly storage: IStorageClient;
16
+ /** Logger instance for the bot executing this action */
17
+ readonly logger: ILogger;
18
+ /** Scheduler instance for the bot executing this action */
19
+ readonly scheduler: IScheduler;
20
+
21
+ /** Trace id of a action execution. */
22
+ traceId!: TraceId;
23
+ /** Name of a bot that executes this action. */
24
+ botName!: string;
25
+
26
+ /** Ordered collection of responses to be processed */
27
+ get responses(): BotResponse[] {
28
+ return [
29
+ new InlineQueryResponse(
30
+ this.queryResults,
31
+ this.queryId,
32
+ this.traceId,
33
+ this.action
34
+ )
35
+ ];
36
+ }
37
+ /** Inline query text */
38
+ queryText!: string;
39
+ /** Internal query id */
40
+ queryId!: string;
41
+ /** Collection of Regexp match results on a message that triggered this action. Will be empty if trigger is not a Regexp. */
42
+ matchResults: RegExpMatchArray[] = [];
43
+
44
+ isInitialized = false;
45
+
46
+ constructor(
47
+ storage: IStorageClient,
48
+ logger: ILogger,
49
+ scheduler: IScheduler
50
+ ) {
51
+ this.storage = storage;
52
+ this.logger = logger;
53
+ this.scheduler = scheduler;
54
+ }
55
+
56
+ initializeContext(
57
+ queryText: string,
58
+ queryId: string,
59
+ botName: string,
60
+ action: InlineQueryAction,
61
+ traceId: TraceId
62
+ ) {
63
+ this.queryText = queryText;
64
+ this.queryId = queryId;
65
+ this.botName = botName;
66
+ this.action = action;
67
+ this.traceId = traceId;
68
+
69
+ this.isInitialized = true;
70
+ this.queryResults = [];
71
+ this.matchResults = [];
72
+
73
+ return this;
74
+ }
75
+
76
+ /**
77
+ * This result will be shown to user as a response to inline query.
78
+ * @param queryResult Inline query result to be shown to user.
79
+ */
80
+ showInlineQueryResult(queryResult: InlineQueryResult) {
81
+ this.queryResults.push(queryResult);
82
+ }
83
+ }
@@ -13,7 +13,7 @@ import {
13
13
  MessageSendingOptions,
14
14
  TextMessageSendingOptions
15
15
  } from '../../types/messageSendingOptions';
16
- import { IActionWithState, ActionKey } from '../../types/actionWithState';
16
+ import { IActionWithState, ActionKey } from '../../types/statefulAction';
17
17
  import { MessageTypeValue } from '../../types/messageTypes';
18
18
  import { ILogger } from '../../types/logger';
19
19
  import { IScheduler } from '../../types/scheduler';
@@ -0,0 +1,62 @@
1
+ import { InlineQueryHandler } from '../../types/handlers';
2
+ import { Seconds } from '../../types/timeValues';
3
+ import { Noop } from '../noop';
4
+ import { InlineQueryAction } from '../../entities/actions/inlineQueryAction';
5
+
6
+ /**
7
+ * Builder for `InlineQueryAction`
8
+ */
9
+ export class InlineQueryActionBuilder {
10
+ name: string;
11
+ pattern: RegExp = /.+/gi;
12
+
13
+ active = true;
14
+ cooldownSeconds: Seconds = 0 as Seconds;
15
+ blacklist: number[] = [];
16
+ allowedUsers: number[] = [];
17
+ handler: InlineQueryHandler = Noop.call;
18
+
19
+ /**
20
+ * Builder for `InlineQueryAction`
21
+ * @param name Action name, will be used for logging and storage
22
+ */
23
+ constructor(name: string) {
24
+ this.name = name;
25
+ }
26
+
27
+ /**
28
+ * Defines action pattern to check if action should be executed, if not setup, check will default to true
29
+ * @param trigger RegExp to check
30
+ */
31
+ on(pattern: RegExp) {
32
+ this.pattern = pattern;
33
+
34
+ return this;
35
+ }
36
+
37
+ /** Defines action logic itself, will be executed.
38
+ * @param handler Callback that will be called
39
+ */
40
+ do(handler: InlineQueryHandler) {
41
+ this.handler = handler;
42
+
43
+ return this;
44
+ }
45
+
46
+ /** If called during building, action is marked as disabled and never checked. */
47
+ disabled() {
48
+ this.active = false;
49
+
50
+ return this;
51
+ }
52
+
53
+ /** Builds action */
54
+ build() {
55
+ return new InlineQueryAction(
56
+ this.handler,
57
+ this.name,
58
+ this.active,
59
+ this.pattern
60
+ );
61
+ }
62
+ }
package/helpers/noop.ts CHANGED
@@ -9,6 +9,8 @@ export class Noop {
9
9
  static false<T1>(arg1: T1) {
10
10
  return false;
11
11
  }
12
+
13
+ static async call<T1>(arg1: T1): Promise<void>;
12
14
  static async call<T1, T2>(arg1: T1, arg2: T2): Promise<void>;
13
- static async call<T1>(arg1: T1) {}
15
+ static async call(arg1: unknown, arg2?: unknown) {}
14
16
  }
@@ -1,9 +1,11 @@
1
1
  import { TraceId } from '../types/trace';
2
2
 
3
3
  export function createTrace<T extends object>(
4
- traceOwner: T,
4
+ traceOwner: T | string,
5
5
  botName: string,
6
6
  traceName: string
7
7
  ) {
8
- return `${traceOwner.constructor.name}:${botName}-${traceName}` as TraceId;
8
+ return `${
9
+ typeof traceOwner == 'string' ? traceOwner : traceOwner.constructor.name
10
+ }:${botName}-${traceName}` as TraceId;
9
11
  }
package/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { startBot, stopBots } from './main';
1
+ export { botOrchestrator } from './main';
2
2
  export { CommandActionBuilder } from './helpers/builders/commandActionBuilder';
3
3
  export { CommandActionBuilderWithState } from './helpers/builders/commandActionBuilder';
4
4
  export { IStorageClient } from './types/storage';
package/main.ts CHANGED
@@ -2,72 +2,79 @@ import { readFile } from 'fs/promises';
2
2
  import { IStorageClient } from './types/storage';
3
3
  import { CommandAction } from './entities/actions/commandAction';
4
4
  import { ScheduledAction } from './entities/actions/scheduledAction';
5
- import { IActionState } from './types/actionState';
6
5
  import { BotInstance } from './entities/botInstance';
7
6
  import { Seconds } from './types/timeValues';
8
7
  import { IScheduler } from './types/scheduler';
9
8
  import { ILogger } from './types/logger';
9
+ import { ActionStateBase } from './entities/states/actionStateBase';
10
+ import { InlineQueryAction } from './entities/actions/inlineQueryAction';
10
11
 
11
- const bots: BotInstance[] = [];
12
+ class BotOrchestrator {
13
+ bots: BotInstance[] = [];
12
14
 
13
- /**
14
- * Starts bot
15
- */
16
- async function startBot(options: {
17
- /** Bot name, used in logging */
18
- name: string;
19
- /** Path to file containing Telegram Bot token. */
20
- tokenFilePath: string;
21
- /** Collection of actions that will be executed as a response to message from used. Created using `CommandActionBuilder`.*/
22
- commands: CommandAction<IActionState>[];
23
- /** Collection of actions that will be executed on timer. Created using `ScheduledActionBuilder`.*/
24
- scheduled: ScheduledAction<IActionState>[];
25
- /** Object containing chat name and chat id pairs. Used for logging and execution of scheduled action. */
26
- chats: Record<string, number>;
27
- /** Storage path for default `JsonFileStorage` client. Will be used only if `storageClient` is not provided. If not provided, default value of `./storage/` will be used.*/
28
- storagePath?: string;
29
- /** Period of time between execution of scheduled actions. */
30
- scheduledPeriod?: Seconds;
31
- /** If true, telegram API objects will be logged instead of message content. */
32
- verboseLoggingForIncomingMessage?: boolean;
33
- services?: {
34
- /** Storage client for bot state storage. If not provided, default `JsonFileStorage` will be used. */
35
- storageClient?: IStorageClient;
36
- /** Logger client for bot logging. If not provided, default `JsonFileStorage` will be used. */
37
- logger?: ILogger;
38
- /** Scheduler client for bot scheduling. If not provided, default `NodeTimeoutScheduler` will be used. */
39
- scheduler?: IScheduler;
40
- };
41
- }) {
42
- const token = await readFile(options.tokenFilePath, 'utf8');
43
- const bot = new BotInstance({
44
- name: options.name,
45
- token,
46
- commands: options.commands,
47
- scheduled: options.scheduled,
48
- chats: options.chats,
49
- storagePath: options.storagePath,
50
- scheduledPeriod: options.scheduledPeriod,
51
- verboseLoggingForIncomingMessage:
52
- options.verboseLoggingForIncomingMessage,
53
- services: {
54
- storageClient: options.services?.storageClient,
55
- logger: options.services?.logger,
56
- scheduler: options.services?.scheduler
57
- }
58
- });
59
- bots.push(bot);
15
+ /**
16
+ * Starts bot
17
+ */
18
+ async startBot(options: {
19
+ /** Bot name, used in logging */
20
+ name: string;
21
+ /** Path to file containing Telegram Bot token. */
22
+ tokenFilePath: string;
23
+ actions: {
24
+ /** Collection of actions that will be executed as a response to message from used. Created using `CommandActionBuilder`.*/
25
+ commands: CommandAction<ActionStateBase>[];
26
+ /** Collection of actions that will be executed on timer. Created using `ScheduledActionBuilder`.*/
27
+ scheduled: ScheduledAction<ActionStateBase>[];
28
+ /** Collection of actions that will handle inline queries */
29
+ inlineQueries: InlineQueryAction[];
30
+ };
31
+ /** Object containing chat name and chat id pairs. Used for logging and execution of scheduled action. */
32
+ chats: Record<string, number>;
33
+ /** Storage path for default `JsonFileStorage` client. Will be used only if `storageClient` is not provided. If not provided, default value of `./storage/` will be used.*/
34
+ storagePath?: string;
35
+ /** Period of time between execution of scheduled actions. */
36
+ scheduledPeriod?: Seconds;
37
+ /** If true, telegram API objects will be logged instead of message content. */
38
+ verboseLoggingForIncomingMessage?: boolean;
39
+ services?: {
40
+ /** Storage client for bot state storage. If not provided, default `JsonFileStorage` will be used. */
41
+ storageClient?: IStorageClient;
42
+ /** Logger client for bot logging. If not provided, default `JsonFileStorage` will be used. */
43
+ logger?: ILogger;
44
+ /** Scheduler client for bot scheduling. If not provided, default `NodeTimeoutScheduler` will be used. */
45
+ scheduler?: IScheduler;
46
+ };
47
+ }) {
48
+ const token = await readFile(options.tokenFilePath, 'utf8');
49
+ const bot = new BotInstance({
50
+ name: options.name,
51
+ token,
52
+ actions: options.actions,
53
+ chats: options.chats,
54
+ storagePath: options.storagePath,
55
+ scheduledPeriod: options.scheduledPeriod,
56
+ verboseLoggingForIncomingMessage:
57
+ options.verboseLoggingForIncomingMessage,
58
+ services: {
59
+ storageClient: options.services?.storageClient,
60
+ logger: options.services?.logger,
61
+ scheduler: options.services?.scheduler
62
+ }
63
+ });
60
64
 
61
- return bot;
62
- }
65
+ this.bots.push(bot);
63
66
 
64
- /**
65
- * Terminates all scheduled tasks, closes storage connections and stops all bots.
66
- */
67
- async function stopBots(reason: string) {
68
- for (const bot of bots) {
69
- await bot.stop(reason);
67
+ return bot;
68
+ }
69
+
70
+ /**
71
+ * Terminates all scheduled tasks, closes storage connections and stops all bots.
72
+ */
73
+ async stopBots(reason: string) {
74
+ for (const bot of this.bots) {
75
+ await bot.stop(reason);
76
+ }
70
77
  }
71
78
  }
72
79
 
73
- export { startBot, stopBots };
80
+ export const botOrchestrator = new BotOrchestrator();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chz-telegram-bot",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "dependencies": {
6
6
  "async-sema": "^3.1.1",
@@ -4,7 +4,7 @@ import { mkdir, readFile, writeFile } from 'fs/promises';
4
4
  import { Sema as Semaphore } from 'async-sema';
5
5
  import { IStorageClient } from '../types/storage';
6
6
  import { IActionState } from '../types/actionState';
7
- import { IActionWithState, ActionKey } from '../types/actionWithState';
7
+ import { IActionWithState, ActionKey } from '../types/statefulAction';
8
8
 
9
9
  export class JsonFileStorage implements IStorageClient {
10
10
  private readonly locks = new Map<ActionKey, Semaphore>();
@@ -41,7 +41,9 @@ export class TelegramApiService {
41
41
  this.logger.errorWithTraceId(
42
42
  this.botName,
43
43
  response.traceId,
44
- response.chatInfo.name,
44
+ 'chatInfo' in response
45
+ ? response.chatInfo.name
46
+ : 'Unknown',
45
47
  error,
46
48
  response
47
49
  );
@@ -150,6 +152,13 @@ export class TelegramApiService {
150
152
  }
151
153
  );
152
154
  break;
155
+ case 'inlineQuery':
156
+ await this.telegram.answerInlineQuery(
157
+ response.queryId,
158
+ response.queryResults,
159
+ { cache_time: 3600 }
160
+ );
161
+ break;
153
162
  case 'delay':
154
163
  break;
155
164
  }
package/types/handlers.ts CHANGED
@@ -1,8 +1,14 @@
1
1
  import { ChatContext } from '../entities/context/chatContext';
2
+ import { InlineQueryContext } from '../entities/context/inlineQueryContext';
2
3
  import { MessageContext } from '../entities/context/messageContext';
3
4
  import { IActionState } from './actionState';
4
5
  import { CachedValueAccessor } from './cachedValueAccessor';
5
6
 
7
+ export type InlineQueryHandler = (
8
+ /** Context of inline query executed in chat, in response to a message. */
9
+ ctx: InlineQueryContext
10
+ ) => Promise<void>;
11
+
6
12
  export type CommandHandler<TActionState extends IActionState> = (
7
13
  /** Context of action executed in chat, in response to a message. */
8
14
  ctx: MessageContext<TActionState>,
package/types/response.ts CHANGED
@@ -1,12 +1,13 @@
1
1
  import { ChatInfo } from '../dtos/chatInfo';
2
2
  import { DelayResponse } from '../dtos/responses/delay';
3
3
  import { ImageMessage } from '../dtos/responses/imageMessage';
4
+ import { InlineQueryResponse } from '../dtos/responses/inlineQueryResponse';
4
5
  import { Reaction } from '../dtos/responses/reaction';
5
6
  import { TextMessage } from '../dtos/responses/textMessage';
6
7
  import { UnpinResponse } from '../dtos/responses/unpin';
7
8
  import { VideoMessage } from '../dtos/responses/videoMessage';
8
9
  import { IActionState } from './actionState';
9
- import { IActionWithState } from './actionWithState';
10
+ import { IActionWithState } from './statefulAction';
10
11
  import { TraceId } from './trace';
11
12
 
12
13
  export const BotResponseTypes = {
@@ -15,7 +16,8 @@ export const BotResponseTypes = {
15
16
  image: 'image',
16
17
  video: 'video',
17
18
  react: 'react',
18
- delay: 'delay'
19
+ delay: 'delay',
20
+ inlineQuery: 'inlineQuery'
19
21
  } as const;
20
22
 
21
23
  export type BotResponse =
@@ -24,6 +26,7 @@ export type BotResponse =
24
26
  | TextMessage
25
27
  | VideoMessage
26
28
  | DelayResponse
29
+ | InlineQueryResponse
27
30
  | ImageMessage;
28
31
 
29
32
  export interface IChatResponse {
@@ -0,0 +1,5 @@
1
+ import { ActionKey } from './statefulAction';
2
+
3
+ export interface IActionWithoutState {
4
+ readonly key: ActionKey;
5
+ }