@2byte/tgbot-framework 1.0.6 → 1.0.8
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.
- package/README.md +300 -300
- package/bin/2byte-cli.ts +97 -97
- package/package.json +55 -55
- package/src/cli/CreateBotCommand.ts +181 -181
- package/src/cli/GenerateCommand.ts +195 -195
- package/src/cli/InitCommand.ts +107 -107
- package/src/cli/TgAccountManager.ts +50 -50
- package/src/console/migrate.ts +82 -82
- package/src/core/ApiService.ts +20 -20
- package/src/core/ApiServiceManager.ts +63 -63
- package/src/core/App.ts +1157 -1143
- package/src/core/BotArtisan.ts +79 -79
- package/src/core/BotMigration.ts +30 -30
- package/src/core/BotSeeder.ts +66 -66
- package/src/core/Model.ts +84 -84
- package/src/core/utils.ts +2 -2
- package/src/illumination/Artisan.ts +149 -149
- package/src/illumination/InlineKeyboard.ts +61 -61
- package/src/illumination/Message2Byte.ts +255 -255
- package/src/illumination/Message2ByteLiveProgressive.ts +278 -278
- package/src/illumination/Message2bytePool.ts +107 -107
- package/src/illumination/Migration.ts +186 -186
- package/src/illumination/RunSectionRoute.ts +85 -85
- package/src/illumination/Section.ts +410 -410
- package/src/illumination/SectionComponent.ts +64 -64
- package/src/illumination/Telegraf2byteContext.ts +32 -32
- package/src/index.ts +42 -42
- package/src/libs/TelegramAccountControl.ts +1140 -1140
- package/src/libs/TgSender.ts +53 -53
- package/src/models/Model.ts +67 -67
- package/src/models/Proxy.ts +217 -217
- package/src/models/TgAccount.ts +362 -362
- package/src/models/index.ts +2 -2
- package/src/types.ts +191 -191
- package/src/user/UserModel.ts +297 -297
- package/src/user/UserStore.ts +119 -119
- package/src/workflow/services/MassSendApiService.ts +83 -80
- package/templates/bot/.env.example +33 -33
- package/templates/bot/artisan.ts +8 -8
- package/templates/bot/bot.ts +82 -82
- package/templates/bot/database/dbConnector.ts +4 -4
- package/templates/bot/database/migrate.ts +9 -9
- package/templates/bot/database/migrations/001_create_users.sql +18 -18
- package/templates/bot/database/migrations/007_proxy.sql +27 -27
- package/templates/bot/database/migrations/008_tg_accounts.sql +32 -32
- package/templates/bot/database/seed.ts +14 -14
- package/templates/bot/docs/CLI_SERVICES.md +536 -536
- package/templates/bot/docs/INPUT_SYSTEM.md +211 -211
- package/templates/bot/docs/MASS_SEND_SERVICE.md +327 -0
- package/templates/bot/docs/SERVICE_EXAMPLES.md +384 -384
- package/templates/bot/docs/TASK_SYSTEM.md +156 -156
- package/templates/bot/models/Model.ts +7 -7
- package/templates/bot/models/index.ts +1 -1
- package/templates/bot/package.json +30 -30
- package/templates/bot/sectionList.ts +9 -9
- package/templates/bot/sections/ExampleInputSection.ts +85 -85
- package/templates/bot/sections/ExampleLiveTaskerSection.ts +60 -60
- package/templates/bot/sections/HomeSection.ts +63 -63
- 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
|
+
}
|