@2byte/tgbot-framework 1.0.3 → 1.0.4
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 +6 -5
- 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 -0
- 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 +1143 -1113
- 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 -35
- package/src/libs/TelegramAccountControl.ts +1140 -738
- package/src/libs/TgSender.ts +53 -0
- package/src/models/Model.ts +67 -0
- package/src/models/Proxy.ts +218 -0
- package/src/models/TgAccount.ts +362 -0
- package/src/models/index.ts +3 -0
- package/src/types.ts +191 -188
- package/src/user/UserModel.ts +297 -297
- package/src/user/UserStore.ts +119 -119
- package/src/workflow/services/MassSendApiService.ts +80 -80
- package/templates/bot/.env.example +23 -19
- package/templates/bot/artisan.ts +8 -8
- package/templates/bot/bot.ts +82 -79
- 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 -0
- package/templates/bot/database/migrations/008_tg_accounts.sql +32 -0
- package/templates/bot/database/seed.ts +14 -14
- package/templates/bot/docs/CLI_SERVICES.md +536 -0
- package/templates/bot/docs/INPUT_SYSTEM.md +211 -0
- package/templates/bot/docs/SERVICE_EXAMPLES.md +384 -0
- package/templates/bot/docs/TASK_SYSTEM.md +156 -0
- package/templates/bot/models/Model.ts +7 -0
- package/templates/bot/models/index.ts +2 -0
- 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/{ExampleServise.ts → ExampleService.ts} +23 -23
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TelegramAccountRemote,
|
|
3
|
+
TelegramManagerCredentials,
|
|
4
|
+
TelegramCredentials,
|
|
5
|
+
} from "./TelegramAccountControl";
|
|
6
|
+
|
|
7
|
+
interface TgSenderParams {
|
|
8
|
+
tgAppId: string;
|
|
9
|
+
tgAppHash: string;
|
|
10
|
+
app: any;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class TgSender {
|
|
14
|
+
private makeInstanceTgRemoteControl: () => {
|
|
15
|
+
remoteControl: TelegramAccountRemote;
|
|
16
|
+
credentialsManager: TelegramManagerCredentials;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
static init(params: TgSenderParams) {
|
|
20
|
+
return new TgSender(params);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
constructor(private params: TgSenderParams) {
|
|
24
|
+
this.makeInstanceTgRemoteControl = () => {
|
|
25
|
+
const credentialsManager = TelegramManagerCredentials.init({
|
|
26
|
+
pathFileStorage: path.join(__dirname, "../storage/tg_credentials.json"),
|
|
27
|
+
pathFileProxyList: path.join(__dirname, "../storage/tg_proxy_list.txt"),
|
|
28
|
+
pathFileCounterOffset: path.join(__dirname, "../storage/tg_counter_offset.txt"),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const remoteControl = TelegramAccountRemote.init({
|
|
32
|
+
appId: params.tgAppId!,
|
|
33
|
+
appHash: params.tgAppHash!,
|
|
34
|
+
credetialsManager: credentialsManager,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
remoteControl,
|
|
39
|
+
credentialsManager,
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
fromRandomAccount() {
|
|
45
|
+
return this;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
fromAccountCredentials(credentials: TelegramCredentials) {
|
|
49
|
+
return this;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
sendMessageByPhone(phone: string, message: string) {}
|
|
53
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { Database } from "bun:sqlite";
|
|
2
|
+
import { MakeManualPaginateButtonsParams, ModelPaginateParams, PaginateResult } from "..";
|
|
3
|
+
import { Section } from "..";
|
|
4
|
+
|
|
5
|
+
export abstract class Model {
|
|
6
|
+
protected static db: Database;
|
|
7
|
+
protected static tableName: string;
|
|
8
|
+
|
|
9
|
+
static setDatabase(database: Database) {
|
|
10
|
+
this.db = database;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
protected static query(sql: string, params: any[] = []): any {
|
|
14
|
+
const stmt = this.db.prepare(sql);
|
|
15
|
+
return stmt.all(...params);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
protected static queryOne(sql: string, params: any[] = []): any {
|
|
19
|
+
const stmt = this.db.prepare(sql);
|
|
20
|
+
return stmt.get(...params);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
protected static execute(sql: string, params: any[] = []): void {
|
|
24
|
+
const stmt = this.db.prepare(sql);
|
|
25
|
+
stmt.run(...params);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
protected static transaction<T>(callback: () => T): T {
|
|
29
|
+
return this.db.transaction(callback)();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public static async paginate(paginateParams: ModelPaginateParams): Promise<PaginateResult> {
|
|
33
|
+
const { page, route, routeParams, limit, whereSql, whereParams } = paginateParams;
|
|
34
|
+
const offset = (page - 1) * limit;
|
|
35
|
+
const sql = `SELECT * FROM ${this.tableName} ${whereSql} LIMIT ${offset}, ${limit}`;
|
|
36
|
+
|
|
37
|
+
const result = await this.query(sql, whereParams);
|
|
38
|
+
const queryTotal = await this.queryOne(`SELECT COUNT(*) as count FROM ${this.tableName} ${whereSql}`, whereParams);
|
|
39
|
+
const total = queryTotal ? queryTotal.count : 0;
|
|
40
|
+
const totalPages = Math.ceil(total / limit);
|
|
41
|
+
const hasPreviousPage = page > 1;
|
|
42
|
+
const hasNextPage = page < totalPages;
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
items: result,
|
|
46
|
+
paginateButtons: Section.makeManualPaginateButtons({
|
|
47
|
+
callbackDataAction: route,
|
|
48
|
+
paramsQuery: routeParams || {},
|
|
49
|
+
currentPage: page,
|
|
50
|
+
totalRecords: total,
|
|
51
|
+
perPage: limit,
|
|
52
|
+
} as MakeManualPaginateButtonsParams),
|
|
53
|
+
total,
|
|
54
|
+
totalPages,
|
|
55
|
+
hasPreviousPage,
|
|
56
|
+
hasNextPage,
|
|
57
|
+
currentPage: page,
|
|
58
|
+
} as PaginateResult;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
static getConnection(): Database {
|
|
62
|
+
if (db) {
|
|
63
|
+
return db;
|
|
64
|
+
}
|
|
65
|
+
throw new Error("Database connection is not set.");
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { Model } from "./Model";
|
|
2
|
+
|
|
3
|
+
export interface ProxyData {
|
|
4
|
+
/** PRIMARY KEY */
|
|
5
|
+
id?: number;
|
|
6
|
+
ip: string;
|
|
7
|
+
port: number;
|
|
8
|
+
username?: string | null;
|
|
9
|
+
password?: string | null;
|
|
10
|
+
secret?: string | null;
|
|
11
|
+
socksType?: number | null; // 4 or 5
|
|
12
|
+
MTProxy?: number; // 0 or 1
|
|
13
|
+
status?: number; // 0 = inactive, 1 = active
|
|
14
|
+
source?: string | null; // Note: keeping original typo from migration
|
|
15
|
+
last_check?: string | null;
|
|
16
|
+
created_at?: string;
|
|
17
|
+
updated_at?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export enum ProxyStatus {
|
|
21
|
+
INACTIVE = 0,
|
|
22
|
+
ACTIVE = 1
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export enum ProxySocksType {
|
|
26
|
+
SOCKS4 = 4,
|
|
27
|
+
SOCKS5 = 5
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class Proxy extends Model {
|
|
31
|
+
static tableName = 'proxy';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Создать новый прокси
|
|
35
|
+
*/
|
|
36
|
+
static async create(data: ProxyData): Promise<number> {
|
|
37
|
+
const {
|
|
38
|
+
ip,
|
|
39
|
+
port,
|
|
40
|
+
username = null,
|
|
41
|
+
password = null,
|
|
42
|
+
secret = null,
|
|
43
|
+
socksType = null,
|
|
44
|
+
MTProxy = 0,
|
|
45
|
+
status = ProxyStatus.INACTIVE,
|
|
46
|
+
source: souurce = null,
|
|
47
|
+
last_check = null,
|
|
48
|
+
} = data;
|
|
49
|
+
|
|
50
|
+
const stmt = this.db.prepare(
|
|
51
|
+
`INSERT INTO ${this.tableName}
|
|
52
|
+
(ip, port, username, password, secret, socksType, MTProxy, status, souurce, last_check)
|
|
53
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const result = stmt.run(ip, port, username, password, secret, socksType, MTProxy, status, souurce, last_check);
|
|
57
|
+
return result.lastInsertRowid as number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Найти прокси по ID
|
|
62
|
+
*/
|
|
63
|
+
static async findById(id: number): Promise<ProxyData | null> {
|
|
64
|
+
return this.queryOne(`SELECT * FROM ${this.tableName} WHERE id = ?`, [id]);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Найти прокси по IP и порту
|
|
69
|
+
*/
|
|
70
|
+
static async findByIpPort(ip: string, port: number): Promise<ProxyData | null> {
|
|
71
|
+
return this.queryOne(`SELECT * FROM ${this.tableName} WHERE ip = ? AND port = ?`, [ip, port]);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Получить все активные прокси
|
|
76
|
+
*/
|
|
77
|
+
static async getActive(): Promise<ProxyData[]> {
|
|
78
|
+
return this.query(`SELECT * FROM ${this.tableName} WHERE status = ? ORDER BY last_check DESC`, [ProxyStatus.ACTIVE]);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Получить все прокси
|
|
83
|
+
*/
|
|
84
|
+
static async getAll(): Promise<ProxyData[]> {
|
|
85
|
+
return this.query(`SELECT * FROM ${this.tableName} ORDER BY created_at DESC`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Получить прокси по типу SOCKS
|
|
90
|
+
*/
|
|
91
|
+
static async getBySocksType(socksType: ProxySocksType): Promise<ProxyData[]> {
|
|
92
|
+
return this.query(`SELECT * FROM ${this.tableName} WHERE socksType = ? AND status = ? ORDER BY last_check DESC`,
|
|
93
|
+
[socksType, ProxyStatus.ACTIVE]);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Получить MTProxy прокси
|
|
98
|
+
*/
|
|
99
|
+
static async getMTProxies(): Promise<ProxyData[]> {
|
|
100
|
+
return this.query(`SELECT * FROM ${this.tableName} WHERE MTProxy = 1 AND status = ? ORDER BY last_check DESC`,
|
|
101
|
+
[ProxyStatus.ACTIVE]);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Обновить статус прокси
|
|
106
|
+
*/
|
|
107
|
+
static async updateStatus(id: number, status: ProxyStatus): Promise<void> {
|
|
108
|
+
this.execute(`UPDATE ${this.tableName} SET status = ? WHERE id = ?`, [status, id]);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Обновить время последней проверки
|
|
113
|
+
*/
|
|
114
|
+
static async updateLastCheck(id: number, lastCheck: string = new Date().toISOString()): Promise<void> {
|
|
115
|
+
this.execute(`UPDATE ${this.tableName} SET last_check = ? WHERE id = ?`, [lastCheck, id]);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Обновить данные прокси
|
|
120
|
+
*/
|
|
121
|
+
static async update(id: number, data: Partial<ProxyData>): Promise<void> {
|
|
122
|
+
const fields = Object.keys(data).filter(key => key !== 'id');
|
|
123
|
+
const values = fields.map(field => data[field as keyof ProxyData]);
|
|
124
|
+
|
|
125
|
+
if (fields.length === 0) return;
|
|
126
|
+
|
|
127
|
+
const setClause = fields.map(field => `${field} = ?`).join(', ');
|
|
128
|
+
this.execute(`UPDATE ${this.tableName} SET ${setClause} WHERE id = ?`, [...values, id]);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Удалить прокси
|
|
133
|
+
*/
|
|
134
|
+
static async delete(id: number): Promise<void> {
|
|
135
|
+
this.execute(`DELETE FROM ${this.tableName} WHERE id = ?`, [id]);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Проверить, существует ли прокси
|
|
140
|
+
*/
|
|
141
|
+
static async exists(ip: string, port: number): Promise<boolean> {
|
|
142
|
+
const result = this.queryOne(`SELECT COUNT(*) as count FROM ${this.tableName} WHERE ip = ? AND port = ?`, [ip, port]);
|
|
143
|
+
return result.count > 0;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Получить количество прокси по статусу
|
|
148
|
+
*/
|
|
149
|
+
static async getCountByStatus(status: ProxyStatus): Promise<number> {
|
|
150
|
+
const result = this.queryOne(`SELECT COUNT(*) as count FROM ${this.tableName} WHERE status = ?`, [status]);
|
|
151
|
+
return result.count;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Получить случайный активный прокси
|
|
156
|
+
*/
|
|
157
|
+
static async getRandomActive(): Promise<ProxyData | null> {
|
|
158
|
+
return this.queryOne(`SELECT * FROM ${this.tableName} WHERE status = ? ORDER BY RANDOM() LIMIT 1`, [ProxyStatus.ACTIVE]);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Массовое добавление прокси
|
|
163
|
+
*/
|
|
164
|
+
static async bulkCreate(proxies: ProxyData[]): Promise<number[]> {
|
|
165
|
+
const ids: number[] = [];
|
|
166
|
+
|
|
167
|
+
this.transaction(() => {
|
|
168
|
+
for (const proxy of proxies) {
|
|
169
|
+
const id = this.create(proxy);
|
|
170
|
+
ids.push(id as any);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
return ids;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Деактивировать все прокси
|
|
179
|
+
*/
|
|
180
|
+
static async deactivateAll(): Promise<void> {
|
|
181
|
+
this.execute(`UPDATE ${this.tableName} SET status = ?`, [ProxyStatus.INACTIVE]);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Поиск прокси по источнику
|
|
186
|
+
*/
|
|
187
|
+
static async findBySource(source: string): Promise<ProxyData[]> {
|
|
188
|
+
return this.query(`SELECT * FROM ${this.tableName} WHERE souurce = ? ORDER BY created_at DESC`, [source]);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Получить статистику по прокси
|
|
193
|
+
*/
|
|
194
|
+
static async getStats(): Promise<{
|
|
195
|
+
total: number;
|
|
196
|
+
active: number;
|
|
197
|
+
inactive: number;
|
|
198
|
+
socks4: number;
|
|
199
|
+
socks5: number;
|
|
200
|
+
mtproxy: number;
|
|
201
|
+
}> {
|
|
202
|
+
const total = this.queryOne(`SELECT COUNT(*) as count FROM ${this.tableName}`).count;
|
|
203
|
+
const active = this.queryOne(`SELECT COUNT(*) as count FROM ${this.tableName} WHERE status = ?`, [ProxyStatus.ACTIVE]).count;
|
|
204
|
+
const inactive = this.queryOne(`SELECT COUNT(*) as count FROM ${this.tableName} WHERE status = ?`, [ProxyStatus.INACTIVE]).count;
|
|
205
|
+
const socks4 = this.queryOne(`SELECT COUNT(*) as count FROM ${this.tableName} WHERE socksType = ?`, [ProxySocksType.SOCKS4]).count;
|
|
206
|
+
const socks5 = this.queryOne(`SELECT COUNT(*) as count FROM ${this.tableName} WHERE socksType = ?`, [ProxySocksType.SOCKS5]).count;
|
|
207
|
+
const mtproxy = this.queryOne(`SELECT COUNT(*) as count FROM ${this.tableName} WHERE MTProxy = 1`).count;
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
total,
|
|
211
|
+
active,
|
|
212
|
+
inactive,
|
|
213
|
+
socks4,
|
|
214
|
+
socks5,
|
|
215
|
+
mtproxy
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
}
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
import { Model } from "./Model";
|
|
2
|
+
|
|
3
|
+
export interface AccountData {
|
|
4
|
+
/** PRIMARY KEY */
|
|
5
|
+
id?: number;
|
|
6
|
+
/** UNIQUE */
|
|
7
|
+
phone: string;
|
|
8
|
+
session?: string | null;
|
|
9
|
+
password?: string | null;
|
|
10
|
+
country?: string | null;
|
|
11
|
+
proxy_id?: number | null; // Foreign key to proxy table
|
|
12
|
+
status?: number;
|
|
13
|
+
created_at?: string;
|
|
14
|
+
updated_at?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export enum TgAccountStatus {
|
|
18
|
+
INACTIVE = 0,
|
|
19
|
+
ACTIVE = 1,
|
|
20
|
+
BANNED = 2,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class TgAccount extends Model {
|
|
24
|
+
static tableName = 'tg_accounts';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Создать новый аккаунт
|
|
28
|
+
*/
|
|
29
|
+
static async create(data: AccountData): Promise<void> {
|
|
30
|
+
const {
|
|
31
|
+
phone,
|
|
32
|
+
session = null,
|
|
33
|
+
password = null,
|
|
34
|
+
country = null,
|
|
35
|
+
proxy_id = null,
|
|
36
|
+
status = 0,
|
|
37
|
+
} = data;
|
|
38
|
+
|
|
39
|
+
await this.execute(
|
|
40
|
+
`INSERT INTO ${this.tableName}
|
|
41
|
+
(phone, session, password, country, proxy_id, status)
|
|
42
|
+
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
43
|
+
[phone, session, password, country, proxy_id, status]
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Найти аккаунт по номеру телефона
|
|
49
|
+
*/
|
|
50
|
+
static async findByPhone(phone: string): Promise<AccountData | null> {
|
|
51
|
+
return this.queryOne(`SELECT * FROM ${this.tableName} WHERE phone = ?`, [phone]);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Найти аккаунт по ID
|
|
56
|
+
*/
|
|
57
|
+
static async findById(id: number): Promise<AccountData | null> {
|
|
58
|
+
return this.queryOne(`SELECT * FROM ${this.tableName} WHERE id = ?`, [id]);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Обновить сессию аккаунта
|
|
63
|
+
*/
|
|
64
|
+
static async updateSession(phone: string, session: string): Promise<void> {
|
|
65
|
+
await this.execute(
|
|
66
|
+
`UPDATE ${this.tableName}
|
|
67
|
+
SET session = ?, updated_at = CURRENT_TIMESTAMP
|
|
68
|
+
WHERE phone = ?`,
|
|
69
|
+
[session, phone]
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Обновить пароль аккаунта
|
|
75
|
+
*/
|
|
76
|
+
static async updatePassword(phone: string, password: string): Promise<void> {
|
|
77
|
+
await this.execute(
|
|
78
|
+
`UPDATE ${this.tableName}
|
|
79
|
+
SET password = ?, updated_at = CURRENT_TIMESTAMP
|
|
80
|
+
WHERE phone = ?`,
|
|
81
|
+
[password, phone]
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Обновить страну аккаунта
|
|
87
|
+
*/
|
|
88
|
+
static async updateCountry(phone: string, country: string): Promise<void> {
|
|
89
|
+
await this.execute(
|
|
90
|
+
`UPDATE ${this.tableName}
|
|
91
|
+
SET country = ?, updated_at = CURRENT_TIMESTAMP
|
|
92
|
+
WHERE phone = ?`,
|
|
93
|
+
[country, phone]
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Обновить прокси аккаунта
|
|
99
|
+
*/
|
|
100
|
+
static async updateProxy(phone: string, proxy_id: number | null): Promise<void> {
|
|
101
|
+
await this.execute(
|
|
102
|
+
`UPDATE ${this.tableName}
|
|
103
|
+
SET proxy_id = ?, updated_at = CURRENT_TIMESTAMP
|
|
104
|
+
WHERE phone = ?`,
|
|
105
|
+
[proxy_id, phone]
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Обновить статус аккаунта
|
|
111
|
+
*/
|
|
112
|
+
static async updateStatus(phone: string, status: number): Promise<void> {
|
|
113
|
+
await this.execute(
|
|
114
|
+
`UPDATE ${this.tableName}
|
|
115
|
+
SET status = ?, updated_at = CURRENT_TIMESTAMP
|
|
116
|
+
WHERE phone = ?`,
|
|
117
|
+
[status, phone]
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Универсальный метод обновления любых полей
|
|
123
|
+
*/
|
|
124
|
+
static async update(phone: string, fields: Partial<Omit<AccountData, "phone" | "id">>): Promise<void> {
|
|
125
|
+
const keys = Object.keys(fields);
|
|
126
|
+
if (keys.length === 0) return;
|
|
127
|
+
|
|
128
|
+
const setClause = keys.map(k => `${k} = ?`).join(", ");
|
|
129
|
+
const values = keys.map(k => (fields as any)[k]);
|
|
130
|
+
|
|
131
|
+
await this.execute(
|
|
132
|
+
`UPDATE ${this.tableName}
|
|
133
|
+
SET ${setClause}, updated_at = CURRENT_TIMESTAMP
|
|
134
|
+
WHERE phone = ?`,
|
|
135
|
+
[...values, phone]
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Вставка или обновление аккаунта (upsert)
|
|
141
|
+
*/
|
|
142
|
+
static async upsert(data: AccountData): Promise<void> {
|
|
143
|
+
const {
|
|
144
|
+
phone,
|
|
145
|
+
session = null,
|
|
146
|
+
password = null,
|
|
147
|
+
country = null,
|
|
148
|
+
proxy_id = null,
|
|
149
|
+
status = 0,
|
|
150
|
+
} = data;
|
|
151
|
+
|
|
152
|
+
await this.execute(
|
|
153
|
+
`INSERT INTO ${this.tableName}
|
|
154
|
+
(phone, session, password, country, proxy_id, status)
|
|
155
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
156
|
+
ON CONFLICT(phone) DO UPDATE
|
|
157
|
+
SET session = excluded.session,
|
|
158
|
+
password = excluded.password,
|
|
159
|
+
country = excluded.country,
|
|
160
|
+
proxy_id = excluded.proxy_id,
|
|
161
|
+
status = excluded.status,
|
|
162
|
+
updated_at = CURRENT_TIMESTAMP`,
|
|
163
|
+
[phone, session, password, country, proxy_id, status]
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Получить все аккаунты
|
|
169
|
+
*/
|
|
170
|
+
static async getAll(): Promise<AccountData[]> {
|
|
171
|
+
return this.query(`SELECT * FROM ${this.tableName} ORDER BY created_at DESC`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Удалить аккаунт
|
|
176
|
+
*/
|
|
177
|
+
static async delete(phone: string): Promise<void> {
|
|
178
|
+
await this.execute(`DELETE FROM ${this.tableName} WHERE phone = ?`, [phone]);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Получить количество аккаунтов
|
|
183
|
+
*/
|
|
184
|
+
static async count(): Promise<number> {
|
|
185
|
+
const result: { count: number } = await this.queryOne(`SELECT COUNT(*) as count FROM ${this.tableName}`);
|
|
186
|
+
return result.count;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
static async countActive(): Promise<number> {
|
|
190
|
+
const result: { count: number } = await this.queryOne(`SELECT COUNT(*) as count FROM ${this.tableName} WHERE status = 1`);
|
|
191
|
+
return result.count;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Получить следующий свободный аккаунт
|
|
196
|
+
*/
|
|
197
|
+
static async getNextAvailable(): Promise<AccountData | null> {
|
|
198
|
+
return this.queryOne(`
|
|
199
|
+
SELECT a.* FROM ${this.tableName} a
|
|
200
|
+
LEFT JOIN report_tasks rt ON a.phone = rt.account_phone AND rt.status = 'pending'
|
|
201
|
+
WHERE rt.id IS NULL
|
|
202
|
+
LIMIT 1
|
|
203
|
+
`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Получить аккаунты с прокси
|
|
208
|
+
*/
|
|
209
|
+
static async getWithProxy(): Promise<AccountData[]> {
|
|
210
|
+
return this.query(`SELECT * FROM ${this.tableName} WHERE proxy_id IS NOT NULL ORDER BY created_at DESC`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Получить аккаунты без прокси
|
|
215
|
+
*/
|
|
216
|
+
static async getWithoutProxy(): Promise<AccountData[]> {
|
|
217
|
+
return this.query(`SELECT * FROM ${this.tableName} WHERE proxy_id IS NULL ORDER BY created_at DESC`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Получить аккаунт с информацией о прокси (JOIN)
|
|
222
|
+
*/
|
|
223
|
+
static async findWithProxy(phone: string): Promise<(AccountData & { proxy?: any }) | null> {
|
|
224
|
+
return this.queryOne(`
|
|
225
|
+
SELECT
|
|
226
|
+
a.*,
|
|
227
|
+
p.ip as proxy_ip,
|
|
228
|
+
p.port as proxy_port,
|
|
229
|
+
p.username as proxy_username,
|
|
230
|
+
p.password as proxy_password,
|
|
231
|
+
p.secret as proxy_secret,
|
|
232
|
+
p.socksType as proxy_socksType,
|
|
233
|
+
p.MTProxy as proxy_MTProxy,
|
|
234
|
+
p.status as proxy_status,
|
|
235
|
+
p.source as proxy_source
|
|
236
|
+
FROM ${this.tableName} a
|
|
237
|
+
LEFT JOIN proxy p ON a.proxy_id = p.id
|
|
238
|
+
WHERE a.phone = ?
|
|
239
|
+
`, [phone]);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Получить все аккаунты с информацией о прокси (JOIN)
|
|
244
|
+
*/
|
|
245
|
+
static async getAllWithProxy(): Promise<(AccountData & { proxy?: any })[]> {
|
|
246
|
+
return this.query(`
|
|
247
|
+
SELECT
|
|
248
|
+
a.*,
|
|
249
|
+
p.ip as proxy_ip,
|
|
250
|
+
p.port as proxy_port,
|
|
251
|
+
p.username as proxy_username,
|
|
252
|
+
p.password as proxy_password,
|
|
253
|
+
p.secret as proxy_secret,
|
|
254
|
+
p.socksType as proxy_socksType,
|
|
255
|
+
p.MTProxy as proxy_MTProxy,
|
|
256
|
+
p.status as proxy_status,
|
|
257
|
+
p.source as proxy_source
|
|
258
|
+
FROM ${this.tableName} a
|
|
259
|
+
LEFT JOIN proxy p ON a.proxy_id = p.id
|
|
260
|
+
ORDER BY a.created_at DESC
|
|
261
|
+
`);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Привязать прокси к аккаунту
|
|
266
|
+
*/
|
|
267
|
+
static async assignProxy(phone: string, proxyId: number): Promise<void> {
|
|
268
|
+
await this.execute(
|
|
269
|
+
`UPDATE ${this.tableName}
|
|
270
|
+
SET proxy_id = ?, updated_at = CURRENT_TIMESTAMP
|
|
271
|
+
WHERE phone = ?`,
|
|
272
|
+
[proxyId, phone]
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Отвязать прокси от аккаунта
|
|
278
|
+
*/
|
|
279
|
+
static async unassignProxy(phone: string): Promise<void> {
|
|
280
|
+
await this.execute(
|
|
281
|
+
`UPDATE ${this.tableName}
|
|
282
|
+
SET proxy_id = NULL, updated_at = CURRENT_TIMESTAMP
|
|
283
|
+
WHERE phone = ?`,
|
|
284
|
+
[phone]
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Получить аккаунты по статусу
|
|
290
|
+
*/
|
|
291
|
+
static async getByStatus(status: number): Promise<AccountData[]> {
|
|
292
|
+
return this.query(`SELECT * FROM ${this.tableName} WHERE status = ? ORDER BY created_at DESC`, [status]);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Получить активные аккаунты
|
|
297
|
+
*/
|
|
298
|
+
static async getActive(): Promise<AccountData[]> {
|
|
299
|
+
return this.getByStatus(TgAccountStatus.ACTIVE);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Получить заблокированные аккаунты
|
|
304
|
+
*/
|
|
305
|
+
static async getBanned(): Promise<AccountData[]> {
|
|
306
|
+
return this.getByStatus(TgAccountStatus.BANNED);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Проверить существование аккаунта
|
|
311
|
+
*/
|
|
312
|
+
static async exists(phone: string): Promise<boolean> {
|
|
313
|
+
const result: { count: number } = await this.queryOne(
|
|
314
|
+
`SELECT COUNT(*) as count FROM ${this.tableName} WHERE phone = ?`,
|
|
315
|
+
[phone]
|
|
316
|
+
);
|
|
317
|
+
return result.count > 0;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Получить статистику аккаунтов
|
|
322
|
+
*/
|
|
323
|
+
static async getStats(): Promise<{
|
|
324
|
+
total: number;
|
|
325
|
+
active: number;
|
|
326
|
+
banned: number;
|
|
327
|
+
inactive: number;
|
|
328
|
+
withProxy: number;
|
|
329
|
+
withoutProxy: number;
|
|
330
|
+
withSession: number;
|
|
331
|
+
}> {
|
|
332
|
+
const total = await this.count();
|
|
333
|
+
const active = (await this.queryOne(`SELECT COUNT(*) as count FROM ${this.tableName} WHERE status = ?`, [TgAccountStatus.ACTIVE])).count;
|
|
334
|
+
const banned = (await this.queryOne(`SELECT COUNT(*) as count FROM ${this.tableName} WHERE status = ?`, [TgAccountStatus.BANNED])).count;
|
|
335
|
+
const inactive = (await this.queryOne(`SELECT COUNT(*) as count FROM ${this.tableName} WHERE status = ?`, [TgAccountStatus.INACTIVE])).count;
|
|
336
|
+
const withProxy = (await this.queryOne(`SELECT COUNT(*) as count FROM ${this.tableName} WHERE proxy_id IS NOT NULL`)).count;
|
|
337
|
+
const withoutProxy = (await this.queryOne(`SELECT COUNT(*) as count FROM ${this.tableName} WHERE proxy_id IS NULL`)).count;
|
|
338
|
+
const withSession = (await this.queryOne(`SELECT COUNT(*) as count FROM ${this.tableName} WHERE session IS NOT NULL`)).count;
|
|
339
|
+
|
|
340
|
+
return {
|
|
341
|
+
total,
|
|
342
|
+
active,
|
|
343
|
+
banned,
|
|
344
|
+
inactive,
|
|
345
|
+
withProxy,
|
|
346
|
+
withoutProxy,
|
|
347
|
+
withSession
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Получить случайный доступный аккаунт
|
|
353
|
+
*/
|
|
354
|
+
static async getRandomAvailable(): Promise<AccountData | null> {
|
|
355
|
+
return this.queryOne(`
|
|
356
|
+
SELECT * FROM ${this.tableName}
|
|
357
|
+
WHERE status = ? AND session IS NOT NULL
|
|
358
|
+
ORDER BY RANDOM()
|
|
359
|
+
LIMIT 1
|
|
360
|
+
`, [TgAccountStatus.ACTIVE]);
|
|
361
|
+
}
|
|
362
|
+
}
|