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' }], [{ text: 'Hörspiele & Comics', callback_data: 'category_hoerspiele_comics' }], [{ text: 'PC Games', callback_data: 'category_pc_games' }] // Neue 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); // /help Befehl für alle Kanäle bot.command('help', async (ctx) => { const helpMessage = `📋 *Hilfemenü*\n\nHier ist eine kurze Anleitung, wie du einen Wunsch äußern kannst:\n\n` + `1️⃣ Verwende den Befehl /wunsch, um den Wunschprozess zu starten.\n\n` + `2️⃣ Wähle eine Kategorie aus, die deinem Wunsch entspricht. 🗂️\n\n` + `3️⃣ Du wirst aufgefordert, einen Link zum Cover oder Spotify anzugeben (dies ist optional). 📎\n\n` + `4️⃣ Gib den Titel deines Wunsches ein. ✍️\n\n` + `5️⃣ Dein Wunsch wird an die Gruppe weitergeleitet und du erhältst eine Bestätigung. ✅\n\n` + `Für weitere Informationen, besuche bitte unsere Anleitung:`; const inlineKeyboard = { reply_markup: JSON.stringify({ inline_keyboard: [ [{ text: 'Anleitung', url: 'https://git.viper.ipv64.net/M_Viper/telegram-film-wunsch-bot' }] // Hier den Link zur Webseite anpassen ] }) }; await ctx.reply(helpMessage, inlineKeyboard); }); // /info Befehl für alle Kanäle bot.command('info', async (ctx) => { const botInfo = `🤖 *Bot-Informationen*\n\n` + `🔢 **Version:** 1.3.9\n\n` + `👨‍💻 **Ersteller:** M_Viper\n\n` + `📝 **Lizenz:** MIT Lizenz\n\n` + `📅 **Erstellt am:** 2024-07-15\n\n` + // Füge hier das Erstellungsdatum hinzu `🛠️ **Letztes Update:** 2024-09-19\n\n\n` + // Füge hier das letzte Update hinzu `📈 **Funktionen:**\n\n` + `- Wunschliste verwalten\n` + `- Bot-Anleitungen bereitstellen\n` + `- Benutzeranfragen bearbeiten\n\n` + `🔧 **Wartung:** Regelmäßig aktualisiert und gewartet`; const inlineKeyboard = { reply_markup: JSON.stringify({ inline_keyboard: [ [ { text: '🌐 Webseite besuchen', url: 'https://m-viper.de' }, { text: '📧 Kontakt', url: 'https://t.me/M_Viper04' } ] ] }) }; await ctx.reply(botInfo, inlineKeyboard); }); // Callback-Query-Handler // Callback-Query-Handler bot.on('callback_query', async (ctx) => { const data = ctx.callbackQuery.data; const messageId = ctx.callbackQuery.message.message_id; // Speichere die ID der Nachricht, um sie eindeutig zu identifizieren 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🎉 Der gewünschte Inhalt ist jetzt verfügbar.\n\n📺 Bitte schaue im Kanal "${category}" rein.`; // Sende die Nachricht, dass der Wunsch erfüllt wurde await bot.telegram.sendMessage(allowedChatId, message, { message_thread_id: allowedThreadId, // In das richtige Thema posten }); // Lösche nur die Nachricht, die diesem Wunsch zugeordnet ist await ctx.deleteMessage(messageId); // Beantworte die Callback-Abfrage, um den Ladekreis zu entfernen 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 spezifische Nachricht await bot.telegram.sendMessage(allowedChatId, `📽️ *Sorry*,\n\nZum ${category} *"${wishTitle}"* wurde leider nichts gefunden. Keine Sorge, der Wunsch wurde auf unsere Liste der nicht gefundenen Titel gesetzt.`, { message_thread_id: allowedThreadId, }); await ctx.deleteMessage(messageId); // Beantworte die Callback-Abfrage await ctx.answerCbQuery(); } else { await ctx.answerCbQuery('Nur Admins können diese Funktion nutzen.'); } } else if (data === 'delete_not_found') { 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 }; } else if (data.startsWith('category_')) { 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', 'category_hoerspiele_comics': 'Hörspiele & Comics', 'category_pc_games': 'PC Games' }; 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, categoryMessageId: categoryMessage.message_id }; await ctx.deleteMessage(messageId); } } catch (error) { logError(new Error(`Error handling callback query: ${error.message}`)); } // Beantworte die Callback-Abfrage als abgeschlossen 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...');