@2byte/tgbot-framework 1.0.0
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 +301 -0
- package/bin/2byte-cli.ts +85 -0
- package/package.json +50 -0
- package/src/cli/CreateBotCommand.ts +182 -0
- package/src/cli/GenerateCommand.ts +112 -0
- package/src/cli/InitCommand.ts +108 -0
- package/src/console/migrate.ts +83 -0
- package/src/core/App.ts +1016 -0
- package/src/core/BotArtisan.ts +80 -0
- package/src/core/BotMigration.ts +31 -0
- package/src/core/BotSeeder.ts +67 -0
- package/src/core/utils.ts +3 -0
- package/src/illumination/Artisan.ts +149 -0
- package/src/illumination/InlineKeyboard.ts +44 -0
- package/src/illumination/Message2Byte.ts +254 -0
- package/src/illumination/Message2ByteLiveProgressive.ts +278 -0
- package/src/illumination/Message2bytePool.ts +108 -0
- package/src/illumination/Migration.ts +186 -0
- package/src/illumination/RunSectionRoute.ts +85 -0
- package/src/illumination/Section.ts +430 -0
- package/src/illumination/SectionComponent.ts +64 -0
- package/src/illumination/Telegraf2byteContext.ts +33 -0
- package/src/index.ts +33 -0
- package/src/libs/TelegramAccountControl.ts +523 -0
- package/src/types.ts +172 -0
- package/src/user/UserModel.ts +132 -0
- package/src/user/UserStore.ts +119 -0
- package/templates/bot/.env.example +18 -0
- package/templates/bot/artisan.ts +9 -0
- package/templates/bot/bot.ts +74 -0
- package/templates/bot/database/dbConnector.ts +5 -0
- package/templates/bot/database/migrate.ts +10 -0
- package/templates/bot/database/migrations/001_create_users.sql +17 -0
- package/templates/bot/database/seed.ts +15 -0
- package/templates/bot/package.json +31 -0
- package/templates/bot/sectionList.ts +7 -0
- package/templates/bot/sections/HomeSection.ts +63 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { Artisan } from '../illumination/Artisan';
|
|
4
|
+
|
|
5
|
+
export interface BotArtisanOptions {
|
|
6
|
+
botName: string;
|
|
7
|
+
sectionsPath?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class BotArtisan {
|
|
11
|
+
private artisan: Artisan;
|
|
12
|
+
private options: BotArtisanOptions;
|
|
13
|
+
|
|
14
|
+
constructor(botPath: string, options: BotArtisanOptions) {
|
|
15
|
+
this.options = options;
|
|
16
|
+
this.artisan = new Artisan(botPath);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async run(): Promise<void> {
|
|
20
|
+
const command = process.argv[2];
|
|
21
|
+
const args = process.argv.slice(3);
|
|
22
|
+
|
|
23
|
+
if (!command) {
|
|
24
|
+
this.showHelp();
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
switch (command) {
|
|
30
|
+
case 'make:section':
|
|
31
|
+
if (args.length === 0) {
|
|
32
|
+
console.error(chalk.red('❌ Error: Section name is required'));
|
|
33
|
+
console.log('Usage: artisan make:section SectionName');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
await this.artisan.createSection(args[0]);
|
|
37
|
+
break;
|
|
38
|
+
|
|
39
|
+
case 'add:method':
|
|
40
|
+
if (args.length < 2) {
|
|
41
|
+
console.error(chalk.red('❌ Error: Section name and method name are required'));
|
|
42
|
+
console.log('Usage: artisan add:method SectionName methodName');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
await this.artisan.addMethod(args[0], args[1]);
|
|
46
|
+
break;
|
|
47
|
+
|
|
48
|
+
case 'list:sections':
|
|
49
|
+
await this.artisan.listSections();
|
|
50
|
+
break;
|
|
51
|
+
|
|
52
|
+
default:
|
|
53
|
+
console.error(chalk.red(`❌ Unknown command: ${command}`));
|
|
54
|
+
this.showHelp();
|
|
55
|
+
}
|
|
56
|
+
} catch (error) {
|
|
57
|
+
if (error instanceof Error) {
|
|
58
|
+
console.error(chalk.red(`❌ Error: ${error.message}`));
|
|
59
|
+
} else {
|
|
60
|
+
console.error(chalk.red('❌ An unknown error occurred'));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private showHelp(): void {
|
|
66
|
+
console.log(`
|
|
67
|
+
🔧 ${this.options.botName} Artisan CLI
|
|
68
|
+
|
|
69
|
+
Available commands:
|
|
70
|
+
make:section <name> Create a new section
|
|
71
|
+
add:method <section> <name> Add a new method to existing section
|
|
72
|
+
list:sections List all sections
|
|
73
|
+
|
|
74
|
+
Examples:
|
|
75
|
+
artisan make:section Auth Create new AuthSection
|
|
76
|
+
artisan add:method Auth login Add login method to AuthSection
|
|
77
|
+
artisan list:sections Show all available sections
|
|
78
|
+
`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { setupMigrations } from '../console/migrate';
|
|
4
|
+
|
|
5
|
+
export interface BotMigrationOptions {
|
|
6
|
+
botPath: string;
|
|
7
|
+
migrationsPath: string;
|
|
8
|
+
databasePath: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class BotMigration {
|
|
12
|
+
private options: BotMigrationOptions;
|
|
13
|
+
|
|
14
|
+
constructor(options: BotMigrationOptions) {
|
|
15
|
+
this.options = options;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async run(): Promise<void> {
|
|
19
|
+
console.log(chalk.blue(`🗃️ Running migrations for bot...`));
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
await setupMigrations({
|
|
23
|
+
pathMigrations: this.options.migrationsPath,
|
|
24
|
+
pathDatabase: this.options.databasePath
|
|
25
|
+
});
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error(chalk.red('❌ Migration failed:'), error);
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { Database } from 'bun:sqlite';
|
|
4
|
+
|
|
5
|
+
export interface BotSeederOptions {
|
|
6
|
+
botPath: string;
|
|
7
|
+
databasePath: string;
|
|
8
|
+
seeders: Array<(db: Database) => Promise<void> | void>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class BotSeeder {
|
|
12
|
+
private options: BotSeederOptions;
|
|
13
|
+
|
|
14
|
+
constructor(options: BotSeederOptions) {
|
|
15
|
+
this.options = options;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async run(): Promise<void> {
|
|
19
|
+
const args = process.argv.slice(2);
|
|
20
|
+
const isCleanOnly = args.includes('--clean-only');
|
|
21
|
+
const isClear = args.includes('--clear');
|
|
22
|
+
|
|
23
|
+
console.log(chalk.blue(`🌱 Running seeders for bot...`));
|
|
24
|
+
|
|
25
|
+
const db = new Database(this.options.databasePath);
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
if (isCleanOnly) {
|
|
29
|
+
console.log(chalk.yellow('🧹 Cleaning database...'));
|
|
30
|
+
await this.cleanDatabase(db);
|
|
31
|
+
console.log(chalk.green('✅ Database cleaned successfully!'));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (isClear) {
|
|
36
|
+
console.log(chalk.yellow('🧹 Clearing and reseeding database...'));
|
|
37
|
+
await this.cleanDatabase(db);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
console.log(chalk.blue('🌱 Seeding database...'));
|
|
41
|
+
for (const seeder of this.options.seeders) {
|
|
42
|
+
await seeder(db);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
console.log(chalk.green('✅ Database seeded successfully!'));
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error(chalk.red('❌ Seeding failed:'), error);
|
|
48
|
+
throw error;
|
|
49
|
+
} finally {
|
|
50
|
+
db.close();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private async cleanDatabase(db: Database): Promise<void> {
|
|
55
|
+
// Get all tables
|
|
56
|
+
const tables = db.query(`
|
|
57
|
+
SELECT name FROM sqlite_master
|
|
58
|
+
WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name != 'migrations'
|
|
59
|
+
`).all() as Array<{ name: string }>;
|
|
60
|
+
|
|
61
|
+
// Clear all tables except migrations
|
|
62
|
+
for (const table of tables) {
|
|
63
|
+
db.query(`DELETE FROM ${table.name}`).run();
|
|
64
|
+
console.log(chalk.yellow(`🧹 Cleared table: ${table.name}`));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
export class Artisan {
|
|
5
|
+
private basePath: string;
|
|
6
|
+
|
|
7
|
+
constructor(basePath: string) {
|
|
8
|
+
this.basePath = basePath;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Создает новую секцию
|
|
13
|
+
* @param name Имя секции (например: Home, Auth, Settings)
|
|
14
|
+
*/
|
|
15
|
+
async createSection(name: string): Promise<void> {
|
|
16
|
+
const sectionName = this.formatSectionName(name);
|
|
17
|
+
const sectionsDir = path.join(this.basePath, 'sections');
|
|
18
|
+
|
|
19
|
+
// Создаем директорию sections если её нет
|
|
20
|
+
if (!fs.existsSync(sectionsDir)) {
|
|
21
|
+
fs.mkdirSync(sectionsDir, { recursive: true });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const sectionPath = path.join(sectionsDir, `${sectionName}Section.ts`);
|
|
25
|
+
|
|
26
|
+
// Проверяем, не существует ли уже такая секция
|
|
27
|
+
if (fs.existsSync(sectionPath)) {
|
|
28
|
+
throw new Error(`Section ${sectionName} already exists at ${sectionPath}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const template = this.getSectionTemplate(sectionName);
|
|
32
|
+
|
|
33
|
+
// Создаем файл секции
|
|
34
|
+
fs.writeFileSync(sectionPath, template);
|
|
35
|
+
console.log(`✅ Created section ${sectionName} at ${sectionPath}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Форматирует имя секции (первая буква заглавная, остальные строчные)
|
|
40
|
+
*/
|
|
41
|
+
private formatSectionName(name: string): string {
|
|
42
|
+
return name.charAt(0).toUpperCase() + name.slice(1).toLowerCase();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Возвращает шаблон для новой секции
|
|
47
|
+
*/
|
|
48
|
+
private getSectionTemplate(name: string): string {
|
|
49
|
+
return `import { Section } from "../../src/illumination/Section";
|
|
50
|
+
import { SectionOptions } from "../../src/types";
|
|
51
|
+
import { InlineKeyboard } from "../../src/illumination/InlineKeyboard";
|
|
52
|
+
|
|
53
|
+
export default class ${name}Section extends Section {
|
|
54
|
+
static command = "${name.toLowerCase()}";
|
|
55
|
+
static description = "${name} section";
|
|
56
|
+
static actionRoutes = {
|
|
57
|
+
"${name.toLowerCase()}.index": "index",
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
public sectionId = "${name.toLowerCase()}";
|
|
61
|
+
private mainInlineKeyboard: InlineKeyboard;
|
|
62
|
+
|
|
63
|
+
constructor(options: SectionOptions) {
|
|
64
|
+
super(options);
|
|
65
|
+
|
|
66
|
+
this.mainInlineKeyboard = this.makeInlineKeyboard([
|
|
67
|
+
[this.makeInlineButton("🏠 На главную", "home.index")],
|
|
68
|
+
]);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
public async up(): Promise<void> {}
|
|
72
|
+
public async down(): Promise<void> {}
|
|
73
|
+
public async setup(): Promise<void> {}
|
|
74
|
+
public async unsetup(): Promise<void> {}
|
|
75
|
+
|
|
76
|
+
async index() {
|
|
77
|
+
const message = \`
|
|
78
|
+
👋 Welcome to ${name} Section
|
|
79
|
+
\`;
|
|
80
|
+
|
|
81
|
+
await this.message(message)
|
|
82
|
+
.inlineKeyboard(this.mainInlineKeyboard)
|
|
83
|
+
.send();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Добавляет новый метод в существующую секцию
|
|
91
|
+
*/
|
|
92
|
+
async addMethod(sectionName: string, methodName: string): Promise<void> {
|
|
93
|
+
const formattedSectionName = this.formatSectionName(sectionName);
|
|
94
|
+
const sectionPath = path.join(this.basePath, 'sections', `${formattedSectionName}Section.ts`);
|
|
95
|
+
|
|
96
|
+
if (!fs.existsSync(sectionPath)) {
|
|
97
|
+
throw new Error(`Section ${formattedSectionName} does not exist at ${sectionPath}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let content = fs.readFileSync(sectionPath, 'utf-8');
|
|
101
|
+
|
|
102
|
+
// Добавляем новый route в actionRoutes
|
|
103
|
+
const routeEntry = `"${sectionName.toLowerCase()}.${methodName}": "${methodName}",`;
|
|
104
|
+
content = content.replace(
|
|
105
|
+
/static actionRoutes = {([^}]*)}/,
|
|
106
|
+
(match, routes) => `static actionRoutes = {${routes} ${routeEntry}\n }`
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
// Добавляем новый метод
|
|
110
|
+
const methodTemplate = `
|
|
111
|
+
async ${methodName}() {
|
|
112
|
+
const message = \`
|
|
113
|
+
// Добавьте ваше сообщение здесь
|
|
114
|
+
\`;
|
|
115
|
+
|
|
116
|
+
await this.message(message)
|
|
117
|
+
.inlineKeyboard(this.mainInlineKeyboard)
|
|
118
|
+
.send();
|
|
119
|
+
}
|
|
120
|
+
`;
|
|
121
|
+
|
|
122
|
+
// Вставляем метод перед последней закрывающей скобкой
|
|
123
|
+
content = content.replace(/}$/, `${methodTemplate}}`);
|
|
124
|
+
|
|
125
|
+
fs.writeFileSync(sectionPath, content);
|
|
126
|
+
console.log(`✅ Added method ${methodName} to section ${formattedSectionName}`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Выводит список всех секций
|
|
131
|
+
*/
|
|
132
|
+
async listSections(): Promise<void> {
|
|
133
|
+
const sectionsDir = path.join(this.basePath, 'sections');
|
|
134
|
+
|
|
135
|
+
if (!fs.existsSync(sectionsDir)) {
|
|
136
|
+
console.log('No sections found');
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const sections = fs.readdirSync(sectionsDir)
|
|
141
|
+
.filter(file => file.endsWith('Section.ts'))
|
|
142
|
+
.map(file => file.replace('Section.ts', ''));
|
|
143
|
+
|
|
144
|
+
console.log('\n📁 Available sections:');
|
|
145
|
+
sections.forEach(section => {
|
|
146
|
+
console.log(` - ${section}`);
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Telegraf2byteContext } from "./Telegraf2byteContext";
|
|
2
|
+
|
|
3
|
+
export class InlineKeyboard {
|
|
4
|
+
|
|
5
|
+
private keyboard: any[][] = [];
|
|
6
|
+
|
|
7
|
+
static init(ctx: Telegraf2byteContext) {
|
|
8
|
+
return new InlineKeyboard(ctx);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
constructor(private ctx: Telegraf2byteContext) {
|
|
12
|
+
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
append(row: any[] | any[][]): InlineKeyboard {
|
|
16
|
+
if (!Array.isArray(row)) {
|
|
17
|
+
this.keyboard.push([row]);
|
|
18
|
+
} else if (Array.isArray(row[0])) {
|
|
19
|
+
this.keyboard.push(...row);
|
|
20
|
+
} else {
|
|
21
|
+
this.keyboard.push(row);
|
|
22
|
+
}
|
|
23
|
+
return this;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
prepend(row: any[]): InlineKeyboard {
|
|
27
|
+
if (!Array.isArray(row)) {
|
|
28
|
+
this.keyboard.unshift([row]);
|
|
29
|
+
} else if (Array.isArray(row[0])) {
|
|
30
|
+
this.keyboard.unshift(...row);
|
|
31
|
+
} else {
|
|
32
|
+
this.keyboard.unshift(row);
|
|
33
|
+
}
|
|
34
|
+
return this;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
valueOf(): any[][] {
|
|
38
|
+
return this.keyboard;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
[Symbol.toPrimitive]() {
|
|
42
|
+
return this.valueOf();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { Telegraf2byteContext } from "./Telegraf2byteContext";
|
|
2
|
+
import { Input, Markup } from "telegraf";
|
|
3
|
+
import type { ReplyKeyboardMarkup } from 'telegraf/core/types/telegram';
|
|
4
|
+
import { InlineKeyboard } from "./InlineKeyboard";
|
|
5
|
+
import { RequestInputOptions } from "../types";
|
|
6
|
+
import { Message } from "telegraf/types";
|
|
7
|
+
import Message2bytePool from "./Message2bytePool";
|
|
8
|
+
import { Section } from "./Section";
|
|
9
|
+
|
|
10
|
+
export default class Message2byte {
|
|
11
|
+
public messageValue: string = "";
|
|
12
|
+
public messageExtra: any = {};
|
|
13
|
+
public isUpdate: boolean = false;
|
|
14
|
+
private ctx: Telegraf2byteContext;
|
|
15
|
+
private imagePath: string | null = null;
|
|
16
|
+
private imageCaption: string | null = null;
|
|
17
|
+
private messageId: number | null = null;
|
|
18
|
+
private doAnswerCbQuery: boolean = true;
|
|
19
|
+
private section: Section;
|
|
20
|
+
|
|
21
|
+
constructor(ctx: Telegraf2byteContext, section: Section) {
|
|
22
|
+
this.ctx = ctx;
|
|
23
|
+
this.section = section;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static init(ctx: Telegraf2byteContext, section: Section) {
|
|
27
|
+
return new Message2byte(ctx, section);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
setNotAnswerCbQuery(): this {
|
|
31
|
+
this.doAnswerCbQuery = false;
|
|
32
|
+
return this;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
message(message: string): this {
|
|
36
|
+
this.messageValue = message;
|
|
37
|
+
return this;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
createPoolMessage(message: string): Message2bytePool {
|
|
41
|
+
this.doAnswerCbQuery = false;
|
|
42
|
+
this.messageValue = message;
|
|
43
|
+
return Message2bytePool.init(this, this.ctx, this.section);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
createUpdatePoolMessage(message: string): Message2bytePool {
|
|
47
|
+
this.isUpdate = true;
|
|
48
|
+
this.messageValue = message;
|
|
49
|
+
return Message2bytePool.init(this, this.ctx, this.section);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
updateMessage(message: string): this {
|
|
53
|
+
this.messageValue = message;
|
|
54
|
+
this.isUpdate = true;
|
|
55
|
+
this.messageExtra.message_id &&= this.messageId;
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
markdown(): this {
|
|
60
|
+
this.messageExtra.parse_mode = "markdown";
|
|
61
|
+
return this;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
html(): this {
|
|
65
|
+
this.messageExtra.parse_mode = "html";
|
|
66
|
+
return this;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
extra(extra: Object): this {
|
|
70
|
+
this.messageExtra = extra;
|
|
71
|
+
return this;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
keyboard(keyboard: ReplyKeyboardMarkup): this {
|
|
75
|
+
this.messageExtra.reply_markup = {
|
|
76
|
+
keyboard: keyboard.keyboard,
|
|
77
|
+
resize_keyboard: keyboard.resize_keyboard,
|
|
78
|
+
one_time_keyboard: keyboard.one_time_keyboard,
|
|
79
|
+
};
|
|
80
|
+
return this;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
inlineKeyboard(keyboard: [][] | InlineKeyboard) {
|
|
84
|
+
let keyboardArray: any[][];
|
|
85
|
+
|
|
86
|
+
if (keyboard instanceof InlineKeyboard) {
|
|
87
|
+
keyboardArray = keyboard.valueOf();
|
|
88
|
+
} else {
|
|
89
|
+
keyboardArray = keyboard;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
Object.assign(this.messageExtra, {
|
|
93
|
+
...Markup.inlineKeyboard(keyboardArray),
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return this;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
requestInput(inputKey: string, options: RequestInputOptions = {}): this {
|
|
100
|
+
// Устанавливаем значения по умолчанию
|
|
101
|
+
const allowCancel = options.allowCancel !== false; // по умолчанию true
|
|
102
|
+
const cancelButtonText = options.cancelButtonText || "Отмена";
|
|
103
|
+
const cancelAction = options.cancelAction || "home.index[cancel_wait=1]";
|
|
104
|
+
|
|
105
|
+
// Если разрешена отмена, добавляем кнопку отмены к клавиатуре
|
|
106
|
+
if (allowCancel && this.messageExtra && "reply_markup" in this.messageExtra) {
|
|
107
|
+
const replyMarkup = (this.messageExtra as any).reply_markup;
|
|
108
|
+
if (replyMarkup && replyMarkup.inline_keyboard) {
|
|
109
|
+
// Добавляем кнопку отмены в начало клавиатуры
|
|
110
|
+
replyMarkup.inline_keyboard.unshift([
|
|
111
|
+
{
|
|
112
|
+
text: `❌ ${cancelButtonText}`,
|
|
113
|
+
callback_data: cancelAction,
|
|
114
|
+
},
|
|
115
|
+
]);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Сохраняем информацию о запрашиваемом вводе в сессии пользователя
|
|
120
|
+
this.ctx.userSession.awaitingInput = {
|
|
121
|
+
key: inputKey,
|
|
122
|
+
validator: options.validator,
|
|
123
|
+
errorMessage: options.errorMessage || "Неверный формат ввода",
|
|
124
|
+
allowCancel,
|
|
125
|
+
cancelButtonText: `❌ ${cancelButtonText}`,
|
|
126
|
+
cancelAction,
|
|
127
|
+
fileValidation: options.fileValidation,
|
|
128
|
+
runSection: options.runSection,
|
|
129
|
+
retryCount: 0,
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
return this;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async requestInputWithAwait(
|
|
136
|
+
inputKey: string,
|
|
137
|
+
options: RequestInputOptions = {}
|
|
138
|
+
): Promise<string | any> {
|
|
139
|
+
// Устанавливаем значения по умолчанию
|
|
140
|
+
const allowCancel = options.allowCancel !== false; // по умолчанию true
|
|
141
|
+
const cancelButtonText = options.cancelButtonText || "Отмена";
|
|
142
|
+
const cancelAction = options.cancelAction || "home.index[cancel_wait=1]";
|
|
143
|
+
|
|
144
|
+
// Если разрешена отмена, добавляем кнопку отмены к клавиатуре
|
|
145
|
+
if (allowCancel && this.messageExtra && "reply_markup" in this.messageExtra) {
|
|
146
|
+
const replyMarkup = (this.messageExtra as any).reply_markup;
|
|
147
|
+
if (replyMarkup && replyMarkup.inline_keyboard) {
|
|
148
|
+
// Добавляем кнопку отмены в начало клавиатуры
|
|
149
|
+
replyMarkup.inline_keyboard.unshift([
|
|
150
|
+
{
|
|
151
|
+
text: `❌ ${cancelButtonText}`,
|
|
152
|
+
callback_data: cancelAction,
|
|
153
|
+
},
|
|
154
|
+
]);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Отправляем сообщение
|
|
159
|
+
await this.send();
|
|
160
|
+
|
|
161
|
+
// Возвращаем Promise, который будет разрешен когда пользователь введет данные
|
|
162
|
+
return new Promise((resolve, reject) => {
|
|
163
|
+
this.ctx.userSession.awaitingInputPromise = {
|
|
164
|
+
key: inputKey,
|
|
165
|
+
validator: options.validator,
|
|
166
|
+
errorMessage: options.errorMessage || "Неверный формат ввода",
|
|
167
|
+
allowCancel,
|
|
168
|
+
cancelButtonText: `❌ ${cancelButtonText}`,
|
|
169
|
+
cancelAction,
|
|
170
|
+
fileValidation: options.fileValidation,
|
|
171
|
+
retryCount: 0,
|
|
172
|
+
resolve,
|
|
173
|
+
reject,
|
|
174
|
+
};
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
image(pathImage: string): this {
|
|
179
|
+
this.imagePath = pathImage;
|
|
180
|
+
this.imageCaption = this.messageValue;
|
|
181
|
+
this.messageExtra.caption = this.imageCaption;
|
|
182
|
+
return this;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
editMessageCaption(message: string, extra: any = {}) {
|
|
186
|
+
return this.ctx.editMessageCaption(message, extra);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
editMessageText(message: string, extra: any = {}) {
|
|
190
|
+
return this.ctx.editMessageText(message, extra);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async send() {
|
|
194
|
+
if (this.isUpdate) {
|
|
195
|
+
if (this.section.route.runIsCallbackQuery && this.doAnswerCbQuery) {
|
|
196
|
+
await this.ctx.answerCbQuery();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const message = this.ctx.callbackQuery?.message as Message;
|
|
200
|
+
|
|
201
|
+
if (message) {
|
|
202
|
+
if ('media_group_id' in message || 'caption' in message) {
|
|
203
|
+
const editMessageCaption = this.editMessageCaption(this.messageValue, this.messageExtra);
|
|
204
|
+
|
|
205
|
+
if (editMessageCaption && 'message_id' in editMessageCaption) {
|
|
206
|
+
this.messageId = editMessageCaption.message_id as number;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return editMessageCaption;
|
|
210
|
+
} else {
|
|
211
|
+
|
|
212
|
+
const editedText = this.editMessageText(this.messageValue, this.messageExtra);
|
|
213
|
+
|
|
214
|
+
if (editedText && 'message_id' in editedText) {
|
|
215
|
+
this.messageId = editedText.message_id as number;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return editedText;
|
|
219
|
+
}
|
|
220
|
+
} else {
|
|
221
|
+
this.messageExtra.message_id = this.messageId;
|
|
222
|
+
|
|
223
|
+
const messageEntity = await this.editMessageText(this.messageValue, this.messageExtra);
|
|
224
|
+
|
|
225
|
+
if (typeof messageEntity === "object" && 'message_id' in messageEntity) {
|
|
226
|
+
this.messageId = messageEntity.message_id as number;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return messageEntity;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (this.imagePath) {
|
|
234
|
+
return this.ctx.replyWithPhoto(Input.fromLocalFile(this.imagePath), this.messageExtra);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const replyEntity = this.ctx.reply(this.messageValue, this.messageExtra);
|
|
238
|
+
|
|
239
|
+
this.messageId = (await replyEntity).message_id;
|
|
240
|
+
|
|
241
|
+
return replyEntity;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
sendReturnThis(): this {
|
|
245
|
+
this.send();
|
|
246
|
+
return this;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
setMessageId(messageId: number): this {
|
|
250
|
+
this.messageId = messageId;
|
|
251
|
+
this.messageExtra.message_id = messageId;
|
|
252
|
+
return this;
|
|
253
|
+
}
|
|
254
|
+
}
|