diff --git a/.env b/.env new file mode 100644 index 0000000..b3f4881 --- /dev/null +++ b/.env @@ -0,0 +1,11 @@ +BOT_TOKEN= +PLEX_TOKEN= +PLEX_DOMAIN= +USER_YML_PATH=user.yml +LOG_DIR=Log +ERROR_LOG_PATH=error.log +CHANGE_DIR=Change +PORT= +MAX_CHANGE_FILE_SIZE=5242880 +USER1_ID='123456789' +USER2_ID='123456789' diff --git a/bot.js b/bot.js new file mode 100644 index 0000000..1a22684 --- /dev/null +++ b/bot.js @@ -0,0 +1,832 @@ +require('dotenv').config(); + +const TelegramBot = require('node-telegram-bot-api'); +const axios = require('axios'); +const fs = require('fs'); +const yaml = require('yamljs'); +const path = require('path'); +const { format } = require('date-fns'); +const express = require('express'); +const bodyParser = require('body-parser'); + +// Setze PROJECT_ROOT auf das aktuelle Verzeichnis +const PROJECT_ROOT = __dirname; + +// Konstanten aus .env-Datei +const BOT_TOKEN = process.env.BOT_TOKEN; +const PLEX_TOKEN = process.env.PLEX_TOKEN; +const PLEX_DOMAIN = process.env.PLEX_DOMAIN; +const PLEX_LIBRARY_URL = `${PLEX_DOMAIN}/library/sections/all?X-Plex-Token=${PLEX_TOKEN}`; +const USER_YML_PATH = path.resolve(PROJECT_ROOT, process.env.USER_YML_PATH); +const LOG_DIR = path.resolve(PROJECT_ROOT, process.env.LOG_DIR); +const ERROR_LOG_PATH = path.resolve(LOG_DIR, process.env.ERROR_LOG_PATH); +const CHANGE_DIR = path.resolve(PROJECT_ROOT, process.env.CHANGE_DIR); +const PORT = process.env.PORT; +const MAX_CHANGE_FILE_SIZE = parseInt(process.env.MAX_CHANGE_FILE_SIZE, 10); +const USER1_ID = process.env.USER1_ID; +const USER2_ID = process.env.USER2_ID; + +// Debug-Ausgaben für Pfade +console.log('USER_YML_PATH:', USER_YML_PATH); +console.log('LOG_DIR:', LOG_DIR); +console.log('ERROR_LOG_PATH:', ERROR_LOG_PATH); +console.log('CHANGE_DIR:', CHANGE_DIR); + +// Sicherstellen, dass Verzeichnisse und Dateien existieren +if (!fs.existsSync(LOG_DIR)) { + fs.mkdirSync(LOG_DIR, { recursive: true }); +} + +if (!fs.existsSync(CHANGE_DIR)) { + fs.mkdirSync(CHANGE_DIR, { recursive: true }); +} + +if (!fs.existsSync(USER_YML_PATH)) { + fs.writeFileSync(USER_YML_PATH, yaml.stringify({}, 4)); +} + +if (!fs.existsSync(ERROR_LOG_PATH)) { + fs.writeFileSync(ERROR_LOG_PATH, ''); // Leere Datei erstellen +} + +// Telegram-Bot-Instanz erstellen +const bot = new TelegramBot(BOT_TOKEN, { polling: true }); + +// Express-Server für Webhooks +const app = express(); +app.use(bodyParser.json()); + +// Funktion zum Protokollieren von allgemeinen Nachrichten +function logMessage(message) { + const today = format(new Date(), 'yyyy-MM-dd'); + const logFilePath = path.join(LOG_DIR, `${today}.log`); + fs.appendFileSync(logFilePath, `${format(new Date(), 'HH:mm:ss')} - ${message}\n`); +} + +// Funktion zur Fehlerprotokollierung +function logError(error) { + const errorMessage = `${format(new Date(), 'HH:mm:ss')} - Error: ${error}\n`; + fs.appendFileSync(ERROR_LOG_PATH, errorMessage); +} + +// Funktion zum Protokollieren von Debug-Informationen in die change-Datei +function logDebug(message) { + const now = new Date(); + const changeFilePath = path.join(CHANGE_DIR, `change_${format(now, 'yyyy-MM-dd_HH')}.log`); + + if (fs.existsSync(changeFilePath) && fs.statSync(changeFilePath).size > MAX_CHANGE_FILE_SIZE) { + fs.renameSync(changeFilePath, path.join(CHANGE_DIR, `change_${format(now, 'yyyy-MM-dd_HH')}_old.log`)); + } + + fs.appendFileSync(changeFilePath, `${format(now, 'HH:mm:ss')} - Debug: ${message}\n`); +} + +// Funktion zum Abrufen von Plex-Daten +async function fetchPlexData(url) { + try { + const response = await axios.get(url, { + headers: { + 'X-Plex-Token': PLEX_TOKEN + } + }); + return response.data; + } catch (error) { + logError(`Error fetching Plex data: ${error.message}`); + throw error; + } +} + +// Funktion zum Abrufen aller Filme +async function fetchAllMovies() { + try { + // Hole alle Sektionen + const sectionsData = await fetchPlexData(PLEX_LIBRARY_URL); + const sections = sectionsData.MediaContainer.Directory; + + let movies = []; + + // Hole Filme aus jeder Sektion + for (const section of sections) { + const sectionUrl = `${PLEX_DOMAIN}/library/sections/${section.key}/all?X-Plex-Token=${PLEX_TOKEN}`; + const sectionData = await fetchPlexData(sectionUrl); + + if (sectionData.MediaContainer && sectionData.MediaContainer.Metadata) { + const metadata = sectionData.MediaContainer.Metadata; + movies = movies.concat(metadata.filter(media => media.type === 'movie')); + } + } + + // Sortiere Filme nach 'addedAt' Zeitstempel in absteigender Reihenfolge + movies.sort((a, b) => (b.addedAt || 0) - (a.addedAt || 0)); + + return movies; + } catch (error) { + logError(`Error fetching all movies: ${error.message}`); + throw error; + } +} + +// Funktion zum Suchen von Filmen nach Genre, Titel und Schauspielern +async function searchMovies(query) { + try { + // Alle Filme abrufen + const movies = await fetchAllMovies(); + + // Suchbegriff in Kleinbuchstaben umwandeln + const searchQuery = query.toLowerCase(); + + // Filtere Filme basierend auf dem Suchbegriff + const results = movies.filter(movie => { + // Sicherstellen, dass wir auf vorhandene Attribute zugreifen + const title = (movie.title || '').toLowerCase(); + const summary = (movie.summary || '').toLowerCase(); + const genres = (movie.genres || []).map(g => g.toLowerCase()); // Genre ist eine Liste + const actors = (movie.actors || []).map(a => a.toLowerCase()); // Schauspieler ist eine Liste + + // Überprüfen, ob der Suchbegriff in einem der Attribute vorkommt + return title.includes(searchQuery) || + summary.includes(searchQuery) || + genres.some(genre => genre.includes(searchQuery)) || + actors.some(actor => actor.includes(searchQuery)); + }).map(movie => ({ + title: movie.title || 'Unbekannt', + summary: movie.summary || 'Keine Zusammenfassung verfügbar', + thumb: movie.thumb ? `${PLEX_DOMAIN}${movie.thumb}?X-Plex-Token=${PLEX_TOKEN}` : '', + genres: (movie.genres || []).join(', '), // Genre-Liste in einen String umwandeln + actors: (movie.actors || []).join(', ') // Schauspieler-Liste in einen String umwandeln + })); + + return results; + } catch (error) { + logError(`Error searching movies: ${error.message}`); + throw error; + } + } + + +// Funktion zum Abrufen eines zufälligen Films +async function fetchRandomMovie() { + try { + const movies = await fetchAllMovies(); + if (movies.length === 0) return null; + + const randomIndex = Math.floor(Math.random() * movies.length); + return movies[randomIndex]; + } catch (error) { + logError(`Error fetching random movie: ${error.message}`); + throw error; + } +} + +// /start-Befehl verarbeiten +bot.onText(/\/start/, (msg) => { + const chatId = msg.chat.id; + const userId = msg.from.id; + + // Benutzerdaten in user.yml speichern + let users = yaml.load(USER_YML_PATH); + users[chatId] = userId; + fs.writeFileSync(USER_YML_PATH, yaml.stringify(users, 4)); + + const welcomeMessage = ` +Willkommen! Dein Zugang zum Bot wurde erfolgreich eingerichtet. + +Um die verfügbaren Befehle anzuzeigen, tippe /help. + `; + bot.sendMessage(chatId, welcomeMessage); + + // /start-Befehl protokollieren + logMessage(`Received /start command from chatId ${chatId} (userId ${userId})`); +}); + +// /latestmovie-Befehl verarbeiten +bot.onText(/\/latestmovie/, async (msg) => { + const chatId = msg.chat.id; + + try { + const movies = await fetchAllMovies(); + const sortedMovies = movies + .filter(movie => movie.addedAt) + .sort((a, b) => b.addedAt - a.addedAt); + + const latestMovie = sortedMovies[0]; + if (latestMovie) { + const movieTitle = latestMovie.title || 'Unbekannt'; + const movieSummary = latestMovie.summary || 'Keine Zusammenfassung verfügbar'; + const addedAtDate = new Date((latestMovie.addedAt || 0) * 1000).toLocaleString(); // Konvertierung von Unix-Zeitstempel in lesbares Datum + const movieThumb = latestMovie.thumb ? `${PLEX_DOMAIN}${latestMovie.thumb}?X-Plex-Token=${PLEX_TOKEN}` : ''; + + const message = `Der zuletzt hinzugefügte Film ist:\n\nTitel: ${movieTitle}\n\nZusammenfassung: \n${movieSummary}\n\nHinzugefügt am: ${addedAtDate}`; + + // Bild anzeigen, wenn vorhanden + if (movieThumb) { + bot.sendPhoto(chatId, movieThumb, { caption: message }).catch(error => { + logError(`Error sending photo to chatId ${chatId}: ${error.message}`); + }); + } else { + bot.sendMessage(chatId, message).catch(error => { + logError(`Error sending message to chatId ${chatId}: ${error.message}`); + }); + } + + logMessage(`Sent latest movie info to chatId ${chatId}`); + } else { + bot.sendMessage(chatId, 'Keine Filme gefunden.').catch(error => { + logError(`Error sending no movies message to chatId ${chatId}: ${error.message}`); + }); + logMessage(`No movies found for chatId ${chatId}`); + } + } catch (error) { + if (error.response) { + bot.sendMessage(chatId, `Fehler beim Abrufen der neuesten Filme. Statuscode: ${error.response.status}`).catch(err => { + logError(`Error sending error message to chatId ${chatId}: ${err.message}`); + }); + logError(`Error fetching latest movie: ${error.response.status} - ${error.response.statusText}`); + } else if (error.request) { + bot.sendMessage(chatId, 'Fehler beim Abrufen der neuesten Filme. Keine Antwort vom Server.').catch(err => { + logError(`Error sending no response message to chatId ${chatId}: ${err.message}`); + }); + logError(`Error fetching latest movie: No response from server`); + } else { + bot.sendMessage(chatId, 'Fehler beim Abrufen der neuesten Filme. Unbekannter Fehler.').catch(err => { + logError(`Error sending unknown error message to chatId ${chatId}: ${err.message}`); + }); + logError(`Error fetching latest movie: ${error.message}`); + } + } +}); + +// /info-Befehl verarbeiten +bot.onText(/\/info/, async (msg) => { + const chatId = msg.chat.id; + const plexDomain = process.env.PLEX_DOMAIN; // Lese die Plex-Domain aus der Umgebungsvariablen + + try { + const { movieCount, showCount } = await fetchAllMedia(); + const message = `In der Bibliothek befinden sich derzeit:\n\nFilme: ${movieCount}\nSerien: ${showCount}\n\n© 2024 M_Viper`; + + const options = { + reply_markup: JSON.stringify({ + inline_keyboard: [ + [{ text: 'Zu Plex gehen', url: plexDomain }] + ] + }) + }; + + await bot.sendMessage(chatId, message, options).catch(error => { + logError(`Error sending message to chatId ${chatId}: ${error.message}`); + }); + + logMessage(`Sent media count, copyright, and Plex button to chatId ${chatId}`); + } catch (error) { + logError(`Error fetching media count: ${error.message}`); + await bot.sendMessage(chatId, 'Fehler beim Abrufen der Medienanzahl.').catch(err => { + logError(`Error sending media count error message to chatId ${chatId}: ${err.message}`); + }); + } +}); + +// Funktion zum Abrufen der Anzahl aller Filme und Serien +async function fetchAllMedia() { + try { + const movies = await fetchAllMovies(); + const shows = await fetchAllShows(); // Diese Funktion musst du ebenfalls hinzufügen + + return { + movieCount: movies.length, + showCount: shows.length + }; + } catch (error) { + logError(`Error fetching all media: ${error.message}`); + throw error; + } +} + +// Funktion zum Abrufen aller Serien +async function fetchAllShows() { + try { + // Hole alle Sektionen + const sectionsData = await fetchPlexData(PLEX_LIBRARY_URL); + const sections = sectionsData.MediaContainer.Directory; + + let shows = []; + + // Hole Serien aus jeder Sektion + for (const section of sections) { + const sectionUrl = `${process.env.PLEX_DOMAIN}/library/sections/${section.key}/all?X-Plex-Token=${process.env.PLEX_TOKEN}`; + const sectionData = await fetchPlexData(sectionUrl); + + if (sectionData.MediaContainer && sectionData.MediaContainer.Metadata) { + const metadata = sectionData.MediaContainer.Metadata; + shows = shows.concat(metadata.filter(media => media.type === 'show')); + } + } + + return shows; + } catch (error) { + logError(`Error fetching all shows: ${error.message}`); + throw error; + } +} + + // Funktion zum Erstellen des Inline-Keyboard für die Auswahl von Film oder Serie +function getTypeKeyboard() { + return { + reply_markup: JSON.stringify({ + inline_keyboard: [ + [{ text: 'Film', callback_data: 'type_film' }], + [{ text: 'Serie', callback_data: 'type_serie' }] + ] + }) + }; +} + +// Funktion zum Senden des Wunsches an zwei Benutzer +async function sendWish(wish, type, chatId) { + const message = `❗️ Achtung ❗️\n\nEin neuer ${type} Wunsch ist eingegangen:\n\nType: ${type}\n\nTitel:\n${wish}`; + + try { + await Promise.all([ + bot.sendMessage(USER1_ID, message), + bot.sendMessage(USER2_ID, message), + ]); + logMessage(`Sent ${type} wish to users ${USER1_ID} and ${USER2_ID}`); + } catch (error) { + logError(`Error sending ${type} wish: ${error.message}`); + console.error(`Error details: ${error}`); + } +} + +// Verarbeite Callback Queries (für die Inline-Buttons) +bot.on('callback_query', async (query) => { + const chatId = query.message.chat.id; + const data = query.data; + + if (data.startsWith('type_')) { + // Benutzer hat den Typ ausgewählt (Film oder Serie) + const type = data === 'type_film' ? 'Film' : 'Serie'; + bot.sendMessage(chatId, `Du hast ${type} ausgewählt. Bitte gib den Titel des ${type} ein.`).catch(error => { + logError(`Error sending type confirmation message to chatId ${chatId}: ${error.message}`); + }); + userStates[chatId] = { type, waitingForWish: true }; // Setze den Status auf "wartend auf Wunsch" + } + + // Markiere die Callback-Abfrage als beantwortet + bot.answerCallbackQuery(query.id).catch(error => { + logError(`Error answering callback query: ${error.message}`); + }); +}); + +// Verarbeite eingehende Nachrichten +bot.on('message', async (msg) => { + const chatId = msg.chat.id; + const text = msg.text; + + if (userStates[chatId] && userStates[chatId].waitingForWish) { + // Verarbeite den Titel des Wunsches + const wish = text.trim(); // Titel erhalten + if (wish) { + const type = userStates[chatId].type; + await sendWish(wish, type, chatId); + bot.sendMessage(chatId, `Dein ${type}-Wunsch wurde übermittelt.`).catch(error => { + logError(`Error sending wish confirmation to chatId ${chatId}: ${error.message}`); + }); + logMessage(`Received and forwarded ${type} wish from chatId ${chatId}: ${wish}`); + userStates[chatId].waitingForWish = false; // Benutzerstatus zurücksetzen + } else { + bot.sendMessage(chatId, `Bitte gib den Titel des ${userStates[chatId].type} ein.`).catch(error => { + logError(`Error sending empty wish message to chatId ${chatId}: ${error.message}`); + }); + } + return; // Beende die Verarbeitung, wenn der Benutzer in der Eingabestimmung ist + } + + if (text.startsWith('/wunsch')) { + // Benutzer zur Auswahl des Typs (Film oder Serie) auffordern + bot.sendMessage(chatId, 'Möchtest du einen Film oder eine Serie wünschen? Wähle bitte eine Option:', getTypeKeyboard()).catch(error => { + logError(`Error sending type request message to chatId ${chatId}: ${error.message}`); + }); + userStates[chatId] = { waitingForType: true }; // Setze den Status auf "wartend auf Typ" + } + + if (userStates[chatId] && userStates[chatId].waitingForQuery) { + // Verarbeite Suchabfragen, falls der Benutzer darauf wartet + const query = text; // Suchbegriff erhalten + + try { + const results = await searchMovies(query); + if (results.length === 0) { + bot.sendMessage(chatId, 'Keine Filme gefunden, die deiner Suche entsprechen.').catch(error => { + logError(`Error sending no results message to chatId ${chatId}: ${error.message}`); + }); + logMessage(`No search results found for chatId ${chatId} with query "${query}"`); + } else { + // Erstelle Nachrichten für jedes Ergebnis + for (const movie of results) { + const { title, summary, thumb } = movie; + const message = `Titel: ${title}\n\nZusammenfassung: \n\n${summary}`; + + if (thumb) { + await bot.sendPhoto(chatId, thumb, { caption: message }).catch(error => { + logError(`Error sending photo to chatId ${chatId}: ${error.message}`); + }); + } else { + await bot.sendMessage(chatId, message).catch(error => { + logError(`Error sending message to chatId ${chatId}: ${error.message}`); + }); + } + } + + logMessage(`Sent search results for query "${query}" to chatId ${chatId}`); + } + } catch (error) { + if (error.response) { + bot.sendMessage(chatId, `Fehler beim Durchführen der Suche. Statuscode: ${error.response.status}`).catch(err => { + logError(`Error sending search error message to chatId ${chatId}: ${err.message}`); + }); + logError(`Error searching movies: ${error.response.status} - ${error.response.statusText}`); + } else if (error.request) { + bot.sendMessage(chatId, 'Fehler beim Durchführen der Suche. Keine Antwort vom Server.').catch(err => { + logError(`Error sending no response message to chatId ${chatId}: ${err.message}`); + }); + logError(`Error searching movies: No response from server`); + } else { + bot.sendMessage(chatId, 'Fehler beim Durchführen der Suche. Unbekannter Fehler.').catch(err => { + logError(`Error sending unknown error message to chatId ${chatId}: ${error.message}`); + }); + logError(`Error searching movies: ${error.message}`); + } + } + + // Benutzerstatus zurücksetzen + userStates[chatId].waitingForQuery = false; + } +}); + +// /zufall-Befehl verarbeiten +bot.onText(/\/zufall/, async (msg) => { + const chatId = msg.chat.id; + + try { + const randomMovie = await fetchRandomMovie(); + if (randomMovie) { + const movieTitle = randomMovie.title || 'Unbekannt'; + const movieSummary = randomMovie.summary || 'Keine Zusammenfassung verfügbar'; + const movieThumb = randomMovie.thumb ? `${PLEX_DOMAIN}${randomMovie.thumb}?X-Plex-Token=${PLEX_TOKEN}` : ''; + + const message = `Hier ist ein zufälliger Film:\n\nTitel: ${movieTitle}\n\nZusammenfassung: \n${movieSummary}`; + + // Bild anzeigen, wenn vorhanden + if (movieThumb) { + bot.sendPhoto(chatId, movieThumb, { caption: message }).catch(error => { + logError(`Error sending photo to chatId ${chatId}: ${error.message}`); + }); + } else { + bot.sendMessage(chatId, message).catch(error => { + logError(`Error sending message to chatId ${chatId}: ${error.message}`); + }); + } + + logMessage(`Sent random movie info to chatId ${chatId}`); + } else { + bot.sendMessage(chatId, 'Keine Filme gefunden.').catch(error => { + logError(`Error sending no movies message to chatId ${chatId}: ${error.message}`); + }); + logMessage(`No movies found for chatId ${chatId}`); + } + } catch (error) { + if (error.response) { + bot.sendMessage(chatId, `Fehler beim Abrufen eines zufälligen Films. Statuscode: ${error.response.status}`).catch(err => { + logError(`Error sending error message to chatId ${chatId}: ${err.message}`); + }); + logError(`Error fetching random movie: ${error.response.status} - ${error.response.statusText}`); + } else if (error.request) { + bot.sendMessage(chatId, 'Fehler beim Abrufen eines zufälligen Films. Keine Antwort vom Server.').catch(err => { + logError(`Error sending no response message to chatId ${chatId}: ${err.message}`); + }); + logError(`Error fetching random movie: No response from server`); + } else { + bot.sendMessage(chatId, 'Fehler beim Abrufen eines zufälligen Films. Unbekannter Fehler.').catch(err => { + logError(`Error sending unknown error message to chatId ${chatId}: ${err.message}`); + }); + logError(`Error fetching random movie: ${error.message}`); + } + } +}); + +// Speichern des Status der Benutzerinteraktionen +const userStates = {}; // Einfache In-Memory-Datenstruktur + +// /search-Befehl verarbeiten +bot.onText(/\/search/, (msg) => { + const chatId = msg.chat.id; + + // Setze den Status auf "wartet auf Suchbegriff" + userStates[chatId] = { waitingForQuery: true }; + + const message = 'Bitte gib den Suchbegriff für die Film-Suche ein.'; + + bot.sendMessage(chatId, message).catch(error => { + logError(`Error sending search prompt to chatId ${chatId}: ${error.message}`); + }); + + logMessage(`Prompted for search query from chatId ${chatId}`); +}); + +// Eingehende Nachrichten verarbeiten +bot.on('message', async (msg) => { + const chatId = msg.chat.id; + const text = msg.text; + + // Überprüfen, ob der Benutzer auf eine Suchabfrage wartet + if (userStates[chatId] && userStates[chatId].waitingForQuery) { + const query = text; // Suchbegriff erhalten + + try { + const results = await searchMovies(query); + if (results.length === 0) { + bot.sendMessage(chatId, 'Keine Filme gefunden, die deiner Suche entsprechen.').catch(error => { + logError(`Error sending no results message to chatId ${chatId}: ${error.message}`); + }); + logMessage(`No search results found for chatId ${chatId} with query "${query}"`); + } else { + // Erstelle Nachrichten für jedes Ergebnis + for (const movie of results) { + const { title, summary, thumb } = movie; + const message = `Titel: ${title}\n\nZusammenfassung: \n\n${summary}`; + + if (thumb) { + await bot.sendPhoto(chatId, thumb, { caption: message }).catch(error => { + logError(`Error sending photo to chatId ${chatId}: ${error.message}`); + }); + } else { + await bot.sendMessage(chatId, message).catch(error => { + logError(`Error sending message to chatId ${chatId}: ${error.message}`); + }); + } + } + + logMessage(`Sent search results for query "${query}" to chatId ${chatId}`); + } + } catch (error) { + if (error.response) { + bot.sendMessage(chatId, `Fehler beim Durchführen der Suche. Statuscode: ${error.response.status}`).catch(err => { + logError(`Error sending search error message to chatId ${chatId}: ${err.message}`); + }); + logError(`Error searching movies: ${error.response.status} - ${error.response.statusText}`); + } else if (error.request) { + bot.sendMessage(chatId, 'Fehler beim Durchführen der Suche. Keine Antwort vom Server.').catch(err => { + logError(`Error sending no response message to chatId ${chatId}: ${err.message}`); + }); + logError(`Error searching movies: No response from server`); + } else { + bot.sendMessage(chatId, 'Fehler beim Durchführen der Suche. Unbekannter Fehler.').catch(err => { + logError(`Error sending unknown error message to chatId ${chatId}: ${err.message}`); + }); + logError(`Error searching movies: ${error.message}`); + } + } + + // Benutzerstatus zurücksetzen + userStates[chatId].waitingForQuery = false; + } +}); + +// Funktion zum Abrufen der gut bewerteten Filme +async function fetchTopRatedMovies() { + try { + const movies = await fetchAllMovies(); + if (!movies.length) return []; + + // Filtere Filme mit Bewertung (hier als Beispiel angenommen, dass die Bewertung in `rating` vorhanden ist) + const ratedMovies = movies.filter(movie => movie.rating && movie.rating > 0); + + // Sortiere Filme nach Bewertung (absteigend) + ratedMovies.sort((a, b) => (b.rating || 0) - (a.rating || 0)); + + return ratedMovies; + } catch (error) { + logError(`Error fetching top-rated movies: ${error.message}`); + throw error; + } +} + +// Funktion zum Abrufen des Films des Tages +async function fetchDailyRecommendation() { + try { + const ratedMovies = await fetchTopRatedMovies(); + if (ratedMovies.length === 0) return null; + + // Heute ist der Index der zu zeigende Film + const todayIndex = new Date().getDate() % ratedMovies.length; + return ratedMovies[todayIndex]; + } catch (error) { + logError(`Error fetching daily recommendation: ${error.message}`); + throw error; + } +} + +// /empfehlung-Befehl verarbeiten +bot.onText(/\/empfehlung/, async (msg) => { + const chatId = msg.chat.id; + + try { + const dailyMovie = await fetchDailyRecommendation(); + if (dailyMovie) { + const movieTitle = dailyMovie.title || 'Unbekannt'; + const movieSummary = dailyMovie.summary || 'Keine Zusammenfassung verfügbar'; + const movieThumb = dailyMovie.thumb ? `${PLEX_DOMAIN}${dailyMovie.thumb}?X-Plex-Token=${PLEX_TOKEN}` : ''; + + const message = `Hier ist der empfohlene Film des Tages:\n\nTitel: ${movieTitle}\n\nZusammenfassung: \n${movieSummary}`; + + // Bild anzeigen, wenn vorhanden + if (movieThumb) { + bot.sendPhoto(chatId, movieThumb, { caption: message }).catch(error => { + logError(`Error sending photo to chatId ${chatId}: ${error.message}`); + }); + } else { + bot.sendMessage(chatId, message).catch(error => { + logError(`Error sending message to chatId ${chatId}: ${error.message}`); + }); + } + + logMessage(`Sent daily recommendation to chatId ${chatId}`); + } else { + bot.sendMessage(chatId, 'Keine Empfehlungen verfügbar.').catch(error => { + logError(`Error sending no recommendation message to chatId ${chatId}: ${error.message}`); + }); + logMessage(`No daily recommendation found for chatId ${chatId}`); + } + } catch (error) { + if (error.response) { + bot.sendMessage(chatId, `Fehler beim Abrufen der Empfehlung. Statuscode: ${error.response.status}`).catch(err => { + logError(`Error sending error message to chatId ${chatId}: ${err.message}`); + }); + logError(`Error fetching daily recommendation: ${error.response.status} - ${error.response.statusText}`); + } else if (error.request) { + bot.sendMessage(chatId, 'Fehler beim Abrufen der Empfehlung. Keine Antwort vom Server.').catch(err => { + logError(`Error sending no response message to chatId ${chatId}: ${err.message}`); + }); + logError(`Error fetching daily recommendation: No response from server`); + } else { + bot.sendMessage(chatId, 'Fehler beim Abrufen der Empfehlung. Unbekannter Fehler.').catch(err => { + logError(`Error sending unknown error message to chatId ${chatId}: ${error.message}`); + }); + logError(`Error fetching daily recommendation: ${error.message}`); + } + } +}); + +// /help-Befehl verarbeiten +bot.onText(/\/help/, (msg) => { + const chatId = msg.chat.id; + + const helpMessage = `📜 **Hier ist eine Liste der verfügbaren Befehle:**\n\n` + + `👋 **/start** - Registriert deinen Zugang.\n\n` + + `🎬 **/latestmovie** - Zeigt den zuletzt hinzugefügten Film an.\n\n` + + `📅 **/latest10movies** - Zeigt die letzten 10 hinzugefügten Filme an.\n\n` + + `ℹ️ **/info** - Gibt die Anzahl der Filme und Serien aus.\n\n` + + `🎲 **/zufall** - Zeigt einen zufälligen Film an.\n\n` + + `🔍 **/search** - Startet die Filmsuche. \n\n` + + `💭 **/wunsch** - Nutze diesen Befehl, um einen Filmwunsch zu äußern. \n\n` + + `🔝 **/empfehlung** - Film Empfehlung des Tages.\n\n` + + `❓ **/help** - Zeigt diese Hilfennachricht an.`; + + + bot.sendMessage(chatId, helpMessage, { parse_mode: 'Markdown' }).catch(error => { + logError(`Error sending help message to chatId ${chatId}: ${error.message}`); + }); + + logMessage(`Sent help message to chatId ${chatId}`); +}); + +// Funktion zum Abrufen der letzten 10 hinzugefügten Filme +async function fetchLatest10Movies() { + try { + const movies = await fetchAllMovies(); + const sortedMovies = movies + .filter(movie => movie.addedAt) + .sort((a, b) => b.addedAt - a.addedAt) + .slice(0, 10); // Nimm nur die neuesten 10 Filme + + return sortedMovies; + } catch (error) { + logError(`Error fetching latest 10 movies: ${error.message}`); + throw error; + } +} + +// /latest10movies-Befehl verarbeiten +bot.onText(/\/latest10movies/, async (msg) => { + const chatId = msg.chat.id; + + try { + const latestMovies = await fetchLatest10Movies(); + + if (latestMovies.length > 0) { + let message = 'Letzten 10 hinzugefügten Filme:\n\n'; + latestMovies.forEach(movie => { + message += `${movie.title || 'Unbekannt'}\n`; + }); + + bot.sendMessage(chatId, message).catch(error => { + logError(`Error sending message to chatId ${chatId}: ${error.message}`); + }); + + logMessage(`Sent latest 10 movies info to chatId ${chatId}`); + } else { + bot.sendMessage(chatId, 'Keine Filme gefunden.').catch(error => { + logError(`Error sending no movies message to chatId ${chatId}: ${error.message}`); + }); + logMessage(`No movies found for chatId ${chatId}`); + } + } catch (error) { + if (error.response) { + bot.sendMessage(chatId, `Fehler beim Abrufen der letzten Filme. Statuscode: ${error.response.status}`).catch(err => { + logError(`Error sending error message to chatId ${chatId}: ${err.message}`); + }); + logError(`Error fetching latest 10 movies: ${error.response.status} - ${error.response.statusText}`); + } else if (error.request) { + bot.sendMessage(chatId, 'Fehler beim Abrufen der letzten Filme. Keine Antwort vom Server.').catch(err => { + logError(`Error sending no response message to chatId ${chatId}: ${err.message}`); + }); + logError(`Error fetching latest 10 movies: No response from server`); + } else { + bot.sendMessage(chatId, 'Fehler beim Abrufen der letzten Filme. Unbekannter Fehler.').catch(err => { + logError(`Error sending unknown error message to chatId ${chatId}: ${err.message}`); + }); + logError(`Error fetching latest 10 movies: ${error.message}`); + } + } +}); + +// Funktion zum Verarbeiten von Webhook-Anfragen +app.post('/webhook', async (req, res) => { + try { + const event = req.body; + + // Ereignisprotokoll für Debugging + logDebug(`Received webhook event: ${JSON.stringify(event, null, 2)}`); + + if (event.type === 'library.new' && event.Metadata) { + const addedMovie = event.Metadata; + const movieTitle = addedMovie.title || 'Unbekannt'; + const message = `Ein neuer Film wurde hinzugefügt:\n\nTitel: ${movieTitle}`; + + // Nachricht an alle Benutzer senden + const users = yaml.load(USER_YML_PATH); + const sendMessages = Object.keys(users).map(chatId => + bot.sendMessage(chatId, message).catch(error => { + logError(`Error sending message to chatId ${chatId}: ${error.message}`); + }) + ); + + // Warte, bis alle Nachrichten gesendet wurden + await Promise.all(sendMessages); + logMessage(`Sent new movie message to all users`); + } else { + logDebug(`Unhandled event type or missing metadata: ${event.type}`); + } + + res.sendStatus(200); // Empfang der Anfrage bestätigen + } catch (error) { + logError(`Error processing webhook: ${error.message}`); + res.sendStatus(500); // Serverfehler + } +}); + +// Express-Server starten +app.listen(PORT, () => { + console.log(`Webhook server running on port ${PORT}`); +}); + +// Log-Rotation +function rotateLogs() { + const today = format(new Date(), 'yyyy-MM-dd'); + const logFilePath = path.join(LOG_DIR, `${today}.log`); + + // Lösche die Log-Datei von gestern, wenn sie existiert + const yesterday = format(new Date(Date.now() - 24 * 60 * 60 * 1000), 'yyyy-MM-dd'); + const oldLogFilePath = path.join(LOG_DIR, `${yesterday}.log`); + + if (fs.existsSync(oldLogFilePath)) { + fs.unlinkSync(oldLogFilePath); // Lösche die alte Logdatei + logMessage(`Deleted old log file: ${yesterday}`); + } +} + +// Logs täglich um Mitternacht rotieren +function scheduleDailyRotation() { + const now = new Date(); + const millisTillMidnight = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1, 0, 0, 0, 0) - now; + + setTimeout(function() { + rotateLogs(); // Rotieren der Logs um Mitternacht + setInterval(rotateLogs, 24 * 60 * 60 * 1000); // Danach täglich wiederholen + }, millisTillMidnight); +} + +// Starte die tägliche Rotation +scheduleDailyRotation(); + +console.log('Bot is running...');