chz-telegram-bot 0.3.8 → 0.3.10

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.
@@ -1 +1 @@
1
- {"version":3,"file":"botInstance.d.ts","sourceRoot":"","sources":["../../entities/botInstance.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAElD,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE5D,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAGhD,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAGhE,qBAAa,WAAW;IACpB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiB;IACzC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAa;IACvC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAU;IACjC,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAA0B;IAElE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,OAAO,EAAE;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE;YACL,QAAQ,EAAE,aAAa,CAAC,YAAY,CAAC,EAAE,CAAC;YACxC,SAAS,EAAE,eAAe,CAAC,YAAY,CAAC,EAAE,CAAC;YAC3C,aAAa,EAAE,iBAAiB,EAAE,CAAC;SACtC,CAAC;QACF,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,EAAE;YACP,aAAa,CAAC,EAAE,cAAc,CAAC;YAC/B,MAAM,CAAC,EAAE,OAAO,CAAC;YACjB,SAAS,CAAC,EAAE,UAAU,CAAC;SAC1B,CAAC;KACL;IA0BK,KAAK,CACP,KAAK,EAAE,MAAM,EACb,OAAO,EAAE;QACL,QAAQ,EAAE,aAAa,CAAC,YAAY,CAAC,EAAE,CAAC;QACxC,SAAS,EAAE,eAAe,CAAC,YAAY,CAAC,EAAE,CAAC;QAC3C,aAAa,EAAE,iBAAiB,EAAE,CAAC;KACtC,EACD,eAAe,CAAC,EAAE,OAAO,EACzB,gCAAgC,CAAC,EAAE,OAAO;IAiBxC,IAAI,CAAC,IAAI,EAAE,MAAM;CAY1B"}
1
+ {"version":3,"file":"botInstance.d.ts","sourceRoot":"","sources":["../../entities/botInstance.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAElD,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE5D,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAGhD,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAGhE,qBAAa,WAAW;IACpB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiB;IACzC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAa;IACvC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAU;IACjC,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAA0B;IAElE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,OAAO,EAAE;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE;YACL,QAAQ,EAAE,aAAa,CAAC,YAAY,CAAC,EAAE,CAAC;YACxC,SAAS,EAAE,eAAe,CAAC,YAAY,CAAC,EAAE,CAAC;YAC3C,aAAa,EAAE,iBAAiB,EAAE,CAAC;SACtC,CAAC;QACF,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,EAAE;YACP,aAAa,CAAC,EAAE,cAAc,CAAC;YAC/B,MAAM,CAAC,EAAE,OAAO,CAAC;YACjB,SAAS,CAAC,EAAE,UAAU,CAAC;SAC1B,CAAC;KACL;IAwBK,KAAK,CACP,KAAK,EAAE,MAAM,EACb,OAAO,EAAE;QACL,QAAQ,EAAE,aAAa,CAAC,YAAY,CAAC,EAAE,CAAC;QACxC,SAAS,EAAE,eAAe,CAAC,YAAY,CAAC,EAAE,CAAC;QAC3C,aAAa,EAAE,iBAAiB,EAAE,CAAC;KACtC,EACD,eAAe,CAAC,EAAE,OAAO,EACzB,gCAAgC,CAAC,EAAE,OAAO;IAiBxC,IAAI,CAAC,IAAI,EAAE,MAAM;CAY1B"}
@@ -21,7 +21,6 @@ class BotInstance {
21
21
  options.services?.storageClient ??
22
22
  new jsonFileStorage_1.JsonFileStorage(options.name, actions, options.storagePath);
23
23
  this.actionProcessingService = new actionProcessingService_1.ActionProcessingService(this.name, options.chats, this.storage, this.scheduler, this.logger);
24
- this.storage.saveMetadata(actions, this.name);
25
24
  }
26
25
  async start(token, actions, scheduledPeriod, verboseLoggingForIncomingMessage) {
27
26
  this.actionProcessingService.initialize(token, actions, scheduledPeriod, verboseLoggingForIncomingMessage);
@@ -1 +1 @@
1
- {"version":3,"file":"actionProcessingService.d.ts","sourceRoot":"","sources":["../../services/actionProcessingService.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAuB,MAAM,qBAAqB,CAAC;AAEnE,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAElD,OAAO,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAC;AAC1E,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,qCAAqC,CAAC;AAMtE,qBAAa,uBAAuB;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiB;IACzC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAU;IAEjC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAyB;IAC1D,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAA2B;IAC9D,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAA6B;IAElE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IAEjC,OAAO,CAAC,QAAQ,CAAY;gBAGxB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC7B,OAAO,EAAE,cAAc,EACvB,SAAS,EAAE,UAAU,EACrB,MAAM,EAAE,OAAO;IA4Bb,UAAU,CACZ,KAAK,EAAE,MAAM,EACb,OAAO,EAAE;QACL,QAAQ,EAAE,aAAa,CAAC,YAAY,CAAC,EAAE,CAAC;QACxC,SAAS,EAAE,eAAe,CAAC,YAAY,CAAC,EAAE,CAAC;QAC3C,aAAa,EAAE,iBAAiB,EAAE,CAAC;KACtC,EACD,eAAe,CAAC,EAAE,OAAO,EACzB,gCAAgC,CAAC,EAAE,OAAO;IAkD9C,IAAI,CAAC,IAAI,EAAE,MAAM;CAGpB"}
1
+ {"version":3,"file":"actionProcessingService.d.ts","sourceRoot":"","sources":["../../services/actionProcessingService.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAuB,MAAM,qBAAqB,CAAC;AAEnE,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAElD,OAAO,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAC;AAC1E,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,qCAAqC,CAAC;AAMtE,qBAAa,uBAAuB;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiB;IACzC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAU;IAEjC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAyB;IAC1D,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAA2B;IAC9D,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAA6B;IAElE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IAEjC,OAAO,CAAC,QAAQ,CAAY;gBAGxB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC7B,OAAO,EAAE,cAAc,EACvB,SAAS,EAAE,UAAU,EACrB,MAAM,EAAE,OAAO;IA4Bb,UAAU,CACZ,KAAK,EAAE,MAAM,EACb,OAAO,EAAE;QACL,QAAQ,EAAE,aAAa,CAAC,YAAY,CAAC,EAAE,CAAC;QACxC,SAAS,EAAE,eAAe,CAAC,YAAY,CAAC,EAAE,CAAC;QAC3C,aAAa,EAAE,iBAAiB,EAAE,CAAC;KACtC,EACD,eAAe,CAAC,EAAE,OAAO,EACzB,gCAAgC,CAAC,EAAE,OAAO;IAsD9C,IAAI,CAAC,IAAI,EAAE,MAAM;CAGpB"}
@@ -21,16 +21,18 @@ class ActionProcessingService {
21
21
  this.telegraf = new telegraf_1.Telegraf(token);
22
22
  const api = new telegramApi_1.TelegramApiService(this.botName, this.telegraf.telegram, this.storage, this.logger, (capture, id, chatInfo, traceId) => this.commandProcessor.captureRegistrationCallback(capture, id, chatInfo, traceId));
23
23
  const botInfo = await this.telegraf.telegram.getMe();
24
- this.commandProcessor.initialize(api, this.telegraf, actions.commands.length > 0
24
+ const commandActions = actions.commands.length > 0
25
25
  ? [
26
26
  (0, helpAction_1.buildHelpCommand)(actions.commands
27
27
  .map((x) => x.readmeFactory(botInfo.username))
28
28
  .filter((x) => !!x), botInfo.username),
29
29
  ...actions.commands
30
30
  ]
31
- : [], verboseLoggingForIncomingMessage ?? false);
31
+ : [];
32
+ this.commandProcessor.initialize(api, this.telegraf, commandActions, verboseLoggingForIncomingMessage ?? false);
32
33
  this.inlineQueryProcessor.initialize(api, this.telegraf, actions.inlineQueries, 300);
33
34
  this.scheduledProcessor.initialize(api, actions.scheduled, scheduledPeriod ?? (0, timeConvertions_1.hoursToSeconds)(1));
35
+ this.storage.saveMetadata([...actions.scheduled, ...commandActions]);
34
36
  this.telegraf.launch();
35
37
  }
36
38
  stop(code) {
@@ -65,13 +65,13 @@ class CommandActionProcessor extends baseProcessor_1.BaseActionProcessor {
65
65
  }
66
66
  for (const commandAction of commandsToCheck) {
67
67
  this.initializeMessageContext(ctx, commandAction, msg);
68
- this.executeAction(commandAction, ctx);
68
+ await this.executeAction(commandAction, ctx);
69
69
  }
70
70
  if (this.replyCaptures.length != 0) {
71
71
  const replyCtx = new replyContext_1.ReplyContext(this.storage, this.scheduler);
72
72
  for (const replyAction of this.replyCaptures) {
73
73
  this.initializeReplyCaptureContext(replyCtx, replyAction, msg);
74
- this.executeAction(replyAction, replyCtx);
74
+ await this.executeAction(replyAction, replyCtx);
75
75
  }
76
76
  }
77
77
  this.api.flushResponses();
@@ -36,7 +36,7 @@ class InlineQueryActionProcessor extends baseProcessor_1.BaseActionProcessor {
36
36
  queriesInProcessing.set(inlineQuery.userId, inlineQuery);
37
37
  for (const inlineQueryAction of this.inlineQueries) {
38
38
  this.initializeInlineQueryContext(ctx, inlineQuery.query, inlineQuery.queryId, inlineQueryAction, inlineQuery.abortController.signal, inlineQuery.traceId);
39
- this.executeAction(inlineQueryAction, ctx, (error, ctx) => {
39
+ await this.executeAction(inlineQueryAction, ctx, (error, ctx) => {
40
40
  if (error.name == 'AbortError') {
41
41
  ctx.logger.logWithTraceId(`Aborting query ${inlineQuery.queryId} (${inlineQuery.query}) successful.`);
42
42
  }
@@ -43,7 +43,7 @@ class ScheduledActionProcessor extends baseProcessor_1.BaseActionProcessor {
43
43
  for (const [chatName, chatId] of Object.entries(this.chats)) {
44
44
  for (const scheduledAction of this.scheduled) {
45
45
  this.initializeChatContext(ctx, scheduledAction, new chatInfo_1.ChatInfo(chatId, chatName), (0, traceFactory_1.createTrace)(scheduledAction, this.botName, `${scheduledAction.key}-${chatId}`));
46
- this.executeAction(scheduledAction, ctx);
46
+ await this.executeAction(scheduledAction, ctx);
47
47
  }
48
48
  }
49
49
  this.api.flushResponses();
@@ -2,17 +2,18 @@ import { IStorageClient } from '../types/storage';
2
2
  import { IActionState } from '../types/actionState';
3
3
  import { IActionWithState, ActionKey } from '../types/action';
4
4
  export declare class JsonFileStorage implements IStorageClient {
5
+ private readonly filePaths;
5
6
  private readonly locks;
6
7
  private readonly cache;
7
8
  private readonly storagePath;
8
9
  private readonly botName;
9
10
  constructor(botName: string, actions: IActionWithState<IActionState>[], path?: string);
10
11
  private lock;
11
- private loadInternal;
12
- private save;
13
- private buidPathFromKey;
12
+ private tryGetFromCache;
13
+ private loadFromFile;
14
+ private updateCacheAndSaveToFile;
14
15
  load<TActionState extends IActionState>(key: ActionKey): Promise<Record<number, TActionState>>;
15
- saveMetadata(actions: IActionWithState<IActionState>[], botName: string): Promise<void>;
16
+ saveMetadata(actions: IActionWithState<IActionState>[]): Promise<void>;
16
17
  getActionState<TActionState extends IActionState>(action: IActionWithState<TActionState>, chatId: number): Promise<TActionState>;
17
18
  saveActionExecutionResult<TActionState extends IActionState>(action: IActionWithState<TActionState>, chatId: number, state: TActionState): Promise<void>;
18
19
  close(): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"jsonFileStorage.d.ts","sourceRoot":"","sources":["../../services/jsonFileStorage.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE9D,qBAAa,eAAgB,YAAW,cAAc;IAClD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAmC;IACzD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA4C;IAClE,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;gBAG7B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,gBAAgB,CAAC,YAAY,CAAC,EAAE,EACzC,IAAI,CAAC,EAAE,MAAM;YAiBH,IAAI;YAcJ,YAAY;YAqBZ,IAAI;IAWlB,OAAO,CAAC,eAAe;IAOjB,IAAI,CAAC,YAAY,SAAS,YAAY,EAAE,GAAG,EAAE,SAAS;IAMtD,YAAY,CACd,OAAO,EAAE,gBAAgB,CAAC,YAAY,CAAC,EAAE,EACzC,OAAO,EAAE,MAAM;IAWb,cAAc,CAAC,YAAY,SAAS,YAAY,EAClD,MAAM,EAAE,gBAAgB,CAAC,YAAY,CAAC,EACtC,MAAM,EAAE,MAAM;IAYZ,yBAAyB,CAAC,YAAY,SAAS,YAAY,EAC7D,MAAM,EAAE,gBAAgB,CAAC,YAAY,CAAC,EACtC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,YAAY;IAWjB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAMtB,cAAc,CAAC,YAAY,SAAS,YAAY,EAClD,MAAM,EAAE,gBAAgB,CAAC,YAAY,CAAC,EACtC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC;CAcrD"}
1
+ {"version":3,"file":"jsonFileStorage.d.ts","sourceRoot":"","sources":["../../services/jsonFileStorage.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAM9D,qBAAa,eAAgB,YAAW,cAAc;IAClD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgC;IAC1D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAmC;IACzD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA4C;IAClE,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;gBAG7B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,gBAAgB,CAAC,YAAY,CAAC,EAAE,EACzC,IAAI,CAAC,EAAE,MAAM;YAqBH,IAAI;IAclB,OAAO,CAAC,eAAe;YAIT,YAAY;YAwBZ,wBAAwB;IAgBhC,IAAI,CAAC,YAAY,SAAS,YAAY,EAAE,GAAG,EAAE,SAAS;IAStD,YAAY,CAAC,OAAO,EAAE,gBAAgB,CAAC,YAAY,CAAC,EAAE;IAQtD,cAAc,CAAC,YAAY,SAAS,YAAY,EAClD,MAAM,EAAE,gBAAgB,CAAC,YAAY,CAAC,EACtC,MAAM,EAAE,MAAM;IAWZ,yBAAyB,CAAC,YAAY,SAAS,YAAY,EAC7D,MAAM,EAAE,gBAAgB,CAAC,YAAY,CAAC,EACtC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,YAAY;IAajB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAMtB,cAAc,CAAC,YAAY,SAAS,YAAY,EAClD,MAAM,EAAE,gBAAgB,CAAC,YAAY,CAAC,EACtC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC;CAiBrD"}
@@ -4,8 +4,12 @@ exports.JsonFileStorage = void 0;
4
4
  const fs_1 = require("fs");
5
5
  const promises_1 = require("fs/promises");
6
6
  const async_sema_1 = require("async-sema");
7
+ function buildPath(storagePath, botName, actionKey) {
8
+ return `${storagePath}/${botName}/${actionKey.replaceAll(':', '/')}.json`;
9
+ }
7
10
  class JsonFileStorage {
8
11
  constructor(botName, actions, path) {
12
+ this.filePaths = new Map();
9
13
  this.locks = new Map();
10
14
  this.cache = new Map();
11
15
  this.botName = botName;
@@ -17,6 +21,7 @@ class JsonFileStorage {
17
21
  }
18
22
  for (const action of actions) {
19
23
  this.locks.set(action.key, new async_sema_1.Sema(1));
24
+ this.filePaths.set(action.key, buildPath(this.storagePath, this.botName, action.key));
20
25
  }
21
26
  }
22
27
  async lock(key, action) {
@@ -31,50 +36,55 @@ class JsonFileStorage {
31
36
  lock.release();
32
37
  }
33
38
  }
34
- async loadInternal(key) {
35
- if (!this.cache.has(key)) {
36
- const targetPath = this.buidPathFromKey(key);
37
- const fileContent = await (0, promises_1.readFile)(targetPath, {
38
- encoding: 'utf-8',
39
- flag: 'a+'
40
- });
41
- if (fileContent) {
42
- const data = JSON.parse(fileContent);
43
- this.cache.set(key, data);
44
- }
39
+ tryGetFromCache(key) {
40
+ return this.cache.get(key);
41
+ }
42
+ async loadFromFile(key) {
43
+ if (!this.filePaths.has(key))
44
+ this.filePaths.set(key, buildPath(this.storagePath, this.botName, key));
45
+ const targetPath = this.filePaths.get(key);
46
+ const fileContent = await (0, promises_1.readFile)(targetPath, {
47
+ encoding: 'utf-8',
48
+ flag: 'a+'
49
+ });
50
+ if (fileContent) {
51
+ const data = JSON.parse(fileContent);
52
+ this.cache.set(key, data);
45
53
  }
46
54
  return (this.cache.get(key) ?? {});
47
55
  }
48
- async save(data, key) {
56
+ async updateCacheAndSaveToFile(data, key) {
49
57
  this.cache.set(key, data);
50
- const targetPath = this.buidPathFromKey(key);
58
+ if (!this.filePaths.has(key))
59
+ this.filePaths.set(key, buildPath(this.storagePath, this.botName, key));
60
+ const targetPath = this.filePaths.get(key);
51
61
  await (0, promises_1.writeFile)(targetPath, JSON.stringify(data), { flag: 'w+' });
52
62
  }
53
- buidPathFromKey(key) {
54
- return `${this.storagePath}/${this.botName}/${key.replaceAll(':', '/')}.json`;
55
- }
56
63
  async load(key) {
57
- return await this.lock(key, async () => {
58
- return await this.loadInternal(key);
59
- });
64
+ return (this.tryGetFromCache(key) ??
65
+ (await this.lock(key, async () => {
66
+ return await this.loadFromFile(key);
67
+ })));
60
68
  }
61
- async saveMetadata(actions, botName) {
62
- const targetPath = this.buidPathFromKey(`Metadata-${botName}`);
69
+ async saveMetadata(actions) {
70
+ const targetPath = `${this.storagePath}/${this.botName}/Metadata-${this.botName}.json`;
63
71
  await (0, promises_1.writeFile)(targetPath, JSON.stringify(actions), {
64
72
  flag: 'w+'
65
73
  });
66
74
  }
67
75
  async getActionState(action, chatId) {
68
- return await this.lock(action.key, async () => {
69
- const data = await this.loadInternal(action.key);
70
- return Object.assign(action.stateConstructor(), data[chatId]);
71
- });
76
+ const value = this.tryGetFromCache(action.key) ??
77
+ (await this.lock(action.key, async () => {
78
+ return await this.loadFromFile(action.key);
79
+ }));
80
+ return Object.assign(action.stateConstructor(), value[chatId]);
72
81
  }
73
82
  async saveActionExecutionResult(action, chatId, state) {
74
83
  return await this.lock(action.key, async () => {
75
- const data = await this.loadInternal(action.key);
84
+ const data = this.tryGetFromCache(action.key) ??
85
+ (await this.loadFromFile(action.key));
76
86
  data[chatId] = state;
77
- await this.save(data, action.key);
87
+ await this.updateCacheAndSaveToFile(data, action.key);
78
88
  });
79
89
  }
80
90
  async close() {
@@ -84,10 +94,11 @@ class JsonFileStorage {
84
94
  }
85
95
  async updateStateFor(action, chatId, update) {
86
96
  await this.lock(action.key, async () => {
87
- const data = await this.loadInternal(action.key);
97
+ const data = this.tryGetFromCache(action.key) ??
98
+ (await this.loadFromFile(action.key));
88
99
  const state = Object.assign(action.stateConstructor(), data[chatId]);
89
100
  await update(state);
90
- await this.save(data, action.key);
101
+ await this.updateCacheAndSaveToFile(data, action.key);
91
102
  });
92
103
  }
93
104
  }
@@ -3,6 +3,7 @@ export type QueueItem = {
3
3
  callback: () => Promise<void>;
4
4
  };
5
5
  export declare class ResponseProcessingQueue {
6
+ rateLimiter: () => Promise<void>;
6
7
  items: QueueItem[];
7
8
  isFlushing: boolean;
8
9
  enqueue(item: QueueItem): void;
@@ -1 +1 @@
1
- {"version":3,"file":"responseProcessingQueue.d.ts","sourceRoot":"","sources":["../../services/responseProcessingQueue.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,SAAS,GAAG;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACjC,CAAC;AAIF,qBAAa,uBAAuB;IAChC,KAAK,EAAE,SAAS,EAAE,CAAM;IACxB,UAAU,UAAS;IAEnB,OAAO,CAAC,IAAI,EAAE,SAAS;IAmBjB,eAAe;CAiBxB"}
1
+ {"version":3,"file":"responseProcessingQueue.d.ts","sourceRoot":"","sources":["../../services/responseProcessingQueue.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,SAAS,GAAG;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACjC,CAAC;AAIF,qBAAa,uBAAuB;IAChC,WAAW,sBAAwD;IACnE,KAAK,EAAE,SAAS,EAAE,CAAM;IACxB,UAAU,UAAS;IAEnB,OAAO,CAAC,IAAI,EAAE,SAAS;IAmBjB,eAAe;CAiBxB"}
@@ -1,10 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ResponseProcessingQueue = void 0;
4
- const promises_1 = require("timers/promises");
4
+ const async_sema_1 = require("async-sema");
5
5
  const TELEGRAM_RATELIMIT_DELAY = 35;
6
6
  class ResponseProcessingQueue {
7
7
  constructor() {
8
+ this.rateLimiter = (0, async_sema_1.RateLimit)(1, { timeUnit: TELEGRAM_RATELIMIT_DELAY });
8
9
  this.items = [];
9
10
  this.isFlushing = false;
10
11
  }
@@ -27,12 +28,10 @@ class ResponseProcessingQueue {
27
28
  this.isFlushing = true;
28
29
  while (this.items.length) {
29
30
  if (Date.now() >= this.items[0].priority) {
31
+ await this.rateLimiter();
30
32
  const item = this.items.shift();
31
33
  await item.callback();
32
34
  }
33
- else {
34
- await (0, promises_1.setTimeout)(TELEGRAM_RATELIMIT_DELAY);
35
- }
36
35
  }
37
36
  this.isFlushing = false;
38
37
  }
@@ -4,7 +4,7 @@ export interface IStorageClient {
4
4
  updateStateFor<TActionState extends IActionState>(action: IActionWithState<TActionState>, chatId: number, update: (state: TActionState) => Promise<void>): Promise<void>;
5
5
  close(): Promise<void>;
6
6
  load<TActionState extends IActionState>(key: ActionKey): Promise<Record<number, TActionState>>;
7
- saveMetadata<TActionState extends IActionState>(actions: IActionWithState<TActionState>[], botName: string): Promise<void>;
7
+ saveMetadata<TActionState extends IActionState>(actions: IActionWithState<TActionState>[]): Promise<void>;
8
8
  getActionState<TActionState extends IActionState>(action: IActionWithState<TActionState>, chatId: number): Promise<TActionState>;
9
9
  saveActionExecutionResult<TActionState extends IActionState>(action: IActionWithState<TActionState>, chatId: number, state: TActionState): Promise<void>;
10
10
  }
@@ -1 +1 @@
1
- {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../types/storage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAEvD,MAAM,WAAW,cAAc;IAC3B,cAAc,CAAC,YAAY,SAAS,YAAY,EAC5C,MAAM,EAAE,gBAAgB,CAAC,YAAY,CAAC,EACtC,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,YAAY,SAAS,YAAY,EAC1C,OAAO,EAAE,gBAAgB,CAAC,YAAY,CAAC,EAAE,EACzC,OAAO,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,cAAc,CAAC,YAAY,SAAS,YAAY,EAC5C,MAAM,EAAE,gBAAgB,CAAC,YAAY,CAAC,EACtC,MAAM,EAAE,MAAM,GACf,OAAO,CAAC,YAAY,CAAC,CAAC;IACzB,yBAAyB,CAAC,YAAY,SAAS,YAAY,EACvD,MAAM,EAAE,gBAAgB,CAAC,YAAY,CAAC,EACtC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,YAAY,GACpB,OAAO,CAAC,IAAI,CAAC,CAAC;CACpB"}
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../types/storage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAEvD,MAAM,WAAW,cAAc;IAC3B,cAAc,CAAC,YAAY,SAAS,YAAY,EAC5C,MAAM,EAAE,gBAAgB,CAAC,YAAY,CAAC,EACtC,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,YAAY,SAAS,YAAY,EAC1C,OAAO,EAAE,gBAAgB,CAAC,YAAY,CAAC,EAAE,GAC1C,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,cAAc,CAAC,YAAY,SAAS,YAAY,EAC5C,MAAM,EAAE,gBAAgB,CAAC,YAAY,CAAC,EACtC,MAAM,EAAE,MAAM,GACf,OAAO,CAAC,YAAY,CAAC,CAAC;IACzB,yBAAyB,CAAC,YAAY,SAAS,YAAY,EACvD,MAAM,EAAE,gBAAgB,CAAC,YAAY,CAAC,EACtC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,YAAY,GACpB,OAAO,CAAC,IAAI,CAAC,CAAC;CACpB"}
@@ -56,8 +56,6 @@ export class BotInstance {
56
56
  this.scheduler,
57
57
  this.logger
58
58
  );
59
-
60
- this.storage.saveMetadata(actions, this.name);
61
59
  }
62
60
 
63
61
  async start(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chz-telegram-bot",
3
- "version": "0.3.8",
3
+ "version": "0.3.10",
4
4
  "type": "module",
5
5
  "dependencies": {
6
6
  "async-sema": "^3.1.1",
@@ -85,10 +85,7 @@ export class ActionProcessingService {
85
85
  );
86
86
 
87
87
  const botInfo = await this.telegraf.telegram.getMe();
88
-
89
- this.commandProcessor.initialize(
90
- api,
91
- this.telegraf,
88
+ const commandActions =
92
89
  actions.commands.length > 0
93
90
  ? [
94
91
  buildHelpCommand(
@@ -99,7 +96,12 @@ export class ActionProcessingService {
99
96
  ),
100
97
  ...actions.commands
101
98
  ]
102
- : [],
99
+ : [];
100
+
101
+ this.commandProcessor.initialize(
102
+ api,
103
+ this.telegraf,
104
+ commandActions,
103
105
  verboseLoggingForIncomingMessage ?? false
104
106
  );
105
107
  this.inlineQueryProcessor.initialize(
@@ -114,6 +116,8 @@ export class ActionProcessingService {
114
116
  scheduledPeriod ?? hoursToSeconds(1 as Hours)
115
117
  );
116
118
 
119
+ this.storage.saveMetadata([...actions.scheduled, ...commandActions]);
120
+
117
121
  this.telegraf.launch();
118
122
  }
119
123
 
@@ -149,7 +149,7 @@ export class CommandActionProcessor extends BaseActionProcessor {
149
149
 
150
150
  for (const commandAction of commandsToCheck) {
151
151
  this.initializeMessageContext(ctx, commandAction, msg);
152
- this.executeAction(commandAction, ctx);
152
+ await this.executeAction(commandAction, ctx);
153
153
  }
154
154
 
155
155
  if (this.replyCaptures.length != 0) {
@@ -160,7 +160,7 @@ export class CommandActionProcessor extends BaseActionProcessor {
160
160
 
161
161
  for (const replyAction of this.replyCaptures) {
162
162
  this.initializeReplyCaptureContext(replyCtx, replyAction, msg);
163
- this.executeAction(replyAction, replyCtx);
163
+ await this.executeAction(replyAction, replyCtx);
164
164
  }
165
165
  }
166
166
 
@@ -101,7 +101,7 @@ export class InlineQueryActionProcessor extends BaseActionProcessor {
101
101
  inlineQuery.traceId
102
102
  );
103
103
 
104
- this.executeAction(
104
+ await this.executeAction(
105
105
  inlineQueryAction,
106
106
  ctx,
107
107
  (error, ctx) => {
@@ -96,7 +96,7 @@ export class ScheduledActionProcessor extends BaseActionProcessor {
96
96
  )
97
97
  );
98
98
 
99
- this.executeAction(scheduledAction, ctx);
99
+ await this.executeAction(scheduledAction, ctx);
100
100
  }
101
101
  }
102
102
 
@@ -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
  }
@@ -1,5 +1,5 @@
1
- import { setTimeout } from 'timers/promises';
2
1
  import { Milliseconds } from '../types/timeValues';
2
+ import { RateLimit } from 'async-sema';
3
3
 
4
4
  export type QueueItem = {
5
5
  priority: number;
@@ -9,6 +9,7 @@ export type QueueItem = {
9
9
  const TELEGRAM_RATELIMIT_DELAY = 35 as Milliseconds;
10
10
 
11
11
  export class ResponseProcessingQueue {
12
+ rateLimiter = RateLimit(1, { timeUnit: TELEGRAM_RATELIMIT_DELAY });
12
13
  items: QueueItem[] = [];
13
14
  isFlushing = false;
14
15
 
@@ -38,11 +39,11 @@ export class ResponseProcessingQueue {
38
39
 
39
40
  while (this.items.length) {
40
41
  if (Date.now() >= this.items[0].priority) {
42
+ await this.rateLimiter();
43
+
41
44
  const item = this.items.shift()!;
42
45
 
43
46
  await item.callback();
44
- } else {
45
- await setTimeout(TELEGRAM_RATELIMIT_DELAY);
46
47
  }
47
48
  }
48
49
 
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>,