@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,523 @@
|
|
|
1
|
+
import { TelegramClient } from "telegram";
|
|
2
|
+
import { StringSession } from "telegram/sessions";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import { TelegramClientParams } from "telegram/client/telegramBaseClient";
|
|
5
|
+
import { ProxyInterface } from "telegram/network/connection/TCPMTProxy";
|
|
6
|
+
|
|
7
|
+
interface TelegramRegistrarInit {
|
|
8
|
+
appId: string;
|
|
9
|
+
appHash: string;
|
|
10
|
+
credetialsManager: TelegramManagerCredentials;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type TelegramClientProxy = {
|
|
14
|
+
ip: string;
|
|
15
|
+
port: number;
|
|
16
|
+
username?: string;
|
|
17
|
+
password?: string;
|
|
18
|
+
secret?: string,
|
|
19
|
+
socksType?: 5 | 4;
|
|
20
|
+
MTProxy?: boolean;
|
|
21
|
+
} & ProxyInterface;
|
|
22
|
+
|
|
23
|
+
interface TelegramInitClient {
|
|
24
|
+
phone: string;
|
|
25
|
+
proxy?: any;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface TelegramCredentials {
|
|
29
|
+
phone: string;
|
|
30
|
+
proxy?: TelegramClientProxy;
|
|
31
|
+
password?: string;
|
|
32
|
+
session?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface TelegramCredentialsManagerInit {
|
|
36
|
+
pathFileStorage: string;
|
|
37
|
+
pathFileProxyList: string;
|
|
38
|
+
pathFileCounterOffset: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class TelegramManagerCredentials {
|
|
42
|
+
private requiredProxyForCredentials: boolean = false;
|
|
43
|
+
private credentials: TelegramCredentials[] = [];
|
|
44
|
+
private proxyList: string[] = [];
|
|
45
|
+
|
|
46
|
+
static init(options: TelegramCredentialsManagerInit) {
|
|
47
|
+
return new TelegramManagerCredentials(options);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
constructor(private options: TelegramCredentialsManagerInit) {
|
|
51
|
+
this.ensureStorageDirectory();
|
|
52
|
+
this.loadCredentials();
|
|
53
|
+
this.loadProxyList();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Ensure storage directory exists
|
|
58
|
+
*/
|
|
59
|
+
private ensureStorageDirectory(): void {
|
|
60
|
+
const storageDir = this.options.pathFileStorage.split("/").slice(0, -1).join("/");
|
|
61
|
+
if (!fs.existsSync(storageDir)) {
|
|
62
|
+
fs.mkdirSync(storageDir, { recursive: true });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Load credentials from JSON file
|
|
68
|
+
*/
|
|
69
|
+
private loadCredentials(): void {
|
|
70
|
+
try {
|
|
71
|
+
if (fs.existsSync(this.options.pathFileStorage)) {
|
|
72
|
+
const data = fs.readFileSync(this.options.pathFileStorage, "utf8");
|
|
73
|
+
this.credentials = JSON.parse(data) || [];
|
|
74
|
+
} else {
|
|
75
|
+
this.credentials = [];
|
|
76
|
+
this.saveCredentials();
|
|
77
|
+
}
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.error("Error loading credentials:", error);
|
|
80
|
+
this.credentials = [];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Загрузить список прокси из файла
|
|
86
|
+
*/
|
|
87
|
+
private loadProxyList(): void {
|
|
88
|
+
try {
|
|
89
|
+
if (fs.existsSync(this.options.pathFileProxyList)) {
|
|
90
|
+
const data = fs.readFileSync(this.options.pathFileProxyList, "utf8");
|
|
91
|
+
this.proxyList = data.split('\n').filter(line => line.trim() !== '');
|
|
92
|
+
} else {
|
|
93
|
+
this.proxyList = [];
|
|
94
|
+
}
|
|
95
|
+
} catch (error) {
|
|
96
|
+
console.error("Ошибка загрузки списка прокси:", error);
|
|
97
|
+
this.proxyList = [];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Получить текущий offset из файла счетчика
|
|
103
|
+
*/
|
|
104
|
+
private getCurrentOffset(): number {
|
|
105
|
+
try {
|
|
106
|
+
if (fs.existsSync(this.options.pathFileCounterOffset)) {
|
|
107
|
+
const data = fs.readFileSync(this.options.pathFileCounterOffset, "utf8");
|
|
108
|
+
return parseInt(data.trim()) || 0;
|
|
109
|
+
}
|
|
110
|
+
return 0;
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.error("Ошибка чтения offset:", error);
|
|
113
|
+
return 0;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Сохранить новый offset в файл счетчика
|
|
119
|
+
*/
|
|
120
|
+
private saveOffset(offset: number): void {
|
|
121
|
+
try {
|
|
122
|
+
fs.writeFileSync(this.options.pathFileCounterOffset, offset.toString(), "utf8");
|
|
123
|
+
} catch (error) {
|
|
124
|
+
console.error("Ошибка сохранения offset:", error);
|
|
125
|
+
throw new Error("Не удалось сохранить offset");
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Парсинг строки прокси в формат TelegramClientProxy
|
|
131
|
+
* Поддерживаемые форматы:
|
|
132
|
+
* - socks4://username:password@host:port
|
|
133
|
+
* - socks5://username:password@host:port
|
|
134
|
+
* - http://username:password@host:port (будет использоваться как SOCKS5)
|
|
135
|
+
*/
|
|
136
|
+
private parseProxyString(proxyString: string): TelegramClientProxy {
|
|
137
|
+
// Проверяем SOCKS4 формат
|
|
138
|
+
let match = proxyString.match(/^socks4:\/\/([^:]+):([^@]+)@([^:]+):(\d+)$/);
|
|
139
|
+
if (match) {
|
|
140
|
+
const [, username, password, ip, port] = match;
|
|
141
|
+
return {
|
|
142
|
+
ip,
|
|
143
|
+
port: parseInt(port),
|
|
144
|
+
username,
|
|
145
|
+
password,
|
|
146
|
+
socksType: 4
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Проверяем SOCKS5 формат
|
|
151
|
+
match = proxyString.match(/^socks5:\/\/([^:]+):([^@]+)@([^:]+):(\d+)$/);
|
|
152
|
+
if (match) {
|
|
153
|
+
const [, username, password, ip, port] = match;
|
|
154
|
+
return {
|
|
155
|
+
ip,
|
|
156
|
+
port: parseInt(port),
|
|
157
|
+
username,
|
|
158
|
+
password,
|
|
159
|
+
socksType: 5
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Проверяем HTTP формат (используем как SOCKS5)
|
|
164
|
+
match = proxyString.match(/^http:\/\/([^:]+):([^@]+)@([^:]+):(\d+)$/);
|
|
165
|
+
if (match) {
|
|
166
|
+
const [, username, password, ip, port] = match;
|
|
167
|
+
return {
|
|
168
|
+
ip,
|
|
169
|
+
port: parseInt(port),
|
|
170
|
+
username,
|
|
171
|
+
password,
|
|
172
|
+
socksType: 5 // HTTP прокси используем как SOCKS5
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
throw new Error(`Неверный формат прокси: ${proxyString}. Поддерживаемые форматы: socks4://, socks5://, http://`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Получить следующий прокси из списка
|
|
181
|
+
*/
|
|
182
|
+
private getNextProxy(): TelegramClientProxy {
|
|
183
|
+
if (this.proxyList.length === 0) {
|
|
184
|
+
throw new Error("Список прокси пуст");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const currentOffset = this.getCurrentOffset();
|
|
188
|
+
|
|
189
|
+
if (currentOffset >= this.proxyList.length) {
|
|
190
|
+
throw new Error("Прокси закончились");
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const proxyString = this.proxyList[currentOffset];
|
|
194
|
+
const proxy = this.parseProxyString(proxyString);
|
|
195
|
+
|
|
196
|
+
// Увеличиваем offset для следующего использования
|
|
197
|
+
this.saveOffset(currentOffset + 1);
|
|
198
|
+
|
|
199
|
+
return proxy;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Save credentials to JSON file
|
|
204
|
+
*/
|
|
205
|
+
private saveCredentials(): void {
|
|
206
|
+
try {
|
|
207
|
+
fs.writeFileSync(
|
|
208
|
+
this.options.pathFileStorage,
|
|
209
|
+
JSON.stringify(this.credentials, null, 2),
|
|
210
|
+
"utf8"
|
|
211
|
+
);
|
|
212
|
+
} catch (error) {
|
|
213
|
+
console.error("Error saving credentials:", error);
|
|
214
|
+
throw new Error("Failed to save credentials");
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Add or update credential
|
|
220
|
+
*/
|
|
221
|
+
addCredential(credential: TelegramCredentials): void {
|
|
222
|
+
|
|
223
|
+
// Attach proxy to credential
|
|
224
|
+
if (this.requiredProxyForCredentials && !credential.proxy) {
|
|
225
|
+
try {
|
|
226
|
+
credential.proxy = this.getNextProxy();
|
|
227
|
+
} catch (error) {
|
|
228
|
+
throw new Error(`Не удалось назначить прокси: ${error.message}`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const existingIndex = this.credentials.findIndex((c) => c != null && c.phone === credential.phone);
|
|
233
|
+
|
|
234
|
+
if (existingIndex !== -1) {
|
|
235
|
+
// Update existing credential
|
|
236
|
+
this.credentials[existingIndex] = { ...this.credentials[existingIndex], ...credential };
|
|
237
|
+
} else {
|
|
238
|
+
// Add new credential
|
|
239
|
+
this.credentials.push(credential);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
this.credentials = this.credentials.filter((c) => c != null);
|
|
243
|
+
|
|
244
|
+
this.saveCredentials();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Get credential by phone number
|
|
249
|
+
*/
|
|
250
|
+
getCredential(phone: string): TelegramCredentials | null {
|
|
251
|
+
return this.credentials.find((c) => c != null && c.phone === phone) || null;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Delete credential by phone number
|
|
256
|
+
*/
|
|
257
|
+
deleteCredential(phone: string): boolean {
|
|
258
|
+
const initialLength = this.credentials.length;
|
|
259
|
+
this.credentials = this.credentials.filter((c) => c.phone !== phone);
|
|
260
|
+
|
|
261
|
+
if (this.credentials.length < initialLength) {
|
|
262
|
+
this.saveCredentials();
|
|
263
|
+
return true;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Get all credentials
|
|
271
|
+
*/
|
|
272
|
+
getAllCredentials(): TelegramCredentials[] {
|
|
273
|
+
return [...this.credentials];
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Check if credential exists
|
|
278
|
+
*/
|
|
279
|
+
hasCredential(phone: string): boolean {
|
|
280
|
+
return this.credentials.some((c) => c.phone === phone);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Update session for existing credential
|
|
285
|
+
*/
|
|
286
|
+
updateSession(phone: string, session: string): boolean {
|
|
287
|
+
const credential = this.credentials.find((c) => c.phone === phone);
|
|
288
|
+
if (credential) {
|
|
289
|
+
credential.session = session;
|
|
290
|
+
this.saveCredentials();
|
|
291
|
+
return true;
|
|
292
|
+
}
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Get credentials count
|
|
298
|
+
*/
|
|
299
|
+
getCredentialsCount(): number {
|
|
300
|
+
return this.credentials.length;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Clear all credentials
|
|
305
|
+
*/
|
|
306
|
+
clearAllCredentials(): void {
|
|
307
|
+
this.credentials = [];
|
|
308
|
+
this.saveCredentials();
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Get credentials by proxy configuration
|
|
313
|
+
*/
|
|
314
|
+
getCredentialsByProxy(proxyHost: string, proxyPort: number): TelegramCredentials[] {
|
|
315
|
+
return this.credentials.filter(
|
|
316
|
+
(c) => c.proxy && c.proxy.ip === proxyHost && c.proxy.port === proxyPort
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Get credentials without proxy
|
|
322
|
+
*/
|
|
323
|
+
getCredentialsWithoutProxy(): TelegramCredentials[] {
|
|
324
|
+
return this.credentials.filter((c) => !c.proxy);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export class TelegramAccountRemote {
|
|
329
|
+
private initOptions: TelegramRegistrarInit;
|
|
330
|
+
private tgClient: TelegramClient;
|
|
331
|
+
private credentialsManager: TelegramManagerCredentials;
|
|
332
|
+
|
|
333
|
+
static init(initOptions: TelegramRegistrarInit) {
|
|
334
|
+
return new TelegramAccountRemote(initOptions);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
constructor(initOptions: TelegramRegistrarInit) {
|
|
338
|
+
this.initOptions = initOptions;
|
|
339
|
+
this.credentialsManager = initOptions.credetialsManager;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
async attemptRestoreSession(credentials: TelegramCredentials): Promise<boolean> {
|
|
343
|
+
const { phone, proxy, session, password } = credentials;
|
|
344
|
+
const { appId, appHash } = this.initOptions;
|
|
345
|
+
|
|
346
|
+
if (session) {
|
|
347
|
+
const clientParams: TelegramClientParams = {connectionRetries: 5};
|
|
348
|
+
|
|
349
|
+
if (credentials.proxy) {
|
|
350
|
+
clientParams.proxy = credentials.proxy;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
this.tgClient = new TelegramClient(new StringSession(session), Number(appId), appHash, clientParams);
|
|
354
|
+
|
|
355
|
+
return await this.tgClient.connect();
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
async login(
|
|
362
|
+
credentials: TelegramCredentials,
|
|
363
|
+
cbRequestCode: CallableFunction,
|
|
364
|
+
cbRequestPassword: CallableFunction,
|
|
365
|
+
cbError: CallableFunction,
|
|
366
|
+
): Promise<void> {
|
|
367
|
+
const { phone, proxy, session, password } = credentials;
|
|
368
|
+
const { appId, appHash } = this.initOptions;
|
|
369
|
+
|
|
370
|
+
if (session) {
|
|
371
|
+
throw new Error("Account is already registered");
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
try {
|
|
375
|
+
this.tgClient = new TelegramClient(new StringSession(""), Number(appId), appHash, {
|
|
376
|
+
connectionRetries: 5,
|
|
377
|
+
proxy: credentials.proxy,
|
|
378
|
+
timeout: 10000 * 60 * 10,
|
|
379
|
+
});
|
|
380
|
+
console.log('Login cresentials:', credentials)
|
|
381
|
+
// @ts-ignore
|
|
382
|
+
await this.tgClient.start({
|
|
383
|
+
phoneNumber: async () => phone,
|
|
384
|
+
password: async () => {
|
|
385
|
+
if (!password) {
|
|
386
|
+
return await cbRequestPassword();
|
|
387
|
+
}
|
|
388
|
+
return password;
|
|
389
|
+
},
|
|
390
|
+
phoneCode: async () => {
|
|
391
|
+
return await cbRequestCode();
|
|
392
|
+
},
|
|
393
|
+
onError: (err: Error) => {
|
|
394
|
+
cbError('Error for wait authentiction', err);
|
|
395
|
+
},
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
const session = this.tgClient.session.save();
|
|
399
|
+
|
|
400
|
+
this.credentialsManager.addCredential({
|
|
401
|
+
phone,
|
|
402
|
+
proxy,
|
|
403
|
+
session,
|
|
404
|
+
password,
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
console.log("Регистрация успешна!");
|
|
408
|
+
console.log("Сессия:\n", session);
|
|
409
|
+
|
|
410
|
+
} catch (err) {
|
|
411
|
+
throw new Error("Failed to register account", { cause: err });
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
async disconnect(): Promise<void> {
|
|
416
|
+
await this.tgClient.disconnect();
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
async disconnectWhenConnect() {
|
|
420
|
+
await this.tgClient.disconnect();
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Отправляет команду /start боту
|
|
425
|
+
* @param botUsername username бота (например: 'my_bot' или '@my_bot')
|
|
426
|
+
* @returns true если команда успешно отправлена
|
|
427
|
+
*/
|
|
428
|
+
async sendStartCommand(botUsername: string): Promise<boolean> {
|
|
429
|
+
if (!this.tgClient) {
|
|
430
|
+
throw new Error("Client not initialized. Call login or attemptRestoreSession first");
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
try {
|
|
434
|
+
// Убираем @ из username если он есть
|
|
435
|
+
const normalizedUsername = botUsername.startsWith('@') ? botUsername.substring(1) : botUsername;
|
|
436
|
+
|
|
437
|
+
// Ищем бота по username
|
|
438
|
+
const result = await this.tgClient.sendMessage(normalizedUsername, {
|
|
439
|
+
message: '/start'
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
return result ? true : false;
|
|
443
|
+
} catch (error) {
|
|
444
|
+
console.error('Error sending /start command:', error);
|
|
445
|
+
throw new Error(`Failed to send /start command to @${botUsername}: ${error.message}`);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Получает ID чата с ботом
|
|
451
|
+
* @param botUsername username бота (например: 'my_bot' или '@my_bot')
|
|
452
|
+
* @returns ID чата с ботом
|
|
453
|
+
*/
|
|
454
|
+
async getBotChatId(botUsername: string): Promise<number> {
|
|
455
|
+
if (!this.tgClient) {
|
|
456
|
+
throw new Error("Client not initialized. Call login or attemptRestoreSession first");
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
try {
|
|
460
|
+
// Убираем @ из username если он есть
|
|
461
|
+
const normalizedUsername = botUsername.startsWith('@') ? botUsername.substring(1) : botUsername;
|
|
462
|
+
|
|
463
|
+
// Получаем информацию о диалоге с ботом
|
|
464
|
+
const dialog = await this.tgClient.getDialogs({
|
|
465
|
+
limit: 1
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
if (!dialog || dialog.length === 0) {
|
|
469
|
+
throw new Error(`Chat with bot @${normalizedUsername} not found`);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
return dialog[0].id.toJSNumber();
|
|
473
|
+
} catch (error) {
|
|
474
|
+
console.error('Error getting bot chat ID:', error);
|
|
475
|
+
throw new Error(`Failed to get chat ID for @${botUsername}: ${error.message}`);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Отправляет жалобу на бота
|
|
481
|
+
* @param botUsername username бота (например: 'my_bot' или '@my_bot')
|
|
482
|
+
* @param reason причина жалобы: 'spam' | 'violence' | 'pornography' | 'custompromoting' | 'other'
|
|
483
|
+
* @returns true если жалоба успешно отправлена
|
|
484
|
+
*/
|
|
485
|
+
async reportBot(
|
|
486
|
+
botUsername: string,
|
|
487
|
+
reason: 'spam' | 'violence' | 'pornography' | 'custompromoting' | 'other' = 'spam'
|
|
488
|
+
): Promise<boolean> {
|
|
489
|
+
if (!this.tgClient) {
|
|
490
|
+
throw new Error("Client not initialized. Call login or attemptRestoreSession first");
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
try {
|
|
494
|
+
const chatId = await this.getBotChatId(botUsername);
|
|
495
|
+
|
|
496
|
+
// Получаем сообщения от бота для репорта
|
|
497
|
+
const messages = await this.tgClient.getMessages(chatId, { limit: 1 });
|
|
498
|
+
if (!messages || messages.length === 0) {
|
|
499
|
+
throw new Error('No messages found to report');
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Формируем причину жалобы
|
|
503
|
+
let reportReason = reason;
|
|
504
|
+
if (reason === 'custompromoting') {
|
|
505
|
+
reportReason = 'spam';
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// Отправляем жалобу на последнее сообщение
|
|
509
|
+
await this.tgClient.sendMessage(chatId, {
|
|
510
|
+
message: `/report ${reportReason}`,
|
|
511
|
+
replyTo: messages[0].id
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
return true;
|
|
515
|
+
} catch (error) {
|
|
516
|
+
console.error('Error reporting bot:', error);
|
|
517
|
+
if (error instanceof Error) {
|
|
518
|
+
throw new Error(`Failed to report @${botUsername}: ${error.message}`);
|
|
519
|
+
}
|
|
520
|
+
throw new Error(`Failed to report @${botUsername}: Unknown error`);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { Telegraf, Context } from 'telegraf';
|
|
2
|
+
import { Section } from './illumination/Section';
|
|
3
|
+
import { UserStore } from './user/UserStore';
|
|
4
|
+
import { Telegraf2byteContext } from './illumination/Telegraf2byteContext';
|
|
5
|
+
import { RunSectionRoute } from './illumination/RunSectionRoute';
|
|
6
|
+
|
|
7
|
+
export interface AppConfig {
|
|
8
|
+
apiUrl: string | null;
|
|
9
|
+
envConfig: EnvVars;
|
|
10
|
+
botToken: string | null;
|
|
11
|
+
telegrafConfigLaunch: Record<string, any> | null;
|
|
12
|
+
settings: Record<string, any> | null;
|
|
13
|
+
userStorage: UserStore | null;
|
|
14
|
+
builderPromises: Promise<any>[];
|
|
15
|
+
sections: SectionList;
|
|
16
|
+
components: Record<string, string>;
|
|
17
|
+
debug: boolean;
|
|
18
|
+
devHotReloadSections: boolean;
|
|
19
|
+
telegrafLog: boolean;
|
|
20
|
+
mainMenuKeyboard: any[][];
|
|
21
|
+
hears: Record<string, string>;
|
|
22
|
+
terminateSigInt: boolean;
|
|
23
|
+
terminateSigTerm: boolean;
|
|
24
|
+
keepSectionInstances: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface SectionOptions {
|
|
28
|
+
ctx: Telegraf2byteContext;
|
|
29
|
+
bot: Telegraf<Telegraf2byteContext>;
|
|
30
|
+
app: any; // App instance,
|
|
31
|
+
route: RunSectionRoute;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface RunnedSection {
|
|
35
|
+
instance: Section;
|
|
36
|
+
route: RunSectionRoute;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface UserAttributes {
|
|
40
|
+
id?: number;
|
|
41
|
+
tg_id: number;
|
|
42
|
+
tg_username: string;
|
|
43
|
+
tg_name: string;
|
|
44
|
+
[key: string]: any;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface FileValidationOptions {
|
|
48
|
+
allowedTypes?: string[]; // ['image/jpeg', 'image/png', 'application/pdf']
|
|
49
|
+
maxSize?: number; // в байтах
|
|
50
|
+
minSize?: number; // в байтах
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface RequestInputOptions {
|
|
54
|
+
validator?: 'number' | 'phone' | 'code' | 'file' | ((value: string | any) => boolean | Promise<boolean>);
|
|
55
|
+
errorMessage?: string;
|
|
56
|
+
allowCancel?: boolean; // по умолчанию true
|
|
57
|
+
cancelButtonText?: string; // текст кнопки отмены
|
|
58
|
+
cancelAction?: string; // действие при отмене
|
|
59
|
+
fileValidation?: FileValidationOptions; // опции для валидации файлов
|
|
60
|
+
runSection?: RunSectionRoute;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface UserSession {
|
|
64
|
+
previousSection?: RunnedSection;
|
|
65
|
+
awaitingInput?: {
|
|
66
|
+
key: string;
|
|
67
|
+
validator?: 'number' | 'phone' | 'code' | 'file' | ((value: string | any) => boolean | Promise<boolean>);
|
|
68
|
+
errorMessage: string;
|
|
69
|
+
allowCancel: boolean;
|
|
70
|
+
cancelButtonText?: string;
|
|
71
|
+
cancelAction?: string;
|
|
72
|
+
fileValidation?: FileValidationOptions;
|
|
73
|
+
runSection?: RunSectionRoute;
|
|
74
|
+
retryCount?: number; // счетчик попыток
|
|
75
|
+
};
|
|
76
|
+
awaitingInputPromise?: {
|
|
77
|
+
key: string;
|
|
78
|
+
validator?: 'number' | 'phone' | 'code' | 'file' | ((value: string | any) => boolean | Promise<boolean>);
|
|
79
|
+
errorMessage: string;
|
|
80
|
+
allowCancel: boolean;
|
|
81
|
+
cancelButtonText?: string;
|
|
82
|
+
cancelAction?: string;
|
|
83
|
+
fileValidation?: FileValidationOptions;
|
|
84
|
+
retryCount?: number;
|
|
85
|
+
resolve: (value: string | any) => void;
|
|
86
|
+
reject: (reason?: any) => void;
|
|
87
|
+
};
|
|
88
|
+
[key: string]: any;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface UserRegistrationData {
|
|
92
|
+
tgUsername: string;
|
|
93
|
+
tgName: string;
|
|
94
|
+
tgId: number;
|
|
95
|
+
userRefid?: number;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface ComponentOptions {
|
|
99
|
+
ctx: Telegraf2byteContext;
|
|
100
|
+
app: any; // App instance
|
|
101
|
+
section: Section;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface UserServiceAttributes {
|
|
105
|
+
lastActive: Date;
|
|
106
|
+
lastMessageIds: number[];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface UserAwaitingReply {
|
|
110
|
+
answer: any;
|
|
111
|
+
validator: ((text: string) => Promise<boolean>) | null;
|
|
112
|
+
is_rejected: boolean;
|
|
113
|
+
run: [Section, string] | null;
|
|
114
|
+
type?: any;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface RunSectionRouteParams {
|
|
118
|
+
section: string | null;
|
|
119
|
+
method: string | null;
|
|
120
|
+
methodArgs: any[] | null;
|
|
121
|
+
callbackParams: URLSearchParams;
|
|
122
|
+
runAsCallcackQuery: boolean;
|
|
123
|
+
actionPath: string | null;
|
|
124
|
+
hearsKey: string | null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export type SectionEnabledList = string[]
|
|
128
|
+
|
|
129
|
+
export interface SectionEntityConfig {
|
|
130
|
+
pathModule?: string;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface SectionList {
|
|
134
|
+
[key: string]: SectionEntityConfig;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface EnvVars {
|
|
138
|
+
BOT_TOKEN?: string;
|
|
139
|
+
BOT_API_URL?: string;
|
|
140
|
+
BOT_HOOK_DOMAIN?: string;
|
|
141
|
+
BOT_HOOK_PORT?: string;
|
|
142
|
+
BOT_HOOK_SECRET_TOKEN?: string;
|
|
143
|
+
BOT_DEV_HOT_RELOAD_SECTIONS?: string;
|
|
144
|
+
[key: string]: string | undefined;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export type ModelPaginateParams = {
|
|
148
|
+
route: string;
|
|
149
|
+
routeParams?: Record<string, any>;
|
|
150
|
+
page: number;
|
|
151
|
+
limit: number;
|
|
152
|
+
whereSql: string;
|
|
153
|
+
whereParams?: any[];
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
export type PaginateResult = {
|
|
157
|
+
items: any[];
|
|
158
|
+
paginateButtons: any[][];
|
|
159
|
+
total: number;
|
|
160
|
+
totalPages: number;
|
|
161
|
+
hasPreviousPage: boolean;
|
|
162
|
+
hasNextPage: boolean;
|
|
163
|
+
currentPage: number;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
export type MakeManualPaginateButtonsParams = {
|
|
167
|
+
callbackDataAction: string;
|
|
168
|
+
paramsQuery: Record<string, any>;
|
|
169
|
+
currentPage: number | string;
|
|
170
|
+
totalRecords: number;
|
|
171
|
+
perPage: number;
|
|
172
|
+
};
|