chz-telegram-bot 0.0.1

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 (83) hide show
  1. package/build.ps1 +34 -0
  2. package/bun.lock +506 -0
  3. package/dist/entities/actions/commandAction.js +79 -0
  4. package/dist/entities/actions/scheduledAction.js +65 -0
  5. package/dist/entities/bot.js +75 -0
  6. package/dist/entities/cachedStateFactory.js +9 -0
  7. package/dist/entities/commandTriggerCheckResult.js +22 -0
  8. package/dist/entities/context/chatContext.js +30 -0
  9. package/dist/entities/context/messageContext.js +46 -0
  10. package/dist/entities/incomingMessage.js +20 -0
  11. package/dist/entities/responses/imageMessage.js +11 -0
  12. package/dist/entities/responses/reaction.js +11 -0
  13. package/dist/entities/responses/textMessage.js +11 -0
  14. package/dist/entities/responses/videoMessage.js +11 -0
  15. package/dist/entities/states/actionStateBase.js +8 -0
  16. package/dist/entities/states/potuzhnoState.js +13 -0
  17. package/dist/entities/taskRecord.js +10 -0
  18. package/dist/entities/transactionResult.js +9 -0
  19. package/dist/helpers/builders/commandActionBuilder.js +61 -0
  20. package/dist/helpers/builders/scheduledActionBuilder.js +42 -0
  21. package/dist/helpers/escapeMarkdown.js +17 -0
  22. package/dist/helpers/getWeek.js +12 -0
  23. package/dist/helpers/noop.js +13 -0
  24. package/dist/helpers/randomInt.js +8 -0
  25. package/dist/helpers/reverseMap.js +6 -0
  26. package/dist/helpers/timeConvertions.js +14 -0
  27. package/dist/helpers/toArray.js +6 -0
  28. package/dist/index.js +33 -0
  29. package/dist/services/logger.js +17 -0
  30. package/dist/services/storage.js +79 -0
  31. package/dist/services/taskScheduler.js +37 -0
  32. package/dist/services/telegramApi.js +94 -0
  33. package/dist/types/actionState.js +2 -0
  34. package/dist/types/actionWithState.js +2 -0
  35. package/dist/types/cachedValueAccessor.js +2 -0
  36. package/dist/types/commandCondition.js +2 -0
  37. package/dist/types/daysOfTheWeek.js +13 -0
  38. package/dist/types/handlers.js +2 -0
  39. package/dist/types/replyMessage.js +2 -0
  40. package/dist/types/scheduledItem.js +2 -0
  41. package/dist/types/timeValues.js +2 -0
  42. package/entities/actions/commandAction.ts +143 -0
  43. package/entities/actions/scheduledAction.ts +121 -0
  44. package/entities/bot.ts +143 -0
  45. package/entities/cachedStateFactory.ts +14 -0
  46. package/entities/commandTriggerCheckResult.ts +30 -0
  47. package/entities/context/chatContext.ts +57 -0
  48. package/entities/context/messageContext.ts +94 -0
  49. package/entities/incomingMessage.ts +27 -0
  50. package/entities/responses/imageMessage.ts +21 -0
  51. package/entities/responses/reaction.ts +20 -0
  52. package/entities/responses/textMessage.ts +20 -0
  53. package/entities/responses/videoMessage.ts +21 -0
  54. package/entities/states/actionStateBase.ts +5 -0
  55. package/entities/states/potuzhnoState.ts +5 -0
  56. package/entities/taskRecord.ts +13 -0
  57. package/entities/transactionResult.ts +11 -0
  58. package/eslint.config.js +10 -0
  59. package/helpers/builders/commandActionBuilder.ts +88 -0
  60. package/helpers/builders/scheduledActionBuilder.ts +67 -0
  61. package/helpers/escapeMarkdown.ts +12 -0
  62. package/helpers/getWeek.ts +8 -0
  63. package/helpers/noop.ts +13 -0
  64. package/helpers/randomInt.ts +7 -0
  65. package/helpers/reverseMap.ts +3 -0
  66. package/helpers/timeConvertions.ts +13 -0
  67. package/helpers/toArray.ts +3 -0
  68. package/index.ts +47 -0
  69. package/package.json +30 -0
  70. package/services/logger.ts +30 -0
  71. package/services/storage.ts +111 -0
  72. package/services/taskScheduler.ts +66 -0
  73. package/services/telegramApi.ts +172 -0
  74. package/tsconfig.json +110 -0
  75. package/types/actionState.ts +3 -0
  76. package/types/actionWithState.ts +6 -0
  77. package/types/cachedValueAccessor.ts +1 -0
  78. package/types/commandCondition.ts +6 -0
  79. package/types/daysOfTheWeek.ts +9 -0
  80. package/types/handlers.ts +14 -0
  81. package/types/replyMessage.ts +6 -0
  82. package/types/scheduledItem.ts +6 -0
  83. package/types/timeValues.ts +29 -0
@@ -0,0 +1,143 @@
1
+ import { Telegraf } from 'telegraf';
2
+ import TelegramApiService from '../services/telegramApi';
3
+ import IncomingMessage from './incomingMessage';
4
+ import taskScheduler from '../services/taskScheduler';
5
+ import logger from '../services/logger';
6
+ import CommandAction from './actions/commandAction';
7
+ import ScheduledAction from './actions/scheduledAction';
8
+ import IActionState from '../types/actionState';
9
+ import {
10
+ hoursToMilliseconds,
11
+ secondsToMilliseconds
12
+ } from '../helpers/timeConvertions';
13
+ import { Hours, Seconds } from '../types/timeValues';
14
+ import storage from '../services/storage';
15
+
16
+ export default class Bot {
17
+ name: string;
18
+ api: TelegramApiService;
19
+ telegraf: Telegraf;
20
+ commands: CommandAction<IActionState>[];
21
+ scheduled: ScheduledAction[];
22
+ chats: Map<string, number>;
23
+ messageQueue: IncomingMessage[] = [];
24
+
25
+ constructor(
26
+ name: string,
27
+ token: string,
28
+ commands: CommandAction<IActionState>[],
29
+ scheduled: ScheduledAction[],
30
+ chats: Map<string, number>
31
+ ) {
32
+ this.name = name;
33
+ this.commands = commands;
34
+ this.scheduled = scheduled;
35
+ this.chats = chats;
36
+
37
+ logger.logWithTraceId(
38
+ this.name,
39
+ `System:Bot-${this.name}-Start`,
40
+ 'System',
41
+ 'Starting bot...'
42
+ );
43
+ this.telegraf = new Telegraf(token);
44
+
45
+ this.api = new TelegramApiService(this.name, this.telegraf, this.chats);
46
+
47
+ this.telegraf.on('message', async (ctx) => {
48
+ const msg = new IncomingMessage(ctx.update.message);
49
+ const messageContent = msg.text || '<non-text message>';
50
+
51
+ const messageFromName = msg.from?.first_name ?? 'Unknown';
52
+ const messageFromId = msg.from?.id ?? 'Unknown';
53
+ logger.logWithTraceId(
54
+ this.name,
55
+ msg.traceId,
56
+ msg.chatName,
57
+ `${messageFromName} (${messageFromId}): ${messageContent}`
58
+ );
59
+
60
+ if (msg.text) {
61
+ this.messageQueue.push(msg);
62
+ }
63
+ });
64
+
65
+ this.telegraf.launch();
66
+
67
+ taskScheduler.createTask(
68
+ 'MessageProcessing',
69
+ async () => {
70
+ while (this.messageQueue.length > 0) {
71
+ await this.processMessages();
72
+ }
73
+ },
74
+ secondsToMilliseconds(0.3 as Seconds),
75
+ false,
76
+ this.name
77
+ );
78
+
79
+ taskScheduler.createTask(
80
+ 'ScheduledProcessing',
81
+ async () => {
82
+ await this.runScheduled();
83
+ },
84
+ hoursToMilliseconds(0.5 as Hours),
85
+ true,
86
+ this.name
87
+ );
88
+
89
+ storage.saveMetadata([...this.commands, ...this.scheduled], this.name);
90
+ }
91
+
92
+ stop(code: string) {
93
+ logger.logWithTraceId(
94
+ this.name,
95
+ `System:Bot-${this.name}-Stop`,
96
+ 'System',
97
+ 'Stopping bot...'
98
+ );
99
+
100
+ this.telegraf.stop(code);
101
+ }
102
+
103
+ private async runScheduled() {
104
+ for (const [chatName, chatId] of this.chats.entries()) {
105
+ for (const trig of this.scheduled) {
106
+ const ctx = this.api.createContextForChat(chatId, trig.name);
107
+
108
+ try {
109
+ await trig.exec(ctx);
110
+ } catch (error) {
111
+ console.dir(error);
112
+ logger.errorWithTraceId(
113
+ ctx.botName,
114
+ ctx.traceId,
115
+ chatName,
116
+ error as string | Error,
117
+ ctx
118
+ );
119
+ }
120
+ }
121
+ }
122
+ }
123
+
124
+ private async processMessages() {
125
+ const msg = this.messageQueue.pop()!;
126
+
127
+ for (const cmd of this.commands) {
128
+ const ctx = this.api.createContextForMessage(msg);
129
+
130
+ try {
131
+ await cmd.exec(ctx);
132
+ } catch (error) {
133
+ logger.errorWithTraceId(
134
+ ctx.botName,
135
+ ctx.traceId,
136
+ ctx.chatName,
137
+ error as string | Error,
138
+ ctx
139
+ );
140
+ }
141
+ }
142
+ }
143
+ }
@@ -0,0 +1,14 @@
1
+ import { Hours } from '../types/timeValues';
2
+
3
+ export default class CachedStateFactory {
4
+ getValue: () => Promise<unknown>;
5
+ invalidationTimeoutInHours: Hours;
6
+
7
+ constructor(
8
+ itemFactory: () => Promise<unknown>,
9
+ invalidationTimeout: Hours
10
+ ) {
11
+ this.getValue = itemFactory;
12
+ this.invalidationTimeoutInHours = invalidationTimeout;
13
+ }
14
+ }
@@ -0,0 +1,30 @@
1
+ export default class CommandTriggerCheckResult {
2
+ static get DontTriggerAndSkipCooldown() {
3
+ return new CommandTriggerCheckResult(false, [], true);
4
+ }
5
+ static get DoNotTrigger() {
6
+ return new CommandTriggerCheckResult(false, [], false);
7
+ }
8
+
9
+ shouldTrigger: boolean;
10
+ matchResults: RegExpExecArray[];
11
+ skipCooldown: boolean;
12
+
13
+ constructor(
14
+ shouldTrigger: boolean,
15
+ matchResults: RegExpExecArray[],
16
+ skipCooldown: boolean
17
+ ) {
18
+ this.shouldTrigger = shouldTrigger;
19
+ this.matchResults = matchResults;
20
+ this.skipCooldown = skipCooldown;
21
+ }
22
+
23
+ mergeWith(other: CommandTriggerCheckResult) {
24
+ this.shouldTrigger = this.shouldTrigger || other.shouldTrigger;
25
+ this.matchResults = this.matchResults.concat(other.matchResults);
26
+ this.skipCooldown = this.skipCooldown || other.skipCooldown;
27
+
28
+ return this;
29
+ }
30
+ }
@@ -0,0 +1,57 @@
1
+ import ImageMessage from '../responses/imageMessage';
2
+ import TextMessage from '../responses/textMessage';
3
+ import VideoMessage from '../responses/videoMessage';
4
+ import { resolve } from 'path';
5
+ import { IBotApiInteractions } from '../../services/telegramApi';
6
+
7
+ export default class ChatContext {
8
+ botName: string;
9
+ interactions: IBotApiInteractions;
10
+ chatId: number;
11
+ chatName: string;
12
+ traceId: number | string;
13
+
14
+ constructor(
15
+ botName: string,
16
+ interactions: IBotApiInteractions,
17
+ chatId: number,
18
+ chatName: string,
19
+ traceId: number | string
20
+ ) {
21
+ this.botName = botName;
22
+ this.interactions = interactions;
23
+ this.chatId = chatId;
24
+ this.chatName = chatName;
25
+ this.traceId = traceId;
26
+ }
27
+
28
+ sendTextToChat(text: string) {
29
+ this.interactions.respond(
30
+ new TextMessage(text, this.chatId, undefined, this.traceId)
31
+ );
32
+ }
33
+
34
+ sendImageToChat(name: string) {
35
+ const filePath = `./content/${name}.png`;
36
+ this.interactions.respond(
37
+ new ImageMessage(
38
+ { source: resolve(filePath) },
39
+ this.chatId,
40
+ undefined,
41
+ this.traceId
42
+ )
43
+ );
44
+ }
45
+
46
+ sendVideoToChat(name: string) {
47
+ const filePath = `./content/${name}.mp4`;
48
+ this.interactions.respond(
49
+ new VideoMessage(
50
+ { source: resolve(filePath) },
51
+ this.chatId,
52
+ undefined,
53
+ this.traceId
54
+ )
55
+ );
56
+ }
57
+ }
@@ -0,0 +1,94 @@
1
+ import ActionStateBase from '../states/actionStateBase';
2
+ import ImageMessage from '../responses/imageMessage';
3
+ import TextMessage from '../responses/textMessage';
4
+ import VideoMessage from '../responses/videoMessage';
5
+ import ChatContext from './chatContext';
6
+ import storage from '../../services/storage';
7
+ import { resolve } from 'path';
8
+ import IActionState from '../../types/actionState';
9
+ import { IBotApiInteractions } from '../../services/telegramApi';
10
+ import { TelegramEmoji } from 'telegraf/types';
11
+ import Reaction from '../responses/reaction';
12
+
13
+ export default class MessageContext<
14
+ TActionState extends IActionState
15
+ > extends ChatContext {
16
+ messageId: number;
17
+ messageText: string;
18
+ matchResults: RegExpMatchArray[] = [];
19
+ fromUserId: number | undefined;
20
+ startCooldown: boolean = true;
21
+ updateActions: Array<(state: TActionState) => void> = [];
22
+ fromUserName: string;
23
+
24
+ constructor(
25
+ botName: string,
26
+ interactions: IBotApiInteractions,
27
+ chatId: number,
28
+ chatName: string,
29
+ messageId: number,
30
+ messageText: string,
31
+ fromUserId: number | undefined,
32
+ traceId: number | string,
33
+ fromUserName: string
34
+ ) {
35
+ super(botName, interactions, chatId, chatName, traceId);
36
+
37
+ this.messageId = messageId;
38
+ this.messageText = messageText;
39
+ this.fromUserId = fromUserId;
40
+ this.fromUserName = fromUserName;
41
+ }
42
+
43
+ async loadStateOf<TAnotherActionState extends IActionState>(
44
+ commandName: string
45
+ ): Promise<TAnotherActionState> {
46
+ return (
47
+ ((await storage.load(`command:${commandName.replace('.', '-')}`))[
48
+ this.chatId
49
+ ] as TAnotherActionState) ?? new ActionStateBase()
50
+ );
51
+ }
52
+
53
+ updateState(stateUpdateAction: (state: TActionState) => void) {
54
+ this.updateActions.push(
55
+ stateUpdateAction as (state: TActionState) => void
56
+ );
57
+ }
58
+
59
+ replyWithText(text: string) {
60
+ this.interactions.respond(
61
+ new TextMessage(text, this.chatId, this.messageId, this.traceId)
62
+ );
63
+ }
64
+
65
+ replyWithImage(name: string) {
66
+ const filePath = `./content/${name}.png`;
67
+ this.interactions.respond(
68
+ new ImageMessage(
69
+ { source: resolve(filePath) },
70
+ this.chatId,
71
+ this.messageId,
72
+ this.traceId
73
+ )
74
+ );
75
+ }
76
+
77
+ replyWithVideo(name: string) {
78
+ const filePath = `./content/${name}.mp4`;
79
+ this.interactions.respond(
80
+ new VideoMessage(
81
+ { source: resolve(filePath) },
82
+ this.chatId,
83
+ this.messageId,
84
+ this.traceId
85
+ )
86
+ );
87
+ }
88
+
89
+ react(emoji: TelegramEmoji) {
90
+ this.interactions.react(
91
+ new Reaction(this.traceId, this.chatId, this.messageId, emoji)
92
+ );
93
+ }
94
+ }
@@ -0,0 +1,27 @@
1
+ import { Chat, User } from 'telegraf/types';
2
+ import randomInteger from '../helpers/randomInt';
3
+
4
+ export default class IncomingMessage {
5
+ message_id: number;
6
+ chat: Chat;
7
+ from: User | undefined;
8
+ text: string;
9
+ chatName: string;
10
+ traceId = randomInteger(10000, 99999);
11
+
12
+ constructor(ctxMessage: {
13
+ message_id: number;
14
+ chat: Chat;
15
+ from?: User;
16
+ text?: string;
17
+ }) {
18
+ this.message_id = ctxMessage.message_id;
19
+ this.chat = ctxMessage.chat;
20
+ this.from = ctxMessage.from;
21
+ this.text = ctxMessage.text || '';
22
+ this.chatName =
23
+ 'title' in ctxMessage.chat
24
+ ? ctxMessage.chat.title + ' ' + ctxMessage.chat.id
25
+ : 'DM';
26
+ }
27
+ }
@@ -0,0 +1,21 @@
1
+ import { InputFile } from 'telegraf/types';
2
+ import IReplyMessage from '../../types/replyMessage';
3
+
4
+ export default class ImageMessage implements IReplyMessage<InputFile> {
5
+ content: InputFile;
6
+ chatId: number;
7
+ replyId: number | undefined;
8
+ traceId: string | number;
9
+
10
+ constructor(
11
+ image: InputFile,
12
+ chatId: number,
13
+ replyId: number | undefined,
14
+ traceId: number | string
15
+ ) {
16
+ this.content = image;
17
+ this.chatId = chatId;
18
+ this.replyId = replyId;
19
+ this.traceId = traceId;
20
+ }
21
+ }
@@ -0,0 +1,20 @@
1
+ import { TelegramEmoji } from 'telegraf/types';
2
+
3
+ export default class Reaction {
4
+ chatId: number;
5
+ messageId: number;
6
+ traceId: number | string;
7
+ emoji: TelegramEmoji;
8
+
9
+ constructor(
10
+ traceId: number | string,
11
+ chatId: number,
12
+ messageId: number,
13
+ emoji: TelegramEmoji
14
+ ) {
15
+ this.chatId = chatId;
16
+ this.messageId = messageId;
17
+ this.emoji = emoji;
18
+ this.traceId = traceId;
19
+ }
20
+ }
@@ -0,0 +1,20 @@
1
+ import IReplyMessage from '../../types/replyMessage';
2
+
3
+ export default class TextMessage implements IReplyMessage<string> {
4
+ content: string;
5
+ chatId: number;
6
+ replyId: number | undefined;
7
+ traceId: string | number;
8
+
9
+ constructor(
10
+ text: string,
11
+ chatId: number,
12
+ replyId: number | undefined,
13
+ traceId: string | number
14
+ ) {
15
+ this.content = text;
16
+ this.chatId = chatId;
17
+ this.replyId = replyId;
18
+ this.traceId = traceId;
19
+ }
20
+ }
@@ -0,0 +1,21 @@
1
+ import { InputFile } from 'telegraf/types';
2
+ import IReplyMessage from '../../types/replyMessage';
3
+
4
+ export default class VideoMessage implements IReplyMessage<InputFile> {
5
+ content: InputFile;
6
+ chatId: number;
7
+ replyId: number | undefined;
8
+ traceId: string | number;
9
+
10
+ constructor(
11
+ video: InputFile,
12
+ chatId: number,
13
+ replyId: number | undefined,
14
+ traceId: number | string
15
+ ) {
16
+ this.content = video;
17
+ this.chatId = chatId;
18
+ this.replyId = replyId;
19
+ this.traceId = traceId;
20
+ }
21
+ }
@@ -0,0 +1,5 @@
1
+ import IActionState from '../../types/actionState';
2
+
3
+ export default class ActionStateBase implements IActionState {
4
+ lastExecutedDate = 0;
5
+ }
@@ -0,0 +1,5 @@
1
+ import ActionStateBase from './actionStateBase';
2
+
3
+ export default class PotuzhnoState extends ActionStateBase {
4
+ scoreBoard: Record<string, number> = {};
5
+ }
@@ -0,0 +1,13 @@
1
+ import { Milliseconds } from '../types/timeValues';
2
+
3
+ export default class TaskRecord {
4
+ name: string;
5
+ taskId: NodeJS.Timeout;
6
+ interval: Milliseconds;
7
+
8
+ constructor(name: string, taskId: NodeJS.Timeout, interval: Milliseconds) {
9
+ this.name = name;
10
+ this.taskId = taskId;
11
+ this.interval = interval;
12
+ }
13
+ }
@@ -0,0 +1,11 @@
1
+ import IActionState from '../types/actionState';
2
+
3
+ export default class TransactionResult {
4
+ data: IActionState;
5
+ shouldUpdate: boolean;
6
+
7
+ constructor(data: IActionState, shouldUpdate: boolean) {
8
+ this.data = data;
9
+ this.shouldUpdate = shouldUpdate;
10
+ }
11
+ }
@@ -0,0 +1,10 @@
1
+ import eslint from '@eslint/js';
2
+ import tseslint from 'typescript-eslint';
3
+
4
+ export default tseslint.config(
5
+ eslint.configs.recommended,
6
+ ...tseslint.configs.recommended,
7
+ {
8
+ ignores: ["build/*"]
9
+ },
10
+ );
@@ -0,0 +1,88 @@
1
+ import CommandAction from '../../entities/actions/commandAction';
2
+ import ActionStateBase from '../../entities/states/actionStateBase';
3
+ import toArray from '../toArray';
4
+ import IActionState from '../../types/actionState';
5
+ import { CommandHandler } from '../../types/handlers';
6
+ import { CommandCondition } from '../../types/commandCondition';
7
+ import { Seconds } from '../../types/timeValues';
8
+ import Noop from '../noop';
9
+
10
+ export class CommandActionBuilderWithState<TActionState extends IActionState> {
11
+ name: string;
12
+ trigger: string | RegExp | Array<string> | Array<RegExp> = [];
13
+
14
+ active = true;
15
+ cooldownSeconds: Seconds = 0 as Seconds;
16
+ blacklist: number[] = [];
17
+ allowedUsers: number[] = [];
18
+ stateConstructor: () => TActionState;
19
+ handler: CommandHandler<TActionState> = Noop.call;
20
+ condition: CommandCondition<TActionState> = Noop.true;
21
+
22
+ constructor(name: string, stateConstructor: () => TActionState) {
23
+ this.name = name;
24
+ this.stateConstructor = stateConstructor;
25
+ }
26
+
27
+ on(trigger: string | RegExp | Array<string> | Array<RegExp>) {
28
+ this.trigger = trigger;
29
+
30
+ return this;
31
+ }
32
+
33
+ from(id: number | Array<number>) {
34
+ this.allowedUsers = toArray(id);
35
+
36
+ return this;
37
+ }
38
+
39
+ do(handler: CommandHandler<TActionState>) {
40
+ this.handler = handler;
41
+
42
+ return this;
43
+ }
44
+
45
+ when(condition: CommandCondition<TActionState>) {
46
+ this.condition = condition;
47
+
48
+ return this;
49
+ }
50
+
51
+ disabled() {
52
+ this.active = false;
53
+
54
+ return this;
55
+ }
56
+
57
+ cooldown(seconds: Seconds) {
58
+ this.cooldownSeconds = seconds;
59
+
60
+ return this;
61
+ }
62
+
63
+ ignoreChat(chatId: number) {
64
+ this.blacklist.push(chatId);
65
+
66
+ return this;
67
+ }
68
+
69
+ build() {
70
+ return new CommandAction(
71
+ this.trigger,
72
+ this.handler,
73
+ this.name,
74
+ this.active,
75
+ this.cooldownSeconds,
76
+ this.blacklist,
77
+ this.allowedUsers,
78
+ this.condition,
79
+ this.stateConstructor
80
+ );
81
+ }
82
+ }
83
+
84
+ export class CommandActionBuilder extends CommandActionBuilderWithState<ActionStateBase> {
85
+ constructor(name: string) {
86
+ super(name, () => new ActionStateBase());
87
+ }
88
+ }
@@ -0,0 +1,67 @@
1
+ import ScheduledAction from '../../entities/actions/scheduledAction';
2
+ import CachedStateFactory from '../../entities/cachedStateFactory';
3
+ import { ScheduledHandler } from '../../types/handlers';
4
+ import { Hours, HoursOfDay } from '../../types/timeValues';
5
+ import Noop from '../noop';
6
+
7
+ export default class ScheduledActionBuilder {
8
+ active = true;
9
+ time: HoursOfDay = 0;
10
+ cachedStateFactories = new Map<string, CachedStateFactory>();
11
+ whitelist: number[] = [];
12
+ handler: ScheduledHandler = Noop.call;
13
+
14
+ name: string;
15
+
16
+ constructor(name: string) {
17
+ this.name = name;
18
+ }
19
+
20
+ allowIn(chatId: number) {
21
+ this.whitelist.push(chatId);
22
+
23
+ return this;
24
+ }
25
+
26
+ runAt(time: HoursOfDay) {
27
+ this.time = time;
28
+
29
+ return this;
30
+ }
31
+
32
+ do(handler: ScheduledHandler) {
33
+ this.handler = handler;
34
+
35
+ return this;
36
+ }
37
+
38
+ withSharedCache(
39
+ key: string,
40
+ itemFactory: () => Promise<unknown>,
41
+ invalidationTimeoutInHours: Hours = 20 as Hours
42
+ ) {
43
+ this.cachedStateFactories.set(
44
+ key,
45
+ new CachedStateFactory(itemFactory, invalidationTimeoutInHours)
46
+ );
47
+
48
+ return this;
49
+ }
50
+
51
+ disabled() {
52
+ this.active = false;
53
+
54
+ return this;
55
+ }
56
+
57
+ build() {
58
+ return new ScheduledAction(
59
+ this.name,
60
+ this.handler,
61
+ this.time,
62
+ this.active,
63
+ this.whitelist,
64
+ this.cachedStateFactories
65
+ );
66
+ }
67
+ }
@@ -0,0 +1,12 @@
1
+ import escape from 'markdown-escape';
2
+
3
+ export default function escapeMarkdown(text: string) {
4
+ return escape(text)
5
+ .replaceAll(/\./g, '\\.')
6
+ .replaceAll(/-/g, '\\-')
7
+ .replaceAll(/\+/g, '\\+')
8
+ .replaceAll(/\{/g, '\\{')
9
+ .replaceAll(/!/g, '\\!')
10
+ .replaceAll(/\|/g, '\\|')
11
+ .replaceAll(/\}/g, '\\}');
12
+ }
@@ -0,0 +1,8 @@
1
+ import moment from 'moment';
2
+
3
+ export default function getCurrentWeek() {
4
+ const firstDay = moment().startOf('isoWeek').format('YYYY-MM-DD');
5
+ const lastDay = moment().endOf('isoWeek').format('YYYY-MM-DD');
6
+
7
+ return { firstDay, lastDay };
8
+ }