const express = require('express'); const session = require('express-session'); 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'); const config = require('./config'); const db = require('./db'); db.testConnection(); // vérification au démarrage const app = express(); const port = config.port; // Middleware pour parser les formulaires POST app.use(express.urlencoded({ extended: true })); app.use(session({ secret: config.sessionSecret, resave: false, saveUninitialized: false })); app.use(express.static('public')); const autopostRouter = express.Router(); autopostRouter.use('/js', express.static(path.join(__dirname, '../node_modules/@tailwindcss/browser/dist'))); autopostRouter.use('/jquery', express.static(path.join(__dirname, '../node_modules/jquery/dist'))); // --------------------------- Auth non protégée ----------------------------- autopostRouter.get('/login', (req, res) => { res.send(` Login

Authentification

`); }); 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'); } }); autopostRouter.get('/logout', (req, res) => { req.session.destroy(); res.redirect('/autopost/login'); }); function checkAuth(req, res, next) { if (req.session && req.session.authenticated) { next(); } else { res.redirect(req.baseUrl + '/login'); } } autopostRouter.use(checkAuth); // --------------------------- PAGE PRINCIPALE ----------------------------- autopostRouter.get('/', async (req, res) => { const limit = 100; // enregistrements par page const page = parseInt(req.query.page) || 1; const offset = (page - 1) * limit; try { // Récupérer le nombre total d'enregistrements const [countResult] = await db.query("SELECT COUNT(*) as total FROM `release`"); const totalRecords = countResult[0].total; const totalPages = Math.ceil(totalRecords / limit); const [rows] = await db.query( "SELECT nom, status, id FROM `release` ORDER BY (status = 2) DESC, id DESC LIMIT ? OFFSET ?", [limit, offset] ); let html = ` Suivi Autopost

Suivi Autopost

Déconnexion

`; rows.forEach(row => { let statusText = ''; let statusClass = ''; switch (parseInt(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/BDInfo' : ''; 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); } catch (err) { console.error(err.message); res.status(500).send("Erreur lors de la requête DB."); } }); // --------------------------- Recherche ----------------------------- autopostRouter.get('/search', async (req, res) => { const q = req.query.q || ""; const searchQuery = "%" + q + "%"; try { const [rows] = await db.query( "SELECT nom, status, id FROM `release` WHERE nom LIKE ? ORDER BY id DESC LIMIT 500", [searchQuery] ); res.json(rows); } catch (err) { console.error(err.message); res.status(500).json({ error: "Erreur lors de la requête." }); } }); // --------------------------- 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 }); }); }); // --------------------------- 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é." }); } const base = filename.includes('.') ? filename.split('.').slice(0, -1).join('.') : filename; const jsonPath = path.join(config.infodirectory, base + '.json'); const bdinfoPath = path.join(config.infodirectory, base + '.bdinfo.txt'); // Vérifie lequel des deux existe, priorité au .json fs.access(jsonPath, fs.constants.F_OK, (errJson) => { if (!errJson) { // .json existe readAndSend(jsonPath); } else { // .json n'existe pas, on essaie .bdinfo.txt fs.access(bdinfoPath, fs.constants.F_OK, (errBdinfo) => { if (!errBdinfo) { readAndSend(bdinfoPath); } else { res.status(404).json({ error: "Aucun fichier mediainfo trouvé." }); } }); } }); function readAndSend(filePath) { fs.readFile(filePath, 'utf8', (err, data) => { if (err) { console.error(err.message); return res.status(500).json({ error: "Erreur lors du chargement du fichier mediainfo." }); } // Essaie de prettifier si JSON try { const obj = JSON.parse(data); const pretty = JSON.stringify(obj, null, 2); res.json({ content: pretty }); } catch (e) { res.json({ content: data }); } }); } }); // --------------------------- Download ----------------------------- 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 = path.join(__dirname, 'tmp'); // 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); } }); }); }); }); // --------------------------- Édition ----------------------------- autopostRouter.post('/edit/:id', async (req, res) => { const id = req.params.id; const newStatus = req.body.status; try { await db.query("UPDATE `release` SET status = ? WHERE id = ?", [newStatus, id]); if (req.xhr || req.headers.accept.indexOf('json') > -1) { res.json({ success: true }); } else { res.redirect("/autopost/"); } } catch (err) { console.error(err.message); res.status(500).send("Erreur lors de la mise à jour."); } }); // --------------------------- Suppression ----------------------------- autopostRouter.post('/delete/:id', async (req, res) => { const id = req.params.id; try { await db.query("DELETE FROM `release` WHERE id = ?", [id]); if (req.xhr || req.headers.accept.indexOf('json') > -1) { res.json({ success: true }); } else { res.redirect("/autopost/"); } } catch (err) { console.error(err.message); res.status(500).send("Erreur lors de la suppression."); } }); // Redirection accès direct GET /edit/:id autopostRouter.get('/edit/:id', (req, res) => { res.redirect("/autopost/"); }); app.use('/autopost', autopostRouter); app.listen(port, () => { console.log(`Serveur démarré sur http://localhost:${port}/autopost`); });