require('dotenv').config(); const { Telegraf } = require('telegraf'); const fs = require('fs'); const path = require('path'); const archiver = require('archiver'); const schedule = require('node-schedule'); // Initialisiere den Bot const bot = new Telegraf(process.env.TELEGRAM_TOKEN); // Erlaubte Gruppen-ID und Themen-ID aus der .env-Datei const allowedChatId = process.env.ALLOWED_CHAT_ID; const allowedThreadId = parseInt(process.env.ALLOWED_THREAD_ID, 10); // Benutzerstatus-Management const userStates = {}; // Log-Ordner und Datei-Pfade const logDir = path.join(__dirname, 'Log'); const errorLogFilePath = path.join(logDir, 'error.log'); const wishLogFilePath = path.join(logDir, 'wish.log'); const notFoundLogFilePath = path.join(logDir, 'not_found.json'); // Pfad zur JSON-Datei für nicht gefundene Wünsche // Stelle sicher, dass der Log-Ordner existiert if (!fs.existsSync(logDir)) { fs.mkdirSync(logDir); } // Funktion zum Schreiben von Fehlern in die Log-Datei function logError(error) { const errorMessage = `${new Date().toISOString()} - ${error.message}\n${error.stack}\n\n`; fs.appendFile(errorLogFilePath, errorMessage, (err) => { if (err) { console.error(`Error writing to error log file: ${err.message}`); } }); } // Funktion zum Schreiben von Wünschen in die Wunsch-Log-Datei function logWish(wish, category, link) { const wishMessage = `${new Date().toISOString()} - Kategorie: ${category} - Wunsch: ${wish} - Link: ${link || 'Kein Link'}\n\n`; fs.appendFile(wishLogFilePath, wishMessage, (err) => { if (err) { console.error(`Error writing to wish log file: ${err.message}`); } }); } // Funktion zum Speichern von „Nicht gefunden“-Wünschen in der JSON-Datei function saveNotFoundWish(wish, category) { let notFoundWishes = []; if (fs.existsSync(notFoundLogFilePath)) { notFoundWishes = JSON.parse(fs.readFileSync(notFoundLogFilePath, 'utf8')); } notFoundWishes.push({ wish, category, timestamp: new Date().toISOString() }); fs.writeFileSync(notFoundLogFilePath, JSON.stringify(notFoundWishes, null, 2), 'utf8'); } // Funktion zum Löschen von „Nicht gefunden“-Wünschen aus der JSON-Datei function deleteNotFoundWish(index) { let notFoundWishes = []; if (fs.existsSync(notFoundLogFilePath)) { notFoundWishes = JSON.parse(fs.readFileSync(notFoundLogFilePath, 'utf8')); } if (index >= 0 && index < notFoundWishes.length) { notFoundWishes.splice(index, 1); fs.writeFileSync(notFoundLogFilePath, JSON.stringify(notFoundWishes, null, 2), 'utf8'); return true; } return false; } // Funktion zum Erstellen des Inline-Keyboards für die Auswahl der Wunschkategorie function getCategoryKeyboard() { return { reply_markup: JSON.stringify({ inline_keyboard: [ [{ text: 'Film', callback_data: 'category_film' }], [{ text: 'Serie', callback_data: 'category_serie' }], [{ text: 'Anime', callback_data: 'category_anime' }], [{ text: 'Disney', callback_data: 'category_disney' }], [{ text: 'Medizin', callback_data: 'category_medizin' }], [{ text: 'Survival', callback_data: 'category_survival' }], [{ text: 'WWE', callback_data: 'category_wwe' }], [{ text: 'Musik', callback_data: 'category_musik' }], [{ text: 'Bollywood', callback_data: 'category_bollywood' }] // Bollywood Kategorie hinzugefügt ] }) }; } // Funktion zum Erstellen der Inline-Keyboards für "Erledigt" und "Nicht gefunden" nebeneinander function getWishActionKeyboard() { return { reply_markup: JSON.stringify({ inline_keyboard: [ [ { text: '✅ Erledigt', callback_data: 'wish_fulfilled' }, { text: '❌ Nicht gefunden', callback_data: 'wish_not_found' } ] ] }) }; } // Funktion zum Erstellen der Inline-Keyboards für das Löschen von „Nicht gefunden“-Wünschen function getDeleteNotFoundWishKeyboard() { return { reply_markup: JSON.stringify({ inline_keyboard: [ [{ text: '🗑️ Löschen', callback_data: 'delete_not_found' }] ] }) }; } // Funktion zum Senden des Wunsches an die Gruppe async function sendWish(wish, category, chatId, userId, link) { const message = `🎬 *Ein neuer Wunsch ist eingegangen!*\n\n🔹 Kategorie: ${category}\n\n🔸 Titel: ${wish}\n\n🔗 Link: ${link || 'Kein Link'}`; try { // Logge den Wunsch logWish(wish, category, link); // Sende die Nachricht an die Gruppe mit "Erledigt"-Button und "Nicht gefunden"-Button nebeneinander await bot.telegram.sendMessage(allowedChatId, message, { message_thread_id: allowedThreadId, // Sende in das erlaubte Thema reply_markup: getWishActionKeyboard().reply_markup, // Füge die Buttons hinzu }); } catch (error) { logError(new Error(`Error sending ${category} wish: ${error.message}`)); } } // Funktion zur Archivierung der Logs function archiveLogs() { const dateStr = new Date().toISOString().split('T')[0]; const zipFilePath = path.join(logDir, `logs_${dateStr}.zip`); const output = fs.createWriteStream(zipFilePath); const archive = archiver('zip', { zlib: { level: 9 } }); output.on('close', () => { console.log(`Logs archived to ${zipFilePath} (${archive.pointer()} total bytes)`); // Lösche die ursprünglichen Log-Dateien if (fs.existsSync(errorLogFilePath)) fs.unlinkSync(errorLogFilePath); if (fs.existsSync(wishLogFilePath)) fs.unlinkSync(wishLogFilePath); // Neue Log-Dateien erstellen fs.writeFileSync(errorLogFilePath, ''); fs.writeFileSync(wishLogFilePath, ''); // Maximal 4 Zip-Archive behalten const files = fs.readdirSync(logDir).filter(file => file.endsWith('.zip')); if (files.length > 4) { const sortedFiles = files.sort(); for (let i = 0; i < sortedFiles.length - 4; i++) { fs.unlinkSync(path.join(logDir, sortedFiles[i])); } } }); archive.on('error', (err) => { logError(new Error(`Error creating log archive: ${err.message}`)); }); archive.pipe(output); archive.file(errorLogFilePath, { name: 'error.log' }); archive.file(wishLogFilePath, { name: 'wish.log' }); archive.finalize(); } // Scheduler für die tägliche Archivierung um Mitternacht schedule.scheduleJob('0 0 * * *', archiveLogs); // Callback-Query-Handler bot.on('callback_query', async (ctx) => { const data = ctx.callbackQuery.data; const chatId = ctx.chat.id.toString(); const userId = ctx.callbackQuery.from.id; try { if (chatId !== allowedChatId) { console.log(`Callback-Query aus nicht erlaubtem Chat: ${chatId}`); return; } if (data === 'wish_fulfilled') { const category = userStates[chatId]?.category || 'Kanal'; const message = `✨ *Wunsch wurde erfüllt!* ✨\n\n\n🎉 Der gewünschte Inhalt ist jetzt verfügbar.\n\n📺 Bitte schaue im Kanal "${category}" rein.`; // Sende die Nachricht mit der Information, dass der Wunsch erfüllt wurde await bot.telegram.sendMessage(allowedChatId, message, { message_thread_id: allowedThreadId, // In das richtige Thema posten }); // Lösche die Nachricht await ctx.deleteMessage(ctx.callbackQuery.message.message_id); // Beantworte die Callback-Abfrage (damit kein Ladekreis bleibt) await ctx.answerCbQuery(); } else if (data === 'wish_not_found') { // Überprüfe, ob der Benutzer ein Admin ist const admins = await bot.telegram.getChatAdministrators(chatId); const isAdmin = admins.some(admin => admin.user.id === userId); if (isAdmin) { const wishTitle = userStates[chatId]?.wishTitle; const category = userStates[chatId]?.category; // Füge den Wunsch in die "Nicht gefunden"-Liste ein if (wishTitle && category) { saveNotFoundWish(wishTitle, category); } // Bestätige die Speicherung und entferne die Nachricht await bot.telegram.sendMessage(allowedChatId, `📽️ *Sorry*,\n\n"Zum ${category} *"${wishTitle}"* \n\nwurde leider nichts gefunden. Keine Sorge, der Wunsch wurde auf unsere Liste der nicht gefundenen Titel gesetzt. Wir werden ihn prüfen und dich informieren, sobald er verfügbar ist oder gefunden wird. Vielen Dank für deine Geduld!`, { message_thread_id: allowedThreadId, // In das richtige Thema posten }); await ctx.deleteMessage(ctx.callbackQuery.message.message_id); // Beantworte die Callback-Abfrage await ctx.answerCbQuery(); } else { await ctx.answerCbQuery('Nur Admins können diese Funktion nutzen.'); } } else if (data === 'delete_not_found') { // Zeige Inline-Tastatur, um nach der Nummer des zu löschenden Eintrags zu fragen await ctx.reply('Bitte gib die Nummer des Eintrags ein, den du löschen möchtest.', { reply_markup: JSON.stringify({ force_reply: true }) }); userStates[chatId] = { ...userStates[chatId], waitingForDeleteIndex: true }; // Benutzerstatus auf „warten auf Löschnummer“ setzen } else if (data.startsWith('category_')) { // Benutzer hat die Kategorie ausgewählt const categoryMap = { 'category_film': 'Film', 'category_serie': 'Serie', 'category_anime': 'Anime', 'category_disney': 'Disney', 'category_medizin': 'Medizin', 'category_survival': 'Survival', 'category_wwe': 'WWE', 'category_musik': 'Musik', 'category_bollywood': 'Bollywood' }; const category = categoryMap[data]; const categoryMessage = await ctx.reply(`Du hast die Kategorie ${category} ausgewählt. Bitte gib einen Link zum Cover oder zu Spotify ein. Falls du keinen Link angeben möchtest, trage einfach ein X ein (optional).`, { disable_notification: true }); userStates[chatId] = { category, waitingForLink: true, // Warten auf den Link categoryMessageId: categoryMessage.message_id // Speichern der ID der Kategorie-Nachricht }; // Entferne die Auswahl-Buttons und die /wunsch-Nachricht if (userStates[chatId]?.commandMessageId) { await ctx.deleteMessage(userStates[chatId].commandMessageId).catch(e => logError(new Error(`Failed to delete command message: ${e.message}`))); userStates[chatId].commandMessageId = null; // ID der Befehl-Nachricht auf null setzen } await ctx.deleteMessage(ctx.callbackQuery.message.message_id).catch(e => logError(new Error(`Failed to delete category message: ${e.message}`))); // Lösche die Kategorieauswahl-Nachricht } } catch (error) { logError(new Error(`Error handling callback query: ${error.message}`)); } // Markiere die Callback-Abfrage als beantwortet ctx.answerCbQuery().catch(error => { logError(new Error(`Error answering callback query: ${error.message}`)); }); }); // Nachrichten-Handler bot.on('text', async (ctx) => { const chatId = ctx.chat.id.toString(); const userId = ctx.message.from.id; const text = ctx.message.text; const threadId = ctx.message?.message_thread_id; // Thema-ID (falls vorhanden) console.log(`Received message in chat ID ${chatId} and thread ID ${threadId}: ${text}`); // Logging zur Diagnose if (chatId !== allowedChatId || threadId !== allowedThreadId) { console.log(`Ignoring message in chat ID ${chatId} and thread ID ${threadId} as it's not allowed.`); if (text.startsWith('/wunsch')) { // Nachricht zurückweisen, wenn der Befehl in einem nicht erlaubten Kanal verwendet wird await ctx.reply('❌ Dieser Befehl ist in diesem Kanal nicht erlaubt. Bitte benutze den Befehl in den Serien- und Filmwünsche Kanal.', { disable_notification: true }); } return; } // Überprüfe, ob der Benutzer in der Eingabestimmung ist if (userStates[chatId]) { if (userStates[chatId].waitingForLink) { // Verarbeite den Link des Wunsches const link = text.trim(); // Link erhalten userStates[chatId].wishLink = link; // Speichern des Links // Frage nach dem Titel des Wunsches await ctx.reply(`Bitte gib den Titel des ${userStates[chatId].category} ein.`, { disable_notification: true }); userStates[chatId].waitingForLink = false; // Status zurücksetzen userStates[chatId].waitingForWish = true; // Nun auf den Wunsch warten return; // Beende die Verarbeitung, da der Benutzer jetzt nach dem Titel gefragt wird } if (userStates[chatId].waitingForWish) { // Verarbeite den Titel des Wunsches const wish = text.trim(); // Titel erhalten if (wish) { const category = userStates[chatId].category; const link = userStates[chatId].wishLink; const categoryMessageId = userStates[chatId].categoryMessageId; const titleMessageId = ctx.message.message_id; // ID der Titel-Nachricht erhalten const commandMessageId = userStates[chatId].commandMessageId; // ID der Befehl-Nachricht erhalten const wishCommandMessageId = userStates[chatId].wishCommandMessageId; // ID der /wunsch-Nachricht erhalten try { userStates[chatId].wishTitle = wish; // Speichern des Wunsch-Titels für spätere Verwendung await sendWish(wish, category, chatId, userId, link); // Lösche die Nachrichten, die beim Verarbeiten des Wunsches gesendet wurden if (categoryMessageId) await ctx.deleteMessage(categoryMessageId).catch(e => logError(new Error(`Failed to delete category message: ${e.message}`))); if (titleMessageId) await ctx.deleteMessage(titleMessageId).catch(e => logError(new Error(`Failed to delete title message: ${e.message}`))); if (commandMessageId) await ctx.deleteMessage(commandMessageId).catch(e => logError(new Error(`Failed to delete command message: ${e.message}`))); if (wishCommandMessageId) await ctx.deleteMessage(wishCommandMessageId).catch(e => logError(new Error(`Failed to delete /wunsch message: ${e.message}`))); userStates[chatId].waitingForWish = false; // Benutzerstatus zurücksetzen } catch (error) { logError(new Error(`Error processing wish: ${error.message}`)); } } else { await ctx.reply(`Bitte gib den Titel des ${userStates[chatId].category} ein.`, { disable_notification: true }); } return; // Beende die Verarbeitung, wenn der Benutzer in der Eingabestimmung ist } if (userStates[chatId].waitingForDeleteIndex) { // Verarbeite die Löschanfrage const index = parseInt(text.trim(), 10) - 1; // Index aus der Nachricht extrahieren if (deleteNotFoundWish(index)) { await ctx.reply('Der Eintrag wurde erfolgreich gelöscht.'); } else { await ctx.reply('Ungültige Nummer. Bitte versuche es erneut.'); } userStates[chatId].waitingForDeleteIndex = false; // Benutzerstatus zurücksetzen return; } } if (text.startsWith('/wunsch')) { // Benutzer zur Auswahl der Kategorie auffordern const commandMessage = await ctx.reply('Möchtest du etwas wünschen? Wähle bitte eine Kategorie:', { ...getCategoryKeyboard(), disable_notification: true }); userStates[chatId] = { waitingForCategory: true, commandMessageId: commandMessage.message_id, // Speichern der ID der Befehl-Nachricht wishCommandMessageId: ctx.message.message_id // Speichern der ID der /wunsch-Nachricht }; // Setze den Status auf "wartend auf Kategorie" } else if (text.startsWith('/notfound')) { // Überprüfe, ob der Benutzer ein Admin ist const admins = await bot.telegram.getChatAdministrators(chatId); const isAdmin = admins.some(admin => admin.user.id === userId); if (isAdmin) { if (fs.existsSync(notFoundLogFilePath)) { const notFoundWishes = JSON.parse(fs.readFileSync(notFoundLogFilePath, 'utf8')); let replyMessage = '🔍 Liste der nicht gefundenen Wünsche:\n\n'; if (notFoundWishes.length === 0) { replyMessage += 'Keine nicht gefundenen Wünsche.'; } else { notFoundWishes.forEach((entry, index) => { replyMessage += `${index + 1}. Kategorie: ${entry.category}\n Wunsch: ${entry.wish}\n\n\n`; }); replyMessage += '\n🗑️ [Löschen]'; } // Sende die Liste direkt an den Admin await ctx.reply(replyMessage, { disable_notification: true, ...getDeleteNotFoundWishKeyboard() }); // Lösche den /notfound Befehl await ctx.deleteMessage(ctx.message.message_id).catch(e => logError(new Error(`Failed to delete /notfound command message: ${e.message}`))); } else { await ctx.reply('Noch keine nicht gefundenen Wünsche aufgezeichnet.', { disable_notification: true }); } } else { await ctx.reply('❌ Du bist nicht berechtigt, diese Funktion zu nutzen. ❌', { disable_notification: true }); } } }); bot.launch(); console.log('Bot is running...');