require('dotenv').config({ path: __dirname + '/.env' }); const express = require('express'); const cors = require('cors'); const app = express(); const port = process.env.PORT || 3001; app.use(cors()); app.use(express.json()); const axios = require('axios'); app.get('/auth/discord', (req, res) => { const url = `https://discord.com/api/oauth2/authorize?client_id=${process.env.DISCORD_CLIENT_ID}&redirect_uri=${encodeURIComponent('http://localhost:3002/auth/discord/callback')}&response_type=code&scope=identify%20guilds`; res.redirect(url); }); app.get('/auth/discord/callback', async (req, res) => { const code = req.query.code; if (!code) { return res.status(400).send('No code provided'); } try { const params = new URLSearchParams(); params.append('client_id', process.env.DISCORD_CLIENT_ID); params.append('client_secret', process.env.DISCORD_CLIENT_SECRET); params.append('grant_type', 'authorization_code'); params.append('code', code); params.append('redirect_uri', 'http://localhost:3002/auth/discord/callback'); const response = await axios.post('https://discord.com/api/oauth2/token', params, { headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, }); const { access_token } = response.data; const userResponse = await axios.get('https://discord.com/api/users/@me', { headers: { Authorization: `Bearer ${access_token}`, }, }); const guildsResponse = await axios.get('https://discord.com/api/users/@me/guilds', { headers: { Authorization: `Bearer ${access_token}`, }, }); const adminGuilds = guildsResponse.data.filter(guild => (guild.permissions & 0x8) === 0x8); const user = userResponse.data; const db = readDb(); user.theme = db.users && db.users[user.id] ? db.users[user.id].theme : 'light'; const guilds = adminGuilds; res.redirect(`http://localhost:3000/dashboard?user=${encodeURIComponent(JSON.stringify(user))}&guilds=${encodeURIComponent(JSON.stringify(guilds))}`); } catch (error) { console.error('Error during Discord OAuth2 callback:', error); res.status(500).send('Internal Server Error'); } }); const { readDb, writeDb } = require('./db'); app.get('/api/servers/:guildId/settings', (req, res) => { const { guildId } = req.params; const db = readDb(); const settings = db[guildId] || { pingCommand: false }; res.json(settings); }); app.post('/api/servers/:guildId/settings', (req, res) => { const { guildId } = req.params; const newSettings = req.body || {}; const db = readDb(); if (!db[guildId]) db[guildId] = {}; // Merge incoming settings with existing settings to avoid overwriting unrelated keys db[guildId] = { ...db[guildId], ...newSettings }; writeDb(db); res.json({ success: true }); }); // Toggle a single command for a guild (preserves other toggles) app.post('/api/servers/:guildId/commands/:cmdName/toggle', (req, res) => { const { guildId, cmdName } = req.params; const { enabled } = req.body; // boolean const protectedCommands = ['help', 'manage-commands']; if (protectedCommands.includes(cmdName)) { return res.status(403).json({ success: false, message: 'This command is locked and cannot be toggled.' }); } const db = readDb(); if (!db[guildId]) db[guildId] = {}; if (!db[guildId].commandToggles) db[guildId].commandToggles = {}; if (typeof enabled === 'boolean') { db[guildId].commandToggles[cmdName] = enabled; writeDb(db); return res.json({ success: true, cmdName, enabled }); } return res.status(400).json({ success: false, message: 'Missing or invalid "enabled" boolean in request body' }); }); app.get('/api/servers/:guildId/bot-status', (req, res) => { const { guildId } = req.params; const guild = bot.client.guilds.cache.get(guildId); if (guild) { res.json({ isBotInServer: true }); } else { res.json({ isBotInServer: false }); } }); app.get('/api/client-id', (req, res) => { res.json({ clientId: process.env.DISCORD_CLIENT_ID }); }); app.post('/api/user/theme', (req, res) => { const { userId, theme } = req.body; const db = readDb(); if (!db.users) { db.users = {}; } if (!db.users[userId]) { db.users[userId] = {}; } db.users[userId].theme = theme; writeDb(db); res.json({ success: true }); }); app.post('/api/servers/:guildId/leave', async (req, res) => { const { guildId } = req.params; try { const guild = await bot.client.guilds.fetch(guildId); if (guild) { await guild.leave(); res.json({ success: true }); } else { res.status(404).json({ success: false, message: 'Bot is not in the specified server' }); } } catch (error) { console.error('Error leaving server:', error); res.status(500).json({ success: false, message: 'Internal Server Error' }); } }); app.get('/api/servers/:guildId/channels', async (req, res) => { const { guildId } = req.params; const guild = bot.client.guilds.cache.get(guildId); if (!guild) { return res.json([]); } try { const channels = await guild.channels.fetch(); const textChannels = channels.filter(channel => channel.type === 0).map(channel => ({ id: channel.id, name: channel.name })); res.json(textChannels); } catch (error) { console.error('Error fetching channels:', error); res.status(500).json({ success: false, message: 'Internal Server Error' }); } }); app.get('/api/servers/:guildId/welcome-leave-settings', (req, res) => { const { guildId } = req.params; const db = readDb(); const settings = db[guildId] || {}; const welcomeLeaveSettings = { welcome: { enabled: settings.welcomeEnabled || false, channel: settings.welcomeChannel || '', message: settings.welcomeMessage || 'Welcome to the server, {user}!', customMessage: settings.welcomeCustomMessage || '', }, leave: { enabled: settings.leaveEnabled || false, channel: settings.leaveChannel || '', message: settings.leaveMessage || '{user} has left the server.', customMessage: settings.leaveCustomMessage || '', }, }; res.json(welcomeLeaveSettings); }); app.post('/api/servers/:guildId/welcome-leave-settings', (req, res) => { const { guildId } = req.params; const newSettings = req.body; const db = readDb(); if (!db[guildId]) { db[guildId] = {}; } db[guildId].welcomeEnabled = newSettings.welcome.enabled; db[guildId].welcomeChannel = newSettings.welcome.channel; db[guildId].welcomeMessage = newSettings.welcome.message; db[guildId].welcomeCustomMessage = newSettings.welcome.customMessage; db[guildId].leaveEnabled = newSettings.leave.enabled; db[guildId].leaveChannel = newSettings.leave.channel; db[guildId].leaveMessage = newSettings.leave.message; db[guildId].leaveCustomMessage = newSettings.leave.customMessage; writeDb(db); res.json({ success: true }); }); app.get('/api/servers/:guildId/roles', async (req, res) => { const { guildId } = req.params; const guild = bot.client.guilds.cache.get(guildId); if (!guild) { return res.json([]); } try { const rolesCollection = await guild.roles.fetch(); // Exclude @everyone (role.id === guild.id), exclude managed roles, and only include roles below the bot's highest role const botHighest = guild.members.me.roles.highest.position; const manageable = rolesCollection .filter(role => role.id !== guild.id && !role.managed && role.position < botHighest) .sort((a, b) => b.position - a.position) .map(role => ({ id: role.id, name: role.name, color: role.hexColor })); res.json(manageable); } catch (error) { console.error('Error fetching roles:', error); res.status(500).json({ success: false, message: 'Internal Server Error' }); } }); app.get('/api/servers/:guildId/autorole-settings', (req, res) => { const { guildId } = req.params; const db = readDb(); const settings = db[guildId] || {}; const autoroleSettings = settings.autorole || { enabled: false, roleId: '' }; res.json(autoroleSettings); }); app.post('/api/servers/:guildId/autorole-settings', (req, res) => { const { guildId } = req.params; const { enabled, roleId } = req.body; const db = readDb(); if (!db[guildId]) { db[guildId] = {}; } db[guildId].autorole = { enabled, roleId }; writeDb(db); res.json({ success: true }); }); app.get('/', (req, res) => { res.send('Hello from the backend!'); }); // Return list of bot commands and per-guild enabled/disabled status app.get('/api/servers/:guildId/commands', (req, res) => { try { const { guildId } = req.params; const db = readDb(); const guildSettings = db[guildId] || {}; const toggles = guildSettings.commandToggles || {}; const protectedCommands = ['manage-commands', 'help']; const commands = Array.from(bot.client.commands.values()).map(cmd => { const isLocked = protectedCommands.includes(cmd.name); const isEnabled = isLocked ? true : (toggles[cmd.name] !== false && cmd.enabled !== false); return { name: cmd.name, description: cmd.description || 'No description.', enabled: isEnabled, locked: isLocked, hasSlashBuilder: !!cmd.builder, }; }); res.json(commands); } catch (error) { console.error('Error returning commands:', error); res.status(500).json({ success: false, message: 'Internal Server Error' }); } }); // INVITES: create, list, delete app.get('/api/servers/:guildId/invites', async (req, res) => { try { const { guildId } = req.params; const db = readDb(); const saved = (db[guildId] && db[guildId].invites) ? db[guildId].invites : []; // try to enrich with live data where possible const guild = bot.client.guilds.cache.get(guildId); let liveInvites = []; if (guild) { try { const fetched = await guild.invites.fetch(); liveInvites = Array.from(fetched.values()); } catch (e) { // ignore fetch errors } } const combined = saved.map(inv => { const live = liveInvites.find(li => li.code === inv.code); return { ...inv, uses: live ? live.uses : inv.uses || 0, maxUses: inv.maxUses || (live ? live.maxUses : 0), maxAge: inv.maxAge || (live ? live.maxAge : 0), }; }); res.json(combined); } catch (error) { console.error('Error listing invites:', error); res.status(500).json({ success: false, message: 'Internal Server Error' }); } }); app.post('/api/servers/:guildId/invites', async (req, res) => { try { const { guildId } = req.params; const { channelId, maxAge, maxUses, temporary } = req.body || {}; const guild = bot.client.guilds.cache.get(guildId); if (!guild) return res.status(404).json({ success: false, message: 'Guild not found' }); let channel = null; if (channelId) { try { channel = await guild.channels.fetch(channelId); } catch (e) { channel = null; } } if (!channel) { // fall back to first text channel const channels = await guild.channels.fetch(); channel = channels.find(c => c.type === 0) || channels.first(); } if (!channel) return res.status(400).json({ success: false, message: 'No channel available to create invite' }); const inviteOptions = { maxAge: typeof maxAge === 'number' ? maxAge : 0, maxUses: typeof maxUses === 'number' ? maxUses : 0, temporary: !!temporary, unique: true, }; const invite = await channel.createInvite(inviteOptions); const db = readDb(); if (!db[guildId]) db[guildId] = {}; if (!db[guildId].invites) db[guildId].invites = []; const item = { code: invite.code, url: invite.url, channelId: channel.id, createdAt: new Date().toISOString(), maxUses: invite.maxUses || inviteOptions.maxUses || 0, maxAge: invite.maxAge || inviteOptions.maxAge || 0, temporary: !!invite.temporary, }; db[guildId].invites.push(item); writeDb(db); res.json({ success: true, invite: item }); } catch (error) { console.error('Error creating invite:', error); res.status(500).json({ success: false, message: 'Internal Server Error' }); } }); app.delete('/api/servers/:guildId/invites/:code', async (req, res) => { try { const { guildId, code } = req.params; const db = readDb(); const guild = bot.client.guilds.cache.get(guildId); // Try to delete on Discord if possible if (guild) { try { // fetch invites and delete matching code const fetched = await guild.invites.fetch(); const inv = fetched.find(i => i.code === code); if (inv) await inv.delete(); } catch (e) { // ignore } } if (db[guildId] && db[guildId].invites) { db[guildId].invites = db[guildId].invites.filter(i => i.code !== code); writeDb(db); } res.json({ success: true }); } catch (error) { console.error('Error deleting invite:', error); res.status(500).json({ success: false, message: 'Internal Server Error' }); } }); const bot = require('../discord-bot'); bot.login(); app.listen(port, () => { console.log(`Server is running on port ${port}`); });