chz-telegram-bot 0.3.7 → 0.3.9

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 (52) hide show
  1. package/dist/entities/botInstance.d.ts.map +1 -1
  2. package/dist/entities/botInstance.js +0 -1
  3. package/dist/entities/context/baseContext.d.ts +35 -0
  4. package/dist/entities/context/baseContext.d.ts.map +1 -0
  5. package/dist/entities/context/baseContext.js +32 -0
  6. package/dist/entities/context/chatContext.d.ts +4 -27
  7. package/dist/entities/context/chatContext.d.ts.map +1 -1
  8. package/dist/entities/context/chatContext.js +3 -19
  9. package/dist/entities/context/inlineQueryContext.d.ts +2 -15
  10. package/dist/entities/context/inlineQueryContext.d.ts.map +1 -1
  11. package/dist/entities/context/inlineQueryContext.js +3 -4
  12. package/dist/entities/context/messageContext.d.ts +2 -1
  13. package/dist/entities/context/messageContext.d.ts.map +1 -1
  14. package/dist/entities/context/replyContext.d.ts +2 -20
  15. package/dist/entities/context/replyContext.d.ts.map +1 -1
  16. package/dist/entities/context/replyContext.js +3 -5
  17. package/dist/services/actionProcessingService.d.ts.map +1 -1
  18. package/dist/services/actionProcessingService.js +4 -2
  19. package/dist/services/actionProcessors/baseProcessor.d.ts +20 -0
  20. package/dist/services/actionProcessors/baseProcessor.d.ts.map +1 -0
  21. package/dist/services/actionProcessors/baseProcessor.js +29 -0
  22. package/dist/services/actionProcessors/commandActionProcessor.d.ts +2 -7
  23. package/dist/services/actionProcessors/commandActionProcessor.d.ts.map +1 -1
  24. package/dist/services/actionProcessors/commandActionProcessor.js +12 -27
  25. package/dist/services/actionProcessors/inlineQueryActionProcessor.d.ts +2 -7
  26. package/dist/services/actionProcessors/inlineQueryActionProcessor.d.ts.map +1 -1
  27. package/dist/services/actionProcessors/inlineQueryActionProcessor.js +6 -15
  28. package/dist/services/actionProcessors/scheduledActionProcessor.d.ts +2 -6
  29. package/dist/services/actionProcessors/scheduledActionProcessor.d.ts.map +1 -1
  30. package/dist/services/actionProcessors/scheduledActionProcessor.js +5 -14
  31. package/dist/services/jsonFileStorage.d.ts +5 -4
  32. package/dist/services/jsonFileStorage.d.ts.map +1 -1
  33. package/dist/services/jsonFileStorage.js +40 -29
  34. package/dist/types/action.d.ts +3 -0
  35. package/dist/types/action.d.ts.map +1 -1
  36. package/dist/types/storage.d.ts +1 -1
  37. package/dist/types/storage.d.ts.map +1 -1
  38. package/entities/botInstance.ts +0 -2
  39. package/entities/context/baseContext.ts +58 -0
  40. package/entities/context/chatContext.ts +8 -45
  41. package/entities/context/inlineQueryContext.ts +3 -20
  42. package/entities/context/messageContext.ts +2 -1
  43. package/entities/context/replyContext.ts +5 -25
  44. package/package.json +1 -1
  45. package/services/actionProcessingService.ts +9 -5
  46. package/services/actionProcessors/baseProcessor.ts +60 -0
  47. package/services/actionProcessors/commandActionProcessor.ts +19 -42
  48. package/services/actionProcessors/inlineQueryActionProcessor.ts +16 -32
  49. package/services/actionProcessors/scheduledActionProcessor.ts +7 -23
  50. package/services/jsonFileStorage.ts +57 -41
  51. package/types/action.ts +3 -0
  52. package/types/storage.ts +1 -2
@@ -4,7 +4,6 @@ import { CommandAction } from '../../entities/actions/commandAction';
4
4
  import { ReplyCaptureAction } from '../../entities/actions/replyCaptureAction';
5
5
  import { MessageContext } from '../../entities/context/messageContext';
6
6
  import { ReplyContext } from '../../entities/context/replyContext';
7
- import { IActionWithState } from '../../types/action';
8
7
  import { IActionState } from '../../types/actionState';
9
8
  import { ILogger } from '../../types/logger';
10
9
  import { IScheduler } from '../../types/scheduler';
@@ -18,17 +17,11 @@ import {
18
17
  MessageType
19
18
  } from '../../types/messageTypes';
20
19
  import { typeSafeObjectFromEntries } from '../../helpers/objectFromEntries';
20
+ import { BaseActionProcessor } from './baseProcessor';
21
21
 
22
- export class CommandActionProcessor {
23
- private readonly storage: IStorageClient;
24
- private readonly scheduler: IScheduler;
25
- private readonly logger: ILogger;
22
+ export class CommandActionProcessor extends BaseActionProcessor {
23
+ private readonly replyCaptures: ReplyCaptureAction<IActionState>[] = [];
26
24
 
27
- private readonly botName: string;
28
- private readonly replyCaptures: ReplyCaptureAction<IActionState>[];
29
-
30
- private api!: TelegramApiService;
31
- private telegraf!: Telegraf;
32
25
  private commands = typeSafeObjectFromEntries(
33
26
  Object.values(MessageType).map((x) => [
34
27
  x,
@@ -42,12 +35,7 @@ export class CommandActionProcessor {
42
35
  scheduler: IScheduler,
43
36
  logger: ILogger
44
37
  ) {
45
- this.storage = storage;
46
- this.scheduler = scheduler;
47
- this.logger = logger;
48
-
49
- this.botName = botName;
50
- this.replyCaptures = [];
38
+ super(botName, storage, scheduler, logger);
51
39
  }
52
40
 
53
41
  initialize(
@@ -56,8 +44,7 @@ export class CommandActionProcessor {
56
44
  commands: CommandAction<IActionState>[],
57
45
  verboseLoggingForIncomingMessage: boolean
58
46
  ) {
59
- this.api = api;
60
- this.telegraf = telegraf;
47
+ this.initializeDependencies(api, telegraf);
61
48
 
62
49
  for (const msgType of Object.values(MessageType)) {
63
50
  if (msgType == MessageType.Text) {
@@ -155,35 +142,25 @@ export class CommandActionProcessor {
155
142
 
156
143
  const commandsToCheck = new Set(this.commands[msg.type]);
157
144
  if (msg.type != MessageType.Text && msg.text != '') {
158
- this.commands[MessageType.Text].map((x) => commandsToCheck.add(x));
145
+ this.commands[MessageType.Text].forEach((x) =>
146
+ commandsToCheck.add(x)
147
+ );
159
148
  }
160
149
 
161
150
  for (const commandAction of commandsToCheck) {
162
151
  this.initializeMessageContext(ctx, commandAction, msg);
163
-
164
- try {
165
- const responses = await commandAction.exec(ctx);
166
- this.api.enqueueBatchedResponses(responses);
167
- ctx.isInitialized = false;
168
- } catch (error) {
169
- ctx.logger.errorWithTraceId(error, ctx);
170
- }
152
+ this.executeAction(commandAction, ctx);
171
153
  }
172
154
 
173
- const replyCtx = new ReplyContext<IActionState>(
174
- this.storage,
175
- this.scheduler
176
- );
177
-
178
- for (const replyAction of this.replyCaptures) {
179
- this.initializeReplyCaptureContext(replyCtx, replyAction, msg);
155
+ if (this.replyCaptures.length != 0) {
156
+ const replyCtx = new ReplyContext<IActionState>(
157
+ this.storage,
158
+ this.scheduler
159
+ );
180
160
 
181
- try {
182
- const responses = await replyAction.exec(replyCtx);
183
- this.api.enqueueBatchedResponses(responses);
184
- replyCtx.isInitialized = false;
185
- } catch (error) {
186
- replyCtx.logger.errorWithTraceId(error, replyCtx);
161
+ for (const replyAction of this.replyCaptures) {
162
+ this.initializeReplyCaptureContext(replyCtx, replyAction, msg);
163
+ this.executeAction(replyAction, replyCtx);
187
164
  }
188
165
  }
189
166
 
@@ -219,9 +196,9 @@ export class CommandActionProcessor {
219
196
  );
220
197
  }
221
198
 
222
- private initializeMessageContext<TActionState extends IActionState>(
199
+ private initializeMessageContext(
223
200
  ctx: MessageContext<IActionState>,
224
- action: IActionWithState<TActionState>,
201
+ action: CommandAction<IActionState>,
225
202
  message: IncomingMessage
226
203
  ) {
227
204
  ctx.messageId = message.messageId;
@@ -9,16 +9,9 @@ import { IStorageClient } from '../../types/storage';
9
9
  import { Milliseconds } from '../../types/timeValues';
10
10
  import { TraceId } from '../../types/trace';
11
11
  import { TelegramApiService } from '../telegramApi';
12
+ import { BaseActionProcessor } from './baseProcessor';
12
13
 
13
- export class InlineQueryActionProcessor {
14
- private readonly storage: IStorageClient;
15
- private readonly scheduler: IScheduler;
16
- private readonly logger: ILogger;
17
-
18
- private readonly botName: string;
19
-
20
- private api!: TelegramApiService;
21
- private telegraf!: Telegraf;
14
+ export class InlineQueryActionProcessor extends BaseActionProcessor {
22
15
  private inlineQueries!: InlineQueryAction[];
23
16
 
24
17
  constructor(
@@ -27,11 +20,7 @@ export class InlineQueryActionProcessor {
27
20
  scheduler: IScheduler,
28
21
  logger: ILogger
29
22
  ) {
30
- this.storage = storage;
31
- this.scheduler = scheduler;
32
- this.logger = logger;
33
-
34
- this.botName = botName;
23
+ super(botName, storage, scheduler, logger);
35
24
  }
36
25
 
37
26
  initialize(
@@ -40,8 +29,7 @@ export class InlineQueryActionProcessor {
40
29
  inlineQueries: InlineQueryAction[],
41
30
  period: Milliseconds
42
31
  ) {
43
- this.api = api;
44
- this.telegraf = telegraf;
32
+ this.initializeDependencies(api, telegraf);
45
33
  this.inlineQueries = inlineQueries;
46
34
 
47
35
  let pendingInlineQueries: IncomingInlineQuery[] = [];
@@ -113,23 +101,19 @@ export class InlineQueryActionProcessor {
113
101
  inlineQuery.traceId
114
102
  );
115
103
 
116
- try {
117
- const responses = await inlineQueryAction.exec(
118
- ctx
119
- );
120
- this.api.enqueueBatchedResponses(responses);
121
- ctx.isInitialized = false;
122
- } catch (err) {
123
- const error = err as Error;
124
-
125
- if (error.name == 'AbortError') {
126
- ctx.logger.logWithTraceId(
127
- `Aborting query ${inlineQuery.queryId} (${inlineQuery.query}) successful.`
128
- );
129
- } else {
130
- ctx.logger.errorWithTraceId(error, ctx);
104
+ this.executeAction(
105
+ inlineQueryAction,
106
+ ctx,
107
+ (error, ctx) => {
108
+ if (error.name == 'AbortError') {
109
+ ctx.logger.logWithTraceId(
110
+ `Aborting query ${inlineQuery.queryId} (${inlineQuery.query}) successful.`
111
+ );
112
+ } else {
113
+ ctx.logger.errorWithTraceId(error, ctx);
114
+ }
131
115
  }
132
- }
116
+ );
133
117
  }
134
118
 
135
119
  queriesInProcessing.delete(inlineQuery.userId);
@@ -4,7 +4,6 @@ import { ScheduledAction } from '../../entities/actions/scheduledAction';
4
4
  import { ChatContext } from '../../entities/context/chatContext';
5
5
  import { secondsToMilliseconds } from '../../helpers/timeConvertions';
6
6
  import { createTrace } from '../../helpers/traceFactory';
7
- import { IActionWithState } from '../../types/action';
8
7
  import { IActionState } from '../../types/actionState';
9
8
  import { ILogger } from '../../types/logger';
10
9
  import { IScheduler } from '../../types/scheduler';
@@ -12,16 +11,11 @@ import { IStorageClient } from '../../types/storage';
12
11
  import { Seconds, Milliseconds } from '../../types/timeValues';
13
12
  import { TraceId } from '../../types/trace';
14
13
  import { TelegramApiService } from '../telegramApi';
14
+ import { BaseActionProcessor } from './baseProcessor';
15
15
 
16
- export class ScheduledActionProcessor {
17
- private readonly storage: IStorageClient;
18
- private readonly scheduler: IScheduler;
19
- private readonly logger: ILogger;
20
-
21
- private readonly botName: string;
16
+ export class ScheduledActionProcessor extends BaseActionProcessor {
22
17
  private readonly chats: Record<string, number>;
23
18
 
24
- private api!: TelegramApiService;
25
19
  private scheduled!: ScheduledAction<IActionState>[];
26
20
 
27
21
  constructor(
@@ -31,11 +25,7 @@ export class ScheduledActionProcessor {
31
25
  scheduler: IScheduler,
32
26
  logger: ILogger
33
27
  ) {
34
- this.storage = storage;
35
- this.scheduler = scheduler;
36
- this.logger = logger;
37
-
38
- this.botName = botName;
28
+ super(botName, storage, scheduler, logger);
39
29
  this.chats = chats;
40
30
  }
41
31
 
@@ -44,7 +34,7 @@ export class ScheduledActionProcessor {
44
34
  scheduled: ScheduledAction<IActionState>[],
45
35
  period: Seconds
46
36
  ) {
47
- this.api = api;
37
+ this.initializeDependencies(api, null!);
48
38
  this.scheduled = scheduled;
49
39
 
50
40
  if (this.scheduled.length > 0) {
@@ -106,22 +96,16 @@ export class ScheduledActionProcessor {
106
96
  )
107
97
  );
108
98
 
109
- try {
110
- const responses = await scheduledAction.exec(ctx);
111
- this.api.enqueueBatchedResponses(responses);
112
- ctx.isInitialized = false;
113
- } catch (error) {
114
- ctx.logger.errorWithTraceId(error, ctx);
115
- }
99
+ this.executeAction(scheduledAction, ctx);
116
100
  }
117
101
  }
118
102
 
119
103
  this.api.flushResponses();
120
104
  }
121
105
 
122
- private initializeChatContext<TActionState extends IActionState>(
106
+ private initializeChatContext(
123
107
  ctx: ChatContext<IActionState>,
124
- action: IActionWithState<TActionState>,
108
+ action: ScheduledAction<IActionState>,
125
109
  chatInfo: ChatInfo,
126
110
  traceId: TraceId
127
111
  ) {
@@ -5,7 +5,12 @@ import { IStorageClient } from '../types/storage';
5
5
  import { IActionState } from '../types/actionState';
6
6
  import { IActionWithState, ActionKey } from '../types/action';
7
7
 
8
+ function buildPath(storagePath: string, botName: string, actionKey: string) {
9
+ return `${storagePath}/${botName}/${actionKey.replaceAll(':', '/')}.json`;
10
+ }
11
+
8
12
  export class JsonFileStorage implements IStorageClient {
13
+ private readonly filePaths = new Map<ActionKey, string>();
9
14
  private readonly locks = new Map<ActionKey, Semaphore>();
10
15
  private readonly cache: Map<string, Record<number, IActionState>>;
11
16
  private readonly storagePath: string;
@@ -28,6 +33,10 @@ export class JsonFileStorage implements IStorageClient {
28
33
 
29
34
  for (const action of actions) {
30
35
  this.locks.set(action.key, new Semaphore(1));
36
+ this.filePaths.set(
37
+ action.key,
38
+ buildPath(this.storagePath, this.botName, action.key)
39
+ );
31
40
  }
32
41
  }
33
42
 
@@ -45,58 +54,61 @@ export class JsonFileStorage implements IStorageClient {
45
54
  }
46
55
  }
47
56
 
48
- private async loadInternal<TActionState extends IActionState>(
57
+ private tryGetFromCache<TActionState extends IActionState>(key: ActionKey) {
58
+ return this.cache.get(key) as Record<number, TActionState> | undefined;
59
+ }
60
+
61
+ private async loadFromFile<TActionState extends IActionState>(
49
62
  key: ActionKey
50
63
  ) {
51
- if (!this.cache.has(key)) {
52
- const targetPath = this.buidPathFromKey(key);
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)!;
53
70
 
54
- const fileContent = await readFile(targetPath, {
55
- encoding: 'utf-8',
56
- flag: 'a+'
57
- });
71
+ const fileContent = await readFile(targetPath, {
72
+ encoding: 'utf-8',
73
+ flag: 'a+'
74
+ });
58
75
 
59
- if (fileContent) {
60
- const data = JSON.parse(fileContent);
76
+ if (fileContent) {
77
+ const data = JSON.parse(fileContent);
61
78
 
62
- this.cache.set(key, data);
63
- }
79
+ this.cache.set(key, data);
64
80
  }
65
81
 
66
82
  return (this.cache.get(key) ?? {}) as Record<number, TActionState>;
67
83
  }
68
84
 
69
- private async save<TActionState extends IActionState>(
85
+ private async updateCacheAndSaveToFile<TActionState extends IActionState>(
70
86
  data: Record<number, TActionState>,
71
87
  key: ActionKey
72
88
  ) {
73
89
  this.cache.set(key, data);
74
90
 
75
- const targetPath = this.buidPathFromKey(key);
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)!;
76
97
 
77
98
  await writeFile(targetPath, JSON.stringify(data), { flag: 'w+' });
78
99
  }
79
100
 
80
- private buidPathFromKey(key: ActionKey) {
81
- return `${this.storagePath}/${this.botName}/${key.replaceAll(
82
- ':',
83
- '/'
84
- )}.json`;
85
- }
86
-
87
101
  async load<TActionState extends IActionState>(key: ActionKey) {
88
- return await this.lock(key, async () => {
89
- return await this.loadInternal<TActionState>(key);
90
- });
102
+ return (
103
+ this.tryGetFromCache<TActionState>(key) ??
104
+ (await this.lock(key, async () => {
105
+ return await this.loadFromFile<TActionState>(key);
106
+ }))
107
+ );
91
108
  }
92
109
 
93
- async saveMetadata(
94
- actions: IActionWithState<IActionState>[],
95
- botName: string
96
- ) {
97
- const targetPath = this.buidPathFromKey(
98
- `Metadata-${botName}` as ActionKey
99
- );
110
+ async saveMetadata(actions: IActionWithState<IActionState>[]) {
111
+ const targetPath = `${this.storagePath}/${this.botName}/Metadata-${this.botName}.json`;
100
112
 
101
113
  await writeFile(targetPath, JSON.stringify(actions), {
102
114
  flag: 'w+'
@@ -107,14 +119,13 @@ export class JsonFileStorage implements IStorageClient {
107
119
  action: IActionWithState<TActionState>,
108
120
  chatId: number
109
121
  ) {
110
- return await this.lock(action.key, async () => {
111
- const data = await this.loadInternal(action.key);
122
+ const value =
123
+ this.tryGetFromCache<TActionState>(action.key) ??
124
+ (await this.lock(action.key, async () => {
125
+ return await this.loadFromFile<TActionState>(action.key);
126
+ }));
112
127
 
113
- return Object.assign(
114
- action.stateConstructor(),
115
- data[chatId]
116
- ) as TActionState;
117
- });
128
+ return Object.assign(action.stateConstructor(), value[chatId]);
118
129
  }
119
130
 
120
131
  async saveActionExecutionResult<TActionState extends IActionState>(
@@ -123,11 +134,13 @@ export class JsonFileStorage implements IStorageClient {
123
134
  state: TActionState
124
135
  ) {
125
136
  return await this.lock(action.key, async () => {
126
- const data = await this.loadInternal<TActionState>(action.key);
137
+ const data =
138
+ this.tryGetFromCache<TActionState>(action.key) ??
139
+ (await this.loadFromFile<TActionState>(action.key));
127
140
 
128
141
  data[chatId] = state;
129
142
 
130
- await this.save(data, action.key);
143
+ await this.updateCacheAndSaveToFile(data, action.key);
131
144
  });
132
145
  }
133
146
 
@@ -143,7 +156,10 @@ export class JsonFileStorage implements IStorageClient {
143
156
  update: (state: TActionState) => Promise<void>
144
157
  ) {
145
158
  await this.lock(action.key, async () => {
146
- const data = await this.loadInternal<TActionState>(action.key);
159
+ const data =
160
+ this.tryGetFromCache<TActionState>(action.key) ??
161
+ (await this.loadFromFile<TActionState>(action.key));
162
+
147
163
  const state = Object.assign(
148
164
  action.stateConstructor(),
149
165
  data[chatId]
@@ -151,7 +167,7 @@ export class JsonFileStorage implements IStorageClient {
151
167
 
152
168
  await update(state);
153
169
 
154
- await this.save(data, action.key);
170
+ await this.updateCacheAndSaveToFile(data, action.key);
155
171
  });
156
172
  }
157
173
  }
package/types/action.ts CHANGED
@@ -1,4 +1,6 @@
1
+ import { BaseContext } from '../entities/context/baseContext';
1
2
  import { IActionState } from './actionState';
3
+ import { BotResponse } from './response';
2
4
 
3
5
  export type ActionKey = string & { __brand: 'actionKey' };
4
6
 
@@ -9,4 +11,5 @@ export interface IActionWithState<TActionState extends IActionState>
9
11
 
10
12
  export interface IAction {
11
13
  readonly key: ActionKey;
14
+ exec(ctx: BaseContext<IAction>): Promise<BotResponse[]>;
12
15
  }
package/types/storage.ts CHANGED
@@ -12,8 +12,7 @@ export interface IStorageClient {
12
12
  key: ActionKey
13
13
  ): Promise<Record<number, TActionState>>;
14
14
  saveMetadata<TActionState extends IActionState>(
15
- actions: IActionWithState<TActionState>[],
16
- botName: string
15
+ actions: IActionWithState<TActionState>[]
17
16
  ): Promise<void>;
18
17
  getActionState<TActionState extends IActionState>(
19
18
  action: IActionWithState<TActionState>,