600 lines
20 KiB
JavaScript
600 lines
20 KiB
JavaScript
require('dotenv').config();
|
||
const { Telegraf } = require('telegraf');
|
||
const express = require('express');
|
||
const path = require('path');
|
||
const fs = require('fs').promises;
|
||
const archiver = require('archiver');
|
||
const schedule = require('node-schedule');
|
||
|
||
// ==================== KONFIGURATION ====================
|
||
|
||
const validateEnv = () => {
|
||
const required = ['TELEGRAM_TOKEN', 'ALLOWED_CHAT_ID'];
|
||
const missing = required.filter(key => !process.env[key]);
|
||
if (missing.length > 0) {
|
||
console.error(`Fehlende Umgebungsvariablen: ${missing.join(', ')}`);
|
||
process.exit(1);
|
||
}
|
||
};
|
||
validateEnv();
|
||
|
||
const CONFIG = {
|
||
token: process.env.TELEGRAM_TOKEN,
|
||
allowedChatId: process.env.ALLOWED_CHAT_ID,
|
||
allowedThreadId: parseInt(process.env.ALLOWED_THREAD_ID, 10) || null,
|
||
adminIds: (process.env.ADM_IDS || '').split(',').map(id => id.trim()).filter(Boolean),
|
||
|
||
// Blacklists
|
||
blacklistChatIds: (process.env.BLACKLIST_CHAT_IDS || '').split(',').map(id => id.trim()).filter(Boolean),
|
||
// WICHTIG: IDs wie 1, 646 müssen HIER stehen, damit Topics geblockt werden
|
||
blacklistThreadIds: (process.env.BLACKLIST_THREAD_IDS || '').split(',').map(id => parseInt(id.trim(), 10)).filter(Boolean),
|
||
|
||
logDir: path.join(__dirname, 'Log'),
|
||
port: process.env.PORT || 3005,
|
||
itemsPerPage: 10
|
||
};
|
||
|
||
const PATHS = {
|
||
errorLog: path.join(CONFIG.logDir, 'error.log'),
|
||
wishLog: path.join(CONFIG.logDir, 'wish.log'),
|
||
notFoundLog: path.join(CONFIG.logDir, 'not_found.json')
|
||
};
|
||
|
||
const CATEGORIES = {
|
||
'category_film': '🎬 Film',
|
||
'category_4k_filme': '📀 4K Filme',
|
||
'category_serie': '📺 Serie',
|
||
'category_anime': '📖 Anime',
|
||
'category_disney': '✨ Disney',
|
||
'category_bollywood': '🎬 Bollywood',
|
||
'category_medizin': '⚕️ Medizin',
|
||
'category_survival': '🏕️ Survival',
|
||
'category_wwe': '🏆 WWE',
|
||
'category_musik': '🎵 Musik',
|
||
'category_hoerspiele_comics': '🎧 Hörspiele & Comics',
|
||
'category_pc_games': '💻 PC Games'
|
||
};
|
||
|
||
// ==================== HELPER KLASSEN ====================
|
||
|
||
class Logger {
|
||
static async error(error) {
|
||
const timestamp = new Date().toISOString();
|
||
const message = `${timestamp} - ${error.message}\n${error.stack}\n\n`;
|
||
console.error(message);
|
||
try {
|
||
if (!await this.fileExists(PATHS.errorLog)) await fs.writeFile(PATHS.errorLog, '');
|
||
await fs.appendFile(PATHS.errorLog, message);
|
||
} catch (err) {
|
||
console.error('Kritisch: Konnte Error-Log nicht schreiben:', err.message);
|
||
}
|
||
}
|
||
|
||
static async wish(wish, category, link) {
|
||
const timestamp = new Date().toISOString();
|
||
const message = `${timestamp} - Kategorie: ${category} - Wunsch: ${wish} - Link: ${link || 'Kein Link'}\n\n`;
|
||
try {
|
||
if (!await this.fileExists(PATHS.wishLog)) await fs.writeFile(PATHS.wishLog, '');
|
||
await fs.appendFile(PATHS.wishLog, message);
|
||
} catch (err) {
|
||
console.error('Fehler beim Schreiben in wish.log:', err.message);
|
||
}
|
||
}
|
||
|
||
static async fileExists(filePath) {
|
||
try {
|
||
await fs.access(filePath);
|
||
return true;
|
||
} catch {
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
|
||
class NotFoundManager {
|
||
static async get() {
|
||
try {
|
||
const data = await fs.readFile(PATHS.notFoundLog, 'utf8');
|
||
return JSON.parse(data);
|
||
} catch (error) {
|
||
if (error.code !== 'ENOENT') console.error('Fehler Lesen not_found.json:', error);
|
||
return [];
|
||
}
|
||
}
|
||
|
||
static async add(wish, category) {
|
||
try {
|
||
const wishes = await this.get();
|
||
wishes.push({ wish, category, timestamp: new Date().toISOString() });
|
||
await fs.writeFile(PATHS.notFoundLog, JSON.stringify(wishes, null, 2));
|
||
} catch (error) {
|
||
console.error('Fehler Speichern not_found:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
static async delete(index) {
|
||
try {
|
||
const wishes = await this.get();
|
||
if (index >= 0 && index < wishes.length) {
|
||
wishes.splice(index, 1);
|
||
await fs.writeFile(PATHS.notFoundLog, JSON.stringify(wishes, null, 2));
|
||
return true;
|
||
}
|
||
return false;
|
||
} catch (error) {
|
||
console.error('Fehler Löschen not_found:', error);
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
|
||
class SessionManager {
|
||
constructor() {
|
||
this.states = {};
|
||
}
|
||
|
||
set(chatId, data) {
|
||
this.states[chatId] = data;
|
||
}
|
||
|
||
get(chatId) {
|
||
return this.states[chatId];
|
||
}
|
||
|
||
delete(chatId) {
|
||
delete this.states[chatId];
|
||
}
|
||
|
||
async clearChat(ctx, chatId) {
|
||
const state = this.get(chatId);
|
||
if (!state) return;
|
||
|
||
const idsToDelete = [
|
||
state.commandMessageId,
|
||
state.uiMessageId,
|
||
state.wishCommandMessageId,
|
||
state.titleRequestMessageId
|
||
].filter(id => id);
|
||
|
||
for (const msgId of idsToDelete) {
|
||
try { await ctx.deleteMessage(msgId); } catch (err) {}
|
||
}
|
||
this.delete(chatId);
|
||
}
|
||
}
|
||
|
||
// ==================== EXPRESS & BOT SETUP ====================
|
||
|
||
const app = express();
|
||
app.use(express.json());
|
||
app.use(express.static(path.join(__dirname, 'public')));
|
||
|
||
const bot = new Telegraf(CONFIG.token);
|
||
const sessions = new SessionManager();
|
||
|
||
// ==================== MIDDLEWARES & HELPER ====================
|
||
|
||
// 1. GLOBALE BLACKLIST (Läuft für JEDE Nachricht und JEDES Update)
|
||
const globalRestrictionCheck = (ctx, next) => {
|
||
// Wir müssen Chat und Thread ID aus verschiedenen Quellen holen
|
||
// (Message, Edited Message, Callback Query)
|
||
const chatId = ctx.chat?.id.toString();
|
||
|
||
// Thread ID kann in message.message_thread_id oder callbackQuery.message.message_thread_id stehen
|
||
const threadId = ctx.message?.message_thread_id || ctx.callbackQuery?.message?.message_thread_id;
|
||
|
||
// 1. Chat Blacklist
|
||
if (chatId && CONFIG.blacklistChatIds.includes(chatId)) {
|
||
console.log(`🛑 GLOBALE BLOCKADE: Chat ID ${chatId} ist gesperrt.`);
|
||
return; // Sofort stoppen
|
||
}
|
||
|
||
// 2. Thread Blacklist (Fix für ID 1, 646, etc.)
|
||
// Wir prüfen nur, wenn eine threadId existiert (Topic)
|
||
if (threadId && CONFIG.blacklistThreadIds.includes(threadId)) {
|
||
console.log(`🛑 GLOBALE BLOCKADE: Thread ID ${threadId} ist auf der Blacklist.`);
|
||
return; // Sofort stoppen
|
||
}
|
||
|
||
// Wenn alles okay ist, geht es weiter
|
||
return next();
|
||
};
|
||
|
||
// Middleware global anwenden
|
||
bot.use(globalRestrictionCheck);
|
||
|
||
const getReplyOptions = (ctx, extra = {}) => {
|
||
const threadId = ctx.message?.message_thread_id || ctx.callbackQuery?.message?.message_thread_id;
|
||
if (threadId) {
|
||
return { ...extra, message_thread_id: threadId };
|
||
}
|
||
return extra;
|
||
};
|
||
|
||
// 3. STRICT MODE (Wenn ALLOWED_THREAD_ID gesetzt ist)
|
||
const restrictToConfiguredThread = (ctx, next) => {
|
||
const chatId = ctx.chat?.id.toString();
|
||
const threadId = ctx.message?.message_thread_id;
|
||
|
||
// Wenn wir nicht im erlaubten Chat sind, machen wir nix (außer Blacklist fängt das schon ab)
|
||
if (chatId !== CONFIG.allowedChatId) {
|
||
return;
|
||
}
|
||
|
||
// Wenn eine explizite ALLOWED_THREAD_ID gesetzt ist, muss sie stimmen
|
||
if (CONFIG.allowedThreadId && threadId !== CONFIG.allowedThreadId) {
|
||
console.log(`⚠️ IGNORIERT (Strict Mode): Falscher Thread. Ziel=${CONFIG.allowedThreadId}, Ist=${threadId}`);
|
||
return;
|
||
}
|
||
|
||
console.log(`✅ AKZEPTIERT: Cmd=${ctx.message.text} Thread=${threadId}`);
|
||
return next();
|
||
};
|
||
|
||
const isAdmin = (ctx, next) => {
|
||
const userId = ctx.from?.id.toString();
|
||
if (CONFIG.adminIds.includes(userId)) {
|
||
return next();
|
||
}
|
||
return;
|
||
};
|
||
|
||
// ==================== BOT LOGIK ====================
|
||
|
||
const deleteMessageSafe = async (ctx, msgId) => {
|
||
try { await ctx.deleteMessage(msgId); } catch (e) {}
|
||
};
|
||
|
||
const sendWishToGroup = async (wish, category, link) => {
|
||
const msg = `🎬 *Ein neuer Wunsch ist eingegangen!*\n\n🔹 Kategorie: ${category}\n\n🔸 Titel: ${wish}\n\n🔗 Link: ${link || 'Kein Link'}`;
|
||
await Logger.wish(wish, category, link);
|
||
|
||
await bot.telegram.sendMessage(CONFIG.allowedChatId, msg, {
|
||
message_thread_id: CONFIG.allowedThreadId,
|
||
parse_mode: 'Markdown',
|
||
reply_markup: {
|
||
inline_keyboard: [
|
||
[{ text: '✅ Erledigt', callback_data: 'wish_fulfilled' }],
|
||
[{ text: '❌ Nicht gefunden', callback_data: 'wish_not_found' }]
|
||
]
|
||
}
|
||
});
|
||
};
|
||
|
||
const getCategoryKeyboard = () => {
|
||
const buttons = Object.entries(CATEGORIES).map(([key, label]) => ({
|
||
text: label,
|
||
callback_data: key
|
||
}));
|
||
|
||
const chunks = [];
|
||
for (let i = 0; i < buttons.length; i += 2) {
|
||
chunks.push(buttons.slice(i, i + 2));
|
||
}
|
||
|
||
return { reply_markup: { inline_keyboard: chunks } };
|
||
};
|
||
|
||
// --- HILFSFUNKTION PAGINATION ---
|
||
|
||
async function sendNotFoundPage(ctx, page = 0) {
|
||
const userId = ctx.from.id;
|
||
const list = await NotFoundManager.get();
|
||
const totalItems = list.length;
|
||
const totalPages = Math.ceil(totalItems / CONFIG.itemsPerPage);
|
||
|
||
if (page >= totalPages && totalPages > 0) page = totalPages - 1;
|
||
|
||
const start = page * CONFIG.itemsPerPage;
|
||
const end = start + CONFIG.itemsPerPage;
|
||
const currentItems = list.slice(start, end);
|
||
|
||
let text = `🔍 *Liste der nicht gefundenen Wünsche (${totalItems} Einträge)*\n`;
|
||
text += `📄 Seite ${page + 1} / ${totalPages || 1}\n\n`;
|
||
|
||
const keyboard = [];
|
||
|
||
currentItems.forEach((item, index) => {
|
||
const globalIndex = start + index;
|
||
const displayTitle = item.wish.length > 25 ? item.wish.substring(0, 22) + '...' : item.wish;
|
||
text += `${globalIndex + 1}. [${item.category}] ${displayTitle}\n`;
|
||
});
|
||
|
||
const deleteButtons = [];
|
||
currentItems.forEach((item, index) => {
|
||
const globalIndex = start + index;
|
||
deleteButtons.push({
|
||
text: `🗑️ #${globalIndex + 1}`,
|
||
callback_data: `del_${globalIndex}_${page}`
|
||
});
|
||
});
|
||
|
||
const chunks = [];
|
||
for (let i = 0; i < deleteButtons.length; i += 3) {
|
||
chunks.push(deleteButtons.slice(i, i + 3));
|
||
}
|
||
keyboard.push(...chunks);
|
||
|
||
const navRow = [];
|
||
if (page > 0) navRow.push({ text: '⬅️ Zurück', callback_data: `page_${page - 1}` });
|
||
navRow.push({ text: `📄 ${page + 1}/${totalPages || 1}`, callback_data: 'ignore' });
|
||
if (page < totalPages - 1) navRow.push({ text: '➡️ Weiter', callback_data: `page_${page + 1}` });
|
||
if (navRow.length > 0) keyboard.push(navRow);
|
||
|
||
try {
|
||
if (ctx.callbackQuery) {
|
||
await ctx.editMessageText(text, {
|
||
parse_mode: 'Markdown',
|
||
reply_markup: { inline_keyboard: keyboard }
|
||
});
|
||
} else {
|
||
await bot.telegram.sendMessage(userId, text, {
|
||
parse_mode: 'Markdown',
|
||
reply_markup: { inline_keyboard: keyboard }
|
||
});
|
||
if (ctx.chat && ctx.chat.id.toString() !== userId.toString()) {
|
||
await ctx.reply('📬 Liste per PN geschickt.', getReplyOptions(ctx));
|
||
}
|
||
}
|
||
} catch (err) {
|
||
console.error('Error rendering page:', err);
|
||
}
|
||
}
|
||
|
||
// --- COMMANDS ---
|
||
|
||
// /start, /help, /info haben KEIN restrictToConfiguredThread, funktionieren also überall
|
||
// AUCH nicht in geblockten Threads, weil globalRestrictionCheck davor sitzt.
|
||
|
||
bot.command('start', (ctx) => ctx.reply('Willkommen! Nutze /wunsch um einen zu starten.', getReplyOptions(ctx)));
|
||
bot.command('help', (ctx) => ctx.reply(
|
||
`📋 *Hilfemenü*\n\n` +
|
||
`1️⃣ /wunsch - Starte einen Wunsch\n` +
|
||
`2️⃣ Kategorie wählen\n` +
|
||
`3️⃣ Optional: Link senden (oder "x")\n` +
|
||
`4️⃣ Titel eingeben`,
|
||
{ ...getReplyOptions(ctx), parse_mode: 'Markdown' }
|
||
));
|
||
|
||
bot.command('info', (ctx) => ctx.reply(
|
||
`🤖 *Bot-Informationen*\n\n` +
|
||
`Version: 2.0 (Strict Global Blacklist)\n` +
|
||
`Status: Laufend`,
|
||
{ ...getReplyOptions(ctx), parse_mode: 'Markdown' }
|
||
));
|
||
|
||
// Wichtig: /wunsch, /cancel, /notfound laufen NUR im erlaubten Thread
|
||
bot.command('wunsch', restrictToConfiguredThread, async (ctx) => {
|
||
const chatId = ctx.chat.id.toString();
|
||
await sessions.clearChat(ctx, chatId);
|
||
|
||
const msg = await ctx.reply('Möchtest du etwas wünschen? Wähle eine Kategorie.', {
|
||
...getCategoryKeyboard(),
|
||
...getReplyOptions(ctx),
|
||
disable_notification: true
|
||
});
|
||
|
||
sessions.set(chatId, {
|
||
waitingFor: 'category',
|
||
commandMessageId: ctx.message.message_id,
|
||
uiMessageId: msg.message_id
|
||
});
|
||
});
|
||
|
||
bot.command('cancel', restrictToConfiguredThread, async (ctx) => {
|
||
const chatId = ctx.chat.id.toString();
|
||
const session = sessions.get(chatId);
|
||
|
||
if (session) {
|
||
await sessions.clearChat(ctx, chatId);
|
||
await deleteMessageSafe(ctx, ctx.message.message_id);
|
||
|
||
const abortMsg = await ctx.reply('🔴 Vorgang abgebrochen.', {
|
||
...getReplyOptions(ctx),
|
||
disable_notification: true
|
||
});
|
||
|
||
setTimeout(() => deleteMessageSafe(ctx, abortMsg.message_id), 3000);
|
||
} else {
|
||
await deleteMessageSafe(ctx, ctx.message.message_id);
|
||
}
|
||
});
|
||
|
||
bot.command('notfound', restrictToConfiguredThread, isAdmin, async (ctx) => {
|
||
await sendNotFoundPage(ctx, 0);
|
||
});
|
||
|
||
// --- TEXT HANDLER ---
|
||
|
||
bot.on('text', restrictToConfiguredThread, async (ctx) => {
|
||
const chatId = ctx.chat.id.toString();
|
||
const session = sessions.get(chatId);
|
||
const text = ctx.message.text.trim();
|
||
|
||
if (!session) return;
|
||
|
||
try {
|
||
if (session.waitingFor === 'link') {
|
||
const link = text.toLowerCase() === 'x' ? null : text;
|
||
|
||
if (session.uiMessageId) await deleteMessageSafe(ctx, session.uiMessageId);
|
||
await deleteMessageSafe(ctx, ctx.message.message_id);
|
||
|
||
sessions.set(chatId, { ...session, link, waitingFor: 'title' });
|
||
|
||
const newMsg = await ctx.reply(`Bitte gib den Titel für ${session.category} ein:`, { ...getReplyOptions(ctx), disable_notification: true });
|
||
sessions.set(chatId, { ...session, link, waitingFor: 'title', uiMessageId: newMsg.message_id });
|
||
|
||
} else if (session.waitingFor === 'title') {
|
||
const wish = text;
|
||
if (!wish) return ctx.reply('Bitte einen Titel eingeben.', getReplyOptions(ctx));
|
||
|
||
await sendWishToGroup(wish, session.category, session.link);
|
||
|
||
await sessions.clearChat(ctx, chatId);
|
||
await deleteMessageSafe(ctx, ctx.message.message_id);
|
||
|
||
const successMsg = `✅ *Dein Wunsch wurde erfolgreich weitergeleitet!*\n\n🔹 Kategorie: ${session.category}\n🔸 Titel: ${wish}`;
|
||
|
||
const confirm = await ctx.reply(successMsg, {
|
||
...getReplyOptions(ctx),
|
||
parse_mode: 'Markdown',
|
||
disable_notification: true
|
||
});
|
||
setTimeout(() => deleteMessageSafe(ctx, confirm.message_id), 5000);
|
||
}
|
||
} catch (error) {
|
||
await Logger.error(error);
|
||
ctx.reply('❌ Ein Fehler ist aufgetreten.', getReplyOptions(ctx));
|
||
await sessions.clearChat(ctx, chatId);
|
||
}
|
||
});
|
||
|
||
// --- CALLBACK HANDLER ---
|
||
|
||
bot.on('callback_query', async (ctx) => {
|
||
const data = ctx.callbackQuery.data;
|
||
const userId = ctx.from.id;
|
||
|
||
if (data.startsWith('category_')) {
|
||
const category = CATEGORIES[data];
|
||
const chatId = ctx.chat?.id.toString();
|
||
if (category && chatId) {
|
||
const session = sessions.get(chatId) || {};
|
||
if (session.uiMessageId) await deleteMessageSafe(ctx, session.uiMessageId);
|
||
|
||
const msg = await ctx.reply(
|
||
`Kategorie: *${category}*\n\nBitte sende mir einen Link (Cover/Spotify) oder "x":`,
|
||
{ ...getReplyOptions(ctx), parse_mode: 'Markdown', disable_notification: true }
|
||
);
|
||
|
||
sessions.set(chatId, {
|
||
...session,
|
||
category,
|
||
waitingFor: 'link',
|
||
uiMessageId: msg.message_id
|
||
});
|
||
}
|
||
await ctx.answerCbQuery();
|
||
return;
|
||
}
|
||
|
||
if (data.startsWith('page_')) {
|
||
if (!CONFIG.adminIds.includes(userId.toString())) return ctx.answerCbQuery('Keine Berechtigung');
|
||
const page = parseInt(data.split('_')[1]);
|
||
await sendNotFoundPage(ctx, page);
|
||
await ctx.answerCbQuery();
|
||
return;
|
||
}
|
||
|
||
if (data === 'ignore') {
|
||
await ctx.answerCbQuery();
|
||
return;
|
||
}
|
||
|
||
if (data.startsWith('del_')) {
|
||
if (!CONFIG.adminIds.includes(userId.toString())) return ctx.answerCbQuery('Keine Berechtigung');
|
||
const parts = data.split('_');
|
||
const indexToDelete = parseInt(parts[1]);
|
||
const currentPage = parseInt(parts[2]);
|
||
|
||
const success = await NotFoundManager.delete(indexToDelete);
|
||
if (success) {
|
||
await ctx.answerCbQuery(`Eintrag #${indexToDelete + 1} gelöscht`);
|
||
await sendNotFoundPage(ctx, currentPage);
|
||
} else {
|
||
await ctx.answerCbQuery('Fehler beim Löschen');
|
||
}
|
||
return;
|
||
}
|
||
|
||
if (data === 'wish_fulfilled' || data === 'wish_not_found') {
|
||
if (!CONFIG.adminIds.includes(userId.toString())) return ctx.answerCbQuery('Keine Berechtigung');
|
||
|
||
if (data === 'wish_fulfilled') {
|
||
await ctx.editMessageText('✨ *Wunsch wurde erfüllt!* ✨\n🎉 Der Inhalt ist verfügbar.', { parse_mode: 'Markdown' });
|
||
} else if (data === 'wish_not_found') {
|
||
const msgText = ctx.callbackQuery.message.text;
|
||
const wishTitleMatch = msgText.match(/🔸 Titel: (.*)/);
|
||
const title = wishTitleMatch ? wishTitleMatch[1] : 'Unbekannt';
|
||
const catMatch = msgText.match(/🔹 Kategorie: (.*)/);
|
||
const category = catMatch ? catMatch[1] : 'Allgemein';
|
||
|
||
await NotFoundManager.add(title, category);
|
||
await ctx.editMessageText(`☹️ Zu "${title}" wurde nichts gefunden.\nAuf die "Not Found" Liste gesetzt.`);
|
||
}
|
||
await ctx.answerCbQuery();
|
||
return;
|
||
}
|
||
});
|
||
|
||
bot.catch((err, ctx) => {
|
||
Logger.error(err);
|
||
console.error(`Error for ${ctx.updateType}`, err);
|
||
});
|
||
|
||
// ==================== EXPRESS ROUTES ====================
|
||
|
||
app.post('/api/sendWish', async (req, res) => {
|
||
const { category, link, title } = req.body;
|
||
if (!category || !title) return res.status(400).json({ error: 'Kategorie und Titel sind erforderlich.' });
|
||
try {
|
||
await sendWishToGroup(title, category, link || null);
|
||
res.status(200).json({ success: true });
|
||
} catch (error) {
|
||
Logger.error(error);
|
||
res.status(500).json({ error: 'Interner Serverfehler' });
|
||
}
|
||
});
|
||
|
||
// ==================== INIT & START ====================
|
||
|
||
async function init() {
|
||
try {
|
||
await fs.mkdir(CONFIG.logDir, { recursive: true });
|
||
const files = [PATHS.errorLog, PATHS.wishLog, PATHS.notFoundLog];
|
||
for (const file of files) {
|
||
try { await fs.access(file); } catch { await fs.writeFile(file, file.endsWith('.json') ? '[]' : ''); }
|
||
}
|
||
} catch (error) {
|
||
console.error('Init Error:', error);
|
||
process.exit(1);
|
||
}
|
||
|
||
schedule.scheduleJob('0 0 * * *', async () => {
|
||
const dateStr = new Date().toISOString().split('T')[0];
|
||
const zipPath = path.join(CONFIG.logDir, `logs_${dateStr}.zip`);
|
||
const output = require('fs').createWriteStream(zipPath);
|
||
const archive = archiver('zip', { zlib: { level: 9 } });
|
||
|
||
output.on('close', async () => {
|
||
console.log(`Logs archiviert: ${zipPath}`);
|
||
try {
|
||
const files = await fs.readdir(CONFIG.logDir);
|
||
const zips = files.filter(f => f.endsWith('.zip')).sort();
|
||
for (let i = 0; i < zips.length - 4; i++) {
|
||
await fs.unlink(path.join(CONFIG.logDir, zips[i]));
|
||
}
|
||
} catch (err) { Logger.error(err); }
|
||
});
|
||
|
||
archive.on('error', (err) => Logger.error(err));
|
||
archive.pipe(output);
|
||
|
||
[PATHS.errorLog, PATHS.wishLog].forEach(async (filePath) => {
|
||
try { await fs.access(filePath); archive.file(filePath, { name: path.basename(filePath) }); } catch (e) {}
|
||
});
|
||
|
||
archive.finalize();
|
||
});
|
||
|
||
app.listen(CONFIG.port, () => console.log(`🌐 Server läuft auf Port ${CONFIG.port}`));
|
||
await bot.launch();
|
||
console.log('✅ Bot gestartet');
|
||
|
||
process.once('SIGINT', () => bot.stop('SIGINT'));
|
||
process.once('SIGTERM', () => bot.stop('SIGTERM'));
|
||
}
|
||
|
||
init(); |