@2byte/tgbot-framework 1.0.2 → 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/bin/2byte-cli.ts +13 -0
- package/package.json +6 -1
- package/src/cli/GenerateCommand.ts +93 -9
- package/src/cli/TgAccountManager.ts +50 -0
- package/src/core/ApiService.ts +21 -0
- package/src/core/ApiServiceManager.ts +63 -0
- package/src/core/App.ts +133 -32
- package/src/illumination/InlineKeyboard.ts +2 -1
- package/src/illumination/Message2Byte.ts +2 -1
- package/src/illumination/Message2ByteLiveProgressive.ts +2 -2
- package/src/illumination/Section.ts +1 -1
- package/src/index.ts +9 -0
- package/src/libs/TelegramAccountControl.ts +409 -7
- 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 +6 -1
- package/src/user/UserModel.ts +9 -0
- package/src/workflow/services/MassSendApiService.ts +80 -0
- package/templates/bot/.env.example +6 -1
- package/templates/bot/bot.ts +6 -1
- package/templates/bot/database/migrations/007_proxy.sql +27 -0
- package/templates/bot/database/migrations/008_tg_accounts.sql +32 -0
- 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/sectionList.ts +4 -2
- package/templates/bot/sections/ExampleInputSection.ts +85 -0
- package/templates/bot/sections/ExampleLiveTaskerSection.ts +60 -0
- package/templates/bot/sections/HomeSection.ts +10 -10
- package/templates/bot/workflow/services/ExampleService.ts +24 -0
|
@@ -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
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -23,6 +23,7 @@ export interface AppConfig {
|
|
|
23
23
|
terminateSigInt: boolean;
|
|
24
24
|
terminateSigTerm: boolean;
|
|
25
25
|
keepSectionInstances: boolean;
|
|
26
|
+
botCwd: string;
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
export interface SectionOptions {
|
|
@@ -155,8 +156,12 @@ export interface EnvVars {
|
|
|
155
156
|
BOT_HOOK_SECRET_TOKEN?: string;
|
|
156
157
|
BOT_DEV_HOT_RELOAD_SECTIONS?: string;
|
|
157
158
|
BOT_ACCESS?: 'private' | 'public';
|
|
159
|
+
BOT_ACCESS_KEYS?: string; // comma separated access keys
|
|
158
160
|
ACCESS_USERNAMES?: string; // comma separated usernames
|
|
159
|
-
|
|
161
|
+
BOT_APP_API_PORT?: number;
|
|
162
|
+
TG_APP_ID?: string;
|
|
163
|
+
TG_APP_HASH?: string;
|
|
164
|
+
[key: string]: string | number |undefined;
|
|
160
165
|
}
|
|
161
166
|
|
|
162
167
|
export type ModelPaginateParams = {
|
package/src/user/UserModel.ts
CHANGED
|
@@ -17,6 +17,11 @@ export class UserModel extends Model {
|
|
|
17
17
|
this.attributes = attributes;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
static getAll(): UserModel[] {
|
|
21
|
+
const usersData = this.query(`SELECT * FROM ${this.tableName}`);
|
|
22
|
+
return usersData.map((data: any) => UserModel.make(data));
|
|
23
|
+
}
|
|
24
|
+
|
|
20
25
|
setSessionStorage(storage: UserStore): this {
|
|
21
26
|
this.userSession = storage;
|
|
22
27
|
return this;
|
|
@@ -191,6 +196,10 @@ export class UserModel extends Model {
|
|
|
191
196
|
return this.attributes.tg_username;
|
|
192
197
|
}
|
|
193
198
|
|
|
199
|
+
get tgId(): number {
|
|
200
|
+
return this.attributes.tg_id;
|
|
201
|
+
}
|
|
202
|
+
|
|
194
203
|
get firstName(): string {
|
|
195
204
|
return this.attributes.tg_first_name;
|
|
196
205
|
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { ApiService} from '../../core/ApiService';
|
|
2
|
+
import { App } from '../../core/App';
|
|
3
|
+
import { UserModel } from '../../user/UserModel';
|
|
4
|
+
|
|
5
|
+
export default class MassSendApiService extends ApiService {
|
|
6
|
+
|
|
7
|
+
private bunServerInstance: any;
|
|
8
|
+
|
|
9
|
+
constructor(
|
|
10
|
+
protected app: App,
|
|
11
|
+
public name: string = "MassSendApiService"
|
|
12
|
+
) {
|
|
13
|
+
super(app, name);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public async setup(): Promise<void> {
|
|
17
|
+
return Promise.resolve();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public async unsetup(): Promise<void> {
|
|
21
|
+
return Promise.resolve();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public async run(): Promise<void> {
|
|
25
|
+
this.bunServerInstance = Bun.serve({
|
|
26
|
+
port: this.app.configApp.envConfig.BOT_APP_API_PORT || 3033,
|
|
27
|
+
routes: {
|
|
28
|
+
'/': async (req) => {
|
|
29
|
+
const receivedData = (await req.json()) as { userIds?: number[]; message?: string };
|
|
30
|
+
this.app.debugLog("Received data for mass message:", receivedData);
|
|
31
|
+
|
|
32
|
+
let userIds: number[] = [];
|
|
33
|
+
let message: string = "Hello from MassSendApiService";
|
|
34
|
+
|
|
35
|
+
if (receivedData && typeof receivedData == 'object') {
|
|
36
|
+
userIds = receivedData?.userIds || [];
|
|
37
|
+
message = receivedData?.message || "Hello from MassSendApiService";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
this.sendMassMessage(userIds, message);
|
|
41
|
+
|
|
42
|
+
return Response.json({ status: 200, body: 'Mass message sending initiated.' });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
this.app.debugLog(`MassSendApiService Bun server running at http://localhost:${this.bunServerInstance.port}/`);
|
|
48
|
+
|
|
49
|
+
return Promise.resolve();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private async sendMassMessage(userIds: number[] = [], message: string): Promise<void> {
|
|
53
|
+
if (userIds.length === 0) {
|
|
54
|
+
|
|
55
|
+
if (!db) {
|
|
56
|
+
throw new Error("Database connection is not established.");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
UserModel.setDatabase(db);
|
|
60
|
+
|
|
61
|
+
this.app.debugLog("Fetching all users for mass message...");
|
|
62
|
+
const users = UserModel.getAll();
|
|
63
|
+
|
|
64
|
+
this.app.debugLog("Fetched users for mass message:", users);
|
|
65
|
+
|
|
66
|
+
if (users && users.length > 0) {
|
|
67
|
+
for (const user of users) {
|
|
68
|
+
this.app.debugLog(`Sending message to user ID: ${user.tgId} username: ${user.username}`);
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
await this.app.bot.telegram.sendMessage(user.tgId, message);
|
|
72
|
+
this.app.debugLog(`Message sent to user ID: ${user.tgId} username: ${user.username}`);
|
|
73
|
+
} catch (error) {
|
|
74
|
+
this.app.debugLog(`Failed to send message to user ID: ${user.tgId} username: ${user.username}`, error);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|