chz-telegram-bot 0.2.1 → 0.3.0

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 (120) hide show
  1. package/dist/dtos/commandTriggerCheckResult.d.ts +1 -0
  2. package/dist/dtos/commandTriggerCheckResult.d.ts.map +1 -1
  3. package/dist/dtos/commandTriggerCheckResult.js +3 -0
  4. package/dist/dtos/incomingMessage.d.ts +2 -1
  5. package/dist/dtos/incomingMessage.d.ts.map +1 -1
  6. package/dist/dtos/incomingMessage.js +5 -1
  7. package/dist/dtos/incomingQuery.d.ts +1 -0
  8. package/dist/dtos/incomingQuery.d.ts.map +1 -1
  9. package/dist/dtos/incomingQuery.js +1 -0
  10. package/dist/dtos/replyInfo.d.ts.map +1 -0
  11. package/dist/dtos/responses/delay.d.ts +1 -1
  12. package/dist/dtos/responses/delay.d.ts.map +1 -1
  13. package/dist/dtos/responses/imageMessage.d.ts +8 -7
  14. package/dist/dtos/responses/imageMessage.d.ts.map +1 -1
  15. package/dist/dtos/responses/imageMessage.js +1 -0
  16. package/dist/dtos/responses/reaction.d.ts +3 -4
  17. package/dist/dtos/responses/reaction.d.ts.map +1 -1
  18. package/dist/dtos/responses/textMessage.d.ts +8 -7
  19. package/dist/dtos/responses/textMessage.d.ts.map +1 -1
  20. package/dist/dtos/responses/textMessage.js +1 -0
  21. package/dist/dtos/responses/unpin.d.ts +1 -1
  22. package/dist/dtos/responses/unpin.d.ts.map +1 -1
  23. package/dist/dtos/responses/videoMessage.d.ts +8 -7
  24. package/dist/dtos/responses/videoMessage.d.ts.map +1 -1
  25. package/dist/dtos/responses/videoMessage.js +1 -0
  26. package/dist/entities/actions/commandAction.d.ts +1 -1
  27. package/dist/entities/actions/commandAction.d.ts.map +1 -1
  28. package/dist/entities/actions/commandAction.js +6 -11
  29. package/dist/entities/actions/inlineQueryAction.d.ts +2 -3
  30. package/dist/entities/actions/inlineQueryAction.d.ts.map +1 -1
  31. package/dist/entities/actions/replyCaptureAction.d.ts +15 -0
  32. package/dist/entities/actions/replyCaptureAction.d.ts.map +1 -0
  33. package/dist/entities/actions/replyCaptureAction.js +56 -0
  34. package/dist/entities/actions/scheduledAction.d.ts +1 -1
  35. package/dist/entities/actions/scheduledAction.d.ts.map +1 -1
  36. package/dist/entities/botInstance.d.ts +7 -36
  37. package/dist/entities/botInstance.d.ts.map +1 -1
  38. package/dist/entities/botInstance.js +5 -162
  39. package/dist/entities/context/chatContext.d.ts +7 -5
  40. package/dist/entities/context/chatContext.d.ts.map +1 -1
  41. package/dist/entities/context/chatContext.js +21 -3
  42. package/dist/entities/context/inlineQueryContext.d.ts +7 -3
  43. package/dist/entities/context/inlineQueryContext.d.ts.map +1 -1
  44. package/dist/entities/context/inlineQueryContext.js +0 -11
  45. package/dist/entities/context/messageContext.d.ts +6 -6
  46. package/dist/entities/context/messageContext.d.ts.map +1 -1
  47. package/dist/entities/context/messageContext.js +10 -4
  48. package/dist/entities/context/replyContext.d.ts +113 -0
  49. package/dist/entities/context/replyContext.d.ts.map +1 -0
  50. package/dist/entities/context/replyContext.js +108 -0
  51. package/dist/main.d.ts.map +1 -1
  52. package/dist/main.js +5 -9
  53. package/dist/services/actionProcessingService.d.ts +25 -0
  54. package/dist/services/actionProcessingService.d.ts.map +1 -0
  55. package/dist/services/actionProcessingService.js +40 -0
  56. package/dist/services/actionProcessors/commandActionProcessor.d.ts +28 -0
  57. package/dist/services/actionProcessors/commandActionProcessor.d.ts.map +1 -0
  58. package/dist/services/actionProcessors/commandActionProcessor.js +116 -0
  59. package/dist/services/actionProcessors/inlineQueryActionProcessor.d.ts +20 -0
  60. package/dist/services/actionProcessors/inlineQueryActionProcessor.d.ts.map +1 -0
  61. package/dist/services/actionProcessors/inlineQueryActionProcessor.js +74 -0
  62. package/dist/services/actionProcessors/scheduledActionProcessor.d.ts +21 -0
  63. package/dist/services/actionProcessors/scheduledActionProcessor.d.ts.map +1 -0
  64. package/dist/services/actionProcessors/scheduledActionProcessor.js +69 -0
  65. package/dist/services/jsonFileStorage.d.ts +1 -1
  66. package/dist/services/jsonFileStorage.d.ts.map +1 -1
  67. package/dist/services/telegramApi.d.ts +5 -1
  68. package/dist/services/telegramApi.d.ts.map +1 -1
  69. package/dist/services/telegramApi.js +19 -23
  70. package/dist/types/{actionWithState.d.ts → action.d.ts} +5 -3
  71. package/dist/types/action.d.ts.map +1 -0
  72. package/dist/types/capture.d.ts +14 -0
  73. package/dist/types/capture.d.ts.map +1 -0
  74. package/dist/types/response.d.ts +9 -6
  75. package/dist/types/response.d.ts.map +1 -1
  76. package/dist/types/storage.d.ts +1 -1
  77. package/dist/types/storage.d.ts.map +1 -1
  78. package/dtos/commandTriggerCheckResult.ts +3 -0
  79. package/dtos/incomingMessage.ts +7 -2
  80. package/dtos/incomingQuery.ts +2 -0
  81. package/dtos/responses/delay.ts +1 -1
  82. package/dtos/responses/imageMessage.ts +11 -7
  83. package/dtos/responses/reaction.ts +3 -4
  84. package/dtos/responses/textMessage.ts +11 -7
  85. package/dtos/responses/unpin.ts +1 -1
  86. package/dtos/responses/videoMessage.ts +11 -7
  87. package/entities/actions/commandAction.ts +11 -18
  88. package/entities/actions/inlineQueryAction.ts +2 -3
  89. package/entities/actions/replyCaptureAction.ts +104 -0
  90. package/entities/actions/scheduledAction.ts +1 -1
  91. package/entities/botInstance.ts +19 -347
  92. package/entities/context/chatContext.ts +59 -29
  93. package/entities/context/inlineQueryContext.ts +7 -22
  94. package/entities/context/messageContext.ts +36 -29
  95. package/entities/context/replyContext.ts +225 -0
  96. package/main.ts +11 -10
  97. package/package.json +1 -1
  98. package/services/actionProcessingService.ts +123 -0
  99. package/services/actionProcessors/commandActionProcessor.ts +242 -0
  100. package/services/actionProcessors/inlineQueryActionProcessor.ts +176 -0
  101. package/services/actionProcessors/scheduledActionProcessor.ts +145 -0
  102. package/services/jsonFileStorage.ts +1 -1
  103. package/services/telegramApi.ts +45 -31
  104. package/types/{statefulAction.ts → action.ts} +6 -2
  105. package/types/capture.ts +21 -0
  106. package/types/response.ts +10 -6
  107. package/types/storage.ts +1 -1
  108. package/dist/types/actionWithState.d.ts.map +0 -1
  109. package/dist/types/replyInfo.d.ts.map +0 -1
  110. package/dist/types/statefulAction.d.ts +0 -9
  111. package/dist/types/statefulAction.d.ts.map +0 -1
  112. package/dist/types/statelessAction.d.ts +0 -5
  113. package/dist/types/statelessAction.d.ts.map +0 -1
  114. package/dist/types/statelessAction.js +0 -2
  115. package/types/statelessAction.ts +0 -5
  116. /package/dist/{types → dtos}/replyInfo.d.ts +0 -0
  117. /package/dist/{types → dtos}/replyInfo.js +0 -0
  118. /package/dist/types/{actionWithState.js → action.js} +0 -0
  119. /package/dist/types/{statefulAction.js → capture.js} +0 -0
  120. /package/{types → dtos}/replyInfo.ts +0 -0
@@ -0,0 +1,242 @@
1
+ import { Telegraf } from 'telegraf';
2
+ import { IncomingMessage } from '../../dtos/incomingMessage';
3
+ import { CommandAction } from '../../entities/actions/commandAction';
4
+ import { ReplyCaptureAction } from '../../entities/actions/replyCaptureAction';
5
+ import { MessageContext } from '../../entities/context/messageContext';
6
+ import { ReplyContext } from '../../entities/context/replyContext';
7
+ import { IActionWithState } from '../../types/action';
8
+ import { IActionState } from '../../types/actionState';
9
+ import { ILogger } from '../../types/logger';
10
+ import {
11
+ INTERNAL_MESSAGE_TYPE_PREFIX,
12
+ MessageType
13
+ } from '../../types/messageTypes';
14
+ import { IScheduler } from '../../types/scheduler';
15
+ import { IStorageClient } from '../../types/storage';
16
+ import { TelegramApiService } from '../telegramApi';
17
+ import { IReplyCapture } from '../../types/capture';
18
+ import { TraceId } from '../../types/trace';
19
+ import { ChatInfo } from '../../dtos/chatInfo';
20
+
21
+ export class CommandActionProcessor {
22
+ private readonly storage: IStorageClient;
23
+ private readonly scheduler: IScheduler;
24
+ private readonly logger: ILogger;
25
+
26
+ private readonly botName: string;
27
+ private readonly replyCaptures: ReplyCaptureAction<IActionState>[];
28
+
29
+ private api!: TelegramApiService;
30
+ private telegraf!: Telegraf;
31
+ private commands!: CommandAction<IActionState>[];
32
+
33
+ private permanentTriggersToBeProcessed!: Set<string>;
34
+
35
+ constructor(
36
+ botName: string,
37
+ storage: IStorageClient,
38
+ scheduler: IScheduler,
39
+ logger: ILogger
40
+ ) {
41
+ this.storage = storage;
42
+ this.scheduler = scheduler;
43
+ this.logger = logger;
44
+
45
+ this.botName = botName;
46
+ this.replyCaptures = [];
47
+ }
48
+
49
+ initialize(
50
+ api: TelegramApiService,
51
+ telegraf: Telegraf,
52
+ commands: CommandAction<IActionState>[],
53
+ verboseLoggingForIncomingMessage: boolean
54
+ ) {
55
+ this.api = api;
56
+ this.telegraf = telegraf;
57
+ this.commands = commands;
58
+
59
+ if (this.commands.length > 0) {
60
+ this.permanentTriggersToBeProcessed = new Set(
61
+ this.commands
62
+ .flatMap((x) => x.triggers)
63
+ .map((x) =>
64
+ typeof x == 'string'
65
+ ? x.startsWith(INTERNAL_MESSAGE_TYPE_PREFIX)
66
+ ? x
67
+ : MessageType.Text
68
+ : MessageType.Text
69
+ )
70
+ );
71
+
72
+ this.telegraf.on('message', async (ctx) => {
73
+ const msg = new IncomingMessage(
74
+ ctx.update.message,
75
+ this.botName
76
+ );
77
+
78
+ if (verboseLoggingForIncomingMessage) {
79
+ this.logger.logObjectWithTraceId(
80
+ this.botName,
81
+ msg.traceId,
82
+ msg.chatInfo.name,
83
+ ctx.update.message
84
+ );
85
+ } else {
86
+ this.logger.logWithTraceId(
87
+ this.botName,
88
+ msg.traceId,
89
+ msg.chatInfo.name,
90
+ `${msg.from?.first_name ?? 'Unknown'} (${
91
+ msg.from?.id ?? 'Unknown'
92
+ }): ${msg.text || `<non-text message: ${msg.type}>`}`
93
+ );
94
+ }
95
+
96
+ if (
97
+ this.permanentTriggersToBeProcessed.has(msg.type) ||
98
+ this.replyCaptures.find(
99
+ (x) => x.parentMessageId == msg.replyToMessageId
100
+ )
101
+ )
102
+ await this.processMessage(msg);
103
+ });
104
+ }
105
+ }
106
+
107
+ captureRegistrationCallback(
108
+ capture: IReplyCapture,
109
+ parentMessageId: number,
110
+ chatInfo: ChatInfo,
111
+ traceId: TraceId
112
+ ) {
113
+ const replyAction = new ReplyCaptureAction(
114
+ parentMessageId,
115
+ capture.action,
116
+ capture.handler,
117
+ capture.trigger,
118
+ capture.abortController
119
+ );
120
+
121
+ this.logger.logWithTraceId(
122
+ this.botName,
123
+ traceId,
124
+ chatInfo.name,
125
+ `Starting capturing replies to message ${parentMessageId} with action ${replyAction.key}`
126
+ );
127
+
128
+ this.replyCaptures.push(replyAction);
129
+
130
+ capture.abortController.signal.addEventListener('abort', () => {
131
+ const index = this.replyCaptures.indexOf(replyAction);
132
+ this.replyCaptures.splice(index, 1);
133
+
134
+ this.logger.logWithTraceId(
135
+ this.botName,
136
+ traceId,
137
+ chatInfo.name,
138
+ `Stopping capturing replies to message ${parentMessageId} with action ${replyAction.key}`
139
+ );
140
+ });
141
+ }
142
+
143
+ private async processMessage(msg: IncomingMessage) {
144
+ const ctx = new MessageContext<IActionState>(
145
+ this.storage,
146
+ this.logger,
147
+ this.scheduler
148
+ );
149
+
150
+ for (const commandAction of this.commands) {
151
+ this.initializeMessageContext(ctx, commandAction, msg);
152
+
153
+ try {
154
+ const responses = await commandAction.exec(ctx);
155
+ this.api.enqueueBatchedResponses(responses);
156
+ ctx.isInitialized = false;
157
+ } catch (error) {
158
+ this.logger.errorWithTraceId(
159
+ ctx.botName,
160
+ ctx.traceId,
161
+ ctx.chatInfo.name,
162
+ error,
163
+ ctx
164
+ );
165
+ }
166
+ }
167
+
168
+ const replyCtx = new ReplyContext<IActionState>(
169
+ this.storage,
170
+ this.logger,
171
+ this.scheduler
172
+ );
173
+
174
+ for (const replyAction of this.replyCaptures) {
175
+ this.initializeReplyCaptureContext(replyCtx, replyAction, msg);
176
+
177
+ try {
178
+ const responses = await replyAction.exec(replyCtx);
179
+ this.api.enqueueBatchedResponses(responses);
180
+ replyCtx.isInitialized = false;
181
+ } catch (error) {
182
+ this.logger.errorWithTraceId(
183
+ replyCtx.botName,
184
+ replyCtx.traceId,
185
+ replyCtx.chatInfo.name,
186
+ error,
187
+ replyCtx
188
+ );
189
+ }
190
+ }
191
+
192
+ this.api.flushResponses();
193
+ }
194
+
195
+ private initializeReplyCaptureContext(
196
+ ctx: ReplyContext<IActionState>,
197
+ action: ReplyCaptureAction<IActionState>,
198
+ message: IncomingMessage
199
+ ) {
200
+ ctx.replyMessageId = message.replyToMessageId;
201
+ ctx.messageId = message.messageId;
202
+ ctx.messageText = message.text ?? '';
203
+ ctx.messageType = message.type;
204
+ ctx.fromUserId = message.from?.id;
205
+ ctx.fromUserName =
206
+ (message.from?.first_name ?? 'Unknown user') +
207
+ (message.from?.last_name ? ` ${message.from.last_name}` : '');
208
+ ctx.messageUpdateObject = message.updateObject;
209
+ ctx.botName = this.botName;
210
+ ctx.action = action;
211
+ ctx.chatInfo = message.chatInfo;
212
+ ctx.traceId = message.traceId;
213
+
214
+ ctx.isInitialized = true;
215
+ ctx.matchResults = [];
216
+ }
217
+
218
+ private initializeMessageContext<TActionState extends IActionState>(
219
+ ctx: MessageContext<IActionState>,
220
+ action: IActionWithState<TActionState>,
221
+ message: IncomingMessage
222
+ ) {
223
+ ctx.messageId = message.messageId;
224
+ ctx.messageText = message.text ?? '';
225
+ ctx.messageType = message.type;
226
+ ctx.fromUserId = message.from?.id;
227
+ ctx.fromUserName =
228
+ (message.from?.first_name ?? 'Unknown user') +
229
+ (message.from?.last_name ? ` ${message.from.last_name}` : '');
230
+ ctx.messageUpdateObject = message.updateObject;
231
+
232
+ ctx.matchResults = [];
233
+ ctx.startCooldown = true;
234
+
235
+ ctx.responses = [];
236
+ ctx.isInitialized = true;
237
+ ctx.botName = this.botName;
238
+ ctx.action = action;
239
+ ctx.chatInfo = message.chatInfo;
240
+ ctx.traceId = message.traceId;
241
+ }
242
+ }
@@ -0,0 +1,176 @@
1
+ import { Telegraf } from 'telegraf';
2
+ import { IncomingInlineQuery } from '../../dtos/incomingQuery';
3
+ import { InlineQueryAction } from '../../entities/actions/inlineQueryAction';
4
+ import { InlineQueryContext } from '../../entities/context/inlineQueryContext';
5
+ import { createTrace } from '../../helpers/traceFactory';
6
+ import { ILogger } from '../../types/logger';
7
+ import { IScheduler } from '../../types/scheduler';
8
+ import { IStorageClient } from '../../types/storage';
9
+ import { Milliseconds } from '../../types/timeValues';
10
+ import { TraceId } from '../../types/trace';
11
+ import { TelegramApiService } from '../telegramApi';
12
+
13
+ export class InlineQueryActionProcessor {
14
+ private readonly storage: IStorageClient;
15
+ private readonly scheduler: IScheduler;
16
+ private readonly logger: ILogger;
17
+
18
+ private readonly botName: string;
19
+
20
+ private api!: TelegramApiService;
21
+ private telegraf!: Telegraf;
22
+ private inlineQueries!: InlineQueryAction[];
23
+
24
+ constructor(
25
+ botName: string,
26
+ storage: IStorageClient,
27
+ scheduler: IScheduler,
28
+ logger: ILogger
29
+ ) {
30
+ this.storage = storage;
31
+ this.scheduler = scheduler;
32
+ this.logger = logger;
33
+
34
+ this.botName = botName;
35
+ }
36
+
37
+ initialize(
38
+ api: TelegramApiService,
39
+ telegraf: Telegraf,
40
+ inlineQueries: InlineQueryAction[],
41
+ period: Milliseconds
42
+ ) {
43
+ this.api = api;
44
+ this.telegraf = telegraf;
45
+ this.inlineQueries = inlineQueries;
46
+
47
+ let pendingInlineQueries: IncomingInlineQuery[] = [];
48
+
49
+ const queriesInProcessing = new Map<number, IncomingInlineQuery>();
50
+
51
+ if (this.inlineQueries.length > 0) {
52
+ this.telegraf.on('inline_query', async (ctx) => {
53
+ const query = new IncomingInlineQuery(
54
+ ctx.inlineQuery.id,
55
+ ctx.inlineQuery.query,
56
+ ctx.inlineQuery.from.id,
57
+ createTrace('InlineQuery', this.botName, ctx.inlineQuery.id)
58
+ );
59
+
60
+ this.logger.logWithTraceId(
61
+ this.botName,
62
+ query.traceId,
63
+ 'Query',
64
+ `${ctx.inlineQuery.from.username} (${ctx.inlineQuery.from.id}): Query for ${ctx.inlineQuery.query}`
65
+ );
66
+
67
+ const queryBeingProcessed = queriesInProcessing.get(
68
+ query.userId
69
+ );
70
+ if (queryBeingProcessed) {
71
+ this.logger.logWithTraceId(
72
+ this.botName,
73
+ query.traceId,
74
+ 'Query',
75
+ `Aborting query ${queryBeingProcessed.queryId} (${queryBeingProcessed.query}): new query recieved from ${query.userId}`
76
+ );
77
+
78
+ queryBeingProcessed.abortController.abort();
79
+ queriesInProcessing.delete(query.userId);
80
+ }
81
+
82
+ pendingInlineQueries = pendingInlineQueries.filter(
83
+ (q) => q.userId != query.userId
84
+ );
85
+
86
+ pendingInlineQueries.push(query);
87
+ });
88
+
89
+ this.scheduler.createTask(
90
+ 'InlineQueryProcessing',
91
+ async () => {
92
+ const ctx = new InlineQueryContext(
93
+ this.storage,
94
+ this.logger,
95
+ this.scheduler
96
+ );
97
+
98
+ const queriesToProcess = [...pendingInlineQueries];
99
+ pendingInlineQueries = [];
100
+
101
+ for (const inlineQuery of queriesToProcess) {
102
+ queriesInProcessing.set(
103
+ inlineQuery.userId,
104
+ inlineQuery
105
+ );
106
+
107
+ for (const inlineQueryAction of this.inlineQueries) {
108
+ this.initializeInlineQueryContext(
109
+ ctx,
110
+ inlineQuery.query,
111
+ inlineQuery.queryId,
112
+ inlineQueryAction,
113
+ inlineQuery.abortController.signal,
114
+ inlineQuery.traceId
115
+ );
116
+
117
+ try {
118
+ const responses = await inlineQueryAction.exec(
119
+ ctx
120
+ );
121
+ this.api.enqueueBatchedResponses(responses);
122
+ ctx.isInitialized = false;
123
+ } catch (err) {
124
+ const error = err as Error;
125
+
126
+ if (error.name == 'AbortError') {
127
+ this.logger.logWithTraceId(
128
+ this.botName,
129
+ inlineQuery.traceId,
130
+ 'Query',
131
+ `Aborting query ${inlineQuery.queryId} (${inlineQuery.query}) successful.`
132
+ );
133
+ } else {
134
+ this.logger.errorWithTraceId(
135
+ ctx.botName,
136
+ ctx.traceId,
137
+ 'Unknown',
138
+ error,
139
+ ctx
140
+ );
141
+ }
142
+ }
143
+ }
144
+
145
+ queriesInProcessing.delete(inlineQuery.userId);
146
+ }
147
+
148
+ this.api.flushResponses();
149
+ },
150
+ period,
151
+ false,
152
+ this.botName
153
+ );
154
+ }
155
+ }
156
+
157
+ private initializeInlineQueryContext(
158
+ ctx: InlineQueryContext,
159
+ queryText: string,
160
+ queryId: string,
161
+ action: InlineQueryAction,
162
+ abortSignal: AbortSignal,
163
+ traceId: TraceId
164
+ ) {
165
+ ctx.queryText = queryText;
166
+ ctx.queryId = queryId;
167
+ ctx.botName = this.botName;
168
+ ctx.action = action;
169
+ ctx.traceId = traceId;
170
+ ctx.abortSignal = abortSignal;
171
+
172
+ ctx.isInitialized = true;
173
+ ctx.queryResults = [];
174
+ ctx.matchResults = [];
175
+ }
176
+ }
@@ -0,0 +1,145 @@
1
+ import moment from 'moment';
2
+ import { ChatInfo } from '../../dtos/chatInfo';
3
+ import { ScheduledAction } from '../../entities/actions/scheduledAction';
4
+ import { ChatContext } from '../../entities/context/chatContext';
5
+ import { secondsToMilliseconds } from '../../helpers/timeConvertions';
6
+ import { createTrace } from '../../helpers/traceFactory';
7
+ import { IActionWithState } from '../../types/action';
8
+ import { IActionState } from '../../types/actionState';
9
+ import { ILogger } from '../../types/logger';
10
+ import { IScheduler } from '../../types/scheduler';
11
+ import { IStorageClient } from '../../types/storage';
12
+ import { Seconds, Milliseconds } from '../../types/timeValues';
13
+ import { TraceId } from '../../types/trace';
14
+ import { TelegramApiService } from '../telegramApi';
15
+
16
+ export class ScheduledActionProcessor {
17
+ private readonly storage: IStorageClient;
18
+ private readonly scheduler: IScheduler;
19
+ private readonly logger: ILogger;
20
+
21
+ private readonly botName: string;
22
+ private readonly chats: Record<string, number>;
23
+
24
+ private api!: TelegramApiService;
25
+ private scheduled!: ScheduledAction<IActionState>[];
26
+
27
+ constructor(
28
+ botName: string,
29
+ chats: Record<string, number>,
30
+ storage: IStorageClient,
31
+ scheduler: IScheduler,
32
+ logger: ILogger
33
+ ) {
34
+ this.storage = storage;
35
+ this.scheduler = scheduler;
36
+ this.logger = logger;
37
+
38
+ this.botName = botName;
39
+ this.chats = chats;
40
+ }
41
+
42
+ initialize(
43
+ api: TelegramApiService,
44
+ scheduled: ScheduledAction<IActionState>[],
45
+ period: Seconds
46
+ ) {
47
+ this.api = api;
48
+ this.scheduled = scheduled;
49
+
50
+ if (this.scheduled.length > 0) {
51
+ const now = moment();
52
+
53
+ if (now.minute() == 0 && now.second() == 0) {
54
+ this.scheduler.createTask(
55
+ 'ScheduledProcessing',
56
+ async () => {
57
+ await this.runScheduled();
58
+ },
59
+ secondsToMilliseconds(period),
60
+ true,
61
+ this.botName
62
+ );
63
+
64
+ return;
65
+ }
66
+
67
+ let nextExecutionTime = now.clone().startOf('hour');
68
+ if (now.minute() > 0 || now.second() > 0) {
69
+ nextExecutionTime = nextExecutionTime.add(1, 'hour');
70
+ }
71
+
72
+ const delay = nextExecutionTime.diff(now);
73
+
74
+ this.scheduler.createOnetimeTask(
75
+ 'ScheduledProcessing_OneTime',
76
+ async () => {
77
+ this.scheduler.createTask(
78
+ 'ScheduledProcessing',
79
+ async () => {
80
+ await this.runScheduled();
81
+ },
82
+ secondsToMilliseconds(period),
83
+ true,
84
+ this.botName
85
+ );
86
+ },
87
+ delay as Milliseconds,
88
+ this.botName
89
+ );
90
+ }
91
+ }
92
+
93
+ private async runScheduled() {
94
+ const ctx = new ChatContext<IActionState>(
95
+ this.storage,
96
+ this.logger,
97
+ this.scheduler
98
+ );
99
+
100
+ for (const [chatName, chatId] of Object.entries(this.chats)) {
101
+ for (const scheduledAction of this.scheduled) {
102
+ this.initializeChatContext(
103
+ ctx,
104
+ scheduledAction,
105
+ new ChatInfo(chatId, chatName),
106
+ createTrace(
107
+ scheduledAction,
108
+ this.botName,
109
+ `${scheduledAction.key}-${chatId}`
110
+ )
111
+ );
112
+
113
+ try {
114
+ const responses = await scheduledAction.exec(ctx);
115
+ this.api.enqueueBatchedResponses(responses);
116
+ ctx.isInitialized = false;
117
+ } catch (error) {
118
+ this.logger.errorWithTraceId(
119
+ ctx.botName,
120
+ ctx.traceId,
121
+ chatName,
122
+ error,
123
+ ctx
124
+ );
125
+ }
126
+ }
127
+ }
128
+
129
+ this.api.flushResponses();
130
+ }
131
+
132
+ private initializeChatContext<TActionState extends IActionState>(
133
+ ctx: ChatContext<IActionState>,
134
+ action: IActionWithState<TActionState>,
135
+ chatInfo: ChatInfo,
136
+ traceId: TraceId
137
+ ) {
138
+ ctx.responses = [];
139
+ ctx.isInitialized = true;
140
+ ctx.botName = this.botName;
141
+ ctx.action = action;
142
+ ctx.chatInfo = chatInfo;
143
+ ctx.traceId = traceId;
144
+ }
145
+ }
@@ -3,7 +3,7 @@ import { readFile, writeFile } from 'fs/promises';
3
3
  import { Sema as Semaphore } from 'async-sema';
4
4
  import { IStorageClient } from '../types/storage';
5
5
  import { IActionState } from '../types/actionState';
6
- import { IActionWithState, ActionKey } from '../types/statefulAction';
6
+ import { IActionWithState, ActionKey } from '../types/action';
7
7
 
8
8
  export class JsonFileStorage implements IStorageClient {
9
9
  private readonly locks = new Map<ActionKey, Semaphore>();