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
|
@@ -0,0 +1,145 @@
|
|
|
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.ConfigWatcher = void 0;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const yaml_1 = require("./yaml");
|
|
40
|
+
const mapper_1 = require("./mapper");
|
|
41
|
+
const logger_1 = require("../helpers/logger");
|
|
42
|
+
class ConfigWatcher {
|
|
43
|
+
constructor(fleet, configPath) {
|
|
44
|
+
this.watcher = null;
|
|
45
|
+
this.debounceTimer = null;
|
|
46
|
+
this.watching = false;
|
|
47
|
+
this.fleet = fleet;
|
|
48
|
+
this.configPath = configPath || (0, yaml_1.getConfigPath)();
|
|
49
|
+
this.configDir = path.dirname(this.configPath);
|
|
50
|
+
}
|
|
51
|
+
get logger() {
|
|
52
|
+
return (0, logger_1.getLogger)();
|
|
53
|
+
}
|
|
54
|
+
start() {
|
|
55
|
+
if (this.watching)
|
|
56
|
+
return;
|
|
57
|
+
try {
|
|
58
|
+
this.watcher = fs.watch(this.configDir, { recursive: true }, (eventType, filename) => {
|
|
59
|
+
if (!filename)
|
|
60
|
+
return;
|
|
61
|
+
const ext = path.extname(filename);
|
|
62
|
+
if (ext !== '.yml' && ext !== '.yaml')
|
|
63
|
+
return;
|
|
64
|
+
if (this.debounceTimer)
|
|
65
|
+
clearTimeout(this.debounceTimer);
|
|
66
|
+
this.debounceTimer = setTimeout(() => {
|
|
67
|
+
this.reload().catch(err => {
|
|
68
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
69
|
+
this.logger.error(`Config reload error: ${msg}`);
|
|
70
|
+
});
|
|
71
|
+
}, 500);
|
|
72
|
+
});
|
|
73
|
+
this.watching = true;
|
|
74
|
+
this.logger.info(`Config watcher started on ${this.configDir}`);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
78
|
+
this.logger.warn(`Failed to start config watcher: ${msg}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
stop() {
|
|
82
|
+
if (this.watcher) {
|
|
83
|
+
this.watcher.close();
|
|
84
|
+
this.watcher = null;
|
|
85
|
+
}
|
|
86
|
+
if (this.debounceTimer) {
|
|
87
|
+
clearTimeout(this.debounceTimer);
|
|
88
|
+
this.debounceTimer = null;
|
|
89
|
+
}
|
|
90
|
+
this.watching = false;
|
|
91
|
+
}
|
|
92
|
+
isWatching() {
|
|
93
|
+
return this.watching;
|
|
94
|
+
}
|
|
95
|
+
async reload() {
|
|
96
|
+
try {
|
|
97
|
+
this.logger.info('Reloading configuration...');
|
|
98
|
+
const configFile = await (0, yaml_1.loadConfig)(this.configPath);
|
|
99
|
+
const newActionCatalog = (0, mapper_1.mapActionCatalog)(configFile.actions || {});
|
|
100
|
+
const newGraphCatalog = (0, mapper_1.mapGraphCatalog)(configFile.graphs || {});
|
|
101
|
+
const loadedBots = (0, mapper_1.mapBotsFromConfig)(configFile.bots);
|
|
102
|
+
this.fleet.reloadCatalogs(newActionCatalog, newGraphCatalog);
|
|
103
|
+
const existingBots = this.fleet.getBots();
|
|
104
|
+
const existingIds = new Set(existingBots.keys());
|
|
105
|
+
const newIds = new Set(loadedBots.map(b => b.id));
|
|
106
|
+
for (const bot of loadedBots) {
|
|
107
|
+
if (!existingIds.has(bot.id)) {
|
|
108
|
+
existingBots.set(bot.id, bot);
|
|
109
|
+
this.fleet.getOutboxService().setupBotQueue(bot);
|
|
110
|
+
this.logger.info(`New bot added via reload: ${bot.id}`);
|
|
111
|
+
this.logger.debug(` allowed_senders=[${bot.settings.allowedSenders.join(', ')}]`);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
const existing = existingBots.get(bot.id);
|
|
115
|
+
if (existing) {
|
|
116
|
+
const graphChanged = existing.graph !== bot.graph;
|
|
117
|
+
existing.settings = bot.settings;
|
|
118
|
+
existing.graph = bot.graph;
|
|
119
|
+
if (graphChanged) {
|
|
120
|
+
this.fleet.getGraphExecutor()?.clearBotSessions(bot.id);
|
|
121
|
+
this.logger.info(`Graph changed for bot "${bot.id}" — active sessions cleared`);
|
|
122
|
+
}
|
|
123
|
+
this.logger.info(`Bot updated via reload: ${bot.id}`);
|
|
124
|
+
this.logger.debug(` allowed_senders=[${bot.settings.allowedSenders.join(', ')}]`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
for (const id of existingIds) {
|
|
129
|
+
if (!newIds.has(id)) {
|
|
130
|
+
const bot = existingBots.get(id);
|
|
131
|
+
if (bot) {
|
|
132
|
+
existingBots.delete(id);
|
|
133
|
+
this.logger.info(`Bot removed via reload: ${id}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
this.logger.info('Configuration reloaded successfully');
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
141
|
+
this.logger.error(`Failed to reload configuration: ${msg}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
exports.ConfigWatcher = ConfigWatcher;
|
|
@@ -0,0 +1,197 @@
|
|
|
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.getDefaultConfigPath = getDefaultConfigPath;
|
|
37
|
+
exports.getConfigPath = getConfigPath;
|
|
38
|
+
exports.setConfigPath = setConfigPath;
|
|
39
|
+
exports.loadConfig = loadConfig;
|
|
40
|
+
exports.saveConfig = saveConfig;
|
|
41
|
+
exports.addBotConfig = addBotConfig;
|
|
42
|
+
const yaml = __importStar(require("js-yaml"));
|
|
43
|
+
const fs = __importStar(require("fs/promises"));
|
|
44
|
+
const fsSync = __importStar(require("fs"));
|
|
45
|
+
const path = __importStar(require("path"));
|
|
46
|
+
const os = __importStar(require("os"));
|
|
47
|
+
const logger_1 = require("../helpers/logger");
|
|
48
|
+
let configPath;
|
|
49
|
+
function getDefaultConfigPath() {
|
|
50
|
+
const home = process.env.HOME || os.homedir();
|
|
51
|
+
return path.join(home, '.config', 'botforje', 'config.yml');
|
|
52
|
+
}
|
|
53
|
+
function getConfigPath() {
|
|
54
|
+
return configPath || getDefaultConfigPath();
|
|
55
|
+
}
|
|
56
|
+
function setConfigPath(newPath) {
|
|
57
|
+
configPath = newPath;
|
|
58
|
+
}
|
|
59
|
+
async function processIncludes(content, baseDir) {
|
|
60
|
+
const lines = content.split('\n');
|
|
61
|
+
const processedLines = [];
|
|
62
|
+
for (let i = 0; i < lines.length; i++) {
|
|
63
|
+
const line = lines[i];
|
|
64
|
+
const includeMatch = line.match(/^\s*-\s*!include\s+(.+)$/);
|
|
65
|
+
if (includeMatch) {
|
|
66
|
+
const includePath = path.resolve(baseDir, includeMatch[1]);
|
|
67
|
+
const includedContent = await fs.readFile(includePath, 'utf8');
|
|
68
|
+
const indent = line.match(/^\s*/)?.[0] || '';
|
|
69
|
+
const indentedContent = includedContent
|
|
70
|
+
.split('\n')
|
|
71
|
+
.map((line, index) => index === 0 ? indent + '- ' + line : indent + ' ' + line)
|
|
72
|
+
.join('\n');
|
|
73
|
+
processedLines.push(indentedContent);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
processedLines.push(line);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return processedLines.join('\n');
|
|
80
|
+
}
|
|
81
|
+
async function loadActionsFromDir(dirPath) {
|
|
82
|
+
const actions = {};
|
|
83
|
+
if (!fsSync.existsSync(dirPath)) {
|
|
84
|
+
return actions;
|
|
85
|
+
}
|
|
86
|
+
const entries = await fs.readdir(dirPath);
|
|
87
|
+
const files = entries.filter(file => file.endsWith('.yml') || file.endsWith('.yaml'));
|
|
88
|
+
for (const file of files) {
|
|
89
|
+
const id = path.basename(file, path.extname(file));
|
|
90
|
+
const filePath = path.join(dirPath, file);
|
|
91
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
92
|
+
const parsed = yaml.load(content);
|
|
93
|
+
if (parsed && typeof parsed === 'object') {
|
|
94
|
+
actions[id] = parsed;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return actions;
|
|
98
|
+
}
|
|
99
|
+
async function loadGraphsFromDir(dirPath) {
|
|
100
|
+
const graphs = {};
|
|
101
|
+
if (!fsSync.existsSync(dirPath)) {
|
|
102
|
+
return graphs;
|
|
103
|
+
}
|
|
104
|
+
const entries = await fs.readdir(dirPath);
|
|
105
|
+
const files = entries.filter(file => file.endsWith('.yml') || file.endsWith('.yaml'));
|
|
106
|
+
for (const file of files) {
|
|
107
|
+
const id = path.basename(file, path.extname(file));
|
|
108
|
+
const filePath = path.join(dirPath, file);
|
|
109
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
110
|
+
const parsed = yaml.load(content);
|
|
111
|
+
if (parsed && typeof parsed === 'object' && parsed.nodes) {
|
|
112
|
+
graphs[id] = parsed;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return graphs;
|
|
116
|
+
}
|
|
117
|
+
async function loadBotsFromDir(dirPath) {
|
|
118
|
+
const bots = {};
|
|
119
|
+
if (!fsSync.existsSync(dirPath)) {
|
|
120
|
+
return bots;
|
|
121
|
+
}
|
|
122
|
+
const entries = await fs.readdir(dirPath);
|
|
123
|
+
const files = entries.filter(file => file.endsWith('.yml') || file.endsWith('.yaml'));
|
|
124
|
+
for (const file of files) {
|
|
125
|
+
const id = path.basename(file, path.extname(file));
|
|
126
|
+
const filePath = path.join(dirPath, file);
|
|
127
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
128
|
+
const parsed = yaml.load(content);
|
|
129
|
+
if (parsed && typeof parsed === 'object') {
|
|
130
|
+
bots[id] = parsed;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return bots;
|
|
134
|
+
}
|
|
135
|
+
async function loadConfig(customPath) {
|
|
136
|
+
const targetPath = customPath || getConfigPath();
|
|
137
|
+
const logger = (0, logger_1.getLogger)();
|
|
138
|
+
try {
|
|
139
|
+
const rawContent = await fs.readFile(targetPath, 'utf-8');
|
|
140
|
+
const processedContent = await processIncludes(rawContent, path.dirname(targetPath));
|
|
141
|
+
const config = yaml.load(processedContent);
|
|
142
|
+
if (config.bots !== undefined && config.bots !== null) {
|
|
143
|
+
if (typeof config.bots !== 'object' || Array.isArray(config.bots)) {
|
|
144
|
+
throw new Error('Configuration must contain a "bots" object');
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
const baseDir = path.dirname(targetPath);
|
|
148
|
+
const actionsFromDir = await loadActionsFromDir(path.join(baseDir, 'actions'));
|
|
149
|
+
const graphsFromDir = await loadGraphsFromDir(path.join(baseDir, 'graphs'));
|
|
150
|
+
const botsFromDir = await loadBotsFromDir(path.join(baseDir, 'bots'));
|
|
151
|
+
config.actions = { ...actionsFromDir, ...config.actions };
|
|
152
|
+
config.graphs = { ...graphsFromDir, ...config.graphs };
|
|
153
|
+
config.bots = { ...botsFromDir, ...config.bots };
|
|
154
|
+
if (!config.bots || typeof config.bots !== 'object' || Object.keys(config.bots).length === 0) {
|
|
155
|
+
throw new Error('Configuration must contain a "bots" object with at least one bot');
|
|
156
|
+
}
|
|
157
|
+
return config;
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
throw new Error(`Failed to read configuration from ${targetPath}: ${error}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
async function saveConfig(config, customPath) {
|
|
164
|
+
const targetPath = customPath || getConfigPath();
|
|
165
|
+
try {
|
|
166
|
+
const configDir = path.dirname(targetPath);
|
|
167
|
+
if (!fsSync.existsSync(configDir)) {
|
|
168
|
+
await fs.mkdir(configDir, { recursive: true });
|
|
169
|
+
}
|
|
170
|
+
const yamlContent = yaml.dump(config, {
|
|
171
|
+
indent: 2,
|
|
172
|
+
lineWidth: -1,
|
|
173
|
+
noRefs: true,
|
|
174
|
+
});
|
|
175
|
+
await fs.writeFile(targetPath, yamlContent, 'utf-8');
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
throw new Error(`Failed to write configuration to ${targetPath}: ${error}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
async function addBotConfig(id, botConfig, customPath) {
|
|
182
|
+
const configDir = path.dirname(customPath || getConfigPath());
|
|
183
|
+
const botsDir = path.join(configDir, 'bots');
|
|
184
|
+
const botFilePath = path.join(botsDir, `${id}.yml`);
|
|
185
|
+
if (!fsSync.existsSync(botsDir)) {
|
|
186
|
+
await fs.mkdir(botsDir, { recursive: true });
|
|
187
|
+
}
|
|
188
|
+
if (fsSync.existsSync(botFilePath)) {
|
|
189
|
+
throw new Error(`Bot with ID "${id}" already exists at ${botFilePath}`);
|
|
190
|
+
}
|
|
191
|
+
const yamlContent = yaml.dump(botConfig, { indent: 2, lineWidth: -1, noRefs: true });
|
|
192
|
+
await fs.writeFile(botFilePath, yamlContent, 'utf-8');
|
|
193
|
+
const mainConfigPath = customPath || getConfigPath();
|
|
194
|
+
if (!fsSync.existsSync(mainConfigPath)) {
|
|
195
|
+
await saveConfig({ bots: {} }, mainConfigPath);
|
|
196
|
+
}
|
|
197
|
+
}
|
package/dist/fleet.js
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
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 fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const mapper_1 = require("./config/mapper");
|
|
40
|
+
const cooldown_1 = require("./actions/cooldown");
|
|
41
|
+
const inbox_1 = require("./messages/inbox");
|
|
42
|
+
const state_1 = require("./graph/state");
|
|
43
|
+
const executor_1 = require("./graph/executor");
|
|
44
|
+
const session_1 = require("./whatsapp/session");
|
|
45
|
+
const service_1 = require("./auth/service");
|
|
46
|
+
const logger_1 = require("./helpers/logger");
|
|
47
|
+
const data_1 = require("./helpers/data");
|
|
48
|
+
class BotFleet {
|
|
49
|
+
constructor(outboxService) {
|
|
50
|
+
this.bots = new Map();
|
|
51
|
+
this.actionCatalog = new Map();
|
|
52
|
+
this.graphCatalog = 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, mapper_1.mapActionCatalog)(configFile.actions || {});
|
|
68
|
+
this.graphCatalog = (0, mapper_1.mapGraphCatalog)(configFile.graphs || {});
|
|
69
|
+
const dataDir = (0, data_1.getDataDir)();
|
|
70
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
71
|
+
const dbPath = path.join(dataDir, 'botforje.db');
|
|
72
|
+
this.graphStateService = new state_1.GraphStateService(dbPath);
|
|
73
|
+
this.authService = new service_1.AuthService(dbPath);
|
|
74
|
+
const graphStateTimeout = configFile.default_timeout ?? 300;
|
|
75
|
+
this.graphExecutor = new executor_1.GraphExecutor(this.actionCatalog, this.graphCatalog, this.graphStateService, this.outboxService, graphStateTimeout, this.cooldownService);
|
|
76
|
+
this.inboxService = new inbox_1.InboxService(this.graphExecutor);
|
|
77
|
+
if (Object.keys(configFile.bots).length === 0) {
|
|
78
|
+
this.logger.warn('No bots configured.');
|
|
79
|
+
return this.bots;
|
|
80
|
+
}
|
|
81
|
+
const loadedBots = (0, mapper_1.mapBotsFromConfig)(configFile.bots);
|
|
82
|
+
this.logger.debug(`Initializing ${loadedBots.length} bot(s)...`);
|
|
83
|
+
for (const bot of loadedBots) {
|
|
84
|
+
this.logger.debug(`Bot "${bot.id}" settings: allowed_senders=[${bot.settings.allowedSenders.join(', ')}]`);
|
|
85
|
+
this.registerBot(bot);
|
|
86
|
+
}
|
|
87
|
+
this.isRunning = true;
|
|
88
|
+
this.logger.info('Waiting for bot connections...');
|
|
89
|
+
this.setupGracefulShutdown();
|
|
90
|
+
return this.bots;
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
94
|
+
this.logger.error(`Failed to start Bot Fleet Launcher: ${msg}`);
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async stop() {
|
|
99
|
+
if (!this.isRunning) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
this.logger.info('Stopping Botforje...');
|
|
103
|
+
try {
|
|
104
|
+
await this.outboxService.shutdown();
|
|
105
|
+
await this.sessionManager.removeAllChannels();
|
|
106
|
+
this.graphStateService?.close();
|
|
107
|
+
this.authService?.close();
|
|
108
|
+
this.bots.clear();
|
|
109
|
+
this.isRunning = false;
|
|
110
|
+
this.logger.info('Botforje stopped successfully');
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
114
|
+
this.logger.error(`Error stopping Bot Fleet: ${msg}`);
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
registerBot(bot) {
|
|
119
|
+
this.logger.info(`Registering bot: ${bot.id}`);
|
|
120
|
+
this.bots.set(bot.id, bot);
|
|
121
|
+
this.outboxService.setupBotQueue(bot);
|
|
122
|
+
this.sessionManager.onSessionReady(bot.id, (channel) => {
|
|
123
|
+
this.linkSessionToBot(bot, channel);
|
|
124
|
+
});
|
|
125
|
+
this.sessionManager.registerSession(bot.id).catch((err) => {
|
|
126
|
+
this.logger.warn(`Bot "${bot.id}" session registration failed: ${err.message}`);
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
linkSessionToBot(bot, channel) {
|
|
130
|
+
bot.channel = channel;
|
|
131
|
+
this.inboxService.registerBot(bot);
|
|
132
|
+
this.setupBotEventHandlers(bot);
|
|
133
|
+
const phone = channel.getPhone();
|
|
134
|
+
if (phone) {
|
|
135
|
+
bot.phone = phone;
|
|
136
|
+
}
|
|
137
|
+
this.logger.info(`Bot "${bot.id}" linked to session`);
|
|
138
|
+
}
|
|
139
|
+
setupBotEventHandlers(bot) {
|
|
140
|
+
if (!bot.channel)
|
|
141
|
+
return;
|
|
142
|
+
bot.channel.onReady(() => {
|
|
143
|
+
this.logger.info(`Bot "${bot.id}" is ready!`);
|
|
144
|
+
const phone = bot.channel.getPhone();
|
|
145
|
+
if (phone) {
|
|
146
|
+
bot.phone = phone;
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
bot.channel.onDisconnected((reason) => {
|
|
150
|
+
this.logger.warn(`Bot "${bot.id}" disconnected: ${reason}`);
|
|
151
|
+
});
|
|
152
|
+
bot.channel.onAuthFailure((error) => {
|
|
153
|
+
this.logger.error(`Bot "${bot.id}" authentication failed: ${error.message}`);
|
|
154
|
+
});
|
|
155
|
+
bot.channel.onConnectionError((error) => {
|
|
156
|
+
this.logger.error(`Bot "${bot.id}" connection error: ${error.message}`);
|
|
157
|
+
});
|
|
158
|
+
bot.channel.onStateChange((state) => {
|
|
159
|
+
this.logger.info(`Bot "${bot.id}" state changed to: ${state}`);
|
|
160
|
+
});
|
|
161
|
+
bot.channel.onAuthRequired?.((info) => {
|
|
162
|
+
this.logger.info(`QR auth required for bot "${bot.id}"`);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
linkExistingSession(botId) {
|
|
166
|
+
const bot = this.bots.get(botId);
|
|
167
|
+
if (!bot) {
|
|
168
|
+
this.logger.warn(`Cannot link session: bot "${botId}" not found`);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
const channel = this.sessionManager.getChannel(botId);
|
|
172
|
+
if (!channel) {
|
|
173
|
+
this.logger.warn(`Cannot link session: no session for "${botId}"`);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
this.linkSessionToBot(bot, channel);
|
|
177
|
+
}
|
|
178
|
+
setupGracefulShutdown() {
|
|
179
|
+
const shutdown = async () => {
|
|
180
|
+
this.logger.info('\nReceived shutdown signal...');
|
|
181
|
+
await this.stop();
|
|
182
|
+
process.exit(0);
|
|
183
|
+
};
|
|
184
|
+
process.on('SIGINT', shutdown);
|
|
185
|
+
process.on('SIGTERM', shutdown);
|
|
186
|
+
process.on('SIGUSR2', shutdown);
|
|
187
|
+
process.on('uncaughtException', (error) => {
|
|
188
|
+
const msg = error instanceof Error ? error.stack || error.message : String(error);
|
|
189
|
+
this.logger.error(`Uncaught Exception: ${msg}`);
|
|
190
|
+
this.stop().finally(() => process.exit(1));
|
|
191
|
+
});
|
|
192
|
+
process.on('unhandledRejection', (reason) => {
|
|
193
|
+
const msg = reason instanceof Error ? reason.stack || reason.message : String(reason);
|
|
194
|
+
this.logger.error(`Unhandled Rejection: ${msg}`);
|
|
195
|
+
this.stop().finally(() => process.exit(1));
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
getStatus() {
|
|
199
|
+
const botStatuses = Array.from(this.bots.values()).map(bot => {
|
|
200
|
+
const queueStatus = this.outboxService.getBotQueueStatus(bot.id);
|
|
201
|
+
const sessionInfo = this.sessionManager.getSessionInfo(bot.id);
|
|
202
|
+
return {
|
|
203
|
+
id: bot.id,
|
|
204
|
+
graph: bot.graph,
|
|
205
|
+
session: sessionInfo ? {
|
|
206
|
+
state: sessionInfo.state,
|
|
207
|
+
phone: sessionInfo.phone,
|
|
208
|
+
} : null,
|
|
209
|
+
queue: {
|
|
210
|
+
size: queueStatus.queueSize,
|
|
211
|
+
delayMs: queueStatus.delayMs,
|
|
212
|
+
isProcessing: queueStatus.isProcessing,
|
|
213
|
+
hasCallback: queueStatus.hasCallback,
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
});
|
|
217
|
+
return {
|
|
218
|
+
isRunning: this.isRunning,
|
|
219
|
+
bots: botStatuses,
|
|
220
|
+
totalBots: botStatuses.length,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
isRunningStatus() {
|
|
224
|
+
return this.isRunning;
|
|
225
|
+
}
|
|
226
|
+
getOutboxService() {
|
|
227
|
+
return this.outboxService;
|
|
228
|
+
}
|
|
229
|
+
getBots() {
|
|
230
|
+
return this.bots;
|
|
231
|
+
}
|
|
232
|
+
reloadCatalogs(actionCatalog, graphCatalog) {
|
|
233
|
+
this.actionCatalog = actionCatalog;
|
|
234
|
+
this.graphCatalog = graphCatalog;
|
|
235
|
+
this.graphExecutor?.updateCatalogs(actionCatalog, graphCatalog);
|
|
236
|
+
}
|
|
237
|
+
getGraphExecutor() {
|
|
238
|
+
return this.graphExecutor;
|
|
239
|
+
}
|
|
240
|
+
getAuthService() {
|
|
241
|
+
if (!this.authService) {
|
|
242
|
+
throw new Error('AuthService not initialized. Call start() first.');
|
|
243
|
+
}
|
|
244
|
+
return this.authService;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
exports.BotFleet = BotFleet;
|