/** * telegram.core.js - Bot Telegram ottimizzato per MEB SignalK * Gestione utenti, comandi e live updates */ const fs = require("fs"); const path = require("path"); const { encrypt, decrypt, generateToken, generateReadableToken, encryptLog, decryptLog, loadSecureFile, saveSecureFile } = require("../tools/crypt"); const TelegramBot = require('node-telegram-bot-api'); function getSK(path) { if (!app) return null; const v = app.getSelfPath(path); return v && v.value !== undefined && v.value !== null ? v.value : null; } // ==================== INIZIALIZZAZIONE BOT ==================== const BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN; let bot = null; function initBot() { if (!BOT_TOKEN) { console.warn("[Telegram] BOT_TOKEN non impostato: bot disabilitato."); return null; } // Riusa istanza esistente se disponibile if (global.__meb_telegram_bot) { bot = global.__meb_telegram_bot; console.log("[Telegram] Riutilizzo istanza bot esistente"); } else { bot = new TelegramBot(BOT_TOKEN, { polling: true }); global.__meb_telegram_bot = bot; console.log("[Telegram] Nuova istanza bot creata"); } // Registra handlers solo una volta if (!global.__meb_telegram_handlers) { global.__meb_telegram_handlers = true; registerHandlers(); console.log("[Telegram] Handlers registrati"); } return bot; } // Inizializza all'import bot = initBot(); // ==================== CONFIGURAZIONE ==================== const CONFIG = { filesPerPage: 8, liveUpdateInterval: 3000, fileExpirationTime: 10 }; const telegram_users_file = path.join(__dirname, "..", "telegram_users.json"); const logs_references_file = path.join(__dirname, "..", "datasetModels/logs_references.json"); const authorized_admins_file = path.join(__dirname, "..", "authorized_admins.txt"); let app = null; // Maps per gestione timer e stati const liveParamIntervals = new Map(); const keyExpirationTimers = new Map(); // ==================== GESTIONE FILE SENSIBILI ==================== function loadAuthorizedAdmins() { try { if (!fs.existsSync(authorized_admins_file)) { return new Set(); } const content = fs.readFileSync(authorized_admins_file, 'utf8'); const admins = content .split('\n') .map(line => line.trim()) .filter(line => line && !line.startsWith('#')); return new Set(admins); } catch (error) { console.error('[Telegram] Errore caricamento admin:', error.message); return new Set(); } } function saveAuthorizedAdmins(admins) { try { const adminArray = Array.from(admins); const content = '# Authorized Admin ChatIDs (one per line)\n' + adminArray.join('\n'); fs.writeFileSync(authorized_admins_file, content, 'utf8'); return true; } catch (error) { console.error('[Telegram] Errore salvataggio admin:', error.message); return false; } } function isAdmin(chatID) { const admins = loadAuthorizedAdmins(); return admins.has(String(chatID)); } function loadUsers() { return loadSecureFile(telegram_users_file, []); } function saveUsers(users) { saveSecureFile(telegram_users_file, users); } function loadLogsReferences() { return loadSecureFile(logs_references_file, { references: [] }); } function saveLogsReferences(data) { saveSecureFile(logs_references_file, data); } function isAuthenticated(chatID) { const user = getUserByChatID(chatID); return user && user.hasLoggedYet; } function createNewUser(permissions = ["basic"]) { const users = loadUsers(); const newUser = { token: generateReadableToken(24), chatID: null, isAuthorized: permissions, hasLoggedYet: false }; users.push(newUser); saveUsers(users); return newUser; } function login(token, chatID) { const users = loadUsers(); const userIDX = users.findIndex(u => u.token === token); if (userIDX === -1) { throw new Error("Token non valido"); } const user = users[userIDX]; if (user.hasLoggedYet && user.chatID && user.chatID !== String(chatID)) { throw new Error("Questo token Γ¨ giΓ  associato ad un altro account"); } if (!user.hasLoggedYet) { const newToken = generateReadableToken(32); user.token = newToken; user.hasLoggedYet = true; user.chatID = String(chatID); users[userIDX] = user; saveUsers(users); return { ...user, isFirstLogin: true, newToken }; } user.chatID = String(chatID); users[userIDX] = user; saveUsers(users); return { ...user, isFirstLogin: false }; } function logout(chatID) { const users = loadUsers(); const userIDX = users.findIndex(u => u.chatID === String(chatID)); if (userIDX === -1) { return null; } saveUsers(users); return users[userIDX]; } function getUserWith(token) { const users = loadUsers(); return users.find(u => u.token === token); } function getUserByChatID(chatID) { const users = loadUsers(); return users.find(u => u.chatID === String(chatID)); } async function linkBot(appInstance) { app = appInstance; if (!bot) { console.warn("[MEB TELEGRAM] linkBot chiamato senza TOKEN: ritorno null."); return null; } return bot; } function fetchFiles(chatId, page = 0) { const logDirectory = path.join(__dirname, "..", "datasetModels/saved_datas"); try { const logsData = loadLogsReferences(); const registeredFiles = new Set((logsData.references || []).map(r => r.name)); const items = fs.readdirSync(logDirectory); const files = items.filter(item => { const fullPath = path.join(logDirectory, item); return fs.statSync(fullPath).isFile() && registeredFiles.has(item); }); if (files.length === 0) { bot.sendMessage(chatId, "πŸ“‚ Non ci sono log salvati."); return; } const sortedFiles = files .map(file => ({ name: file, time: fs.statSync(path.join(logDirectory, file)).mtime.getTime() })) .sort((a, b) => b.time - a.time) .map(file => file.name); const totalPages = Math.ceil(sortedFiles.length / CONFIG.filesPerPage); let currentPage = page; if (currentPage < 0) currentPage = 0; if (currentPage > totalPages - 1) currentPage = totalPages - 1; const startIdx = currentPage * CONFIG.filesPerPage; const endIdx = startIdx + CONFIG.filesPerPage; const pageFiles = sortedFiles.slice(startIdx, endIdx); const fileButtons = pageFiles.map(file => [ { text: `πŸ“„ ${file}`, callback_data: `request_file_${file}` } ]); const navigationButtons = []; if (totalPages > 1) { const navRow = []; if (currentPage > 0) { navRow.push({ text: "←", callback_data: `page_${currentPage - 1}` }); } navRow.push({ text: `πŸ“– ${currentPage + 1}/${totalPages}`, callback_data: `page_info` }); if (currentPage < totalPages - 1) { navRow.push({ text: "β†’", callback_data: `page_${currentPage + 1}` }); } navigationButtons.push(navRow); } navigationButtons.push([{ text: "Annulla", callback_data: "dismiss" }]); bot.sendMessage(chatId, `πŸ“₯ *Logs di Bordo*\n` + `Ogni file corrisponde ad una *sessione*. Seleziona un file per scaricarlo.\n` + `⚠️ Avrai solo *10 secondi* per salvare file e chiave.`, { parse_mode: 'Markdown', reply_markup: { inline_keyboard: [...fileButtons, ...navigationButtons] } } ); } catch (error) { bot.sendMessage(chatId, `Errore lettura directory: ${error.message}`); } } function getCurrentPosition() { if (!app) return null; const position = app.getSelfPath('navigation.position'); if (!position) return null; return { latitude: position.value.latitude, longitude: position.value.longitude, }; } async function send(message) { if (!bot) return; const users = loadUsers(); const loggedUsers = users.filter(u => u.hasLoggedYet && u.chatID); for (const user of loggedUsers) { try { await bot.sendMessage(user.chatID, message); } catch (error) { console.error(`[Telegram] Send error to ${user.chatID}:`, error.message); } } } // ==================== RENDER FUNCTIONS ==================== function renderPositionText() { if (!app) return "❌ App non disponibile"; const pos = app.getSelfPath('navigation.position')?.value; const sog = getSK('navigation.speedOverGround'); const cog = getSK('navigation.courseOverGroundTrue'); const heading = getSK('navigation.headingTrue'); const lat = pos?.latitude?.toFixed(5) ?? "N/A"; const lon = pos?.longitude?.toFixed(5) ?? "N/A"; const speed = sog != null ? (sog * 1.94384).toFixed(1) : "N/A"; // m/s to knots const course = cog != null ? (cog * 180 / Math.PI).toFixed(0) : "N/A"; // rad to deg const headingDeg = heading != null ? (heading * 180 / Math.PI).toFixed(0) : "N/A"; return `πŸ“ *Posizione & VelocitΓ *\n\n` + `Latitudine: \`${lat}\`\n` + `Longitudine: \`${lon}\`\n` + `SOG: ${speed} kn\n` + `COG: ${course}Β°\n` + `Heading: ${headingDeg}Β°`; } function renderWindText() { const speed = getSK('meb.wind.speed'); const direction = getSK('meb.wind.direction'); return `🌬️ *Vento*\n\n` + `VelocitΓ : ${speed} km/h\n` + `Direzione: ${direction}Β°\n`; } function renderWavesText() { const height = getSK('meb.waves.height'); const period = getSK('meb.waves.period'); const dir = getSK('meb.waves.direction'); return `🌊 *Onde*\n\n` + `Altezza: ${height} m\n` + `Periodo: ${period} s\n` + `Direzione: ${dir}Β°`; } function renderForecastsText() { const temp = getSK('meb.temperature'); const humidity = getSK('meb.humidity'); const pressure = getSK('meb.pressure'); const rain = getSK('meb.precipitation'); return `⛅️ *Previsioni Meteo*\n\n` + `Temperatura: ${temp} Β°C\n` + `UmiditΓ : ${humidity} %\n` + `Pressione: ${pressure} hPa\n`; } function renderBatteriesText() { const voltage = getSK('electrical.batteries.traction.voltage'); const current = getSK('electrical.batteries.traction.current'); const soc = getSK('electrical.batteries.traction.stateOfCharge'); const power = getSK('electrical.batteries.traction.power'); return `πŸ”‹ *Batterie*\n\n` + `Tensione: ${voltage?.toFixed(1) ?? "N/A"} V\n` + `Corrente: ${current?.toFixed(1) ?? "N/A"} A\n` + `SOC: ${soc != null ? (soc * 100).toFixed(0) : "N/A"} %\n` + `Potenza: ${power?.toFixed(0) ?? "N/A"} W`; } function renderDashboardText() { const posText = renderPositionText() const windText = renderWindText() const wavesText = renderWavesText() const forecastText = renderForecastsText() const battText = renderBatteriesText() return `πŸ“Š *Dashboard Completa*\n` + `\n${posText}\n\n` + `\n${forecastText}\n\n` + `\n${windText}\n\n` + `\n${wavesText}\n\n` + `\n${battText}`; } // ==================== REGISTRAZIONE HANDLERS ==================== function registerHandlers() { if (!bot) return; // Handler: /start bot.onText(/\/start/, (msg) => { const chatId = msg.chat.id; if (isAuthenticated(chatId)) { const menu = { keyboard: [ [{ text: "πŸ“Š Dashboard" }], [{ text: "Parametri di Bordo" }], [{ text: "File di Logs" }], [{ text: "Genera un nuovo log" }], [{ text: "Stato dei Log" }] ], resize_keyboard: true, one_time_keyboard: false }; bot.sendMessage(chatId, "Benvenuto nel Data Console.\n" + "β€’ Visualizza i dati del computer di bordo\n" + "β€’ Ricevi aggiornamenti su parametri a scelta\n" + "β€’ Scarica i file di log della barca", { parse_mode: 'Markdown', reply_markup: menu } ); } else { bot.sendMessage(chatId, "Benvenuto nel MEB Data Console!\n" + "Per accedere ai dati Γ¨ necessario un token di accesso.", { parse_mode: 'Markdown' } ); bot.sendMessage(chatId, "πŸ‘€ Login", { reply_markup: { inline_keyboard: [ [{ text: "❓ Come ottengo un token", callback_data: "token_login_question" }], [{ text: "πŸ”‘ Ho un token", callback_data: "token_ready" }] ] }, parse_mode: 'Markdown' }); } }); // Menu testuale bot.onText(/πŸ“Š Dashboard/, (msg) => { const chatId = msg.chat.id; if (!isAuthenticated(chatId)) { bot.sendMessage(chatId, "Effettua prima il login con /login "); return; } const dashboardMsg = renderDashboardText(); bot.sendMessage(chatId, dashboardMsg, { parse_mode: 'Markdown', reply_markup: { inline_keyboard: [ [{ text: "πŸ”„ Aggiorna", callback_data: "refresh_dashboard" }], [{ text: "πŸ“‘ Live (3s)", callback_data: "live_dashboard" }] ] } }); }); bot.onText(/File di Logs/, (msg) => { const chatId = msg.chat.id; if (!isAuthenticated(chatId)) { bot.sendMessage(chatId, "Effettua prima il login con /login "); return; } fetchFiles(chatId, 0); }); bot.onText(/Parametri di Bordo/, (msg) => { const chatId = msg.chat.id; if (!isAuthenticated(chatId)) { bot.sendMessage(chatId, "Effettua il login con /login "); return; } bot.sendMessage(chatId, "*Parametri di Bordo*\nQui potrai visualizzare i parametri attuali del computer di bordo. Scegli il parametro che vuoi visualizzare dal menu qui sotto.", { parse_mode: 'Markdown', reply_markup: { inline_keyboard: [ [{ text: "πŸ“Š Dashboard", callback_data: "get_dashboard" }], [{ text: "⛅️ Meteo", callback_data: "get_forecasts" }], [{ text: "πŸ“ Posizione", callback_data: "get_position" }], [{ text: "🌬️ Vento", callback_data: "get_wind" }], [{ text: "🌊 Onde", callback_data: "get_waves" }], [{ text: "πŸ”‹ Batterie", callback_data: "get_batteries" }], [{ text: "Annulla", callback_data: "dismiss" }] ] } }); }); // Login bot.onText(/\/login\s+(.+)/, (msg, match) => { const chatID = msg.chat.id; const token = (match && match[1] || "").trim(); if (!token) { bot.sendMessage(chatID, "Inserisci il token: /login "); return; } try { const result = login(token, chatID); if (!result) { bot.sendMessage(chatID, "Token non valido."); return; } if (result.isFirstLogin) { bot.sendMessage(chatID, `*Primo accesso completato!*\n\n` + `Il tuo nuovo token permanente:\n\`${result.newToken}\`\n\n` + `Salvalo! Non potrΓ  essere usato da altri account.`, { parse_mode: 'Markdown' } ); } else { bot.sendMessage(chatID, "βœ… Login effettuato!"); } const menu = { keyboard: [ [{ text: "πŸ“Š Dashboard" }], [{ text: "Parametri di Bordo" }], [{ text: "File di Logs" }], [{ text: "Genera un nuovo log" }], [{ text: "Stato dei Log" }] ], resize_keyboard: true }; bot.sendMessage(chatID, "Menu principale:", { reply_markup: menu }); } catch (error) { bot.sendMessage(chatID, `❌ ${error.message}`); } }); bot.onText(/\/logout/, (msg) => { const chatID = msg.chat.id; const user = logout(chatID); if (!user) { bot.sendMessage(chatID, "Non sei loggato."); return; } bot.sendMessage(chatID, "Logout effettuato. Usa /login per rientrare."); }); // Admin commands bot.onText(/\/newuser(?:\s+(.*))?/, (msg, match) => { const chatID = msg.chat.id; if (!isAdmin(chatID)) { bot.sendMessage(chatID, "β›” Non autorizzato."); return; } const permissionsArg = (match && match[1] || "").trim(); const permissions = permissionsArg ? permissionsArg.split(',').map(p => p.trim()).filter(p => p) : ["basic"]; try { const newUser = createNewUser(permissions); bot.sendMessage(chatID, `βœ… *Nuovo utente creato*\n\nToken: \`${newUser.token}\``, { parse_mode: 'Markdown' } ); } catch (error) { bot.sendMessage(chatID, `❌ ${error.message}`); } }); bot.onText(/\/addadmin\s+(\d+)/, (msg, match) => { const chatID = msg.chat.id; const newAdminID = match && match[1]; if (!isAdmin(chatID)) { bot.sendMessage(chatID, "β›” Non autorizzato."); return; } const admins = loadAuthorizedAdmins(); if (admins.has(newAdminID)) { bot.sendMessage(chatID, "GiΓ  admin."); return; } admins.add(newAdminID); saveAuthorizedAdmins(admins); bot.sendMessage(chatID, `βœ… Admin \`${newAdminID}\` aggiunto.`, { parse_mode: 'Markdown' }); }); bot.onText(/\/removeadmin\s+(\d+)/, (msg, match) => { const chatID = msg.chat.id; const adminToRemove = match && match[1]; if (!isAdmin(chatID)) { bot.sendMessage(chatID, "β›” Non autorizzato."); return; } if (adminToRemove === String(chatID)) { bot.sendMessage(chatID, "Non puoi rimuovere te stesso."); return; } const admins = loadAuthorizedAdmins(); if (!admins.has(adminToRemove)) { bot.sendMessage(chatID, "Non Γ¨ admin."); return; } admins.delete(adminToRemove); saveAuthorizedAdmins(admins); bot.sendMessage(chatID, `βœ… Admin \`${adminToRemove}\` rimosso.`, { parse_mode: 'Markdown' }); }); bot.onText(/\/listusers/, (msg) => { const chatID = msg.chat.id; if (!isAdmin(chatID)) { bot.sendMessage(chatID, "β›” Non autorizzato."); return; } const users = loadUsers(); if (users.length === 0) { bot.sendMessage(chatID, "Nessun utente."); return; } let message = `πŸ‘₯ *Utenti:* ${users.length}\n\n`; users.forEach((user, idx) => { const status = user.hasLoggedYet ? 'βœ…' : '⏳'; message += `${idx + 1}. ${status} \`${user.chatID || 'N/A'}\`\n`; }); bot.sendMessage(chatID, message, { parse_mode: 'Markdown' }); }); bot.onText(/\/mychatid/, (msg) => { bot.sendMessage(msg.chat.id, `ChatID: \`${msg.chat.id}\``, { parse_mode: 'Markdown' }); }); // Interval control bot.onText(/\/changei\s+(log|api)\s+(\d+)/, (msg, match) => { const chatID = msg.chat.id; if (!isAdmin(chatID)) { bot.sendMessage(chatID, "β›” Non autorizzato."); return; } const type = match[1]; const seconds = parseInt(match[2], 10); if (isNaN(seconds) || seconds < 1) { bot.sendMessage(chatID, "❌ Secondi non validi (min 1)."); return; } const newIntervalMs = seconds * 1000; // Debug: verifica stato app if (!app) { bot.sendMessage(chatID, "❌ App non inizializzata. Riprova tra qualche secondo."); console.error('[Telegram] app Γ¨ null in change_interval'); return; } if (!app.intervalControl) { bot.sendMessage(chatID, "❌ Sistema intervalControl non disponibile. Il plugin potrebbe non essere ancora avviato."); console.error('[Telegram] app.intervalControl non esiste'); return; } try { const result = app.intervalControl.updateInterval(type, newIntervalMs); if (result) { const typeLabel = type === 'log' ? 'Log recording' : 'OpenMeteo API'; bot.sendMessage(chatID, `βœ… *${typeLabel}* aggiornato a *${seconds}s*`, { parse_mode: 'Markdown' } ); } else { bot.sendMessage(chatID, "❌ Tipo non valido. Usa: `log` o `api`", { parse_mode: 'Markdown' }); } } catch (error) { console.error('[Telegram] Errore change_interval:', error); bot.sendMessage(chatID, `❌ Errore: ${error.message}`); } }); bot.onText(/\/intervals/, (msg) => { const chatID = msg.chat.id; if (!isAdmin(chatID)) { bot.sendMessage(chatID, "β›” Non autorizzato."); return; } if (!app) { bot.sendMessage(chatID, "❌ App non inizializzata."); return; } if (!app.intervalControl) { bot.sendMessage(chatID, "❌ Sistema intervalControl non disponibile."); return; } try { const intervals = app.intervalControl.getIntervals(); bot.sendMessage(chatID, `⏱️ *Intervalli Attuali*\n\n` + `πŸ“ Log: *${intervals.log_interval / 1000}s*\n` + `🌀️ API: *${intervals.openmeteo_interval / 1000}s*\n\n` + `Per modificare:\n` + `\`/changei log \`\n` + `\`/changei api \``, { parse_mode: 'Markdown' } ); } catch (error) { console.error('[Telegram] Errore intervals:', error); bot.sendMessage(chatID, `❌ Errore: ${error.message}`); } }); // Callback query handler bot.on('callback_query', async (query) => { const chatId = query.message.chat.id; const messageId = query.message.message_id; const data = query.data; await bot.answerCallbackQuery(query.id); if (!isAuthenticated(chatId) && !['token_login_question', 'token_ready'].includes(data)) { bot.sendMessage(chatId, "Effettua prima il login."); return; } switch (data) { case 'dismiss': bot.deleteMessage(chatId, messageId).catch(() => {}); break; case 'token_login_question': bot.sendMessage(chatId, "Per ottenere un token, contatta un amministratore del sistema." ); break; case 'token_ready': bot.sendMessage(chatId, "Usa: /login "); break; case 'get_dashboard': case 'refresh_dashboard': bot.editMessageText(renderDashboardText(), { chat_id: chatId, message_id: messageId, parse_mode: 'Markdown', reply_markup: { inline_keyboard: [ [{ text: "πŸ”„ Aggiorna", callback_data: "refresh_dashboard" }], [{ text: "πŸ“‘ Live (3s)", callback_data: "live_dashboard" }], [{ text: "⏹️ Chiudi", callback_data: "dismiss" }] ] } }).catch(() => {}); break; case 'live_dashboard': // Ferma eventuali live precedenti if (liveParamIntervals.has(chatId)) { clearInterval(liveParamIntervals.get(chatId)); } const interval = setInterval(() => { bot.editMessageText(renderDashboardText(), { chat_id: chatId, message_id: messageId, parse_mode: 'Markdown', reply_markup: { inline_keyboard: [ [{ text: "⏹️ Stop Live", callback_data: "stop_live" }] ] } }).catch(() => { clearInterval(interval); liveParamIntervals.delete(chatId); }); }, CONFIG.liveUpdateInterval); liveParamIntervals.set(chatId, interval); break; case 'stop_live': if (liveParamIntervals.has(chatId)) { clearInterval(liveParamIntervals.get(chatId)); liveParamIntervals.delete(chatId); } bot.editMessageText(renderDashboardText(), { chat_id: chatId, message_id: messageId, parse_mode: 'Markdown', reply_markup: { inline_keyboard: [ [{ text: "πŸ”„ Aggiorna", callback_data: "refresh_dashboard" }], [{ text: "πŸ“‘ Live (3s)", callback_data: "live_dashboard" }], [{ text: "⏹️ Chiudi", callback_data: "dismiss" }] ] } }).catch(() => {}); break; case 'get_position': bot.editMessageText(renderPositionText(), { chat_id: chatId, message_id: messageId, parse_mode: 'Markdown', reply_markup: { inline_keyboard: [[{ text: "← Indietro", callback_data: "back_to_params" }]] } }).catch(() => {}); break; case 'get_wind': bot.editMessageText(renderWindText(), { chat_id: chatId, message_id: messageId, parse_mode: 'Markdown', reply_markup: { inline_keyboard: [[{ text: "← Indietro", callback_data: "back_to_params" }]] } }).catch(() => {}); break; case 'get_waves': bot.editMessageText(renderWavesText(), { chat_id: chatId, message_id: messageId, parse_mode: 'Markdown', reply_markup: { inline_keyboard: [[{ text: "← Indietro", callback_data: "back_to_params" }]] } }).catch(() => {}); break; case 'get_forecasts': bot.editMessageText(renderForecastsText(), { chat_id: chatId, message_id: messageId, parse_mode: 'Markdown', reply_markup: { inline_keyboard: [[{ text: "← Indietro", callback_data: "back_to_params" }]] } }).catch(() => {}); break; case 'get_batteries': bot.editMessageText(renderBatteriesText(), { chat_id: chatId, message_id: messageId, parse_mode: 'Markdown', reply_markup: { inline_keyboard: [[{ text: "← Indietro", callback_data: "back_to_params" }]] } }).catch(() => {}); break; case 'back_to_params': bot.editMessageText("*Parametri di Bordo*\nQui potrai visualizzare i parametri attuali del computer di bordo. Scegli il parametro che vuoi visualizzare dal menu qui sotto.", { chat_id: chatId, message_id: messageId, parse_mode: 'Markdown', reply_markup: { inline_keyboard: [ [{ text: "πŸ“Š Dashboard Completa", callback_data: "get_dashboard" }], [{ text: "⛅️ Meteo", callback_data: "get_forecasts" }], [{ text: "πŸ“ Posizione", callback_data: "get_position" }], [{ text: "🌬️ Vento", callback_data: "get_wind" }], [{ text: "🌊 Onde", callback_data: "get_waves" }], [{ text: "πŸ”‹ Batterie", callback_data: "get_batteries" }], [{ text: "Annulla", callback_data: "dismiss" }] ] } }).catch(() => {}); break; default: // Gestione paginazione file if (data.startsWith('page_')) { const page = parseInt(data.replace('page_', ''), 10); if (!isNaN(page)) { bot.deleteMessage(chatId, messageId).catch(() => {}); fetchFiles(chatId, page); } } // Gestione richiesta file else if (data.startsWith('request_file_')) { const fileName = data.replace('request_file_', ''); const filePath = path.join(__dirname, "..", "datasetModels/saved_datas", fileName); if (fs.existsSync(filePath)) { const logsData = loadLogsReferences(); const fileRef = (logsData.references || []).find(r => r.name === fileName); const key = fileRef?.key || "Chiave non trovata"; try { const fileMsg = await bot.sendDocument(chatId, filePath, { caption: `πŸ”‘ Chiave: \`${key}\`\n⚠️ Questo messaggio verrΓ  eliminato tra 10 secondi.`, parse_mode: 'Markdown' }); // Elimina dopo 10 secondi setTimeout(() => { bot.deleteMessage(chatId, fileMsg.message_id).catch(() => {}); }, CONFIG.fileExpirationTime * 1000); } catch (error) { bot.sendMessage(chatId, `❌ Errore invio file: ${error.message}`); } } else { bot.sendMessage(chatId, "❌ File non trovato."); } bot.deleteMessage(chatId, messageId).catch(() => {}); } break; } }); } // Fine registerHandlers module.exports = { linkBot, send, loadUsers, saveUsers, getUserByChatID, isAuthenticated, isAdmin };