chz-telegram-bot 0.3.10 → 0.3.12

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 (127) hide show
  1. package/builtin/helpAction.ts +1 -1
  2. package/dist/builtin/helpAction.js +1 -1
  3. package/dist/dtos/chatInfo.js +4 -0
  4. package/dist/dtos/commandTriggerCheckResult.js +3 -0
  5. package/dist/dtos/incomingMessage.js +9 -1
  6. package/dist/dtos/incomingQuery.js +5 -0
  7. package/dist/dtos/replyInfo.js +2 -0
  8. package/dist/dtos/responses/delay.js +6 -2
  9. package/dist/dtos/responses/imageMessage.js +10 -4
  10. package/dist/dtos/responses/inlineQueryResponse.js +6 -2
  11. package/dist/dtos/responses/reaction.js +7 -2
  12. package/dist/dtos/responses/textMessage.js +10 -3
  13. package/dist/dtos/responses/unpin.js +6 -2
  14. package/dist/dtos/responses/videoMessage.js +10 -4
  15. package/dist/entities/actions/commandAction.d.ts +4 -1
  16. package/dist/entities/actions/commandAction.d.ts.map +1 -1
  17. package/dist/entities/actions/commandAction.js +46 -17
  18. package/dist/entities/actions/inlineQueryAction.d.ts.map +1 -1
  19. package/dist/entities/actions/inlineQueryAction.js +8 -1
  20. package/dist/entities/actions/replyCaptureAction.d.ts.map +1 -1
  21. package/dist/entities/actions/replyCaptureAction.js +8 -1
  22. package/dist/entities/actions/scheduledAction.d.ts.map +1 -1
  23. package/dist/entities/actions/scheduledAction.js +13 -14
  24. package/dist/entities/botInstance.js +6 -1
  25. package/dist/entities/cachedStateFactory.js +2 -0
  26. package/dist/entities/context/baseContext.js +14 -2
  27. package/dist/entities/context/chatContext.d.ts +0 -3
  28. package/dist/entities/context/chatContext.d.ts.map +1 -1
  29. package/dist/entities/context/chatContext.js +38 -41
  30. package/dist/entities/context/inlineQueryContext.d.ts +0 -3
  31. package/dist/entities/context/inlineQueryContext.d.ts.map +1 -1
  32. package/dist/entities/context/inlineQueryContext.js +12 -6
  33. package/dist/entities/context/messageContext.d.ts +0 -3
  34. package/dist/entities/context/messageContext.d.ts.map +1 -1
  35. package/dist/entities/context/messageContext.js +77 -68
  36. package/dist/entities/context/replyContext.d.ts +0 -3
  37. package/dist/entities/context/replyContext.d.ts.map +1 -1
  38. package/dist/entities/context/replyContext.js +90 -65
  39. package/dist/entities/states/actionStateBase.js +2 -4
  40. package/dist/entities/taskRecord.js +3 -0
  41. package/dist/helpers/builders/commandActionBuilder.d.ts +3 -0
  42. package/dist/helpers/builders/commandActionBuilder.d.ts.map +1 -1
  43. package/dist/helpers/builders/commandActionBuilder.js +18 -9
  44. package/dist/helpers/builders/inlineQueryActionBuilder.js +7 -6
  45. package/dist/helpers/builders/scheduledActionBuilder.js +7 -5
  46. package/dist/helpers/mapUtils.d.ts +3 -0
  47. package/dist/helpers/mapUtils.d.ts.map +1 -0
  48. package/dist/helpers/mapUtils.js +17 -0
  49. package/dist/helpers/noop.d.ts +3 -4
  50. package/dist/helpers/noop.d.ts.map +1 -1
  51. package/dist/helpers/noop.js +2 -1
  52. package/dist/helpers/traceFactory.d.ts +1 -1
  53. package/dist/helpers/traceFactory.d.ts.map +1 -1
  54. package/dist/main.js +1 -3
  55. package/dist/services/actionProcessingService.d.ts.map +1 -1
  56. package/dist/services/actionProcessingService.js +15 -3
  57. package/dist/services/actionProcessors/baseProcessor.d.ts +1 -3
  58. package/dist/services/actionProcessors/baseProcessor.d.ts.map +1 -1
  59. package/dist/services/actionProcessors/baseProcessor.js +6 -2
  60. package/dist/services/actionProcessors/commandActionProcessor.d.ts +0 -4
  61. package/dist/services/actionProcessors/commandActionProcessor.d.ts.map +1 -1
  62. package/dist/services/actionProcessors/commandActionProcessor.js +10 -13
  63. package/dist/services/actionProcessors/inlineQueryActionProcessor.d.ts +0 -4
  64. package/dist/services/actionProcessors/inlineQueryActionProcessor.d.ts.map +1 -1
  65. package/dist/services/actionProcessors/inlineQueryActionProcessor.js +4 -6
  66. package/dist/services/actionProcessors/scheduledActionProcessor.js +6 -4
  67. package/dist/services/jsonFileStorage.d.ts +4 -3
  68. package/dist/services/jsonFileStorage.d.ts.map +1 -1
  69. package/dist/services/jsonFileStorage.js +22 -14
  70. package/dist/services/jsonLogger.d.ts +1 -1
  71. package/dist/services/jsonLogger.d.ts.map +1 -1
  72. package/dist/services/jsonLogger.js +9 -3
  73. package/dist/services/nodeTimeoutScheduler.d.ts +2 -2
  74. package/dist/services/nodeTimeoutScheduler.d.ts.map +1 -1
  75. package/dist/services/nodeTimeoutScheduler.js +6 -5
  76. package/dist/services/responseProcessingQueue.d.ts.map +1 -1
  77. package/dist/services/responseProcessingQueue.js +9 -7
  78. package/dist/services/telegramApi.d.ts.map +1 -1
  79. package/dist/services/telegramApi.js +17 -8
  80. package/dist/types/commandTrigger.d.ts +1 -2
  81. package/dist/types/commandTrigger.d.ts.map +1 -1
  82. package/dist/types/handlers.d.ts +1 -1
  83. package/dist/types/handlers.d.ts.map +1 -1
  84. package/dist/types/logger.d.ts +2 -2
  85. package/dist/types/logger.d.ts.map +1 -1
  86. package/dist/types/scheduler.d.ts +2 -2
  87. package/dist/types/scheduler.d.ts.map +1 -1
  88. package/dist/types/storage.d.ts +2 -2
  89. package/dist/types/storage.d.ts.map +1 -1
  90. package/dist/types/timeValues.d.ts +3 -3
  91. package/dist/types/timeValues.d.ts.map +1 -1
  92. package/dist/types/trace.d.ts +1 -1
  93. package/dist/types/trace.d.ts.map +1 -1
  94. package/dtos/incomingMessage.ts +1 -1
  95. package/entities/actions/commandAction.ts +55 -28
  96. package/entities/actions/inlineQueryAction.ts +5 -1
  97. package/entities/actions/replyCaptureAction.ts +5 -1
  98. package/entities/actions/scheduledAction.ts +11 -13
  99. package/entities/botInstance.ts +7 -7
  100. package/entities/context/chatContext.ts +0 -6
  101. package/entities/context/inlineQueryContext.ts +0 -6
  102. package/entities/context/messageContext.ts +0 -6
  103. package/entities/context/replyContext.ts +18 -15
  104. package/eslint.config.js +26 -1
  105. package/helpers/builders/commandActionBuilder.ts +10 -0
  106. package/helpers/mapUtils.ts +15 -0
  107. package/helpers/noop.ts +4 -4
  108. package/helpers/traceFactory.ts +2 -2
  109. package/package.json +1 -1
  110. package/services/actionProcessingService.ts +8 -4
  111. package/services/actionProcessors/baseProcessor.ts +5 -8
  112. package/services/actionProcessors/commandActionProcessor.ts +5 -17
  113. package/services/actionProcessors/inlineQueryActionProcessor.ts +5 -15
  114. package/services/actionProcessors/scheduledActionProcessor.ts +4 -4
  115. package/services/jsonFileStorage.ts +39 -21
  116. package/services/jsonLogger.ts +12 -8
  117. package/services/nodeTimeoutScheduler.ts +6 -6
  118. package/services/responseProcessingQueue.ts +9 -4
  119. package/services/telegramApi.ts +7 -5
  120. package/tsconfig.json +1 -1
  121. package/types/commandTrigger.ts +1 -3
  122. package/types/handlers.ts +1 -1
  123. package/types/logger.ts +3 -6
  124. package/types/scheduler.ts +2 -2
  125. package/types/storage.ts +2 -2
  126. package/types/timeValues.ts +3 -3
  127. package/types/trace.ts +3 -1
@@ -13,8 +13,6 @@ import {
13
13
  MessageTypeValue,
14
14
  TelegrafContextMessage
15
15
  } from '../../types/messageTypes';
16
- import { IScheduler } from '../../types/scheduler';
17
- import { IStorageClient } from '../../types/storage';
18
16
  import { ReplyCaptureAction } from '../actions/replyCaptureAction';
19
17
  import { resolve } from 'path';
20
18
  import { BaseContext } from './baseContext';
@@ -41,10 +39,6 @@ export class ReplyContext<
41
39
 
42
40
  isInitialized = false;
43
41
 
44
- constructor(storage: IStorageClient, scheduler: IScheduler) {
45
- super(storage, scheduler);
46
- }
47
-
48
42
  private getQuotePart(quote: boolean | string) {
49
43
  return typeof quote == 'boolean'
50
44
  ? this.matchResults.length != 0
@@ -136,7 +130,9 @@ export class ReplyContext<
136
130
  text: string,
137
131
  quote?: string,
138
132
  options?: TextMessageSendingOptions
139
- ) => this.replyWithText(text, quote ?? true, options),
133
+ ) => {
134
+ this.replyWithText(text, quote ?? true, options);
135
+ },
140
136
  /**
141
137
  * Reply with image message to message that triggered this action after action execution is finished.
142
138
  * If multiple responses are sent, they will be sent in the order they were added, with delay of at least 35ms as per Telegram rate-limit.
@@ -147,7 +143,9 @@ export class ReplyContext<
147
143
  name: string,
148
144
  quote?: string,
149
145
  options?: MessageSendingOptions
150
- ) => this.replyWithImage(name, quote ?? true, options),
146
+ ) => {
147
+ this.replyWithImage(name, quote ?? true, options);
148
+ },
151
149
 
152
150
  /**
153
151
  * Reply with video/gif message to message that triggered this action after action execution is finished.
@@ -159,7 +157,9 @@ export class ReplyContext<
159
157
  name: string,
160
158
  quote?: string,
161
159
  options?: MessageSendingOptions
162
- ) => this.replyWithVideo(name, quote ?? true, options)
160
+ ) => {
161
+ this.replyWithVideo(name, quote ?? true, options);
162
+ }
163
163
  },
164
164
 
165
165
  /**
@@ -168,16 +168,18 @@ export class ReplyContext<
168
168
  * @param text Message contents.
169
169
  * @param options Message sending option.
170
170
  */
171
- withText: (text: string, options?: TextMessageSendingOptions) =>
172
- this.replyWithText(text, false, options),
171
+ withText: (text: string, options?: TextMessageSendingOptions) => {
172
+ this.replyWithText(text, false, options);
173
+ },
173
174
  /**
174
175
  * Reply with image message to message that triggered this action after action execution is finished.
175
176
  * If multiple responses are sent, they will be sent in the order they were added, with delay of at least 35ms as per Telegram rate-limit.
176
177
  * @param text Message contents.
177
178
  * @param options Message sending option.
178
179
  */
179
- withImage: (name: string, options?: MessageSendingOptions) =>
180
- this.replyWithImage(name, false, options),
180
+ withImage: (name: string, options?: MessageSendingOptions) => {
181
+ this.replyWithImage(name, false, options);
182
+ },
181
183
 
182
184
  /**
183
185
  * Reply with video/gif message to message that triggered this action after action execution is finished.
@@ -185,8 +187,9 @@ export class ReplyContext<
185
187
  * @param text Message contents.
186
188
  * @param options Message sending option.
187
189
  */
188
- withVideo: (name: string, options?: MessageSendingOptions) =>
189
- this.replyWithVideo(name, false, options),
190
+ withVideo: (name: string, options?: MessageSendingOptions) => {
191
+ this.replyWithVideo(name, false, options);
192
+ },
190
193
 
191
194
  /**
192
195
  * React to the message that triggered this action after action execution is finished.
package/eslint.config.js CHANGED
@@ -3,8 +3,14 @@ import tseslint from 'typescript-eslint';
3
3
 
4
4
  export default tseslint.config(
5
5
  eslint.configs.recommended,
6
- ...tseslint.configs.recommended,
6
+ ...tseslint.configs.strictTypeChecked,
7
7
  {
8
+ languageOptions: {
9
+ parserOptions: {
10
+ project: './tsconfig.json',
11
+ sourceType: 'module'
12
+ }
13
+ },
8
14
  rules: {
9
15
  '@typescript-eslint/no-unused-vars': [
10
16
  'error',
@@ -17,6 +23,25 @@ export default tseslint.config(
17
23
  varsIgnorePattern: '^_',
18
24
  ignoreRestSiblings: true
19
25
  }
26
+ ],
27
+ '@typescript-eslint/no-misused-promises': [
28
+ 'error',
29
+ {
30
+ checksVoidReturn: true,
31
+ checksConditionals: true
32
+ }
33
+ ],
34
+ '@typescript-eslint/restrict-template-expressions': [
35
+ 'error',
36
+ {
37
+ allowNumber: true
38
+ }
39
+ ],
40
+ '@typescript-eslint/unbound-method': [
41
+ 'error',
42
+ {
43
+ ignoreStatic: true
44
+ }
20
45
  ]
21
46
  },
22
47
  ignores: ['dist/*']
@@ -23,6 +23,7 @@ export class CommandActionBuilderWithState<TActionState extends IActionState> {
23
23
  stateConstructor: () => TActionState;
24
24
  handler: CommandHandler<TActionState> = Noop.call;
25
25
  condition: CommandCondition<TActionState> = Noop.true;
26
+ maxAllowedSimultaniousExecutions: number = 0;
26
27
 
27
28
  /**
28
29
  * Builder for `CommandAction` with state represented by `TActionState`
@@ -86,6 +87,14 @@ export class CommandActionBuilderWithState<TActionState extends IActionState> {
86
87
  return this;
87
88
  }
88
89
 
90
+ /** Sets maximum number of simultaniously executing handlers for this command per chat. 0 is treated as unlimited. */
91
+ ratelimit(maxAllowedSimultaniousExecutions: number) {
92
+ this.maxAllowedSimultaniousExecutions =
93
+ maxAllowedSimultaniousExecutions;
94
+
95
+ return this;
96
+ }
97
+
89
98
  /** Sets action cooldown.
90
99
  * @param seconds Cooldown in seconds.
91
100
  */
@@ -115,6 +124,7 @@ export class CommandActionBuilderWithState<TActionState extends IActionState> {
115
124
  this.cooldownSeconds,
116
125
  this.blacklist,
117
126
  this.allowedUsers,
127
+ this.maxAllowedSimultaniousExecutions,
118
128
  this.condition,
119
129
  this.stateConstructor,
120
130
  this.readmeFactory != null ? this.readmeFactory : (_) => ''
@@ -0,0 +1,15 @@
1
+ export function getOrSetIfNotExists<K, V>(map: Map<K, V>, key: K, fallback: V) {
2
+ const existingValue = map.get(key);
3
+ if (existingValue) return existingValue;
4
+
5
+ map.set(key, fallback);
6
+
7
+ return fallback;
8
+ }
9
+
10
+ export function getOrThrow<K, V>(map: Map<K, V>, key: K, error: string) {
11
+ const existingValue = map.get(key);
12
+ if (existingValue) return existingValue;
13
+
14
+ throw new Error(error);
15
+ }
package/helpers/noop.ts CHANGED
@@ -1,16 +1,16 @@
1
1
  import { BotResponse } from '../types/response';
2
2
 
3
3
  /* eslint-disable @typescript-eslint/no-unused-vars */
4
+ // eslint-disable-next-line @typescript-eslint/no-extraneous-class
4
5
  export class Noop {
5
6
  static NoResponse: BotResponse[] = [];
6
- static true<T1>(arg1: T1) {
7
+ static true(arg1: unknown) {
7
8
  return true;
8
9
  }
9
- static false<T1>(arg1: T1) {
10
+ static false(arg1: unknown) {
10
11
  return false;
11
12
  }
12
13
 
13
- static async call<T1>(arg1: T1): Promise<void>;
14
- static async call<T1, T2>(arg1: T1, arg2: T2): Promise<void>;
14
+ static async call(arg1: unknown): Promise<void>;
15
15
  static async call(arg1: unknown, arg2?: unknown) {}
16
16
  }
@@ -1,7 +1,7 @@
1
1
  import { TraceId } from '../types/trace';
2
2
 
3
- export function createTrace<T extends object>(
4
- traceOwner: T | string,
3
+ export function createTrace(
4
+ traceOwner: object | string,
5
5
  botName: string,
6
6
  traceName: string
7
7
  ) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chz-telegram-bot",
3
- "version": "0.3.10",
3
+ "version": "0.3.12",
4
4
  "type": "module",
5
5
  "dependencies": {
6
6
  "async-sema": "^3.1.1",
@@ -75,13 +75,14 @@ export class ActionProcessingService {
75
75
  this.telegraf.telegram,
76
76
  this.storage,
77
77
  this.logger,
78
- (capture, id, chatInfo, traceId) =>
78
+ (capture, id, chatInfo, traceId) => {
79
79
  this.commandProcessor.captureRegistrationCallback(
80
80
  capture,
81
81
  id,
82
82
  chatInfo,
83
83
  traceId
84
- )
84
+ );
85
+ }
85
86
  );
86
87
 
87
88
  const botInfo = await this.telegraf.telegram.getMe();
@@ -116,9 +117,12 @@ export class ActionProcessingService {
116
117
  scheduledPeriod ?? hoursToSeconds(1 as Hours)
117
118
  );
118
119
 
119
- this.storage.saveMetadata([...actions.scheduled, ...commandActions]);
120
+ void this.storage.saveMetadata([
121
+ ...actions.scheduled,
122
+ ...commandActions
123
+ ]);
120
124
 
121
- this.telegraf.launch();
125
+ void this.telegraf.launch();
122
126
  }
123
127
 
124
128
  stop(code: string) {
@@ -1,4 +1,3 @@
1
- import { Telegraf } from 'telegraf';
2
1
  import { ILogger } from '../../types/logger';
3
2
  import { IScheduler } from '../../types/scheduler';
4
3
  import { IStorageClient } from '../../types/storage';
@@ -14,7 +13,6 @@ export abstract class BaseActionProcessor {
14
13
  protected readonly botName: string;
15
14
 
16
15
  protected api!: TelegramApiService;
17
- protected telegraf!: Telegraf;
18
16
 
19
17
  constructor(
20
18
  botName: string,
@@ -29,16 +27,15 @@ export abstract class BaseActionProcessor {
29
27
  this.botName = botName;
30
28
  }
31
29
 
32
- private defaultErrorHandler<
33
- TAction extends IAction,
34
- TActionContext extends BaseContext<TAction>
35
- >(error: Error, ctx: TActionContext) {
30
+ private defaultErrorHandler<TAction extends IAction>(
31
+ error: Error,
32
+ ctx: BaseContext<TAction>
33
+ ) {
36
34
  ctx.logger.errorWithTraceId(error, ctx);
37
35
  }
38
36
 
39
- initializeDependencies(api: TelegramApiService, telegraf: Telegraf) {
37
+ initializeDependencies(api: TelegramApiService) {
40
38
  this.api = api;
41
- this.telegraf = telegraf;
42
39
  }
43
40
 
44
41
  async executeAction<
@@ -5,9 +5,6 @@ import { ReplyCaptureAction } from '../../entities/actions/replyCaptureAction';
5
5
  import { MessageContext } from '../../entities/context/messageContext';
6
6
  import { ReplyContext } from '../../entities/context/replyContext';
7
7
  import { IActionState } from '../../types/actionState';
8
- import { ILogger } from '../../types/logger';
9
- import { IScheduler } from '../../types/scheduler';
10
- import { IStorageClient } from '../../types/storage';
11
8
  import { TelegramApiService } from '../telegramApi';
12
9
  import { IReplyCapture } from '../../types/capture';
13
10
  import { TraceId } from '../../types/trace';
@@ -29,22 +26,13 @@ export class CommandActionProcessor extends BaseActionProcessor {
29
26
  ])
30
27
  );
31
28
 
32
- constructor(
33
- botName: string,
34
- storage: IStorageClient,
35
- scheduler: IScheduler,
36
- logger: ILogger
37
- ) {
38
- super(botName, storage, scheduler, logger);
39
- }
40
-
41
29
  initialize(
42
30
  api: TelegramApiService,
43
31
  telegraf: Telegraf,
44
32
  commands: CommandAction<IActionState>[],
45
33
  verboseLoggingForIncomingMessage: boolean
46
34
  ) {
47
- this.initializeDependencies(api, telegraf);
35
+ this.initializeDependencies(api);
48
36
 
49
37
  for (const msgType of Object.values(MessageType)) {
50
38
  if (msgType == MessageType.Text) {
@@ -72,7 +60,7 @@ export class CommandActionProcessor extends BaseActionProcessor {
72
60
  }
73
61
 
74
62
  if (commands.length > 0) {
75
- this.telegraf.on('message', async (ctx) => {
63
+ telegraf.on('message', (ctx) => {
76
64
  const msg = new IncomingMessage(
77
65
  ctx.update.message,
78
66
  this.botName
@@ -94,7 +82,7 @@ export class CommandActionProcessor extends BaseActionProcessor {
94
82
  );
95
83
  }
96
84
 
97
- await this.processMessage(msg);
85
+ void this.processMessage(msg);
98
86
  });
99
87
  }
100
88
  }
@@ -174,7 +162,7 @@ export class CommandActionProcessor extends BaseActionProcessor {
174
162
  ) {
175
163
  ctx.replyMessageId = message.replyToMessageId;
176
164
  ctx.messageId = message.messageId;
177
- ctx.messageText = message.text ?? '';
165
+ ctx.messageText = message.text;
178
166
  ctx.messageType = message.type;
179
167
  ctx.fromUserId = message.from?.id;
180
168
  ctx.fromUserName =
@@ -202,7 +190,7 @@ export class CommandActionProcessor extends BaseActionProcessor {
202
190
  message: IncomingMessage
203
191
  ) {
204
192
  ctx.messageId = message.messageId;
205
- ctx.messageText = message.text ?? '';
193
+ ctx.messageText = message.text;
206
194
  ctx.messageType = message.type;
207
195
  ctx.fromUserId = message.from?.id;
208
196
  ctx.fromUserName =
@@ -3,9 +3,6 @@ import { IncomingInlineQuery } from '../../dtos/incomingQuery';
3
3
  import { InlineQueryAction } from '../../entities/actions/inlineQueryAction';
4
4
  import { InlineQueryContext } from '../../entities/context/inlineQueryContext';
5
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
6
  import { Milliseconds } from '../../types/timeValues';
10
7
  import { TraceId } from '../../types/trace';
11
8
  import { TelegramApiService } from '../telegramApi';
@@ -14,22 +11,13 @@ import { BaseActionProcessor } from './baseProcessor';
14
11
  export class InlineQueryActionProcessor extends BaseActionProcessor {
15
12
  private inlineQueries!: InlineQueryAction[];
16
13
 
17
- constructor(
18
- botName: string,
19
- storage: IStorageClient,
20
- scheduler: IScheduler,
21
- logger: ILogger
22
- ) {
23
- super(botName, storage, scheduler, logger);
24
- }
25
-
26
14
  initialize(
27
15
  api: TelegramApiService,
28
16
  telegraf: Telegraf,
29
17
  inlineQueries: InlineQueryAction[],
30
18
  period: Milliseconds
31
19
  ) {
32
- this.initializeDependencies(api, telegraf);
20
+ this.initializeDependencies(api);
33
21
  this.inlineQueries = inlineQueries;
34
22
 
35
23
  let pendingInlineQueries: IncomingInlineQuery[] = [];
@@ -37,7 +25,7 @@ export class InlineQueryActionProcessor extends BaseActionProcessor {
37
25
  const queriesInProcessing = new Map<number, IncomingInlineQuery>();
38
26
 
39
27
  if (this.inlineQueries.length > 0) {
40
- this.telegraf.on('inline_query', async (ctx) => {
28
+ telegraf.on('inline_query', (ctx) => {
41
29
  const query = new IncomingInlineQuery(
42
30
  ctx.inlineQuery.id,
43
31
  ctx.inlineQuery.query,
@@ -52,7 +40,9 @@ export class InlineQueryActionProcessor extends BaseActionProcessor {
52
40
  );
53
41
 
54
42
  logger.logWithTraceId(
55
- `${ctx.inlineQuery.from.username} (${ctx.inlineQuery.from.id}): Query for ${ctx.inlineQuery.query}`
43
+ `${ctx.inlineQuery.from.username ?? 'Unknown'} (${
44
+ ctx.inlineQuery.from.id
45
+ }): Query for ${ctx.inlineQuery.query}`
56
46
  );
57
47
 
58
48
  const queryBeingProcessed = queriesInProcessing.get(
@@ -34,7 +34,7 @@ export class ScheduledActionProcessor extends BaseActionProcessor {
34
34
  scheduled: ScheduledAction<IActionState>[],
35
35
  period: Seconds
36
36
  ) {
37
- this.initializeDependencies(api, null!);
37
+ this.initializeDependencies(api);
38
38
  this.scheduled = scheduled;
39
39
 
40
40
  if (this.scheduled.length > 0) {
@@ -63,11 +63,11 @@ export class ScheduledActionProcessor extends BaseActionProcessor {
63
63
 
64
64
  this.scheduler.createOnetimeTask(
65
65
  'ScheduledProcessing_OneTime',
66
- async () => {
66
+ () => {
67
67
  this.scheduler.createTask(
68
68
  'ScheduledProcessing',
69
- async () => {
70
- await this.runScheduled();
69
+ () => {
70
+ void this.runScheduled();
71
71
  },
72
72
  secondsToMilliseconds(period),
73
73
  true,
@@ -4,6 +4,7 @@ import { Sema as Semaphore } from 'async-sema';
4
4
  import { IStorageClient } from '../types/storage';
5
5
  import { IActionState } from '../types/actionState';
6
6
  import { IActionWithState, ActionKey } from '../types/action';
7
+ import { getOrSetIfNotExists } from '../helpers/mapUtils';
7
8
 
8
9
  function buildPath(storagePath: string, botName: string, actionKey: string) {
9
10
  return `${storagePath}/${botName}/${actionKey.replaceAll(':', '/')}.json`;
@@ -40,10 +41,21 @@ export class JsonFileStorage implements IStorageClient {
40
41
  }
41
42
  }
42
43
 
43
- private async lock<TType>(key: ActionKey, action: () => Promise<TType>) {
44
- if (!this.locks.has(key)) this.locks.set(key, new Semaphore(1));
44
+ private backfillEmptyActionStates<TActionState extends IActionState>(
45
+ action: IActionWithState<TActionState>,
46
+ data: Record<number, TActionState | undefined>
47
+ ): data is Record<number, TActionState> {
48
+ for (const [stringKey, value] of Object.entries(data)) {
49
+ if (value) continue;
50
+
51
+ data[parseInt(stringKey)] = action.stateConstructor();
52
+ }
45
53
 
46
- const lock = this.locks.get(key)!;
54
+ return true;
55
+ }
56
+
57
+ private async lock<TType>(key: ActionKey, action: () => Promise<TType>) {
58
+ const lock = getOrSetIfNotExists(this.locks, key, new Semaphore(1));
47
59
 
48
60
  await lock.acquire();
49
61
 
@@ -61,12 +73,11 @@ export class JsonFileStorage implements IStorageClient {
61
73
  private async loadFromFile<TActionState extends IActionState>(
62
74
  key: ActionKey
63
75
  ) {
64
- if (!this.filePaths.has(key))
65
- this.filePaths.set(
66
- key,
67
- buildPath(this.storagePath, this.botName, key)
68
- );
69
- const targetPath = this.filePaths.get(key)!;
76
+ const targetPath = getOrSetIfNotExists(
77
+ this.filePaths,
78
+ key,
79
+ buildPath(this.storagePath, this.botName, key)
80
+ );
70
81
 
71
82
  const fileContent = await readFile(targetPath, {
72
83
  encoding: 'utf-8',
@@ -74,12 +85,18 @@ export class JsonFileStorage implements IStorageClient {
74
85
  });
75
86
 
76
87
  if (fileContent) {
77
- const data = JSON.parse(fileContent);
88
+ const data = JSON.parse(fileContent) as Record<
89
+ number,
90
+ TActionState
91
+ >;
78
92
 
79
93
  this.cache.set(key, data);
80
94
  }
81
95
 
82
- return (this.cache.get(key) ?? {}) as Record<number, TActionState>;
96
+ return (this.cache.get(key) ?? {}) as Record<
97
+ number,
98
+ TActionState | undefined
99
+ >;
83
100
  }
84
101
 
85
102
  private async updateCacheAndSaveToFile<TActionState extends IActionState>(
@@ -88,12 +105,11 @@ export class JsonFileStorage implements IStorageClient {
88
105
  ) {
89
106
  this.cache.set(key, data);
90
107
 
91
- if (!this.filePaths.has(key))
92
- this.filePaths.set(
93
- key,
94
- buildPath(this.storagePath, this.botName, key)
95
- );
96
- const targetPath = this.filePaths.get(key)!;
108
+ const targetPath = getOrSetIfNotExists(
109
+ this.filePaths,
110
+ key,
111
+ buildPath(this.storagePath, this.botName, key)
112
+ );
97
113
 
98
114
  await writeFile(targetPath, JSON.stringify(data), { flag: 'w+' });
99
115
  }
@@ -133,14 +149,15 @@ export class JsonFileStorage implements IStorageClient {
133
149
  chatId: number,
134
150
  state: TActionState
135
151
  ) {
136
- return await this.lock(action.key, async () => {
152
+ await this.lock(action.key, async () => {
137
153
  const data =
138
154
  this.tryGetFromCache<TActionState>(action.key) ??
139
155
  (await this.loadFromFile<TActionState>(action.key));
140
156
 
141
157
  data[chatId] = state;
142
158
 
143
- await this.updateCacheAndSaveToFile(data, action.key);
159
+ if (this.backfillEmptyActionStates(action, data))
160
+ await this.updateCacheAndSaveToFile(data, action.key);
144
161
  });
145
162
  }
146
163
 
@@ -153,7 +170,7 @@ export class JsonFileStorage implements IStorageClient {
153
170
  async updateStateFor<TActionState extends IActionState>(
154
171
  action: IActionWithState<TActionState>,
155
172
  chatId: number,
156
- update: (state: TActionState) => Promise<void>
173
+ update: (state: TActionState) => Promise<void> | void
157
174
  ) {
158
175
  await this.lock(action.key, async () => {
159
176
  const data =
@@ -167,7 +184,8 @@ export class JsonFileStorage implements IStorageClient {
167
184
 
168
185
  await update(state);
169
186
 
170
- await this.updateCacheAndSaveToFile(data, action.key);
187
+ if (this.backfillEmptyActionStates(action, data))
188
+ await this.updateCacheAndSaveToFile(data, action.key);
171
189
  });
172
190
  }
173
191
  }
@@ -1,3 +1,4 @@
1
+ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
1
2
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
3
  import { ILogger, IScopedLogger } from '../types/logger';
3
4
  import { TraceId } from '../types/trace';
@@ -13,18 +14,21 @@ export class JsonLogger implements ILogger {
13
14
 
14
15
  createScope(botName: string, traceId: TraceId, chatName: string) {
15
16
  return {
16
- logObjectWithTraceId: (data: any) =>
17
- this.logObjectWithTraceId(botName, traceId, chatName, data),
18
- logWithTraceId: (text: string) =>
19
- this.logWithTraceId(botName, traceId, chatName, text),
20
- errorWithTraceId: <TData>(errorObj: unknown, extraData?: TData) =>
17
+ logObjectWithTraceId: (data: any) => {
18
+ this.logObjectWithTraceId(botName, traceId, chatName, data);
19
+ },
20
+ logWithTraceId: (text: string) => {
21
+ this.logWithTraceId(botName, traceId, chatName, text);
22
+ },
23
+ errorWithTraceId: (errorObj: unknown, extraData?: unknown) => {
21
24
  this.errorWithTraceId(
22
25
  botName,
23
26
  traceId,
24
27
  chatName,
25
28
  errorObj,
26
29
  extraData
27
- )
30
+ );
31
+ }
28
32
  } as IScopedLogger;
29
33
  }
30
34
 
@@ -51,12 +55,12 @@ export class JsonLogger implements ILogger {
51
55
  );
52
56
  }
53
57
 
54
- errorWithTraceId<TData>(
58
+ errorWithTraceId(
55
59
  botName: string,
56
60
  traceId: TraceId,
57
61
  chatName: string,
58
62
  errorObj: unknown,
59
- extraData?: TData
63
+ extraData?: unknown
60
64
  ) {
61
65
  console.error(
62
66
  `{"botName":"${botName}","traceId":"${traceId}","chatName":"${chatName}","error":${this.serializeError(
@@ -20,7 +20,7 @@ export class NodeTimeoutScheduler implements IScheduler {
20
20
 
21
21
  createTask(
22
22
  name: string,
23
- action: () => void,
23
+ action: () => unknown,
24
24
  interval: Milliseconds,
25
25
  executeRightAway: boolean,
26
26
  ownerName: string
@@ -36,7 +36,7 @@ export class NodeTimeoutScheduler implements IScheduler {
36
36
  ownerName,
37
37
  createTrace(this, ownerName, name),
38
38
  'System',
39
- `Created task [${taskId}]${name}, that will run every ${interval}ms.`
39
+ `Created task ${name}, that will run every ${interval}ms.`
40
40
  );
41
41
 
42
42
  this.activeTasks.push(task);
@@ -44,7 +44,7 @@ export class NodeTimeoutScheduler implements IScheduler {
44
44
 
45
45
  createOnetimeTask(
46
46
  name: string,
47
- action: () => void,
47
+ action: () => unknown,
48
48
  delay: Milliseconds,
49
49
  ownerName: string
50
50
  ) {
@@ -53,17 +53,17 @@ export class NodeTimeoutScheduler implements IScheduler {
53
53
  ownerName,
54
54
  createTrace(this, ownerName, name),
55
55
  'System',
56
- `Executing delayed oneshot [${taskId}]${name}`
56
+ `Executing delayed oneshot ${name}`
57
57
  );
58
58
  action();
59
59
  };
60
- const taskId = setTimeout(actionWrapper, delay);
60
+ setTimeout(actionWrapper, delay);
61
61
 
62
62
  this.logger.logWithTraceId(
63
63
  ownerName,
64
64
  createTrace(this, ownerName, name),
65
65
  'System',
66
- `Created oneshot task [${taskId}]${name}, that will run in ${delay}ms.`
66
+ `Created oneshot task ${name}, that will run in ${delay}ms.`
67
67
  );
68
68
  }
69
69
  }