require('dotenv').config(); const { Telegraf } = require('telegraf'); const fs = require('fs'); const path = require('path'); const archiver = require('archiver'); const schedule = require('node-schedule'); const express = require('express'); const bodyParser = require('body-parser'); const axios = require('axios'); const app = express(); app.use(bodyParser.json()); 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 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.startsWith('delete_not_found_')) { const index = parseInt(data.split('_')[3], 10); if (deleteNotFoundWish(index)) { await ctx.reply('Der Eintrag wurde erfolgreich gelöscht.'); } else { await ctx.reply('Ungültige Nummer. Bitte versuche es erneut.'); } // Beantworte die Callback-Abfrage await ctx.answerCbQuery(); } 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.trim(); 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')) { 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; } // Prüfe, ob der Benutzer den Vorgang abbrechen möchte if (text === '/cancel') { if (userStates[chatId]) { const categoryMessageId = userStates[chatId].categoryMessageId; const wishCommandMessageId = userStates[chatId].wishCommandMessageId; const commandMessageId = userStates[chatId].commandMessageId; // Lösche alle relevanten Nachrichten if (categoryMessageId) await ctx.deleteMessage(categoryMessageId); if (wishCommandMessageId) await ctx.deleteMessage(wishCommandMessageId); if (commandMessageId) await ctx.deleteMessage(commandMessageId); // Setze den Benutzerstatus zurück delete userStates[chatId]; await ctx.reply('🔴 Der Wunschvorgang wurde abgebrochen.', { disable_notification: true }); } else { await ctx.reply('🔴 Es gibt keinen laufenden Vorgang zum Abbrechen.', { disable_notification: true }); } return; // Beende die Verarbeitung } // Der Rest des Codes bleibt gleich if (userStates[chatId]) { if (userStates[chatId].waitingForLink) { const link = text; userStates[chatId].wishLink = link; await ctx.reply(`Bitte gib den Titel des ${userStates[chatId].category} ein.`, { disable_notification: true }); userStates[chatId].waitingForLink = false; userStates[chatId].waitingForWish = true; return; } if (userStates[chatId].waitingForWish) { const wish = text; if (wish) { const category = userStates[chatId].category; const link = userStates[chatId].wishLink; const categoryMessageId = userStates[chatId].categoryMessageId; const titleMessageId = ctx.message.message_id; const commandMessageId = userStates[chatId].commandMessageId; const wishCommandMessageId = userStates[chatId].wishCommandMessageId; try { userStates[chatId].wishTitle = wish; await sendWish(wish, category, chatId, userId, link); if (categoryMessageId) await ctx.deleteMessage(categoryMessageId); if (titleMessageId) await ctx.deleteMessage(titleMessageId); if (commandMessageId) await ctx.deleteMessage(commandMessageId); if (wishCommandMessageId) await ctx.deleteMessage(wishCommandMessageId); userStates[chatId].waitingForWish = false; } 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; } if (userStates[chatId].waitingForDeleteIndex) { const index = parseInt(text, 10) - 1; 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; return; } } if (text.startsWith('/wunsch')) { const commandMessage = await ctx.reply('Möchtest du etwas wünschen? Wähle bitte eine Kategorie. Du kannst den Vorgang jederzeit mit dem Befehl /cancel abbrechen.', { ...getCategoryKeyboard(), disable_notification: true }); userStates[chatId] = { waitingForCategory: true, commandMessageId: commandMessage.message_id, wishCommandMessageId: ctx.message.message_id }; } else if (text.startsWith('/notfound')) { 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} - Wunsch: ${entry.wish}\n`; }); } // Füge die Löschen-Buttons für jeden Eintrag hinzu const inlineKeyboard = { reply_markup: JSON.stringify({ inline_keyboard: notFoundWishes.map((_, index) => [ { text: `🗑️ Löschen ${index + 1}`, callback_data: `delete_not_found_${index}` } ]) }) }; await ctx.reply(replyMessage, inlineKeyboard); } else { await ctx.reply('Es gibt keine nicht gefundenen Wünsche.'); } } } }); //start Frontend // Middleware, um statische Dateien aus dem "public"-Ordner zu servieren app.use(express.static(path.join(__dirname, 'public'))); // API-Route zum Senden von Wünschen app.post('/api/sendWish', async (req, res) => { const { category, link, title } = req.body; // Überprüfen, ob die erforderlichen Daten vorhanden sind if (!category || !title) { return res.status(400).send('Kategorie und Titel sind erforderlich.'); } // Erstelle die Nachricht, die an die Telegram-Gruppe gesendet wird const message = `🎬 *Ein neuer Wunsch ist eingegangen!*\n\n🔹 Kategorie: ${category}\n\n🔸 Titel: ${title}\n\n🔗 Link: ${link || 'Kein Link'}`; // Inline-Tastatur mit den Buttons const replyMarkup = { reply_markup: JSON.stringify({ inline_keyboard: [ [ { text: '✅ Erledigt', callback_data: 'wish_fulfilled' }, { text: '❌ Nicht gefunden', callback_data: 'wish_not_found' } ] ] }) }; try { // Sende die Nachricht an die Telegram-Gruppe im richtigen Thema mit den Buttons await bot.telegram.sendMessage(allowedChatId, message, { message_thread_id: allowedThreadId, // Sende in das erlaubte Thema reply_markup: replyMarkup.reply_markup // Füge die Buttons hinzu }); res.status(200).send('Wunsch gesendet'); } catch (error) { console.error('Error sending wish:', error); res.status(500).send('Fehler beim Senden des Wunsches'); } }); //End Frontend // Server starten const PORT = process.env.PORT || 3005; app.listen(PORT, () => { console.log(`Server läuft auf http://localhost:${PORT}`); }); bot.launch(); console.log('Bot is running...');