chz-telegram-bot 0.0.51 → 0.0.52

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 (70) hide show
  1. package/dist/dtos/commandTriggerCheckResult.d.ts +5 -5
  2. package/dist/dtos/commandTriggerCheckResult.d.ts.map +1 -1
  3. package/dist/dtos/commandTriggerCheckResult.js +3 -6
  4. package/dist/dtos/incomingMessage.d.ts +6 -6
  5. package/dist/dtos/incomingMessage.d.ts.map +1 -1
  6. package/dist/entities/actions/commandAction.d.ts +1 -0
  7. package/dist/entities/actions/commandAction.d.ts.map +1 -1
  8. package/dist/entities/actions/commandAction.js +38 -37
  9. package/dist/entities/actions/scheduledAction.d.ts +1 -1
  10. package/dist/entities/actions/scheduledAction.d.ts.map +1 -1
  11. package/dist/entities/actions/scheduledAction.js +12 -16
  12. package/dist/entities/botInstance.d.ts +11 -3
  13. package/dist/entities/botInstance.d.ts.map +1 -1
  14. package/dist/entities/botInstance.js +24 -17
  15. package/dist/entities/context/chatContext.d.ts +10 -10
  16. package/dist/entities/context/chatContext.d.ts.map +1 -1
  17. package/dist/entities/context/chatContext.js +5 -12
  18. package/dist/entities/context/messageContext.d.ts +4 -2
  19. package/dist/entities/context/messageContext.d.ts.map +1 -1
  20. package/dist/entities/context/messageContext.js +4 -4
  21. package/dist/helpers/noop.d.ts +4 -2
  22. package/dist/helpers/noop.d.ts.map +1 -1
  23. package/dist/helpers/noop.js +3 -2
  24. package/dist/index.d.ts +3 -1
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/main.d.ts +10 -2
  27. package/dist/main.d.ts.map +1 -1
  28. package/dist/main.js +6 -11
  29. package/dist/services/jsonFileStorage.d.ts +1 -2
  30. package/dist/services/jsonFileStorage.d.ts.map +1 -1
  31. package/dist/services/jsonFileStorage.js +3 -5
  32. package/dist/services/jsonLogger.d.ts +8 -0
  33. package/dist/services/jsonLogger.d.ts.map +1 -0
  34. package/dist/services/jsonLogger.js +28 -0
  35. package/dist/services/nodeTimeoutScheduler.d.ts +13 -0
  36. package/dist/services/nodeTimeoutScheduler.d.ts.map +1 -0
  37. package/dist/services/nodeTimeoutScheduler.js +33 -0
  38. package/dist/services/telegramApi.d.ts +7 -5
  39. package/dist/services/telegramApi.d.ts.map +1 -1
  40. package/dist/services/telegramApi.js +4 -4
  41. package/dist/types/commandCondition.d.ts +1 -1
  42. package/dist/types/commandCondition.d.ts.map +1 -1
  43. package/dist/types/logger.d.ts +6 -0
  44. package/dist/types/logger.d.ts.map +1 -0
  45. package/dist/types/logger.js +2 -0
  46. package/dist/types/scheduler.d.ts +7 -0
  47. package/dist/types/scheduler.d.ts.map +1 -0
  48. package/dist/types/scheduler.js +2 -0
  49. package/dist/types/storage.d.ts +1 -2
  50. package/dist/types/storage.d.ts.map +1 -1
  51. package/dtos/commandTriggerCheckResult.ts +10 -10
  52. package/dtos/incomingMessage.ts +6 -6
  53. package/entities/actions/commandAction.ts +48 -41
  54. package/entities/actions/scheduledAction.ts +28 -31
  55. package/entities/botInstance.ts +50 -32
  56. package/entities/context/chatContext.ts +20 -16
  57. package/entities/context/messageContext.ts +10 -6
  58. package/helpers/noop.ts +5 -2
  59. package/index.ts +3 -1
  60. package/main.ts +16 -15
  61. package/package.json +1 -1
  62. package/services/jsonFileStorage.ts +4 -6
  63. package/services/{logger.ts → jsonLogger.ts} +3 -3
  64. package/services/{taskScheduler.ts → nodeTimeoutScheduler.ts} +11 -7
  65. package/services/telegramApi.ts +15 -8
  66. package/types/commandCondition.ts +1 -1
  67. package/types/logger.ts +24 -0
  68. package/types/scheduler.ts +20 -0
  69. package/types/storage.ts +1 -2
  70. package/dtos/actionExecutionResult.ts +0 -11
@@ -7,9 +7,8 @@ import { IActionState } from '../../types/actionState';
7
7
  import { IActionWithState, ActionKey } from '../../types/actionWithState';
8
8
  import { CachedStateFactory } from '../cachedStateFactory';
9
9
  import { ChatContext } from '../context/chatContext';
10
- import { ActionExecutionResult } from '../../dtos/actionExecutionResult';
11
- import { Logger } from '../../services/logger';
12
- import { Scheduler } from '../../services/taskScheduler';
10
+ import { Noop } from '../../helpers/noop';
11
+ import { IScheduler } from '../../types/scheduler';
13
12
 
14
13
  export class ScheduledAction<TActionState extends IActionState>
15
14
  implements IActionWithState<TActionState>
@@ -53,47 +52,45 @@ export class ScheduledAction<TActionState extends IActionState>
53
52
  );
54
53
 
55
54
  if (!this.active || !this.chatsWhitelist.includes(ctx.chatInfo.id))
56
- return [];
55
+ return Noop.NoResponse;
57
56
 
58
57
  const state = await ctx.storage.getActionState<TActionState>(
59
58
  this,
60
59
  ctx.chatInfo.id
61
60
  );
62
- const isAllowedToTrigger = this.shouldTrigger(state);
63
-
64
- if (isAllowedToTrigger) {
65
- Logger.logWithTraceId(
66
- ctx.botName,
67
- ctx.traceId,
68
- ctx.chatInfo.name,
69
- ` - Executing [${this.name}] in ${ctx.chatInfo.id}`
70
- );
71
61
 
72
- await this.handler(
73
- ctx,
74
- <TResult>(key: string) =>
75
- this.getCachedValue<TResult>(key, ctx.botName),
76
- state
77
- );
62
+ const isAllowedToTrigger = this.checkIfShouldBeExecuted(state);
63
+ if (!isAllowedToTrigger) return Noop.NoResponse;
78
64
 
79
- state.lastExecutedDate = moment().valueOf();
65
+ ctx.logger.logWithTraceId(
66
+ ctx.botName,
67
+ ctx.traceId,
68
+ ctx.chatInfo.name,
69
+ ` - Executing [${this.name}] in ${ctx.chatInfo.id}`
70
+ );
80
71
 
81
- ctx.updateActions.forEach((action) => action(state));
82
- await ctx.storage.saveActionExecutionResult(
83
- this,
84
- ctx.chatInfo.id,
85
- new ActionExecutionResult(state, isAllowedToTrigger)
86
- );
87
- }
72
+ await this.handler(
73
+ ctx,
74
+ <TResult>(key: string) =>
75
+ this.getCachedValue<TResult>(key, ctx.botName, ctx.scheduler),
76
+ state
77
+ );
78
+
79
+ state.lastExecutedDate = moment().valueOf();
88
80
 
89
- ctx.isInitialized = false;
81
+ await ctx.storage.saveActionExecutionResult(
82
+ this,
83
+ ctx.chatInfo.id,
84
+ state
85
+ );
90
86
 
91
87
  return ctx.responses;
92
88
  }
93
89
 
94
90
  private async getCachedValue<TResult>(
95
91
  key: string,
96
- botName: string
92
+ botName: string,
93
+ scheduler: IScheduler
97
94
  ): Promise<TResult> {
98
95
  if (!this.cachedStateFactories.has(key)) {
99
96
  throw new Error(
@@ -122,7 +119,7 @@ export class ScheduledAction<TActionState extends IActionState>
122
119
 
123
120
  this.cachedState.set(key, value);
124
121
 
125
- Scheduler.createOnetimeTask(
122
+ scheduler.createOnetimeTask(
126
123
  `Drop cached value [${this.name} : ${key}]`,
127
124
  () => this.cachedState.delete(key),
128
125
  hoursToMilliseconds(
@@ -137,7 +134,7 @@ export class ScheduledAction<TActionState extends IActionState>
137
134
  }
138
135
  }
139
136
 
140
- private shouldTrigger(state: IActionState): boolean {
137
+ private checkIfShouldBeExecuted(state: IActionState): boolean {
141
138
  const startOfToday = moment().startOf('day').valueOf();
142
139
  const lastExecutedDate = moment(state.lastExecutedDate);
143
140
  const currentTime = moment();
@@ -10,22 +10,27 @@ import { TelegramApiService } from '../services/telegramApi';
10
10
  import { IActionState } from '../types/actionState';
11
11
  import { CommandAction } from './actions/commandAction';
12
12
  import { ScheduledAction } from './actions/scheduledAction';
13
- import { Logger } from '../services/logger';
14
- import { Scheduler } from '../services/taskScheduler';
13
+ import { JsonLogger } from '../services/jsonLogger';
15
14
  import { IncomingMessage } from '../dtos/incomingMessage';
16
15
  import moment from 'moment';
17
16
  import { ChatContext } from './context/chatContext';
18
17
  import { MessageContext } from './context/messageContext';
19
18
  import { ChatInfo } from '../dtos/chatInfo';
19
+ import { ILogger } from '../types/logger';
20
+ import { IScheduler } from '../types/scheduler';
21
+ import { NodeTimeoutScheduler } from '../services/nodeTimeoutScheduler';
20
22
 
21
23
  export class BotInstance {
22
- readonly name: string;
23
24
  private readonly api: TelegramApiService;
25
+ private readonly storage: IStorageClient;
26
+ private readonly scheduler: IScheduler;
27
+ private readonly logger: ILogger;
28
+
29
+ readonly name: string;
24
30
  private readonly telegraf: Telegraf;
25
31
  private readonly commands: CommandAction<IActionState>[];
26
32
  private readonly scheduled: ScheduledAction<IActionState>[];
27
33
  private readonly chats: Record<string, number>;
28
- readonly storage: IStorageClient;
29
34
 
30
35
  constructor(options: {
31
36
  name: string;
@@ -33,10 +38,14 @@ export class BotInstance {
33
38
  commands: CommandAction<IActionState>[];
34
39
  scheduled: ScheduledAction<IActionState>[];
35
40
  chats: Record<string, number>;
36
- storageClient?: IStorageClient;
37
41
  storagePath?: string;
38
42
  scheduledPeriod?: Seconds;
39
43
  verboseLoggingForIncomingMessage?: boolean;
44
+ services?: {
45
+ storageClient?: IStorageClient;
46
+ logger?: ILogger;
47
+ scheduler?: IScheduler;
48
+ };
40
49
  }) {
41
50
  this.name = options.name;
42
51
  this.commands = options.commands;
@@ -44,21 +53,19 @@ export class BotInstance {
44
53
  this.chats = options.chats;
45
54
 
46
55
  const actions = [...this.commands, ...this.scheduled];
47
-
48
- Logger.logWithTraceId(
49
- this.name,
50
- `System:Bot-${this.name}-Start`,
51
- 'System',
52
- 'Starting bot...'
53
- );
54
56
  this.telegraf = new Telegraf(options.token);
57
+ this.logger = options.services?.logger ?? new JsonLogger();
58
+ this.scheduler =
59
+ options.services?.scheduler ??
60
+ new NodeTimeoutScheduler(this.logger);
55
61
  this.storage =
56
- options.storageClient ??
62
+ options.services?.storageClient ??
57
63
  new JsonFileStorage(options.name, actions, options.storagePath);
58
64
  this.api = new TelegramApiService(
59
65
  this.name,
60
66
  this.telegraf.telegram,
61
- this.storage
67
+ this.storage,
68
+ this.logger
62
69
  );
63
70
 
64
71
  this.initializeMessageProcessing(
@@ -70,6 +77,12 @@ export class BotInstance {
70
77
 
71
78
  this.storage.saveMetadata(actions, this.name);
72
79
 
80
+ this.logger.logWithTraceId(
81
+ this.name,
82
+ `System:Bot-${this.name}-Start`,
83
+ 'System',
84
+ 'Starting bot...'
85
+ );
73
86
  this.telegraf.launch();
74
87
  }
75
88
 
@@ -78,7 +91,7 @@ export class BotInstance {
78
91
  const now = moment();
79
92
 
80
93
  if (now.minute() == 0 && now.second() == 0) {
81
- Scheduler.createTask(
94
+ this.scheduler.createTask(
82
95
  'ScheduledProcessing',
83
96
  async () => {
84
97
  await this.runScheduled();
@@ -98,10 +111,10 @@ export class BotInstance {
98
111
 
99
112
  const delay = nextExecutionTime.diff(now);
100
113
 
101
- Scheduler.createOnetimeTask(
114
+ this.scheduler.createOnetimeTask(
102
115
  'ScheduledProcessing_OneTime',
103
116
  async () => {
104
- Scheduler.createTask(
117
+ this.scheduler.createTask(
105
118
  'ScheduledProcessing',
106
119
  async () => {
107
120
  await this.runScheduled();
@@ -129,14 +142,14 @@ export class BotInstance {
129
142
  const messageFromId = msg.from?.id ?? 'Unknown';
130
143
 
131
144
  if (verboseLoggingForIncomingMessage) {
132
- Logger.logObjectWithTraceId(
145
+ this.logger.logObjectWithTraceId(
133
146
  this.name,
134
147
  msg.traceId,
135
148
  msg.chatInfo.name,
136
149
  ctx.update.message
137
150
  );
138
151
  } else {
139
- Logger.logWithTraceId(
152
+ this.logger.logWithTraceId(
140
153
  this.name,
141
154
  msg.traceId,
142
155
  msg.chatInfo.name,
@@ -150,19 +163,24 @@ export class BotInstance {
150
163
  }
151
164
 
152
165
  async stop(code: string) {
153
- Logger.logWithTraceId(
166
+ this.logger.logWithTraceId(
154
167
  this.name,
155
168
  `System:Bot-${this.name}-Stop`,
156
169
  'System',
157
170
  'Stopping bot...'
158
171
  );
159
172
 
173
+ this.scheduler.stopAll();
160
174
  await this.storage.close();
161
175
  this.telegraf.stop(code);
162
176
  }
163
177
 
164
178
  private async runScheduled() {
165
- const ctx = new ChatContext<IActionState>();
179
+ const ctx = new ChatContext<IActionState>(
180
+ this.storage,
181
+ this.logger,
182
+ this.scheduler
183
+ );
166
184
 
167
185
  for (const [chatName, chatId] of Object.entries(this.chats)) {
168
186
  for (const scheduledAction of this.scheduled) {
@@ -170,15 +188,15 @@ export class BotInstance {
170
188
  this.name,
171
189
  scheduledAction,
172
190
  new ChatInfo(chatId, chatName),
173
- `Scheduled:${scheduledAction.key}:${chatId}`,
174
- this.storage
191
+ `Scheduled:${scheduledAction.key}:${chatId}`
175
192
  );
176
193
 
177
194
  try {
178
195
  const responses = await scheduledAction.exec(ctx);
179
196
  this.api.enqueueBatchedResponses(responses);
197
+ ctx.isInitialized = false;
180
198
  } catch (error) {
181
- Logger.errorWithTraceId(
199
+ this.logger.errorWithTraceId(
182
200
  ctx.botName,
183
201
  ctx.traceId,
184
202
  chatName,
@@ -193,21 +211,21 @@ export class BotInstance {
193
211
  }
194
212
 
195
213
  private async processMessage(msg: IncomingMessage) {
196
- const ctx = new MessageContext<IActionState>();
214
+ const ctx = new MessageContext<IActionState>(
215
+ this.storage,
216
+ this.logger,
217
+ this.scheduler
218
+ );
197
219
 
198
220
  for (const commandAction of this.commands) {
199
- ctx.initializeMessageContext(
200
- this.name,
201
- commandAction,
202
- msg,
203
- this.storage
204
- );
221
+ ctx.initializeMessageContext(this.name, commandAction, msg);
205
222
 
206
223
  try {
207
224
  const responses = await commandAction.exec(ctx);
208
225
  this.api.enqueueBatchedResponses(responses);
226
+ ctx.isInitialized = false;
209
227
  } catch (error) {
210
- Logger.errorWithTraceId(
228
+ this.logger.errorWithTraceId(
211
229
  ctx.botName,
212
230
  ctx.traceId,
213
231
  ctx.chatInfo.name,
@@ -14,56 +14,60 @@ import { BotResponse } from '../../types/response';
14
14
  import { Milliseconds } from '../../types/timeValues';
15
15
  import { DelayResponse } from '../../dtos/responses/delay';
16
16
  import { ChatInfo } from '../../dtos/chatInfo';
17
+ import { ILogger } from '../../types/logger';
18
+ import { IScheduler } from '../../types/scheduler';
17
19
 
18
20
  /**
19
21
  * Context of action executed in chat.
20
22
  */
21
23
  export class ChatContext<TActionState extends IActionState> {
22
24
  protected action!: IActionWithState<TActionState>;
23
- updateActions: Array<(state: TActionState) => void> = [];
25
+
26
+ /** Storage client instance for the bot executing this action. */
27
+ readonly storage!: IStorageClient;
28
+ /** Logger instance for the bot executing this action */
29
+ readonly logger!: ILogger;
30
+ /** Scheduler instance for the bot executing this action */
31
+ readonly scheduler!: IScheduler;
32
+
24
33
  /** Trace id of a action execution. */
25
34
  traceId!: number | string;
26
35
  /** Name of a bot that executes this action. */
27
36
  botName!: string;
28
37
  /** Chat information. */
29
38
  chatInfo!: ChatInfo;
30
- /** Storage client instance for this bot. */
31
- storage!: IStorageClient;
32
39
  /** Ordered collection of responses to be processed */
33
40
  responses: BotResponse[] = [];
34
41
 
35
42
  isInitialized = false;
36
43
 
37
- constructor() {}
44
+ constructor(
45
+ storage: IStorageClient,
46
+ logger: ILogger,
47
+ scheduler: IScheduler
48
+ ) {
49
+ this.storage = storage;
50
+ this.logger = logger;
51
+ this.scheduler = scheduler;
52
+ }
38
53
 
39
54
  initializeChatContext(
40
55
  botName: string,
41
56
  action: IActionWithState<TActionState>,
42
57
  chatInfo: ChatInfo,
43
- traceId: number | string,
44
- storage: IStorageClient
58
+ traceId: number | string
45
59
  ) {
46
60
  this.botName = botName;
47
61
  this.action = action;
48
62
  this.chatInfo = chatInfo;
49
63
  this.traceId = traceId;
50
- this.storage = storage;
51
64
 
52
- this.updateActions = [];
53
65
  this.isInitialized = true;
54
66
  this.responses = [];
55
67
 
56
68
  return this;
57
69
  }
58
70
 
59
- /**
60
- * Manually update the state of an action.
61
- * @param stateUpdateAction Function that will modify state.
62
- */
63
- updateState(stateUpdateAction: (state: TActionState) => void) {
64
- this.updateActions.push(stateUpdateAction);
65
- }
66
-
67
71
  /**
68
72
  * Sends text message to chat after action execution is finished.
69
73
  * 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.
@@ -15,6 +15,8 @@ import {
15
15
  } from '../../types/messageSendingOptions';
16
16
  import { IActionWithState, ActionKey } from '../../types/actionWithState';
17
17
  import { MessageTypeValue } from '../../types/messageTypes';
18
+ import { ILogger } from '../../types/logger';
19
+ import { IScheduler } from '../../types/scheduler';
18
20
  /**
19
21
  * Context of action executed in chat, in response to a message
20
22
  */
@@ -36,15 +38,18 @@ export class MessageContext<
36
38
  /** Type of message being received */
37
39
  messageType!: MessageTypeValue;
38
40
 
39
- constructor() {
40
- super();
41
+ constructor(
42
+ storage: IStorageClient,
43
+ logger: ILogger,
44
+ scheduler: IScheduler
45
+ ) {
46
+ super(storage, logger, scheduler);
41
47
  }
42
48
 
43
49
  initializeMessageContext(
44
50
  botName: string,
45
51
  action: IActionWithState<TActionState>,
46
- message: IncomingMessage,
47
- storage: IStorageClient
52
+ message: IncomingMessage
48
53
  ) {
49
54
  this.messageId = message.message_id;
50
55
  this.messageText = message.text ?? '';
@@ -61,8 +66,7 @@ export class MessageContext<
61
66
  botName,
62
67
  action,
63
68
  message.chatInfo,
64
- message.traceId,
65
- storage
69
+ message.traceId
66
70
  );
67
71
  }
68
72
 
package/helpers/noop.ts CHANGED
@@ -1,9 +1,12 @@
1
+ import { BotResponse } from '../types/response';
2
+
1
3
  /* eslint-disable @typescript-eslint/no-unused-vars */
2
4
  export class Noop {
3
- static async true<T1>(arg1: T1) {
5
+ static NoResponse: BotResponse[] = [];
6
+ static true<T1>(arg1: T1) {
4
7
  return true;
5
8
  }
6
- static async false<T1>(arg1: T1) {
9
+ static false<T1>(arg1: T1) {
7
10
  return false;
8
11
  }
9
12
  static async call<T1, T2>(arg1: T1, arg2: T2): Promise<void>;
package/index.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  export { startBot, stopBots } from './main';
2
2
  export { CommandActionBuilder } from './helpers/builders/commandActionBuilder';
3
3
  export { CommandActionBuilderWithState } from './helpers/builders/commandActionBuilder';
4
- export * from './helpers/builders/scheduledActionBuilder';
5
4
  export { IStorageClient } from './types/storage';
5
+ export { ILogger } from './types/logger';
6
+ export { IScheduler } from './types/scheduler';
7
+ export * from './helpers/builders/scheduledActionBuilder';
6
8
  export * from './types/actionState';
7
9
  export * from './entities/states/actionStateBase';
8
10
  export { Hours, Milliseconds, Seconds } from './types/timeValues';
package/main.ts CHANGED
@@ -1,19 +1,15 @@
1
1
  import { readFile } from 'fs/promises';
2
2
  import { IStorageClient } from './types/storage';
3
- import { Logger } from './services/logger';
4
3
  import { CommandAction } from './entities/actions/commandAction';
5
4
  import { ScheduledAction } from './entities/actions/scheduledAction';
6
5
  import { IActionState } from './types/actionState';
7
- import { Scheduler } from './services/taskScheduler';
8
6
  import { BotInstance } from './entities/botInstance';
9
7
  import { Seconds } from './types/timeValues';
8
+ import { IScheduler } from './types/scheduler';
9
+ import { ILogger } from './types/logger';
10
10
 
11
11
  const bots: BotInstance[] = [];
12
12
 
13
- function log(text: string) {
14
- Logger.logWithTraceId('ALL BOTS', 'System:Bot', 'System', text);
15
- }
16
-
17
13
  /**
18
14
  * Starts bot
19
15
  */
@@ -28,14 +24,20 @@ async function startBot(options: {
28
24
  scheduled: ScheduledAction<IActionState>[];
29
25
  /** Object containing chat name and chat id pairs. Used for logging and execution of scheduled action. */
30
26
  chats: Record<string, number>;
31
- /** Storage client for bot state storage. If not provided, default `JsonFileStorage` will be used. */
32
- storageClient?: IStorageClient;
33
27
  /** Storage path for default `JsonFileStorage` client. Will be used only if `storageClient` is not provided. If not provided, default value of `./storage/` will be used.*/
34
28
  storagePath?: string;
35
29
  /** Period of time between execution of scheduled actions. */
36
30
  scheduledPeriod?: Seconds;
37
31
  /** If true, telegram API objects will be logged instead of message content. */
38
32
  verboseLoggingForIncomingMessage?: boolean;
33
+ services?: {
34
+ /** Storage client for bot state storage. If not provided, default `JsonFileStorage` will be used. */
35
+ storageClient?: IStorageClient;
36
+ /** Logger client for bot logging. If not provided, default `JsonFileStorage` will be used. */
37
+ logger?: ILogger;
38
+ /** Scheduler client for bot scheduling. If not provided, default `NodeTimeoutScheduler` will be used. */
39
+ scheduler?: IScheduler;
40
+ };
39
41
  }) {
40
42
  const token = await readFile(options.tokenFilePath, 'utf8');
41
43
  const bot = new BotInstance({
@@ -44,11 +46,15 @@ async function startBot(options: {
44
46
  commands: options.commands,
45
47
  scheduled: options.scheduled,
46
48
  chats: options.chats,
47
- storageClient: options.storageClient,
48
49
  storagePath: options.storagePath,
49
50
  scheduledPeriod: options.scheduledPeriod,
50
51
  verboseLoggingForIncomingMessage:
51
- options.verboseLoggingForIncomingMessage
52
+ options.verboseLoggingForIncomingMessage,
53
+ services: {
54
+ storageClient: options.services?.storageClient,
55
+ logger: options.services?.logger,
56
+ scheduler: options.services?.scheduler
57
+ }
52
58
  });
53
59
  bots.push(bot);
54
60
 
@@ -59,11 +65,6 @@ async function startBot(options: {
59
65
  * Terminates all scheduled tasks, closes storage connections and stops all bots.
60
66
  */
61
67
  async function stopBots(reason: string) {
62
- log(`Recieved termination code: ${reason}`);
63
- Scheduler.stopAll();
64
- log('Acquiring storage semaphore...');
65
-
66
- log('Stopping bots...');
67
68
  for (const bot of bots) {
68
69
  await bot.stop(reason);
69
70
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chz-telegram-bot",
3
- "version": "0.0.51",
3
+ "version": "0.0.52",
4
4
  "type": "module",
5
5
  "dependencies": {
6
6
  "async-sema": "^3.1.1",
@@ -3,7 +3,6 @@ import { dirname } from 'path';
3
3
  import { mkdir, readFile, writeFile } from 'fs/promises';
4
4
  import { Sema as Semaphore } from 'async-sema';
5
5
  import { IStorageClient } from '../types/storage';
6
- import { ActionExecutionResult } from '../dtos/actionExecutionResult';
7
6
  import { IActionState } from '../types/actionState';
8
7
  import { IActionWithState, ActionKey } from '../types/actionWithState';
9
8
 
@@ -129,15 +128,14 @@ export class JsonFileStorage implements IStorageClient {
129
128
  async saveActionExecutionResult<TActionState extends IActionState>(
130
129
  action: IActionWithState<TActionState>,
131
130
  chatId: number,
132
- transactionResult: ActionExecutionResult<TActionState>
131
+ state: TActionState
133
132
  ) {
134
133
  await this.lock(action.key, async () => {
135
134
  const data = await this.loadInternal<TActionState>(action.key);
136
135
 
137
- if (transactionResult.shouldUpdate) {
138
- data[chatId] = transactionResult.data;
139
- await this.save(data, action.key);
140
- }
136
+ data[chatId] = state;
137
+
138
+ await this.save(data, action.key);
141
139
  });
142
140
  }
143
141
 
@@ -1,4 +1,6 @@
1
- class JsonLogger {
1
+ import { ILogger } from '../types/logger';
2
+
3
+ export class JsonLogger implements ILogger {
2
4
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
3
5
  private serializeError(error: any) {
4
6
  const plainObject: Record<string, unknown> = {};
@@ -46,5 +48,3 @@ class JsonLogger {
46
48
  );
47
49
  }
48
50
  }
49
-
50
- export const Logger = new JsonLogger();
@@ -1,10 +1,16 @@
1
1
  import { TaskRecord } from '../entities/taskRecord';
2
+ import { ILogger } from '../types/logger';
3
+ import { IScheduler } from '../types/scheduler';
2
4
  import { Milliseconds } from '../types/timeValues';
3
- import { Logger } from './logger';
4
5
 
5
- class TaskScheduler {
6
+ export class NodeTimeoutScheduler implements IScheduler {
7
+ private readonly logger!: ILogger;
6
8
  readonly activeTasks: TaskRecord[] = [];
7
9
 
10
+ constructor(logger: ILogger) {
11
+ this.logger = logger;
12
+ }
13
+
8
14
  stopAll() {
9
15
  this.activeTasks.forEach((task) => {
10
16
  clearInterval(task.taskId);
@@ -25,7 +31,7 @@ class TaskScheduler {
25
31
  setImmediate(action);
26
32
  }
27
33
 
28
- Logger.logWithTraceId(
34
+ this.logger.logWithTraceId(
29
35
  ownerName,
30
36
  `System:TaskScheduler-${ownerName}-${name}`,
31
37
  'System',
@@ -42,7 +48,7 @@ class TaskScheduler {
42
48
  ownerName: string
43
49
  ) {
44
50
  const actionWrapper = () => {
45
- Logger.logWithTraceId(
51
+ this.logger.logWithTraceId(
46
52
  ownerName,
47
53
  `System:TaskScheduler-${ownerName}-${name}`,
48
54
  'System',
@@ -52,7 +58,7 @@ class TaskScheduler {
52
58
  };
53
59
  const taskId = setTimeout(actionWrapper, delay);
54
60
 
55
- Logger.logWithTraceId(
61
+ this.logger.logWithTraceId(
56
62
  ownerName,
57
63
  `System:TaskScheduler-${ownerName}-${name}`,
58
64
  'System',
@@ -60,5 +66,3 @@ class TaskScheduler {
60
66
  );
61
67
  }
62
68
  }
63
-
64
- export const Scheduler = new TaskScheduler();
@@ -1,25 +1,32 @@
1
1
  import { Message } from 'telegraf/types';
2
2
  import { IStorageClient } from '../types/storage';
3
- import { Logger } from './logger';
4
3
  import { BotResponse, IReplyMessage } from '../types/response';
5
4
  import { Telegram } from 'telegraf/typings/telegram';
6
5
  import { setTimeout } from 'timers/promises';
7
6
  import { Milliseconds } from '../types/timeValues';
7
+ import { ILogger } from '../types/logger';
8
8
 
9
9
  const TELEGRAM_RATELIMIT_DELAY = 35 as Milliseconds;
10
10
 
11
11
  export class TelegramApiService {
12
- isFlushing = false;
13
- readonly messageQueue: BotResponse[] = [];
12
+ private readonly telegram: Telegram;
13
+ private readonly storage: IStorageClient;
14
+ private readonly logger: ILogger;
14
15
 
15
- readonly botName: string;
16
- readonly telegram: Telegram;
17
- readonly storage: IStorageClient;
16
+ private readonly botName: string;
17
+ private readonly messageQueue: BotResponse[] = [];
18
+ isFlushing = false;
18
19
 
19
- constructor(botName: string, telegram: Telegram, storage: IStorageClient) {
20
+ constructor(
21
+ botName: string,
22
+ telegram: Telegram,
23
+ storage: IStorageClient,
24
+ logger: ILogger
25
+ ) {
20
26
  this.telegram = telegram;
21
27
  this.botName = botName;
22
28
  this.storage = storage;
29
+ this.logger = logger;
23
30
  }
24
31
 
25
32
  enqueueBatchedResponses(responses: BotResponse[]) {
@@ -42,7 +49,7 @@ export class TelegramApiService {
42
49
  await this.processResponse(message);
43
50
  await setTimeout(TELEGRAM_RATELIMIT_DELAY);
44
51
  } catch (error) {
45
- Logger.errorWithTraceId(
52
+ this.logger.errorWithTraceId(
46
53
  this.botName,
47
54
  message.traceId,
48
55
  message.chatInfo.name,