@2byte/tgbot-framework 1.0.6 → 1.0.7

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 (58) hide show
  1. package/README.md +300 -300
  2. package/bin/2byte-cli.ts +97 -97
  3. package/package.json +54 -54
  4. package/src/cli/CreateBotCommand.ts +181 -181
  5. package/src/cli/GenerateCommand.ts +195 -195
  6. package/src/cli/InitCommand.ts +107 -107
  7. package/src/cli/TgAccountManager.ts +50 -50
  8. package/src/console/migrate.ts +82 -82
  9. package/src/core/ApiService.ts +20 -20
  10. package/src/core/ApiServiceManager.ts +63 -63
  11. package/src/core/App.ts +1157 -1143
  12. package/src/core/BotArtisan.ts +79 -79
  13. package/src/core/BotMigration.ts +30 -30
  14. package/src/core/BotSeeder.ts +66 -66
  15. package/src/core/Model.ts +84 -84
  16. package/src/core/utils.ts +2 -2
  17. package/src/illumination/Artisan.ts +149 -149
  18. package/src/illumination/InlineKeyboard.ts +61 -61
  19. package/src/illumination/Message2Byte.ts +255 -255
  20. package/src/illumination/Message2ByteLiveProgressive.ts +278 -278
  21. package/src/illumination/Message2bytePool.ts +107 -107
  22. package/src/illumination/Migration.ts +186 -186
  23. package/src/illumination/RunSectionRoute.ts +85 -85
  24. package/src/illumination/Section.ts +410 -410
  25. package/src/illumination/SectionComponent.ts +64 -64
  26. package/src/illumination/Telegraf2byteContext.ts +32 -32
  27. package/src/index.ts +42 -42
  28. package/src/libs/TelegramAccountControl.ts +1140 -1140
  29. package/src/libs/TgSender.ts +53 -53
  30. package/src/models/Model.ts +67 -67
  31. package/src/models/Proxy.ts +217 -217
  32. package/src/models/TgAccount.ts +362 -362
  33. package/src/models/index.ts +2 -2
  34. package/src/types.ts +191 -191
  35. package/src/user/UserModel.ts +297 -297
  36. package/src/user/UserStore.ts +119 -119
  37. package/src/workflow/services/MassSendApiService.ts +80 -80
  38. package/templates/bot/.env.example +33 -33
  39. package/templates/bot/artisan.ts +8 -8
  40. package/templates/bot/bot.ts +82 -82
  41. package/templates/bot/database/dbConnector.ts +4 -4
  42. package/templates/bot/database/migrate.ts +9 -9
  43. package/templates/bot/database/migrations/001_create_users.sql +18 -18
  44. package/templates/bot/database/migrations/007_proxy.sql +27 -27
  45. package/templates/bot/database/migrations/008_tg_accounts.sql +32 -32
  46. package/templates/bot/database/seed.ts +14 -14
  47. package/templates/bot/docs/CLI_SERVICES.md +536 -536
  48. package/templates/bot/docs/INPUT_SYSTEM.md +211 -211
  49. package/templates/bot/docs/SERVICE_EXAMPLES.md +384 -384
  50. package/templates/bot/docs/TASK_SYSTEM.md +156 -156
  51. package/templates/bot/models/Model.ts +7 -7
  52. package/templates/bot/models/index.ts +1 -1
  53. package/templates/bot/package.json +30 -30
  54. package/templates/bot/sectionList.ts +9 -9
  55. package/templates/bot/sections/ExampleInputSection.ts +85 -85
  56. package/templates/bot/sections/ExampleLiveTaskerSection.ts +60 -60
  57. package/templates/bot/sections/HomeSection.ts +63 -63
  58. package/templates/bot/workflow/services/ExampleService.ts +23 -23
@@ -1,410 +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 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
- }
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
+ }