@darksol/terminal 0.9.2 → 0.11.0
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/README.md +111 -1
- package/package.json +4 -1
- package/src/browser/actions.js +58 -0
- package/src/cli.js +327 -24
- package/src/config/keys.js +12 -2
- package/src/daemon/index.js +225 -0
- package/src/daemon/manager.js +148 -0
- package/src/daemon/pid.js +80 -0
- package/src/services/browser.js +659 -0
- package/src/services/poker.js +937 -0
- package/src/services/telegram.js +570 -0
- package/src/web/commands.js +387 -1
- package/src/web/server.js +27 -6
- package/src/web/terminal.js +1 -1
|
@@ -0,0 +1,570 @@
|
|
|
1
|
+
import fetch from 'node-fetch';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import { theme } from '../ui/theme.js';
|
|
4
|
+
import { spinner, kvDisplay, success, error, warn, info, card } from '../ui/components.js';
|
|
5
|
+
import { showSection, showMiniBanner } from '../ui/banner.js';
|
|
6
|
+
import { getConfig, setConfig } from '../config/store.js';
|
|
7
|
+
import { addKeyDirect, getKeyAuto, hasKey } from '../config/keys.js';
|
|
8
|
+
import { SessionMemory } from '../memory/index.js';
|
|
9
|
+
import { formatSystemPrompt } from '../soul/index.js';
|
|
10
|
+
|
|
11
|
+
const TELEGRAM_API = 'https://api.telegram.org/bot';
|
|
12
|
+
|
|
13
|
+
// ─────────────────────────────────────
|
|
14
|
+
// TELEGRAM BOT API HELPERS
|
|
15
|
+
// ─────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Call a Telegram Bot API method.
|
|
19
|
+
* @param {string} token
|
|
20
|
+
* @param {string} method
|
|
21
|
+
* @param {object} [params]
|
|
22
|
+
* @returns {Promise<object>}
|
|
23
|
+
*/
|
|
24
|
+
async function tgCall(token, method, params = {}) {
|
|
25
|
+
const url = `${TELEGRAM_API}${token}/${method}`;
|
|
26
|
+
const res = await fetch(url, {
|
|
27
|
+
method: 'POST',
|
|
28
|
+
headers: { 'Content-Type': 'application/json' },
|
|
29
|
+
body: JSON.stringify(params),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const data = await res.json();
|
|
33
|
+
|
|
34
|
+
if (!data.ok) {
|
|
35
|
+
if (res.status === 429 && data.parameters?.retry_after) {
|
|
36
|
+
const wait = data.parameters.retry_after;
|
|
37
|
+
await new Promise((r) => setTimeout(r, wait * 1000));
|
|
38
|
+
return tgCall(token, method, params);
|
|
39
|
+
}
|
|
40
|
+
throw new Error(data.description || `Telegram API error: ${method} (${res.status})`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return data.result;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Validate a bot token by calling getMe.
|
|
48
|
+
* @param {string} token
|
|
49
|
+
* @returns {Promise<object>} Bot info object
|
|
50
|
+
*/
|
|
51
|
+
export async function validateToken(token) {
|
|
52
|
+
if (!token || typeof token !== 'string') {
|
|
53
|
+
throw new Error('Invalid token format');
|
|
54
|
+
}
|
|
55
|
+
const parts = token.split(':');
|
|
56
|
+
if (parts.length !== 2 || !/^\d+$/.test(parts[0])) {
|
|
57
|
+
throw new Error('Token must be in format 123456:ABC-DEF...');
|
|
58
|
+
}
|
|
59
|
+
return tgCall(token, 'getMe');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Send a text message.
|
|
64
|
+
* @param {string} token
|
|
65
|
+
* @param {number|string} chatId
|
|
66
|
+
* @param {string} text
|
|
67
|
+
* @param {object} [opts]
|
|
68
|
+
* @returns {Promise<object>}
|
|
69
|
+
*/
|
|
70
|
+
export async function sendMessage(token, chatId, text, opts = {}) {
|
|
71
|
+
return tgCall(token, 'sendMessage', {
|
|
72
|
+
chat_id: chatId,
|
|
73
|
+
text,
|
|
74
|
+
parse_mode: opts.parseMode || 'Markdown',
|
|
75
|
+
...opts.extra,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Send a "typing" chat action.
|
|
81
|
+
* @param {string} token
|
|
82
|
+
* @param {number|string} chatId
|
|
83
|
+
* @returns {Promise<void>}
|
|
84
|
+
*/
|
|
85
|
+
async function sendTyping(token, chatId) {
|
|
86
|
+
try {
|
|
87
|
+
await tgCall(token, 'sendChatAction', {
|
|
88
|
+
chat_id: chatId,
|
|
89
|
+
action: 'typing',
|
|
90
|
+
});
|
|
91
|
+
} catch {
|
|
92
|
+
// non-critical
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Get updates from Telegram via long polling.
|
|
98
|
+
* @param {string} token
|
|
99
|
+
* @param {number} offset
|
|
100
|
+
* @param {number} [timeout=30]
|
|
101
|
+
* @returns {Promise<Array>}
|
|
102
|
+
*/
|
|
103
|
+
async function getUpdates(token, offset, timeout = 30) {
|
|
104
|
+
return tgCall(token, 'getUpdates', {
|
|
105
|
+
offset,
|
|
106
|
+
timeout,
|
|
107
|
+
allowed_updates: ['message'],
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ─────────────────────────────────────
|
|
112
|
+
// BOT STATE
|
|
113
|
+
// ─────────────────────────────────────
|
|
114
|
+
|
|
115
|
+
let botRunning = false;
|
|
116
|
+
let botToken = null;
|
|
117
|
+
let botInfo = null;
|
|
118
|
+
let updateOffset = 0;
|
|
119
|
+
let pollAbort = null;
|
|
120
|
+
const chatSessions = new Map();
|
|
121
|
+
const chatCooldowns = new Map();
|
|
122
|
+
|
|
123
|
+
const COOLDOWN_MS = 1000; // 1 req/sec per chat
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Get or create a SessionMemory for a chat.
|
|
127
|
+
* @param {number|string} chatId
|
|
128
|
+
* @returns {SessionMemory}
|
|
129
|
+
*/
|
|
130
|
+
function getSession(chatId) {
|
|
131
|
+
const key = String(chatId);
|
|
132
|
+
if (!chatSessions.has(key)) {
|
|
133
|
+
chatSessions.set(key, new SessionMemory({ maxTurns: 20 }));
|
|
134
|
+
}
|
|
135
|
+
return chatSessions.get(key);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Check if a chat is rate-limited.
|
|
140
|
+
* @param {number|string} chatId
|
|
141
|
+
* @returns {boolean}
|
|
142
|
+
*/
|
|
143
|
+
function isRateLimited(chatId) {
|
|
144
|
+
const key = String(chatId);
|
|
145
|
+
const last = chatCooldowns.get(key) || 0;
|
|
146
|
+
const now = Date.now();
|
|
147
|
+
if (now - last < COOLDOWN_MS) return true;
|
|
148
|
+
chatCooldowns.set(key, now);
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ─────────────────────────────────────
|
|
153
|
+
// MESSAGE HANDLER
|
|
154
|
+
// ─────────────────────────────────────
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Handle a single incoming Telegram update.
|
|
158
|
+
* @param {object} update
|
|
159
|
+
* @param {string} token
|
|
160
|
+
* @returns {Promise<void>}
|
|
161
|
+
*/
|
|
162
|
+
export async function handleMessage(update, token) {
|
|
163
|
+
const msg = update.message;
|
|
164
|
+
if (!msg || !msg.text) return;
|
|
165
|
+
|
|
166
|
+
const chatId = msg.chat.id;
|
|
167
|
+
const text = msg.text.trim();
|
|
168
|
+
const userName = msg.from?.first_name || 'User';
|
|
169
|
+
|
|
170
|
+
// Rate limit check
|
|
171
|
+
if (isRateLimited(chatId)) return;
|
|
172
|
+
|
|
173
|
+
// Built-in commands
|
|
174
|
+
if (text === '/start') {
|
|
175
|
+
const soul = getConfig('soul') || {};
|
|
176
|
+
const agentName = soul.agentName || 'Darksol';
|
|
177
|
+
await sendMessage(token, chatId,
|
|
178
|
+
`*${agentName}* is online.\n\n` +
|
|
179
|
+
`Hey ${userName}, I'm your DARKSOL Terminal agent.\n` +
|
|
180
|
+
`Send me any message and I'll respond using AI.\n\n` +
|
|
181
|
+
`Commands:\n` +
|
|
182
|
+
`/help - Show available commands\n` +
|
|
183
|
+
`/status - Bot status\n`,
|
|
184
|
+
);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (text === '/help') {
|
|
189
|
+
await sendMessage(token, chatId,
|
|
190
|
+
`*DARKSOL Telegram Bot*\n\n` +
|
|
191
|
+
`Just send me any message and I'll respond.\n\n` +
|
|
192
|
+
`/start - Welcome message\n` +
|
|
193
|
+
`/help - This help\n` +
|
|
194
|
+
`/status - Bot status info\n`,
|
|
195
|
+
);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (text === '/status') {
|
|
200
|
+
const soul = getConfig('soul') || {};
|
|
201
|
+
const provider = getConfig('llm.provider') || 'openai';
|
|
202
|
+
await sendMessage(token, chatId,
|
|
203
|
+
`*Bot Status*\n` +
|
|
204
|
+
`Agent: ${soul.agentName || 'Darksol'}\n` +
|
|
205
|
+
`LLM: ${provider}\n` +
|
|
206
|
+
`Active chats: ${chatSessions.size}\n` +
|
|
207
|
+
`Uptime: online`,
|
|
208
|
+
);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// AI-powered response
|
|
213
|
+
await sendTyping(token, chatId);
|
|
214
|
+
|
|
215
|
+
try {
|
|
216
|
+
const { createLLM } = await import('../llm/engine.js');
|
|
217
|
+
const session = getSession(chatId);
|
|
218
|
+
|
|
219
|
+
const llm = await createLLM({
|
|
220
|
+
sessionMemory: session,
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
const soulPrompt = formatSystemPrompt();
|
|
224
|
+
if (soulPrompt) {
|
|
225
|
+
llm.setSystemPrompt(soulPrompt);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const result = await llm.chat(text, {
|
|
229
|
+
skipMemoryExtraction: true,
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
const reply = result.content || 'I couldn\'t generate a response.';
|
|
233
|
+
|
|
234
|
+
// Escape markdown special chars that might break Telegram's parser
|
|
235
|
+
await sendMessage(token, chatId, reply, { parseMode: '' });
|
|
236
|
+
} catch (err) {
|
|
237
|
+
const errorMsg = err.message?.includes('API key')
|
|
238
|
+
? 'LLM not configured. Run `darksol setup` on the terminal first.'
|
|
239
|
+
: 'Something went wrong processing your message. Try again.';
|
|
240
|
+
await sendMessage(token, chatId, errorMsg, { parseMode: '' });
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// ─────────────────────────────────────
|
|
245
|
+
// BOT LIFECYCLE
|
|
246
|
+
// ─────────────────────────────────────
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Start the bot long-polling loop.
|
|
250
|
+
* @param {string} token
|
|
251
|
+
* @returns {Promise<object>} Bot info
|
|
252
|
+
*/
|
|
253
|
+
export async function startBot(token) {
|
|
254
|
+
if (botRunning) {
|
|
255
|
+
throw new Error('Bot is already running');
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
botToken = token;
|
|
259
|
+
botInfo = await validateToken(token);
|
|
260
|
+
botRunning = true;
|
|
261
|
+
updateOffset = 0;
|
|
262
|
+
|
|
263
|
+
// Start polling in background
|
|
264
|
+
pollLoop(token);
|
|
265
|
+
|
|
266
|
+
return botInfo;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Stop the bot.
|
|
271
|
+
*/
|
|
272
|
+
export function stopBot() {
|
|
273
|
+
botRunning = false;
|
|
274
|
+
botToken = null;
|
|
275
|
+
botInfo = null;
|
|
276
|
+
if (pollAbort) {
|
|
277
|
+
pollAbort.abort();
|
|
278
|
+
pollAbort = null;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Get current bot status.
|
|
284
|
+
* @returns {object}
|
|
285
|
+
*/
|
|
286
|
+
export function getBotStatus() {
|
|
287
|
+
return {
|
|
288
|
+
running: botRunning,
|
|
289
|
+
botInfo,
|
|
290
|
+
activeChats: chatSessions.size,
|
|
291
|
+
updateOffset,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Internal long-polling loop.
|
|
297
|
+
* @param {string} token
|
|
298
|
+
*/
|
|
299
|
+
async function pollLoop(token) {
|
|
300
|
+
while (botRunning) {
|
|
301
|
+
try {
|
|
302
|
+
const updates = await getUpdates(token, updateOffset, 30);
|
|
303
|
+
|
|
304
|
+
for (const update of updates) {
|
|
305
|
+
updateOffset = update.update_id + 1;
|
|
306
|
+
try {
|
|
307
|
+
await handleMessage(update, token);
|
|
308
|
+
} catch (err) {
|
|
309
|
+
// Log but don't crash the loop
|
|
310
|
+
if (process.env.DARKSOL_DAEMON) {
|
|
311
|
+
const { appendFileSync } = await import('fs');
|
|
312
|
+
const { join } = await import('path');
|
|
313
|
+
const { homedir } = await import('os');
|
|
314
|
+
try {
|
|
315
|
+
appendFileSync(
|
|
316
|
+
join(homedir(), '.darksol', 'logs', 'daemon.log'),
|
|
317
|
+
`[${new Date().toISOString()}] Telegram message error: ${err.message}\n`,
|
|
318
|
+
);
|
|
319
|
+
} catch {
|
|
320
|
+
// ignore log failure
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
} catch (err) {
|
|
326
|
+
if (!botRunning) break;
|
|
327
|
+
|
|
328
|
+
// On error, wait before retrying
|
|
329
|
+
const waitMs = err.message?.includes('429') ? 5000 : 2000;
|
|
330
|
+
await new Promise((r) => setTimeout(r, waitMs));
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// ─────────────────────────────────────
|
|
336
|
+
// CLI COMMANDS
|
|
337
|
+
// ─────────────────────────────────────
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Interactive Telegram bot setup walkthrough.
|
|
341
|
+
*/
|
|
342
|
+
export async function telegramSetup() {
|
|
343
|
+
showMiniBanner();
|
|
344
|
+
showSection('TELEGRAM BOT SETUP');
|
|
345
|
+
|
|
346
|
+
console.log('');
|
|
347
|
+
console.log(theme.bright(' Follow these steps to create your Telegram bot:'));
|
|
348
|
+
console.log('');
|
|
349
|
+
console.log(` ${theme.gold('1.')} Open Telegram and search for ${theme.gold('@BotFather')}`);
|
|
350
|
+
console.log(` ${theme.gold('2.')} Send ${theme.gold('/newbot')} to BotFather`);
|
|
351
|
+
console.log(` ${theme.gold('3.')} Choose a ${theme.bright('name')} for your bot (display name)`);
|
|
352
|
+
console.log(` ${theme.gold('4.')} Choose a ${theme.bright('username')} ending in "bot" (e.g. mydarksol_bot)`);
|
|
353
|
+
console.log(` ${theme.gold('5.')} BotFather will give you an API token — copy it`);
|
|
354
|
+
console.log('');
|
|
355
|
+
console.log(theme.dim(' The token looks like: 123456789:ABCdefGhIjKlmNoPQRsTuVwXyZ'));
|
|
356
|
+
console.log('');
|
|
357
|
+
|
|
358
|
+
const { token } = await inquirer.prompt([{
|
|
359
|
+
type: 'password',
|
|
360
|
+
name: 'token',
|
|
361
|
+
message: theme.gold('Paste your bot token:'),
|
|
362
|
+
mask: '*',
|
|
363
|
+
validate: (v) => {
|
|
364
|
+
if (!v || !v.trim()) return 'Token is required';
|
|
365
|
+
const parts = v.trim().split(':');
|
|
366
|
+
if (parts.length !== 2 || !/^\d+$/.test(parts[0])) {
|
|
367
|
+
return 'Invalid format — should look like 123456:ABC-DEF...';
|
|
368
|
+
}
|
|
369
|
+
return true;
|
|
370
|
+
},
|
|
371
|
+
}]);
|
|
372
|
+
|
|
373
|
+
const spin = spinner('Validating token with Telegram...').start();
|
|
374
|
+
|
|
375
|
+
try {
|
|
376
|
+
const botResult = await validateToken(token.trim());
|
|
377
|
+
spin.succeed('Token validated');
|
|
378
|
+
|
|
379
|
+
console.log('');
|
|
380
|
+
kvDisplay([
|
|
381
|
+
['Bot Name', botResult.first_name],
|
|
382
|
+
['Username', `@${botResult.username}`],
|
|
383
|
+
['Bot ID', String(botResult.id)],
|
|
384
|
+
]);
|
|
385
|
+
console.log('');
|
|
386
|
+
|
|
387
|
+
// Store token in vault
|
|
388
|
+
addKeyDirect('telegram', token.trim());
|
|
389
|
+
setConfig('telegram.botUsername', botResult.username);
|
|
390
|
+
setConfig('telegram.botId', botResult.id);
|
|
391
|
+
success('Bot token stored securely in vault');
|
|
392
|
+
console.log('');
|
|
393
|
+
|
|
394
|
+
// Ask if they want to start now
|
|
395
|
+
const { startNow } = await inquirer.prompt([{
|
|
396
|
+
type: 'confirm',
|
|
397
|
+
name: 'startNow',
|
|
398
|
+
message: theme.gold('Start the bot now?'),
|
|
399
|
+
default: true,
|
|
400
|
+
}]);
|
|
401
|
+
|
|
402
|
+
if (startNow) {
|
|
403
|
+
await telegramStartForeground();
|
|
404
|
+
} else {
|
|
405
|
+
info('Start later with: darksol telegram start');
|
|
406
|
+
console.log('');
|
|
407
|
+
}
|
|
408
|
+
} catch (err) {
|
|
409
|
+
spin.fail('Token validation failed');
|
|
410
|
+
error(err.message);
|
|
411
|
+
info('Double-check the token from BotFather and try again');
|
|
412
|
+
console.log('');
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Start the Telegram bot in foreground (blocking).
|
|
418
|
+
*/
|
|
419
|
+
export async function telegramStartForeground() {
|
|
420
|
+
const token = getKeyAuto('telegram');
|
|
421
|
+
if (!token) {
|
|
422
|
+
error('No Telegram bot token found');
|
|
423
|
+
info('Run: darksol telegram setup');
|
|
424
|
+
console.log('');
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const spin = spinner('Starting Telegram bot...').start();
|
|
429
|
+
|
|
430
|
+
try {
|
|
431
|
+
const botResult = await startBot(token);
|
|
432
|
+
spin.succeed(`Bot started: @${botResult.username}`);
|
|
433
|
+
info('Listening for messages... Press Ctrl+C to stop');
|
|
434
|
+
console.log('');
|
|
435
|
+
|
|
436
|
+
// Keep process alive until interrupted
|
|
437
|
+
await new Promise((resolve) => {
|
|
438
|
+
const onExit = () => {
|
|
439
|
+
stopBot();
|
|
440
|
+
console.log('');
|
|
441
|
+
success('Telegram bot stopped');
|
|
442
|
+
console.log('');
|
|
443
|
+
resolve();
|
|
444
|
+
};
|
|
445
|
+
process.on('SIGINT', onExit);
|
|
446
|
+
process.on('SIGTERM', onExit);
|
|
447
|
+
});
|
|
448
|
+
} catch (err) {
|
|
449
|
+
spin.fail('Failed to start bot');
|
|
450
|
+
error(err.message);
|
|
451
|
+
if (err.message?.includes('401')) {
|
|
452
|
+
warn('Token may be invalid or revoked — run: darksol telegram setup');
|
|
453
|
+
}
|
|
454
|
+
console.log('');
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Stop the Telegram bot.
|
|
460
|
+
*/
|
|
461
|
+
export async function telegramStopCommand() {
|
|
462
|
+
if (botRunning) {
|
|
463
|
+
stopBot();
|
|
464
|
+
success('Telegram bot stopped');
|
|
465
|
+
} else {
|
|
466
|
+
warn('Telegram bot is not running in this process');
|
|
467
|
+
info('If running via daemon, use: darksol daemon stop');
|
|
468
|
+
}
|
|
469
|
+
console.log('');
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Show Telegram bot status.
|
|
474
|
+
*/
|
|
475
|
+
export async function telegramStatusCommand() {
|
|
476
|
+
showSection('TELEGRAM BOT STATUS');
|
|
477
|
+
|
|
478
|
+
const token = getKeyAuto('telegram');
|
|
479
|
+
const hasToken = Boolean(token);
|
|
480
|
+
const savedUsername = getConfig('telegram.botUsername');
|
|
481
|
+
|
|
482
|
+
const pairs = [
|
|
483
|
+
['Token', hasToken ? theme.success('configured') : theme.dim('not set')],
|
|
484
|
+
];
|
|
485
|
+
|
|
486
|
+
if (hasToken) {
|
|
487
|
+
try {
|
|
488
|
+
const spin = spinner('Checking bot...').start();
|
|
489
|
+
const botResult = await validateToken(token);
|
|
490
|
+
spin.succeed('Bot reachable');
|
|
491
|
+
pairs.push(['Bot Name', botResult.first_name]);
|
|
492
|
+
pairs.push(['Username', `@${botResult.username}`]);
|
|
493
|
+
pairs.push(['Bot ID', String(botResult.id)]);
|
|
494
|
+
pairs.push(['Can Join Groups', botResult.can_join_groups ? 'yes' : 'no']);
|
|
495
|
+
} catch (err) {
|
|
496
|
+
pairs.push(['Connection', theme.error('failed')]);
|
|
497
|
+
pairs.push(['Error', err.message]);
|
|
498
|
+
}
|
|
499
|
+
} else if (savedUsername) {
|
|
500
|
+
pairs.push(['Username', `@${savedUsername} (cached)`]);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (botRunning) {
|
|
504
|
+
pairs.push(['Polling', theme.success('active')]);
|
|
505
|
+
pairs.push(['Active Chats', String(chatSessions.size)]);
|
|
506
|
+
} else {
|
|
507
|
+
pairs.push(['Polling', theme.dim('stopped')]);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
kvDisplay(pairs);
|
|
511
|
+
console.log('');
|
|
512
|
+
|
|
513
|
+
if (!hasToken) {
|
|
514
|
+
info('Setup: darksol telegram setup');
|
|
515
|
+
console.log('');
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Send a direct message to a chat.
|
|
521
|
+
* @param {string} chatId
|
|
522
|
+
* @param {string[]} messageParts
|
|
523
|
+
*/
|
|
524
|
+
export async function telegramSendCommand(chatId, messageParts) {
|
|
525
|
+
const token = getKeyAuto('telegram');
|
|
526
|
+
if (!token) {
|
|
527
|
+
error('No Telegram bot token found');
|
|
528
|
+
info('Run: darksol telegram setup');
|
|
529
|
+
console.log('');
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const text = messageParts.join(' ');
|
|
534
|
+
if (!text) {
|
|
535
|
+
error('Message text is required');
|
|
536
|
+
console.log('');
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const spin = spinner('Sending message...').start();
|
|
541
|
+
try {
|
|
542
|
+
await sendMessage(token, chatId, text, { parseMode: '' });
|
|
543
|
+
spin.succeed('Message sent');
|
|
544
|
+
} catch (err) {
|
|
545
|
+
spin.fail('Send failed');
|
|
546
|
+
error(err.message);
|
|
547
|
+
}
|
|
548
|
+
console.log('');
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// ─────────────────────────────────────
|
|
552
|
+
// DAEMON SERVICE INTERFACE
|
|
553
|
+
// ─────────────────────────────────────
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Service handler for daemon manager registration.
|
|
557
|
+
*/
|
|
558
|
+
export const telegramServiceHandler = {
|
|
559
|
+
async start(opts = {}) {
|
|
560
|
+
const token = opts.token || getKeyAuto('telegram');
|
|
561
|
+
if (!token) throw new Error('No Telegram bot token configured');
|
|
562
|
+
await startBot(token);
|
|
563
|
+
},
|
|
564
|
+
async stop() {
|
|
565
|
+
stopBot();
|
|
566
|
+
},
|
|
567
|
+
status() {
|
|
568
|
+
return getBotStatus();
|
|
569
|
+
},
|
|
570
|
+
};
|