@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,1140 +1,1140 @@
|
|
|
1
|
-
import { TelegramClient, Api } 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
|
-
import type { Database } from "bun:sqlite";
|
|
7
|
-
import { TgAccount, Proxy, TgAccountStatus, ProxyStatus } from '../models';
|
|
8
|
-
|
|
9
|
-
interface TelegramAccountRemoteInit {
|
|
10
|
-
appId: string;
|
|
11
|
-
appHash: string;
|
|
12
|
-
credetialsManager: TelegramManagerCredentialsDriver;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
type TelegramClientProxy = {
|
|
16
|
-
ip: string;
|
|
17
|
-
port: number;
|
|
18
|
-
username?: string;
|
|
19
|
-
password?: string;
|
|
20
|
-
secret?: string,
|
|
21
|
-
socksType?: 5 | 4;
|
|
22
|
-
MTProxy?: boolean;
|
|
23
|
-
} & ProxyInterface;
|
|
24
|
-
|
|
25
|
-
interface TelegramInitClient {
|
|
26
|
-
phone: string;
|
|
27
|
-
proxy?: any;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface TelegramCredentials {
|
|
31
|
-
phone: string;
|
|
32
|
-
proxy?: TelegramClientProxy;
|
|
33
|
-
password?: string;
|
|
34
|
-
session?: string;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
interface TelegramCredentialsManagerInit {
|
|
38
|
-
pathFileStorage: string;
|
|
39
|
-
pathFileProxyList: string;
|
|
40
|
-
pathFileCounterOffset: string;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export type TelegramManagerCredentialsDriver = TelegramManagerCredentials | TelegramManagerCredentialsDB;
|
|
44
|
-
|
|
45
|
-
export class TelegramManagerCredentials {
|
|
46
|
-
private requiredProxyForCredentials: boolean = false;
|
|
47
|
-
private credentials: TelegramCredentials[] = [];
|
|
48
|
-
private proxyList: string[] = [];
|
|
49
|
-
|
|
50
|
-
static init(options: TelegramCredentialsManagerInit) {
|
|
51
|
-
return new TelegramManagerCredentials(options);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
constructor(private options: TelegramCredentialsManagerInit) {
|
|
55
|
-
this.ensureStorageDirectory();
|
|
56
|
-
this.loadCredentials();
|
|
57
|
-
this.loadProxyList();
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Ensure storage directory exists
|
|
62
|
-
*/
|
|
63
|
-
private ensureStorageDirectory(): void {
|
|
64
|
-
const storageDir = this.options.pathFileStorage.split("/").slice(0, -1).join("/");
|
|
65
|
-
if (!fs.existsSync(storageDir)) {
|
|
66
|
-
fs.mkdirSync(storageDir, { recursive: true });
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Load credentials from JSON file
|
|
72
|
-
*/
|
|
73
|
-
private loadCredentials(): void {
|
|
74
|
-
try {
|
|
75
|
-
if (fs.existsSync(this.options.pathFileStorage)) {
|
|
76
|
-
const data = fs.readFileSync(this.options.pathFileStorage, "utf8");
|
|
77
|
-
this.credentials = JSON.parse(data) || [];
|
|
78
|
-
} else {
|
|
79
|
-
this.credentials = [];
|
|
80
|
-
this.saveCredentials();
|
|
81
|
-
}
|
|
82
|
-
} catch (error) {
|
|
83
|
-
console.error("Error loading credentials:", error);
|
|
84
|
-
this.credentials = [];
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Загрузить список прокси из файла
|
|
90
|
-
*/
|
|
91
|
-
private loadProxyList(): void {
|
|
92
|
-
try {
|
|
93
|
-
if (fs.existsSync(this.options.pathFileProxyList)) {
|
|
94
|
-
const data = fs.readFileSync(this.options.pathFileProxyList, "utf8");
|
|
95
|
-
this.proxyList = data.split('\n').filter(line => line.trim() !== '');
|
|
96
|
-
} else {
|
|
97
|
-
this.proxyList = [];
|
|
98
|
-
}
|
|
99
|
-
} catch (error) {
|
|
100
|
-
console.error("Ошибка загрузки списка прокси:", error);
|
|
101
|
-
this.proxyList = [];
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Получить текущий offset из файла счетчика
|
|
107
|
-
*/
|
|
108
|
-
private getCurrentOffset(): number {
|
|
109
|
-
try {
|
|
110
|
-
if (fs.existsSync(this.options.pathFileCounterOffset)) {
|
|
111
|
-
const data = fs.readFileSync(this.options.pathFileCounterOffset, "utf8");
|
|
112
|
-
return parseInt(data.trim()) || 0;
|
|
113
|
-
}
|
|
114
|
-
return 0;
|
|
115
|
-
} catch (error) {
|
|
116
|
-
console.error("Ошибка чтения offset:", error);
|
|
117
|
-
return 0;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Сохранить новый offset в файл счетчика
|
|
123
|
-
*/
|
|
124
|
-
private saveOffset(offset: number): void {
|
|
125
|
-
try {
|
|
126
|
-
fs.writeFileSync(this.options.pathFileCounterOffset, offset.toString(), "utf8");
|
|
127
|
-
} catch (error) {
|
|
128
|
-
console.error("Ошибка сохранения offset:", error);
|
|
129
|
-
throw new Error("Не удалось сохранить offset");
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Парсинг строки прокси в формат TelegramClientProxy
|
|
135
|
-
* Поддерживаемые форматы:
|
|
136
|
-
* - socks4://username:password@host:port
|
|
137
|
-
* - socks5://username:password@host:port
|
|
138
|
-
* - http://username:password@host:port (будет использоваться как SOCKS5)
|
|
139
|
-
*/
|
|
140
|
-
private parseProxyString(proxyString: string): TelegramClientProxy {
|
|
141
|
-
// Проверяем SOCKS4 формат
|
|
142
|
-
let match = proxyString.match(/^socks4:\/\/([^:]+):([^@]+)@([^:]+):(\d+)$/);
|
|
143
|
-
if (match) {
|
|
144
|
-
const [, username, password, ip, port] = match;
|
|
145
|
-
return {
|
|
146
|
-
ip,
|
|
147
|
-
port: parseInt(port),
|
|
148
|
-
username,
|
|
149
|
-
password,
|
|
150
|
-
socksType: 4
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Проверяем SOCKS5 формат
|
|
155
|
-
match = proxyString.match(/^socks5:\/\/([^:]+):([^@]+)@([^:]+):(\d+)$/);
|
|
156
|
-
if (match) {
|
|
157
|
-
const [, username, password, ip, port] = match;
|
|
158
|
-
return {
|
|
159
|
-
ip,
|
|
160
|
-
port: parseInt(port),
|
|
161
|
-
username,
|
|
162
|
-
password,
|
|
163
|
-
socksType: 5
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Проверяем HTTP формат (используем как SOCKS5)
|
|
168
|
-
match = proxyString.match(/^http:\/\/([^:]+):([^@]+)@([^:]+):(\d+)$/);
|
|
169
|
-
if (match) {
|
|
170
|
-
const [, username, password, ip, port] = match;
|
|
171
|
-
return {
|
|
172
|
-
ip,
|
|
173
|
-
port: parseInt(port),
|
|
174
|
-
username,
|
|
175
|
-
password,
|
|
176
|
-
socksType: 5 // HTTP прокси используем как SOCKS5
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
throw new Error(`Неверный формат прокси: ${proxyString}. Поддерживаемые форматы: socks4://, socks5://, http://`);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Получить следующий прокси из списка
|
|
185
|
-
*/
|
|
186
|
-
private getNextProxy(): TelegramClientProxy {
|
|
187
|
-
if (this.proxyList.length === 0) {
|
|
188
|
-
throw new Error("Список прокси пуст");
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
const currentOffset = this.getCurrentOffset();
|
|
192
|
-
|
|
193
|
-
if (currentOffset >= this.proxyList.length) {
|
|
194
|
-
throw new Error("Прокси закончились");
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
const proxyString = this.proxyList[currentOffset];
|
|
198
|
-
const proxy = this.parseProxyString(proxyString);
|
|
199
|
-
|
|
200
|
-
// Увеличиваем offset для следующего использования
|
|
201
|
-
this.saveOffset(currentOffset + 1);
|
|
202
|
-
|
|
203
|
-
return proxy;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Save credentials to JSON file
|
|
208
|
-
*/
|
|
209
|
-
private saveCredentials(): void {
|
|
210
|
-
try {
|
|
211
|
-
fs.writeFileSync(
|
|
212
|
-
this.options.pathFileStorage,
|
|
213
|
-
JSON.stringify(this.credentials, null, 2),
|
|
214
|
-
"utf8"
|
|
215
|
-
);
|
|
216
|
-
} catch (error) {
|
|
217
|
-
console.error("Error saving credentials:", error);
|
|
218
|
-
throw new Error("Failed to save credentials");
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* Add or update credential
|
|
224
|
-
*/
|
|
225
|
-
addCredential(credential: TelegramCredentials): void {
|
|
226
|
-
// replace +
|
|
227
|
-
if (credential.phone.includes('+')) {
|
|
228
|
-
credential.phone = credential.phone.replace('+', '');
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// Attach proxy to credential
|
|
232
|
-
if (this.requiredProxyForCredentials && !credential.proxy) {
|
|
233
|
-
try {
|
|
234
|
-
credential.proxy = this.getNextProxy();
|
|
235
|
-
} catch (error) {
|
|
236
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
237
|
-
throw new Error(`Не удалось назначить прокси: ${errorMessage}`);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
const existingIndex = this.credentials.findIndex((c) => c != null && c.phone === credential.phone);
|
|
242
|
-
|
|
243
|
-
if (existingIndex !== -1) {
|
|
244
|
-
// Update existing credential
|
|
245
|
-
this.credentials[existingIndex] = { ...this.credentials[existingIndex], ...credential };
|
|
246
|
-
} else {
|
|
247
|
-
// Add new credential
|
|
248
|
-
this.credentials.push(credential);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
this.credentials = this.credentials.filter((c) => c != null);
|
|
252
|
-
|
|
253
|
-
this.saveCredentials();
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* Get credential by phone number
|
|
258
|
-
*/
|
|
259
|
-
getCredential(phone: string): TelegramCredentials | null {
|
|
260
|
-
return this.credentials.find((c) => c != null && c.phone === phone) || null;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/**
|
|
264
|
-
* Delete credential by phone number
|
|
265
|
-
*/
|
|
266
|
-
deleteCredential(phone: string): boolean {
|
|
267
|
-
const initialLength = this.credentials.length;
|
|
268
|
-
this.credentials = this.credentials.filter((c) => c.phone !== phone);
|
|
269
|
-
|
|
270
|
-
if (this.credentials.length < initialLength) {
|
|
271
|
-
this.saveCredentials();
|
|
272
|
-
return true;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
return false;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
* Get all credentials
|
|
280
|
-
*/
|
|
281
|
-
getAllCredentials(): TelegramCredentials[] {
|
|
282
|
-
return [...this.credentials];
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* Check if credential exists
|
|
287
|
-
*/
|
|
288
|
-
hasCredential(phone: string): boolean {
|
|
289
|
-
return this.credentials.some((c) => c.phone === phone);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
/**
|
|
293
|
-
* Update session for existing credential
|
|
294
|
-
*/
|
|
295
|
-
updateSession(phone: string, session: string): boolean {
|
|
296
|
-
const credential = this.credentials.find((c) => c.phone === phone);
|
|
297
|
-
if (credential) {
|
|
298
|
-
credential.session = session;
|
|
299
|
-
this.saveCredentials();
|
|
300
|
-
return true;
|
|
301
|
-
}
|
|
302
|
-
return false;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
/**
|
|
306
|
-
* Get credentials count
|
|
307
|
-
*/
|
|
308
|
-
getCredentialsCount(): number {
|
|
309
|
-
return this.credentials.length;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
/**
|
|
313
|
-
* Clear all credentials
|
|
314
|
-
*/
|
|
315
|
-
clearAllCredentials(): void {
|
|
316
|
-
this.credentials = [];
|
|
317
|
-
this.saveCredentials();
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* Get credentials by proxy configuration
|
|
322
|
-
*/
|
|
323
|
-
getCredentialsByProxy(proxyHost: string, proxyPort: number): TelegramCredentials[] {
|
|
324
|
-
return this.credentials.filter(
|
|
325
|
-
(c) => c.proxy && c.proxy.ip === proxyHost && c.proxy.port === proxyPort
|
|
326
|
-
);
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
/**
|
|
330
|
-
* Get credentials without proxy
|
|
331
|
-
*/
|
|
332
|
-
getCredentialsWithoutProxy(): TelegramCredentials[] {
|
|
333
|
-
return this.credentials.filter((c) => !c.proxy);
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
export class TelegramAccountRemote {
|
|
338
|
-
private initOptions: TelegramAccountRemoteInit;
|
|
339
|
-
private tgClient!: TelegramClient; // Используем definite assignment assertion
|
|
340
|
-
private credentialsManager: TelegramManagerCredentialsDriver;
|
|
341
|
-
|
|
342
|
-
static init(initOptions: TelegramAccountRemoteInit) {
|
|
343
|
-
return new TelegramAccountRemote(initOptions);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
constructor(initOptions: TelegramAccountRemoteInit) {
|
|
347
|
-
this.initOptions = initOptions;
|
|
348
|
-
this.credentialsManager = initOptions.credetialsManager;
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
async attemptRestoreSession(credentials: TelegramCredentials): Promise<boolean> {
|
|
352
|
-
const { phone, proxy, session, password } = credentials;
|
|
353
|
-
const { appId, appHash } = this.initOptions;
|
|
354
|
-
|
|
355
|
-
if (session) {
|
|
356
|
-
const clientParams: TelegramClientParams = {connectionRetries: 5};
|
|
357
|
-
|
|
358
|
-
if (credentials.proxy) {
|
|
359
|
-
clientParams.proxy = credentials.proxy;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
this.tgClient = new TelegramClient(new StringSession(session), Number(appId), appHash, clientParams);
|
|
363
|
-
|
|
364
|
-
return await this.tgClient.connect();
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
return false;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
async login(
|
|
371
|
-
credentials: TelegramCredentials,
|
|
372
|
-
cbRequestCode: CallableFunction,
|
|
373
|
-
cbRequestPassword: CallableFunction,
|
|
374
|
-
cbError: CallableFunction,
|
|
375
|
-
): Promise<void> {
|
|
376
|
-
const { phone, proxy, session, password } = credentials;
|
|
377
|
-
const { appId, appHash } = this.initOptions;
|
|
378
|
-
|
|
379
|
-
if (session) {
|
|
380
|
-
throw new Error("Account is already registered");
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
try {
|
|
384
|
-
this.tgClient = new TelegramClient(new StringSession(""), Number(appId), appHash, {
|
|
385
|
-
connectionRetries: 5,
|
|
386
|
-
proxy: credentials.proxy,
|
|
387
|
-
timeout: 10000 * 60 * 10,
|
|
388
|
-
});
|
|
389
|
-
console.log('Login cresentials:', credentials)
|
|
390
|
-
// @ts-ignore
|
|
391
|
-
await this.tgClient.start({
|
|
392
|
-
phoneNumber: async () => phone,
|
|
393
|
-
password: async () => {
|
|
394
|
-
if (!password) {
|
|
395
|
-
return await cbRequestPassword();
|
|
396
|
-
}
|
|
397
|
-
return password;
|
|
398
|
-
},
|
|
399
|
-
phoneCode: async () => {
|
|
400
|
-
return await cbRequestCode();
|
|
401
|
-
},
|
|
402
|
-
onError: (err: Error) => {
|
|
403
|
-
cbError('Error for wait authentiction', err);
|
|
404
|
-
},
|
|
405
|
-
});
|
|
406
|
-
|
|
407
|
-
const session = this.tgClient.session.save() as unknown as string;
|
|
408
|
-
|
|
409
|
-
this.credentialsManager.addCredential({
|
|
410
|
-
phone,
|
|
411
|
-
proxy,
|
|
412
|
-
session,
|
|
413
|
-
password,
|
|
414
|
-
});
|
|
415
|
-
|
|
416
|
-
console.log("Регистрация успешна!");
|
|
417
|
-
console.log("Сессия:\n", session);
|
|
418
|
-
|
|
419
|
-
} catch (err) {
|
|
420
|
-
throw new Error("Failed to register account", { cause: err });
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
async disconnect(): Promise<void> {
|
|
425
|
-
await this.tgClient.disconnect();
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
async disconnectWhenConnect() {
|
|
429
|
-
await this.tgClient.disconnect();
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
/**
|
|
433
|
-
* Отправляет команду /start боту
|
|
434
|
-
* @param botUsername username бота (например: 'my_bot' или '@my_bot')
|
|
435
|
-
* @returns true если команда успешно отправлена
|
|
436
|
-
*/
|
|
437
|
-
async sendStartCommand(botUsername: string): Promise<boolean> {
|
|
438
|
-
if (!this.tgClient) {
|
|
439
|
-
throw new Error("Client not initialized. Call login or attemptRestoreSession first");
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
try {
|
|
443
|
-
// Убираем @ из username если он есть
|
|
444
|
-
const normalizedUsername = botUsername.startsWith('@') ? botUsername.substring(1) : botUsername;
|
|
445
|
-
|
|
446
|
-
// Ищем бота по username
|
|
447
|
-
const result = await this.tgClient.sendMessage(normalizedUsername, {
|
|
448
|
-
message: '/start'
|
|
449
|
-
});
|
|
450
|
-
|
|
451
|
-
return result ? true : false;
|
|
452
|
-
} catch (error) {
|
|
453
|
-
console.error('Error sending /start command:', error);
|
|
454
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
455
|
-
throw new Error(`Failed to send /start command to @${botUsername}: ${errorMessage}`);
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
/**
|
|
460
|
-
* Получает ID чата с ботом
|
|
461
|
-
* @param botUsername username бота (например: 'my_bot' или '@my_bot')
|
|
462
|
-
* @returns ID чата с ботом
|
|
463
|
-
*/
|
|
464
|
-
async getBotChatId(botUsername: string): Promise<number> {
|
|
465
|
-
if (!this.tgClient) {
|
|
466
|
-
throw new Error("Client not initialized. Call login or attemptRestoreSession first");
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
try {
|
|
470
|
-
// Убираем @ из username если он есть
|
|
471
|
-
const normalizedUsername = botUsername.startsWith('@') ? botUsername.substring(1) : botUsername;
|
|
472
|
-
|
|
473
|
-
// Получаем информацию о диалоге с ботом
|
|
474
|
-
const dialog = await this.tgClient.getDialogs({
|
|
475
|
-
limit: 1
|
|
476
|
-
});
|
|
477
|
-
|
|
478
|
-
if (!dialog || dialog.length === 0 || !dialog[0] || !dialog[0].id) {
|
|
479
|
-
throw new Error(`Chat with bot @${normalizedUsername} not found`);
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
return dialog[0].id!.toJSNumber();
|
|
483
|
-
} catch (error) {
|
|
484
|
-
console.error('Error getting bot chat ID:', error);
|
|
485
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
486
|
-
throw new Error(`Failed to get chat ID for @${botUsername}: ${errorMessage}`);
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
/**
|
|
491
|
-
* Отправляет жалобу на бота
|
|
492
|
-
* @param botUsername username бота (например: 'my_bot' или '@my_bot')
|
|
493
|
-
* @param reason причина жалобы: 'spam' | 'violence' | 'pornography' | 'custompromoting' | 'other'
|
|
494
|
-
* @returns true если жалоба успешно отправлена
|
|
495
|
-
*/
|
|
496
|
-
async reportBot(
|
|
497
|
-
botUsername: string,
|
|
498
|
-
reason: 'spam' | 'violence' | 'pornography' | 'custompromoting' | 'other' = 'spam'
|
|
499
|
-
): Promise<boolean> {
|
|
500
|
-
if (!this.tgClient) {
|
|
501
|
-
throw new Error("Client not initialized. Call login or attemptRestoreSession first");
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
try {
|
|
505
|
-
const chatId = await this.getBotChatId(botUsername);
|
|
506
|
-
|
|
507
|
-
// Получаем сообщения от бота для репорта
|
|
508
|
-
const messages = await this.tgClient.getMessages(chatId, { limit: 1 });
|
|
509
|
-
if (!messages || messages.length === 0) {
|
|
510
|
-
throw new Error('No messages found to report');
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
// Формируем причину жалобы
|
|
514
|
-
let reportReason = reason;
|
|
515
|
-
if (reason === 'custompromoting') {
|
|
516
|
-
reportReason = 'spam';
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
// Отправляем жалобу на последнее сообщение
|
|
520
|
-
await this.tgClient.sendMessage(chatId, {
|
|
521
|
-
message: `/report ${reportReason}`,
|
|
522
|
-
replyTo: messages[0].id
|
|
523
|
-
});
|
|
524
|
-
|
|
525
|
-
return true;
|
|
526
|
-
} catch (error) {
|
|
527
|
-
console.error('Error reporting bot:', error);
|
|
528
|
-
if (error instanceof Error) {
|
|
529
|
-
throw new Error(`Failed to report @${botUsername}: ${error.message}`);
|
|
530
|
-
}
|
|
531
|
-
throw new Error(`Failed to report @${botUsername}: Unknown error`);
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
/**
|
|
536
|
-
* Проверяет, зарегистрирован ли номер телефона в Telegram
|
|
537
|
-
* @param phoneNumber номер телефона в международном формате (например: '+380123456789')
|
|
538
|
-
* @returns true если номер зарегистрирован в Telegram, false если нет
|
|
539
|
-
*/
|
|
540
|
-
async isPhoneRegistered(phoneNumber: string): Promise<boolean> {
|
|
541
|
-
if (!this.tgClient) {
|
|
542
|
-
throw new Error("Client not initialized. Call login or attemptRestoreSession first");
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
try {
|
|
546
|
-
// Попытаемся найти пользователя, отправив сообщение самому себе с информацией о номере
|
|
547
|
-
// Это безопасный способ проверки без отправки реальных сообщений
|
|
548
|
-
const me = await this.tgClient.getMe();
|
|
549
|
-
|
|
550
|
-
// Используем поиск по username если номер содержит буквы, иначе считаем что это номер
|
|
551
|
-
if (phoneNumber.includes('@')) {
|
|
552
|
-
try {
|
|
553
|
-
const entity = await this.tgClient.getEntity(phoneNumber);
|
|
554
|
-
return entity ? true : false;
|
|
555
|
-
} catch {
|
|
556
|
-
return false;
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
// Для номеров телефонов возвращаем true по умолчанию
|
|
561
|
-
// В реальном приложении здесь должен быть более сложный API вызов
|
|
562
|
-
console.log(`Проверка номера ${phoneNumber} - предполагаем что зарегистрирован`);
|
|
563
|
-
return true;
|
|
564
|
-
} catch (error) {
|
|
565
|
-
console.error('Error checking phone registration:', error);
|
|
566
|
-
return false;
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
/**
|
|
571
|
-
* Добавляет контакт в адресную книгу Telegram
|
|
572
|
-
* @param phoneNumber номер телефона в международном формате
|
|
573
|
-
* @param firstName имя контакта
|
|
574
|
-
* @param lastName фамилия контакта (необязательно)
|
|
575
|
-
* @returns true если контакт успешно добавлен
|
|
576
|
-
*/
|
|
577
|
-
async addContact(phoneNumber: string, firstName: string, lastName?: string): Promise<boolean> {
|
|
578
|
-
if (!this.tgClient) {
|
|
579
|
-
throw new Error("Client not initialized. Call login or attemptRestoreSession first");
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
try {
|
|
583
|
-
// Импортируем API для работы с контактами
|
|
584
|
-
const bigInteger = (await import('big-integer')).default;
|
|
585
|
-
|
|
586
|
-
// Нормализуем номер телефона (убираем все символы кроме цифр и +)
|
|
587
|
-
const normalizedPhone = phoneNumber.replace(/[^\d+]/g, '');
|
|
588
|
-
|
|
589
|
-
// Создаем контакт для импорта
|
|
590
|
-
const contact = new Api.InputPhoneContact({
|
|
591
|
-
clientId: bigInteger(Math.floor(Math.random() * 1000000000)), // Генерируем случайный ID
|
|
592
|
-
phone: normalizedPhone.replace(/^\+/, ''), // Убираем + для API
|
|
593
|
-
firstName: firstName,
|
|
594
|
-
lastName: lastName || ''
|
|
595
|
-
});
|
|
596
|
-
|
|
597
|
-
console.log(`🔍 Попытка добавить контакт: ${firstName} ${lastName || ''} (${phoneNumber})`);
|
|
598
|
-
|
|
599
|
-
// Импортируем контакт через API
|
|
600
|
-
const result = await this.tgClient.invoke(
|
|
601
|
-
new Api.contacts.ImportContacts({
|
|
602
|
-
contacts: [contact]
|
|
603
|
-
})
|
|
604
|
-
);
|
|
605
|
-
|
|
606
|
-
// Проверяем результат импорта
|
|
607
|
-
if (result.imported && result.imported.length > 0) {
|
|
608
|
-
console.log(`✅ Контакт ${firstName} ${lastName || ''} (${phoneNumber}) успешно добавлен`);
|
|
609
|
-
|
|
610
|
-
// Если есть информация о пользователе
|
|
611
|
-
if (result.users && result.users.length > 0) {
|
|
612
|
-
const user = result.users[0];
|
|
613
|
-
const username = (user as any).username;
|
|
614
|
-
console.log(`📱 Найден пользователь Telegram: @${username || 'без username'}`);
|
|
615
|
-
console.log(`🆔 ID пользователя: ${user.id}`);
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
return true;
|
|
619
|
-
} else if (result.retryContacts && result.retryContacts.length > 0) {
|
|
620
|
-
console.log(`⚠️ Контакт ${phoneNumber} требует повторной попытки`);
|
|
621
|
-
throw new Error(`Contact ${phoneNumber} requires retry`);
|
|
622
|
-
} else {
|
|
623
|
-
// Проверяем, найден ли пользователь в результате (контакт уже существует)
|
|
624
|
-
if (result.users && result.users.length > 0) {
|
|
625
|
-
const user = result.users[0];
|
|
626
|
-
const username = (user as any).username;
|
|
627
|
-
console.log(`� Пользователь уже существует в контактах: @${username || 'без username'}`);
|
|
628
|
-
return true;
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
console.log(`ℹ️ Контакт ${phoneNumber} не найден в Telegram или не удалось добавить`);
|
|
632
|
-
throw new Error(`Contact ${phoneNumber} not found or could not be added`);
|
|
633
|
-
}
|
|
634
|
-
} catch (error) {
|
|
635
|
-
console.error('Error adding contact:', error);
|
|
636
|
-
|
|
637
|
-
// Если пользователь не найден, это не критическая ошибка
|
|
638
|
-
if (error instanceof Error && (
|
|
639
|
-
error.message.includes('USER_NOT_FOUND') ||
|
|
640
|
-
error.message.includes('PHONE_NOT_OCCUPIED') ||
|
|
641
|
-
error.message.includes('USERNAME_NOT_OCCUPIED')
|
|
642
|
-
)) {
|
|
643
|
-
console.log(`ℹ️ Пользователь с номером ${phoneNumber} не зарегистрирован в Telegram`);
|
|
644
|
-
throw new Error(`User with phone ${phoneNumber} is not registered in Telegram`);
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
throw error;
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
/**
|
|
652
|
-
* Получает информацию о пользователе по номеру телефона или username
|
|
653
|
-
* @param identifier номер телефона или username
|
|
654
|
-
* @returns информация о пользователе или null если не найден
|
|
655
|
-
*/
|
|
656
|
-
async getUserByPhone(identifier: string): Promise<any | null> {
|
|
657
|
-
if (!this.tgClient) {
|
|
658
|
-
throw new Error("Client not initialized. Call login or attemptRestoreSession first");
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
try {
|
|
662
|
-
// Пытаемся получить информацию о пользователе
|
|
663
|
-
let entity;
|
|
664
|
-
|
|
665
|
-
if (identifier.startsWith('@') || !identifier.startsWith('+')) {
|
|
666
|
-
// Если это username, пытаемся найти по username
|
|
667
|
-
entity = await this.tgClient.getEntity(identifier);
|
|
668
|
-
} else {
|
|
669
|
-
// Если это номер телефона, логируем попытку поиска
|
|
670
|
-
console.log(`Поиск пользователя по номеру: ${identifier}`);
|
|
671
|
-
return null; // В упрощенной версии возвращаем null для номеров
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
if (entity) {
|
|
675
|
-
return {
|
|
676
|
-
id: entity.id?.toString() || '',
|
|
677
|
-
firstName: (entity as any).firstName || '',
|
|
678
|
-
lastName: (entity as any).lastName || '',
|
|
679
|
-
username: (entity as any).username || '',
|
|
680
|
-
phone: identifier.startsWith('+') ? identifier : '',
|
|
681
|
-
isBot: (entity as any).bot || false,
|
|
682
|
-
isVerified: (entity as any).verified || false,
|
|
683
|
-
isPremium: (entity as any).premium || false
|
|
684
|
-
};
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
return null;
|
|
688
|
-
} catch (error) {
|
|
689
|
-
console.error('Error getting user info:', error);
|
|
690
|
-
return null;
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
/**
|
|
695
|
-
* Массовая проверка номеров телефонов на регистрацию в Telegram
|
|
696
|
-
* @param phoneNumbers массив номеров телефонов
|
|
697
|
-
* @returns объект с результатами проверки для каждого номера
|
|
698
|
-
*/
|
|
699
|
-
async checkMultiplePhones(phoneNumbers: string[]): Promise<{[phone: string]: boolean}> {
|
|
700
|
-
if (!this.tgClient) {
|
|
701
|
-
throw new Error("Client not initialized. Call login or attemptRestoreSession first");
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
const results: {[phone: string]: boolean} = {};
|
|
705
|
-
|
|
706
|
-
// Проверяем каждый номер по очереди
|
|
707
|
-
for (const phone of phoneNumbers) {
|
|
708
|
-
try {
|
|
709
|
-
results[phone] = await this.isPhoneRegistered(phone);
|
|
710
|
-
|
|
711
|
-
// Небольшая задержка между запросами для избежания rate limit
|
|
712
|
-
await new Promise(resolve => setTimeout(resolve, 200));
|
|
713
|
-
} catch (error) {
|
|
714
|
-
console.error(`Error checking phone ${phone}:`, error);
|
|
715
|
-
results[phone] = false;
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
return results;
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
/**
|
|
723
|
-
* Отправляет сообщение пользователю по ID или username
|
|
724
|
-
* @param target ID пользователя или username
|
|
725
|
-
* @param message текст сообщения
|
|
726
|
-
* @returns true если сообщение отправлено успешно
|
|
727
|
-
*/
|
|
728
|
-
async sendMessageToUser(target: string, message: string): Promise<boolean> {
|
|
729
|
-
if (!this.tgClient) {
|
|
730
|
-
throw new Error("Client not initialized. Call login or attemptRestoreSession first");
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
try {
|
|
734
|
-
// Отправляем сообщение
|
|
735
|
-
const result = await this.tgClient.sendMessage(target, {
|
|
736
|
-
message: message
|
|
737
|
-
});
|
|
738
|
-
|
|
739
|
-
console.log(`✅ Сообщение отправлено пользователю ${target}`);
|
|
740
|
-
return result ? true : false;
|
|
741
|
-
} catch (error) {
|
|
742
|
-
console.error('Error sending message:', error);
|
|
743
|
-
throw new Error(`Failed to send message to ${target}: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
/**
|
|
749
|
-
* Менеджер учетных данных Telegram с поддержкой базы данных SQLite
|
|
750
|
-
*/
|
|
751
|
-
export class TelegramManagerCredentialsDB {
|
|
752
|
-
private database: Database;
|
|
753
|
-
private requiredProxyForCredentials: boolean = false;
|
|
754
|
-
|
|
755
|
-
// Локальные импорты для избежания конфликтов путей
|
|
756
|
-
private TgAccount: any;
|
|
757
|
-
private Proxy: any;
|
|
758
|
-
private TgAccountStatus: any;
|
|
759
|
-
private ProxyStatus: any;
|
|
760
|
-
|
|
761
|
-
constructor(database: Database) {
|
|
762
|
-
this.database = database;
|
|
763
|
-
|
|
764
|
-
// Динамический импорт моделей
|
|
765
|
-
this.initModels();
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
private async initModels() {
|
|
769
|
-
this.TgAccount = TgAccount;
|
|
770
|
-
this.Proxy = Proxy;
|
|
771
|
-
this.TgAccountStatus = TgAccountStatus;
|
|
772
|
-
this.ProxyStatus = ProxyStatus;
|
|
773
|
-
|
|
774
|
-
// Устанавливаем базу данных для моделей
|
|
775
|
-
this.TgAccount.setDatabase(this.database);
|
|
776
|
-
this.Proxy.setDatabase(this.database);
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
/**
|
|
780
|
-
* Включить/выключить обязательное использование прокси для учетных данных
|
|
781
|
-
*/
|
|
782
|
-
setRequiredProxyForCredentials(required: boolean): void {
|
|
783
|
-
this.requiredProxyForCredentials = required;
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
/**
|
|
787
|
-
* Преобразовать данные прокси из БД в формат TelegramClientProxy
|
|
788
|
-
*/
|
|
789
|
-
private convertProxyToTelegramFormat(proxyData: any): TelegramClientProxy {
|
|
790
|
-
return {
|
|
791
|
-
ip: proxyData.ip,
|
|
792
|
-
port: proxyData.port,
|
|
793
|
-
username: proxyData.username || undefined,
|
|
794
|
-
password: proxyData.password || undefined,
|
|
795
|
-
secret: proxyData.secret || undefined,
|
|
796
|
-
socksType: (proxyData.socksType === 4 || proxyData.socksType === 5) ? proxyData.socksType as 4 | 5 : 5,
|
|
797
|
-
MTProxy: proxyData.MTProxy === 1
|
|
798
|
-
};
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
/**
|
|
802
|
-
* Получить следующий доступный прокси из БД
|
|
803
|
-
*/
|
|
804
|
-
private async getNextProxy(): Promise<TelegramClientProxy> {
|
|
805
|
-
const proxy = await this.Proxy.getRandomActive();
|
|
806
|
-
if (!proxy) {
|
|
807
|
-
throw new Error("Нет доступных прокси в базе данных");
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
return this.convertProxyToTelegramFormat(proxy);
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
/**
|
|
814
|
-
* Добавить или обновить учетные данные
|
|
815
|
-
*/
|
|
816
|
-
async addCredential(credential: TelegramCredentials): Promise<void> {
|
|
817
|
-
let proxyId: number | null = null;
|
|
818
|
-
|
|
819
|
-
// Назначить прокси если требуется
|
|
820
|
-
if (this.requiredProxyForCredentials && !credential.proxy) {
|
|
821
|
-
try {
|
|
822
|
-
const proxy = await this.getNextProxy();
|
|
823
|
-
credential.proxy = proxy;
|
|
824
|
-
|
|
825
|
-
// Найти ID прокси в БД
|
|
826
|
-
const proxyData = await this.Proxy.findByIpPort(proxy.ip, proxy.port);
|
|
827
|
-
if (proxyData) {
|
|
828
|
-
proxyId = proxyData.id!;
|
|
829
|
-
}
|
|
830
|
-
} catch (error) {
|
|
831
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
832
|
-
throw new Error(`Не удалось назначить прокси: ${errorMessage}`);
|
|
833
|
-
}
|
|
834
|
-
} else if (credential.proxy) {
|
|
835
|
-
// Найти существующий прокси или создать новый
|
|
836
|
-
let proxyData = await this.Proxy.findByIpPort(credential.proxy.ip, credential.proxy.port);
|
|
837
|
-
if (!proxyData) {
|
|
838
|
-
// Создать новый прокси в БД
|
|
839
|
-
proxyId = await this.Proxy.create({
|
|
840
|
-
ip: credential.proxy.ip,
|
|
841
|
-
port: credential.proxy.port,
|
|
842
|
-
username: credential.proxy.username || null,
|
|
843
|
-
password: credential.proxy.password || null,
|
|
844
|
-
secret: credential.proxy.secret || null,
|
|
845
|
-
socksType: credential.proxy.socksType || null,
|
|
846
|
-
MTProxy: credential.proxy.MTProxy ? 1 : 0,
|
|
847
|
-
status: this.ProxyStatus.ACTIVE
|
|
848
|
-
});
|
|
849
|
-
} else {
|
|
850
|
-
proxyId = proxyData.id!;
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
// Используем upsert для вставки или обновления
|
|
855
|
-
await this.TgAccount.upsert({
|
|
856
|
-
phone: credential.phone.replace('+', ''),
|
|
857
|
-
session: credential.session || null,
|
|
858
|
-
password: credential.password || null,
|
|
859
|
-
proxy_id: proxyId,
|
|
860
|
-
status: credential.session ? this.TgAccountStatus.ACTIVE : this.TgAccountStatus.INACTIVE
|
|
861
|
-
});
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
/**
|
|
865
|
-
* Получить учетные данные по номеру телефона
|
|
866
|
-
*/
|
|
867
|
-
async getCredential(phone: string): Promise<TelegramCredentials | null> {
|
|
868
|
-
const accountData = await this.TgAccount.findWithProxy(phone);
|
|
869
|
-
if (!accountData) {
|
|
870
|
-
return null;
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
let proxy: TelegramClientProxy | undefined;
|
|
874
|
-
if (accountData.proxy_id && accountData.proxy_ip) {
|
|
875
|
-
proxy = {
|
|
876
|
-
ip: accountData.proxy_ip,
|
|
877
|
-
port: accountData.proxy_port,
|
|
878
|
-
username: accountData.proxy_username || undefined,
|
|
879
|
-
password: accountData.proxy_password || undefined,
|
|
880
|
-
secret: accountData.proxy_secret || undefined,
|
|
881
|
-
socksType: (accountData.proxy_socksType === 4 || accountData.proxy_socksType === 5) ?
|
|
882
|
-
accountData.proxy_socksType as 4 | 5 : 5,
|
|
883
|
-
MTProxy: accountData.proxy_MTProxy === 1
|
|
884
|
-
};
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
return {
|
|
888
|
-
phone: accountData.phone,
|
|
889
|
-
session: accountData.session || undefined,
|
|
890
|
-
password: accountData.password || undefined,
|
|
891
|
-
proxy
|
|
892
|
-
};
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
/**
|
|
896
|
-
* Удалить учетные данные по номеру телефона
|
|
897
|
-
*/
|
|
898
|
-
async deleteCredential(phone: string): Promise<boolean> {
|
|
899
|
-
const account = await this.TgAccount.findByPhone(phone);
|
|
900
|
-
if (account) {
|
|
901
|
-
await this.TgAccount.delete(phone);
|
|
902
|
-
return true;
|
|
903
|
-
}
|
|
904
|
-
return false;
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
/**
|
|
908
|
-
* Получить все учетные данные
|
|
909
|
-
*/
|
|
910
|
-
async getAllCredentials(): Promise<TelegramCredentials[]> {
|
|
911
|
-
const accounts = await this.TgAccount.getAllWithProxy();
|
|
912
|
-
const credentials: TelegramCredentials[] = [];
|
|
913
|
-
|
|
914
|
-
for (const account of accounts) {
|
|
915
|
-
let proxy: TelegramClientProxy | undefined;
|
|
916
|
-
|
|
917
|
-
if (account.proxy_id && account.proxy_ip) {
|
|
918
|
-
proxy = {
|
|
919
|
-
ip: account.proxy_ip,
|
|
920
|
-
port: account.proxy_port,
|
|
921
|
-
username: account.proxy_username || undefined,
|
|
922
|
-
password: account.proxy_password || undefined,
|
|
923
|
-
secret: account.proxy_secret || undefined,
|
|
924
|
-
socksType: (account.proxy_socksType === 4 || account.proxy_socksType === 5) ?
|
|
925
|
-
account.proxy_socksType as 4 | 5 : 5,
|
|
926
|
-
MTProxy: account.proxy_MTProxy === 1
|
|
927
|
-
};
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
credentials.push({
|
|
931
|
-
phone: account.phone,
|
|
932
|
-
session: account.session || undefined,
|
|
933
|
-
password: account.password || undefined,
|
|
934
|
-
proxy
|
|
935
|
-
});
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
return credentials;
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
/**
|
|
942
|
-
* Проверить, существуют ли учетные данные
|
|
943
|
-
*/
|
|
944
|
-
async hasCredential(phone: string): Promise<boolean> {
|
|
945
|
-
return await this.TgAccount.exists(phone);
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
/**
|
|
949
|
-
* Обновить сессию для существующего аккаунта
|
|
950
|
-
*/
|
|
951
|
-
async updateSession(phone: string, session: string): Promise<boolean> {
|
|
952
|
-
const account = await this.TgAccount.findByPhone(phone);
|
|
953
|
-
if (account) {
|
|
954
|
-
await this.TgAccount.updateSession(phone, session);
|
|
955
|
-
await this.TgAccount.updateStatus(phone, this.TgAccountStatus.ACTIVE);
|
|
956
|
-
return true;
|
|
957
|
-
}
|
|
958
|
-
return false;
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
/**
|
|
962
|
-
* Получить количество учетных данных
|
|
963
|
-
*/
|
|
964
|
-
async getCredentialsCount(): Promise<number> {
|
|
965
|
-
const stats = await this.TgAccount.getStats();
|
|
966
|
-
return stats.total;
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
/**
|
|
970
|
-
* Очистить все учетные данные
|
|
971
|
-
*/
|
|
972
|
-
async clearAllCredentials(): Promise<void> {
|
|
973
|
-
const accounts = await this.TgAccount.getAll();
|
|
974
|
-
for (const account of accounts) {
|
|
975
|
-
await this.TgAccount.delete(account.id!);
|
|
976
|
-
}
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
/**
|
|
980
|
-
* Получить учетные данные по прокси
|
|
981
|
-
*/
|
|
982
|
-
async getCredentialsByProxy(proxyHost: string, proxyPort: number): Promise<TelegramCredentials[]> {
|
|
983
|
-
const proxy = await this.Proxy.findByIpPort(proxyHost, proxyPort);
|
|
984
|
-
if (!proxy) {
|
|
985
|
-
return [];
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
const accounts = await this.TgAccount.getAll();
|
|
989
|
-
const credentials: TelegramCredentials[] = [];
|
|
990
|
-
|
|
991
|
-
for (const account of accounts) {
|
|
992
|
-
if (account.proxy_id === proxy.id) {
|
|
993
|
-
const proxyData = this.convertProxyToTelegramFormat(proxy);
|
|
994
|
-
credentials.push({
|
|
995
|
-
phone: account.phone,
|
|
996
|
-
session: account.session || undefined,
|
|
997
|
-
password: account.password || undefined,
|
|
998
|
-
proxy: proxyData
|
|
999
|
-
});
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
|
-
|
|
1003
|
-
return credentials;
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
/**
|
|
1007
|
-
* Получить учетные данные без прокси
|
|
1008
|
-
*/
|
|
1009
|
-
async getCredentialsWithoutProxy(): Promise<TelegramCredentials[]> {
|
|
1010
|
-
const accounts = await this.TgAccount.getWithoutProxy();
|
|
1011
|
-
|
|
1012
|
-
return accounts.map(account => ({
|
|
1013
|
-
phone: account.phone,
|
|
1014
|
-
session: account.session || undefined,
|
|
1015
|
-
password: account.password || undefined
|
|
1016
|
-
}));
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
|
-
/**
|
|
1020
|
-
* Получить активные учетные данные
|
|
1021
|
-
*/
|
|
1022
|
-
async getActiveCredentials(): Promise<TelegramCredentials[]> {
|
|
1023
|
-
const accounts = await this.TgAccount.getByStatus(this.TgAccountStatus.ACTIVE);
|
|
1024
|
-
const credentials: TelegramCredentials[] = [];
|
|
1025
|
-
|
|
1026
|
-
for (const account of accounts) {
|
|
1027
|
-
let proxy: TelegramClientProxy | undefined;
|
|
1028
|
-
|
|
1029
|
-
if (account.proxy_id) {
|
|
1030
|
-
const proxyData = await this.Proxy.findById(account.proxy_id);
|
|
1031
|
-
if (proxyData && proxyData.status === this.ProxyStatus.ACTIVE) {
|
|
1032
|
-
proxy = this.convertProxyToTelegramFormat(proxyData);
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
credentials.push({
|
|
1037
|
-
phone: account.phone,
|
|
1038
|
-
session: account.session || undefined,
|
|
1039
|
-
password: account.password || undefined,
|
|
1040
|
-
proxy
|
|
1041
|
-
});
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
return credentials;
|
|
1045
|
-
}
|
|
1046
|
-
|
|
1047
|
-
/**
|
|
1048
|
-
* Получить случайные активные учетные данные
|
|
1049
|
-
*/
|
|
1050
|
-
async getRandomActiveCredential(): Promise<TelegramCredentials | null> {
|
|
1051
|
-
const account = await this.TgAccount.getRandomAvailable();
|
|
1052
|
-
if (!account) {
|
|
1053
|
-
return null;
|
|
1054
|
-
}
|
|
1055
|
-
|
|
1056
|
-
let proxy: TelegramClientProxy | undefined;
|
|
1057
|
-
if (account.proxy_id) {
|
|
1058
|
-
const proxyData = await this.Proxy.findById(account.proxy_id);
|
|
1059
|
-
if (proxyData && proxyData.status === this.ProxyStatus.ACTIVE) {
|
|
1060
|
-
proxy = this.convertProxyToTelegramFormat(proxyData);
|
|
1061
|
-
}
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1064
|
-
return {
|
|
1065
|
-
phone: account.phone,
|
|
1066
|
-
session: account.session || undefined,
|
|
1067
|
-
password: account.password || undefined,
|
|
1068
|
-
proxy
|
|
1069
|
-
};
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
/**
|
|
1073
|
-
* Обновить информацию о пользователе
|
|
1074
|
-
*/
|
|
1075
|
-
async updateUserInfo(phone: string, userInfo: {
|
|
1076
|
-
first_name?: string;
|
|
1077
|
-
last_name?: string;
|
|
1078
|
-
username?: string;
|
|
1079
|
-
user_id?: number;
|
|
1080
|
-
}): Promise<void> {
|
|
1081
|
-
await this.TgAccount.updateUserInfo(phone, userInfo);
|
|
1082
|
-
}
|
|
1083
|
-
|
|
1084
|
-
/**
|
|
1085
|
-
* Обновить статус аккаунта
|
|
1086
|
-
*/
|
|
1087
|
-
async updateAccountStatus(phone: string, status: any): Promise<void> {
|
|
1088
|
-
const account = await this.TgAccount.findByPhone(phone);
|
|
1089
|
-
if (account) {
|
|
1090
|
-
await this.TgAccount.updateStatus(account.id!, status);
|
|
1091
|
-
}
|
|
1092
|
-
}
|
|
1093
|
-
|
|
1094
|
-
/**
|
|
1095
|
-
* Отметить активность аккаунта
|
|
1096
|
-
*/
|
|
1097
|
-
async markActivity(phone: string): Promise<void> {
|
|
1098
|
-
const account = await this.TgAccount.findByPhone(phone);
|
|
1099
|
-
if (account) {
|
|
1100
|
-
await this.TgAccount.updateLastActivity(account.id!);
|
|
1101
|
-
}
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
|
-
/**
|
|
1105
|
-
* Получить статистику
|
|
1106
|
-
*/
|
|
1107
|
-
async getStats(): Promise<{
|
|
1108
|
-
accounts: any;
|
|
1109
|
-
proxies: any;
|
|
1110
|
-
}> {
|
|
1111
|
-
const accountStats = await this.TgAccount.getStats();
|
|
1112
|
-
const proxyStats = await this.Proxy.getStats();
|
|
1113
|
-
|
|
1114
|
-
return {
|
|
1115
|
-
accounts: accountStats,
|
|
1116
|
-
proxies: proxyStats
|
|
1117
|
-
};
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
|
-
/**
|
|
1121
|
-
* Добавить прокси в БД
|
|
1122
|
-
*/
|
|
1123
|
-
async addProxy(proxyData: any): Promise<number> {
|
|
1124
|
-
return await this.Proxy.create(proxyData);
|
|
1125
|
-
}
|
|
1126
|
-
|
|
1127
|
-
/**
|
|
1128
|
-
* Получить все прокси
|
|
1129
|
-
*/
|
|
1130
|
-
async getAllProxies(): Promise<any[]> {
|
|
1131
|
-
return await this.Proxy.getAll();
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
/**
|
|
1135
|
-
* Получить активные прокси
|
|
1136
|
-
*/
|
|
1137
|
-
async getActiveProxies(): Promise<any[]> {
|
|
1138
|
-
return await this.Proxy.getActive();
|
|
1139
|
-
}
|
|
1140
|
-
}
|
|
1
|
+
import { TelegramClient, Api } 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
|
+
import type { Database } from "bun:sqlite";
|
|
7
|
+
import { TgAccount, Proxy, TgAccountStatus, ProxyStatus } from '../models';
|
|
8
|
+
|
|
9
|
+
interface TelegramAccountRemoteInit {
|
|
10
|
+
appId: string;
|
|
11
|
+
appHash: string;
|
|
12
|
+
credetialsManager: TelegramManagerCredentialsDriver;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
type TelegramClientProxy = {
|
|
16
|
+
ip: string;
|
|
17
|
+
port: number;
|
|
18
|
+
username?: string;
|
|
19
|
+
password?: string;
|
|
20
|
+
secret?: string,
|
|
21
|
+
socksType?: 5 | 4;
|
|
22
|
+
MTProxy?: boolean;
|
|
23
|
+
} & ProxyInterface;
|
|
24
|
+
|
|
25
|
+
interface TelegramInitClient {
|
|
26
|
+
phone: string;
|
|
27
|
+
proxy?: any;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface TelegramCredentials {
|
|
31
|
+
phone: string;
|
|
32
|
+
proxy?: TelegramClientProxy;
|
|
33
|
+
password?: string;
|
|
34
|
+
session?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface TelegramCredentialsManagerInit {
|
|
38
|
+
pathFileStorage: string;
|
|
39
|
+
pathFileProxyList: string;
|
|
40
|
+
pathFileCounterOffset: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type TelegramManagerCredentialsDriver = TelegramManagerCredentials | TelegramManagerCredentialsDB;
|
|
44
|
+
|
|
45
|
+
export class TelegramManagerCredentials {
|
|
46
|
+
private requiredProxyForCredentials: boolean = false;
|
|
47
|
+
private credentials: TelegramCredentials[] = [];
|
|
48
|
+
private proxyList: string[] = [];
|
|
49
|
+
|
|
50
|
+
static init(options: TelegramCredentialsManagerInit) {
|
|
51
|
+
return new TelegramManagerCredentials(options);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
constructor(private options: TelegramCredentialsManagerInit) {
|
|
55
|
+
this.ensureStorageDirectory();
|
|
56
|
+
this.loadCredentials();
|
|
57
|
+
this.loadProxyList();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Ensure storage directory exists
|
|
62
|
+
*/
|
|
63
|
+
private ensureStorageDirectory(): void {
|
|
64
|
+
const storageDir = this.options.pathFileStorage.split("/").slice(0, -1).join("/");
|
|
65
|
+
if (!fs.existsSync(storageDir)) {
|
|
66
|
+
fs.mkdirSync(storageDir, { recursive: true });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Load credentials from JSON file
|
|
72
|
+
*/
|
|
73
|
+
private loadCredentials(): void {
|
|
74
|
+
try {
|
|
75
|
+
if (fs.existsSync(this.options.pathFileStorage)) {
|
|
76
|
+
const data = fs.readFileSync(this.options.pathFileStorage, "utf8");
|
|
77
|
+
this.credentials = JSON.parse(data) || [];
|
|
78
|
+
} else {
|
|
79
|
+
this.credentials = [];
|
|
80
|
+
this.saveCredentials();
|
|
81
|
+
}
|
|
82
|
+
} catch (error) {
|
|
83
|
+
console.error("Error loading credentials:", error);
|
|
84
|
+
this.credentials = [];
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Загрузить список прокси из файла
|
|
90
|
+
*/
|
|
91
|
+
private loadProxyList(): void {
|
|
92
|
+
try {
|
|
93
|
+
if (fs.existsSync(this.options.pathFileProxyList)) {
|
|
94
|
+
const data = fs.readFileSync(this.options.pathFileProxyList, "utf8");
|
|
95
|
+
this.proxyList = data.split('\n').filter(line => line.trim() !== '');
|
|
96
|
+
} else {
|
|
97
|
+
this.proxyList = [];
|
|
98
|
+
}
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.error("Ошибка загрузки списка прокси:", error);
|
|
101
|
+
this.proxyList = [];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Получить текущий offset из файла счетчика
|
|
107
|
+
*/
|
|
108
|
+
private getCurrentOffset(): number {
|
|
109
|
+
try {
|
|
110
|
+
if (fs.existsSync(this.options.pathFileCounterOffset)) {
|
|
111
|
+
const data = fs.readFileSync(this.options.pathFileCounterOffset, "utf8");
|
|
112
|
+
return parseInt(data.trim()) || 0;
|
|
113
|
+
}
|
|
114
|
+
return 0;
|
|
115
|
+
} catch (error) {
|
|
116
|
+
console.error("Ошибка чтения offset:", error);
|
|
117
|
+
return 0;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Сохранить новый offset в файл счетчика
|
|
123
|
+
*/
|
|
124
|
+
private saveOffset(offset: number): void {
|
|
125
|
+
try {
|
|
126
|
+
fs.writeFileSync(this.options.pathFileCounterOffset, offset.toString(), "utf8");
|
|
127
|
+
} catch (error) {
|
|
128
|
+
console.error("Ошибка сохранения offset:", error);
|
|
129
|
+
throw new Error("Не удалось сохранить offset");
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Парсинг строки прокси в формат TelegramClientProxy
|
|
135
|
+
* Поддерживаемые форматы:
|
|
136
|
+
* - socks4://username:password@host:port
|
|
137
|
+
* - socks5://username:password@host:port
|
|
138
|
+
* - http://username:password@host:port (будет использоваться как SOCKS5)
|
|
139
|
+
*/
|
|
140
|
+
private parseProxyString(proxyString: string): TelegramClientProxy {
|
|
141
|
+
// Проверяем SOCKS4 формат
|
|
142
|
+
let match = proxyString.match(/^socks4:\/\/([^:]+):([^@]+)@([^:]+):(\d+)$/);
|
|
143
|
+
if (match) {
|
|
144
|
+
const [, username, password, ip, port] = match;
|
|
145
|
+
return {
|
|
146
|
+
ip,
|
|
147
|
+
port: parseInt(port),
|
|
148
|
+
username,
|
|
149
|
+
password,
|
|
150
|
+
socksType: 4
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Проверяем SOCKS5 формат
|
|
155
|
+
match = proxyString.match(/^socks5:\/\/([^:]+):([^@]+)@([^:]+):(\d+)$/);
|
|
156
|
+
if (match) {
|
|
157
|
+
const [, username, password, ip, port] = match;
|
|
158
|
+
return {
|
|
159
|
+
ip,
|
|
160
|
+
port: parseInt(port),
|
|
161
|
+
username,
|
|
162
|
+
password,
|
|
163
|
+
socksType: 5
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Проверяем HTTP формат (используем как SOCKS5)
|
|
168
|
+
match = proxyString.match(/^http:\/\/([^:]+):([^@]+)@([^:]+):(\d+)$/);
|
|
169
|
+
if (match) {
|
|
170
|
+
const [, username, password, ip, port] = match;
|
|
171
|
+
return {
|
|
172
|
+
ip,
|
|
173
|
+
port: parseInt(port),
|
|
174
|
+
username,
|
|
175
|
+
password,
|
|
176
|
+
socksType: 5 // HTTP прокси используем как SOCKS5
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
throw new Error(`Неверный формат прокси: ${proxyString}. Поддерживаемые форматы: socks4://, socks5://, http://`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Получить следующий прокси из списка
|
|
185
|
+
*/
|
|
186
|
+
private getNextProxy(): TelegramClientProxy {
|
|
187
|
+
if (this.proxyList.length === 0) {
|
|
188
|
+
throw new Error("Список прокси пуст");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const currentOffset = this.getCurrentOffset();
|
|
192
|
+
|
|
193
|
+
if (currentOffset >= this.proxyList.length) {
|
|
194
|
+
throw new Error("Прокси закончились");
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const proxyString = this.proxyList[currentOffset];
|
|
198
|
+
const proxy = this.parseProxyString(proxyString);
|
|
199
|
+
|
|
200
|
+
// Увеличиваем offset для следующего использования
|
|
201
|
+
this.saveOffset(currentOffset + 1);
|
|
202
|
+
|
|
203
|
+
return proxy;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Save credentials to JSON file
|
|
208
|
+
*/
|
|
209
|
+
private saveCredentials(): void {
|
|
210
|
+
try {
|
|
211
|
+
fs.writeFileSync(
|
|
212
|
+
this.options.pathFileStorage,
|
|
213
|
+
JSON.stringify(this.credentials, null, 2),
|
|
214
|
+
"utf8"
|
|
215
|
+
);
|
|
216
|
+
} catch (error) {
|
|
217
|
+
console.error("Error saving credentials:", error);
|
|
218
|
+
throw new Error("Failed to save credentials");
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Add or update credential
|
|
224
|
+
*/
|
|
225
|
+
addCredential(credential: TelegramCredentials): void {
|
|
226
|
+
// replace +
|
|
227
|
+
if (credential.phone.includes('+')) {
|
|
228
|
+
credential.phone = credential.phone.replace('+', '');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Attach proxy to credential
|
|
232
|
+
if (this.requiredProxyForCredentials && !credential.proxy) {
|
|
233
|
+
try {
|
|
234
|
+
credential.proxy = this.getNextProxy();
|
|
235
|
+
} catch (error) {
|
|
236
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
237
|
+
throw new Error(`Не удалось назначить прокси: ${errorMessage}`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const existingIndex = this.credentials.findIndex((c) => c != null && c.phone === credential.phone);
|
|
242
|
+
|
|
243
|
+
if (existingIndex !== -1) {
|
|
244
|
+
// Update existing credential
|
|
245
|
+
this.credentials[existingIndex] = { ...this.credentials[existingIndex], ...credential };
|
|
246
|
+
} else {
|
|
247
|
+
// Add new credential
|
|
248
|
+
this.credentials.push(credential);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
this.credentials = this.credentials.filter((c) => c != null);
|
|
252
|
+
|
|
253
|
+
this.saveCredentials();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Get credential by phone number
|
|
258
|
+
*/
|
|
259
|
+
getCredential(phone: string): TelegramCredentials | null {
|
|
260
|
+
return this.credentials.find((c) => c != null && c.phone === phone) || null;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Delete credential by phone number
|
|
265
|
+
*/
|
|
266
|
+
deleteCredential(phone: string): boolean {
|
|
267
|
+
const initialLength = this.credentials.length;
|
|
268
|
+
this.credentials = this.credentials.filter((c) => c.phone !== phone);
|
|
269
|
+
|
|
270
|
+
if (this.credentials.length < initialLength) {
|
|
271
|
+
this.saveCredentials();
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Get all credentials
|
|
280
|
+
*/
|
|
281
|
+
getAllCredentials(): TelegramCredentials[] {
|
|
282
|
+
return [...this.credentials];
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Check if credential exists
|
|
287
|
+
*/
|
|
288
|
+
hasCredential(phone: string): boolean {
|
|
289
|
+
return this.credentials.some((c) => c.phone === phone);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Update session for existing credential
|
|
294
|
+
*/
|
|
295
|
+
updateSession(phone: string, session: string): boolean {
|
|
296
|
+
const credential = this.credentials.find((c) => c.phone === phone);
|
|
297
|
+
if (credential) {
|
|
298
|
+
credential.session = session;
|
|
299
|
+
this.saveCredentials();
|
|
300
|
+
return true;
|
|
301
|
+
}
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Get credentials count
|
|
307
|
+
*/
|
|
308
|
+
getCredentialsCount(): number {
|
|
309
|
+
return this.credentials.length;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Clear all credentials
|
|
314
|
+
*/
|
|
315
|
+
clearAllCredentials(): void {
|
|
316
|
+
this.credentials = [];
|
|
317
|
+
this.saveCredentials();
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Get credentials by proxy configuration
|
|
322
|
+
*/
|
|
323
|
+
getCredentialsByProxy(proxyHost: string, proxyPort: number): TelegramCredentials[] {
|
|
324
|
+
return this.credentials.filter(
|
|
325
|
+
(c) => c.proxy && c.proxy.ip === proxyHost && c.proxy.port === proxyPort
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Get credentials without proxy
|
|
331
|
+
*/
|
|
332
|
+
getCredentialsWithoutProxy(): TelegramCredentials[] {
|
|
333
|
+
return this.credentials.filter((c) => !c.proxy);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export class TelegramAccountRemote {
|
|
338
|
+
private initOptions: TelegramAccountRemoteInit;
|
|
339
|
+
private tgClient!: TelegramClient; // Используем definite assignment assertion
|
|
340
|
+
private credentialsManager: TelegramManagerCredentialsDriver;
|
|
341
|
+
|
|
342
|
+
static init(initOptions: TelegramAccountRemoteInit) {
|
|
343
|
+
return new TelegramAccountRemote(initOptions);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
constructor(initOptions: TelegramAccountRemoteInit) {
|
|
347
|
+
this.initOptions = initOptions;
|
|
348
|
+
this.credentialsManager = initOptions.credetialsManager;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
async attemptRestoreSession(credentials: TelegramCredentials): Promise<boolean> {
|
|
352
|
+
const { phone, proxy, session, password } = credentials;
|
|
353
|
+
const { appId, appHash } = this.initOptions;
|
|
354
|
+
|
|
355
|
+
if (session) {
|
|
356
|
+
const clientParams: TelegramClientParams = {connectionRetries: 5};
|
|
357
|
+
|
|
358
|
+
if (credentials.proxy) {
|
|
359
|
+
clientParams.proxy = credentials.proxy;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
this.tgClient = new TelegramClient(new StringSession(session), Number(appId), appHash, clientParams);
|
|
363
|
+
|
|
364
|
+
return await this.tgClient.connect();
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return false;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
async login(
|
|
371
|
+
credentials: TelegramCredentials,
|
|
372
|
+
cbRequestCode: CallableFunction,
|
|
373
|
+
cbRequestPassword: CallableFunction,
|
|
374
|
+
cbError: CallableFunction,
|
|
375
|
+
): Promise<void> {
|
|
376
|
+
const { phone, proxy, session, password } = credentials;
|
|
377
|
+
const { appId, appHash } = this.initOptions;
|
|
378
|
+
|
|
379
|
+
if (session) {
|
|
380
|
+
throw new Error("Account is already registered");
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
try {
|
|
384
|
+
this.tgClient = new TelegramClient(new StringSession(""), Number(appId), appHash, {
|
|
385
|
+
connectionRetries: 5,
|
|
386
|
+
proxy: credentials.proxy,
|
|
387
|
+
timeout: 10000 * 60 * 10,
|
|
388
|
+
});
|
|
389
|
+
console.log('Login cresentials:', credentials)
|
|
390
|
+
// @ts-ignore
|
|
391
|
+
await this.tgClient.start({
|
|
392
|
+
phoneNumber: async () => phone,
|
|
393
|
+
password: async () => {
|
|
394
|
+
if (!password) {
|
|
395
|
+
return await cbRequestPassword();
|
|
396
|
+
}
|
|
397
|
+
return password;
|
|
398
|
+
},
|
|
399
|
+
phoneCode: async () => {
|
|
400
|
+
return await cbRequestCode();
|
|
401
|
+
},
|
|
402
|
+
onError: (err: Error) => {
|
|
403
|
+
cbError('Error for wait authentiction', err);
|
|
404
|
+
},
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
const session = this.tgClient.session.save() as unknown as string;
|
|
408
|
+
|
|
409
|
+
this.credentialsManager.addCredential({
|
|
410
|
+
phone,
|
|
411
|
+
proxy,
|
|
412
|
+
session,
|
|
413
|
+
password,
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
console.log("Регистрация успешна!");
|
|
417
|
+
console.log("Сессия:\n", session);
|
|
418
|
+
|
|
419
|
+
} catch (err) {
|
|
420
|
+
throw new Error("Failed to register account", { cause: err });
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
async disconnect(): Promise<void> {
|
|
425
|
+
await this.tgClient.disconnect();
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
async disconnectWhenConnect() {
|
|
429
|
+
await this.tgClient.disconnect();
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Отправляет команду /start боту
|
|
434
|
+
* @param botUsername username бота (например: 'my_bot' или '@my_bot')
|
|
435
|
+
* @returns true если команда успешно отправлена
|
|
436
|
+
*/
|
|
437
|
+
async sendStartCommand(botUsername: string): Promise<boolean> {
|
|
438
|
+
if (!this.tgClient) {
|
|
439
|
+
throw new Error("Client not initialized. Call login or attemptRestoreSession first");
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
try {
|
|
443
|
+
// Убираем @ из username если он есть
|
|
444
|
+
const normalizedUsername = botUsername.startsWith('@') ? botUsername.substring(1) : botUsername;
|
|
445
|
+
|
|
446
|
+
// Ищем бота по username
|
|
447
|
+
const result = await this.tgClient.sendMessage(normalizedUsername, {
|
|
448
|
+
message: '/start'
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
return result ? true : false;
|
|
452
|
+
} catch (error) {
|
|
453
|
+
console.error('Error sending /start command:', error);
|
|
454
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
455
|
+
throw new Error(`Failed to send /start command to @${botUsername}: ${errorMessage}`);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Получает ID чата с ботом
|
|
461
|
+
* @param botUsername username бота (например: 'my_bot' или '@my_bot')
|
|
462
|
+
* @returns ID чата с ботом
|
|
463
|
+
*/
|
|
464
|
+
async getBotChatId(botUsername: string): Promise<number> {
|
|
465
|
+
if (!this.tgClient) {
|
|
466
|
+
throw new Error("Client not initialized. Call login or attemptRestoreSession first");
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
try {
|
|
470
|
+
// Убираем @ из username если он есть
|
|
471
|
+
const normalizedUsername = botUsername.startsWith('@') ? botUsername.substring(1) : botUsername;
|
|
472
|
+
|
|
473
|
+
// Получаем информацию о диалоге с ботом
|
|
474
|
+
const dialog = await this.tgClient.getDialogs({
|
|
475
|
+
limit: 1
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
if (!dialog || dialog.length === 0 || !dialog[0] || !dialog[0].id) {
|
|
479
|
+
throw new Error(`Chat with bot @${normalizedUsername} not found`);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
return dialog[0].id!.toJSNumber();
|
|
483
|
+
} catch (error) {
|
|
484
|
+
console.error('Error getting bot chat ID:', error);
|
|
485
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
486
|
+
throw new Error(`Failed to get chat ID for @${botUsername}: ${errorMessage}`);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Отправляет жалобу на бота
|
|
492
|
+
* @param botUsername username бота (например: 'my_bot' или '@my_bot')
|
|
493
|
+
* @param reason причина жалобы: 'spam' | 'violence' | 'pornography' | 'custompromoting' | 'other'
|
|
494
|
+
* @returns true если жалоба успешно отправлена
|
|
495
|
+
*/
|
|
496
|
+
async reportBot(
|
|
497
|
+
botUsername: string,
|
|
498
|
+
reason: 'spam' | 'violence' | 'pornography' | 'custompromoting' | 'other' = 'spam'
|
|
499
|
+
): Promise<boolean> {
|
|
500
|
+
if (!this.tgClient) {
|
|
501
|
+
throw new Error("Client not initialized. Call login or attemptRestoreSession first");
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
try {
|
|
505
|
+
const chatId = await this.getBotChatId(botUsername);
|
|
506
|
+
|
|
507
|
+
// Получаем сообщения от бота для репорта
|
|
508
|
+
const messages = await this.tgClient.getMessages(chatId, { limit: 1 });
|
|
509
|
+
if (!messages || messages.length === 0) {
|
|
510
|
+
throw new Error('No messages found to report');
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Формируем причину жалобы
|
|
514
|
+
let reportReason = reason;
|
|
515
|
+
if (reason === 'custompromoting') {
|
|
516
|
+
reportReason = 'spam';
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// Отправляем жалобу на последнее сообщение
|
|
520
|
+
await this.tgClient.sendMessage(chatId, {
|
|
521
|
+
message: `/report ${reportReason}`,
|
|
522
|
+
replyTo: messages[0].id
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
return true;
|
|
526
|
+
} catch (error) {
|
|
527
|
+
console.error('Error reporting bot:', error);
|
|
528
|
+
if (error instanceof Error) {
|
|
529
|
+
throw new Error(`Failed to report @${botUsername}: ${error.message}`);
|
|
530
|
+
}
|
|
531
|
+
throw new Error(`Failed to report @${botUsername}: Unknown error`);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Проверяет, зарегистрирован ли номер телефона в Telegram
|
|
537
|
+
* @param phoneNumber номер телефона в международном формате (например: '+380123456789')
|
|
538
|
+
* @returns true если номер зарегистрирован в Telegram, false если нет
|
|
539
|
+
*/
|
|
540
|
+
async isPhoneRegistered(phoneNumber: string): Promise<boolean> {
|
|
541
|
+
if (!this.tgClient) {
|
|
542
|
+
throw new Error("Client not initialized. Call login or attemptRestoreSession first");
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
try {
|
|
546
|
+
// Попытаемся найти пользователя, отправив сообщение самому себе с информацией о номере
|
|
547
|
+
// Это безопасный способ проверки без отправки реальных сообщений
|
|
548
|
+
const me = await this.tgClient.getMe();
|
|
549
|
+
|
|
550
|
+
// Используем поиск по username если номер содержит буквы, иначе считаем что это номер
|
|
551
|
+
if (phoneNumber.includes('@')) {
|
|
552
|
+
try {
|
|
553
|
+
const entity = await this.tgClient.getEntity(phoneNumber);
|
|
554
|
+
return entity ? true : false;
|
|
555
|
+
} catch {
|
|
556
|
+
return false;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// Для номеров телефонов возвращаем true по умолчанию
|
|
561
|
+
// В реальном приложении здесь должен быть более сложный API вызов
|
|
562
|
+
console.log(`Проверка номера ${phoneNumber} - предполагаем что зарегистрирован`);
|
|
563
|
+
return true;
|
|
564
|
+
} catch (error) {
|
|
565
|
+
console.error('Error checking phone registration:', error);
|
|
566
|
+
return false;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Добавляет контакт в адресную книгу Telegram
|
|
572
|
+
* @param phoneNumber номер телефона в международном формате
|
|
573
|
+
* @param firstName имя контакта
|
|
574
|
+
* @param lastName фамилия контакта (необязательно)
|
|
575
|
+
* @returns true если контакт успешно добавлен
|
|
576
|
+
*/
|
|
577
|
+
async addContact(phoneNumber: string, firstName: string, lastName?: string): Promise<boolean> {
|
|
578
|
+
if (!this.tgClient) {
|
|
579
|
+
throw new Error("Client not initialized. Call login or attemptRestoreSession first");
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
try {
|
|
583
|
+
// Импортируем API для работы с контактами
|
|
584
|
+
const bigInteger = (await import('big-integer')).default;
|
|
585
|
+
|
|
586
|
+
// Нормализуем номер телефона (убираем все символы кроме цифр и +)
|
|
587
|
+
const normalizedPhone = phoneNumber.replace(/[^\d+]/g, '');
|
|
588
|
+
|
|
589
|
+
// Создаем контакт для импорта
|
|
590
|
+
const contact = new Api.InputPhoneContact({
|
|
591
|
+
clientId: bigInteger(Math.floor(Math.random() * 1000000000)), // Генерируем случайный ID
|
|
592
|
+
phone: normalizedPhone.replace(/^\+/, ''), // Убираем + для API
|
|
593
|
+
firstName: firstName,
|
|
594
|
+
lastName: lastName || ''
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
console.log(`🔍 Попытка добавить контакт: ${firstName} ${lastName || ''} (${phoneNumber})`);
|
|
598
|
+
|
|
599
|
+
// Импортируем контакт через API
|
|
600
|
+
const result = await this.tgClient.invoke(
|
|
601
|
+
new Api.contacts.ImportContacts({
|
|
602
|
+
contacts: [contact]
|
|
603
|
+
})
|
|
604
|
+
);
|
|
605
|
+
|
|
606
|
+
// Проверяем результат импорта
|
|
607
|
+
if (result.imported && result.imported.length > 0) {
|
|
608
|
+
console.log(`✅ Контакт ${firstName} ${lastName || ''} (${phoneNumber}) успешно добавлен`);
|
|
609
|
+
|
|
610
|
+
// Если есть информация о пользователе
|
|
611
|
+
if (result.users && result.users.length > 0) {
|
|
612
|
+
const user = result.users[0];
|
|
613
|
+
const username = (user as any).username;
|
|
614
|
+
console.log(`📱 Найден пользователь Telegram: @${username || 'без username'}`);
|
|
615
|
+
console.log(`🆔 ID пользователя: ${user.id}`);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
return true;
|
|
619
|
+
} else if (result.retryContacts && result.retryContacts.length > 0) {
|
|
620
|
+
console.log(`⚠️ Контакт ${phoneNumber} требует повторной попытки`);
|
|
621
|
+
throw new Error(`Contact ${phoneNumber} requires retry`);
|
|
622
|
+
} else {
|
|
623
|
+
// Проверяем, найден ли пользователь в результате (контакт уже существует)
|
|
624
|
+
if (result.users && result.users.length > 0) {
|
|
625
|
+
const user = result.users[0];
|
|
626
|
+
const username = (user as any).username;
|
|
627
|
+
console.log(`� Пользователь уже существует в контактах: @${username || 'без username'}`);
|
|
628
|
+
return true;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
console.log(`ℹ️ Контакт ${phoneNumber} не найден в Telegram или не удалось добавить`);
|
|
632
|
+
throw new Error(`Contact ${phoneNumber} not found or could not be added`);
|
|
633
|
+
}
|
|
634
|
+
} catch (error) {
|
|
635
|
+
console.error('Error adding contact:', error);
|
|
636
|
+
|
|
637
|
+
// Если пользователь не найден, это не критическая ошибка
|
|
638
|
+
if (error instanceof Error && (
|
|
639
|
+
error.message.includes('USER_NOT_FOUND') ||
|
|
640
|
+
error.message.includes('PHONE_NOT_OCCUPIED') ||
|
|
641
|
+
error.message.includes('USERNAME_NOT_OCCUPIED')
|
|
642
|
+
)) {
|
|
643
|
+
console.log(`ℹ️ Пользователь с номером ${phoneNumber} не зарегистрирован в Telegram`);
|
|
644
|
+
throw new Error(`User with phone ${phoneNumber} is not registered in Telegram`);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
throw error;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Получает информацию о пользователе по номеру телефона или username
|
|
653
|
+
* @param identifier номер телефона или username
|
|
654
|
+
* @returns информация о пользователе или null если не найден
|
|
655
|
+
*/
|
|
656
|
+
async getUserByPhone(identifier: string): Promise<any | null> {
|
|
657
|
+
if (!this.tgClient) {
|
|
658
|
+
throw new Error("Client not initialized. Call login or attemptRestoreSession first");
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
try {
|
|
662
|
+
// Пытаемся получить информацию о пользователе
|
|
663
|
+
let entity;
|
|
664
|
+
|
|
665
|
+
if (identifier.startsWith('@') || !identifier.startsWith('+')) {
|
|
666
|
+
// Если это username, пытаемся найти по username
|
|
667
|
+
entity = await this.tgClient.getEntity(identifier);
|
|
668
|
+
} else {
|
|
669
|
+
// Если это номер телефона, логируем попытку поиска
|
|
670
|
+
console.log(`Поиск пользователя по номеру: ${identifier}`);
|
|
671
|
+
return null; // В упрощенной версии возвращаем null для номеров
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
if (entity) {
|
|
675
|
+
return {
|
|
676
|
+
id: entity.id?.toString() || '',
|
|
677
|
+
firstName: (entity as any).firstName || '',
|
|
678
|
+
lastName: (entity as any).lastName || '',
|
|
679
|
+
username: (entity as any).username || '',
|
|
680
|
+
phone: identifier.startsWith('+') ? identifier : '',
|
|
681
|
+
isBot: (entity as any).bot || false,
|
|
682
|
+
isVerified: (entity as any).verified || false,
|
|
683
|
+
isPremium: (entity as any).premium || false
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
return null;
|
|
688
|
+
} catch (error) {
|
|
689
|
+
console.error('Error getting user info:', error);
|
|
690
|
+
return null;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/**
|
|
695
|
+
* Массовая проверка номеров телефонов на регистрацию в Telegram
|
|
696
|
+
* @param phoneNumbers массив номеров телефонов
|
|
697
|
+
* @returns объект с результатами проверки для каждого номера
|
|
698
|
+
*/
|
|
699
|
+
async checkMultiplePhones(phoneNumbers: string[]): Promise<{[phone: string]: boolean}> {
|
|
700
|
+
if (!this.tgClient) {
|
|
701
|
+
throw new Error("Client not initialized. Call login or attemptRestoreSession first");
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
const results: {[phone: string]: boolean} = {};
|
|
705
|
+
|
|
706
|
+
// Проверяем каждый номер по очереди
|
|
707
|
+
for (const phone of phoneNumbers) {
|
|
708
|
+
try {
|
|
709
|
+
results[phone] = await this.isPhoneRegistered(phone);
|
|
710
|
+
|
|
711
|
+
// Небольшая задержка между запросами для избежания rate limit
|
|
712
|
+
await new Promise(resolve => setTimeout(resolve, 200));
|
|
713
|
+
} catch (error) {
|
|
714
|
+
console.error(`Error checking phone ${phone}:`, error);
|
|
715
|
+
results[phone] = false;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
return results;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* Отправляет сообщение пользователю по ID или username
|
|
724
|
+
* @param target ID пользователя или username
|
|
725
|
+
* @param message текст сообщения
|
|
726
|
+
* @returns true если сообщение отправлено успешно
|
|
727
|
+
*/
|
|
728
|
+
async sendMessageToUser(target: string, message: string): Promise<boolean> {
|
|
729
|
+
if (!this.tgClient) {
|
|
730
|
+
throw new Error("Client not initialized. Call login or attemptRestoreSession first");
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
try {
|
|
734
|
+
// Отправляем сообщение
|
|
735
|
+
const result = await this.tgClient.sendMessage(target, {
|
|
736
|
+
message: message
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
console.log(`✅ Сообщение отправлено пользователю ${target}`);
|
|
740
|
+
return result ? true : false;
|
|
741
|
+
} catch (error) {
|
|
742
|
+
console.error('Error sending message:', error);
|
|
743
|
+
throw new Error(`Failed to send message to ${target}: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/**
|
|
749
|
+
* Менеджер учетных данных Telegram с поддержкой базы данных SQLite
|
|
750
|
+
*/
|
|
751
|
+
export class TelegramManagerCredentialsDB {
|
|
752
|
+
private database: Database;
|
|
753
|
+
private requiredProxyForCredentials: boolean = false;
|
|
754
|
+
|
|
755
|
+
// Локальные импорты для избежания конфликтов путей
|
|
756
|
+
private TgAccount: any;
|
|
757
|
+
private Proxy: any;
|
|
758
|
+
private TgAccountStatus: any;
|
|
759
|
+
private ProxyStatus: any;
|
|
760
|
+
|
|
761
|
+
constructor(database: Database) {
|
|
762
|
+
this.database = database;
|
|
763
|
+
|
|
764
|
+
// Динамический импорт моделей
|
|
765
|
+
this.initModels();
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
private async initModels() {
|
|
769
|
+
this.TgAccount = TgAccount;
|
|
770
|
+
this.Proxy = Proxy;
|
|
771
|
+
this.TgAccountStatus = TgAccountStatus;
|
|
772
|
+
this.ProxyStatus = ProxyStatus;
|
|
773
|
+
|
|
774
|
+
// Устанавливаем базу данных для моделей
|
|
775
|
+
this.TgAccount.setDatabase(this.database);
|
|
776
|
+
this.Proxy.setDatabase(this.database);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
/**
|
|
780
|
+
* Включить/выключить обязательное использование прокси для учетных данных
|
|
781
|
+
*/
|
|
782
|
+
setRequiredProxyForCredentials(required: boolean): void {
|
|
783
|
+
this.requiredProxyForCredentials = required;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
/**
|
|
787
|
+
* Преобразовать данные прокси из БД в формат TelegramClientProxy
|
|
788
|
+
*/
|
|
789
|
+
private convertProxyToTelegramFormat(proxyData: any): TelegramClientProxy {
|
|
790
|
+
return {
|
|
791
|
+
ip: proxyData.ip,
|
|
792
|
+
port: proxyData.port,
|
|
793
|
+
username: proxyData.username || undefined,
|
|
794
|
+
password: proxyData.password || undefined,
|
|
795
|
+
secret: proxyData.secret || undefined,
|
|
796
|
+
socksType: (proxyData.socksType === 4 || proxyData.socksType === 5) ? proxyData.socksType as 4 | 5 : 5,
|
|
797
|
+
MTProxy: proxyData.MTProxy === 1
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
/**
|
|
802
|
+
* Получить следующий доступный прокси из БД
|
|
803
|
+
*/
|
|
804
|
+
private async getNextProxy(): Promise<TelegramClientProxy> {
|
|
805
|
+
const proxy = await this.Proxy.getRandomActive();
|
|
806
|
+
if (!proxy) {
|
|
807
|
+
throw new Error("Нет доступных прокси в базе данных");
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
return this.convertProxyToTelegramFormat(proxy);
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
/**
|
|
814
|
+
* Добавить или обновить учетные данные
|
|
815
|
+
*/
|
|
816
|
+
async addCredential(credential: TelegramCredentials): Promise<void> {
|
|
817
|
+
let proxyId: number | null = null;
|
|
818
|
+
|
|
819
|
+
// Назначить прокси если требуется
|
|
820
|
+
if (this.requiredProxyForCredentials && !credential.proxy) {
|
|
821
|
+
try {
|
|
822
|
+
const proxy = await this.getNextProxy();
|
|
823
|
+
credential.proxy = proxy;
|
|
824
|
+
|
|
825
|
+
// Найти ID прокси в БД
|
|
826
|
+
const proxyData = await this.Proxy.findByIpPort(proxy.ip, proxy.port);
|
|
827
|
+
if (proxyData) {
|
|
828
|
+
proxyId = proxyData.id!;
|
|
829
|
+
}
|
|
830
|
+
} catch (error) {
|
|
831
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
832
|
+
throw new Error(`Не удалось назначить прокси: ${errorMessage}`);
|
|
833
|
+
}
|
|
834
|
+
} else if (credential.proxy) {
|
|
835
|
+
// Найти существующий прокси или создать новый
|
|
836
|
+
let proxyData = await this.Proxy.findByIpPort(credential.proxy.ip, credential.proxy.port);
|
|
837
|
+
if (!proxyData) {
|
|
838
|
+
// Создать новый прокси в БД
|
|
839
|
+
proxyId = await this.Proxy.create({
|
|
840
|
+
ip: credential.proxy.ip,
|
|
841
|
+
port: credential.proxy.port,
|
|
842
|
+
username: credential.proxy.username || null,
|
|
843
|
+
password: credential.proxy.password || null,
|
|
844
|
+
secret: credential.proxy.secret || null,
|
|
845
|
+
socksType: credential.proxy.socksType || null,
|
|
846
|
+
MTProxy: credential.proxy.MTProxy ? 1 : 0,
|
|
847
|
+
status: this.ProxyStatus.ACTIVE
|
|
848
|
+
});
|
|
849
|
+
} else {
|
|
850
|
+
proxyId = proxyData.id!;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
// Используем upsert для вставки или обновления
|
|
855
|
+
await this.TgAccount.upsert({
|
|
856
|
+
phone: credential.phone.replace('+', ''),
|
|
857
|
+
session: credential.session || null,
|
|
858
|
+
password: credential.password || null,
|
|
859
|
+
proxy_id: proxyId,
|
|
860
|
+
status: credential.session ? this.TgAccountStatus.ACTIVE : this.TgAccountStatus.INACTIVE
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
/**
|
|
865
|
+
* Получить учетные данные по номеру телефона
|
|
866
|
+
*/
|
|
867
|
+
async getCredential(phone: string): Promise<TelegramCredentials | null> {
|
|
868
|
+
const accountData = await this.TgAccount.findWithProxy(phone);
|
|
869
|
+
if (!accountData) {
|
|
870
|
+
return null;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
let proxy: TelegramClientProxy | undefined;
|
|
874
|
+
if (accountData.proxy_id && accountData.proxy_ip) {
|
|
875
|
+
proxy = {
|
|
876
|
+
ip: accountData.proxy_ip,
|
|
877
|
+
port: accountData.proxy_port,
|
|
878
|
+
username: accountData.proxy_username || undefined,
|
|
879
|
+
password: accountData.proxy_password || undefined,
|
|
880
|
+
secret: accountData.proxy_secret || undefined,
|
|
881
|
+
socksType: (accountData.proxy_socksType === 4 || accountData.proxy_socksType === 5) ?
|
|
882
|
+
accountData.proxy_socksType as 4 | 5 : 5,
|
|
883
|
+
MTProxy: accountData.proxy_MTProxy === 1
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
return {
|
|
888
|
+
phone: accountData.phone,
|
|
889
|
+
session: accountData.session || undefined,
|
|
890
|
+
password: accountData.password || undefined,
|
|
891
|
+
proxy
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
/**
|
|
896
|
+
* Удалить учетные данные по номеру телефона
|
|
897
|
+
*/
|
|
898
|
+
async deleteCredential(phone: string): Promise<boolean> {
|
|
899
|
+
const account = await this.TgAccount.findByPhone(phone);
|
|
900
|
+
if (account) {
|
|
901
|
+
await this.TgAccount.delete(phone);
|
|
902
|
+
return true;
|
|
903
|
+
}
|
|
904
|
+
return false;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
/**
|
|
908
|
+
* Получить все учетные данные
|
|
909
|
+
*/
|
|
910
|
+
async getAllCredentials(): Promise<TelegramCredentials[]> {
|
|
911
|
+
const accounts = await this.TgAccount.getAllWithProxy();
|
|
912
|
+
const credentials: TelegramCredentials[] = [];
|
|
913
|
+
|
|
914
|
+
for (const account of accounts) {
|
|
915
|
+
let proxy: TelegramClientProxy | undefined;
|
|
916
|
+
|
|
917
|
+
if (account.proxy_id && account.proxy_ip) {
|
|
918
|
+
proxy = {
|
|
919
|
+
ip: account.proxy_ip,
|
|
920
|
+
port: account.proxy_port,
|
|
921
|
+
username: account.proxy_username || undefined,
|
|
922
|
+
password: account.proxy_password || undefined,
|
|
923
|
+
secret: account.proxy_secret || undefined,
|
|
924
|
+
socksType: (account.proxy_socksType === 4 || account.proxy_socksType === 5) ?
|
|
925
|
+
account.proxy_socksType as 4 | 5 : 5,
|
|
926
|
+
MTProxy: account.proxy_MTProxy === 1
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
credentials.push({
|
|
931
|
+
phone: account.phone,
|
|
932
|
+
session: account.session || undefined,
|
|
933
|
+
password: account.password || undefined,
|
|
934
|
+
proxy
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
return credentials;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
/**
|
|
942
|
+
* Проверить, существуют ли учетные данные
|
|
943
|
+
*/
|
|
944
|
+
async hasCredential(phone: string): Promise<boolean> {
|
|
945
|
+
return await this.TgAccount.exists(phone);
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
/**
|
|
949
|
+
* Обновить сессию для существующего аккаунта
|
|
950
|
+
*/
|
|
951
|
+
async updateSession(phone: string, session: string): Promise<boolean> {
|
|
952
|
+
const account = await this.TgAccount.findByPhone(phone);
|
|
953
|
+
if (account) {
|
|
954
|
+
await this.TgAccount.updateSession(phone, session);
|
|
955
|
+
await this.TgAccount.updateStatus(phone, this.TgAccountStatus.ACTIVE);
|
|
956
|
+
return true;
|
|
957
|
+
}
|
|
958
|
+
return false;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
/**
|
|
962
|
+
* Получить количество учетных данных
|
|
963
|
+
*/
|
|
964
|
+
async getCredentialsCount(): Promise<number> {
|
|
965
|
+
const stats = await this.TgAccount.getStats();
|
|
966
|
+
return stats.total;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
/**
|
|
970
|
+
* Очистить все учетные данные
|
|
971
|
+
*/
|
|
972
|
+
async clearAllCredentials(): Promise<void> {
|
|
973
|
+
const accounts = await this.TgAccount.getAll();
|
|
974
|
+
for (const account of accounts) {
|
|
975
|
+
await this.TgAccount.delete(account.id!);
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
/**
|
|
980
|
+
* Получить учетные данные по прокси
|
|
981
|
+
*/
|
|
982
|
+
async getCredentialsByProxy(proxyHost: string, proxyPort: number): Promise<TelegramCredentials[]> {
|
|
983
|
+
const proxy = await this.Proxy.findByIpPort(proxyHost, proxyPort);
|
|
984
|
+
if (!proxy) {
|
|
985
|
+
return [];
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
const accounts = await this.TgAccount.getAll();
|
|
989
|
+
const credentials: TelegramCredentials[] = [];
|
|
990
|
+
|
|
991
|
+
for (const account of accounts) {
|
|
992
|
+
if (account.proxy_id === proxy.id) {
|
|
993
|
+
const proxyData = this.convertProxyToTelegramFormat(proxy);
|
|
994
|
+
credentials.push({
|
|
995
|
+
phone: account.phone,
|
|
996
|
+
session: account.session || undefined,
|
|
997
|
+
password: account.password || undefined,
|
|
998
|
+
proxy: proxyData
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
return credentials;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
/**
|
|
1007
|
+
* Получить учетные данные без прокси
|
|
1008
|
+
*/
|
|
1009
|
+
async getCredentialsWithoutProxy(): Promise<TelegramCredentials[]> {
|
|
1010
|
+
const accounts = await this.TgAccount.getWithoutProxy();
|
|
1011
|
+
|
|
1012
|
+
return accounts.map(account => ({
|
|
1013
|
+
phone: account.phone,
|
|
1014
|
+
session: account.session || undefined,
|
|
1015
|
+
password: account.password || undefined
|
|
1016
|
+
}));
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
/**
|
|
1020
|
+
* Получить активные учетные данные
|
|
1021
|
+
*/
|
|
1022
|
+
async getActiveCredentials(): Promise<TelegramCredentials[]> {
|
|
1023
|
+
const accounts = await this.TgAccount.getByStatus(this.TgAccountStatus.ACTIVE);
|
|
1024
|
+
const credentials: TelegramCredentials[] = [];
|
|
1025
|
+
|
|
1026
|
+
for (const account of accounts) {
|
|
1027
|
+
let proxy: TelegramClientProxy | undefined;
|
|
1028
|
+
|
|
1029
|
+
if (account.proxy_id) {
|
|
1030
|
+
const proxyData = await this.Proxy.findById(account.proxy_id);
|
|
1031
|
+
if (proxyData && proxyData.status === this.ProxyStatus.ACTIVE) {
|
|
1032
|
+
proxy = this.convertProxyToTelegramFormat(proxyData);
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
credentials.push({
|
|
1037
|
+
phone: account.phone,
|
|
1038
|
+
session: account.session || undefined,
|
|
1039
|
+
password: account.password || undefined,
|
|
1040
|
+
proxy
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
return credentials;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
/**
|
|
1048
|
+
* Получить случайные активные учетные данные
|
|
1049
|
+
*/
|
|
1050
|
+
async getRandomActiveCredential(): Promise<TelegramCredentials | null> {
|
|
1051
|
+
const account = await this.TgAccount.getRandomAvailable();
|
|
1052
|
+
if (!account) {
|
|
1053
|
+
return null;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
let proxy: TelegramClientProxy | undefined;
|
|
1057
|
+
if (account.proxy_id) {
|
|
1058
|
+
const proxyData = await this.Proxy.findById(account.proxy_id);
|
|
1059
|
+
if (proxyData && proxyData.status === this.ProxyStatus.ACTIVE) {
|
|
1060
|
+
proxy = this.convertProxyToTelegramFormat(proxyData);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
return {
|
|
1065
|
+
phone: account.phone,
|
|
1066
|
+
session: account.session || undefined,
|
|
1067
|
+
password: account.password || undefined,
|
|
1068
|
+
proxy
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
/**
|
|
1073
|
+
* Обновить информацию о пользователе
|
|
1074
|
+
*/
|
|
1075
|
+
async updateUserInfo(phone: string, userInfo: {
|
|
1076
|
+
first_name?: string;
|
|
1077
|
+
last_name?: string;
|
|
1078
|
+
username?: string;
|
|
1079
|
+
user_id?: number;
|
|
1080
|
+
}): Promise<void> {
|
|
1081
|
+
await this.TgAccount.updateUserInfo(phone, userInfo);
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
/**
|
|
1085
|
+
* Обновить статус аккаунта
|
|
1086
|
+
*/
|
|
1087
|
+
async updateAccountStatus(phone: string, status: any): Promise<void> {
|
|
1088
|
+
const account = await this.TgAccount.findByPhone(phone);
|
|
1089
|
+
if (account) {
|
|
1090
|
+
await this.TgAccount.updateStatus(account.id!, status);
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
/**
|
|
1095
|
+
* Отметить активность аккаунта
|
|
1096
|
+
*/
|
|
1097
|
+
async markActivity(phone: string): Promise<void> {
|
|
1098
|
+
const account = await this.TgAccount.findByPhone(phone);
|
|
1099
|
+
if (account) {
|
|
1100
|
+
await this.TgAccount.updateLastActivity(account.id!);
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
/**
|
|
1105
|
+
* Получить статистику
|
|
1106
|
+
*/
|
|
1107
|
+
async getStats(): Promise<{
|
|
1108
|
+
accounts: any;
|
|
1109
|
+
proxies: any;
|
|
1110
|
+
}> {
|
|
1111
|
+
const accountStats = await this.TgAccount.getStats();
|
|
1112
|
+
const proxyStats = await this.Proxy.getStats();
|
|
1113
|
+
|
|
1114
|
+
return {
|
|
1115
|
+
accounts: accountStats,
|
|
1116
|
+
proxies: proxyStats
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
/**
|
|
1121
|
+
* Добавить прокси в БД
|
|
1122
|
+
*/
|
|
1123
|
+
async addProxy(proxyData: any): Promise<number> {
|
|
1124
|
+
return await this.Proxy.create(proxyData);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
/**
|
|
1128
|
+
* Получить все прокси
|
|
1129
|
+
*/
|
|
1130
|
+
async getAllProxies(): Promise<any[]> {
|
|
1131
|
+
return await this.Proxy.getAll();
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
/**
|
|
1135
|
+
* Получить активные прокси
|
|
1136
|
+
*/
|
|
1137
|
+
async getActiveProxies(): Promise<any[]> {
|
|
1138
|
+
return await this.Proxy.getActive();
|
|
1139
|
+
}
|
|
1140
|
+
}
|