@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.
Files changed (36) hide show
  1. package/bin/2byte-cli.ts +13 -0
  2. package/package.json +6 -1
  3. package/src/cli/GenerateCommand.ts +93 -9
  4. package/src/cli/TgAccountManager.ts +50 -0
  5. package/src/core/ApiService.ts +21 -0
  6. package/src/core/ApiServiceManager.ts +63 -0
  7. package/src/core/App.ts +133 -32
  8. package/src/illumination/InlineKeyboard.ts +2 -1
  9. package/src/illumination/Message2Byte.ts +2 -1
  10. package/src/illumination/Message2ByteLiveProgressive.ts +2 -2
  11. package/src/illumination/Section.ts +1 -1
  12. package/src/index.ts +9 -0
  13. package/src/libs/TelegramAccountControl.ts +409 -7
  14. package/src/libs/TgSender.ts +53 -0
  15. package/src/models/Model.ts +67 -0
  16. package/src/models/Proxy.ts +218 -0
  17. package/src/models/TgAccount.ts +362 -0
  18. package/src/models/index.ts +3 -0
  19. package/src/types.ts +6 -1
  20. package/src/user/UserModel.ts +9 -0
  21. package/src/workflow/services/MassSendApiService.ts +80 -0
  22. package/templates/bot/.env.example +6 -1
  23. package/templates/bot/bot.ts +6 -1
  24. package/templates/bot/database/migrations/007_proxy.sql +27 -0
  25. package/templates/bot/database/migrations/008_tg_accounts.sql +32 -0
  26. package/templates/bot/docs/CLI_SERVICES.md +536 -0
  27. package/templates/bot/docs/INPUT_SYSTEM.md +211 -0
  28. package/templates/bot/docs/SERVICE_EXAMPLES.md +384 -0
  29. package/templates/bot/docs/TASK_SYSTEM.md +156 -0
  30. package/templates/bot/models/Model.ts +7 -0
  31. package/templates/bot/models/index.ts +2 -0
  32. package/templates/bot/sectionList.ts +4 -2
  33. package/templates/bot/sections/ExampleInputSection.ts +85 -0
  34. package/templates/bot/sections/ExampleLiveTaskerSection.ts +60 -0
  35. package/templates/bot/sections/HomeSection.ts +10 -10
  36. 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
+ }
@@ -0,0 +1,3 @@
1
+ export * from './Model';
2
+ export * from './TgAccount';
3
+ export * from './Proxy';
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
- [key: string]: string | undefined;
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 = {
@@ -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
+ }