chz-telegram-bot 0.3.11 → 0.3.12

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.
@@ -5,7 +5,9 @@ import { IActionState } from '../../types/actionState';
5
5
  import { IActionWithState, ActionKey } from '../../types/action';
6
6
  import { MessageContext } from '../context/messageContext';
7
7
  import { CommandTrigger } from '../../types/commandTrigger';
8
+ import { Sema as Semaphore } from 'async-sema';
8
9
  export declare class CommandAction<TActionState extends IActionState> implements IActionWithState<TActionState> {
10
+ readonly ratelimitSemaphores: Map<number, Semaphore>;
9
11
  readonly triggers: CommandTrigger[];
10
12
  readonly handler: CommandHandler<TActionState>;
11
13
  readonly name: string;
@@ -17,7 +19,8 @@ export declare class CommandAction<TActionState extends IActionState> implements
17
19
  readonly stateConstructor: () => TActionState;
18
20
  readonly key: ActionKey;
19
21
  readonly readmeFactory: (botName: string) => string;
20
- constructor(trigger: CommandTrigger | CommandTrigger[], handler: CommandHandler<TActionState>, name: string, active: boolean, cooldown: Seconds, chatsBlacklist: number[], allowedUsers: number[], condition: CommandCondition<TActionState>, stateConstructor: () => TActionState, readmeFactory: (botName: string) => string);
22
+ readonly maxAllowedSimultaniousExecutions: number;
23
+ constructor(trigger: CommandTrigger | CommandTrigger[], handler: CommandHandler<TActionState>, name: string, active: boolean, cooldown: Seconds, chatsBlacklist: number[], allowedUsers: number[], maxAllowedSimultaniousExecutions: number, condition: CommandCondition<TActionState>, stateConstructor: () => TActionState, readmeFactory: (botName: string) => string);
21
24
  exec(ctx: MessageContext<TActionState>): Promise<import("../../types/response").BotResponse[]>;
22
25
  private checkIfShouldBeExecuted;
23
26
  private checkTrigger;
@@ -1 +1 @@
1
- {"version":3,"file":"commandAction.d.ts","sourceRoot":"","sources":["../../../entities/actions/commandAction.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAGjD,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAEjE,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAI5D,qBAAa,aAAa,CAAC,YAAY,SAAS,YAAY,CACxD,YAAW,gBAAgB,CAAC,YAAY,CAAC;IAEzC,QAAQ,CAAC,QAAQ,EAAE,cAAc,EAAE,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC,YAAY,CAAC,CAAC;IAC/C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,cAAc,EAAE,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,YAAY,EAAE,MAAM,EAAE,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAC,YAAY,CAAC,CAAC;IACnD,QAAQ,CAAC,gBAAgB,EAAE,MAAM,YAAY,CAAC;IAC9C,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC;IACxB,QAAQ,CAAC,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;gBAGhD,OAAO,EAAE,cAAc,GAAG,cAAc,EAAE,EAC1C,OAAO,EAAE,cAAc,CAAC,YAAY,CAAC,EACrC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,OAAO,EACf,QAAQ,EAAE,OAAO,EACjB,cAAc,EAAE,MAAM,EAAE,EACxB,YAAY,EAAE,MAAM,EAAE,EACtB,SAAS,EAAE,gBAAgB,CAAC,YAAY,CAAC,EACzC,gBAAgB,EAAE,MAAM,YAAY,EACpC,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM;IAgBxC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,YAAY,CAAC;IA+C5C,OAAO,CAAC,uBAAuB;IA+B/B,OAAO,CAAC,YAAY;CAuCvB"}
1
+ {"version":3,"file":"commandAction.d.ts","sourceRoot":"","sources":["../../../entities/actions/commandAction.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAGjD,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAEjE,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAG5D,OAAO,EAAE,IAAI,IAAI,SAAS,EAAE,MAAM,YAAY,CAAC;AAG/C,qBAAa,aAAa,CAAC,YAAY,SAAS,YAAY,CACxD,YAAW,gBAAgB,CAAC,YAAY,CAAC;IAEzC,QAAQ,CAAC,mBAAmB,yBAAgC;IAE5D,QAAQ,CAAC,QAAQ,EAAE,cAAc,EAAE,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC,YAAY,CAAC,CAAC;IAC/C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,cAAc,EAAE,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,YAAY,EAAE,MAAM,EAAE,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAC,YAAY,CAAC,CAAC;IACnD,QAAQ,CAAC,gBAAgB,EAAE,MAAM,YAAY,CAAC;IAC9C,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC;IACxB,QAAQ,CAAC,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;IACpD,QAAQ,CAAC,gCAAgC,EAAE,MAAM,CAAC;gBAG9C,OAAO,EAAE,cAAc,GAAG,cAAc,EAAE,EAC1C,OAAO,EAAE,cAAc,CAAC,YAAY,CAAC,EACrC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,OAAO,EACf,QAAQ,EAAE,OAAO,EACjB,cAAc,EAAE,MAAM,EAAE,EACxB,YAAY,EAAE,MAAM,EAAE,EACtB,gCAAgC,EAAE,MAAM,EACxC,SAAS,EAAE,gBAAgB,CAAC,YAAY,CAAC,EACzC,gBAAgB,EAAE,MAAM,YAAY,EACpC,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM;IAkBxC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,YAAY,CAAC;IA8D5C,OAAO,CAAC,uBAAuB;IA+B/B,OAAO,CAAC,YAAY;CAuCvB"}
@@ -10,7 +10,10 @@ const toArray_1 = require("../../helpers/toArray");
10
10
  const commandTriggerCheckResult_1 = require("../../dtos/commandTriggerCheckResult");
11
11
  const noop_1 = require("../../helpers/noop");
12
12
  const messageTypes_1 = require("../../types/messageTypes");
13
+ const async_sema_1 = require("async-sema");
14
+ const mapUtils_1 = require("../../helpers/mapUtils");
13
15
  class CommandAction {
16
+ ratelimitSemaphores = new Map();
14
17
  triggers;
15
18
  handler;
16
19
  name;
@@ -22,7 +25,8 @@ class CommandAction {
22
25
  stateConstructor;
23
26
  key;
24
27
  readmeFactory;
25
- constructor(trigger, handler, name, active, cooldown, chatsBlacklist, allowedUsers, condition, stateConstructor, readmeFactory) {
28
+ maxAllowedSimultaniousExecutions;
29
+ constructor(trigger, handler, name, active, cooldown, chatsBlacklist, allowedUsers, maxAllowedSimultaniousExecutions, condition, stateConstructor, readmeFactory) {
26
30
  this.triggers = (0, toArray_1.toArray)(trigger);
27
31
  this.handler = handler;
28
32
  this.name = name;
@@ -33,6 +37,8 @@ class CommandAction {
33
37
  this.condition = condition;
34
38
  this.stateConstructor = stateConstructor;
35
39
  this.readmeFactory = readmeFactory;
40
+ this.maxAllowedSimultaniousExecutions =
41
+ maxAllowedSimultaniousExecutions;
36
42
  this.key = `command:${this.name.replace('.', '-')}`;
37
43
  }
38
44
  async exec(ctx) {
@@ -40,23 +46,33 @@ class CommandAction {
40
46
  throw new Error(`Context for ${this.key} is not initialized or already consumed`);
41
47
  if (!this.active || this.chatsBlacklist.includes(ctx.chatInfo.id))
42
48
  return noop_1.Noop.NoResponse;
43
- const state = await ctx.storage.getActionState(this, ctx.chatInfo.id);
44
- const { shouldExecute, matchResults, skipCooldown } = this.triggers
45
- .map((x) => this.checkIfShouldBeExecuted(ctx, x, state))
46
- .reduce((acc, curr) => acc.mergeWith(curr), commandTriggerCheckResult_1.CommandTriggerCheckResult.DoNotTrigger);
47
- if (!shouldExecute)
48
- return noop_1.Noop.NoResponse;
49
- ctx.logger.logWithTraceId(` - Executing [${this.name}] in ${ctx.chatInfo.id}`);
50
- ctx.matchResults = matchResults;
51
- await this.handler(ctx, state);
52
- if (skipCooldown) {
53
- ctx.startCooldown = false;
49
+ let lock;
50
+ if (this.maxAllowedSimultaniousExecutions != 0) {
51
+ lock = (0, mapUtils_1.getOrSetIfNotExists)(this.ratelimitSemaphores, ctx.chatInfo.id, new async_sema_1.Sema(this.maxAllowedSimultaniousExecutions));
52
+ await lock.acquire();
53
+ }
54
+ try {
55
+ const state = await ctx.storage.getActionState(this, ctx.chatInfo.id);
56
+ const { shouldExecute, matchResults, skipCooldown } = this.triggers
57
+ .map((x) => this.checkIfShouldBeExecuted(ctx, x, state))
58
+ .reduce((acc, curr) => acc.mergeWith(curr), commandTriggerCheckResult_1.CommandTriggerCheckResult.DoNotTrigger);
59
+ if (!shouldExecute)
60
+ return noop_1.Noop.NoResponse;
61
+ ctx.logger.logWithTraceId(` - Executing [${this.name}] in ${ctx.chatInfo.id}`);
62
+ ctx.matchResults = matchResults;
63
+ await this.handler(ctx, state);
64
+ if (skipCooldown) {
65
+ ctx.startCooldown = false;
66
+ }
67
+ if (ctx.startCooldown) {
68
+ state.lastExecutedDate = (0, moment_1.default)().valueOf();
69
+ }
70
+ await ctx.storage.saveActionExecutionResult(this, ctx.chatInfo.id, state);
71
+ return ctx.responses;
54
72
  }
55
- if (ctx.startCooldown) {
56
- state.lastExecutedDate = (0, moment_1.default)().valueOf();
73
+ finally {
74
+ lock?.release();
57
75
  }
58
- await ctx.storage.saveActionExecutionResult(this, ctx.chatInfo.id, state);
59
- return ctx.responses;
60
76
  }
61
77
  checkIfShouldBeExecuted(ctx, trigger, state) {
62
78
  if (!ctx.fromUserId)
@@ -19,6 +19,7 @@ export declare class CommandActionBuilderWithState<TActionState extends IActionS
19
19
  stateConstructor: () => TActionState;
20
20
  handler: CommandHandler<TActionState>;
21
21
  condition: CommandCondition<TActionState>;
22
+ maxAllowedSimultaniousExecutions: number;
22
23
  /**
23
24
  * Builder for `CommandAction` with state represented by `TActionState`
24
25
  * @param name Action name, will be used for logging and storage
@@ -47,6 +48,8 @@ export declare class CommandActionBuilderWithState<TActionState extends IActionS
47
48
  withHelp(readmeFactory: (botName: string) => string): this;
48
49
  /** If called during building, action is marked as disabled and never checked. */
49
50
  disabled(): this;
51
+ /** Sets maximum number of simultaniously executing handlers for this command per chat. 0 is treated as unlimited. */
52
+ ratelimit(maxAllowedSimultaniousExecutions: number): this;
50
53
  /** Sets action cooldown.
51
54
  * @param seconds Cooldown in seconds.
52
55
  */
@@ -1 +1 @@
1
- {"version":3,"file":"commandActionBuilder.d.ts","sourceRoot":"","sources":["../../../helpers/builders/commandActionBuilder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,sCAAsC,CAAC;AACrE,OAAO,EAAE,eAAe,EAAE,MAAM,uCAAuC,CAAC;AACxE,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAGvD,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAE5D;;GAEG;AACH,qBAAa,6BAA6B,CAAC,YAAY,SAAS,YAAY;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,cAAc,GAAG,cAAc,EAAE,CAAM;IAEhD,MAAM,UAAQ;IACd,aAAa,EAAE,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC,CAAQ;IAC3D,eAAe,EAAE,OAAO,CAAgB;IACxC,SAAS,EAAE,MAAM,EAAE,CAAM;IACzB,YAAY,EAAE,MAAM,EAAE,CAAM;IAC5B,gBAAgB,EAAE,MAAM,YAAY,CAAC;IACrC,OAAO,EAAE,cAAc,CAAC,YAAY,CAAC,CAAa;IAClD,SAAS,EAAE,gBAAgB,CAAC,YAAY,CAAC,CAAa;IAEtD;;;;OAIG;gBACS,IAAI,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,YAAY;IAK9D;;;;;OAKG;IACH,EAAE,CAAC,OAAO,EAAE,cAAc,GAAG,cAAc,EAAE;IAM7C;;OAEG;IACH,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE;IAM1B;;OAEG;IACH,EAAE,CAAC,OAAO,EAAE,cAAc,CAAC,YAAY,CAAC;IAMxC;;OAEG;IACH,IAAI,CAAC,SAAS,EAAE,gBAAgB,CAAC,YAAY,CAAC;IAM9C,QAAQ,CAAC,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM;IAMnD,iFAAiF;IACjF,QAAQ;IAMR;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,OAAO;IAMzB;;;OAGG;IACH,UAAU,CAAC,MAAM,EAAE,MAAM;IAMzB,oBAAoB;IACpB,KAAK;CAcR;AAED;;GAEG;AACH,qBAAa,oBAAqB,SAAQ,6BAA6B,CAAC,eAAe,CAAC;IACpF;;OAEG;gBACS,IAAI,EAAE,MAAM;CAG3B"}
1
+ {"version":3,"file":"commandActionBuilder.d.ts","sourceRoot":"","sources":["../../../helpers/builders/commandActionBuilder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,sCAAsC,CAAC;AACrE,OAAO,EAAE,eAAe,EAAE,MAAM,uCAAuC,CAAC;AACxE,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAGvD,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAE5D;;GAEG;AACH,qBAAa,6BAA6B,CAAC,YAAY,SAAS,YAAY;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,cAAc,GAAG,cAAc,EAAE,CAAM;IAEhD,MAAM,UAAQ;IACd,aAAa,EAAE,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC,CAAQ;IAC3D,eAAe,EAAE,OAAO,CAAgB;IACxC,SAAS,EAAE,MAAM,EAAE,CAAM;IACzB,YAAY,EAAE,MAAM,EAAE,CAAM;IAC5B,gBAAgB,EAAE,MAAM,YAAY,CAAC;IACrC,OAAO,EAAE,cAAc,CAAC,YAAY,CAAC,CAAa;IAClD,SAAS,EAAE,gBAAgB,CAAC,YAAY,CAAC,CAAa;IACtD,gCAAgC,EAAE,MAAM,CAAK;IAE7C;;;;OAIG;gBACS,IAAI,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,YAAY;IAK9D;;;;;OAKG;IACH,EAAE,CAAC,OAAO,EAAE,cAAc,GAAG,cAAc,EAAE;IAM7C;;OAEG;IACH,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE;IAM1B;;OAEG;IACH,EAAE,CAAC,OAAO,EAAE,cAAc,CAAC,YAAY,CAAC;IAMxC;;OAEG;IACH,IAAI,CAAC,SAAS,EAAE,gBAAgB,CAAC,YAAY,CAAC;IAM9C,QAAQ,CAAC,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM;IAMnD,iFAAiF;IACjF,QAAQ;IAMR,qHAAqH;IACrH,SAAS,CAAC,gCAAgC,EAAE,MAAM;IAOlD;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,OAAO;IAMzB;;;OAGG;IACH,UAAU,CAAC,MAAM,EAAE,MAAM;IAMzB,oBAAoB;IACpB,KAAK;CAeR;AAED;;GAEG;AACH,qBAAa,oBAAqB,SAAQ,6BAA6B,CAAC,eAAe,CAAC;IACpF;;OAEG;gBACS,IAAI,EAAE,MAAM;CAG3B"}
@@ -19,6 +19,7 @@ class CommandActionBuilderWithState {
19
19
  stateConstructor;
20
20
  handler = noop_1.Noop.call;
21
21
  condition = noop_1.Noop.true;
22
+ maxAllowedSimultaniousExecutions = 0;
22
23
  /**
23
24
  * Builder for `CommandAction` with state represented by `TActionState`
24
25
  * @param name Action name, will be used for logging and storage
@@ -68,6 +69,12 @@ class CommandActionBuilderWithState {
68
69
  this.active = false;
69
70
  return this;
70
71
  }
72
+ /** Sets maximum number of simultaniously executing handlers for this command per chat. 0 is treated as unlimited. */
73
+ ratelimit(maxAllowedSimultaniousExecutions) {
74
+ this.maxAllowedSimultaniousExecutions =
75
+ maxAllowedSimultaniousExecutions;
76
+ return this;
77
+ }
71
78
  /** Sets action cooldown.
72
79
  * @param seconds Cooldown in seconds.
73
80
  */
@@ -85,7 +92,7 @@ class CommandActionBuilderWithState {
85
92
  }
86
93
  /** Builds action */
87
94
  build() {
88
- return new commandAction_1.CommandAction(this.trigger, this.handler, this.name, this.active, this.cooldownSeconds, this.blacklist, this.allowedUsers, this.condition, this.stateConstructor, this.readmeFactory != null ? this.readmeFactory : (_) => '');
95
+ return new commandAction_1.CommandAction(this.trigger, this.handler, this.name, this.active, this.cooldownSeconds, this.blacklist, this.allowedUsers, this.maxAllowedSimultaniousExecutions, this.condition, this.stateConstructor, this.readmeFactory != null ? this.readmeFactory : (_) => '');
89
96
  }
90
97
  }
91
98
  exports.CommandActionBuilderWithState = CommandActionBuilderWithState;
@@ -30,7 +30,7 @@ class CommandActionProcessor extends baseProcessor_1.BaseActionProcessor {
30
30
  cmd.triggers.includes(messageTypes_1.MessageType.Any));
31
31
  }
32
32
  if (commands.length > 0) {
33
- telegraf.on('message', async (ctx) => {
33
+ telegraf.on('message', (ctx) => {
34
34
  const msg = new incomingMessage_1.IncomingMessage(ctx.update.message, this.botName);
35
35
  const logger = this.logger.createScope(this.botName, msg.traceId, msg.chatInfo.name);
36
36
  if (verboseLoggingForIncomingMessage) {
@@ -39,7 +39,7 @@ class CommandActionProcessor extends baseProcessor_1.BaseActionProcessor {
39
39
  else {
40
40
  logger.logWithTraceId(`${msg.from?.first_name ?? 'Unknown'} (${msg.from?.id ?? 'Unknown'}): ${msg.text || `<non-text message: ${msg.type}>`}`);
41
41
  }
42
- await this.processMessage(msg);
42
+ void this.processMessage(msg);
43
43
  });
44
44
  }
45
45
  }
@@ -11,10 +11,14 @@ import { MessageContext } from '../context/messageContext';
11
11
  import { CommandTrigger } from '../../types/commandTrigger';
12
12
  import { Noop } from '../../helpers/noop';
13
13
  import { MessageType } from '../../types/messageTypes';
14
+ import { Sema as Semaphore } from 'async-sema';
15
+ import { getOrSetIfNotExists } from '../../helpers/mapUtils';
14
16
 
15
17
  export class CommandAction<TActionState extends IActionState>
16
18
  implements IActionWithState<TActionState>
17
19
  {
20
+ readonly ratelimitSemaphores = new Map<number, Semaphore>();
21
+
18
22
  readonly triggers: CommandTrigger[];
19
23
  readonly handler: CommandHandler<TActionState>;
20
24
  readonly name: string;
@@ -26,6 +30,7 @@ export class CommandAction<TActionState extends IActionState>
26
30
  readonly stateConstructor: () => TActionState;
27
31
  readonly key: ActionKey;
28
32
  readonly readmeFactory: (botName: string) => string;
33
+ readonly maxAllowedSimultaniousExecutions: number;
29
34
 
30
35
  constructor(
31
36
  trigger: CommandTrigger | CommandTrigger[],
@@ -35,6 +40,7 @@ export class CommandAction<TActionState extends IActionState>
35
40
  cooldown: Seconds,
36
41
  chatsBlacklist: number[],
37
42
  allowedUsers: number[],
43
+ maxAllowedSimultaniousExecutions: number,
38
44
  condition: CommandCondition<TActionState>,
39
45
  stateConstructor: () => TActionState,
40
46
  readmeFactory: (botName: string) => string
@@ -49,6 +55,8 @@ export class CommandAction<TActionState extends IActionState>
49
55
  this.condition = condition;
50
56
  this.stateConstructor = stateConstructor;
51
57
  this.readmeFactory = readmeFactory;
58
+ this.maxAllowedSimultaniousExecutions =
59
+ maxAllowedSimultaniousExecutions;
52
60
 
53
61
  this.key = `command:${this.name.replace('.', '-')}` as ActionKey;
54
62
  }
@@ -62,42 +70,57 @@ export class CommandAction<TActionState extends IActionState>
62
70
  if (!this.active || this.chatsBlacklist.includes(ctx.chatInfo.id))
63
71
  return Noop.NoResponse;
64
72
 
65
- const state = await ctx.storage.getActionState<TActionState>(
66
- this,
67
- ctx.chatInfo.id
68
- );
73
+ let lock: Semaphore | undefined;
74
+ if (this.maxAllowedSimultaniousExecutions != 0) {
75
+ lock = getOrSetIfNotExists(
76
+ this.ratelimitSemaphores,
77
+ ctx.chatInfo.id,
78
+ new Semaphore(this.maxAllowedSimultaniousExecutions)
79
+ );
80
+
81
+ await lock.acquire();
82
+ }
69
83
 
70
- const { shouldExecute, matchResults, skipCooldown } = this.triggers
71
- .map((x) => this.checkIfShouldBeExecuted(ctx, x, state))
72
- .reduce(
73
- (acc, curr) => acc.mergeWith(curr),
74
- CommandTriggerCheckResult.DoNotTrigger
84
+ try {
85
+ const state = await ctx.storage.getActionState<TActionState>(
86
+ this,
87
+ ctx.chatInfo.id
75
88
  );
76
89
 
77
- if (!shouldExecute) return Noop.NoResponse;
90
+ const { shouldExecute, matchResults, skipCooldown } = this.triggers
91
+ .map((x) => this.checkIfShouldBeExecuted(ctx, x, state))
92
+ .reduce(
93
+ (acc, curr) => acc.mergeWith(curr),
94
+ CommandTriggerCheckResult.DoNotTrigger
95
+ );
78
96
 
79
- ctx.logger.logWithTraceId(
80
- ` - Executing [${this.name}] in ${ctx.chatInfo.id}`
81
- );
82
- ctx.matchResults = matchResults;
97
+ if (!shouldExecute) return Noop.NoResponse;
83
98
 
84
- await this.handler(ctx, state);
99
+ ctx.logger.logWithTraceId(
100
+ ` - Executing [${this.name}] in ${ctx.chatInfo.id}`
101
+ );
102
+ ctx.matchResults = matchResults;
85
103
 
86
- if (skipCooldown) {
87
- ctx.startCooldown = false;
88
- }
104
+ await this.handler(ctx, state);
89
105
 
90
- if (ctx.startCooldown) {
91
- state.lastExecutedDate = moment().valueOf();
92
- }
106
+ if (skipCooldown) {
107
+ ctx.startCooldown = false;
108
+ }
93
109
 
94
- await ctx.storage.saveActionExecutionResult(
95
- this,
96
- ctx.chatInfo.id,
97
- state
98
- );
110
+ if (ctx.startCooldown) {
111
+ state.lastExecutedDate = moment().valueOf();
112
+ }
99
113
 
100
- return ctx.responses;
114
+ await ctx.storage.saveActionExecutionResult(
115
+ this,
116
+ ctx.chatInfo.id,
117
+ state
118
+ );
119
+
120
+ return ctx.responses;
121
+ } finally {
122
+ lock?.release();
123
+ }
101
124
  }
102
125
 
103
126
  private checkIfShouldBeExecuted(
@@ -23,6 +23,7 @@ export class CommandActionBuilderWithState<TActionState extends IActionState> {
23
23
  stateConstructor: () => TActionState;
24
24
  handler: CommandHandler<TActionState> = Noop.call;
25
25
  condition: CommandCondition<TActionState> = Noop.true;
26
+ maxAllowedSimultaniousExecutions: number = 0;
26
27
 
27
28
  /**
28
29
  * Builder for `CommandAction` with state represented by `TActionState`
@@ -86,6 +87,14 @@ export class CommandActionBuilderWithState<TActionState extends IActionState> {
86
87
  return this;
87
88
  }
88
89
 
90
+ /** Sets maximum number of simultaniously executing handlers for this command per chat. 0 is treated as unlimited. */
91
+ ratelimit(maxAllowedSimultaniousExecutions: number) {
92
+ this.maxAllowedSimultaniousExecutions =
93
+ maxAllowedSimultaniousExecutions;
94
+
95
+ return this;
96
+ }
97
+
89
98
  /** Sets action cooldown.
90
99
  * @param seconds Cooldown in seconds.
91
100
  */
@@ -115,6 +124,7 @@ export class CommandActionBuilderWithState<TActionState extends IActionState> {
115
124
  this.cooldownSeconds,
116
125
  this.blacklist,
117
126
  this.allowedUsers,
127
+ this.maxAllowedSimultaniousExecutions,
118
128
  this.condition,
119
129
  this.stateConstructor,
120
130
  this.readmeFactory != null ? this.readmeFactory : (_) => ''
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chz-telegram-bot",
3
- "version": "0.3.11",
3
+ "version": "0.3.12",
4
4
  "type": "module",
5
5
  "dependencies": {
6
6
  "async-sema": "^3.1.1",
@@ -60,7 +60,7 @@ export class CommandActionProcessor extends BaseActionProcessor {
60
60
  }
61
61
 
62
62
  if (commands.length > 0) {
63
- telegraf.on('message', async (ctx) => {
63
+ telegraf.on('message', (ctx) => {
64
64
  const msg = new IncomingMessage(
65
65
  ctx.update.message,
66
66
  this.botName
@@ -82,7 +82,7 @@ export class CommandActionProcessor extends BaseActionProcessor {
82
82
  );
83
83
  }
84
84
 
85
- await this.processMessage(msg);
85
+ void this.processMessage(msg);
86
86
  });
87
87
  }
88
88
  }