@2byte/tgbot-framework 1.0.1 → 1.0.3

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 (44) hide show
  1. package/README.md +300 -300
  2. package/bin/2byte-cli.ts +97 -84
  3. package/package.json +6 -2
  4. package/src/cli/CreateBotCommand.ts +181 -181
  5. package/src/cli/GenerateCommand.ts +195 -111
  6. package/src/cli/InitCommand.ts +107 -107
  7. package/src/console/migrate.ts +82 -82
  8. package/src/core/ApiService.ts +21 -0
  9. package/src/core/ApiServiceManager.ts +63 -0
  10. package/src/core/App.ts +1113 -1042
  11. package/src/core/BotArtisan.ts +79 -79
  12. package/src/core/BotMigration.ts +30 -30
  13. package/src/core/BotSeeder.ts +66 -66
  14. package/src/core/Model.ts +84 -80
  15. package/src/core/utils.ts +2 -2
  16. package/src/illumination/Artisan.ts +149 -149
  17. package/src/illumination/InlineKeyboard.ts +61 -44
  18. package/src/illumination/Message2Byte.ts +255 -254
  19. package/src/illumination/Message2ByteLiveProgressive.ts +278 -278
  20. package/src/illumination/Message2bytePool.ts +107 -107
  21. package/src/illumination/Migration.ts +186 -186
  22. package/src/illumination/RunSectionRoute.ts +85 -85
  23. package/src/illumination/Section.ts +410 -430
  24. package/src/illumination/SectionComponent.ts +64 -64
  25. package/src/illumination/Telegraf2byteContext.ts +32 -32
  26. package/src/index.ts +35 -33
  27. package/src/libs/TelegramAccountControl.ts +738 -523
  28. package/src/types.ts +188 -186
  29. package/src/user/UserModel.ts +297 -288
  30. package/src/user/UserStore.ts +119 -119
  31. package/src/workflow/services/MassSendApiService.ts +80 -0
  32. package/templates/bot/.env.example +18 -17
  33. package/templates/bot/artisan.ts +8 -8
  34. package/templates/bot/bot.ts +79 -77
  35. package/templates/bot/database/dbConnector.ts +4 -4
  36. package/templates/bot/database/migrate.ts +9 -9
  37. package/templates/bot/database/migrations/001_create_users.sql +18 -18
  38. package/templates/bot/database/seed.ts +14 -14
  39. package/templates/bot/package.json +30 -30
  40. package/templates/bot/sectionList.ts +9 -7
  41. package/templates/bot/sections/ExampleInputSection.ts +85 -0
  42. package/templates/bot/sections/ExampleLiveTaskerSection.ts +60 -0
  43. package/templates/bot/sections/HomeSection.ts +63 -65
  44. package/templates/bot/workflow/services/ExampleServise.ts +24 -0
@@ -1,430 +1,410 @@
1
- import { type Database } from "bun:sqlite";
2
- import mustache from "mustache";
3
- import { Markup, Telegraf } from "telegraf";
4
- import { App } from "../core/App";
5
- import { MakeManualPaginateButtonsParams, RunnedSection, SectionOptions } from "../types";
6
- import { InlineKeyboard } from "./InlineKeyboard";
7
- import Message2byte from "./Message2Byte";
8
- import Message2bytePool from "./Message2bytePool";
9
- import { RunSectionRoute } from "./RunSectionRoute";
10
- import { Telegraf2byteContext } from "./Telegraf2byteContext";
11
-
12
- export class Section {
13
- static command: string;
14
- static description: string;
15
- static actionRoutes: { [key: string]: string };
16
- public sectionId: string = "BaseSection";
17
- public route: RunSectionRoute;
18
- protected ctx: Telegraf2byteContext;
19
- protected bot: Telegraf<Telegraf2byteContext>;
20
- protected app: App;
21
- protected markup: typeof Markup = Markup;
22
- protected btnHome = this.markup.button.callback("🏠 Лобби", "home.index");
23
- protected iconBack: string = "🔙";
24
- protected iconPlus: string = "➕";
25
- protected iconDelete: string = "✖️";
26
- protected iconOk: string = "☑️";
27
- protected iconInput: string = "⤵️";
28
- protected iconOutput: string = "⤴️";
29
- protected iconTime: string = "⏱";
30
- protected iconCheck: string = "\u{2714}";
31
- protected iconSet: string = "🔖";
32
- protected iconRefresh: string = "🔃";
33
- protected iconHistory: string = "🗂";
34
- protected iconEuro: string = "💶";
35
- protected iconRejected: string = "";
36
- protected labelBack: string = `${this.iconBack} Назад`;
37
-
38
- protected callbackParams: URLSearchParams | null = null;
39
- protected mustache: typeof mustache = mustache;
40
- protected mainMenuKeyboardArray: any[][] = [];
41
- protected db: Database; // Database connection
42
-
43
- constructor(options: SectionOptions) {
44
- this.ctx = options.ctx;
45
- this.bot = options.bot;
46
- this.app = options.app;
47
- this.mainMenuKeyboardArray = this.app.config.mainMenuKeyboard;
48
- this.route = options.route;
49
- this.db = (global as any).db as Database;
50
- this.callbackParams = this.parseParamsCallbackdata();
51
- this.cancelUserWaitingReply();
52
- }
53
-
54
- public async setup(): Promise<void> {}
55
- public async unsetup(): Promise<void> {}
56
-
57
- public async up(): Promise<void> {}
58
- public async down(): Promise<void> {}
59
-
60
- parseParamsCallbackdata(): URLSearchParams {
61
- let strparams = this.ctx.update?.callback_query?.data?.match(/\[(.+?)\]/);
62
-
63
- if (strparams !== null && strparams?.[1] !== undefined) {
64
- const valueToType = isFinite(Number(strparams[1])) ? +strparams[1] : strparams[1];
65
- return new URLSearchParams(String(valueToType));
66
- }
67
-
68
- return new URLSearchParams();
69
- }
70
-
71
- makePaginateButtons(
72
- metadata: any,
73
- callbackDataAction: string,
74
- paramsQuery: Record<string, any> = {}
75
- ): any[][] {
76
- if (metadata.hasOwnProperty("meta")) metadata = metadata.meta;
77
-
78
- if (metadata.last_page == 1) return [];
79
-
80
- const makeActionData = (page: number): string => {
81
- const params = { ...paramsQuery, page: page.toString() };
82
- return `${callbackDataAction}[${new URLSearchParams(
83
- params as Record<string, string>
84
- ).toString()}]`;
85
- };
86
-
87
- const layoutButtons: any[][] = [];
88
- const pageButtons: any[] = [];
89
-
90
- const makeButtonCallback = (text: string, callbackData: string): any => {
91
- return this.markup.button.callback(text, callbackData);
92
- };
93
-
94
- // Pair buttons the start and end
95
- if (metadata.last_page > 1) {
96
- const lineFirstNext: any[] = [];
97
-
98
- if (metadata.current_page > 1) {
99
- lineFirstNext.push(makeButtonCallback("Назад", makeActionData(metadata.current_page - 1)));
100
- }
101
-
102
- if (metadata.current_page < metadata.last_page) {
103
- lineFirstNext.push(makeButtonCallback("Вперед", makeActionData(metadata.current_page + 1)));
104
- }
105
-
106
- layoutButtons.push(lineFirstNext);
107
- }
108
-
109
- const generatorPageNumber = (startPage: number, lastPage: number): any[] => {
110
- const buttons: any[] = [];
111
-
112
- for (let i = startPage, c = 1; i <= lastPage && c <= 8; i++, c++) {
113
- let btn = makeButtonCallback(`${i}`, makeActionData(i));
114
-
115
- if (i == metadata.current_page) {
116
- btn = makeButtonCallback(`\u{2714} ${i}`, makeActionData(metadata.current_page));
117
- }
118
-
119
- buttons.push(btn);
120
-
121
- if (startPage + c > metadata.last_page) break;
122
- }
123
-
124
- return buttons;
125
- };
126
-
127
- // Page numbers a generator
128
- if (metadata.last_page > 8) {
129
- let startPage = 0;
130
- let lastPage = 0;
131
-
132
- if (metadata.current_page < 8) {
133
- startPage = 1;
134
- lastPage = 8;
135
- } else {
136
- startPage = metadata.current_page - 3;
137
- lastPage = metadata.current_page + 4;
138
- }
139
-
140
- layoutButtons.push(generatorPageNumber(startPage, lastPage));
141
- } else {
142
- layoutButtons.push(generatorPageNumber(1, metadata.last_page));
143
- }
144
-
145
- layoutButtons.push(pageButtons);
146
-
147
- // Pair buttons the start and end
148
- if (metadata.current_page > 1 && metadata.last_page > 8) {
149
- const lineStartLast: any[] = [];
150
-
151
- lineStartLast.push(makeButtonCallback("В начало", makeActionData(1)));
152
-
153
- if (metadata.current_page < metadata.last_page) {
154
- lineStartLast.push(makeButtonCallback("В конец", makeActionData(metadata.last_page)));
155
- }
156
-
157
- layoutButtons.push(lineStartLast);
158
- }
159
-
160
- return layoutButtons;
161
- }
162
-
163
- public static makeManualPaginateButtons(params: MakeManualPaginateButtonsParams): any[][] {
164
- let { callbackDataAction, currentPage, totalRecords, perPage, paramsQuery } = params;
165
-
166
- currentPage = parseInt(String(currentPage));
167
- const last_page = Math.ceil(totalRecords / perPage);
168
-
169
- if (last_page <= 1) return [];
170
-
171
- const makeActionData = (page: number): string => {
172
- const params = { ...paramsQuery, page: page.toString() };
173
- return `${callbackDataAction}[${new URLSearchParams(
174
- params as Record<string, string>
175
- ).toString()}]`;
176
- };
177
-
178
- const layoutButtons: any[][] = [];
179
-
180
- const makeButtonCallback = (text: string, callbackData: string): any => {
181
- return Markup.button.callback(text, callbackData);
182
- };
183
-
184
- // Pair buttons the start and end
185
- if (last_page > 1) {
186
- const lineFirstNext: any[] = [];
187
-
188
- if (currentPage > 1) {
189
- lineFirstNext.push(makeButtonCallback("⬅️ Назад", makeActionData(currentPage - 1)));
190
- }
191
-
192
- if (currentPage < last_page) {
193
- lineFirstNext.push(makeButtonCallback("Вперед ➡️", makeActionData(currentPage + 1)));
194
- }
195
-
196
- layoutButtons.push(lineFirstNext);
197
- }
198
-
199
- const generatorPageNumber = (startPage: number, lastPage: number): any[] => {
200
- const buttons: any[] = [];
201
-
202
- for (let i = startPage, c = 1; i <= lastPage && c <= 8; i++, c++) {
203
- let btn = makeButtonCallback(`${i}`, makeActionData(i));
204
-
205
- if (i == currentPage) {
206
- btn = makeButtonCallback(`\u{2714} ${i}`, makeActionData(currentPage));
207
- }
208
-
209
- buttons.push(btn);
210
-
211
- if (startPage + c > last_page) break;
212
- }
213
-
214
- return buttons;
215
- };
216
-
217
- // Page numbers a generator
218
- if (last_page > 8) {
219
- let startPage = 0;
220
- let lastPage = 0;
221
-
222
- if (currentPage < 8) {
223
- startPage = 1;
224
- lastPage = 8;
225
- } else {
226
- startPage = currentPage - 3;
227
- lastPage = currentPage + 4;
228
- }
229
-
230
- layoutButtons.push(generatorPageNumber(startPage, lastPage));
231
- } else {
232
- layoutButtons.push(generatorPageNumber(1, last_page));
233
- }
234
-
235
- // Pair buttons the start and end
236
- if (currentPage > 1 && last_page > 8) {
237
- const lineStartLast: any[] = [];
238
-
239
- lineStartLast.push(makeButtonCallback("В начало", makeActionData(1)));
240
-
241
- if (currentPage < last_page) {
242
- lineStartLast.push(makeButtonCallback("В конец", makeActionData(last_page)));
243
- }
244
-
245
- layoutButtons.push(lineStartLast);
246
- }
247
-
248
- return layoutButtons;
249
- }
250
-
251
- isRepeatedQuery(objParamsNeedle: Record<string, any>): boolean {
252
- if (!this.callbackParams) return false;
253
-
254
- const isEquals = Object.keys(objParamsNeedle)
255
- .map((key) => [key, objParamsNeedle[key]])
256
- .every((entry) => {
257
- return this.callbackParams?.get(entry[0]) == entry[1];
258
- });
259
-
260
- return isEquals;
261
- }
262
-
263
- async setupKeyboard(): Promise<void> {
264
- if (this.ctx.userSession.setupKeyboardDone) return;
265
-
266
- await this.newMessage("Welcome!")
267
- .keyboard({
268
- keyboard: this.mainMenuKeyboardArray,
269
- resize_keyboard: true,
270
- one_time_keyboard: true,
271
- })
272
- .send();
273
-
274
- this.ctx.userSession.setupKeyboardDone = true;
275
- }
276
-
277
- async getSetting(name: string): Promise<any> {
278
- try {
279
- const allSettings = await (global as any).settings;
280
-
281
- return allSettings.data[name];
282
- } catch (err) {
283
- throw err;
284
- }
285
- }
286
-
287
- cancelUserWaitingReply(): boolean {
288
- if (this.callbackParams?.has("cancel_wait")) {
289
- // Очищаем состояние ожидания ввода
290
- if (this.ctx.userSession.awaitingInput) {
291
- delete this.ctx.userSession.awaitingInput;
292
- }
293
-
294
- if (this.ctx.userSession.awaitingInputPromise) {
295
- // Отклоняем Promise с сообщением об отмене
296
- this.ctx.userSession.awaitingInputPromise.reject(new Error("Ввод отменен пользователем"));
297
- delete this.ctx.userSession.awaitingInputPromise;
298
- }
299
-
300
- return true;
301
- }
302
-
303
- return false;
304
- }
305
-
306
- backInlineButtion(data: string): any {
307
- return this.markup.button.callback(this.labelBack, data);
308
- }
309
-
310
- inlineButton(text: string, data: string): any {
311
- return this.markup.button.callback(text, data);
312
- }
313
-
314
- setCallbackParams(params: URLSearchParams): this {
315
- this.callbackParams = params;
316
- return this;
317
- }
318
-
319
- existsAnswerInput(inputKey: string): boolean {
320
- return this.ctx.userSession[inputKey] !== undefined;
321
- }
322
-
323
- getAnswerInput(inputKey: string): any {
324
- return this.ctx.userSession[inputKey];
325
- }
326
-
327
- setAnswerInput(inputKey: string, value: any): void {
328
- this.ctx.userSession[inputKey] = value;
329
- }
330
-
331
- makeQueryParams(...args: any[]): URLSearchParams {
332
- const params = new URLSearchParams();
333
-
334
- args.forEach((arg) => {
335
- if (typeof arg === "string") {
336
- new URLSearchParams(arg).forEach((value, key) => {
337
- params.set(key, value);
338
- });
339
- }
340
- if (arg !== null && typeof arg === "object") {
341
- this.app.debugLog("arg", arg);
342
- for (const [key, value] of Object.entries(arg)) {
343
- params.set(key, String(value));
344
- }
345
- }
346
- });
347
-
348
- return params;
349
- }
350
-
351
- makeInlineKeyboard(buttons: any[][]): InlineKeyboard {
352
- const keyboard = InlineKeyboard.init(this.ctx);
353
- buttons.forEach((row) => {
354
- keyboard.append(row);
355
- });
356
- return keyboard;
357
- }
358
-
359
- makeInlineButton(text: string, data: string): any {
360
- return Markup.button.callback(text, data);
361
- }
362
-
363
- message(message: string): Message2byte {
364
- if (this.route.runIsCallbackQuery) {
365
- return this.updateMessage(message);
366
- }
367
- return Message2byte.init(this.ctx, this).message(message);
368
- }
369
-
370
- newMessage(message: string): Message2byte {
371
- return Message2byte.init(this.ctx, this).message(message);
372
- }
373
-
374
- updateMessage(message: string): Message2byte {
375
- return Message2byte.init(this.ctx, this).updateMessage(message);
376
- }
377
-
378
- createPoolNewMessage(message: string): Message2bytePool {
379
- return Message2byte.init(this.ctx, this).createPoolMessage(message);
380
- }
381
-
382
- createUpdatePoolMessage(message: string): Message2bytePool {
383
- return Message2byte.init(this.ctx, this).createUpdatePoolMessage(message);
384
- }
385
-
386
- createPoolMessage(message: string): Message2bytePool {
387
- return this.route.runIsCallbackQuery
388
- ? this.createUpdatePoolMessage(message)
389
- : this.createPoolNewMessage(message);
390
- }
391
-
392
- getCtx(): Telegraf2byteContext {
393
- return this.ctx;
394
- }
395
-
396
- getCurrentSection(): RunnedSection {
397
- return this.app.getRunnedSection(this.ctx.user);
398
- }
399
-
400
- getPreviousSection(): RunnedSection | undefined {
401
- return this.ctx.userSession.previousSection;
402
- }
403
-
404
- async sleepProgressBar(messageWait: string, ms: number): Promise<void> {
405
- const { promise, resolve, reject } = Promise.withResolvers<void>();
406
- const pgIcons = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
407
-
408
- let pgIndex = 0;
409
- message += `[pg]${pgIcons[pgIndex]} ${message}`;
410
-
411
- const pgIntervalTimer = setInterval(() => {
412
- // Update progress message here
413
- message = message.replace(/\[pg\].*/, `[pg]${pgIcons[pgIndex]} ${messageWait}`);
414
- pgIndex = (pgIndex + 1) % pgIcons.length;
415
-
416
- this.message(message)
417
- .send()
418
- .catch((err) => {
419
- clearInterval(pgIntervalTimer);
420
- reject(err);
421
- });
422
- }, 1000);
423
- setTimeout(() => {
424
- message = message.replace(/\[pg\].*/, ``);
425
- clearInterval(pgIntervalTimer);
426
- resolve();
427
- }, ms);
428
- return promise;
429
- };
430
- }
1
+ import { type Database } from "bun:sqlite";
2
+ import mustache from "mustache";
3
+ import { Markup, Telegraf } from "telegraf";
4
+ import { App } from "../core/App";
5
+ import { MakeManualPaginateButtonsParams, RunnedSection, SectionOptions } from "../types";
6
+ import { InlineKeyboard } from "./InlineKeyboard";
7
+ import Message2byte from "./Message2Byte";
8
+ import Message2bytePool from "./Message2bytePool";
9
+ import { RunSectionRoute } from "./RunSectionRoute";
10
+ import { Telegraf2byteContext } from "./Telegraf2byteContext";
11
+
12
+ export class Section {
13
+ static command: string;
14
+ static description: string;
15
+ static actionRoutes: { [key: string]: string };
16
+ public sectionId: string = "BaseSection";
17
+ public route: RunSectionRoute;
18
+ protected ctx: Telegraf2byteContext;
19
+ protected bot: Telegraf<Telegraf2byteContext>;
20
+ protected app: App;
21
+ protected markup: typeof Markup = Markup;
22
+ protected btnHome = this.markup.button.callback("🏠 Лобби", "home.index");
23
+ protected iconBack: string = "🔙";
24
+ protected iconPlus: string = "➕";
25
+ protected iconDelete: string = "✖️";
26
+ protected iconOk: string = "☑️";
27
+ protected iconInput: string = "⤵️";
28
+ protected iconOutput: string = "⤴️";
29
+ protected iconTime: string = "⏱";
30
+ protected iconCheck: string = "\u{2714}";
31
+ protected iconSet: string = "🔖";
32
+ protected iconRefresh: string = "🔃";
33
+ protected iconHistory: string = "🗂";
34
+ protected iconEuro: string = "💶";
35
+ protected iconDollar: string = "💵";
36
+ protected iconRuble: string = "₽";
37
+ protected iconPencil: string = "🖉";
38
+ protected iconInfo: string = "ℹ️";
39
+ protected iconWarning: string = "⚠️";
40
+ protected iconQuestion: string = "❓";
41
+ protected iconSuccess: string = "✅";
42
+ protected iconRejected: string = "❌";
43
+ protected labelBack: string = `${this.iconBack} Назад`;
44
+
45
+ protected callbackParams: URLSearchParams | null = null;
46
+ protected mustache: typeof mustache = mustache;
47
+ protected mainMenuKeyboardArray: any[][] = [];
48
+ protected db: Database; // Database connection
49
+
50
+ constructor(options: SectionOptions) {
51
+ this.ctx = options.ctx;
52
+ this.bot = options.bot;
53
+ this.app = options.app;
54
+ this.mainMenuKeyboardArray = this.app.configApp.mainMenuKeyboard;
55
+ this.route = options.route;
56
+ this.db = (global as any).db as Database;
57
+ this.callbackParams = this.parseParamsCallbackdata();
58
+ this.cancelUserWaitingReply();
59
+ }
60
+
61
+ public async setup(): Promise<void> {}
62
+ public async unsetup(): Promise<void> {}
63
+
64
+ public async up(): Promise<void> {}
65
+ public async down(): Promise<void> {}
66
+
67
+ parseParamsCallbackdata(): URLSearchParams {
68
+ let strparams = this.ctx.update?.callback_query?.data?.match(/\[(.+?)\]/);
69
+
70
+ if (strparams !== null && strparams?.[1] !== undefined) {
71
+ const valueToType = isFinite(Number(strparams[1])) ? +strparams[1] : strparams[1];
72
+ return new URLSearchParams(String(valueToType));
73
+ }
74
+
75
+ return new URLSearchParams();
76
+ }
77
+
78
+ makePaginateButtons(
79
+ metadata: any,
80
+ callbackDataAction: string,
81
+ paramsQuery: Record<string, any> = {}
82
+ ): any[][] {
83
+ if (metadata.hasOwnProperty("meta")) metadata = metadata.meta;
84
+
85
+ if (metadata.last_page == 1) return [];
86
+
87
+ const makeActionData = (page: number): string => {
88
+ const params = { ...paramsQuery, page: page.toString() };
89
+ return `${callbackDataAction}[${new URLSearchParams(
90
+ params as Record<string, string>
91
+ ).toString()}]`;
92
+ };
93
+
94
+ const layoutButtons: any[][] = [];
95
+ const pageButtons: any[] = [];
96
+
97
+ const makeButtonCallback = (text: string, callbackData: string): any => {
98
+ return this.markup.button.callback(text, callbackData);
99
+ };
100
+
101
+ // Pair buttons the start and end
102
+ if (metadata.last_page > 1) {
103
+ const lineFirstNext: any[] = [];
104
+
105
+ if (metadata.current_page > 1) {
106
+ lineFirstNext.push(makeButtonCallback("Назад", makeActionData(metadata.current_page - 1)));
107
+ }
108
+
109
+ if (metadata.current_page < metadata.last_page) {
110
+ lineFirstNext.push(makeButtonCallback("Вперед", makeActionData(metadata.current_page + 1)));
111
+ }
112
+
113
+ layoutButtons.push(lineFirstNext);
114
+ }
115
+
116
+ const generatorPageNumber = (startPage: number, lastPage: number): any[] => {
117
+ const buttons: any[] = [];
118
+
119
+ for (let i = startPage, c = 1; i <= lastPage && c <= 8; i++, c++) {
120
+ let btn = makeButtonCallback(`${i}`, makeActionData(i));
121
+
122
+ if (i == metadata.current_page) {
123
+ btn = makeButtonCallback(`\u{2714} ${i}`, makeActionData(metadata.current_page));
124
+ }
125
+
126
+ buttons.push(btn);
127
+
128
+ if (startPage + c > metadata.last_page) break;
129
+ }
130
+
131
+ return buttons;
132
+ };
133
+
134
+ // Page numbers a generator
135
+ if (metadata.last_page > 8) {
136
+ let startPage = 0;
137
+ let lastPage = 0;
138
+
139
+ if (metadata.current_page < 8) {
140
+ startPage = 1;
141
+ lastPage = 8;
142
+ } else {
143
+ startPage = metadata.current_page - 3;
144
+ lastPage = metadata.current_page + 4;
145
+ }
146
+
147
+ layoutButtons.push(generatorPageNumber(startPage, lastPage));
148
+ } else {
149
+ layoutButtons.push(generatorPageNumber(1, metadata.last_page));
150
+ }
151
+
152
+ layoutButtons.push(pageButtons);
153
+
154
+ // Pair buttons the start and end
155
+ if (metadata.current_page > 1 && metadata.last_page > 8) {
156
+ const lineStartLast: any[] = [];
157
+
158
+ lineStartLast.push(makeButtonCallback("В начало", makeActionData(1)));
159
+
160
+ if (metadata.current_page < metadata.last_page) {
161
+ lineStartLast.push(makeButtonCallback("В конец", makeActionData(metadata.last_page)));
162
+ }
163
+
164
+ layoutButtons.push(lineStartLast);
165
+ }
166
+
167
+ return layoutButtons;
168
+ }
169
+
170
+ public static makeManualPaginateButtons(params: MakeManualPaginateButtonsParams): any[][] {
171
+ let { callbackDataAction, currentPage, totalRecords, perPage, paramsQuery } = params;
172
+
173
+ currentPage = parseInt(String(currentPage));
174
+ const last_page = Math.ceil(totalRecords / perPage);
175
+
176
+ if (last_page <= 1) return [];
177
+
178
+ const makeActionData = (page: number): string => {
179
+ const params = { ...paramsQuery, page: page.toString() };
180
+ return `${callbackDataAction}[${new URLSearchParams(
181
+ params as Record<string, string>
182
+ ).toString()}]`;
183
+ };
184
+
185
+ const layoutButtons: any[][] = [];
186
+
187
+ const makeButtonCallback = (text: string, callbackData: string): any => {
188
+ return Markup.button.callback(text, callbackData);
189
+ };
190
+
191
+ // Pair buttons the start and end
192
+ if (last_page > 1) {
193
+ const lineFirstNext: any[] = [];
194
+
195
+ if (currentPage > 1) {
196
+ lineFirstNext.push(makeButtonCallback("⬅️ Назад", makeActionData(currentPage - 1)));
197
+ }
198
+
199
+ if (currentPage < last_page) {
200
+ lineFirstNext.push(makeButtonCallback("Вперед ➡️", makeActionData(currentPage + 1)));
201
+ }
202
+
203
+ layoutButtons.push(lineFirstNext);
204
+ }
205
+
206
+ const generatorPageNumber = (startPage: number, lastPage: number): any[] => {
207
+ const buttons: any[] = [];
208
+
209
+ for (let i = startPage, c = 1; i <= lastPage && c <= 8; i++, c++) {
210
+ let btn = makeButtonCallback(`${i}`, makeActionData(i));
211
+
212
+ if (i == currentPage) {
213
+ btn = makeButtonCallback(`\u{2714} ${i}`, makeActionData(currentPage));
214
+ }
215
+
216
+ buttons.push(btn);
217
+
218
+ if (startPage + c > last_page) break;
219
+ }
220
+
221
+ return buttons;
222
+ };
223
+
224
+ // Page numbers a generator
225
+ if (last_page > 8) {
226
+ let startPage = 0;
227
+ let lastPage = 0;
228
+
229
+ if (currentPage < 8) {
230
+ startPage = 1;
231
+ lastPage = 8;
232
+ } else {
233
+ startPage = currentPage - 3;
234
+ lastPage = currentPage + 4;
235
+ }
236
+
237
+ layoutButtons.push(generatorPageNumber(startPage, lastPage));
238
+ } else {
239
+ layoutButtons.push(generatorPageNumber(1, last_page));
240
+ }
241
+
242
+ // Pair buttons the start and end
243
+ if (currentPage > 1 && last_page > 8) {
244
+ const lineStartLast: any[] = [];
245
+
246
+ lineStartLast.push(makeButtonCallback("В начало", makeActionData(1)));
247
+
248
+ if (currentPage < last_page) {
249
+ lineStartLast.push(makeButtonCallback("В конец", makeActionData(last_page)));
250
+ }
251
+
252
+ layoutButtons.push(lineStartLast);
253
+ }
254
+
255
+ return layoutButtons;
256
+ }
257
+
258
+ isRepeatedQuery(objParamsNeedle: Record<string, any>): boolean {
259
+ if (!this.callbackParams) return false;
260
+
261
+ const isEquals = Object.keys(objParamsNeedle)
262
+ .map((key) => [key, objParamsNeedle[key]])
263
+ .every((entry) => {
264
+ return this.callbackParams?.get(entry[0]) == entry[1];
265
+ });
266
+
267
+ return isEquals;
268
+ }
269
+
270
+ async setupKeyboard(): Promise<void> {
271
+ if (this.ctx.userSession.setupKeyboardDone) return;
272
+
273
+ await this.newMessage("Welcome!")
274
+ .keyboard({
275
+ keyboard: this.mainMenuKeyboardArray,
276
+ resize_keyboard: true,
277
+ one_time_keyboard: true,
278
+ })
279
+ .send();
280
+
281
+ this.ctx.userSession.setupKeyboardDone = true;
282
+ }
283
+
284
+ async getSetting(name: string): Promise<any> {
285
+ try {
286
+ const allSettings = await (global as any).settings;
287
+
288
+ return allSettings.data[name];
289
+ } catch (err) {
290
+ throw err;
291
+ }
292
+ }
293
+
294
+ cancelUserWaitingReply(): boolean {
295
+ if (this.callbackParams?.has("cancel_wait")) {
296
+ // Очищаем состояние ожидания ввода
297
+ if (this.ctx.userSession.awaitingInput) {
298
+ delete this.ctx.userSession.awaitingInput;
299
+ }
300
+
301
+ if (this.ctx.userSession.awaitingInputPromise) {
302
+ // Отклоняем Promise с сообщением об отмене
303
+ this.ctx.userSession.awaitingInputPromise.reject(new Error("Ввод отменен пользователем"));
304
+ delete this.ctx.userSession.awaitingInputPromise;
305
+ }
306
+
307
+ return true;
308
+ }
309
+
310
+ return false;
311
+ }
312
+
313
+ backInlineButtion(data: string): any {
314
+ return this.markup.button.callback(this.labelBack, data);
315
+ }
316
+
317
+ inlineButton(text: string, data: string): any {
318
+ return this.markup.button.callback(text, data);
319
+ }
320
+
321
+ setCallbackParams(params: URLSearchParams): this {
322
+ this.callbackParams = params;
323
+ return this;
324
+ }
325
+
326
+ existsAnswerInput(inputKey: string): boolean {
327
+ return this.ctx.userSession[inputKey] !== undefined;
328
+ }
329
+
330
+ getAnswerInput(inputKey: string): any {
331
+ return this.ctx.userSession[inputKey];
332
+ }
333
+
334
+ setAnswerInput(inputKey: string, value: any): void {
335
+ this.ctx.userSession[inputKey] = value;
336
+ }
337
+
338
+ makeQueryParams(...args: any[]): URLSearchParams {
339
+ const params = new URLSearchParams();
340
+
341
+ args.forEach((arg) => {
342
+ if (typeof arg === "string") {
343
+ new URLSearchParams(arg).forEach((value, key) => {
344
+ params.set(key, value);
345
+ });
346
+ }
347
+ if (arg !== null && typeof arg === "object") {
348
+ this.app.debugLog("arg", arg);
349
+ for (const [key, value] of Object.entries(arg)) {
350
+ params.set(key, String(value));
351
+ }
352
+ }
353
+ });
354
+
355
+ return params;
356
+ }
357
+
358
+ makeInlineKeyboard(buttons: any[][] = []): InlineKeyboard {
359
+ const keyboard = InlineKeyboard.init(this.ctx, this);
360
+ buttons.forEach((row) => {
361
+ keyboard.append(row);
362
+ });
363
+ return keyboard;
364
+ }
365
+
366
+ makeInlineButton(text: string, data: string): any {
367
+ return Markup.button.callback(text, data);
368
+ }
369
+
370
+ message(message: string): Message2byte {
371
+ if (this.route.runIsCallbackQuery) {
372
+ return this.updateMessage(message);
373
+ }
374
+ return Message2byte.init(this.ctx, this).message(message);
375
+ }
376
+
377
+ newMessage(message: string): Message2byte {
378
+ return Message2byte.init(this.ctx, this).message(message);
379
+ }
380
+
381
+ updateMessage(message: string): Message2byte {
382
+ return Message2byte.init(this.ctx, this).updateMessage(message);
383
+ }
384
+
385
+ createPoolNewMessage(message: string): Message2bytePool {
386
+ return Message2byte.init(this.ctx, this).createPoolMessage(message);
387
+ }
388
+
389
+ createUpdatePoolMessage(message: string): Message2bytePool {
390
+ return Message2byte.init(this.ctx, this).createUpdatePoolMessage(message);
391
+ }
392
+
393
+ createPoolMessage(message: string): Message2bytePool {
394
+ return this.route.runIsCallbackQuery
395
+ ? this.createUpdatePoolMessage(message)
396
+ : this.createPoolNewMessage(message);
397
+ }
398
+
399
+ getCtx(): Telegraf2byteContext {
400
+ return this.ctx;
401
+ }
402
+
403
+ getCurrentSection(): RunnedSection {
404
+ return this.app.getRunnedSection(this.ctx.user);
405
+ }
406
+
407
+ getPreviousSection(): RunnedSection | undefined {
408
+ return this.ctx.userSession.previousSection;
409
+ }
410
+ }