chz-telegram-bot 0.0.26 → 0.0.27

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 (53) hide show
  1. package/dist/entities/actions/commandAction.d.ts +2 -2
  2. package/dist/entities/actions/commandAction.d.ts.map +1 -1
  3. package/dist/entities/actions/scheduledAction.d.ts +2 -2
  4. package/dist/entities/actions/scheduledAction.d.ts.map +1 -1
  5. package/dist/entities/botInstance.d.ts.map +1 -1
  6. package/dist/entities/botInstance.js +3 -2
  7. package/dist/entities/context/chatContext.d.ts +3 -2
  8. package/dist/entities/context/chatContext.d.ts.map +1 -1
  9. package/dist/entities/context/chatContext.js +6 -6
  10. package/dist/entities/context/messageContext.d.ts +2 -1
  11. package/dist/entities/context/messageContext.d.ts.map +1 -1
  12. package/dist/entities/context/messageContext.js +6 -6
  13. package/dist/entities/responses/imageMessage.d.ts +3 -2
  14. package/dist/entities/responses/imageMessage.d.ts.map +1 -1
  15. package/dist/entities/responses/imageMessage.js +2 -2
  16. package/dist/entities/responses/reaction.d.ts +3 -2
  17. package/dist/entities/responses/reaction.d.ts.map +1 -1
  18. package/dist/entities/responses/reaction.js +2 -2
  19. package/dist/entities/responses/textMessage.d.ts +3 -2
  20. package/dist/entities/responses/textMessage.d.ts.map +1 -1
  21. package/dist/entities/responses/textMessage.js +2 -2
  22. package/dist/entities/responses/unpin.d.ts +3 -2
  23. package/dist/entities/responses/unpin.d.ts.map +1 -1
  24. package/dist/entities/responses/unpin.js +2 -2
  25. package/dist/entities/responses/videoMessage.d.ts +3 -2
  26. package/dist/entities/responses/videoMessage.d.ts.map +1 -1
  27. package/dist/entities/responses/videoMessage.js +2 -2
  28. package/dist/services/jsonFileStorage.d.ts +6 -7
  29. package/dist/services/jsonFileStorage.d.ts.map +1 -1
  30. package/dist/services/jsonFileStorage.js +29 -23
  31. package/dist/services/telegramApi.js +4 -4
  32. package/dist/types/actionWithState.d.ts +4 -1
  33. package/dist/types/actionWithState.d.ts.map +1 -1
  34. package/dist/types/response.d.ts +2 -1
  35. package/dist/types/response.d.ts.map +1 -1
  36. package/dist/types/storage.d.ts +4 -4
  37. package/dist/types/storage.d.ts.map +1 -1
  38. package/entities/actions/commandAction.ts +3 -3
  39. package/entities/actions/scheduledAction.ts +3 -3
  40. package/entities/botInstance.ts +4 -5
  41. package/entities/context/chatContext.ts +8 -12
  42. package/entities/context/messageContext.ts +11 -7
  43. package/entities/responses/imageMessage.ts +4 -3
  44. package/entities/responses/reaction.ts +4 -3
  45. package/entities/responses/textMessage.ts +4 -3
  46. package/entities/responses/unpin.ts +4 -3
  47. package/entities/responses/videoMessage.ts +4 -3
  48. package/package.json +1 -1
  49. package/services/jsonFileStorage.ts +41 -27
  50. package/services/telegramApi.ts +4 -4
  51. package/types/actionWithState.ts +3 -1
  52. package/types/response.ts +3 -1
  53. package/types/storage.ts +4 -4
@@ -1,6 +1,9 @@
1
1
  import { IActionState } from './actionState';
2
+ export type ActionKey = string & {
3
+ __brand: 'actionKey';
4
+ };
2
5
  export interface IActionWithState {
3
- key: string;
6
+ key: ActionKey;
4
7
  stateConstructor: () => IActionState;
5
8
  }
6
9
  //# sourceMappingURL=actionWithState.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"actionWithState.d.ts","sourceRoot":"","sources":["../../types/actionWithState.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C,MAAM,WAAW,gBAAgB;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,gBAAgB,EAAE,MAAM,YAAY,CAAC;CACxC"}
1
+ {"version":3,"file":"actionWithState.d.ts","sourceRoot":"","sources":["../../types/actionWithState.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG;IAAE,OAAO,EAAE,WAAW,CAAA;CAAE,CAAC;AAE1D,MAAM,WAAW,gBAAgB;IAC7B,GAAG,EAAE,SAAS,CAAC;IACf,gBAAgB,EAAE,MAAM,YAAY,CAAC;CACxC"}
@@ -3,6 +3,7 @@ import { Reaction } from '../entities/responses/reaction';
3
3
  import { TextMessage } from '../entities/responses/textMessage';
4
4
  import { UnpinResponse } from '../entities/responses/unpin';
5
5
  import { VideoMessage } from '../entities/responses/videoMessage';
6
+ import { IActionWithState } from './actionWithState';
6
7
  export declare const BotResponseTypes: {
7
8
  readonly unpin: "unpin";
8
9
  readonly text: "text";
@@ -15,7 +16,7 @@ export interface IChatResponse {
15
16
  kind: keyof typeof BotResponseTypes;
16
17
  chatId: number;
17
18
  traceId: number | string;
18
- sourceActionKey: string;
19
+ action: IActionWithState;
19
20
  }
20
21
  export interface IReplyMessage<TType> extends IChatResponse {
21
22
  content: TType;
@@ -1 +1 @@
1
- {"version":3,"file":"response.d.ts","sourceRoot":"","sources":["../../types/response.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAClE,OAAO,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,mCAAmC,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAElE,eAAO,MAAM,gBAAgB;;;;;;CAMnB,CAAC;AAEX,MAAM,MAAM,WAAW,GACjB,aAAa,GACb,QAAQ,GACR,WAAW,GACX,YAAY,GACZ,YAAY,CAAC;AAEnB,MAAM,WAAW,aAAa;IAC1B,IAAI,EAAE,MAAM,OAAO,gBAAgB,CAAC;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,aAAa,CAAC,KAAK,CAAE,SAAQ,aAAa;IACvD,OAAO,EAAE,KAAK,CAAC;IACf,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,iBAAiB,EAAE,OAAO,CAAC;IAC3B,SAAS,EAAE,OAAO,CAAC;CACtB"}
1
+ {"version":3,"file":"response.d.ts","sourceRoot":"","sources":["../../types/response.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAClE,OAAO,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,mCAAmC,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAClE,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAErD,eAAO,MAAM,gBAAgB;;;;;;CAMnB,CAAC;AAEX,MAAM,MAAM,WAAW,GACjB,aAAa,GACb,QAAQ,GACR,WAAW,GACX,YAAY,GACZ,YAAY,CAAC;AAEnB,MAAM,WAAW,aAAa;IAC1B,IAAI,EAAE,MAAM,OAAO,gBAAgB,CAAC;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;IAEzB,MAAM,EAAE,gBAAgB,CAAC;CAC5B;AAED,MAAM,WAAW,aAAa,CAAC,KAAK,CAAE,SAAQ,aAAa;IACvD,OAAO,EAAE,KAAK,CAAC;IACf,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,iBAAiB,EAAE,OAAO,CAAC;IAC3B,SAAS,EAAE,OAAO,CAAC;CACtB"}
@@ -1,12 +1,12 @@
1
1
  import { ActionExecutionResult } from '../entities/actionExecutionResult';
2
2
  import { IActionState } from './actionState';
3
- import { IActionWithState } from './actionWithState';
3
+ import { ActionKey, IActionWithState } from './actionWithState';
4
4
  export interface IStorageClient {
5
- updateStateFor<TActionState extends IActionState>(sourceActionKey: string, chatId: number, update: (state: TActionState) => Promise<void>): Promise<void>;
5
+ updateStateFor<TActionState extends IActionState>(action: IActionWithState, chatId: number, update: (state: TActionState) => Promise<void>): Promise<void>;
6
6
  close(): Promise<void>;
7
- load<TActionState extends IActionState>(key: string): Promise<Record<number, TActionState>>;
7
+ load<TActionState extends IActionState>(key: ActionKey): Promise<Record<number, TActionState>>;
8
8
  saveMetadata(actions: IActionWithState[], botName: string): Promise<void>;
9
- getActionState<TActionState extends IActionState>(entity: IActionWithState, chatId: number): Promise<TActionState>;
9
+ getActionState<TActionState extends IActionState>(action: IActionWithState, chatId: number): Promise<TActionState>;
10
10
  saveActionExecutionResult(action: IActionWithState, chatId: number, transactionResult: ActionExecutionResult): Promise<void>;
11
11
  }
12
12
  //# sourceMappingURL=storage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../types/storage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,mCAAmC,CAAC;AAC1E,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAErD,MAAM,WAAW,cAAc;IAC3B,cAAc,CAAC,YAAY,SAAS,YAAY,EAC5C,eAAe,EAAE,MAAM,EACvB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,GAC/C,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,CAAC,YAAY,SAAS,YAAY,EAClC,GAAG,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC;IACzC,YAAY,CAAC,OAAO,EAAE,gBAAgB,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1E,cAAc,CAAC,YAAY,SAAS,YAAY,EAC5C,MAAM,EAAE,gBAAgB,EACxB,MAAM,EAAE,MAAM,GACf,OAAO,CAAC,YAAY,CAAC,CAAC;IACzB,yBAAyB,CACrB,MAAM,EAAE,gBAAgB,EACxB,MAAM,EAAE,MAAM,EACd,iBAAiB,EAAE,qBAAqB,GACzC,OAAO,CAAC,IAAI,CAAC,CAAC;CACpB"}
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../types/storage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,mCAAmC,CAAC;AAC1E,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAEhE,MAAM,WAAW,cAAc;IAC3B,cAAc,CAAC,YAAY,SAAS,YAAY,EAC5C,MAAM,EAAE,gBAAgB,EACxB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,GAC/C,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,CAAC,YAAY,SAAS,YAAY,EAClC,GAAG,EAAE,SAAS,GACf,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC;IACzC,YAAY,CAAC,OAAO,EAAE,gBAAgB,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1E,cAAc,CAAC,YAAY,SAAS,YAAY,EAC5C,MAAM,EAAE,gBAAgB,EACxB,MAAM,EAAE,MAAM,GACf,OAAO,CAAC,YAAY,CAAC,CAAC;IACzB,yBAAyB,CACrB,MAAM,EAAE,gBAAgB,EACxB,MAAM,EAAE,MAAM,EACd,iBAAiB,EAAE,qBAAqB,GACzC,OAAO,CAAC,IAAI,CAAC,CAAC;CACpB"}
@@ -5,7 +5,7 @@ import { Seconds } from '../../types/timeValues';
5
5
  import { secondsToMilliseconds } from '../../helpers/timeConvertions';
6
6
  import { toArray } from '../../helpers/toArray';
7
7
  import { IActionState } from '../../types/actionState';
8
- import { IActionWithState } from '../../types/actionWithState';
8
+ import { IActionWithState, ActionKey } from '../../types/actionWithState';
9
9
  import { CommandTriggerCheckResult } from '../commandTriggerCheckResult';
10
10
  import { MessageContext } from '../context/messageContext';
11
11
  import { Logger } from '../../services/logger';
@@ -23,7 +23,7 @@ export class CommandAction<TActionState extends IActionState>
23
23
  allowedUsers: number[];
24
24
  condition: CommandCondition<TActionState>;
25
25
  stateConstructor: () => TActionState;
26
- key: string;
26
+ key: ActionKey;
27
27
 
28
28
  constructor(
29
29
  trigger: string | RegExp | string[] | RegExp[],
@@ -46,7 +46,7 @@ export class CommandAction<TActionState extends IActionState>
46
46
  this.condition = condition;
47
47
  this.stateConstructor = stateConstructor;
48
48
 
49
- this.key = `command:${this.name.replace('.', '-')}`;
49
+ this.key = `command:${this.name.replace('.', '-')}` as ActionKey;
50
50
  }
51
51
 
52
52
  async exec(ctx: MessageContext<TActionState>) {
@@ -4,7 +4,7 @@ import { ScheduledHandler } from '../../types/handlers';
4
4
  import { hoursToMilliseconds } from '../../helpers/timeConvertions';
5
5
  import { HoursOfDay } from '../../types/timeValues';
6
6
  import { IActionState } from '../../types/actionState';
7
- import { IActionWithState } from '../../types/actionWithState';
7
+ import { IActionWithState, ActionKey } from '../../types/actionWithState';
8
8
  import { CachedStateFactory } from '../cachedStateFactory';
9
9
  import { ChatContext } from '../context/chatContext';
10
10
  import { ActionExecutionResult } from '../actionExecutionResult';
@@ -20,7 +20,7 @@ export class ScheduledAction<TActionState extends IActionState>
20
20
  timeinHours: HoursOfDay;
21
21
  active: boolean;
22
22
  chatsWhitelist: number[];
23
- key: string;
23
+ key: ActionKey;
24
24
 
25
25
  cachedState = new Map<string, unknown>();
26
26
  stateConstructor: () => TActionState;
@@ -42,7 +42,7 @@ export class ScheduledAction<TActionState extends IActionState>
42
42
  this.active = active;
43
43
  this.chatsWhitelist = whitelist;
44
44
  this.cachedStateFactories = cachedStateFactories;
45
- this.key = `scheduled:${this.name.replace('.', '-')}`;
45
+ this.key = `scheduled:${this.name.replace('.', '-')}` as ActionKey;
46
46
  this.stateConstructor = stateConstructor;
47
47
  }
48
48
 
@@ -35,6 +35,8 @@ export class BotInstance {
35
35
  this.scheduled = options.scheduled;
36
36
  this.chats = options.chats;
37
37
 
38
+ const actions = [...this.commands, ...this.scheduled];
39
+
38
40
  Logger.logWithTraceId(
39
41
  this.name,
40
42
  `System:Bot-${this.name}-Start`,
@@ -44,7 +46,7 @@ export class BotInstance {
44
46
  this.telegraf = new Telegraf(options.token);
45
47
  this.storage =
46
48
  options.storageClient ??
47
- new JsonFileStorage(options.name, options.storagePath);
49
+ new JsonFileStorage(options.name, actions, options.storagePath);
48
50
  this.api = new TelegramApiService(
49
51
  this.name,
50
52
  this.telegraf.telegram,
@@ -55,10 +57,7 @@ export class BotInstance {
55
57
  this.initializeMessageProcessing();
56
58
  this.initializeScheduledProcessing();
57
59
 
58
- this.storage.saveMetadata(
59
- [...this.commands, ...this.scheduled],
60
- this.name
61
- );
60
+ this.storage.saveMetadata(actions, this.name);
62
61
 
63
62
  this.telegraf.launch();
64
63
  }
@@ -9,12 +9,13 @@ import {
9
9
  MessageSendingOptions,
10
10
  TextMessageSendingOptions
11
11
  } from '../../types/messageSendingOptions';
12
+ import { IActionWithState } from '../../types/actionWithState';
12
13
 
13
14
  /**
14
15
  * Context of action executed in chat.
15
16
  */
16
17
  export class ChatContext<TActionState> {
17
- protected actionKey: string;
18
+ protected action: IActionWithState;
18
19
  protected interactions: IBotApiInteractions;
19
20
  updateActions: Array<(state: TActionState) => void> = [];
20
21
  /** Trace id of a action execution. */
@@ -30,7 +31,7 @@ export class ChatContext<TActionState> {
30
31
 
31
32
  constructor(
32
33
  botName: string,
33
- actionKey: string,
34
+ action: IActionWithState,
34
35
  interactions: IBotApiInteractions,
35
36
  chatId: number,
36
37
  chatName: string,
@@ -38,7 +39,7 @@ export class ChatContext<TActionState> {
38
39
  storage: IStorageClient
39
40
  ) {
40
41
  this.botName = botName;
41
- this.actionKey = actionKey;
42
+ this.action = action;
42
43
  this.interactions = interactions;
43
44
  this.chatId = chatId;
44
45
  this.chatName = chatName;
@@ -66,7 +67,7 @@ export class ChatContext<TActionState> {
66
67
  this.chatId,
67
68
  undefined,
68
69
  this.traceId,
69
- this.actionKey,
70
+ this.action,
70
71
  options
71
72
  )
72
73
  );
@@ -85,7 +86,7 @@ export class ChatContext<TActionState> {
85
86
  this.chatId,
86
87
  undefined,
87
88
  this.traceId,
88
- this.actionKey,
89
+ this.action,
89
90
  options
90
91
  )
91
92
  );
@@ -104,7 +105,7 @@ export class ChatContext<TActionState> {
104
105
  this.chatId,
105
106
  undefined,
106
107
  this.traceId,
107
- this.actionKey,
108
+ this.action,
108
109
  options
109
110
  )
110
111
  );
@@ -116,12 +117,7 @@ export class ChatContext<TActionState> {
116
117
  */
117
118
  unpinMessage(messageId: number) {
118
119
  this.interactions.unpin(
119
- new UnpinResponse(
120
- messageId,
121
- this.chatId,
122
- this.traceId,
123
- this.actionKey
124
- )
120
+ new UnpinResponse(messageId, this.chatId, this.traceId, this.action)
125
121
  );
126
122
  }
127
123
  }
@@ -14,6 +14,7 @@ import {
14
14
  MessageSendingOptions,
15
15
  TextMessageSendingOptions
16
16
  } from '../../types/messageSendingOptions';
17
+ import { IActionWithState, ActionKey } from '../../types/actionWithState';
17
18
 
18
19
  /**
19
20
  * Context of action executed in chat, in response to a message
@@ -36,14 +37,14 @@ export class MessageContext<
36
37
 
37
38
  constructor(
38
39
  botName: string,
39
- scheduledKey: string,
40
+ action: IActionWithState,
40
41
  interactions: IBotApiInteractions,
41
42
  message: IncomingMessage,
42
43
  storage: IStorageClient
43
44
  ) {
44
45
  super(
45
46
  botName,
46
- scheduledKey,
47
+ action,
47
48
  interactions,
48
49
  message.chat.id,
49
50
  message.chatName,
@@ -67,7 +68,10 @@ export class MessageContext<
67
68
  async loadStateOf<TAnotherActionState extends IActionState>(
68
69
  commandName: string
69
70
  ): Promise<TAnotherActionState> {
70
- const storageKey = `command:${commandName.replace('.', '-')}`;
71
+ const storageKey = `command:${commandName.replace(
72
+ '.',
73
+ '-'
74
+ )}` as ActionKey;
71
75
  const allStates = await this.storage.load(storageKey);
72
76
  const stateForChat = allStates[this.chatId];
73
77
 
@@ -90,7 +94,7 @@ export class MessageContext<
90
94
  this.chatId,
91
95
  this.messageId,
92
96
  this.traceId,
93
- this.actionKey,
97
+ this.action,
94
98
  options
95
99
  )
96
100
  );
@@ -109,7 +113,7 @@ export class MessageContext<
109
113
  this.chatId,
110
114
  this.messageId,
111
115
  this.traceId,
112
- this.actionKey,
116
+ this.action,
113
117
  options
114
118
  )
115
119
  );
@@ -128,7 +132,7 @@ export class MessageContext<
128
132
  this.chatId,
129
133
  this.messageId,
130
134
  this.traceId,
131
- this.actionKey,
135
+ this.action,
132
136
  options
133
137
  )
134
138
  );
@@ -145,7 +149,7 @@ export class MessageContext<
145
149
  this.chatId,
146
150
  this.messageId,
147
151
  emoji,
148
- this.actionKey
152
+ this.action
149
153
  )
150
154
  );
151
155
  }
@@ -1,6 +1,7 @@
1
1
  import { InputFile } from 'telegraf/types';
2
2
  import { BotResponseTypes, IReplyMessage } from '../../types/response';
3
3
  import { MessageSendingOptions } from '../../types/messageSendingOptions';
4
+ import { IActionWithState } from '../../types/actionWithState';
4
5
 
5
6
  export class ImageMessage implements IReplyMessage<InputFile> {
6
7
  kind = BotResponseTypes.image;
@@ -11,14 +12,14 @@ export class ImageMessage implements IReplyMessage<InputFile> {
11
12
  traceId: string | number;
12
13
  disableWebPreview = false;
13
14
  shouldPin: boolean;
14
- sourceActionKey: string;
15
+ action: IActionWithState;
15
16
 
16
17
  constructor(
17
18
  image: InputFile,
18
19
  chatId: number,
19
20
  replyId: number | undefined,
20
21
  traceId: number | string,
21
- sourceActionKey: string,
22
+ action: IActionWithState,
22
23
  options?: MessageSendingOptions
23
24
  ) {
24
25
  this.content = image;
@@ -26,6 +27,6 @@ export class ImageMessage implements IReplyMessage<InputFile> {
26
27
  this.replyId = replyId;
27
28
  this.traceId = traceId;
28
29
  this.shouldPin = options?.pin ?? false;
29
- this.sourceActionKey = sourceActionKey;
30
+ this.action = action;
30
31
  }
31
32
  }
@@ -1,5 +1,6 @@
1
1
  import { TelegramEmoji } from 'telegraf/types';
2
2
  import { BotResponseTypes, IChatResponse } from '../../types/response';
3
+ import { IActionWithState } from '../../types/actionWithState';
3
4
 
4
5
  export class Reaction implements IChatResponse {
5
6
  kind = BotResponseTypes.react;
@@ -8,19 +9,19 @@ export class Reaction implements IChatResponse {
8
9
  messageId: number;
9
10
  traceId: number | string;
10
11
  emoji: TelegramEmoji;
11
- sourceActionKey: string;
12
+ action: IActionWithState;
12
13
 
13
14
  constructor(
14
15
  traceId: number | string,
15
16
  chatId: number,
16
17
  messageId: number,
17
18
  emoji: TelegramEmoji,
18
- sourceActionKey: string
19
+ action: IActionWithState
19
20
  ) {
20
21
  this.chatId = chatId;
21
22
  this.messageId = messageId;
22
23
  this.emoji = emoji;
23
24
  this.traceId = traceId;
24
- this.sourceActionKey = sourceActionKey;
25
+ this.action = action;
25
26
  }
26
27
  }
@@ -1,5 +1,6 @@
1
1
  import { TextMessageSendingOptions } from '../../types/messageSendingOptions';
2
2
  import { BotResponseTypes, IReplyMessage } from '../../types/response';
3
+ import { IActionWithState } from '../../types/actionWithState';
3
4
 
4
5
  export class TextMessage implements IReplyMessage<string> {
5
6
  kind = BotResponseTypes.text;
@@ -10,14 +11,14 @@ export class TextMessage implements IReplyMessage<string> {
10
11
  traceId: string | number;
11
12
  disableWebPreview: boolean;
12
13
  shouldPin: boolean;
13
- sourceActionKey: string;
14
+ action: IActionWithState;
14
15
 
15
16
  constructor(
16
17
  text: string,
17
18
  chatId: number,
18
19
  replyId: number | undefined,
19
20
  traceId: string | number,
20
- sourceActionKey: string,
21
+ action: IActionWithState,
21
22
  options?: TextMessageSendingOptions
22
23
  ) {
23
24
  this.content = text;
@@ -26,6 +27,6 @@ export class TextMessage implements IReplyMessage<string> {
26
27
  this.traceId = traceId;
27
28
  this.disableWebPreview = options?.disableWebPreview ?? false;
28
29
  this.shouldPin = options?.pin ?? false;
29
- this.sourceActionKey = sourceActionKey;
30
+ this.action = action;
30
31
  }
31
32
  }
@@ -1,4 +1,5 @@
1
1
  import { BotResponseTypes, IChatResponse } from '../../types/response';
2
+ import { IActionWithState } from '../../types/actionWithState';
2
3
 
3
4
  export class UnpinResponse implements IChatResponse {
4
5
  kind = BotResponseTypes.unpin;
@@ -6,17 +7,17 @@ export class UnpinResponse implements IChatResponse {
6
7
  messageId: number;
7
8
  chatId: number;
8
9
  traceId: number | string;
9
- sourceActionKey: string;
10
+ action: IActionWithState;
10
11
 
11
12
  constructor(
12
13
  messageId: number,
13
14
  chatId: number,
14
15
  traceId: number | string,
15
- sourceActionKey: string
16
+ action: IActionWithState
16
17
  ) {
17
18
  this.messageId = messageId;
18
19
  this.chatId = chatId;
19
20
  this.traceId = traceId;
20
- this.sourceActionKey = sourceActionKey;
21
+ this.action = action;
21
22
  }
22
23
  }
@@ -1,6 +1,7 @@
1
1
  import { InputFile } from 'telegraf/types';
2
2
  import { BotResponseTypes, IReplyMessage } from '../../types/response';
3
3
  import { MessageSendingOptions } from '../../types/messageSendingOptions';
4
+ import { IActionWithState } from '../../types/actionWithState';
4
5
 
5
6
  export class VideoMessage implements IReplyMessage<InputFile> {
6
7
  kind = BotResponseTypes.video;
@@ -11,14 +12,14 @@ export class VideoMessage implements IReplyMessage<InputFile> {
11
12
  traceId: string | number;
12
13
  disableWebPreview = false;
13
14
  shouldPin: boolean;
14
- sourceActionKey: string;
15
+ action: IActionWithState;
15
16
 
16
17
  constructor(
17
18
  video: InputFile,
18
19
  chatId: number,
19
20
  replyId: number | undefined,
20
21
  traceId: number | string,
21
- sourceActionKey: string,
22
+ action: IActionWithState,
22
23
  options?: MessageSendingOptions
23
24
  ) {
24
25
  this.content = video;
@@ -26,6 +27,6 @@ export class VideoMessage implements IReplyMessage<InputFile> {
26
27
  this.replyId = replyId;
27
28
  this.traceId = traceId;
28
29
  this.shouldPin = options?.pin ?? false;
29
- this.sourceActionKey = sourceActionKey;
30
+ this.action = action;
30
31
  }
31
32
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chz-telegram-bot",
3
- "version": "0.0.26",
3
+ "version": "0.0.27",
4
4
  "type": "module",
5
5
  "dependencies": {
6
6
  "async-sema": "^3.1.1",
@@ -6,15 +6,15 @@ import { IStorageClient } from '../types/storage';
6
6
  import { ActionStateBase } from '../entities/states/actionStateBase';
7
7
  import { ActionExecutionResult } from '../entities/actionExecutionResult';
8
8
  import { IActionState } from '../types/actionState';
9
- import { IActionWithState } from '../types/actionWithState';
9
+ import { IActionWithState, ActionKey } from '../types/actionWithState';
10
10
 
11
11
  export class JsonFileStorage implements IStorageClient {
12
- semaphore = new Semaphore(1);
12
+ private locks = new Map<ActionKey, Semaphore>();
13
13
  private cache: Map<string, Record<number, IActionState>>;
14
14
  private storagePath: string;
15
15
  private botName: string;
16
16
 
17
- constructor(botName: string, path?: string) {
17
+ constructor(botName: string, actions: IActionWithState[], path?: string) {
18
18
  this.cache = new Map<string, Record<number, IActionState>>();
19
19
  this.botName = botName;
20
20
  this.storagePath = path ?? 'storage';
@@ -24,19 +24,31 @@ export class JsonFileStorage implements IStorageClient {
24
24
  recursive: true
25
25
  });
26
26
  }
27
+
28
+ for (const action of actions) {
29
+ this.locks.set(action.key, new Semaphore(1));
30
+ }
27
31
  }
28
32
 
29
- private async lock<TType>(action: () => Promise<TType>) {
30
- await this.semaphore.acquire();
33
+ private async lock<TType>(key: ActionKey, action: () => Promise<TType>) {
34
+ const lock = this.locks.get(key);
35
+
36
+ if (!lock) {
37
+ throw new Error(`Lock for action ${key} not found`);
38
+ }
39
+
40
+ await lock.acquire();
31
41
 
32
42
  try {
33
43
  return await action();
34
44
  } finally {
35
- this.semaphore.release();
45
+ lock.release();
36
46
  }
37
47
  }
38
48
 
39
- private async loadInternal<TActionState extends IActionState>(key: string) {
49
+ private async loadInternal<TActionState extends IActionState>(
50
+ key: ActionKey
51
+ ) {
40
52
  if (!this.cache.has(key)) {
41
53
  const targetPath = this.buidPathFromKey(key);
42
54
  if (!existsSync(targetPath)) {
@@ -55,7 +67,7 @@ export class JsonFileStorage implements IStorageClient {
55
67
  return (this.cache.get(key) ?? {}) as Record<number, TActionState>;
56
68
  }
57
69
 
58
- private async save(data: Record<number, ActionStateBase>, key: string) {
70
+ private async save(data: Record<number, ActionStateBase>, key: ActionKey) {
59
71
  this.cache.delete(key);
60
72
 
61
73
  const targetPath = this.buidPathFromKey(key);
@@ -68,38 +80,38 @@ export class JsonFileStorage implements IStorageClient {
68
80
  await writeFile(targetPath, JSON.stringify(data), { flag: 'w+' });
69
81
  }
70
82
 
71
- private buidPathFromKey(key: string) {
83
+ private buidPathFromKey(key: ActionKey) {
72
84
  return `${this.storagePath}/${this.botName}/${key.replaceAll(
73
85
  ':',
74
86
  '/'
75
87
  )}.json`;
76
88
  }
77
89
 
78
- async load<TActionState extends IActionState>(key: string) {
79
- return await this.lock(async () => {
90
+ async load<TActionState extends IActionState>(key: ActionKey) {
91
+ return await this.lock(key, async () => {
80
92
  return this.loadInternal<TActionState>(key);
81
93
  });
82
94
  }
83
95
 
84
96
  async saveMetadata(actions: IActionWithState[], botName: string) {
85
- return await this.lock(async () => {
86
- const targetPath = this.buidPathFromKey(`Metadata-${botName}`);
97
+ const targetPath = this.buidPathFromKey(
98
+ `Metadata-${botName}` as ActionKey
99
+ );
87
100
 
88
- await writeFile(targetPath, JSON.stringify(actions), {
89
- flag: 'w+'
90
- });
101
+ await writeFile(targetPath, JSON.stringify(actions), {
102
+ flag: 'w+'
91
103
  });
92
104
  }
93
105
 
94
106
  async getActionState<TActionState extends IActionState>(
95
- entity: IActionWithState,
107
+ action: IActionWithState,
96
108
  chatId: number
97
109
  ) {
98
- return await this.lock(async () => {
99
- const data = await this.loadInternal(entity.key);
110
+ return await this.lock(action.key, async () => {
111
+ const data = await this.loadInternal(action.key);
100
112
 
101
113
  return Object.assign(
102
- entity.stateConstructor(),
114
+ action.stateConstructor(),
103
115
  data[chatId]
104
116
  ) as TActionState;
105
117
  });
@@ -110,7 +122,7 @@ export class JsonFileStorage implements IStorageClient {
110
122
  chatId: number,
111
123
  transactionResult: ActionExecutionResult
112
124
  ) {
113
- await this.lock(async () => {
125
+ await this.lock(action.key, async () => {
114
126
  const data = await this.loadInternal(action.key);
115
127
 
116
128
  if (transactionResult.shouldUpdate) {
@@ -121,24 +133,26 @@ export class JsonFileStorage implements IStorageClient {
121
133
  }
122
134
 
123
135
  async close(): Promise<void> {
124
- await this.semaphore.acquire();
136
+ for (const lock of this.locks.values()) {
137
+ await lock.acquire();
138
+ }
125
139
  }
126
140
 
127
141
  async updateStateFor<TActionState extends IActionState>(
128
- sourceActionKey: string,
142
+ action: IActionWithState,
129
143
  chatId: number,
130
144
  update: (state: TActionState) => Promise<void>
131
145
  ) {
132
- await this.lock(async () => {
133
- const data = await this.loadInternal(sourceActionKey);
146
+ await this.lock(action.key, async () => {
147
+ const data = await this.loadInternal(action.key);
134
148
  const state = Object.assign(
135
- new ActionStateBase(),
149
+ action.stateConstructor(),
136
150
  data[chatId]
137
151
  ) as TActionState;
138
152
 
139
153
  await update(state);
140
154
 
141
- await this.save(data, sourceActionKey);
155
+ await this.save(data, action.key);
142
156
  });
143
157
  }
144
158
  }
@@ -80,7 +80,7 @@ export class TelegramApiService {
80
80
  );
81
81
 
82
82
  await this.storage.updateStateFor(
83
- response.sourceActionKey,
83
+ response.action,
84
84
  response.chatId,
85
85
  async (state) => {
86
86
  state.pinnedMessages.push(sentMessage.message_id);
@@ -152,7 +152,7 @@ export class TelegramApiService {
152
152
  );
153
153
 
154
154
  await this.storage.updateStateFor(
155
- response.sourceActionKey,
155
+ response.action,
156
156
  response.chatId,
157
157
  async (state) => {
158
158
  state.pinnedMessages = state.pinnedMessages.filter(
@@ -182,7 +182,7 @@ export class TelegramApiService {
182
182
  ) {
183
183
  return new MessageContext<TActionState>(
184
184
  this.botName,
185
- command.key,
185
+ command,
186
186
  this.getInteractions(),
187
187
  incomingMessage,
188
188
  this.storage
@@ -195,7 +195,7 @@ export class TelegramApiService {
195
195
  ) {
196
196
  return new ChatContext<TActionState>(
197
197
  this.botName,
198
- scheduledAction.key,
198
+ scheduledAction,
199
199
  this.getInteractions(),
200
200
  chatId,
201
201
  this.chats[chatId],
@@ -1,6 +1,8 @@
1
1
  import { IActionState } from './actionState';
2
2
 
3
+ export type ActionKey = string & { __brand: 'actionKey' };
4
+
3
5
  export interface IActionWithState {
4
- key: string;
6
+ key: ActionKey;
5
7
  stateConstructor: () => IActionState;
6
8
  }