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(); |