botforje 0.0.1
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/LICENSE +21 -0
- package/README.md +381 -0
- package/dist/action/action.js +44 -0
- package/dist/action/catalog.js +40 -0
- package/dist/action/cooldown.js +60 -0
- package/dist/action/executor.js +23 -0
- package/dist/action/template.js +13 -0
- package/dist/action/types.js +2 -0
- package/dist/action/webhook.js +36 -0
- package/dist/actions/action.js +26 -0
- package/dist/actions/cooldown.js +66 -0
- package/dist/actions/request.js +40 -0
- package/dist/api/routes/auth.js +56 -0
- package/dist/api/routes/bots.js +51 -0
- package/dist/api/routes/config.js +24 -0
- package/dist/api/routes/health.js +15 -0
- package/dist/api/routes/index.js +14 -0
- package/dist/api/routes/messages.js +69 -0
- package/dist/api/routes/sessions.js +97 -0
- package/dist/api/routes/status.js +38 -0
- package/dist/api/server.js +153 -0
- package/dist/auth/service.js +102 -0
- package/dist/boot/fleet.js +207 -0
- package/dist/bot/bot.js +30 -0
- package/dist/bot/fleet.js +211 -0
- package/dist/bot/fuzzy.js +20 -0
- package/dist/bot/mapper.js +47 -0
- package/dist/bot/types.js +2 -0
- package/dist/bot/validation.js +38 -0
- package/dist/bot.js +31 -0
- package/dist/cli/create-bot.js +84 -0
- package/dist/cli.js +138 -0
- package/dist/commands/auth.js +200 -0
- package/dist/commands/create-bot.js +64 -0
- package/dist/commands/guide.js +563 -0
- package/dist/commands/lock.js +73 -0
- package/dist/commands/status.js +145 -0
- package/dist/commands/unlock.js +88 -0
- package/dist/commands/validate.js +19 -0
- package/dist/config/mapper.js +152 -0
- package/dist/config/schema.js +2 -0
- package/dist/config/validation.js +705 -0
- package/dist/config/watcher.js +145 -0
- package/dist/config/yaml.js +197 -0
- package/dist/fleet.js +247 -0
- package/dist/flow/executor.js +286 -0
- package/dist/flow/flow.js +2 -0
- package/dist/flow/mapper.js +72 -0
- package/dist/flow/state.js +115 -0
- package/dist/flow/types.js +2 -0
- package/dist/graph/executor.js +294 -0
- package/dist/graph/graph.js +2 -0
- package/dist/graph/state.js +118 -0
- package/dist/helpers/data.js +42 -0
- package/dist/helpers/fuzzy.js +30 -0
- package/dist/helpers/logger.js +89 -0
- package/dist/helpers/validation.js +38 -0
- package/dist/index.js +92 -0
- package/dist/messages/contracts.js +2 -0
- package/dist/messages/inbox.js +58 -0
- package/dist/messages/outbox.js +136 -0
- package/dist/services/auto-response.js +62 -0
- package/dist/services/cooldown.js +60 -0
- package/dist/services/fleet.js +207 -0
- package/dist/services/flow-executor.js +195 -0
- package/dist/services/flow-state.js +118 -0
- package/dist/services/inbox.js +50 -0
- package/dist/services/message-handler.js +78 -0
- package/dist/services/message-queue.js +138 -0
- package/dist/services/outbox.js +138 -0
- package/dist/services/session.js +118 -0
- package/dist/services/webhook.js +87 -0
- package/dist/utils/logger.js +89 -0
- package/dist/utils/webhook.js +36 -0
- package/dist/validation/validate.js +592 -0
- package/dist/whatsapp/client.js +293 -0
- package/dist/whatsapp/session.js +158 -0
- package/dist/whatsapp/types.js +109 -0
- package/dist/whatsapp/whatsapp.js +109 -0
- package/package.json +97 -0
- package/scripts/postinstall.js +45 -0
- package/scripts/preuninstall.js +48 -0
- package/scripts/setup-systemd.js +91 -0
- package/service/botforje.service.template +25 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
const commander_1 = require("commander");
|
|
38
|
+
const child_process_1 = require("child_process");
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const fleet_1 = require("./bot/fleet");
|
|
42
|
+
const outbox_1 = require("./services/outbox");
|
|
43
|
+
const server_1 = require("./api/server");
|
|
44
|
+
const yaml_1 = require("./config/yaml");
|
|
45
|
+
const logger_1 = require("./utils/logger");
|
|
46
|
+
const client_1 = require("./whatsapp/client");
|
|
47
|
+
const create_bot_1 = require("./cli/create-bot");
|
|
48
|
+
const packageJsonPath = path.join(__dirname, '..', 'package.json');
|
|
49
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
50
|
+
const program = new commander_1.Command();
|
|
51
|
+
program
|
|
52
|
+
.name('botforge')
|
|
53
|
+
.description('CLI tool for creating and managing WhatsApp bots')
|
|
54
|
+
.version(packageJson.version)
|
|
55
|
+
.option('-c, --config <path>', 'Path to config file (default: ~/.config/wweb-botforge/config.yml)');
|
|
56
|
+
program
|
|
57
|
+
.command('setup')
|
|
58
|
+
.description('Setup/repair systemd service')
|
|
59
|
+
.action(() => {
|
|
60
|
+
const setupScript = path.join(__dirname, '..', 'scripts', 'setup-systemd.js');
|
|
61
|
+
(0, child_process_1.execSync)(`node ${setupScript}`, { stdio: 'inherit' });
|
|
62
|
+
});
|
|
63
|
+
program
|
|
64
|
+
.command('create-bot')
|
|
65
|
+
.description('Create a new WhatsApp bot interactively')
|
|
66
|
+
.action(() => (0, create_bot_1.runCreateBot)(program.opts().config));
|
|
67
|
+
program
|
|
68
|
+
.command('start')
|
|
69
|
+
.description('Start the WhatsApp bots')
|
|
70
|
+
.action(async () => {
|
|
71
|
+
await startBots(program.opts().config);
|
|
72
|
+
});
|
|
73
|
+
async function startBots(configPath) {
|
|
74
|
+
if (configPath)
|
|
75
|
+
(0, yaml_1.setConfigPath)(configPath);
|
|
76
|
+
const configFile = await (0, yaml_1.loadConfig)(configPath);
|
|
77
|
+
if (configFile.global) {
|
|
78
|
+
(0, logger_1.setGlobalLogger)(configFile.global);
|
|
79
|
+
(0, client_1.setGlobalConfig)(configFile.global);
|
|
80
|
+
}
|
|
81
|
+
const logger = (0, logger_1.getLogger)();
|
|
82
|
+
logger.info('WWeb BotForge - Starting bots...');
|
|
83
|
+
const outboxService = new outbox_1.OutboxService();
|
|
84
|
+
const fleet = new fleet_1.BotFleet(outboxService);
|
|
85
|
+
const bots = await fleet.start(configFile);
|
|
86
|
+
if (configFile.global?.apiEnabled) {
|
|
87
|
+
const apiPort = configFile.global.apiPort || 3000;
|
|
88
|
+
const apiServer = new server_1.ApiServer(outboxService, bots, apiPort);
|
|
89
|
+
await apiServer.start();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
program.parse();
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.InboxService = void 0;
|
|
4
|
+
const logger_1 = require("../helpers/logger");
|
|
5
|
+
class InboxService {
|
|
6
|
+
constructor(graphExecutor) {
|
|
7
|
+
this.graphExecutor = graphExecutor;
|
|
8
|
+
}
|
|
9
|
+
get logger() {
|
|
10
|
+
return (0, logger_1.getLogger)();
|
|
11
|
+
}
|
|
12
|
+
registerBot(bot) {
|
|
13
|
+
if (!bot.channel) {
|
|
14
|
+
throw new Error(`Bot "${bot.id}" does not have a registered channel`);
|
|
15
|
+
}
|
|
16
|
+
bot.channel.onMessage((message) => {
|
|
17
|
+
this.handleIncomingMessage(bot, message);
|
|
18
|
+
});
|
|
19
|
+
bot.channel.onReady(() => {
|
|
20
|
+
this.logger.info(`Bot "${bot.id}" is ready and listening for messages`);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
async handleIncomingMessage(bot, message) {
|
|
24
|
+
this.logger.info(`Message received for bot "${bot.id}": ${message.content.substring(0, 50)}...`);
|
|
25
|
+
try {
|
|
26
|
+
if (message.metadata?.fromMe) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (this.isSenderNotAllowed(bot, message.from)) {
|
|
30
|
+
this.logger.debug(`Ignoring message from "${message.from}" for bot "${bot.id}" (sender not in allowed list)`);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (this.isSenderIgnored(bot, message.from)) {
|
|
34
|
+
this.logger.debug(`Ignoring message from "${message.from}" for bot "${bot.id}" (sender in ignored list)`);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (bot.settings.ignoreGroups && this.isGroupMessage(message.from)) {
|
|
38
|
+
this.logger.debug(`Ignoring group message for bot "${bot.id}"`);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
await this.graphExecutor.handleMessage(bot, message);
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
45
|
+
this.logger.error(`Error handling message for bot "${bot.id}": ${msg}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
isSenderNotAllowed(bot, sender) {
|
|
49
|
+
return bot.settings.allowedSenders.length > 0 && !bot.settings.allowedSenders.includes(sender);
|
|
50
|
+
}
|
|
51
|
+
isSenderIgnored(bot, sender) {
|
|
52
|
+
return bot.settings.ignoredSenders.includes(sender);
|
|
53
|
+
}
|
|
54
|
+
isGroupMessage(from) {
|
|
55
|
+
return from.includes('g.us');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
exports.InboxService = InboxService;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OutboxService = void 0;
|
|
4
|
+
const logger_1 = require("../helpers/logger");
|
|
5
|
+
class OutboxService {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.queues = new Map();
|
|
8
|
+
this.processing = new Map();
|
|
9
|
+
this.delays = new Map();
|
|
10
|
+
this.sendCallbacks = new Map();
|
|
11
|
+
}
|
|
12
|
+
get logger() {
|
|
13
|
+
return (0, logger_1.getLogger)();
|
|
14
|
+
}
|
|
15
|
+
setBotDelay(botId, delayMs) {
|
|
16
|
+
this.delays.set(botId, delayMs);
|
|
17
|
+
this.logger.info(`Queue delay for bot "${botId}" set to ${delayMs}ms`);
|
|
18
|
+
}
|
|
19
|
+
setBotSendCallback(botId, callback) {
|
|
20
|
+
this.sendCallbacks.set(botId, callback);
|
|
21
|
+
}
|
|
22
|
+
setupBotQueue(bot) {
|
|
23
|
+
const botId = bot.id;
|
|
24
|
+
this.setBotDelay(botId, bot.settings.queueDelay);
|
|
25
|
+
this.setBotSendCallback(botId, async (botId, message) => {
|
|
26
|
+
await bot.channel.send(message);
|
|
27
|
+
});
|
|
28
|
+
this.logger.info(`Configured message queue for bot "${bot.id}": delay=${bot.settings.queueDelay}ms`);
|
|
29
|
+
}
|
|
30
|
+
enqueue(botId, to, content, metadata) {
|
|
31
|
+
const messageId = `${botId}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
32
|
+
this.logger.debug(`Queueing message for bot "${botId}" with metadata: ${JSON.stringify(metadata)}`);
|
|
33
|
+
const outgoingMessage = {
|
|
34
|
+
to,
|
|
35
|
+
content,
|
|
36
|
+
metadata,
|
|
37
|
+
};
|
|
38
|
+
const queuedMessage = {
|
|
39
|
+
id: messageId,
|
|
40
|
+
botId,
|
|
41
|
+
message: outgoingMessage,
|
|
42
|
+
timestamp: Date.now(),
|
|
43
|
+
};
|
|
44
|
+
if (!this.queues.has(botId)) {
|
|
45
|
+
this.queues.set(botId, []);
|
|
46
|
+
}
|
|
47
|
+
const botQueue = this.queues.get(botId);
|
|
48
|
+
botQueue.push(queuedMessage);
|
|
49
|
+
this.logger.info(`Message queued for bot "${botId}": ${messageId} (queue size: ${botQueue.length})`);
|
|
50
|
+
if (!this.processing.get(botId)) {
|
|
51
|
+
this.startProcessing(botId);
|
|
52
|
+
}
|
|
53
|
+
return messageId;
|
|
54
|
+
}
|
|
55
|
+
async startProcessing(botId) {
|
|
56
|
+
if (this.processing.get(botId)) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
this.processing.set(botId, true);
|
|
60
|
+
this.logger.info(`Started processing queue for bot "${botId}"`);
|
|
61
|
+
const botQueue = this.queues.get(botId);
|
|
62
|
+
if (!botQueue) {
|
|
63
|
+
this.processing.set(botId, false);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
while (botQueue.length > 0) {
|
|
67
|
+
const messageData = botQueue.shift();
|
|
68
|
+
const delay = this.delays.get(botId) || 2000;
|
|
69
|
+
try {
|
|
70
|
+
this.logger.debug(`Waiting ${delay}ms before sending message ${messageData.id} from bot "${botId}"...`);
|
|
71
|
+
await this.delay(delay);
|
|
72
|
+
this.logger.info(`Processing queued message: ${messageData.id} from bot "${botId}"`);
|
|
73
|
+
const callback = this.sendCallbacks.get(botId);
|
|
74
|
+
if (callback) {
|
|
75
|
+
await callback(botId, messageData.message);
|
|
76
|
+
this.logger.info(`Queued message sent successfully: ${messageData.id}`);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
this.logger.error(`No send callback configured for bot "${botId}"`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
84
|
+
this.logger.error(`Error sending queued message ${messageData.id} from bot "${botId}": ${msg}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
this.processing.set(botId, false);
|
|
88
|
+
this.logger.info(`Queue processing completed for bot "${botId}"`);
|
|
89
|
+
}
|
|
90
|
+
getBotQueueStatus(botId) {
|
|
91
|
+
const queue = this.queues.get(botId) || [];
|
|
92
|
+
const isProcessing = this.processing.get(botId) || false;
|
|
93
|
+
const delay = this.delays.get(botId) || 2000;
|
|
94
|
+
return {
|
|
95
|
+
botId,
|
|
96
|
+
queueSize: queue.length,
|
|
97
|
+
isProcessing,
|
|
98
|
+
delayMs: delay,
|
|
99
|
+
hasCallback: this.sendCallbacks.has(botId),
|
|
100
|
+
nextMessage: queue.length > 0 ? {
|
|
101
|
+
id: queue[0].id,
|
|
102
|
+
to: queue[0].message.to,
|
|
103
|
+
age: Date.now() - queue[0].timestamp,
|
|
104
|
+
} : null,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
clearBotQueue(botId) {
|
|
108
|
+
this.queues.delete(botId);
|
|
109
|
+
this.logger.info(`Queue cleared for bot "${botId}"`);
|
|
110
|
+
}
|
|
111
|
+
getAllQueuesStatus() {
|
|
112
|
+
const allStatuses = [];
|
|
113
|
+
for (const [botId] of this.queues.entries()) {
|
|
114
|
+
const status = this.getBotQueueStatus(botId);
|
|
115
|
+
allStatuses.push(status);
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
totalQueues: allStatuses.length,
|
|
119
|
+
queues: allStatuses,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
async shutdown() {
|
|
123
|
+
this.logger.info('Shutting down outbox service...');
|
|
124
|
+
for (const [botId] of this.processing.entries()) {
|
|
125
|
+
this.processing.set(botId, false);
|
|
126
|
+
}
|
|
127
|
+
this.queues.clear();
|
|
128
|
+
this.delays.clear();
|
|
129
|
+
this.sendCallbacks.clear();
|
|
130
|
+
this.logger.info('Outbox service shut down');
|
|
131
|
+
}
|
|
132
|
+
delay(ms) {
|
|
133
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
exports.OutboxService = OutboxService;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AutoResponseService = void 0;
|
|
4
|
+
const fuzzy_1 = require("../bot/fuzzy");
|
|
5
|
+
const logger_1 = require("../utils/logger");
|
|
6
|
+
class AutoResponseService {
|
|
7
|
+
constructor(cooldownService) {
|
|
8
|
+
this.cooldownService = cooldownService;
|
|
9
|
+
}
|
|
10
|
+
get logger() {
|
|
11
|
+
return (0, logger_1.getLogger)();
|
|
12
|
+
}
|
|
13
|
+
processMessage(bot, message) {
|
|
14
|
+
this.cooldownService.cleanupExpiredCooldowns();
|
|
15
|
+
if (message.metadata?.fromMe) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
if (bot.settings.ignoreGroups && this.isGroupMessage(message.from)) {
|
|
19
|
+
this.logger.debug(`🚫 Ignoring group message for bot "${bot.name}"`);
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
const sorted = [...bot.autoResponses].sort((a, b) => b.priority - a.priority);
|
|
23
|
+
const matchingResponse = sorted.find(response => (0, fuzzy_1.matchFuzzy)(response.fuzzySegments, message.content, response.fuzzyThreshold));
|
|
24
|
+
if (matchingResponse) {
|
|
25
|
+
const cooldownMs = (matchingResponse.cooldown || 0) * 1000;
|
|
26
|
+
const patternKey = matchingResponse.patternString;
|
|
27
|
+
if (this.cooldownService.isOnCooldown(message.from, patternKey, cooldownMs)) {
|
|
28
|
+
this.logger.debug(`⏳ Cooldown active for sender "${message.from}" on pattern "${patternKey}" in bot "${bot.name}"`);
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
this.cooldownService.setCooldown(message.from, patternKey);
|
|
32
|
+
this.logger.info(`🤖 Auto-response triggered for bot "${bot.name}": "${patternKey}" → "${matchingResponse.response}"`);
|
|
33
|
+
return matchingResponse;
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
shouldProcessMessage(bot, message) {
|
|
38
|
+
if (message.metadata?.fromMe) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
if (bot.settings.ignoreGroups && this.isGroupMessage(message.from)) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
isGroupMessage(from) {
|
|
47
|
+
return from.includes('g.us');
|
|
48
|
+
}
|
|
49
|
+
sendAutoResponse(messageQueue, bot, originalMessage, autoResponse) {
|
|
50
|
+
try {
|
|
51
|
+
const metadata = autoResponse.responseOptions || {};
|
|
52
|
+
const messageId = messageQueue.enqueue(bot.id, originalMessage.from, autoResponse.response, metadata);
|
|
53
|
+
this.logger.info(`📤 Auto-response queued for bot "${bot.name}" with ID: ${messageId}`);
|
|
54
|
+
return messageId;
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
this.logger.error(`❌ Error sending auto-response for bot "${bot.name}":`, error);
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
exports.AutoResponseService = AutoResponseService;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CooldownService = void 0;
|
|
4
|
+
class CooldownService {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.cooldowns = new Map();
|
|
7
|
+
}
|
|
8
|
+
isOnCooldown(sender, key, cooldownMs) {
|
|
9
|
+
if (cooldownMs <= 0)
|
|
10
|
+
return false;
|
|
11
|
+
const senderCooldowns = this.cooldowns.get(sender);
|
|
12
|
+
if (!senderCooldowns)
|
|
13
|
+
return false;
|
|
14
|
+
const lastTrigger = senderCooldowns.get(key);
|
|
15
|
+
if (!lastTrigger)
|
|
16
|
+
return false;
|
|
17
|
+
return Date.now() - lastTrigger < cooldownMs;
|
|
18
|
+
}
|
|
19
|
+
setCooldown(sender, key) {
|
|
20
|
+
let senderCooldowns = this.cooldowns.get(sender);
|
|
21
|
+
if (!senderCooldowns) {
|
|
22
|
+
senderCooldowns = new Map();
|
|
23
|
+
this.cooldowns.set(sender, senderCooldowns);
|
|
24
|
+
}
|
|
25
|
+
senderCooldowns.set(key, Date.now());
|
|
26
|
+
}
|
|
27
|
+
cleanupExpiredCooldowns() {
|
|
28
|
+
const now = Date.now();
|
|
29
|
+
const maxAge = 60 * 60 * 1000;
|
|
30
|
+
for (const [sender, keyMap] of this.cooldowns.entries()) {
|
|
31
|
+
for (const [key, timestamp] of keyMap.entries()) {
|
|
32
|
+
if (now - timestamp > maxAge) {
|
|
33
|
+
keyMap.delete(key);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (keyMap.size === 0) {
|
|
37
|
+
this.cooldowns.delete(sender);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
getCooldownStatus(sender, key) {
|
|
42
|
+
const senderCooldowns = this.cooldowns.get(sender);
|
|
43
|
+
const lastTrigger = senderCooldowns?.get(key);
|
|
44
|
+
if (!lastTrigger) {
|
|
45
|
+
return { isOnCooldown: false, remainingMs: 0 };
|
|
46
|
+
}
|
|
47
|
+
const now = Date.now();
|
|
48
|
+
const elapsed = now - lastTrigger;
|
|
49
|
+
const isOnCooldown = elapsed < 30000;
|
|
50
|
+
const remainingMs = Math.max(0, 30000 - elapsed);
|
|
51
|
+
return { isOnCooldown, remainingMs, lastTrigger };
|
|
52
|
+
}
|
|
53
|
+
clearAllCooldowns() {
|
|
54
|
+
this.cooldowns.clear();
|
|
55
|
+
}
|
|
56
|
+
clearSenderCooldowns(sender) {
|
|
57
|
+
this.cooldowns.delete(sender);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
exports.CooldownService = CooldownService;
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.BotFleet = void 0;
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
const mapper_1 = require("../bot/mapper");
|
|
39
|
+
const catalog_1 = require("../action/catalog");
|
|
40
|
+
const mapper_2 = require("../flow/mapper");
|
|
41
|
+
const cooldown_1 = require("./cooldown");
|
|
42
|
+
const inbox_1 = require("./inbox");
|
|
43
|
+
const flow_state_1 = require("./flow-state");
|
|
44
|
+
const flow_executor_1 = require("./flow-executor");
|
|
45
|
+
const session_1 = require("../whatsapp/session");
|
|
46
|
+
const yaml_1 = require("../config/yaml");
|
|
47
|
+
const logger_1 = require("../utils/logger");
|
|
48
|
+
class BotFleet {
|
|
49
|
+
constructor(outboxService) {
|
|
50
|
+
this.bots = new Map();
|
|
51
|
+
this.actionCatalog = new Map();
|
|
52
|
+
this.flowCatalog = new Map();
|
|
53
|
+
this.isRunning = false;
|
|
54
|
+
this.sessionManager = session_1.SessionManager.getInstance();
|
|
55
|
+
this.outboxService = outboxService;
|
|
56
|
+
this.cooldownService = new cooldown_1.CooldownService();
|
|
57
|
+
}
|
|
58
|
+
get logger() {
|
|
59
|
+
return (0, logger_1.getLogger)();
|
|
60
|
+
}
|
|
61
|
+
async start(configFile) {
|
|
62
|
+
if (this.isRunning) {
|
|
63
|
+
this.logger.info('Bot Fleet Launcher is already running');
|
|
64
|
+
return this.bots;
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
this.actionCatalog = (0, catalog_1.mapActionCatalog)(configFile.actions || {});
|
|
68
|
+
this.flowCatalog = (0, mapper_2.mapFlowCatalog)(configFile.flows || {});
|
|
69
|
+
const configDir = path.dirname((0, yaml_1.getConfigPath)());
|
|
70
|
+
const flowStateDbPath = path.join(configDir, 'flows.db');
|
|
71
|
+
this.flowStateService = new flow_state_1.FlowStateService(flowStateDbPath);
|
|
72
|
+
const flowStateTimeout = configFile.global?.sessionTimeout ?? 300;
|
|
73
|
+
this.flowExecutor = new flow_executor_1.FlowExecutor(this.actionCatalog, this.flowCatalog, this.flowStateService, this.outboxService, flowStateTimeout, this.cooldownService);
|
|
74
|
+
this.inboxService = new inbox_1.InboxService(this.flowExecutor);
|
|
75
|
+
if (configFile.bots.length === 0) {
|
|
76
|
+
this.logger.warn('No bots configured.');
|
|
77
|
+
return this.bots;
|
|
78
|
+
}
|
|
79
|
+
const loadedBots = (0, mapper_1.mapBotsFromConfig)(configFile.bots);
|
|
80
|
+
for (const bot of loadedBots) {
|
|
81
|
+
await this.initializeBot(bot);
|
|
82
|
+
if (loadedBots.length > 1) {
|
|
83
|
+
this.logger.info('Waiting 3 seconds before initializing next bot...');
|
|
84
|
+
await this.delay(3000);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
this.isRunning = true;
|
|
88
|
+
this.logger.info(`WWeb BotForge started successfully with ${this.bots.size} bot(s)!`);
|
|
89
|
+
this.logger.info('Bots are now listening for messages...');
|
|
90
|
+
this.setupGracefulShutdown();
|
|
91
|
+
return this.bots;
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
this.logger.error('Failed to start Bot Fleet Launcher:', error);
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async stop() {
|
|
99
|
+
if (!this.isRunning) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
this.logger.info('Stopping WWeb BotForge...');
|
|
103
|
+
try {
|
|
104
|
+
await this.outboxService.shutdown();
|
|
105
|
+
await this.sessionManager.removeAllChannels();
|
|
106
|
+
this.flowStateService?.close();
|
|
107
|
+
this.bots.clear();
|
|
108
|
+
this.isRunning = false;
|
|
109
|
+
this.logger.info('WWeb BotForge stopped successfully');
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
this.logger.error('Error stopping Bot Fleet:', error);
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async initializeBot(bot) {
|
|
117
|
+
try {
|
|
118
|
+
this.logger.info(`Initializing bot: ${bot.name} (${bot.id})`);
|
|
119
|
+
this.bots.set(bot.id, bot);
|
|
120
|
+
const channel = this.sessionManager.createChannel(bot.id);
|
|
121
|
+
bot.channel = channel;
|
|
122
|
+
this.outboxService.setupBotQueue(bot);
|
|
123
|
+
this.inboxService.registerBot(bot);
|
|
124
|
+
this.setupBotEventHandlers(bot);
|
|
125
|
+
await bot.channel.connect();
|
|
126
|
+
this.logger.info(`Bot "${bot.name}" initialized and ready`);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
this.logger.error(`Failed to initialize bot "${bot.name}":`, error);
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
setupBotEventHandlers(bot) {
|
|
134
|
+
if (!bot.channel) {
|
|
135
|
+
throw new Error(`Bot "${bot.name}" does not have a registered channel`);
|
|
136
|
+
}
|
|
137
|
+
bot.channel.onReady(() => {
|
|
138
|
+
this.logger.info(`Bot "${bot.name}" is ready!`);
|
|
139
|
+
});
|
|
140
|
+
bot.channel.onDisconnected((reason) => {
|
|
141
|
+
this.logger.warn(`Bot "${bot.name}" disconnected:`, reason);
|
|
142
|
+
});
|
|
143
|
+
bot.channel.onAuthFailure((error) => {
|
|
144
|
+
this.logger.error(`Bot "${bot.name}" authentication failed:`, error.message);
|
|
145
|
+
});
|
|
146
|
+
bot.channel.onConnectionError((error) => {
|
|
147
|
+
this.logger.error(`Bot "${bot.name}" connection error:`, error.message);
|
|
148
|
+
});
|
|
149
|
+
bot.channel.onStateChange((state) => {
|
|
150
|
+
this.logger.info(`Bot "${bot.name}" state changed to:`, state);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
setupGracefulShutdown() {
|
|
154
|
+
const shutdown = async () => {
|
|
155
|
+
this.logger.info('\nReceived shutdown signal...');
|
|
156
|
+
await this.stop();
|
|
157
|
+
process.exit(0);
|
|
158
|
+
};
|
|
159
|
+
process.on('SIGINT', shutdown);
|
|
160
|
+
process.on('SIGTERM', shutdown);
|
|
161
|
+
process.on('SIGUSR2', shutdown);
|
|
162
|
+
process.on('uncaughtException', (error) => {
|
|
163
|
+
this.logger.error('Uncaught Exception:', error);
|
|
164
|
+
this.stop().finally(() => process.exit(1));
|
|
165
|
+
});
|
|
166
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
167
|
+
this.logger.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
|
168
|
+
this.stop().finally(() => process.exit(1));
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
getStatus() {
|
|
172
|
+
const botStatuses = Array.from(this.bots.values()).map(bot => {
|
|
173
|
+
const queueStatus = this.outboxService.getBotQueueStatus(bot.id);
|
|
174
|
+
return {
|
|
175
|
+
id: bot.id,
|
|
176
|
+
name: bot.name,
|
|
177
|
+
flowsCount: bot.flows.length,
|
|
178
|
+
queue: {
|
|
179
|
+
size: queueStatus.queueSize,
|
|
180
|
+
delayMs: queueStatus.delayMs,
|
|
181
|
+
isProcessing: queueStatus.isProcessing,
|
|
182
|
+
hasCallback: queueStatus.hasCallback,
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
});
|
|
186
|
+
const queueStatus = this.outboxService.getAllQueuesStatus();
|
|
187
|
+
return {
|
|
188
|
+
isRunning: this.isRunning,
|
|
189
|
+
bots: botStatuses,
|
|
190
|
+
totalBots: botStatuses.length,
|
|
191
|
+
queues: queueStatus,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
isRunningStatus() {
|
|
195
|
+
return this.isRunning;
|
|
196
|
+
}
|
|
197
|
+
delay(ms) {
|
|
198
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
199
|
+
}
|
|
200
|
+
getOutboxService() {
|
|
201
|
+
return this.outboxService;
|
|
202
|
+
}
|
|
203
|
+
getBots() {
|
|
204
|
+
return this.bots;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
exports.BotFleet = BotFleet;
|