- SIGNALK_FILES, per specificare dove verranno salvati i file generati dal plugin - HOST_NAME, per specificare il nome del dispositivo che sta hostando il plugin. Inoltre, migliora il codice e lo adatta al nuovo path variabile SIGNALK_FILES
948 lines
32 KiB
JavaScript
948 lines
32 KiB
JavaScript
/**
|
|
* telegram.core.js - Bot Telegram ottimizzato per MEB SignalK
|
|
* Gestione utenti, comandi e live updates
|
|
*/
|
|
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
const { paths } = require("../config.js");
|
|
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 = paths.telegramUsers;
|
|
const logs_references_file = paths.logsReferences;
|
|
const authorized_admins_file = paths.authorizedAdmins;
|
|
|
|
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) {
|
|
console.warn('[Telegram] send() chiamato ma bot non inizializzato');
|
|
return;
|
|
}
|
|
const users = loadUsers();
|
|
const loggedUsers = users.filter(u => u.hasLoggedYet && u.chatID);
|
|
|
|
console.log(`[Telegram] send() - Utenti totali: ${users.length}, Utenti loggati: ${loggedUsers.length}`);
|
|
|
|
if (loggedUsers.length === 0) {
|
|
console.warn('[Telegram] Nessun utente loggato a cui inviare il messaggio');
|
|
return;
|
|
}
|
|
|
|
for (const user of loggedUsers) {
|
|
try {
|
|
await bot.sendMessage(user.chatID, message);
|
|
console.log(`[Telegram] Messaggio inviato a ${user.chatID}`);
|
|
} 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 <token>");
|
|
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 <token>");
|
|
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 <token>");
|
|
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 <token>");
|
|
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 <token> 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 <sec>\`\n` +
|
|
`\`/changei api <sec>\``,
|
|
{ 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 <token>");
|
|
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
|
|
};
|
|
|
|
|
|
|