const express = require('express'); const session = require('express-session'); const sqlite3 = require('sqlite3').verbose(); const path = require('path'); const fs = require('fs'); const AnsiToHtml = require('ansi-to-html'); const convert = new AnsiToHtml(); const { exec } = require('child_process'); const os = require('os'); // Import de la configuration depuis config.js const config = require('./config'); const app = express(); const port = config.port; // Middleware pour parser les formulaires POST app.use(express.urlencoded({ extended: true })); // Configuration des sessions en utilisant le secret depuis la config app.use(session({ secret: config.sessionSecret, resave: false, saveUninitialized: false })); // Servir des fichiers statiques (CSS, images, etc.) app.use(express.static('public')); // Chemin vers la base SQLite défini dans la config const DB_FILE = config.dbFile; // Création du routeur pour /autopost const autopostRouter = express.Router(); /* ------------------------------------------------------------------------- Routes non protégées (login, logout) sous /autopost ------------------------------------------------------------------------- */ // Affichage du formulaire de login à l'URL /autopost/login autopostRouter.get('/login', (req, res) => { res.send(` Login

Authentification

`); }); // Traitement du formulaire de login en utilisant la config pour vérifier les identifiants autopostRouter.post('/login', (req, res) => { const { username, password } = req.body; if (username === config.auth.username && password === config.auth.password) { req.session.authenticated = true; res.redirect('/autopost'); } else { res.send('Identifiants invalides. Réessayer'); } }); // Déconnexion autopostRouter.get('/logout', (req, res) => { req.session.destroy(); res.redirect('/autopost/login'); }); /* ------------------------------------------------------------------------- Middleware de protection pour les routes suivantes ------------------------------------------------------------------------- */ function checkAuth(req, res, next) { if (req.session && req.session.authenticated) { next(); } else { res.redirect(req.baseUrl + '/login'); } } autopostRouter.use(checkAuth); /* ------------------------------------------------------------------------- Route GET principale pour /autopost avec pagination ------------------------------------------------------------------------- */ autopostRouter.get('/', (req, res) => { const limit = 100; // enregistrements par page const page = parseInt(req.query.page) || 1; const offset = (page - 1) * limit; let db = new sqlite3.Database(DB_FILE, sqlite3.OPEN_READONLY, (err) => { if (err) { console.error(err.message); return res.status(500).send("Erreur lors de l'ouverture de la base de données."); } }); // Récupérer le nombre total d'enregistrements const countQuery = "SELECT COUNT(*) as total FROM release"; db.get(countQuery, [], (err, countResult) => { if (err) { console.error(err.message); res.status(500).send("Erreur lors du comptage des enregistrements."); return; } const totalRecords = countResult.total; const totalPages = Math.ceil(totalRecords / limit); const query = ` SELECT nom, status, id FROM release ORDER BY (status = 2) DESC, id DESC LIMIT ${limit} OFFSET ${offset}; `; db.all(query, [], (err, rows) => { if (err) { console.error(err.message); res.status(500).send("Erreur lors de la requête."); return; } let html = ` Suivi Autopost

Suivi Autopost

Déconnexion

`; rows.forEach(row => { let statusText = ''; let statusClass = ''; switch (row.status) { case 0: statusText = 'EN ATTENTE'; statusClass = 'bg-cyan-500 text-black font-bold'; break; case 1: statusText = 'ENVOI TERMINÉ'; statusClass = 'bg-green-300 text-black font-bold'; break; case 2: statusText = 'ERREUR'; statusClass = 'bg-red-300 text-black font-bold'; break; case 3: statusText = 'DEJA DISPONIBLE'; statusClass = 'bg-pink-300 text-black font-bold'; break; default: statusText = 'INCONNU'; } let logLink = (parseInt(row.status) === 1 || parseInt(row.status) === 2) ? ' | Log' : ''; let mediainfoLink = (parseInt(row.status) === 0 || parseInt(row.status) === 1 || parseInt(row.status) === 2) ? ' | Mediainfo' : ''; let dlLink = row.status === 1 ? ` | DL` : ''; html += ` `; }); html += `
Name Status ID Actions
${row.nom} ${statusText} ${row.id} Editer | Supprimer ${logLink} ${mediainfoLink} ${dlLink}
`; res.send(html); db.close(); }); }); }); /* ------------------------------------------------------------------------- Route GET pour la recherche côté serveur ------------------------------------------------------------------------- */ autopostRouter.get('/search', (req, res) => { const q = req.query.q || ""; const searchQuery = "%" + q + "%"; let db = new sqlite3.Database(DB_FILE, sqlite3.OPEN_READONLY, (err) => { if (err) { console.error(err.message); return res.status(500).json({ error: "Erreur lors de l'ouverture de la base de données." }); } }); db.all("SELECT nom, status, id FROM release WHERE nom LIKE ? ORDER BY id DESC LIMIT 500", [searchQuery], (err, rows) => { if (err) { console.error(err.message); res.status(500).json({ error: "Erreur lors de la requête." }); return; } res.json(rows); db.close(); }); }); /* ------------------------------------------------------------------------- Route GET pour récupérer le contenu d'un fichier log ------------------------------------------------------------------------- */ autopostRouter.get('/log', (req, res) => { const filename = req.query.name; if (!filename) { return res.status(400).json({ error: "Nom du fichier non spécifié." }); } let base = filename.includes('.') ? filename.split('.').slice(0, -1).join('.') : filename; const logFilePath = path.join(config.logdirectory, base + '.log'); fs.readFile(logFilePath, 'utf8', (err, data) => { if (err) { console.error(err.message); return res.status(500).json({ error: "Erreur lors du chargement du fichier log." }); } const htmlContent = convert.toHtml(data); res.json({ content: htmlContent }); }); }); /* ------------------------------------------------------------------------- Route GET pour récupérer le contenu d'un fichier mediainfo ------------------------------------------------------------------------- */ autopostRouter.get('/mediainfo', (req, res) => { const filename = req.query.name; if (!filename) { return res.status(400).json({ error: "Nom du fichier non spécifié." }); } let base = filename.includes('.') ? filename.split('.').slice(0, -1).join('.') : filename; const mediainfoFilePath = path.join(config.infodirectory, base + '.json'); fs.readFile(mediainfoFilePath, 'utf8', (err, data) => { if (err) { console.error(err.message); return res.status(500).json({ error: "Erreur lors du chargement du fichier mediainfo." }); } try { const obj = JSON.parse(data); const pretty = JSON.stringify(obj, null, 2); res.json({ content: pretty }); } catch(e) { res.json({ content: data }); } }); }); /* ------------------------------------------------------------------------- Route GET pour télécharger le fichier NZB ------------------------------------------------------------------------- */ autopostRouter.get('/dl', (req, res) => { const filename = req.query.name; if (!filename) { return res.status(400).send("Nom du fichier non spécifié."); } // Extraire le nom de base sans extension let base = filename.includes('.') ? filename.split('.').slice(0, -1).join('.') : filename; const subfolder = base.charAt(0).toUpperCase(); // L'archive est en .7z maintenant const archiveFilePath = path.join(config.finishdirectory, subfolder, base + '.7z'); console.log("Tentative de téléchargement (archive 7z) :", archiveFilePath); // Utilisation du répertoire temporaire const tmpDir = os.tmpdir(); // Le fichier extrait attendu (sans dossier interne grâce à "7z e") const extractedFilePath = path.join(tmpDir, base + '.nzb'); // Utiliser "7z e" pour extraire sans recréer la structure de dossiers const command = `7z e "${archiveFilePath}" -o"${tmpDir}" -y`; exec(command, (error, stdout, stderr) => { if (error) { console.error(`Erreur lors de la décompression: ${error.message}`); return res.status(500).send("Erreur lors de la décompression."); } // On vérifie que le fichier extrait existe res.download(extractedFilePath, base + '.nzb', (err) => { if (err) { console.error("Erreur lors du téléchargement :", err); res.status(500).send("Erreur lors du téléchargement."); } // Suppression du fichier temporaire après téléchargement (optionnel) fs.unlink(extractedFilePath, (err) => { if (err) { console.error("Erreur lors de la suppression du fichier temporaire :", err); } }); }); }); }); /* ------------------------------------------------------------------------- Route POST pour mettre à jour le statut (édition) ------------------------------------------------------------------------- */ autopostRouter.post('/edit/:id', (req, res) => { const id = req.params.id; const newStatus = req.body.status; let db = new sqlite3.Database(DB_FILE, sqlite3.OPEN_READWRITE, (err) => { if (err) { console.error(err.message); return res.status(500).send("Erreur lors de l'ouverture de la base de données."); } }); db.run("UPDATE release SET status = ? WHERE id = ?", [newStatus, id], function(err) { if (err) { console.error(err.message); res.status(500).send("Erreur lors de la mise à jour."); return; } if (req.xhr || req.headers.accept.indexOf('json') > -1) { res.json({ success: true }); } else { res.redirect("/autopost/"); } db.close(); }); }); /* ------------------------------------------------------------------------- Route POST pour supprimer un enregistrement ------------------------------------------------------------------------- */ autopostRouter.post('/delete/:id', (req, res) => { const id = req.params.id; let db = new sqlite3.Database(DB_FILE, sqlite3.OPEN_READWRITE, (err) => { if (err) { console.error(err.message); return res.status(500).send("Erreur lors de l'ouverture de la base de données."); } }); db.run("DELETE FROM release WHERE id = ?", [id], function(err) { if (err) { console.error(err.message); res.status(500).send("Erreur lors de la suppression."); return; } if (req.xhr || req.headers.accept.indexOf('json') > -1) { res.json({ success: true }); } else { res.redirect("/autopost/"); } db.close(); }); }); // Redirection de la route GET d'édition si accès direct autopostRouter.get('/edit/:id', (req, res) => { res.redirect("/autopost/"); }); // Monter le routeur sur le chemin /autopost app.use('/autopost', autopostRouter); app.listen(port, () => { console.log(`Serveur démarré sur http://localhost:${port}/autopost`); });