UNPKG

discord-media-server

Version:

Self-hosted Discord bot for scanning and serving information on local media files.

421 lines (331 loc) 12.3 kB
// server.js (ES module version with database.js integration) import express from 'express'; import path from 'path'; import fs from 'fs'; import { getBotStatus } from './botStatus.js'; import { resetMovieTable } from './reset.js'; import { CONFIG_DIR, DATA_DIR } from './setupConfig.js'; import dotenv from 'dotenv'; dotenv.config(); let botRunning = false; let botInstance = null; const MEDIA_DIR = process.env.MEDIA_DIR; const DB_NAME = process.env.DB_NAME || 'media'; if (!MEDIA_DIR) { console.error('MEDIA_DIR is not set in .env'); process.exit(1); } import { fileURLToPath } from 'url'; import { dirname } from 'path'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); import { initDatabase, query, close } from './database.js'; await initDatabase(); // Ensure DB is connected const app = express(); const PORT = process.env.PORT || 8080; const DB_PATH = path.join(DATA_DIR, `${DB_NAME}.json`); let mediaIndex = []; function getStuff(text) { document.getElementById('status').innerHTML = `<p>the word is ${text}</p>`; } function loadIndex() { if (fs.existsSync(DB_PATH)) { mediaIndex = JSON.parse(fs.readFileSync(DB_PATH)); console.log(`Loaded ${mediaIndex.length} entries from ${DB_NAME}.json`); } else { mediaIndex = []; console.warn(`${DB_NAME}.json not found — running empty.`); } } loadIndex(); // Serve static files from mediaDirectory (movies and posters) app.use('/movies', express.static(MEDIA_DIR)); app.use(express.static(path.join(__dirname, 'public'))); // ✅ This line serves /public app.use(express.urlencoded({ extended: true })); app.use(express.json()); app.use(express.static('public')); app.get('/', (req, res) => { // Redirect to the /setup route //res.redirect('/setup'); const env = process.env; let data=''; let response=''; res.send(` <h1>Discord Media Server</h1> <form method="POST" action="/edit"> <label><b>Discord Token:</b> ${env.DISCORD_TOKEN || ''}</label><br> <label>TMDb API Key: ${env.TMDB_API_KEY || ''}</label><br> <label>Media Directory: ${env.MEDIA_DIR || ''}</label><br> <label>DB Type: ${env.DB_TYPE || ''}</label><br> <label>DB Name: ${env.DB_NAME || ''}</label><br> <button type="submit">Edit</button> </form> <form action="/launch" method="get"> <button type="submit" id='getBot' name='startBot' value=${botRunning ? 'reset' : 'start'}>${botRunning ? 'Restart Bot' : 'Turn on Bot'}</button> </form> <button type="submit" id='startBot' value=${botRunning ? 'reset' : 'start'}>${botRunning ? 'Restart' : 'Off'}</button> <div id='status'></div> <script> document.getElementById('startBot').addEventListener('click', async () => { try { response = await fetch('/launch'); // Make an AJAX request to your Node.js endpoint data = await response.json(); // Assuming your server sends JSON // Update a div with new data document.getElementById('status').innerHTML = '<p>${data.message}</p>'; // Optionally, update the button's state document.getElementById('getBot').textContent = 'Updated!'; } catch (error) { console.error('Error fetching data:', error); } }); </script> <p id="botStatus">Checking bot status...</p> <script> document.getElementById('botStatus').innerText = ${botRunning} ? 'Bot is running' : 'Bot is NOT running'; </script> `); }); app.get('/setup', (req, res) => { res.redirect('/'); }); app.post('/edit', (req, res) => { const env = process.env; res.send(` <h1>Discord Media Server Setup</h1> <form method="POST" action="/submit"> <label>Discord Token: <input name="discordToken" value="${env.DISCORD_TOKEN || ''}" required></label><br> <label>TMDb API Key: <input name="tmdbKey" value="${env.TMDB_API_KEY || ''}"></label><br> <label>Media Directory: <input name="mediaDirectory" value="${env.MEDIA_DIR || ''}" required></label><br> <label>DB Type: <select name="dbType"> <option value="sqlite" ${env.DB_TYPE === 'sqlite' ? 'selected' : ''}>SQLite</option> <option value="mysql" ${env.DB_TYPE === 'mysql' ? 'selected' : ''}>MySQL</option> </select> </label><br> <label>DB Name: <input name="dbName" value="${env.DB_NAME || ''}"></label><br> <div id="mysqlFields" style="display:${env.DB_TYPE === 'mysql' ? 'block' : 'none'};"> <label>DB Host: <input name="dbHost" value="${env.DB_HOST || ''}"></label><br> <label>DB User: <input name="dbUser" value="${env.DB_USER || ''}"></label><br> <label>DB Pass: <input name="dbPass" value="${env.DB_PASS || ''}"></label><br> </div> <button type="submit">Save</button> </form> <form action="/launch" method="post"> <button type="submit">Start Server & Bot</button> </form> <script> const dbTypeSelect = document.querySelector('select[name="dbType"]'); const mysqlFields = document.getElementById('mysqlFields'); dbTypeSelect.addEventListener('change', () => { mysqlFields.style.display = dbTypeSelect.value === 'mysql' ? 'block' : 'none'; }); </script> `); }); app.get('/reset-db', async (req, res) => { try { await resetMovieTable(); res.status(200).send('Movie_Info table reset successfully'); } catch (err) { res.status(500).send('Error resetting DB: ' + err.message); } }); app.get('/bot-status', (req, res) => { const status = getBotStatus(); res.json({ running: status }); }); app.get('/dashboard', (req, res) => { res.sendFile(path.join(__dirname, '../public', 'dashboard.html')); }); // --- Web UI --- app.get('/dashboard/media', (req, res) => { const recent = [...mediaIndex] .sort((a, b) => new Date(b.added) - new Date(a.added)) .slice(0, 20); //res.render('media-dashboard', { media: recent, total: mediaIndex.length }); res.json({ media: recent, total: mediaIndex.length }); }); app.get('/dashboard/movies', (req, res) => { const { title, year, format, imdb } = req.query; let results = mediaIndex; if (title) { results = results.filter(m => m.title?.toLowerCase().includes(title.toLowerCase()) ); } if (year) { results = results.filter(m => String(m.year) === String(year)); } if (format) { results = results.filter(m => m.format?.toLowerCase() === format.toLowerCase()); } if (imdb) { results = results.filter(m => m.imdb?.toLowerCase().includes(imdb.toLowerCase())); } res.json({ total: results.length, results }); }); app.get('/dashboard/search', async (req, res) => { const { title, year, format, imdb } = req.query; let sql = 'SELECT * FROM Movie_Info'; let conditions = []; let params = []; if (title) { conditions.push('title LIKE ?'); params.push(`%${title}%`); } if (year) { conditions.push('year = ?'); params.push(year); } if (format) { conditions.push('format like ?'); params.push(`%${format}%`); } if (imdb) { conditions.push('imdb LIKE ?'); params.push(`%${imdb}%`); } if (conditions.length) { sql += ' WHERE ' + conditions.join(' AND '); } sql += ' ORDER BY added_at DESC LIMIT 100'; try { const results = await query(sql, params); res.json({ count: results.length, results }); } catch (err) { console.error('SQL Query Failed:', err.message); res.status(500).json({ error: 'Query failed' }); } }); // Search movies app.get('/api/search', async (req, res) => { const { title = '', year = '' } = req.query; let sql = 'SELECT * FROM Movie_Info WHERE 1=1'; const args = []; if (title) { sql += ' AND title LIKE ?'; args.push(`%${title}%`); } if (year) { sql += ' AND year = ?'; args.push(year); } sql += ' ORDER BY title ASC LIMIT 25'; try { const rows = await query(sql, args); const results = rows.map(movie => { const fullPosterPath = path.join(MEDIA_DIR, movie.poster || ''); const posterURL = fs.existsSync(fullPosterPath) ? `/movies/${encodeURI(movie.poster)}` : movie.poster_fallback || null; return { ...movie, posterURL }; }); res.json(results); } catch (err) { console.error('DB search error:', err); res.status(500).json({ error: 'Database error' }); } }); // Return one random movie app.get('/api/random', async (req, res) => { try { const rows = await query('SELECT * FROM Movie_Info ORDER BY RANDOM() LIMIT 1'); if (!rows.length) return res.status(404).json({ error: 'No movies found' }); const movie = rows[0]; const fullPosterPath = path.join(MEDIA_DIR, movie.poster || ''); const posterURL = fs.existsSync(fullPosterPath) ? `/movies/${encodeURI(movie.poster)}` : movie.poster_fallback || null; res.json({ ...movie, posterURL }); } catch (err) { console.error('DB random fetch error:', err); res.status(500).json({ error: 'Database error' }); } }); let cachedConfig = null; app.get('/api/config', (req, res) => { if (!cachedConfig) { cachedConfig = { TMDB_API_KEY: process.env.TMDB_API_KEY || '', MEDIA_DIR: process.env.MEDIA_DIR || '', DB_TYPE: process.env.DB_TYPE || '', DB_NAME: process.env.DB_NAME || '' }; } res.json(cachedConfig); }); import { spawn } from 'child_process'; app.get('/launch', async (req, res) => { const buttonValue = req.query.startBot; // Access the value using the button's name console.log('Button value received:', buttonValue); //res.send(`You clicked the button with value: ${buttonValue}`); if (botRunning) { return res.status(200).send('Bot is already running.'); } try { console.log('Starting Discord bot...'); // Option 1: Run as child process botInstance = spawn('node', ['lib/bot.js'], { stdio: 'inherit', env: process.env, // pass current .env variables }); botRunning = true; // Handle unexpected exit botInstance.on('exit', (code) => { console.log(`Bot process exited with code ${code}`); botRunning = false; }); res.json({ message: 'Bot started successfully.'}); } catch (err) { console.error('Failed to start bot:', err); res.status(500).send('Failed to start bot.'); } }); // this is to edit the current values app.post('/submit', async (req, res) => { const envPath = path.join(CONFIG_DIR, '.env'); // 1. Load existing .env content if it exists let existingEnv = {}; if (fs.existsSync(envPath)) { const envLines = fs.readFileSync(envPath, 'utf-8').split('\n'); for (const line of envLines) { const [key, ...valParts] = line.split('='); if (key) { existingEnv[key.trim()] = valParts.join('=').trim(); } } } // 2. Build new environment variables from form const newEnv = { MEDIA_DIR: req.body.mediaDirectory, DISCORD_CLIENT_ID: req.body.discordClientId, TMDB_API_KEY: req.body.tmdbKey, DB_TYPE: req.body.dbType, DB_NAME: req.body.dbName }; // 3. Merge old with new (new values overwrite old) const mergedEnv = { ...existingEnv, ...newEnv }; // 4. Write merged values back to .env const output = Object.entries(mergedEnv) .map(([key, val]) => `${key}=${val}`) .join('\n'); fs.writeFileSync(envPath, output); console.log(`.env updated at: ${envPath}`); cachedConfig = null; res.redirect('/'); }); // Graceful shutdown process.on('SIGINT', async () => { await close(); console.log('\nServer shutting down.'); process.exit(0); }); // Start server app.listen(PORT, () => { console.log(`\nMedia server running at http://localhost:${PORT}`); console.log(`Serving media from: ${MEDIA_DIR}`); });