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 dayjs = require('dayjs'); const dayOfYear = require('dayjs/plugin/dayOfYear'); // Importiere das Plugin const express = require('express'); const bodyParser = require('body-parser'); const NodeCache = require('node-cache'); const schedule = require('node-schedule'); const moment = require('moment'); const CacheDir = path.join(__dirname, 'Cache'); const cacheFilePath = path.join(CacheDir, 'cache.json'); // 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 PORT = process.env.PORT; const USER1_ID = process.env.USER1_ID; const USER2_ID = process.env.USER2_ID; const WEBHOOK_URL = process.env.WEBHOOK_URL; // 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); // Sicherstellen, dass Verzeichnisse und Dateien existieren if (!fs.existsSync(LOG_DIR)) { fs.mkdirSync(LOG_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 } // Erstelle den Cache-Ordner, falls er nicht existiert if (!fs.existsSync(CacheDir)) { fs.mkdirSync(CacheDir); } // Initialisiere den Cache mit einer bestimmten Lebensdauer (TTL) von 1 Stunde const cache = new NodeCache({ stdTTL: 3600 }); // Funktion zum Speichern des Caches in eine Datei function saveCacheToFile() { const cacheData = cache.keys().reduce((acc, key) => { acc[key] = cache.get(key); return acc; }, {}); fs.writeFileSync(cacheFilePath, JSON.stringify(cacheData)); } // Funktion zum Laden des Caches aus einer Datei function loadCacheFromFile() { if (fs.existsSync(cacheFilePath)) { const cacheData = JSON.parse(fs.readFileSync(cacheFilePath)); for (const [key, value] of Object.entries(cacheData)) { cache.set(key, value); } } } // 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 { const sectionsData = await fetchPlexData(PLEX_LIBRARY_URL); const sections = sectionsData.MediaContainer.Directory; let movies = []; 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')); } } 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 Abrufen der Filme mit Caching async function fetchMoviesWithCache() { const cacheKey = 'allMovies'; const cachedMovies = cache.get(cacheKey); if (cachedMovies) { logMessage('Movies fetched from cache'); return cachedMovies; } try { const movies = await fetchAllMovies(); cache.set(cacheKey, movies); logMessage('Movies fetched from API and cached'); return movies; } catch (error) { logError(`Error fetching movies: ${error.message}`); throw error; } } // Funktion zum Abrufen eines zufälligen Films mit Caching async function fetchRandomMovie() { try { const movies = await fetchMoviesWithCache(); 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; } } // Funktion zum Durchführen der Filmsuche mit Caching async function searchMovies(query) { try { const movies = await fetchMoviesWithCache(); const results = movies.filter(movie => movie.title.toLowerCase().includes(query.toLowerCase()) ); return results; } catch (error) { logError(`Error searching movies: ${error.message}`); throw error; } } // Funktion zum Abrufen der gut bewerteten Filme mit Caching async function fetchTopRatedMovies() { try { const movies = await fetchMoviesWithCache(); const ratedMovies = movies.filter(movie => movie.rating && movie.rating > 0); 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 mit Caching async function fetchDailyRecommendation() { try { const ratedMovies = await fetchTopRatedMovies(); if (ratedMovies.length === 0) return null; dayjs.extend(dayOfYear); // Füge das Plugin hier hinzu const dayOfYear = dayjs().dayOfYear(); const todayIndex = dayOfYear % ratedMovies.length; return ratedMovies[todayIndex]; } catch (error) { logError(`Error fetching daily recommendation: ${error.message}`); throw error; } } // Funktion zum Abrufen der letzten 10 hinzugefügten Filme mit Caching async function fetchLatest10Movies() { try { const movies = await fetchMoviesWithCache(); const sortedMovies = movies .filter(movie => movie.addedAt) .sort((a, b) => b.addedAt - a.addedAt) .slice(0, 10); return sortedMovies; } catch (error) { logError(`Error fetching latest 10 movies: ${error.message}`); throw error; } } // Funktion zum automatischen Aktualisieren des Caches async function updateCache() { try { await fetchMoviesWithCache(); // Stellt sicher, dass der Cache aktualisiert wird logMessage('Cache wurde automatisch aktualisiert'); } catch (error) { logError(`Fehler beim automatischen Aktualisieren des Caches: ${error.message}`); } } // Lade den Cache beim Start (async function start() { try { await fetchMoviesWithCache(); // Initialisiert den Cache beim Start logMessage('Cache beim Start initialisiert'); // Speicher den Cache regelmäßig (z.B. jede Stunde) schedule.scheduleJob('0 * * * *', saveCacheToFile); // Plane die automatische Aktualisierung des Caches jede Stunde schedule.scheduleJob('0 * * * *', updateCache); // Beispiel für die Verwendung von node-schedule function checkForNewMovies() { // Hier könntest du eine Funktion zum Überprüfen neuer Filme einfügen console.log('Checking for new movies...'); } // Beispiel für geplante Aufgaben schedule.scheduleJob('*/1 * * * *', checkForNewMovies); } catch (error) { logError(`Fehler beim Start des Bots: ${error.message}`); } })(); // 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 = dayjs().format('YYYY-MM-DD'); const logFilePath = path.join(LOG_DIR, `${today}.log`); fs.appendFileSync(logFilePath, `${dayjs().format('HH:mm:ss')} - ${message}\n`); } // Funktion zur Fehlerprotokollierung function logError(error) { const errorMessage = `${dayjs().format('HH:mm:ss')} - Error: ${error}\n`; fs.appendFileSync(ERROR_LOG_PATH, errorMessage); } // /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: userId, notifications: true }; // Standardmäßig Benachrichtigungen aktiviert 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})`); }); // /notification-on-Befehl verarbeiten bot.onText(/\/notification_on/, (msg) => { const chatId = msg.chat.id; // Benutzerdaten in user.yml laden let users = yaml.load(USER_YML_PATH); if (users[chatId]) { users[chatId].notifications = true; fs.writeFileSync(USER_YML_PATH, yaml.stringify(users, 4)); bot.sendMessage(chatId, 'Benachrichtigungen wurden aktiviert.'); } else { bot.sendMessage(chatId, 'Du musst den Bot zuerst mit /start aktivieren.'); } }); // /notification-off-Befehl verarbeiten bot.onText(/\/notification_off/, (msg) => { const chatId = msg.chat.id; // Benutzerdaten in user.yml laden let users = yaml.load(USER_YML_PATH); if (users[chatId]) { users[chatId].notifications = false; fs.writeFileSync(USER_YML_PATH, yaml.stringify(users, 4)); bot.sendMessage(chatId, 'Benachrichtigungen wurden deaktiviert.'); } else { bot.sendMessage(chatId, 'Du musst den Bot zuerst mit /start aktivieren.'); } }); let lastAddedMovieTime = null; // Variable zum Speichern des Zeitpunkts des letzten Films // Funktion zum Abrufen der letzten hinzugefügten Filme async function fetchLatestMovies() { try { const response = await axios.get(`${PLEX_DOMAIN}/library/recentlyAdded?X-Plex-Token=${PLEX_TOKEN}`); const movies = response.data.MediaContainer.Metadata; if (movies && movies.length > 0) { return movies; } return []; } catch (error) { console.error(`Error fetching latest movies: ${error.message}`); return []; } } // Funktion zum Überprüfen und Benachrichtigen über neue Filme async function checkForNewMovies() { const movies = await fetchLatestMovies(); if (movies.length > 0) { const latestMovie = movies[0]; if (!lastAddedMovieTime || dayjs.unix(latestMovie.addedAt).isAfter(lastAddedMovieTime)) { // Neuer Film hinzugefügt lastAddedMovieTime = dayjs.unix(latestMovie.addedAt); const movieTitle = latestMovie.title || 'Unbekannt'; const movieSummary = latestMovie.summary || 'Keine Zusammenfassung verfügbar'; const movieThumb = latestMovie.thumb ? `${PLEX_DOMAIN}${latestMovie.thumb}?X-Plex-Token=${PLEX_TOKEN}` : ''; // Kürze die Zusammenfassung, wenn sie zu lang ist const maxSummaryLength = 200; // Maximale Länge der Zusammenfassung const truncatedSummary = movieSummary.length > maxSummaryLength ? `${movieSummary.substring(0, maxSummaryLength)}...` : movieSummary; const message = `Ein neuer Film wurde hinzugefügt:\n\nTitel: ${movieTitle}\n\nZusammenfassung:\n${truncatedSummary}`; const users = yaml.load(USER_YML_PATH); const sendMessages = Object.keys(users).map(chatId => { if (users[chatId].notifications) { if (movieThumb) { // Wenn ein Bild vorhanden ist, sende es mit der Nachricht return bot.sendPhoto(chatId, movieThumb, { caption: message }).catch(error => { console.error(`Error sending photo to chatId ${chatId}: ${error.message}`); }); } else { // Wenn kein Bild vorhanden ist, sende nur die Nachricht return bot.sendMessage(chatId, message).catch(error => { console.error(`Error sending message to chatId ${chatId}: ${error.message}`); }); } } }).filter(promise => promise !== undefined); await Promise.all(sendMessages); console.log(`Sent new movie message to all users`); } } } // Plane die Überprüfung alle 5 Minuten schedule.scheduleJob('*/1 * * * *', checkForNewMovies); // Initiale Überprüfung beim Start checkForNewMovies(); // /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; try { const { movieCount, showCount, episodeCount, seasonCount, topGenre, totalSize, oldestMovie, newestMovie } = await fetchAllMedia(); const message = `In der Bibliothek befinden sich derzeit:\n\n` + `📽️ Filme: ${movieCount}\n\n` + `📺 Serien: ${showCount}\n\n` + `🎞️ Episoden: ${episodeCount}\n\n` + `📚 Staffeln: ${seasonCount}\n\n\n` + `📊 Top-Genre: ${topGenre}\n\n` + `💾 Gesamtgröße: ${totalSize}\n\n\n` + `⏳ Ältester Film: ${oldestMovie.title} (${oldestMovie.year})\n\n` + `🆕 Neuester Film: ${newestMovie.title} (${newestMovie.year})\n\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 detailed media info, copyright, and Plex button to chatId ${chatId}`); } catch (error) { logError(`Error fetching media info: ${error.message}`); await bot.sendMessage(chatId, 'Fehler beim Abrufen der Medieninformationen.').catch(err => { logError(`Error sending media info error message to chatId ${chatId}: ${err.message}`); }); } }); // Funktion zum Abrufen der erweiterten Medieninformationen async function fetchAllMedia() { try { const movies = await fetchAllMovies(); const shows = await fetchAllShows(); const episodeCount = shows.reduce((sum, show) => sum + (show.leafCount || 0), 0); const seasonCount = shows.reduce((sum, show) => sum + (show.childCount || 0), 0); const topGenre = findTopGenre(movies.concat(shows)); const totalSize = await calculateTotalSize(movies.concat(shows)); const oldestMovie = findOldestMedia(movies); const newestMovie = findNewestMedia(movies); return { movieCount: movies.length, showCount: shows.length, episodeCount: episodeCount, seasonCount: seasonCount, topGenre: topGenre, totalSize: totalSize, oldestMovie: oldestMovie, newestMovie: newestMovie }; } catch (error) { logError(`Error fetching all media: ${error.message}`); throw error; } } // Funktion zur Ermittlung des am häufigsten vorkommenden Genres function findTopGenre(mediaArray) { const genreCount = {}; mediaArray.forEach(media => { if (media.Genre) { media.Genre.forEach(genre => { genreCount[genre.tag] = (genreCount[genre.tag] || 0) + 1; }); } }); return Object.keys(genreCount).reduce((a, b) => genreCount[a] > genreCount[b] ? a : b, ''); } // Funktion zur Berechnung der Gesamtgröße der Mediendateien async function calculateTotalSize(mediaArray) { let totalSizeBytes = 0; for (const media of mediaArray) { if (media.Media && media.Media.length > 0) { media.Media.forEach(mediaItem => { if (mediaItem.Part && mediaItem.Part.length > 0) { mediaItem.Part.forEach(part => { if (part.size) { const sizeInBytes = parseInt(part.size, 10); totalSizeBytes += sizeInBytes; } }); } }); } } // Log total size in bytes for debugging console.log(`Total size in bytes: ${totalSizeBytes}`); // Convert bytes to terabytes (TB) and gigabytes (GB) const totalSizeTB = totalSizeBytes / (1024 * 1024 * 1024 * 1024); const totalSizeGB = totalSizeBytes / (1024 * 1024 * 1024); // Log sizes in GB and TB console.log(`Total size in TB: ${totalSizeTB}`); console.log(`Total size in GB: ${totalSizeGB}`); // Determine the appropriate size unit to display if (totalSizeTB >= 1) { return `${totalSizeTB.toFixed(2)} TB`; } else { return `${totalSizeGB.toFixed(2)} GB`; } } // Funktion zum Finden des ältesten Mediums function findOldestMedia(mediaArray) { return mediaArray.reduce((oldest, media) => { if (!oldest || (media.year && media.year < oldest.year)) { return media; } return oldest; }, null); } // Funktion zum Finden des neuesten Mediums function findNewestMedia(mediaArray) { return mediaArray.reduce((newest, media) => { if (!newest || (media.year && media.year > newest.year)) { return media; } return newest; }, null); } // Funktion zum Abrufen aller Filme async function fetchAllMovies() { try { const sectionsData = await fetchPlexData(PLEX_LIBRARY_URL); const sections = sectionsData.MediaContainer.Directory; let movies = []; 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; movies = movies.concat(metadata.filter(media => media.type === 'movie')); } } return movies; } catch (error) { logError(`Error fetching all movies: ${error.message}`); throw error; } } // Funktion zum Abrufen aller Serien async function fetchAllShows() { try { const sectionsData = await fetchPlexData(PLEX_LIBRARY_URL); const sections = sectionsData.MediaContainer.Directory; let shows = []; 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; } } // Hilfsfunktion zum Abrufen von Plex-Daten async function fetchPlexData(url) { try { const response = await axios.get(url); return response.data; } catch (error) { logError(`Error fetching Plex data: ${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 { 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) { await 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 const messages = results.map(async (movie) => { const { title, summary, thumb } = movie; const movieThumbUrl = thumb ? `${process.env.PLEX_DOMAIN}${thumb}?X-Plex-Token=${process.env.PLEX_TOKEN}` : ''; const message = `Titel: ${title}\n\nZusammenfassung: \n\n${summary}`; try { if (movieThumbUrl) { await bot.sendPhoto(chatId, movieThumbUrl, { caption: message }); logMessage(`Sent photo for movie "${title}" to chatId ${chatId}`); } else { await bot.sendMessage(chatId, message); logMessage(`Sent message for movie "${title}" to chatId ${chatId}`); } } catch (error) { logError(`Error sending message or photo to chatId ${chatId}: ${error.message}`); // Optional: Sende nur die Textnachricht, wenn das Bild nicht gesendet werden konnte await bot.sendMessage(chatId, message).catch(err => { logError(`Error sending fallback message to chatId ${chatId}: ${err.message}`); }); } }); // Führe alle Nachrichten-Operationen aus await Promise.all(messages); logMessage(`Sent search results for query "${query}" to chatId ${chatId}`); } } catch (error) { let errorMessage = 'Fehler beim Durchführen der Suche.'; if (error.response) { errorMessage += ` Statuscode: ${error.response.status}`; logError(`Error searching movies: ${error.response.status} - ${error.response.statusText}`); } else if (error.request) { errorMessage += ' Keine Antwort vom Server.'; logError(`Error searching movies: No response from server`); } else { errorMessage += ` Unbekannter Fehler: ${error.message}`; logError(`Error searching movies: ${error.message}`); } await bot.sendMessage(chatId, errorMessage).catch(err => { logError(`Error sending search error message to chatId ${chatId}: ${err.message}`); }); } // Benutzerstatus zurücksetzen userStates[chatId].waitingForQuery = false; } }); // Funktion zum Abrufen der Filme basierend auf der Suche async function searchMovies(query) { try { // Placeholder für die tatsächliche Implementierung // Diese Funktion sollte Filme basierend auf dem Suchbegriff abfragen und zurückgeben const movies = await fetchMoviesFromAPI(query); // Ersetze dies durch die echte Implementierung return movies; } catch (error) { logError(`Error searching movies: ${error.message}`); throw error; } } // Funktion zum Abrufen der Filme aus der API (placeholder) async function fetchMoviesFromAPI(query) { try { const url = `${process.env.PLEX_DOMAIN}/search?query=${encodeURIComponent(query)}&X-Plex-Token=${process.env.PLEX_TOKEN}`; const response = await axios.get(url); return response.data.MediaContainer.Metadata; // Oder wie auch immer die API antwortet } catch (error) { logError(`Error fetching movies from API: ${error.message}`); throw error; } } // Array, um empfohlene Filme zu speichern const recommendedMovies = []; let dailyMovieCache = {}; // Cache für den Film des Tages // Funktion zum Abrufen des täglichen Films basierend auf dem Datum async function fetchDailyRecommendation() { try { // Berechne das heutige Datum const today = moment().format('YYYY-MM-DD'); // Überprüfen, ob wir bereits einen Film für heute gespeichert haben if (dailyMovieCache[today]) { return dailyMovieCache[today]; } // Anfrage zur Mediathek, um alle Filme abzurufen const url = `${process.env.PLEX_DOMAIN}/library/sections/1/all?X-Plex-Token=${process.env.PLEX_TOKEN}`; const response = await axios.get(url); const data = response.data; if (data && data.MediaContainer && Array.isArray(data.MediaContainer.Metadata) && data.MediaContainer.Metadata.length > 0) { // Wähle einen zufälligen Film aus der Liste der Filme aus const movies = data.MediaContainer.Metadata; const randomIndex = Math.floor(Math.random() * movies.length); const selectedMovie = movies[randomIndex]; // Speichern des Films für heute im Cache dailyMovieCache[today] = selectedMovie; return selectedMovie; } else { // Protokolliere, wenn keine Filme gefunden wurden console.log('No movies found in API response or unexpected response format'); return null; } } catch (error) { logError(`Error fetching daily recommendation from API: ${error.message}`); throw error; } } // /empfehlung-Befehl verarbeiten bot.onText(/\/empfehlung/, async (msg) => { const chatId = msg.chat.id; try { const dailyMovie = await fetchDailyRecommendation(); if (dailyMovie) { // Film empfehlen const movieTitle = dailyMovie.title || 'Unbekannt'; const movieSummary = dailyMovie.summary || 'Keine Zusammenfassung verfügbar'; const movieThumb = dailyMovie.thumb ? `${process.env.PLEX_DOMAIN}${dailyMovie.thumb}?X-Plex-Token=${process.env.PLEX_TOKEN}` : ''; const message = `Hier ist der empfohlene Film des Tages:\n\nTitel: ${movieTitle}\n\nZusammenfassung: \n${movieSummary}`; // Bild anzeigen, wenn vorhanden if (movieThumb) { await bot.sendPhoto(chatId, movieThumb, { 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 daily recommendation to chatId ${chatId}`); } else { await 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) { let errorMessage = 'Fehler beim Abrufen der Empfehlung.'; if (error.response) { errorMessage += ` Statuscode: ${error.response.status}`; logError(`Error fetching daily recommendation: ${error.response.status} - ${error.response.statusText}`); } else if (error.request) { errorMessage += ' Keine Antwort vom Server.'; logError(`Error fetching daily recommendation: No response from server`); } else { errorMessage += ` Unbekannter Fehler: ${error.message}`; logError(`Error fetching daily recommendation: ${error.message}`); } await bot.sendMessage(chatId, errorMessage).catch(err => { logError(`Error sending daily recommendation error message to chatId ${chatId}: ${err.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` + `🔔 /notification\\_on - Aktiviert Benachrichtigungen für neue Filme.\n\n` + `🔕 /notification\\_off - Deaktiviert Benachrichtigungen für neue Filme.\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) { const numberEmojis = ['1️⃣', '2️⃣', '3️⃣', '4️⃣', '5️⃣', '6️⃣', '7️⃣', '8️⃣', '9️⃣', '🔟']; const inlineKeyboard = [[], []]; // Zwei Zeilen für das Inline-Keyboard let message = 'Letzten 10 hinzugefügten Filme:\n\n'; latestMovies.forEach((movie, index) => { const numberEmoji = numberEmojis[index] || ''; message += `${numberEmoji} - ${movie.title || 'Unbekannt'}\n\n`; // Ordne die Schaltflächen in zwei Zeilen an (5 pro Zeile) const rowIndex = index < 5 ? 0 : 1; inlineKeyboard[rowIndex].push({ text: numberEmoji, callback_data: `movie_${index}` }); }); // Füge die Anweisung unter den Filmnamen hinzu message += '\nKlicke auf die Zahl, um nähere Informationen zu bekommen.'; bot.sendMessage(chatId, message, { reply_markup: { inline_keyboard: inlineKeyboard } }).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) { handleError(chatId, error); } }); // Inline-Knopf-Ereignis für Film auswählen verarbeiten bot.on('callback_query', async (callbackQuery) => { const chatId = callbackQuery.message.chat.id; const data = callbackQuery.data; if (data.startsWith('movie_')) { const movieIndex = parseInt(data.split('_')[1], 10); try { const latestMovies = await fetchLatest10Movies(); const selectedMovie = latestMovies[movieIndex]; if (selectedMovie) { const movieDetails = ` 🎬 *Titel*: ${selectedMovie.title || 'Unbekannt'}\n\n 📝 *Zusammenfassung*: \n${selectedMovie.summary || 'Keine Zusammenfassung verfügbar.'}\n\n 📅 *Hinzugefügt am*: ${dayjs(selectedMovie.addedAt * 1000).format('DD.MM.YYYY')} `; if (selectedMovie.thumb) { const imageUrl = `${PLEX_DOMAIN}${selectedMovie.thumb}?X-Plex-Token=${PLEX_TOKEN}`; bot.sendPhoto(chatId, imageUrl, { caption: movieDetails, parse_mode: 'Markdown' }).catch(error => { logError(`Error sending photo to chatId ${chatId}: ${error.message}`); }); } else { bot.sendMessage(chatId, movieDetails, { parse_mode: 'Markdown' }).catch(error => { logError(`Error sending message to chatId ${chatId}: ${error.message}`); }); } logMessage(`Sent movie details for movie index ${movieIndex} to chatId ${chatId}`); } else { bot.sendMessage(chatId, 'Film nicht gefunden.').catch(error => { logError(`Error sending movie not found message to chatId ${chatId}: ${error.message}`); }); } } catch (error) { handleError(chatId, error); } } }); function handleError(chatId, error) { if (error.response) { bot.sendMessage(chatId, `Fehler beim Abrufen der Daten. Statuscode: ${error.response.status}`).catch(err => { logError(`Error sending error message to chatId ${chatId}: ${err.message}`); }); logError(`Error fetching data: ${error.response.status} - ${error.response.statusText}`); } else if (error.request) { bot.sendMessage(chatId, 'Fehler beim Abrufen der Daten. Keine Antwort vom Server.').catch(err => { logError(`Error sending no response message to chatId ${chatId}: ${err.message}`); }); logError(`Error fetching data: No response from server`); } else { logError(`Error fetching data: ${error.message}`); } } // Funktion zum Verarbeiten von Webhook-Anfragen app.post('/mywebhook', async (req, res) => { try { const event = req.body; 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}`; 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}`); }) ); 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); } catch (error) { logError(`Error processing webhook: ${error.message}`); res.sendStatus(500); } }); // 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...');