chz-telegram-bot 0.7.13 → 0.7.15

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 (98) hide show
  1. package/dist/dtos/propertyProviderSets.d.ts +0 -1
  2. package/dist/dtos/propertyProviderSets.d.ts.map +1 -1
  3. package/dist/entities/actions/commandAction.d.ts +1 -1
  4. package/dist/entities/actions/commandAction.d.ts.map +1 -1
  5. package/dist/entities/actions/commandAction.js +2 -2
  6. package/dist/helpers/builders/commandActionBuilder.d.ts.map +1 -1
  7. package/dist/helpers/builders/commandActionBuilder.js +2 -3
  8. package/package.json +4 -1
  9. package/eslint.config.ts +0 -62
  10. package/src/builtin/helpAction.ts +0 -17
  11. package/src/dtos/chatHistoryMessage.ts +0 -22
  12. package/src/dtos/chatInfo.ts +0 -12
  13. package/src/dtos/commandTriggerCheckResult.ts +0 -40
  14. package/src/dtos/cooldownInfo.ts +0 -10
  15. package/src/dtos/incomingMessage.ts +0 -71
  16. package/src/dtos/incomingQuery.ts +0 -14
  17. package/src/dtos/messageInfo.ts +0 -15
  18. package/src/dtos/propertyProviderSets.ts +0 -21
  19. package/src/dtos/replyInfo.ts +0 -9
  20. package/src/dtos/responses/delay.ts +0 -28
  21. package/src/dtos/responses/imageMessage.ts +0 -41
  22. package/src/dtos/responses/inlineQueryResponse.ts +0 -26
  23. package/src/dtos/responses/reaction.ts +0 -30
  24. package/src/dtos/responses/textMessage.ts +0 -44
  25. package/src/dtos/responses/unpin.ts +0 -27
  26. package/src/dtos/responses/videoMessage.ts +0 -41
  27. package/src/dtos/userInfo.ts +0 -8
  28. package/src/entities/actions/commandAction.ts +0 -275
  29. package/src/entities/actions/inlineQueryAction.ts +0 -83
  30. package/src/entities/actions/replyCaptureAction.ts +0 -110
  31. package/src/entities/actions/scheduledAction.ts +0 -182
  32. package/src/entities/botInstance.ts +0 -92
  33. package/src/entities/cachedStateFactory.ts +0 -14
  34. package/src/entities/context/baseContext.ts +0 -111
  35. package/src/entities/context/chatContext.ts +0 -135
  36. package/src/entities/context/inlineQueryContext.ts +0 -63
  37. package/src/entities/context/messageContext.ts +0 -250
  38. package/src/entities/context/replyContext.ts +0 -260
  39. package/src/entities/states/actionStateBase.ts +0 -6
  40. package/src/entities/taskRecord.ts +0 -11
  41. package/src/helpers/builders/commandActionBuilder.ts +0 -214
  42. package/src/helpers/builders/inlineQueryActionBuilder.ts +0 -71
  43. package/src/helpers/builders/scheduledActionBuilder.ts +0 -143
  44. package/src/helpers/mapUtils.ts +0 -28
  45. package/src/helpers/noop.ts +0 -20
  46. package/src/helpers/objectFromEntries.ts +0 -7
  47. package/src/helpers/timeConvertions.ts +0 -13
  48. package/src/helpers/toArray.ts +0 -3
  49. package/src/helpers/traceFactory.ts +0 -11
  50. package/src/index.ts +0 -33
  51. package/src/main.ts +0 -76
  52. package/src/services/actionProcessingService.ts +0 -125
  53. package/src/services/actionProcessors/baseProcessor.ts +0 -67
  54. package/src/services/actionProcessors/commandActionProcessor.ts +0 -231
  55. package/src/services/actionProcessors/inlineQueryActionProcessor.ts +0 -165
  56. package/src/services/actionProcessors/scheduledActionProcessor.ts +0 -136
  57. package/src/services/jsonFileStorage.ts +0 -181
  58. package/src/services/nodeTimeoutScheduler.ts +0 -79
  59. package/src/services/responseProcessingQueue.ts +0 -57
  60. package/src/services/telegramApi.ts +0 -278
  61. package/src/types/action.ts +0 -15
  62. package/src/types/actionState.ts +0 -4
  63. package/src/types/cachedValueAccessor.ts +0 -1
  64. package/src/types/capture.ts +0 -33
  65. package/src/types/commandCondition.ts +0 -9
  66. package/src/types/commandTrigger.ts +0 -1
  67. package/src/types/events.ts +0 -286
  68. package/src/types/externalAliases.ts +0 -18
  69. package/src/types/handlers.ts +0 -26
  70. package/src/types/inputFile.ts +0 -4
  71. package/src/types/messageSendingOptions.ts +0 -10
  72. package/src/types/messageTypes.ts +0 -21
  73. package/src/types/propertyProvider.ts +0 -14
  74. package/src/types/response.ts +0 -51
  75. package/src/types/scheduler.ts +0 -20
  76. package/src/types/storage.ts +0 -23
  77. package/src/types/timeValues.ts +0 -33
  78. package/src/types/trace.ts +0 -5
  79. package/tests/dtos/commandTriggerCheckResult.test.ts +0 -301
  80. package/tests/entities/actions/inlineQueryAction.test.ts +0 -359
  81. package/tests/entities/actions/replyCaptureAction.test.ts +0 -501
  82. package/tests/entities/cachedStateFactory.test.ts +0 -98
  83. package/tests/entities/context/chatContext.test.ts +0 -606
  84. package/tests/entities/context/messageContext.test.ts +0 -370
  85. package/tests/entities/states/actionStateBase.test.ts +0 -138
  86. package/tests/entities/taskRecord.test.ts +0 -195
  87. package/tests/helpers/mapUtils.test.ts +0 -163
  88. package/tests/helpers/timeConvertions.test.ts +0 -129
  89. package/tests/services/actionProcessors/baseActionProcessor.test.ts +0 -359
  90. package/tests/services/actionProcessors/commandActionProcessor.test.ts +0 -268
  91. package/tests/services/actionProcessors/inlineQueryActionProcessor.test.ts +0 -616
  92. package/tests/services/actionProcessors/processorTestHelpers.ts +0 -147
  93. package/tests/services/actionProcessors/scheduledActionProcessor.test.ts +0 -153
  94. package/tests/services/jsonFileStorage.test.ts +0 -927
  95. package/tests/services/nodeTimeoutScheduler.test.ts +0 -421
  96. package/tests/services/responseProcessingQueue.test.ts +0 -388
  97. package/tsconfig.build.json +0 -8
  98. package/tsconfig.json +0 -118
@@ -1,8 +0,0 @@
1
- export class UserInfo {
2
- constructor(
3
- /** Id of a user that sent a message that triggered this action. */
4
- readonly id: number,
5
- /** Name of a user that sent a message that triggered this action. */
6
- readonly name: string
7
- ) {}
8
- }
@@ -1,275 +0,0 @@
1
- import moment from 'moment';
2
- import { CommandHandler } from '../../types/handlers';
3
- import { CommandCondition } from '../../types/commandCondition';
4
- import { secondsToMilliseconds } from '../../helpers/timeConvertions';
5
- import { toArray } from '../../helpers/toArray';
6
- import { IActionState } from '../../types/actionState';
7
- import { IActionWithState, ActionKey } from '../../types/action';
8
- import { CommandTriggerCheckResult } from '../../dtos/commandTriggerCheckResult';
9
- import { MessageContextInternal } from '../context/messageContext';
10
- import { CommandTrigger } from '../../types/commandTrigger';
11
- import { Noop } from '../../helpers/noop';
12
- import { MessageType } from '../../types/messageTypes';
13
- import { Sema as Semaphore } from 'async-sema';
14
- import { getOrSetIfNotExists } from '../../helpers/mapUtils';
15
- import { CooldownInfo } from '../../dtos/cooldownInfo';
16
- import { TextMessage } from '../../dtos/responses/textMessage';
17
- import { ReplyInfo } from '../../dtos/replyInfo';
18
- import { Seconds } from '../../types/timeValues';
19
- import { CommandActionPropertyProvider } from '../../types/propertyProvider';
20
- import { CommandActionProviders } from '../../dtos/propertyProviderSets';
21
- import { BotResponse } from '../../types/response';
22
- import { BotEventType } from '../../types/events';
23
-
24
- export class CommandAction<
25
- TActionState extends IActionState
26
- > implements IActionWithState<TActionState> {
27
- private readonly cooldownInfoProvider: CommandActionPropertyProvider<CooldownInfo>;
28
- private readonly isActiveProvider: CommandActionPropertyProvider<boolean>;
29
- private readonly chatsBlacklistProvider: CommandActionPropertyProvider<
30
- number[]
31
- >;
32
- private readonly chatsWhitelistProvider: CommandActionPropertyProvider<
33
- number[]
34
- >;
35
- private readonly usersWhitelistProvider: CommandActionPropertyProvider<
36
- number[]
37
- >;
38
-
39
- readonly key: ActionKey;
40
- readonly name: string;
41
- readonly ratelimitSemaphores = new Map<number, Semaphore>();
42
- readonly maxAllowedSimultaniousExecutions: number;
43
-
44
- readonly triggers: CommandTrigger[];
45
-
46
- readonly handler: CommandHandler<TActionState>;
47
- readonly condition: CommandCondition<TActionState>;
48
- readonly stateConstructor: () => TActionState;
49
- readonly readmeFactory: (botName: string) => string;
50
-
51
- private lastCustomCooldown: Seconds | undefined;
52
-
53
- constructor(
54
- trigger: CommandTrigger | CommandTrigger[],
55
- handler: CommandHandler<TActionState>,
56
- name: string,
57
- providers: CommandActionProviders,
58
- condition: CommandCondition<TActionState>,
59
- stateConstructor: () => TActionState,
60
- readmeFactory: (botName: string) => string
61
- ) {
62
- this.triggers = toArray(trigger);
63
- this.name = name;
64
-
65
- this.cooldownInfoProvider = providers.cooldownProvider;
66
- this.isActiveProvider = providers.isActiveProvider;
67
- this.chatsBlacklistProvider = providers.chatsBlacklistProvider;
68
- this.chatsWhitelistProvider = providers.chatsWhitelistProvider;
69
- this.usersWhitelistProvider = providers.usersWhitelistProvider;
70
-
71
- this.handler = handler;
72
- this.condition = condition;
73
- this.stateConstructor = stateConstructor;
74
- this.readmeFactory = readmeFactory;
75
-
76
- this.maxAllowedSimultaniousExecutions =
77
- providers.maxAllowedSimultaniousExecutions;
78
-
79
- this.key = `command:${this.name.replace('.', '-')}` as ActionKey;
80
- }
81
-
82
- async exec(
83
- ctx: MessageContextInternal<TActionState>
84
- ): Promise<BotResponse[]> {
85
- let lock: Semaphore | undefined;
86
- if (this.maxAllowedSimultaniousExecutions != 0) {
87
- lock = getOrSetIfNotExists(
88
- this.ratelimitSemaphores,
89
- ctx.chatInfo.id,
90
- new Semaphore(this.maxAllowedSimultaniousExecutions)
91
- );
92
-
93
- await lock.acquire();
94
- }
95
-
96
- try {
97
- const state = ctx.storage.getActionState<TActionState>(
98
- this,
99
- ctx.chatInfo.id
100
- );
101
-
102
- const { shouldExecute, matchResults, skipCooldown, reason } =
103
- this.triggers
104
- .map((x) => this.checkIfShouldBeExecuted(ctx, x, state))
105
- .reduce(
106
- (acc, curr) => acc.mergeWith(curr),
107
- CommandTriggerCheckResult.DoNotTrigger('Other')
108
- );
109
-
110
- if (!shouldExecute) {
111
- if (reason == 'OnCooldown') {
112
- const cooldownMessage =
113
- this.cooldownInfoProvider(ctx).message;
114
-
115
- return cooldownMessage
116
- ? [
117
- new TextMessage(
118
- cooldownMessage,
119
- ctx.chatInfo,
120
- ctx.observability.traceId,
121
- this,
122
- new ReplyInfo(ctx.messageInfo.id)
123
- )
124
- ]
125
- : Noop.NoResponse;
126
- }
127
-
128
- return Noop.NoResponse;
129
- }
130
-
131
- ctx.observability.eventEmitter.emit(
132
- BotEventType.commandActionExecuting,
133
- {
134
- action: this,
135
- ctx,
136
- state,
137
- traceId: ctx.observability.traceId
138
- }
139
- );
140
- ctx.matchResults = matchResults;
141
-
142
- await this.handler(ctx, state);
143
-
144
- if (skipCooldown) {
145
- ctx.startCooldown = false;
146
- }
147
-
148
- if (ctx.startCooldown) {
149
- this.lastCustomCooldown = ctx.customCooldown;
150
-
151
- state.lastExecutedDate = moment().valueOf();
152
- }
153
-
154
- await ctx.storage.saveActionExecutionResult(
155
- this,
156
- ctx.chatInfo.id,
157
- state
158
- );
159
-
160
- ctx.observability.eventEmitter.emit(
161
- BotEventType.commandActionExecuted,
162
- {
163
- action: this,
164
- ctx,
165
- state,
166
- traceId: ctx.observability.traceId
167
- }
168
- );
169
- return ctx.responses;
170
- } finally {
171
- lock?.release();
172
- }
173
- }
174
-
175
- private checkIfShouldBeExecuted(
176
- ctx: MessageContextInternal<TActionState>,
177
- trigger: CommandTrigger,
178
- state: TActionState
179
- ) {
180
- if (!this.isActiveProvider(ctx))
181
- return CommandTriggerCheckResult.DontTriggerAndSkipCooldown(
182
- 'ActionDisabled'
183
- );
184
-
185
- const chatsBlacklist = this.chatsBlacklistProvider(ctx);
186
- const chatsWhitelist = this.chatsWhitelistProvider(ctx);
187
-
188
- const isChatInBlacklist = chatsBlacklist.includes(ctx.chatInfo.id);
189
- const isChatInWhitelist =
190
- chatsWhitelist.length == 0 ||
191
- chatsWhitelist.includes(ctx.chatInfo.id);
192
-
193
- if (isChatInBlacklist || !isChatInWhitelist)
194
- return CommandTriggerCheckResult.DontTriggerAndSkipCooldown(
195
- 'ChatForbidden'
196
- );
197
-
198
- const triggerCheckResult = this.checkTrigger(ctx, trigger);
199
-
200
- if (!triggerCheckResult.shouldExecute) return triggerCheckResult;
201
-
202
- if (!ctx.userInfo.id)
203
- return CommandTriggerCheckResult.DontTriggerAndSkipCooldown(
204
- 'UserIdMissing'
205
- );
206
-
207
- const usersWhitelist = this.usersWhitelistProvider(ctx);
208
- const isUserAllowed =
209
- usersWhitelist.length == 0 ||
210
- usersWhitelist.includes(ctx.userInfo.id);
211
-
212
- if (!isUserAllowed)
213
- return CommandTriggerCheckResult.DontTriggerAndSkipCooldown(
214
- 'UserForbidden'
215
- );
216
-
217
- const lastExecutedDate = moment(state.lastExecutedDate);
218
- const cooldownInMilliseconds = secondsToMilliseconds(
219
- this.lastCustomCooldown ?? this.cooldownInfoProvider(ctx).cooldown
220
- );
221
- const onCooldown =
222
- moment().diff(lastExecutedDate) < cooldownInMilliseconds;
223
-
224
- if (onCooldown)
225
- return CommandTriggerCheckResult.DoNotTrigger('OnCooldown');
226
-
227
- const isCustomConditionMet = this.condition(ctx, state);
228
- if (!isCustomConditionMet)
229
- return CommandTriggerCheckResult.DontTriggerAndSkipCooldown(
230
- 'CustomConditionNotMet'
231
- );
232
-
233
- return triggerCheckResult;
234
- }
235
-
236
- private checkTrigger(
237
- ctx: MessageContextInternal<TActionState>,
238
- trigger: CommandTrigger
239
- ) {
240
- if (trigger == MessageType.Any || trigger == ctx.messageInfo.type)
241
- return CommandTriggerCheckResult.Trigger();
242
-
243
- if (typeof trigger == 'string')
244
- return ctx.messageInfo.text.toLowerCase() == trigger.toLowerCase()
245
- ? CommandTriggerCheckResult.Trigger()
246
- : CommandTriggerCheckResult.DoNotTrigger('TriggerNotSatisfied');
247
-
248
- const matchResults: RegExpExecArray[] = [];
249
-
250
- trigger.lastIndex = 0;
251
-
252
- const execResult = trigger.exec(ctx.messageInfo.text);
253
- if (execResult != null) {
254
- let regexMatchLimit = 100;
255
- matchResults.push(execResult);
256
-
257
- if (trigger.global) {
258
- while (regexMatchLimit > 0) {
259
- const nextResult = trigger.exec(ctx.messageInfo.text);
260
-
261
- if (nextResult == null) break;
262
-
263
- matchResults.push(nextResult);
264
- regexMatchLimit -= 1;
265
- }
266
- }
267
- }
268
-
269
- return new CommandTriggerCheckResult(
270
- matchResults.length > 0,
271
- matchResults,
272
- false
273
- );
274
- }
275
- }
@@ -1,83 +0,0 @@
1
- import { Noop } from '../../helpers/noop';
2
- import { ActionKey, IAction } from '../../types/action';
3
- import { InlineQueryContextInternal } from '../context/inlineQueryContext';
4
- import { InlineQueryHandler } from '../../types/handlers';
5
- import { InlineActionPropertyProvider } from '../../types/propertyProvider';
6
- import { BotEventType } from '../../types/events';
7
- import { InlineQueryResponse } from '../../dtos/responses/inlineQueryResponse';
8
-
9
- export class InlineQueryAction implements IAction {
10
- readonly key: ActionKey;
11
- readonly isActiveProvider: InlineActionPropertyProvider<boolean>;
12
- readonly handler: InlineQueryHandler;
13
- readonly name: string;
14
- readonly pattern: RegExp;
15
-
16
- constructor(
17
- handler: InlineQueryHandler,
18
- name: string,
19
- activeProvider: InlineActionPropertyProvider<boolean>,
20
- pattern: RegExp
21
- ) {
22
- this.handler = handler;
23
- this.name = name;
24
- this.isActiveProvider = activeProvider;
25
- this.pattern = pattern;
26
-
27
- this.key = `inline:${this.name.replace('.', '-')}` as ActionKey;
28
- }
29
-
30
- async exec(ctx: InlineQueryContextInternal) {
31
- if (!this.isActiveProvider(ctx)) return Noop.NoResponse;
32
-
33
- const matchResults: RegExpExecArray[] = [];
34
-
35
- this.pattern.lastIndex = 0;
36
-
37
- const execResult = this.pattern.exec(ctx.queryText);
38
- if (execResult != null) {
39
- let regexMatchLimit = 100;
40
- matchResults.push(execResult);
41
-
42
- if (this.pattern.global) {
43
- while (regexMatchLimit > 0) {
44
- const nextResult = this.pattern.exec(ctx.queryText);
45
-
46
- if (nextResult == null) break;
47
-
48
- matchResults.push(nextResult);
49
- regexMatchLimit -= 1;
50
- }
51
- }
52
- }
53
-
54
- if (matchResults.length == 0) return Noop.NoResponse;
55
-
56
- ctx.matchResults = matchResults;
57
-
58
- ctx.observability.eventEmitter.emit(
59
- BotEventType.inlineActionExecuting,
60
- {
61
- action: this,
62
- ctx,
63
- traceId: ctx.observability.traceId
64
- }
65
- );
66
-
67
- await this.handler(ctx);
68
-
69
- ctx.observability.eventEmitter.emit(BotEventType.inlineActionExecuted, {
70
- action: this,
71
- ctx,
72
- traceId: ctx.observability.traceId
73
- });
74
- return [
75
- new InlineQueryResponse(
76
- ctx.queryResults,
77
- ctx.queryId,
78
- ctx.observability.traceId,
79
- ctx.action
80
- )
81
- ];
82
- }
83
- }
@@ -1,110 +0,0 @@
1
- import { CommandTriggerCheckResult } from '../../dtos/commandTriggerCheckResult';
2
- import { Noop } from '../../helpers/noop';
3
- import { IActionState } from '../../types/actionState';
4
- import { CommandTrigger } from '../../types/commandTrigger';
5
- import { ActionKey, IAction } from '../../types/action';
6
- import { ReplyContextInternal } from '../context/replyContext';
7
- import { BotEventType } from '../../types/events';
8
-
9
- export class ReplyCaptureAction<
10
- TParentActionState extends IActionState
11
- > implements IAction {
12
- readonly parentMessageId: number;
13
- readonly key: ActionKey;
14
- readonly handler: (
15
- replyContext: ReplyContextInternal<TParentActionState>
16
- ) => Promise<void>;
17
- readonly triggers: CommandTrigger[];
18
- readonly abortController: AbortController;
19
-
20
- constructor(
21
- parentMessageId: number,
22
- parentAction: IAction,
23
- handler: (
24
- replyContext: ReplyContextInternal<TParentActionState>
25
- ) => Promise<void>,
26
- triggers: CommandTrigger[],
27
- abortController: AbortController
28
- ) {
29
- this.parentMessageId = parentMessageId;
30
- this.handler = handler;
31
- this.triggers = triggers;
32
- this.abortController = abortController;
33
-
34
- this.key = `capture:${parentAction.key}:${Math.random()
35
- .toString()
36
- .replace('.', '')}` as ActionKey;
37
- }
38
-
39
- async exec(ctx: ReplyContextInternal<TParentActionState>) {
40
- const { shouldExecute, matchResults } = this.triggers
41
- .map((x) => this.checkIfShouldBeExecuted(ctx, x))
42
- .reduce(
43
- (acc, curr) => acc.mergeWith(curr),
44
- CommandTriggerCheckResult.DoNotTrigger('Other')
45
- );
46
-
47
- if (!shouldExecute) return Noop.NoResponse;
48
-
49
- ctx.observability.eventEmitter.emit(BotEventType.replyActionExecuting, {
50
- action: this,
51
- ctx,
52
- traceId: ctx.observability.traceId
53
- });
54
- ctx.matchResults = matchResults;
55
-
56
- await this.handler(ctx);
57
-
58
- ctx.observability.eventEmitter.emit(BotEventType.replyActionExecuted, {
59
- action: this,
60
- ctx,
61
- traceId: ctx.observability.traceId
62
- });
63
- return ctx.responses;
64
- }
65
-
66
- private checkIfShouldBeExecuted(
67
- ctx: ReplyContextInternal<TParentActionState>,
68
- trigger: CommandTrigger
69
- ) {
70
- if (ctx.replyMessageId != this.parentMessageId)
71
- return CommandTriggerCheckResult.DoNotTrigger(
72
- 'TriggerNotSatisfied'
73
- );
74
-
75
- if (trigger == ctx.messageInfo.type)
76
- return CommandTriggerCheckResult.Trigger();
77
-
78
- if (typeof trigger == 'string')
79
- return ctx.messageInfo.text.toLowerCase() == trigger.toLowerCase()
80
- ? CommandTriggerCheckResult.Trigger()
81
- : CommandTriggerCheckResult.DoNotTrigger('TriggerNotSatisfied');
82
-
83
- const matchResults: RegExpExecArray[] = [];
84
-
85
- trigger.lastIndex = 0;
86
-
87
- const execResult = trigger.exec(ctx.messageInfo.text);
88
- if (execResult != null) {
89
- let regexMatchLimit = 100;
90
- matchResults.push(execResult);
91
-
92
- if (trigger.global) {
93
- while (regexMatchLimit > 0) {
94
- const nextResult = trigger.exec(ctx.messageInfo.text);
95
-
96
- if (nextResult == null) break;
97
-
98
- matchResults.push(nextResult);
99
- regexMatchLimit -= 1;
100
- }
101
- }
102
- }
103
-
104
- return new CommandTriggerCheckResult(
105
- matchResults.length > 0,
106
- matchResults,
107
- false
108
- );
109
- }
110
- }
@@ -1,182 +0,0 @@
1
- import moment from 'moment';
2
- import { Sema as Semaphore } from 'async-sema';
3
- import { ScheduledHandler } from '../../types/handlers';
4
- import { hoursToMilliseconds } from '../../helpers/timeConvertions';
5
- import { HoursOfDay } from '../../types/timeValues';
6
- import { IActionState } from '../../types/actionState';
7
- import { IActionWithState, ActionKey } from '../../types/action';
8
- import { CachedStateFactory } from '../cachedStateFactory';
9
- import { ChatContextInternal } from '../context/chatContext';
10
- import { Noop } from '../../helpers/noop';
11
- import { getOrSetIfNotExists, getOrThrow } from '../../helpers/mapUtils';
12
- import { ScheduledActionPropertyProvider } from '../../types/propertyProvider';
13
- import { ScheduledActionProviders } from '../../dtos/propertyProviderSets';
14
- import { BotEventType } from '../../types/events';
15
-
16
- export class ScheduledAction<
17
- TActionState extends IActionState
18
- > implements IActionWithState<TActionState> {
19
- static readonly locks = new Map<string, Semaphore>();
20
-
21
- readonly name: string;
22
- readonly key: ActionKey;
23
-
24
- private readonly timeinHoursProvider: ScheduledActionPropertyProvider<HoursOfDay>;
25
- private readonly activeProvider: ScheduledActionPropertyProvider<boolean>;
26
- private readonly chatsWhitelistProvider: ScheduledActionPropertyProvider<
27
- number[]
28
- >;
29
-
30
- readonly cachedState = new Map<string, unknown>();
31
- readonly stateConstructor: () => TActionState;
32
- readonly cachedStateFactories: Map<string, CachedStateFactory>;
33
- readonly handler: ScheduledHandler<TActionState>;
34
-
35
- constructor(
36
- name: string,
37
- handler: ScheduledHandler<TActionState>,
38
- providers: ScheduledActionProviders,
39
- cachedStateFactories: Map<string, CachedStateFactory>,
40
- stateConstructor: () => TActionState
41
- ) {
42
- this.name = name;
43
- this.key = `scheduled:${this.name.replace('.', '-')}` as ActionKey;
44
-
45
- this.timeinHoursProvider = providers.timeinHoursProvider;
46
- this.activeProvider = providers.isActiveProvider;
47
- this.chatsWhitelistProvider = providers.chatsWhitelistProvider;
48
-
49
- this.cachedStateFactories = cachedStateFactories;
50
- this.stateConstructor = stateConstructor;
51
- this.handler = handler;
52
- }
53
-
54
- async exec(ctx: ChatContextInternal<TActionState>) {
55
- if (
56
- !this.activeProvider(ctx) ||
57
- !this.chatsWhitelistProvider(ctx).includes(ctx.chatInfo.id)
58
- )
59
- return Noop.NoResponse;
60
-
61
- const state = ctx.storage.getActionState<TActionState>(
62
- this,
63
- ctx.chatInfo.id
64
- );
65
-
66
- const isAllowedToTrigger = this.checkIfShouldBeExecuted(state, ctx);
67
- if (!isAllowedToTrigger) return Noop.NoResponse;
68
-
69
- ctx.observability.eventEmitter.emit(
70
- BotEventType.scheduledActionExecuting,
71
- {
72
- action: this,
73
- ctx,
74
- state,
75
- traceId: ctx.observability.traceId
76
- }
77
- );
78
-
79
- await this.handler(
80
- ctx,
81
- <TResult>(key: string) => this.getCachedValue<TResult>(key, ctx),
82
- state
83
- );
84
-
85
- state.lastExecutedDate = moment().valueOf();
86
-
87
- await ctx.storage.saveActionExecutionResult(
88
- this,
89
- ctx.chatInfo.id,
90
- state
91
- );
92
-
93
- ctx.observability.eventEmitter.emit(
94
- BotEventType.scheduledActionExecuted,
95
- {
96
- action: this,
97
- ctx,
98
- state,
99
- traceId: ctx.observability.traceId
100
- }
101
- );
102
- return ctx.responses;
103
- }
104
-
105
- private async getCachedValue<TResult>(
106
- key: string,
107
- ctx: ChatContextInternal<TActionState>
108
- ): Promise<TResult> {
109
- const cachedItemFactory = getOrThrow(
110
- this.cachedStateFactories,
111
- key,
112
- `No shared cache was set up for the key [${key}] in action '${this.name}'`
113
- );
114
-
115
- const semaphoreKey = `${this.key}_cached:${key}`;
116
- const semaphore = getOrSetIfNotExists(
117
- ScheduledAction.locks,
118
- semaphoreKey,
119
- new Semaphore(1)
120
- );
121
-
122
- await semaphore.acquire();
123
-
124
- try {
125
- if (this.cachedState.has(key)) {
126
- return this.cachedState.get(key) as TResult;
127
- }
128
-
129
- ctx.observability.eventEmitter.emit(
130
- BotEventType.scheduledActionCacheValueCreating,
131
- {
132
- action: this,
133
- ctx,
134
- key,
135
- traceId: ctx.observability.traceId
136
- }
137
- );
138
- const value = await cachedItemFactory.getValue();
139
-
140
- this.cachedState.set(key, value);
141
-
142
- ctx.scheduler.createOnetimeTask(
143
- `Drop cached value [${this.name} : ${key}]`,
144
- () => this.cachedState.delete(key),
145
- hoursToMilliseconds(
146
- cachedItemFactory.invalidationTimeoutInHours
147
- ),
148
- ctx.botName
149
- );
150
-
151
- return value as TResult;
152
- } finally {
153
- ctx.observability.eventEmitter.emit(
154
- BotEventType.scheduledActionCacheValueReturned,
155
- {
156
- action: this,
157
- ctx,
158
- key,
159
- traceId: ctx.observability.traceId
160
- }
161
- );
162
- semaphore.release();
163
- }
164
- }
165
-
166
- private checkIfShouldBeExecuted(
167
- state: IActionState,
168
- ctx: ChatContextInternal<TActionState>
169
- ): boolean {
170
- const startOfToday = moment().startOf('day').valueOf();
171
- const lastExecutedDate = moment(state.lastExecutedDate);
172
- const currentTime = moment();
173
- const scheduledTime = moment()
174
- .startOf('day')
175
- .add(this.timeinHoursProvider(ctx), 'hours');
176
-
177
- const isAllowedToTrigger = currentTime.isSameOrAfter(scheduledTime);
178
- const hasTriggeredToday = lastExecutedDate.isAfter(startOfToday);
179
-
180
- return isAllowedToTrigger && !hasTriggeredToday;
181
- }
182
- }