chz-telegram-bot 0.1.20 → 0.2.1

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 (38) hide show
  1. package/builtin/helpAction.ts +1 -1
  2. package/bun.lock +307 -0
  3. package/dist/builtin/helpAction.js +1 -1
  4. package/dist/dtos/responses/imageMessage.d.ts +5 -4
  5. package/dist/dtos/responses/imageMessage.d.ts.map +1 -1
  6. package/dist/dtos/responses/imageMessage.js +2 -2
  7. package/dist/dtos/responses/textMessage.d.ts +5 -4
  8. package/dist/dtos/responses/textMessage.d.ts.map +1 -1
  9. package/dist/dtos/responses/textMessage.js +2 -2
  10. package/dist/dtos/responses/videoMessage.d.ts +5 -4
  11. package/dist/dtos/responses/videoMessage.d.ts.map +1 -1
  12. package/dist/dtos/responses/videoMessage.js +2 -2
  13. package/dist/entities/botInstance.d.ts +2 -0
  14. package/dist/entities/botInstance.d.ts.map +1 -1
  15. package/dist/entities/botInstance.js +23 -2
  16. package/dist/entities/context/chatContext.d.ts +25 -21
  17. package/dist/entities/context/chatContext.d.ts.map +1 -1
  18. package/dist/entities/context/chatContext.js +32 -38
  19. package/dist/entities/context/messageContext.d.ts +60 -28
  20. package/dist/entities/context/messageContext.d.ts.map +1 -1
  21. package/dist/entities/context/messageContext.js +79 -49
  22. package/dist/services/telegramApi.d.ts.map +1 -1
  23. package/dist/services/telegramApi.js +27 -11
  24. package/dist/types/replyInfo.d.ts +6 -0
  25. package/dist/types/replyInfo.d.ts.map +1 -0
  26. package/dist/types/replyInfo.js +10 -0
  27. package/dist/types/response.d.ts +3 -2
  28. package/dist/types/response.d.ts.map +1 -1
  29. package/dtos/responses/imageMessage.ts +6 -5
  30. package/dtos/responses/textMessage.ts +6 -5
  31. package/dtos/responses/videoMessage.ts +6 -5
  32. package/entities/botInstance.ts +44 -3
  33. package/entities/context/chatContext.ts +58 -72
  34. package/entities/context/messageContext.ts +133 -88
  35. package/package.json +30 -29
  36. package/services/telegramApi.ts +30 -14
  37. package/types/replyInfo.ts +9 -0
  38. package/types/response.ts +3 -2
@@ -1,18 +1,19 @@
1
1
  import { InputFile } from 'telegraf/types';
2
- import { BotResponseTypes, IReplyMessage } from '../../types/response';
2
+ import { BotResponseTypes, IReplyResponse } from '../../types/response';
3
3
  import { MessageSendingOptions } from '../../types/messageSendingOptions';
4
4
  import { IActionWithState } from '../../types/statefulAction';
5
5
  import { IActionState } from '../../types/actionState';
6
6
  import { ChatInfo } from '../chatInfo';
7
7
  import { TraceId } from '../../types/trace';
8
+ import { ReplyInfo } from '../../types/replyInfo';
8
9
 
9
- export class VideoMessage implements IReplyMessage<InputFile> {
10
+ export class VideoMessage implements IReplyResponse<InputFile> {
10
11
  readonly kind = BotResponseTypes.video;
11
12
  readonly createdAt = Date.now();
12
13
 
13
14
  readonly content: InputFile;
14
15
  readonly chatInfo: ChatInfo;
15
- readonly replyId: number | undefined;
16
+ readonly replyInfo: ReplyInfo | undefined;
16
17
  readonly traceId: TraceId;
17
18
  readonly disableWebPreview = false;
18
19
  readonly shouldPin: boolean;
@@ -21,14 +22,14 @@ export class VideoMessage implements IReplyMessage<InputFile> {
21
22
  constructor(
22
23
  video: InputFile,
23
24
  chatInfo: ChatInfo,
24
- replyId: number | undefined,
25
25
  traceId: TraceId,
26
26
  action: IActionWithState<IActionState>,
27
+ replyInfo: ReplyInfo | undefined,
27
28
  options?: MessageSendingOptions
28
29
  ) {
29
30
  this.content = video;
30
31
  this.chatInfo = chatInfo;
31
- this.replyId = replyId;
32
+ this.replyInfo = replyInfo;
32
33
  this.traceId = traceId;
33
34
  this.shouldPin = options?.pin ?? false;
34
35
  this.action = action;
@@ -29,6 +29,8 @@ import {
29
29
  INTERNAL_MESSAGE_TYPE_PREFIX,
30
30
  MessageType
31
31
  } from '../types/messageTypes';
32
+ import { IActionWithState } from '../types/statefulAction';
33
+ import { TraceId } from '../types/trace';
32
34
 
33
35
  export class BotInstance {
34
36
  private readonly api: TelegramApiService;
@@ -318,6 +320,45 @@ export class BotInstance {
318
320
  this.telegraf.stop(code);
319
321
  }
320
322
 
323
+ private initializeMessageContext<TActionState extends IActionState>(
324
+ ctx: MessageContext<IActionState>,
325
+ action: IActionWithState<TActionState>,
326
+ message: IncomingMessage
327
+ ) {
328
+ ctx.messageId = message.message_id;
329
+ ctx.messageText = message.text ?? '';
330
+ ctx.messageType = message.type;
331
+ ctx.fromUserId = message.from?.id;
332
+ ctx.fromUserName =
333
+ (message.from?.first_name ?? 'Unknown user') +
334
+ (message.from?.last_name ? ` ${message.from.last_name}` : '');
335
+ ctx.messageUpdateObject = message.updateObject;
336
+
337
+ ctx.matchResults = [];
338
+ ctx.startCooldown = true;
339
+
340
+ this.initializeChatContext(
341
+ ctx,
342
+ action,
343
+ message.chatInfo,
344
+ message.traceId
345
+ );
346
+ }
347
+
348
+ private initializeChatContext<TActionState extends IActionState>(
349
+ ctx: ChatContext<IActionState>,
350
+ action: IActionWithState<TActionState>,
351
+ chatInfo: ChatInfo,
352
+ traceId: TraceId
353
+ ) {
354
+ ctx.responses = [];
355
+ ctx.isInitialized = true;
356
+ ctx.botName = this.name;
357
+ ctx.action = action;
358
+ ctx.chatInfo = chatInfo;
359
+ ctx.traceId = traceId;
360
+ }
361
+
321
362
  private async runScheduled() {
322
363
  const ctx = new ChatContext<IActionState>(
323
364
  this.storage,
@@ -327,8 +368,8 @@ export class BotInstance {
327
368
 
328
369
  for (const [chatName, chatId] of Object.entries(this.chats)) {
329
370
  for (const scheduledAction of this.scheduled) {
330
- ctx.initializeChatContext(
331
- this.name,
371
+ this.initializeChatContext(
372
+ ctx,
332
373
  scheduledAction,
333
374
  new ChatInfo(chatId, chatName),
334
375
  createTrace(
@@ -365,7 +406,7 @@ export class BotInstance {
365
406
  );
366
407
 
367
408
  for (const commandAction of this.commands) {
368
- ctx.initializeMessageContext(this.name, commandAction, msg);
409
+ this.initializeMessageContext(ctx, commandAction, msg);
369
410
 
370
411
  try {
371
412
  const responses = await commandAction.exec(ctx);
@@ -22,7 +22,7 @@ import { TraceId } from '../../types/trace';
22
22
  * Context of action executed in chat.
23
23
  */
24
24
  export class ChatContext<TActionState extends IActionState> {
25
- protected action!: IActionWithState<TActionState>;
25
+ action!: IActionWithState<TActionState>;
26
26
 
27
27
  /** Storage client instance for the bot executing this action. */
28
28
  readonly storage: IStorageClient;
@@ -52,81 +52,67 @@ export class ChatContext<TActionState extends IActionState> {
52
52
  this.scheduler = scheduler;
53
53
  }
54
54
 
55
- initializeChatContext(
56
- botName: string,
57
- action: IActionWithState<TActionState>,
58
- chatInfo: ChatInfo,
59
- traceId: TraceId
60
- ) {
61
- this.botName = botName;
62
- this.action = action;
63
- this.chatInfo = chatInfo;
64
- this.traceId = traceId;
65
-
66
- this.isInitialized = true;
67
- this.responses = [];
68
-
69
- return this;
70
- }
71
-
72
55
  /**
73
- * Sends text message to chat after action execution is finished.
74
- * 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.
75
- * @param text Message contents.
76
- * @param options Message sending option.
56
+ * Collection of actions that send something to chat as a standalone message.
77
57
  */
78
- sendTextToChat(text: string, options?: TextMessageSendingOptions) {
79
- this.responses.push(
80
- new TextMessage(
81
- text,
82
- this.chatInfo,
83
- undefined,
84
- this.traceId,
85
- this.action,
86
- options
87
- )
88
- );
89
- }
58
+ send = {
59
+ /**
60
+ * Sends text message to chat after action execution is finished.
61
+ * 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.
62
+ * @param text Message contents.
63
+ * @param options Message sending option.
64
+ */
65
+ text: (text: string, options?: TextMessageSendingOptions) => {
66
+ this.responses.push(
67
+ new TextMessage(
68
+ text,
69
+ this.chatInfo,
70
+ this.traceId,
71
+ this.action,
72
+ undefined,
73
+ options
74
+ )
75
+ );
76
+ },
90
77
 
91
- /**
92
- * Sends image message to chat after action execution is finished.
93
- * 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.
94
- * @param name Message contents.
95
- * @param options Message sending option.
96
- */
97
- sendImageToChat(name: string, options?: MessageSendingOptions) {
98
- const filePath = `./content/${name}.png`;
99
- this.responses.push(
100
- new ImageMessage(
101
- { source: resolve(filePath) },
102
- this.chatInfo,
103
- undefined,
104
- this.traceId,
105
- this.action,
106
- options
107
- )
108
- );
109
- }
78
+ /**
79
+ * Sends image message to chat after action execution is finished.
80
+ * 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.
81
+ * @param name Message contents.
82
+ * @param options Message sending option.
83
+ */
84
+ image: (name: string, options?: MessageSendingOptions) => {
85
+ this.responses.push(
86
+ new ImageMessage(
87
+ { source: resolve(`./content/${name}.png`) },
88
+ this.chatInfo,
89
+ this.traceId,
90
+ this.action,
91
+ undefined,
92
+ options
93
+ )
94
+ );
95
+ },
110
96
 
111
- /**
112
- * Sends video/gif message to chat after action execution is finished.
113
- * 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.
114
- * @param name Message contents.
115
- * @param options Message sending option.
116
- */
117
- sendVideoToChat(name: string, options?: MessageSendingOptions) {
118
- const filePath = `./content/${name}.mp4`;
119
- this.responses.push(
120
- new VideoMessage(
121
- { source: resolve(filePath) },
122
- this.chatInfo,
123
- undefined,
124
- this.traceId,
125
- this.action,
126
- options
127
- )
128
- );
129
- }
97
+ /**
98
+ * Sends video/gif message to chat after action execution is finished.
99
+ * 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.
100
+ * @param name Message contents.
101
+ * @param options Message sending option.
102
+ */
103
+ video: (name: string, options?: MessageSendingOptions) => {
104
+ this.responses.push(
105
+ new VideoMessage(
106
+ { source: resolve(`./content/${name}.mp4`) },
107
+ this.chatInfo,
108
+ this.traceId,
109
+ this.action,
110
+ undefined,
111
+ options
112
+ )
113
+ );
114
+ }
115
+ };
130
116
 
131
117
  /**
132
118
  * Unpins message after action execution is finished.
@@ -8,18 +8,18 @@ import { TextMessage } from '../../dtos/responses/textMessage';
8
8
  import { VideoMessage } from '../../dtos/responses/videoMessage';
9
9
  import { ActionStateBase } from '../states/actionStateBase';
10
10
  import { ChatContext } from './chatContext';
11
- import { IncomingMessage } from '../../dtos/incomingMessage';
12
11
  import {
13
12
  MessageSendingOptions,
14
13
  TextMessageSendingOptions
15
14
  } from '../../types/messageSendingOptions';
16
- import { IActionWithState, ActionKey } from '../../types/statefulAction';
15
+ import { ActionKey } from '../../types/statefulAction';
17
16
  import {
18
17
  MessageTypeValue,
19
18
  TelegrafContextMessage
20
19
  } from '../../types/messageTypes';
21
20
  import { ILogger } from '../../types/logger';
22
21
  import { IScheduler } from '../../types/scheduler';
22
+ import { ReplyInfo } from '../../types/replyInfo';
23
23
  /**
24
24
  * Context of action executed in chat, in response to a message
25
25
  */
@@ -51,126 +51,171 @@ export class MessageContext<
51
51
  super(storage, logger, scheduler);
52
52
  }
53
53
 
54
- initializeMessageContext(
55
- botName: string,
56
- action: IActionWithState<TActionState>,
57
- message: IncomingMessage
54
+ private replyWithText(
55
+ text: string,
56
+ quote: boolean,
57
+ options?: TextMessageSendingOptions
58
58
  ) {
59
- this.messageId = message.message_id;
60
- this.messageText = message.text ?? '';
61
- this.messageType = message.type;
62
- this.fromUserId = message.from?.id;
63
- this.fromUserName =
64
- (message.from?.first_name ?? 'Unknown user') +
65
- (message.from?.last_name ? ` ${message.from.last_name}` : '');
66
- this.messageUpdateObject = message.updateObject;
59
+ const quotedPart =
60
+ this.matchResults.length != 0
61
+ ? this.matchResults[0][1]
62
+ : this.messageText;
67
63
 
68
- this.matchResults = [];
69
- this.startCooldown = true;
70
-
71
- return this.initializeChatContext(
72
- botName,
73
- action,
74
- message.chatInfo,
75
- message.traceId
76
- );
77
- }
78
-
79
- /**
80
- * Loads state of another action. Changes to the loaded state will no affect actual state of other action.
81
- * @param commandName Name of an action to load state of.
82
- * @template TAnotherActionState - Type of a state that is used by another action.
83
- */
84
- async loadStateOf<TAnotherActionState extends IActionState>(
85
- commandName: string
86
- ): Promise<TAnotherActionState> {
87
- const storageKey = `command:${commandName.replace(
88
- '.',
89
- '-'
90
- )}` as ActionKey;
91
- const allStates = await this.storage.load(storageKey);
92
- const stateForChat = allStates[this.chatInfo.id];
93
-
94
- if (!stateForChat) {
95
- return new ActionStateBase() as TAnotherActionState;
96
- }
97
-
98
- return stateForChat as TAnotherActionState;
99
- }
100
-
101
- /**
102
- * Reply with text message to message that triggered this action after action execution is finished.
103
- * 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.
104
- * @param text Message contents.
105
- * @param options Message sending option.
106
- */
107
- replyWithText(text: string, options?: TextMessageSendingOptions) {
108
64
  this.responses.push(
109
65
  new TextMessage(
110
66
  text,
111
67
  this.chatInfo,
112
- this.messageId,
113
68
  this.traceId,
114
69
  this.action,
70
+ new ReplyInfo(this.messageId, quote ? quotedPart : undefined),
115
71
  options
116
72
  )
117
73
  );
118
74
  }
119
75
 
120
- /**
121
- * Reply with image message to message that triggered this action after action execution is finished.
122
- * 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.
123
- * @param text Message contents.
124
- * @param options Message sending option.
125
- */
126
- replyWithImage(name: string, options?: MessageSendingOptions) {
127
- const filePath = `./content/${name}.png`;
76
+ private replyWithImage(
77
+ name: string,
78
+ quote: boolean,
79
+ options?: MessageSendingOptions
80
+ ) {
81
+ const quotedPart =
82
+ this.matchResults.length != 0
83
+ ? this.matchResults[0][1]
84
+ : this.messageText;
85
+
128
86
  this.responses.push(
129
87
  new ImageMessage(
130
- { source: resolve(filePath) },
88
+ { source: resolve(`./content/${name}.png`) },
131
89
  this.chatInfo,
132
- this.messageId,
133
90
  this.traceId,
134
91
  this.action,
92
+ new ReplyInfo(this.messageId, quote ? quotedPart : undefined),
135
93
  options
136
94
  )
137
95
  );
138
96
  }
139
97
 
140
- /**
141
- * Reply with video/gif message to message that triggered this action after action execution is finished.
142
- * 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.
143
- * @param text Message contents.
144
- * @param options Message sending option.
145
- */
146
- replyWithVideo(name: string, options?: MessageSendingOptions) {
147
- const filePath = `./content/${name}.mp4`;
98
+ private replyWithVideo(
99
+ name: string,
100
+ quote: boolean,
101
+ options?: MessageSendingOptions
102
+ ) {
103
+ const quotedPart =
104
+ this.matchResults.length != 0
105
+ ? this.matchResults[0][1]
106
+ : this.messageText;
107
+
148
108
  this.responses.push(
149
109
  new VideoMessage(
150
- { source: resolve(filePath) },
110
+ { source: resolve(`./content/${name}.mp4`) },
151
111
  this.chatInfo,
152
- this.messageId,
153
112
  this.traceId,
154
113
  this.action,
114
+ new ReplyInfo(this.messageId, quote ? quotedPart : undefined),
155
115
  options
156
116
  )
157
117
  );
158
118
  }
159
119
 
160
120
  /**
161
- * React to the message that triggered this action after action execution is finished.
162
- * 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.
163
- * @param emoji Telegram emoji to react with.
121
+ * Loads state of another action. Changes to the loaded state will no affect actual state of other action.
122
+ * @param commandName Name of an action to load state of.
123
+ * @template TAnotherActionState - Type of a state that is used by another action.
164
124
  */
165
- react(emoji: TelegramEmoji) {
166
- this.responses.push(
167
- new Reaction(
168
- this.traceId,
169
- this.chatInfo,
170
- this.messageId,
171
- emoji,
172
- this.action
173
- )
174
- );
125
+ async loadStateOf<TAnotherActionState extends IActionState>(
126
+ commandName: string
127
+ ): Promise<TAnotherActionState> {
128
+ const storageKey = `command:${commandName.replace(
129
+ '.',
130
+ '-'
131
+ )}` as ActionKey;
132
+ const allStates = await this.storage.load(storageKey);
133
+ const stateForChat = allStates[this.chatInfo.id];
134
+
135
+ if (!stateForChat) {
136
+ return new ActionStateBase() as TAnotherActionState;
137
+ }
138
+
139
+ return stateForChat as TAnotherActionState;
175
140
  }
141
+
142
+ /**
143
+ * Collection of actions that can be done as a reply to a message that triggered this action
144
+ */
145
+ reply = {
146
+ /**
147
+ * Collection of actions that can be done as a reply to a message, quoting the part that triggered this action
148
+ * If regex is matched, first match will be quoted
149
+ */
150
+ andQuote: {
151
+ /**
152
+ * Reply with text message to message that triggered this action after action execution is finished.
153
+ * 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.
154
+ * @param text Message contents.
155
+ * @param options Message sending option.
156
+ */
157
+ withText: (text: string, options?: TextMessageSendingOptions) =>
158
+ this.replyWithText(text, true, options),
159
+ /**
160
+ * Reply with image message to message that triggered this action after action execution is finished.
161
+ * 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.
162
+ * @param text Message contents.
163
+ * @param options Message sending option.
164
+ */
165
+ withImage: (name: string, options?: MessageSendingOptions) =>
166
+ this.replyWithImage(name, true, options),
167
+
168
+ /**
169
+ * Reply with video/gif message to message that triggered this action after action execution is finished.
170
+ * 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.
171
+ * @param text Message contents.
172
+ * @param options Message sending option.
173
+ */
174
+ withVideo: (name: string, options?: MessageSendingOptions) =>
175
+ this.replyWithVideo(name, true, options)
176
+ },
177
+
178
+ /**
179
+ * Reply with text message to message that triggered this action after action execution is finished.
180
+ * 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.
181
+ * @param text Message contents.
182
+ * @param options Message sending option.
183
+ */
184
+ withText: (text: string, options?: TextMessageSendingOptions) =>
185
+ this.replyWithText(text, false, options),
186
+ /**
187
+ * Reply with image message to message that triggered this action after action execution is finished.
188
+ * 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.
189
+ * @param text Message contents.
190
+ * @param options Message sending option.
191
+ */
192
+ withImage: (name: string, options?: MessageSendingOptions) =>
193
+ this.replyWithImage(name, false, options),
194
+
195
+ /**
196
+ * Reply with video/gif message to message that triggered this action after action execution is finished.
197
+ * 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.
198
+ * @param text Message contents.
199
+ * @param options Message sending option.
200
+ */
201
+ withVideo: (name: string, options?: MessageSendingOptions) =>
202
+ this.replyWithVideo(name, false, options),
203
+
204
+ /**
205
+ * React to the message that triggered this action after action execution is finished.
206
+ * 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.
207
+ * @param emoji Telegram emoji to react with.
208
+ */
209
+ withReaction: (emoji: TelegramEmoji) => {
210
+ this.responses.push(
211
+ new Reaction(
212
+ this.traceId,
213
+ this.chatInfo,
214
+ this.messageId,
215
+ emoji,
216
+ this.action
217
+ )
218
+ );
219
+ }
220
+ };
176
221
  }
package/package.json CHANGED
@@ -1,29 +1,30 @@
1
- {
2
- "name": "chz-telegram-bot",
3
- "version": "0.1.20",
4
- "type": "module",
5
- "dependencies": {
6
- "async-sema": "^3.1.1",
7
- "moment": "^2.29.4",
8
- "telegraf": "^4.16.3"
9
- },
10
- "main": "dist/index.js",
11
- "types": "dist/index.d.ts",
12
- "devDependencies": {
13
- "@eslint/js": "^9.10.0",
14
- "@types/markdown-escape": "^1.1.3",
15
- "@types/node": "^22.5.5",
16
- "eslint": "^9.10.0",
17
- "typescript": "^5.6.2",
18
- "typescript-eslint": "^8.5.0"
19
- },
20
- "scripts": {
21
- "build": "tsc",
22
- "lint": "npx eslint && tsc --noEmit"
23
- },
24
- "overrides": {
25
- "telegraf": {
26
- "node-fetch": "3.3.2"
27
- }
28
- }
29
- }
1
+ {
2
+ "name": "chz-telegram-bot",
3
+ "version": "0.2.1",
4
+ "type": "module",
5
+ "dependencies": {
6
+ "async-sema": "^3.1.1",
7
+ "moment": "^2.29.4",
8
+ "telegraf": "^4.16.3"
9
+ },
10
+ "main": "dist/index.js",
11
+ "types": "dist/index.d.ts",
12
+ "devDependencies": {
13
+ "@eslint/js": "^9.29.0",
14
+ "@types/markdown-escape": "^1.1.3",
15
+ "@types/node": "^22.5.5",
16
+ "eslint": "^9.29.0",
17
+ "globals": "^16.2.0",
18
+ "typescript": "^5.6.2",
19
+ "typescript-eslint": "^8.34.1"
20
+ },
21
+ "scripts": {
22
+ "build": "tsc",
23
+ "lint": "npx eslint && tsc --noEmit"
24
+ },
25
+ "overrides": {
26
+ "telegraf": {
27
+ "node-fetch": "3.3.2"
28
+ }
29
+ }
30
+ }