@2byte/tgbot-framework 1.0.13 → 1.0.14
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 +129 -31
- package/src/core/Model.ts +1 -1
- 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>;
|
|
@@ -188,6 +190,11 @@ export class App {
|
|
|
188
190
|
return this;
|
|
189
191
|
}
|
|
190
192
|
|
|
193
|
+
services(services: ApiService[]): this {
|
|
194
|
+
this.app.config.services = services;
|
|
195
|
+
return this;
|
|
196
|
+
}
|
|
197
|
+
|
|
191
198
|
build(): App {
|
|
192
199
|
return this.app;
|
|
193
200
|
}
|
|
@@ -274,7 +281,7 @@ export class App {
|
|
|
274
281
|
}
|
|
275
282
|
|
|
276
283
|
// Check access by username and register user if not exists
|
|
277
|
-
if (!this.config.userStorage.exists(tgUsername)) {
|
|
284
|
+
if (!this.config.userStorage.exists(tgUsername) && !this.rememberUser(tgUsername)) {
|
|
278
285
|
const isAuthByUsername = !this.config.accessPublic && !accessKey;
|
|
279
286
|
|
|
280
287
|
// check access by username for private bots
|
|
@@ -288,6 +295,7 @@ export class App {
|
|
|
288
295
|
checkAccess &&
|
|
289
296
|
checkAccess.every((name) => name.toLowerCase() !== requestUsername.toLowerCase())
|
|
290
297
|
) {
|
|
298
|
+
this.debugLog("Username access denied:", requestUsername);
|
|
291
299
|
return ctx.reply("Access denied. Your username is not in the access list.");
|
|
292
300
|
}
|
|
293
301
|
this.debugLog("Username access granted.");
|
|
@@ -438,7 +446,10 @@ export class App {
|
|
|
438
446
|
}
|
|
439
447
|
});
|
|
440
448
|
} else {
|
|
441
|
-
this.debugLog(
|
|
449
|
+
this.debugLog(
|
|
450
|
+
"Message input already handled by awaitingInput or awaitingInputPromise. stateAfterValidatedUserResponse:",
|
|
451
|
+
ctx.userSession.stateAfterValidatedUserResponse
|
|
452
|
+
);
|
|
442
453
|
}
|
|
443
454
|
|
|
444
455
|
delete ctx.userSession.stateAfterValidatedUserResponse; // Clear the state after handling the message
|
|
@@ -462,7 +473,7 @@ export class App {
|
|
|
462
473
|
// Get the largest photo (the last one in the array is usually the largest)
|
|
463
474
|
const largestPhoto = photo[photo.length - 1];
|
|
464
475
|
await this.handleUserInput(ctx, largestPhoto, "photo");
|
|
465
|
-
|
|
476
|
+
|
|
466
477
|
delete ctx.userSession.stateAfterValidatedUserResponse; // Clear the state after handling the message
|
|
467
478
|
});
|
|
468
479
|
}
|
|
@@ -470,39 +481,38 @@ export class App {
|
|
|
470
481
|
private async registerServices() {
|
|
471
482
|
this.apiServiceManager = ApiServiceManager.init(this);
|
|
472
483
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
throw error;
|
|
479
|
-
}
|
|
484
|
+
// Register services from config
|
|
485
|
+
this.debugLog(
|
|
486
|
+
"Registering services from config:",
|
|
487
|
+
this.config.services.map((service) => service.constructor.name)
|
|
488
|
+
);
|
|
480
489
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
490
|
+
this.config.services.forEach((service) => {
|
|
491
|
+
this.debugLog(`Registering service: ${service.constructor.name}`);
|
|
492
|
+
this.apiServiceManager.registerService(service.name, service.setApp(this));
|
|
493
|
+
this.debugLog(`Service ${service.constructor.name} registered`);
|
|
494
|
+
});
|
|
486
495
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
};
|
|
496
|
+
try {
|
|
497
|
+
await this.apiServiceManager.setupAllServices();
|
|
498
|
+
} catch (error) {
|
|
499
|
+
this.debugLog("Error setting up services:", error);
|
|
500
|
+
throw error;
|
|
501
|
+
}
|
|
494
502
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
503
|
+
try {
|
|
504
|
+
await this.apiServiceManager.runAllServices();
|
|
505
|
+
} catch (error) {
|
|
506
|
+
this.debugLog("Error running services:", error);
|
|
507
|
+
throw error;
|
|
508
|
+
}
|
|
499
509
|
}
|
|
500
510
|
|
|
501
511
|
private async unregisterServices() {
|
|
502
512
|
this.apiServiceManager = ApiServiceManager.init(this);
|
|
503
513
|
|
|
504
514
|
try {
|
|
505
|
-
this.apiServiceManager.unsetupAllServices();
|
|
515
|
+
await this.apiServiceManager.unsetupAllServices();
|
|
506
516
|
} catch (error) {
|
|
507
517
|
this.debugLog("Error unsetting up services:", error);
|
|
508
518
|
throw error;
|
|
@@ -923,7 +933,7 @@ export class App {
|
|
|
923
933
|
if (sectionRoute.hasTriggers()) {
|
|
924
934
|
this.debugLog("Section route has triggers, executing them before running method:", sectionId);
|
|
925
935
|
sectionRoute.getTriggers().forEach((trigger) => {
|
|
926
|
-
if (trigger.name ===
|
|
936
|
+
if (trigger.name === "cbBeforeRunMethod") {
|
|
927
937
|
this.debugLog(`Executing cbBeforeRunMethod trigger for section ${sectionId}`);
|
|
928
938
|
this.debugLog("Trigger details:", trigger);
|
|
929
939
|
|
|
@@ -1019,6 +1029,32 @@ export class App {
|
|
|
1019
1029
|
}
|
|
1020
1030
|
}
|
|
1021
1031
|
|
|
1032
|
+
/**
|
|
1033
|
+
* 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.
|
|
1034
|
+
* @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.
|
|
1035
|
+
* @returns A boolean indicating whether the user was successfully remembered (true) or not (false).
|
|
1036
|
+
*/
|
|
1037
|
+
async rememberUser(tgUsername: string): Promise<boolean> {
|
|
1038
|
+
if (this.config.userStorage && !this.config.userStorage.exists(tgUsername)) {
|
|
1039
|
+
this.debugLog("Warning: Username not found in storage:", tgUsername);
|
|
1040
|
+
this.debugLog("Trying getting to database:", tgUsername);
|
|
1041
|
+
|
|
1042
|
+
// Try to get user from database and add to storage
|
|
1043
|
+
UserModel.resolveDb();
|
|
1044
|
+
const userFromDb = UserModel.findByUsername(tgUsername);
|
|
1045
|
+
|
|
1046
|
+
if (userFromDb) {
|
|
1047
|
+
this.config.userStorage.add(tgUsername, userFromDb);
|
|
1048
|
+
this.debugLog("Success: User found in database and added to storage:", tgUsername);
|
|
1049
|
+
this.debugLog('Success: Remembered user "' + tgUsername + '"');
|
|
1050
|
+
return true;
|
|
1051
|
+
} else {
|
|
1052
|
+
this.debugLog("Warning: User not found in database:", tgUsername);
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
return false;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1022
1058
|
/**
|
|
1023
1059
|
* Runs a task with bidirectional communication support
|
|
1024
1060
|
* @param ctx Telegram context
|
|
@@ -1227,9 +1263,71 @@ export class App {
|
|
|
1227
1263
|
}
|
|
1228
1264
|
|
|
1229
1265
|
debugLog(...args: any[]): void {
|
|
1230
|
-
if (this.config.debug)
|
|
1231
|
-
|
|
1232
|
-
|
|
1266
|
+
if (!this.config.debug) return;
|
|
1267
|
+
|
|
1268
|
+
// Color palette
|
|
1269
|
+
const colors = {
|
|
1270
|
+
reset: "\x1b[0m",
|
|
1271
|
+
bright: "\x1b[1m",
|
|
1272
|
+
dim: "\x1b[2m",
|
|
1273
|
+
underscore: "\x1b[4m",
|
|
1274
|
+
fg: {
|
|
1275
|
+
red: "\x1b[31m",
|
|
1276
|
+
green: "\x1b[32m",
|
|
1277
|
+
yellow: "\x1b[33m",
|
|
1278
|
+
blue: "\x1b[34m",
|
|
1279
|
+
magenta: "\x1b[35m",
|
|
1280
|
+
cyan: "\x1b[36m",
|
|
1281
|
+
white: "\x1b[37m",
|
|
1282
|
+
},
|
|
1283
|
+
bg: {
|
|
1284
|
+
red: "\x1b[41m",
|
|
1285
|
+
green: "\x1b[42m",
|
|
1286
|
+
yellow: "\x1b[43m",
|
|
1287
|
+
blue: "\x1b[44m",
|
|
1288
|
+
magenta: "\x1b[45m",
|
|
1289
|
+
cyan: "\x1b[46m",
|
|
1290
|
+
white: "\x1b[47m",
|
|
1291
|
+
},
|
|
1292
|
+
};
|
|
1293
|
+
|
|
1294
|
+
// Timestamp
|
|
1295
|
+
const now = new Date();
|
|
1296
|
+
const timestamp = `${colors.dim}${colors.fg.cyan}[${now.toLocaleTimeString()}]${colors.reset}`;
|
|
1297
|
+
|
|
1298
|
+
// Source (App debug)
|
|
1299
|
+
const source = `${colors.bright}${colors.fg.magenta}AppDebug${colors.reset}`;
|
|
1300
|
+
|
|
1301
|
+
// Format args: highlight objects, errors, etc.
|
|
1302
|
+
const formattedArgs = args.map(arg => {
|
|
1303
|
+
if (arg instanceof Error) {
|
|
1304
|
+
return `${colors.fg.red}${arg.stack || arg.message}${colors.reset}`;
|
|
1305
|
+
}
|
|
1306
|
+
if (typeof arg === "object" && arg !== null) {
|
|
1307
|
+
try {
|
|
1308
|
+
return `${colors.fg.yellow}${JSON.stringify(arg, null, 2)}${colors.reset}`;
|
|
1309
|
+
} catch {
|
|
1310
|
+
return `${colors.fg.yellow}[Object]${colors.reset}`;
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
if (typeof arg === "string") {
|
|
1314
|
+
// Highlight keywords
|
|
1315
|
+
if (/error|fail|exception/i.test(arg)) {
|
|
1316
|
+
return `${colors.fg.red}${arg}${colors.reset}`;
|
|
1317
|
+
}
|
|
1318
|
+
if (/success|done|complete/i.test(arg)) {
|
|
1319
|
+
return `${colors.fg.green}${arg}${colors.reset}`;
|
|
1320
|
+
}
|
|
1321
|
+
if (/warn|warning/i.test(arg)) {
|
|
1322
|
+
return `${colors.fg.yellow}${arg}${colors.reset}`;
|
|
1323
|
+
}
|
|
1324
|
+
return `${colors.fg.white}${arg}${colors.reset}`;
|
|
1325
|
+
}
|
|
1326
|
+
return String(arg);
|
|
1327
|
+
});
|
|
1328
|
+
|
|
1329
|
+
// Compose and print
|
|
1330
|
+
console.log(`${timestamp} ${source}:`, ...formattedArgs);
|
|
1233
1331
|
}
|
|
1234
1332
|
|
|
1235
1333
|
get sections(): SectionList {
|
package/src/core/Model.ts
CHANGED
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
|
+
|
|
41
|
+
this.sendMassMessage(userIds, message, receivedData.extra);
|
|
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
|
}
|