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

504 lines
22 KiB
JavaScript
Raw Normal View History

2024-09-07 18:05:16 +00:00
require('dotenv').config();
const { Telegraf } = require('telegraf');
const fs = require('fs');
const path = require('path');
const archiver = require('archiver');
const schedule = require('node-schedule');
2024-10-14 19:54:55 +00:00
const express = require('express');
const bodyParser = require('body-parser');
2024-09-07 18:05:16 +00:00
2024-10-14 19:54:55 +00:00
const app = express();
app.use(bodyParser.json());
2024-09-07 18:05:16 +00:00
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' }],
2024-09-19 15:33:59 +00:00
[{ text: 'Bollywood', callback_data: 'category_bollywood' }],
2024-10-05 17:07:25 +00:00
[{ text: 'Hörspiele & Comics', callback_data: 'category_hoerspiele_comics' }],
[{ text: 'PC Games', callback_data: 'category_pc_games' }] // Neue Kategorie hinzugefügt
2024-09-07 18:05:16 +00:00
]
})
};
}
// 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);
2024-09-19 15:33:59 +00:00
// /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);
});
2024-10-05 17:07:25 +00:00
// Callback-Query-Handler
2024-09-07 18:05:16 +00:00
// Callback-Query-Handler
bot.on('callback_query', async (ctx) => {
const data = ctx.callbackQuery.data;
2024-10-05 17:07:25 +00:00
const messageId = ctx.callbackQuery.message.message_id; // Speichere die ID der Nachricht, um sie eindeutig zu identifizieren
2024-09-07 18:05:16 +00:00
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';
2024-10-05 17:07:25 +00:00
const message = `✨ *Wunsch wurde erfüllt!* ✨\n\n🎉 Der gewünschte Inhalt ist jetzt verfügbar.\n\n📺 Bitte schaue im Kanal "${category}" rein.`;
2024-09-07 18:05:16 +00:00
2024-10-05 17:07:25 +00:00
// Sende die Nachricht, dass der Wunsch erfüllt wurde
2024-09-07 18:05:16 +00:00
await bot.telegram.sendMessage(allowedChatId, message, {
message_thread_id: allowedThreadId, // In das richtige Thema posten
});
2024-10-05 17:07:25 +00:00
// Lösche nur die Nachricht, die diesem Wunsch zugeordnet ist
await ctx.deleteMessage(messageId);
// Beantworte die Callback-Abfrage, um den Ladekreis zu entfernen
2024-09-07 18:05:16 +00:00
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);
}
2024-10-05 17:07:25 +00:00
// 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,
2024-09-07 18:05:16 +00:00
});
2024-10-05 17:07:25 +00:00
await ctx.deleteMessage(messageId);
2024-09-07 18:05:16 +00:00
// Beantworte die Callback-Abfrage
await ctx.answerCbQuery();
} else {
await ctx.answerCbQuery('Nur Admins können diese Funktion nutzen.');
}
} else if (data === 'delete_not_found') {
await ctx.reply('Bitte gib die Nummer des Eintrags ein, den du löschen möchtest.', {
reply_markup: JSON.stringify({
force_reply: true
})
});
2024-10-05 17:07:25 +00:00
userStates[chatId] = { ...userStates[chatId], waitingForDeleteIndex: true };
2024-09-07 18:05:16 +00:00
} 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',
2024-09-19 15:33:59 +00:00
'category_bollywood': 'Bollywood',
2024-10-05 17:07:25 +00:00
'category_hoerspiele_comics': 'Hörspiele & Comics',
'category_pc_games': 'PC Games'
2024-09-07 18:05:16 +00:00
};
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,
2024-10-05 17:07:25 +00:00
waitingForLink: true,
categoryMessageId: categoryMessage.message_id
2024-09-07 18:05:16 +00:00
};
2024-10-05 17:07:25 +00:00
await ctx.deleteMessage(messageId);
2024-09-07 18:05:16 +00:00
}
} catch (error) {
logError(new Error(`Error handling callback query: ${error.message}`));
}
2024-10-05 17:07:25 +00:00
// Beantworte die Callback-Abfrage als abgeschlossen
2024-09-07 18:05:16 +00:00
ctx.answerCbQuery().catch(error => {
logError(new Error(`Error answering callback query: ${error.message}`));
});
});
// Nachrichten-Handler
bot.on('text', async (ctx) => {
const chatId = ctx.chat.id.toString();
const userId = ctx.message.from.id;
const text = ctx.message.text;
const threadId = ctx.message?.message_thread_id; // Thema-ID (falls vorhanden)
console.log(`Received message in chat ID ${chatId} and thread ID ${threadId}: ${text}`); // Logging zur Diagnose
if (chatId !== allowedChatId || threadId !== allowedThreadId) {
console.log(`Ignoring message in chat ID ${chatId} and thread ID ${threadId} as it's not allowed.`);
if (text.startsWith('/wunsch')) {
// Nachricht zurückweisen, wenn der Befehl in einem nicht erlaubten Kanal verwendet wird
await ctx.reply('❌ Dieser Befehl ist in diesem Kanal nicht erlaubt. Bitte benutze den Befehl in den Serien- und Filmwünsche Kanal.', { disable_notification: true });
}
return;
}
// Überprüfe, ob der Benutzer in der Eingabestimmung ist
if (userStates[chatId]) {
if (userStates[chatId].waitingForLink) {
// Verarbeite den Link des Wunsches
const link = text.trim(); // Link erhalten
userStates[chatId].wishLink = link; // Speichern des Links
// Frage nach dem Titel des Wunsches
await ctx.reply(`Bitte gib den Titel des ${userStates[chatId].category} ein.`, { disable_notification: true });
userStates[chatId].waitingForLink = false; // Status zurücksetzen
userStates[chatId].waitingForWish = true; // Nun auf den Wunsch warten
return; // Beende die Verarbeitung, da der Benutzer jetzt nach dem Titel gefragt wird
}
if (userStates[chatId].waitingForWish) {
// Verarbeite den Titel des Wunsches
const wish = text.trim(); // Titel erhalten
if (wish) {
const category = userStates[chatId].category;
const link = userStates[chatId].wishLink;
const categoryMessageId = userStates[chatId].categoryMessageId;
const titleMessageId = ctx.message.message_id; // ID der Titel-Nachricht erhalten
const commandMessageId = userStates[chatId].commandMessageId; // ID der Befehl-Nachricht erhalten
const wishCommandMessageId = userStates[chatId].wishCommandMessageId; // ID der /wunsch-Nachricht erhalten
try {
userStates[chatId].wishTitle = wish; // Speichern des Wunsch-Titels für spätere Verwendung
await sendWish(wish, category, chatId, userId, link);
// Lösche die Nachrichten, die beim Verarbeiten des Wunsches gesendet wurden
if (categoryMessageId) await ctx.deleteMessage(categoryMessageId).catch(e => logError(new Error(`Failed to delete category message: ${e.message}`)));
if (titleMessageId) await ctx.deleteMessage(titleMessageId).catch(e => logError(new Error(`Failed to delete title message: ${e.message}`)));
if (commandMessageId) await ctx.deleteMessage(commandMessageId).catch(e => logError(new Error(`Failed to delete command message: ${e.message}`)));
if (wishCommandMessageId) await ctx.deleteMessage(wishCommandMessageId).catch(e => logError(new Error(`Failed to delete /wunsch message: ${e.message}`)));
userStates[chatId].waitingForWish = false; // Benutzerstatus zurücksetzen
} catch (error) {
logError(new Error(`Error processing wish: ${error.message}`));
}
} else {
await ctx.reply(`Bitte gib den Titel des ${userStates[chatId].category} ein.`, { disable_notification: true });
}
return; // Beende die Verarbeitung, wenn der Benutzer in der Eingabestimmung ist
}
if (userStates[chatId].waitingForDeleteIndex) {
// Verarbeite die Löschanfrage
const index = parseInt(text.trim(), 10) - 1; // Index aus der Nachricht extrahieren
if (deleteNotFoundWish(index)) {
await ctx.reply('Der Eintrag wurde erfolgreich gelöscht.');
} else {
await ctx.reply('Ungültige Nummer. Bitte versuche es erneut.');
}
userStates[chatId].waitingForDeleteIndex = false; // Benutzerstatus zurücksetzen
return;
}
}
if (text.startsWith('/wunsch')) {
// Benutzer zur Auswahl der Kategorie auffordern
const commandMessage = await ctx.reply('Möchtest du etwas wünschen? Wähle bitte eine Kategorie:', { ...getCategoryKeyboard(), disable_notification: true });
userStates[chatId] = {
waitingForCategory: true,
commandMessageId: commandMessage.message_id, // Speichern der ID der Befehl-Nachricht
wishCommandMessageId: ctx.message.message_id // Speichern der ID der /wunsch-Nachricht
}; // Setze den Status auf "wartend auf Kategorie"
} else if (text.startsWith('/notfound')) {
// Überprüfe, ob der Benutzer ein Admin ist
const admins = await bot.telegram.getChatAdministrators(chatId);
const isAdmin = admins.some(admin => admin.user.id === userId);
if (isAdmin) {
if (fs.existsSync(notFoundLogFilePath)) {
const notFoundWishes = JSON.parse(fs.readFileSync(notFoundLogFilePath, 'utf8'));
let replyMessage = '🔍 Liste der nicht gefundenen Wünsche:\n\n';
if (notFoundWishes.length === 0) {
replyMessage += 'Keine nicht gefundenen Wünsche.';
} else {
notFoundWishes.forEach((entry, index) => {
replyMessage += `${index + 1}. Kategorie: ${entry.category}\n Wunsch: ${entry.wish}\n\n\n`;
});
replyMessage += '\n🗑 [Löschen]';
}
// Sende die Liste direkt an den Admin
await ctx.reply(replyMessage, { disable_notification: true, ...getDeleteNotFoundWishKeyboard() });
// Lösche den /notfound Befehl
await ctx.deleteMessage(ctx.message.message_id).catch(e => logError(new Error(`Failed to delete /notfound command message: ${e.message}`)));
} else {
await ctx.reply('Noch keine nicht gefundenen Wünsche aufgezeichnet.', { disable_notification: true });
}
} else {
await ctx.reply('❌ Du bist nicht berechtigt, diese Funktion zu nutzen. ❌', { disable_notification: true });
}
}
});
2024-10-14 19:54:55 +00:00
//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}`);
});
2024-09-07 18:05:16 +00:00
bot.launch();
console.log('Bot is running...');