telegram-film-wunsch-bot/wunsch-bot.js

523 lines
22 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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...');