diff --git a/commands/db/balance.js b/commands/db/balance.js index f6d352e..e084aa5 100644 --- a/commands/db/balance.js +++ b/commands/db/balance.js @@ -15,11 +15,9 @@ module.exports = { const dbClient = await connectToDatabase(); const db = dbClient.db('discord'); const collection = db.collection('currency'); - const target = interaction.options.getUser('user') ?? interaction.user; const targetData = await collection.findOne({ userID: target.id }); const balance = targetData?.balance ?? 0; - return interaction.reply({ content: `${target.tag} has **${balance}** coins.`, }); diff --git a/commands/db/earn.js b/commands/db/earn.js index b2ff060..25b3618 100644 --- a/commands/db/earn.js +++ b/commands/db/earn.js @@ -9,12 +9,10 @@ const formatDuration = (ms) => { const hours = Math.floor(totalSeconds / 3600); const minutes = Math.floor((totalSeconds % 3600) / 60); const seconds = totalSeconds % 60; - const parts = []; if (hours) parts.push(`${hours}h`); if (minutes) parts.push(`${minutes}m`); if (!hours && !minutes) parts.push(`${seconds}s`); - return parts.join(' '); }; @@ -28,11 +26,9 @@ module.exports = { const db = dbClient.db('discord'); const collection = db.collection('currency'); const now = new Date(); - const userId = interaction.user.id; const userData = await collection.findOne({ userID: userId }); const lastClaim = userData?.lastClaimedAt ? new Date(userData.lastClaimedAt) : null; - if (lastClaim) { const elapsed = now - lastClaim; if (elapsed < COOLDOWN_MS) { @@ -43,7 +39,6 @@ module.exports = { }); } } - await collection.updateOne( { userID: userId }, { @@ -52,7 +47,6 @@ module.exports = { }, { upsert: true } ); - return interaction.reply({ content: `You earned **${REWARD_AMOUNT}** coins! Come back tomorrow for more.`, }); diff --git a/commands/db/give.js b/commands/db/give.js index f8dca2e..2eab92e 100644 --- a/commands/db/give.js +++ b/commands/db/give.js @@ -22,23 +22,19 @@ module.exports = { const dbClient = await connectToDatabase(); const target = interaction.options.getUser('target'); const amount = interaction.options.getInteger('amount'); - if (target.bot) { return interaction.reply({ content: 'You cannot grant coins to bot accounts.', ephemeral: true, }); } - const db = dbClient.db('discord'); const collection = db.collection('currency'); - await collection.updateOne( { userID: target.id }, { $inc: { balance: amount } }, { upsert: true } ); - return interaction.reply({ content: `You granted **${amount}** coins to ${target.tag}.`, }); diff --git a/commands/db/test.js b/commands/db/test.js index 937f368..ad584a2 100644 --- a/commands/db/test.js +++ b/commands/db/test.js @@ -1,4 +1,3 @@ -// exampleCommand.js const { SlashCommandBuilder } = require('discord.js'); const { connectToDatabase } = require('../../db.js'); @@ -15,14 +14,9 @@ module.exports = { async execute(interaction) { const dbClient = await connectToDatabase(); const message = interaction.options.getString('message'); - - // Choose a DB + Collection const db = dbClient.db('discord'); const collection = db.collection('test'); - - // Example: Insert something await collection.insertOne({ userId: interaction.user.id, message: message }); - await interaction.reply('Successfully inserted your data into MongoDB.'); }, }; diff --git a/commands/moderation/kick.js b/commands/moderation/kick.js index e10c0c4..59fd524 100644 --- a/commands/moderation/kick.js +++ b/commands/moderation/kick.js @@ -30,7 +30,6 @@ module.exports = { ephemeral: true, }); } - await member.kick(reason); await recordModerationAction({ guildId: interaction.guildId, @@ -40,7 +39,6 @@ module.exports = { reason, metadata: {}, }); - await interaction.reply(`Kicked ${user.tag} for: ${reason}`); } catch (error) { console.error(error); diff --git a/dashboardServer.js b/dashboardServer.js index 67b3e54..c094d01 100644 --- a/dashboardServer.js +++ b/dashboardServer.js @@ -1,13 +1,10 @@ const crypto = require('node:crypto'); const fs = require('node:fs'); const path = require('node:path'); - const axios = require('axios'); const express = require('express'); - -const { addWarning, getWarnings } = require('./services/warnings'); -const { getGuildPermissions, updateGuildPermissions } = require('./services/permissions'); - +const {addWarning, getWarnings} = require('./services/warnings'); +const {getGuildPermissions, updateGuildPermissions} = require('./services/permissions'); const DEFAULT_SESSION_TTL_MS = 24 * 60 * 60 * 1000; const PERMISSION_BITS = { ADMINISTRATOR: 0x8n, @@ -50,6 +47,14 @@ const SESSION_TTL_MS = Number.parseInt( 10, ); +console.log('[oauth config]', { + DISCORD_CLIENT_ID_present: Boolean(DISCORD_CLIENT_ID && String(DISCORD_CLIENT_ID).trim()), + DISCORD_CLIENT_SECRET_present: Boolean(DISCORD_CLIENT_SECRET && String(DISCORD_CLIENT_SECRET).trim()), + DISCORD_REDIRECT_URI: DISCORD_REDIRECT_URI || null, + configFilePath: path.join(__dirname, 'config.json'), + configFileExists: fs.existsSync(path.join(__dirname, 'config.json')), +}); + const DISCORD_API_BASE = 'https://discord.com/api'; const sessionStore = new Map(); @@ -91,7 +96,7 @@ const setCookie = (res, name, value, options = {}) => { }; const clearCookie = (res, name) => { - setCookie(res, name, '', { maxAge: 0 }); + setCookie(res, name, '', {maxAge: 0}); }; const createSession = (payload) => { @@ -103,7 +108,7 @@ const createSession = (payload) => { expiresAt, }); - return { sessionId, expiresAt }; + return {sessionId, expiresAt}; }; const getSession = (sessionId) => { @@ -225,12 +230,12 @@ const ensureSession = (req, res, next) => { const cookies = parseCookies(req.headers.cookie); const sessionId = cookies.dashboard_session; if (!sessionId) { - return res.status(401).json({ error: 'Unauthorized' }); + return res.status(401).json({error: 'Unauthorized'}); } const session = getSession(sessionId); if (!session) { - return res.status(401).json({ error: 'Unauthorized' }); + return res.status(401).json({error: 'Unauthorized'}); } req.session = session; @@ -243,16 +248,16 @@ const ensureManageableGuild = async (req, res, next) => { const guilds = await fetchDiscordUserGuilds(req.session.accessToken); const guild = guilds.find((entry) => entry.id === req.params.guildId); if (!guild) { - return res.status(404).json({ error: 'Guild not found' }); + return res.status(404).json({error: 'Guild not found'}); } if (!canManageGuild(guild)) { - return res.status(403).json({ error: 'Forbidden' }); + return res.status(403).json({error: 'Forbidden'}); } req.guild = guild; return next(); } catch (error) { - return res.status(502).json({ error: 'Failed to fetch guilds' }); + return res.status(502).json({error: 'Failed to fetch guilds'}); } }; @@ -264,7 +269,7 @@ const createDashboardApp = () => { app.get('/auth/discord', (req, res) => { if (!ensureOAuthConfig()) { - return res.status(500).json({ error: 'Missing Discord OAuth configuration' }); + return res.status(500).json({error: 'Missing Discord OAuth configuration'}); } const state = crypto.randomBytes(16).toString('hex'); @@ -280,26 +285,25 @@ const createDashboardApp = () => { app.get('/auth/discord/callback', async (req, res) => { if (!ensureOAuthConfig()) { - return res.status(500).json({ error: 'Missing Discord OAuth configuration' }); + return res.status(500).json({error: 'Missing Discord OAuth configuration'}); } - const { code, state } = req.query; + const {code, state} = req.query; const cookies = parseCookies(req.headers.cookie); if (!code || !state || cookies.oauth_state !== state) { - return res.status(400).json({ error: 'Invalid OAuth state' }); + return res.status(400).json({error: 'Invalid OAuth state'}); } try { const tokenData = await fetchDiscordAccessToken(code); const user = await fetchDiscordUser(tokenData.access_token); - const { sessionId, expiresAt } = createSession({ + const {sessionId, expiresAt} = createSession({ accessToken: tokenData.access_token, refreshToken: tokenData.refresh_token, tokenType: tokenData.token_type, scope: tokenData.scope, user, - expiresAt, }); setCookie(res, 'dashboard_session', sessionId, { @@ -318,9 +322,18 @@ const createDashboardApp = () => { return res.redirect('/'); } - return res.status(200).json({ user }); + return res.status(200).json({user}); } catch (error) { - return res.status(502).json({ error: 'Failed to authenticate with Discord' }); + console.error('Discord OAuth failed', { + message: error.message, + status: error.response?.status, + data: error.response?.data, + }); + + return res.status(502).json({ + error: 'Failed to authenticate with Discord', + details: error.response?.data || error.message, + }); } }); @@ -334,9 +347,9 @@ const createDashboardApp = () => { try { const guilds = await fetchDiscordUserGuilds(req.session.accessToken); const manageableGuilds = guilds.filter(canManageGuild); - return res.json({ guilds: manageableGuilds }); + return res.json({guilds: manageableGuilds}); } catch (error) { - return res.status(502).json({ error: 'Failed to fetch guilds' }); + return res.status(502).json({error: 'Failed to fetch guilds'}); } }); @@ -347,9 +360,9 @@ const createDashboardApp = () => { async (req, res) => { try { const permissions = await getGuildPermissions(req.params.guildId); - return res.json({ permissions }); + return res.json({permissions}); } catch (error) { - return res.status(500).json({ error: 'Failed to fetch permissions' }); + return res.status(500).json({error: 'Failed to fetch permissions'}); } }, ); @@ -364,9 +377,9 @@ const createDashboardApp = () => { guildId: req.params.guildId, permissions: req.body || {}, }); - return res.json({ permissions }); + return res.json({permissions}); } catch (error) { - return res.status(500).json({ error: 'Failed to update permissions' }); + return res.status(500).json({error: 'Failed to update permissions'}); } }, ); @@ -378,9 +391,9 @@ const createDashboardApp = () => { async (req, res) => { try { const roles = await fetchDiscordGuildRoles(req.params.guildId); - return res.json({ roles }); + return res.json({roles}); } catch (error) { - return res.status(500).json({ error: 'Failed to fetch roles' }); + return res.status(500).json({error: 'Failed to fetch roles'}); } }, ); @@ -395,9 +408,9 @@ const createDashboardApp = () => { guildId: req.params.guildId, userId: req.params.userId, }); - return res.json({ warnings }); + return res.json({warnings}); } catch (error) { - return res.status(500).json({ error: 'Failed to fetch warnings' }); + return res.status(500).json({error: 'Failed to fetch warnings'}); } }, ); @@ -407,11 +420,10 @@ const createDashboardApp = () => { ensureSession, ensureManageableGuild, async (req, res) => { - const { reason } = req.body || {}; + const {reason} = req.body || {}; if (!reason) { - return res.status(400).json({ error: 'Reason is required' }); + return res.status(400).json({error: 'Reason is required'}); } - try { const result = await addWarning({ guildId: req.params.guildId, @@ -421,11 +433,10 @@ const createDashboardApp = () => { }); return res.status(201).json(result); } catch (error) { - return res.status(500).json({ error: 'Failed to add warning' }); + return res.status(500).json({error: 'Failed to add warning'}); } }, ); - return app; }; diff --git a/db.js b/db.js index d2a0598..1ad12b4 100644 --- a/db.js +++ b/db.js @@ -1,11 +1,10 @@ -const { MongoClient, ServerApiVersion } = require('mongodb'); -const { mongodb } = require("./config.json"); +const {MongoClient, ServerApiVersion} = require('mongodb'); +const {mongodb} = require("./config.json"); let dbClient; async function connectToDatabase() { if (!dbClient) { - // Create a new MongoClient only once dbClient = new MongoClient(mongodb, { serverApi: { version: ServerApiVersion.v1, @@ -13,15 +12,11 @@ async function connectToDatabase() { deprecationErrors: true, } }); - await dbClient.connect(); console.log("Connected to MongoDB!"); - - await dbClient.db("admin").command({ ping: 1 }); + await dbClient.db("admin").command({ping: 1}); console.log("Pinged your deployment. You successfully connected to MongoDB!"); } - - // Return the existing connection if already connected return dbClient; } diff --git a/events/interactionsCreate.js b/events/interactionsCreate.js index 252f394..6e9e46c 100644 --- a/events/interactionsCreate.js +++ b/events/interactionsCreate.js @@ -1,18 +1,15 @@ -const { Events, MessageFlags } = require('discord.js'); -const { isCommandAllowed } = require('../services/permissions'); +const {Events, MessageFlags} = require('discord.js'); +const {isCommandAllowed} = require('../services/permissions'); module.exports = { name: Events.InteractionCreate, async execute(interaction) { if (!interaction.isChatInputCommand()) return; - const command = interaction.client.commands.get(interaction.commandName); - if (!command) { console.error(`No command matching ${interaction.commandName} was found.`); return; } - try { const allowed = await isCommandAllowed({ guildId: interaction.guildId, @@ -21,7 +18,6 @@ module.exports = { commandName: interaction.commandName, permissionGroup: command.permissionGroup, }); - if (!allowed) { await interaction.reply({ content: 'You do not have permission to use this command.', @@ -29,14 +25,19 @@ module.exports = { }); return; } - await command.execute(interaction); } catch (error) { console.error(error); if (interaction.replied || interaction.deferred) { - await interaction.followUp({ content: 'There was an error while executing this command!', flags: MessageFlags.Ephemeral }); + await interaction.followUp({ + content: 'There was an error while executing this command!', + flags: MessageFlags.Ephemeral + }); } else { - await interaction.reply({ content: 'There was an error while executing this command!', flags: MessageFlags.Ephemeral }); + await interaction.reply({ + content: 'There was an error while executing this command!', + flags: MessageFlags.Ephemeral + }); } } }, diff --git a/events/tempban.js b/events/tempban.js index 941412e..7bff351 100644 --- a/events/tempban.js +++ b/events/tempban.js @@ -10,8 +10,6 @@ module.exports = { const tempBanCollection = db.collection('tempban'); const scheduledBanIds = new Set(); let pollingInterval = null; - - // 2) Define a function to schedule unbans async function scheduleUnban(banData) { const banId = banData?._id?.toString(); if (banId && scheduledBanIds.has(banId)) { @@ -54,7 +52,6 @@ module.exports = { if (pollingInterval) { return; } - console.warn('Change streams unavailable; falling back to polling for temp bans.'); pollingInterval = setInterval(() => { loadAndScheduleBans().catch((err) => { console.error('Error polling temp bans:', err); diff --git a/index.js b/index.js index 87f3086..e5dd771 100644 --- a/index.js +++ b/index.js @@ -3,13 +3,12 @@ const { Client, Collection, GatewayIntentBits } = require('discord.js'); const { token } = require('./config.json'); const { loadCommandModules } = require('./loaders/commands'); const { loadEventModules } = require('./loaders/events'); - const client = new Client({ intents: [GatewayIntentBits.Guilds] }); - -// Retrieves commands from the command folder client.commands = new Collection(); const commandsPath = path.join(__dirname, 'commands'); const { commands, invalidCommands } = loadCommandModules(commandsPath); +const eventsPath = path.join(__dirname, 'events'); +const eventModules = loadEventModules(eventsPath); for (const command of commands) { client.commands.set(command.data.name, command); @@ -19,10 +18,6 @@ for (const filePath of invalidCommands) { console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`); } -// Retrieves events from the events folder -const eventsPath = path.join(__dirname, 'events'); -const eventModules = loadEventModules(eventsPath); - for (const { event } of eventModules) { if (event.once) { client.once(event.name, (...args) => event.execute(...args)); diff --git a/loaders/events.js b/loaders/events.js index e1668fc..c91abd9 100644 --- a/loaders/events.js +++ b/loaders/events.js @@ -11,7 +11,6 @@ const getEventFilePaths = (eventsPath) => const loadEventModules = (eventsPath) => { const eventFiles = getEventFilePaths(eventsPath); - return eventFiles.map((filePath) => ({ filePath, event: require(filePath), diff --git a/services/guildSettings.js b/services/guildSettings.js index 1d6cd3d..a6d6c53 100644 --- a/services/guildSettings.js +++ b/services/guildSettings.js @@ -15,7 +15,6 @@ const DEFAULT_GUILD_SETTINGS = { const normalizeGuildSettings = (settings = {}) => { const moderation = settings.moderation ?? {}; const strikeThresholds = moderation.strikeThresholds ?? {}; - return { ...DEFAULT_GUILD_SETTINGS, ...settings, @@ -34,12 +33,10 @@ const getGuildSettings = async (guildId) => { if (!guildId) { return normalizeGuildSettings(); } - const dbClient = await connectToDatabase(); const db = dbClient.db('discord'); const collection = db.collection('guild_settings'); const settings = await collection.findOne({ guildId }); - return normalizeGuildSettings(settings); };