@2byte/tgbot-framework 1.0.13 → 1.0.15
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/package.json +1 -1
- package/src/core/ApiService.ts +11 -3
- package/src/core/ApiServiceManager.ts +17 -1
- package/src/core/App.ts +164 -37
- package/src/core/Model.ts +1 -1
- package/src/illumination/RunSectionRoute.ts +4 -0
- package/src/illumination/Section.ts +1 -0
- package/src/illumination/Telegraf2byteContext.ts +1 -0
- package/src/index.ts +1 -0
- package/src/types.ts +2 -0
- package/src/workflow/services/MassSendApiService.ts +91 -80
- package/templates/bot/package.json +1 -1
package/package.json
CHANGED
package/src/core/ApiService.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { App } from "./App";
|
|
2
2
|
|
|
3
|
-
export
|
|
3
|
+
export class ApiService<T = any> {
|
|
4
|
+
|
|
5
|
+
public name: string = "ApiService";
|
|
6
|
+
|
|
7
|
+
protected app!: App;
|
|
4
8
|
|
|
5
9
|
constructor(
|
|
6
|
-
|
|
7
|
-
public name: string
|
|
10
|
+
public params: T = {} as T
|
|
8
11
|
) {}
|
|
9
12
|
|
|
10
13
|
async setup(): Promise<void> {
|
|
@@ -18,4 +21,9 @@ export abstract class ApiService {
|
|
|
18
21
|
async run(): Promise<void> {
|
|
19
22
|
// Implement your API logic here
|
|
20
23
|
}
|
|
24
|
+
|
|
25
|
+
public setApp(app: App): this {
|
|
26
|
+
this.app = app;
|
|
27
|
+
return this;
|
|
28
|
+
}
|
|
21
29
|
}
|
|
@@ -51,13 +51,29 @@ export class ApiServiceManager {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
public async runAllServices(): Promise<void> {
|
|
55
|
+
for (const [name, service] of this.services) {
|
|
56
|
+
this.app.debugLog(`Running API service: ${name}`);
|
|
57
|
+
await service.run();
|
|
58
|
+
this.app.debugLog(`API service run completed: ${name}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
54
62
|
public getAll(): Map<string, ApiService> {
|
|
55
63
|
return this.services;
|
|
56
64
|
}
|
|
57
65
|
|
|
66
|
+
public async setupAllServices(): Promise<void> {
|
|
67
|
+
for (const [name, service] of this.services) {
|
|
68
|
+
this.app.debugLog(`Setting up API service: ${name}`);
|
|
69
|
+
await this.setupService(name);
|
|
70
|
+
this.app.debugLog(`API service setup completed: ${name}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
58
74
|
public async unsetupAllServices(): Promise<void> {
|
|
59
75
|
for (const [name, service] of this.services) {
|
|
60
|
-
await
|
|
76
|
+
await this.unsetupService(name);
|
|
61
77
|
}
|
|
62
78
|
}
|
|
63
79
|
}
|
package/src/core/App.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
import { nameToCapitalize } from "./utils";
|
|
22
22
|
import { ApiServiceManager } from "./ApiServiceManager";
|
|
23
23
|
import { message } from "telegraf/filters";
|
|
24
|
+
import { ApiService } from "./ApiService";
|
|
24
25
|
|
|
25
26
|
export class App {
|
|
26
27
|
private config: AppConfig = {
|
|
@@ -43,6 +44,7 @@ export class App {
|
|
|
43
44
|
terminateSigTerm: true,
|
|
44
45
|
keepSectionInstances: false,
|
|
45
46
|
botCwd: process.cwd(),
|
|
47
|
+
services: [],
|
|
46
48
|
};
|
|
47
49
|
|
|
48
50
|
public bot!: Telegraf<Telegraf2byteContext>;
|
|
@@ -72,7 +74,7 @@ export class App {
|
|
|
72
74
|
}
|
|
73
75
|
> = new Map();
|
|
74
76
|
|
|
75
|
-
private messageHandlers:
|
|
77
|
+
private messageHandlers: RunSectionRoute[] | CallableFunction<this>[] = [];
|
|
76
78
|
|
|
77
79
|
constructor() {
|
|
78
80
|
this.middlewares.push(this.mainMiddleware.bind(this));
|
|
@@ -160,13 +162,12 @@ export class App {
|
|
|
160
162
|
return this;
|
|
161
163
|
}
|
|
162
164
|
|
|
163
|
-
messageHandlers(handlers:
|
|
165
|
+
messageHandlers(handlers: RunSectionRoute[] | CallableFunction<this>[]): this {
|
|
164
166
|
this.app.messageHandlers = handlers;
|
|
165
167
|
return this;
|
|
166
168
|
}
|
|
167
169
|
|
|
168
170
|
/**
|
|
169
|
-
*
|
|
170
171
|
* @param keep Whether to keep section instances in memory after they are run.
|
|
171
172
|
* If true, sections will not be reloaded on each request, improving performance for frequently accessed sections.
|
|
172
173
|
* If false, sections will be reloaded each time they are accessed, ensuring the latest version is used.
|
|
@@ -188,6 +189,11 @@ export class App {
|
|
|
188
189
|
return this;
|
|
189
190
|
}
|
|
190
191
|
|
|
192
|
+
services(services: ApiService[]): this {
|
|
193
|
+
this.app.config.services = services;
|
|
194
|
+
return this;
|
|
195
|
+
}
|
|
196
|
+
|
|
191
197
|
build(): App {
|
|
192
198
|
return this.app;
|
|
193
199
|
}
|
|
@@ -274,7 +280,7 @@ export class App {
|
|
|
274
280
|
}
|
|
275
281
|
|
|
276
282
|
// Check access by username and register user if not exists
|
|
277
|
-
if (!this.config.userStorage.exists(tgUsername)) {
|
|
283
|
+
if (!this.config.userStorage.exists(tgUsername) && !this.rememberUser(tgUsername)) {
|
|
278
284
|
const isAuthByUsername = !this.config.accessPublic && !accessKey;
|
|
279
285
|
|
|
280
286
|
// check access by username for private bots
|
|
@@ -288,6 +294,7 @@ export class App {
|
|
|
288
294
|
checkAccess &&
|
|
289
295
|
checkAccess.every((name) => name.toLowerCase() !== requestUsername.toLowerCase())
|
|
290
296
|
) {
|
|
297
|
+
this.debugLog("Username access denied:", requestUsername);
|
|
291
298
|
return ctx.reply("Access denied. Your username is not in the access list.");
|
|
292
299
|
}
|
|
293
300
|
this.debugLog("Username access granted.");
|
|
@@ -425,20 +432,53 @@ export class App {
|
|
|
425
432
|
!ctx.userSession.stateAfterValidatedUserResponse
|
|
426
433
|
) {
|
|
427
434
|
this.messageHandlers.forEach(async (handler: any) => {
|
|
435
|
+
if (ctx.caught) {
|
|
436
|
+
this.debugLog("Message already caught by another handler, skipping remaining handlers.");
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const isHandlerRunSectionRoute = handler instanceof RunSectionRoute;
|
|
441
|
+
|
|
442
|
+
if (isHandlerRunSectionRoute) {
|
|
443
|
+
this.debugLog("Checking message handler section route:", handler);
|
|
444
|
+
await this.runSection(ctx, handler, {
|
|
445
|
+
cbBeforeRunMethod: async (sectionInstance: Section) => {
|
|
446
|
+
sectionInstance.runForMessageHandler = true;
|
|
447
|
+
},
|
|
448
|
+
});
|
|
449
|
+
if (ctx.caught) {
|
|
450
|
+
this.debugLog("Message handler route caught the message, skipping remaining handlers.");
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
428
455
|
const handlerIsClass =
|
|
429
456
|
typeof handler === "function" && /^\s*class\s+/.test(handler.toString());
|
|
430
457
|
const nameHandler = handlerIsClass
|
|
431
458
|
? handler.name
|
|
432
459
|
: handler.constructor?.name || "unknown";
|
|
433
460
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
if (handlerIsClass) {
|
|
461
|
+
if (handlerIsClass && !ctx.caught) {
|
|
462
|
+
this.debugLog(`Running message handler class ${nameHandler} for user ${ctx.user.username}`);
|
|
437
463
|
await new handler(this).handle(ctx);
|
|
464
|
+
if (ctx.caught) {
|
|
465
|
+
this.debugLog("Message handler class caught the message, skipping remaining handlers.");
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
} else if (!handlerIsClass && typeof handler === "function" && !ctx.caught) {
|
|
469
|
+
this.debugLog(`Running message handler function ${nameHandler} for user ${ctx.user.username}`);
|
|
470
|
+
await handler(ctx);
|
|
471
|
+
if (ctx.caught) {
|
|
472
|
+
this.debugLog("Message handler function caught the message, skipping remaining handlers.");
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
438
475
|
}
|
|
439
476
|
});
|
|
440
477
|
} else {
|
|
441
|
-
this.debugLog(
|
|
478
|
+
this.debugLog(
|
|
479
|
+
"Message input already handled by awaitingInput or awaitingInputPromise. stateAfterValidatedUserResponse:",
|
|
480
|
+
ctx.userSession.stateAfterValidatedUserResponse
|
|
481
|
+
);
|
|
442
482
|
}
|
|
443
483
|
|
|
444
484
|
delete ctx.userSession.stateAfterValidatedUserResponse; // Clear the state after handling the message
|
|
@@ -462,7 +502,7 @@ export class App {
|
|
|
462
502
|
// Get the largest photo (the last one in the array is usually the largest)
|
|
463
503
|
const largestPhoto = photo[photo.length - 1];
|
|
464
504
|
await this.handleUserInput(ctx, largestPhoto, "photo");
|
|
465
|
-
|
|
505
|
+
|
|
466
506
|
delete ctx.userSession.stateAfterValidatedUserResponse; // Clear the state after handling the message
|
|
467
507
|
});
|
|
468
508
|
}
|
|
@@ -470,39 +510,38 @@ export class App {
|
|
|
470
510
|
private async registerServices() {
|
|
471
511
|
this.apiServiceManager = ApiServiceManager.init(this);
|
|
472
512
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
throw error;
|
|
479
|
-
}
|
|
513
|
+
// Register services from config
|
|
514
|
+
this.debugLog(
|
|
515
|
+
"Registering services from config:",
|
|
516
|
+
this.config.services.map((service) => service.constructor.name)
|
|
517
|
+
);
|
|
480
518
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
519
|
+
this.config.services.forEach((service) => {
|
|
520
|
+
this.debugLog(`Registering service: ${service.constructor.name}`);
|
|
521
|
+
this.apiServiceManager.registerService(service.name, service.setApp(this));
|
|
522
|
+
this.debugLog(`Service ${service.constructor.name} registered`);
|
|
523
|
+
});
|
|
486
524
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
};
|
|
525
|
+
try {
|
|
526
|
+
await this.apiServiceManager.setupAllServices();
|
|
527
|
+
} catch (error) {
|
|
528
|
+
this.debugLog("Error setting up services:", error);
|
|
529
|
+
throw error;
|
|
530
|
+
}
|
|
494
531
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
532
|
+
try {
|
|
533
|
+
await this.apiServiceManager.runAllServices();
|
|
534
|
+
} catch (error) {
|
|
535
|
+
this.debugLog("Error running services:", error);
|
|
536
|
+
throw error;
|
|
537
|
+
}
|
|
499
538
|
}
|
|
500
539
|
|
|
501
540
|
private async unregisterServices() {
|
|
502
541
|
this.apiServiceManager = ApiServiceManager.init(this);
|
|
503
542
|
|
|
504
543
|
try {
|
|
505
|
-
this.apiServiceManager.unsetupAllServices();
|
|
544
|
+
await this.apiServiceManager.unsetupAllServices();
|
|
506
545
|
} catch (error) {
|
|
507
546
|
this.debugLog("Error unsetting up services:", error);
|
|
508
547
|
throw error;
|
|
@@ -923,7 +962,7 @@ export class App {
|
|
|
923
962
|
if (sectionRoute.hasTriggers()) {
|
|
924
963
|
this.debugLog("Section route has triggers, executing them before running method:", sectionId);
|
|
925
964
|
sectionRoute.getTriggers().forEach((trigger) => {
|
|
926
|
-
if (trigger.name ===
|
|
965
|
+
if (trigger.name === "cbBeforeRunMethod") {
|
|
927
966
|
this.debugLog(`Executing cbBeforeRunMethod trigger for section ${sectionId}`);
|
|
928
967
|
this.debugLog("Trigger details:", trigger);
|
|
929
968
|
|
|
@@ -1019,6 +1058,32 @@ export class App {
|
|
|
1019
1058
|
}
|
|
1020
1059
|
}
|
|
1021
1060
|
|
|
1061
|
+
/**
|
|
1062
|
+
* Remembers a user in storage by their Telegram username. If the user does not exist in storage, it attempts to fetch the user from the database and add them to storage. This is useful for ensuring that the storage has the latest user data from the database, especially in cases where user information might have been updated.
|
|
1063
|
+
* @param tgUsername Telegram username of the user to remember. This method checks if the user exists in storage, and if not, tries to fetch it from the database and add to storage. This is useful for cases when user data might be updated in the database and we want to refresh the storage with the latest data.
|
|
1064
|
+
* @returns A boolean indicating whether the user was successfully remembered (true) or not (false).
|
|
1065
|
+
*/
|
|
1066
|
+
async rememberUser(tgUsername: string): Promise<boolean> {
|
|
1067
|
+
if (this.config.userStorage && !this.config.userStorage.exists(tgUsername)) {
|
|
1068
|
+
this.debugLog("Warning: Username not found in storage:", tgUsername);
|
|
1069
|
+
this.debugLog("Trying getting to database:", tgUsername);
|
|
1070
|
+
|
|
1071
|
+
// Try to get user from database and add to storage
|
|
1072
|
+
UserModel.resolveDb();
|
|
1073
|
+
const userFromDb = UserModel.findByUsername(tgUsername);
|
|
1074
|
+
|
|
1075
|
+
if (userFromDb) {
|
|
1076
|
+
this.config.userStorage.add(tgUsername, userFromDb);
|
|
1077
|
+
this.debugLog("Success: User found in database and added to storage:", tgUsername);
|
|
1078
|
+
this.debugLog('Success: Remembered user "' + tgUsername + '"');
|
|
1079
|
+
return true;
|
|
1080
|
+
} else {
|
|
1081
|
+
this.debugLog("Warning: User not found in database:", tgUsername);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
return false;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1022
1087
|
/**
|
|
1023
1088
|
* Runs a task with bidirectional communication support
|
|
1024
1089
|
* @param ctx Telegram context
|
|
@@ -1227,9 +1292,71 @@ export class App {
|
|
|
1227
1292
|
}
|
|
1228
1293
|
|
|
1229
1294
|
debugLog(...args: any[]): void {
|
|
1230
|
-
if (this.config.debug)
|
|
1231
|
-
|
|
1232
|
-
|
|
1295
|
+
if (!this.config.debug) return;
|
|
1296
|
+
|
|
1297
|
+
// Color palette
|
|
1298
|
+
const colors = {
|
|
1299
|
+
reset: "\x1b[0m",
|
|
1300
|
+
bright: "\x1b[1m",
|
|
1301
|
+
dim: "\x1b[2m",
|
|
1302
|
+
underscore: "\x1b[4m",
|
|
1303
|
+
fg: {
|
|
1304
|
+
red: "\x1b[31m",
|
|
1305
|
+
green: "\x1b[32m",
|
|
1306
|
+
yellow: "\x1b[33m",
|
|
1307
|
+
blue: "\x1b[34m",
|
|
1308
|
+
magenta: "\x1b[35m",
|
|
1309
|
+
cyan: "\x1b[36m",
|
|
1310
|
+
white: "\x1b[37m",
|
|
1311
|
+
},
|
|
1312
|
+
bg: {
|
|
1313
|
+
red: "\x1b[41m",
|
|
1314
|
+
green: "\x1b[42m",
|
|
1315
|
+
yellow: "\x1b[43m",
|
|
1316
|
+
blue: "\x1b[44m",
|
|
1317
|
+
magenta: "\x1b[45m",
|
|
1318
|
+
cyan: "\x1b[46m",
|
|
1319
|
+
white: "\x1b[47m",
|
|
1320
|
+
},
|
|
1321
|
+
};
|
|
1322
|
+
|
|
1323
|
+
// Timestamp
|
|
1324
|
+
const now = new Date();
|
|
1325
|
+
const timestamp = `${colors.dim}${colors.fg.cyan}[${now.toLocaleTimeString()}]${colors.reset}`;
|
|
1326
|
+
|
|
1327
|
+
// Source (App debug)
|
|
1328
|
+
const source = `${colors.bright}${colors.fg.magenta}AppDebug${colors.reset}`;
|
|
1329
|
+
|
|
1330
|
+
// Format args: highlight objects, errors, etc.
|
|
1331
|
+
const formattedArgs = args.map(arg => {
|
|
1332
|
+
if (arg instanceof Error) {
|
|
1333
|
+
return `${colors.fg.red}${arg.stack || arg.message}${colors.reset}`;
|
|
1334
|
+
}
|
|
1335
|
+
if (typeof arg === "object" && arg !== null) {
|
|
1336
|
+
try {
|
|
1337
|
+
return `${colors.fg.yellow}${JSON.stringify(arg, null, 2)}${colors.reset}`;
|
|
1338
|
+
} catch {
|
|
1339
|
+
return `${colors.fg.yellow}[Object]${colors.reset}`;
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
if (typeof arg === "string") {
|
|
1343
|
+
// Highlight keywords
|
|
1344
|
+
if (/error|fail|exception/i.test(arg)) {
|
|
1345
|
+
return `${colors.fg.red}${arg}${colors.reset}`;
|
|
1346
|
+
}
|
|
1347
|
+
if (/success|done|complete/i.test(arg)) {
|
|
1348
|
+
return `${colors.fg.green}${arg}${colors.reset}`;
|
|
1349
|
+
}
|
|
1350
|
+
if (/warn|warning/i.test(arg)) {
|
|
1351
|
+
return `${colors.fg.yellow}${arg}${colors.reset}`;
|
|
1352
|
+
}
|
|
1353
|
+
return `${colors.fg.white}${arg}${colors.reset}`;
|
|
1354
|
+
}
|
|
1355
|
+
return String(arg);
|
|
1356
|
+
});
|
|
1357
|
+
|
|
1358
|
+
// Compose and print
|
|
1359
|
+
console.log(`${timestamp} ${source}:`, ...formattedArgs);
|
|
1233
1360
|
}
|
|
1234
1361
|
|
|
1235
1362
|
get sections(): SectionList {
|
package/src/core/Model.ts
CHANGED
|
@@ -63,6 +63,10 @@ export class RunSectionRoute {
|
|
|
63
63
|
return this;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
static for(sectionId: string, methodName: string = 'index'): RunSectionRoute {
|
|
67
|
+
return new RunSectionRoute().section(sectionId).method(methodName);
|
|
68
|
+
}
|
|
69
|
+
|
|
66
70
|
hasTriggers(): boolean {
|
|
67
71
|
return this.runParams.triggers.length > 0;
|
|
68
72
|
}
|
|
@@ -15,6 +15,7 @@ export class Section {
|
|
|
15
15
|
static actionRoutes: { [key: string]: string };
|
|
16
16
|
public sectionId: string = "BaseSection";
|
|
17
17
|
public route: RunSectionRoute;
|
|
18
|
+
public runForMessageHandler: boolean = false;
|
|
18
19
|
protected ctx: Telegraf2byteContext;
|
|
19
20
|
protected bot: Telegraf<Telegraf2byteContext>;
|
|
20
21
|
protected app: App;
|
package/src/index.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
export { App } from './core/App';
|
|
3
3
|
export { ApiService } from './core/ApiService';
|
|
4
4
|
export { ApiServiceManager } from './core/ApiServiceManager';
|
|
5
|
+
export { MassSendApiService } from './workflow/services/MassSendApiService';
|
|
5
6
|
export { BotArtisan } from './core/BotArtisan';
|
|
6
7
|
export { BotMigration } from './core/BotMigration';
|
|
7
8
|
export { BotSeeder } from './core/BotSeeder';
|
package/src/types.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { Section } from './illumination/Section';
|
|
|
3
3
|
import { UserStore } from './user/UserStore';
|
|
4
4
|
import { Telegraf2byteContext } from './illumination/Telegraf2byteContext';
|
|
5
5
|
import { RunSectionRoute } from './illumination/RunSectionRoute';
|
|
6
|
+
import { ApiService } from './core/ApiService';
|
|
6
7
|
|
|
7
8
|
export interface AppConfig {
|
|
8
9
|
accessPublic: boolean;
|
|
@@ -24,6 +25,7 @@ export interface AppConfig {
|
|
|
24
25
|
terminateSigTerm: boolean;
|
|
25
26
|
keepSectionInstances: boolean;
|
|
26
27
|
botCwd: string;
|
|
28
|
+
services: ApiService[];
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
export interface SectionOptions {
|
|
@@ -1,83 +1,94 @@
|
|
|
1
|
-
import { ExtraReplyMessage } from
|
|
2
|
-
import { ApiService} from
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
1
|
+
import { ExtraReplyMessage } from "telegraf/typings/telegram-types";
|
|
2
|
+
import { ApiService } from "../../core/ApiService";
|
|
3
|
+
import { UserModel } from "../../user/UserModel";
|
|
4
|
+
|
|
5
|
+
export type MassSendApiParams = {
|
|
6
|
+
port?: number;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export class MassSendApiService extends ApiService<MassSendApiParams> {
|
|
10
|
+
public override name = "MassSendApiService";
|
|
11
|
+
private bunServerInstance: any;
|
|
12
|
+
|
|
13
|
+
public async setup(): Promise<void> {
|
|
14
|
+
return Promise.resolve();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
public async unsetup(): Promise<void> {
|
|
18
|
+
return Promise.resolve();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public async run(): Promise<void> {
|
|
22
|
+
this.bunServerInstance = Bun.serve({
|
|
23
|
+
port: this.params.port || this.app.configApp.envConfig.BOT_APP_API_PORT || 3033,
|
|
24
|
+
routes: {
|
|
25
|
+
"/": async (req) => {
|
|
26
|
+
const receivedData = (await req.json()) as {
|
|
27
|
+
userIds?: number[];
|
|
28
|
+
message?: string;
|
|
29
|
+
extra?: ExtraReplyMessage;
|
|
30
|
+
};
|
|
31
|
+
this.app.debugLog("Received data for mass message:", receivedData);
|
|
32
|
+
|
|
33
|
+
let userIds: number[] = [];
|
|
34
|
+
let message: string = "Hello from MassSendApiService";
|
|
35
|
+
|
|
36
|
+
if (receivedData && typeof receivedData == "object") {
|
|
37
|
+
userIds = receivedData?.userIds || [];
|
|
38
|
+
message = receivedData?.message || "Hello from MassSendApiService";
|
|
39
|
+
|
|
40
|
+
this.sendMassMessage(userIds, message, receivedData.extra);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return Response.json({ status: 200, body: "Mass message sending initiated." });
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
this.app.debugLog(
|
|
49
|
+
`MassSendApiService Bun server running at http://localhost:${this.bunServerInstance.port}/`
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
return Promise.resolve();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private async sendMassMessage(
|
|
56
|
+
userIds: number[] = [],
|
|
57
|
+
message: string,
|
|
58
|
+
extra?: ExtraReplyMessage
|
|
59
|
+
): Promise<void> {
|
|
60
|
+
if (userIds.length === 0) {
|
|
61
|
+
if (!db) {
|
|
62
|
+
throw new Error("Database connection is not established.");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
UserModel.setDatabase(db);
|
|
66
|
+
|
|
67
|
+
this.app.debugLog("Fetching all users for mass message...");
|
|
68
|
+
const users = UserModel.getAll();
|
|
69
|
+
|
|
70
|
+
this.app.debugLog("Fetched users for mass message:", users);
|
|
71
|
+
|
|
72
|
+
if (users && users.length > 0) {
|
|
73
|
+
for (const user of users) {
|
|
74
|
+
this.app.debugLog(`Sending message to user ID: ${user.tgId} username: ${user.username}`);
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const extraOptions = extra || {};
|
|
78
|
+
await this.app.bot.telegram.sendMessage(user.tgId, message, extraOptions);
|
|
79
|
+
this.app.debugLog(`Message sent to user ID: ${user.tgId} username: ${user.username}`);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
this.app.debugLog(
|
|
82
|
+
`Sending message ${message} to user ID: ${user.tgId} username: ${user.username} failed`,
|
|
83
|
+
error
|
|
84
|
+
);
|
|
85
|
+
this.app.debugLog(
|
|
86
|
+
`Failed to send message to user ID: ${user.tgId} username: ${user.username}`,
|
|
87
|
+
error
|
|
88
|
+
);
|
|
89
|
+
}
|
|
81
90
|
}
|
|
91
|
+
}
|
|
82
92
|
}
|
|
93
|
+
}
|
|
83
94
|
}
|