diff --git a/api/db/init.ts b/api/db/init.ts index 5ef1fce..895fd53 100644 --- a/api/db/init.ts +++ b/api/db/init.ts @@ -7,6 +7,7 @@ export async function initTables() { name VARCHAR(255), icon VARCHAR(255), members INT, + cooldown INT DEFAULT 30000, updates_enabled BOOLEAN DEFAULT FALSE, updates_channel VARCHAR(255) ) diff --git a/api/db/queries/guilds.ts b/api/db/queries/guilds.ts index f66e55e..d3fc581 100644 --- a/api/db/queries/guilds.ts +++ b/api/db/queries/guilds.ts @@ -6,11 +6,12 @@ export interface Guild { name: string; icon: string; members: number; + cooldown: number; updates_enabled: boolean; updates_channel: string; } -export async function getGuild(guildId: string): Promise<[QueryError | null, Guild | null]> { +export async function getGuild(guildId: string): Promise<[QueryError, null] | [null, Guild | null]> { return new Promise((resolve, reject) => { pool.query("SELECT * FROM guilds WHERE id = ?", [guildId], (err, results) => { if (err) { diff --git a/api/index.ts b/api/index.ts index c9537d5..9541556 100644 --- a/api/index.ts +++ b/api/index.ts @@ -235,6 +235,36 @@ app.post("/admin/:action/:guild/:target", authMiddleware, async (req, res) => { default: return res.status(500).json({ message: "Internal server error" }); } + case "cooldown": + if (target !== "set" && target !== "get") { + return res.status(400).json({ message: "Illegal request" }); + } + + if(target === "set" && !extraData) { + return res.status(400).json({ message: "Illegal request" }); + } + + switch (target) { + case "get": + try { + const [err, data] = await getGuild(guild); + if(err) { + return res.status(500).json({ message: "Internal server error" }); + } + return res.status(200).json({ cooldown: data?.cooldown ?? 30_000 }); + } catch (error) { + return res.status(500).json({ message: "Internal server error" }); + } + case "set": + try { + const data = await adminCooldownSet(guild, extraData.cooldown); + return res.status(200).json(data); + } catch (error) { + return res.status(500).json({ message: "Internal server error" }); + } + default: + return res.status(500).json({ message: "Internal server error" }); + } default: return res.status(400).json({ message: "Illegal request" }); } @@ -392,3 +422,26 @@ async function adminUpdatesRemove(guildId: string) { } //#endregion + +// TODO: actually implement this in a real way +//#region Admin: Cooldown +async function adminCooldownSet(guild: string, cooldown: number) { + const updateCooldownQuery = ` + INSERT INTO guilds (id, cooldown) VALUES (?, ?) + ON DUPLICATE KEY UPDATE + cooldown = VALUES(cooldown) + `; + + return new Promise((resolve, reject) => { + pool.query(updateCooldownQuery, [guild, cooldown], (err, results) => { + if (err) { + console.error("Error setting cooldown:", err); + reject(err); + } else { + resolve(results); + } + }); + }); +} + +//#endregion diff --git a/bot/commands.ts b/bot/commands.ts index d2c6c5d..18986e2 100644 --- a/bot/commands.ts +++ b/bot/commands.ts @@ -3,7 +3,7 @@ import client from '.'; import { ActionRowBuilder, ButtonBuilder, ButtonStyle, type CommandInteraction, ChannelType } from 'discord.js'; import { heapStats } from 'bun:jsc'; -import { getGuildLeaderboard, makeGETRequest, getRoles, removeRole, addRole, enableUpdates, disableUpdates } from './utils/requestAPI'; +import { getGuildLeaderboard, makeGETRequest, getRoles, removeRole, addRole, enableUpdates, disableUpdates, getCooldown, setCooldown } from './utils/requestAPI'; import convertToLevels from './utils/convertToLevels'; import quickEmbed from './utils/quickEmbed'; @@ -429,6 +429,82 @@ const commands: Record = { return; } }, + }, + cooldown: { + data: { + options: [{ + name: 'action', + id: 'action', + description: 'Select an action', + type: 3, + required: true, + choices: [ + { + name: 'Get', + value: 'get', + }, + { + name: 'Set', + value: 'set', + } + ] + },{ + name: 'cooldown', + id: 'cooldown', + description: 'Enter the cooldown in seconds. Required for set action.', + type: 4, + required: false, + choices: [] + }], + name: 'cooldown', + description: 'Manage the cooldown for XP!', + integration_types: [0], + contexts: [0, 2], + }, + execute: async (interaction) => { + if (!interaction.memberPermissions?.has('ManageChannels')) { + const errorEmbed = quickEmbed({ + color: 'Red', + title: 'Error!', + description: 'Missing permissions: `Manage Channels`' + }, interaction); + await interaction.reply({ + ephemeral: true, + embeds: [errorEmbed] + }) + .catch(console.error); + return; + } + + const action = interaction.options.get('action')?.value; + const cooldown = interaction.options.get('cooldown')?.value; + + let cooldownData; + let apiSuccess; + + switch (action) { + case 'get': + cooldownData = await getCooldown(interaction.guildId as string); + if (!cooldownData) { + await interaction.reply({ ephemeral: true, content: 'Error fetching cooldown data!' }); + return; + } + await interaction.reply({ ephemeral: true, content: `Cooldown: ${(cooldownData?.cooldown ?? 30_000) / 1000} seconds` }); + return; + case 'set': + if (!cooldown) { + await interaction.reply({ ephemeral: true, content: 'ERROR: Cooldown was not specified!' }); + return; + } + apiSuccess = await setCooldown(interaction.guildId as string, parseInt(cooldown as string) * 1000); + if (!apiSuccess) { + await interaction.reply({ ephemeral: true, content: 'Error setting cooldown!' }); + return; + } + await interaction.reply({ ephemeral: true, content: `Cooldown set to ${cooldown} seconds` }); + return; + } + } } }; diff --git a/bot/events/messageCreate.ts b/bot/events/messageCreate.ts index 6f145e2..e3dafa6 100644 --- a/bot/events/messageCreate.ts +++ b/bot/events/messageCreate.ts @@ -1,13 +1,14 @@ import { Message } from 'discord.js'; import client from '../index'; -import { makePOSTRequest, updateGuildInfo } from '../utils/requestAPI'; +import { getCooldown, makePOSTRequest, updateGuildInfo } from '../utils/requestAPI'; const cooldowns = new Map(); -const cooldownTime = 30 * 1000; // Run this event whenever a message has been sent client.on('messageCreate', async (message: Message) => { if (message.author.bot) return; + + const cooldownTime = (await getCooldown(message.guildId as string))?.cooldown ?? 30_000; const cooldown = cooldowns.get(message.author.id); if (cooldown && Date.now() - cooldown < cooldownTime) return; diff --git a/bot/utils/requestAPI.ts b/bot/utils/requestAPI.ts index d4bb543..929fde5 100644 --- a/bot/utils/requestAPI.ts +++ b/bot/utils/requestAPI.ts @@ -144,3 +144,29 @@ export async function disableUpdates(guild: string) { return response.status === 200; } //#endregion + +//#region Cooldowns +export async function getCooldown(guild: string) { + const response = await fetch(`http://localhost:18103/admin/cooldown/${guild}/get`, { + "headers": { + 'Content-Type': 'application/json', + 'Authorization': process.env.AUTH as string, + }, + "body": JSON.stringify({}), + "method": "POST" + }); + return response.json(); +} + +export async function setCooldown(guild: string, cooldown: number) { + const response = await fetch(`http://localhost:18103/admin/cooldown/${guild}/set`, { + "headers": { + 'Content-Type': 'application/json', + 'Authorization': process.env.AUTH as string, + }, + "body": JSON.stringify({ extraData: { cooldown } }), + "method": "POST" + }); + return response.status === 200; +} +//#endregion