diff --git a/.env.sample b/.env.sample index 3d448af..a53645f 100644 --- a/.env.sample +++ b/.env.sample @@ -1,3 +1,36 @@ -APP_ID= -DISCORD_TOKEN= -PUBLIC_KEY= +APP_ID= +DISCORD_TOKEN= +PUBLIC_KEY= + +# Server, used only for chaos timer +GUILD_ID= + +# Role that can vote, only people with this role will be considered to calculate the majority +VOTING_ROLE_ID= + +# Polls max up time, in seconds +# default: 300 (5 minutes) +POLL_TIME=300 + +# Minimum votes needed, active if greater than majority +# default: 2 (prevents votes needed to be 1 if majority == 1) +MIN_VOTES=2 + +# Probability of quoi -> feur, between 0 and 1 +# default : 0.3 (30% chance of Flopobot saying 'feur' when someone says 'quoi') +FEUR_PROB=0.3 + +# Probability of triggering the chaos timer at midnight, between 0 and 1 +# default : 0.1 (10% chance of Flopobot picking randomely a user to time out for 12 hours at midnight) +CHAOS_PROB=0.1 + +# Everyone can be picked at the chaos timer +# default : false (Only VOTING_ROLE_ID users can be picked) +EVERYONE_IN_CHAOS=false + +# Every time the death roulette, cron expression +# default : '0 0 * * *' (Every day at midnight) +#CRON_EXPR='*/1 * * * * *' +CRON_EXPR='0 0 * * *' + +OPENAI_API_KEY= \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/flopobot_v2.iml b/.idea/flopobot_v2.iml new file mode 100644 index 0000000..c956989 --- /dev/null +++ b/.idea/flopobot_v2.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..36054e9 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/php.xml b/.idea/php.xml new file mode 100644 index 0000000..f324872 --- /dev/null +++ b/.idea/php.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/LICENSE b/LICENSE deleted file mode 100644 index b8642a5..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2022 Shay DeWael - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md index 16ebc68..7a4a59c 100644 --- a/README.md +++ b/README.md @@ -1,108 +1,20 @@ -# Getting Started app for Discord - -This project contains a basic rock-paper-scissors-style Discord app written in JavaScript, built for the [getting started guide](https://discord.com/developers/docs/getting-started). - -![Demo of app](https://github.com/discord/discord-example-app/raw/main/assets/getting-started-demo.gif?raw=true) +# FLOPOBOT DEUXIEME DU NOM ## Project structure Below is a basic overview of the project structure: ``` -├── examples -> short, feature-specific sample apps -│ ├── app.js -> finished app.js code -│ ├── button.js -│ ├── command.js -│ ├── modal.js -│ ├── selectMenu.js -├── .env.sample -> sample .env file +├── .env.sample ├── app.js -> main entrypoint for app ├── commands.js -> slash command payloads + helpers -├── game.js -> logic specific to RPS +├── game.js -> some stuff and data ├── utils.js -> utility functions and enums ├── package.json ├── README.md └── .gitignore ``` -## Running app locally - -Before you start, you'll need to install [NodeJS](https://nodejs.org/en/download/) and [create a Discord app](https://discord.com/developers/applications) with the proper permissions: -- `applications.commands` -- `bot` (with Send Messages enabled) - - -Configuring the app is covered in detail in the [getting started guide](https://discord.com/developers/docs/getting-started). - -### Setup project - -First clone the project: -``` -git clone https://github.com/discord/discord-example-app.git -``` - -Then navigate to its directory and install dependencies: -``` -cd discord-example-app -npm install -``` -### Get app credentials - -Fetch the credentials from your app's settings and add them to a `.env` file (see `.env.sample` for an example). You'll need your app ID (`APP_ID`), bot token (`DISCORD_TOKEN`), and public key (`PUBLIC_KEY`). - -Fetching credentials is covered in detail in the [getting started guide](https://discord.com/developers/docs/getting-started). - -> 🔑 Environment variables can be added to the `.env` file in Glitch or when developing locally, and in the Secrets tab in Replit (the lock icon on the left). - -### Install slash commands - -The commands for the example app are set up in `commands.js`. All of the commands in the `ALL_COMMANDS` array at the bottom of `commands.js` will be installed when you run the `register` command configured in `package.json`: - -``` -npm run register -``` - -### Run the app - -After your credentials are added, go ahead and run the app: - -``` -node app.js -``` - -> ⚙️ A package [like `nodemon`](https://github.com/remy/nodemon), which watches for local changes and restarts your app, may be helpful while locally developing. - -If you aren't following the [getting started guide](https://discord.com/developers/docs/getting-started), you can move the contents of `examples/app.js` (the finished `app.js` file) to the top-level `app.js`. - -### Set up interactivity - -The project needs a public endpoint where Discord can send requests. To develop and test locally, you can use something like [`ngrok`](https://ngrok.com/) to tunnel HTTP traffic. - -Install ngrok if you haven't already, then start listening on port `3000`: - -``` -ngrok http 3000 -``` - -You should see your connection open: - -``` -Tunnel Status online -Version 2.0/2.0 -Web Interface http://127.0.0.1:4040 -Forwarding https://1234-someurl.ngrok.io -> localhost:3000 - -Connections ttl opn rt1 rt5 p50 p90 - 0 0 0.00 0.00 0.00 0.00 -``` - -Copy the forwarding address that starts with `https`, in this case `https://1234-someurl.ngrok.io`, then go to your [app's settings](https://discord.com/developers/applications). - -On the **General Information** tab, there will be an **Interactions Endpoint URL**. Paste your ngrok address there, and append `/interactions` to it (`https://1234-someurl.ngrok.io/interactions` in the example). - -Click **Save Changes**, and your app should be ready to run 🚀 - ## Other resources -- Read **[the documentation](https://discord.com/developers/docs/intro)** for in-depth information about API features. -- Browse the `examples/` folder in this project for smaller, feature-specific code examples +- Read **[the discord dev documentation](https://discord.com/developers/docs/intro)** for in-depth information about API features. - Join the **[Discord Developers server](https://discord.gg/discord-developers)** to ask questions about the API, attend events hosted by the Discord API team, and interact with other devs. - Check out **[community resources](https://discord.com/developers/docs/topics/community-resources#community-resources)** for language-specific tools maintained by community members. diff --git a/app.js b/app.js index b358737..0be66f3 100644 --- a/app.js +++ b/app.js @@ -8,8 +8,10 @@ import { MessageComponentTypes, verifyKeyMiddleware, } from 'discord-interactions'; -import { getRandomEmoji, DiscordRequest } from './utils.js'; +import { getRandomEmoji, DiscordRequest, getOnlineUsersWithRole, formatTime, gork } from './utils.js'; import { getShuffledOptions, getResult } from './game.js'; +import { Client, GatewayIntentBits } from 'discord.js'; +import cron from 'node-cron'; // Create an express app const app = express(); @@ -17,6 +19,132 @@ const app = express(); const PORT = process.env.PORT || 3000; // To keep track of our active games const activeGames = {}; +const activePolls = {}; + +const client = new Client({ + intents: [ + GatewayIntentBits.Guilds, // For guild events + GatewayIntentBits.GuildMessages, // For messages in guilds + GatewayIntentBits.MessageContent, // For reading message content (privileged intent) + ] +}); + +// Login to Discord using your bot token (set BOT_TOKEN in your .env file) +client.login(process.env.BOT_TOKEN); + +// Listen for message events +client.on('messageCreate', async (message) => { + // Ignore messages from bots to avoid feedback loops + if (message.author.bot) return; + + // Check if the message content includes the word "quoi" (case-insensitive) + if (message.content.toLowerCase().includes("quoi")) { + let prob = Math.random() + if (prob < process.env.FEUR_PROB) { + // Send a message "feur" to the same channel + message.channel.send(`feur`) + .catch(console.error); + } + } + + if (message.content.toLowerCase().startsWith(`<@${process.env.APP_ID}>`)) { + try { + // Fetch last 10 messages from the channel + const fetched = await message.channel.messages.fetch({ limit: 100 }); + const messagesArray = Array.from(fetched.values()).reverse(); // oldest to newest + + const requestMessage = message.content.replace('@gork', '') + + // Map to OpenAI format + let formatted = messagesArray.map(msg => ({ + role: msg.author.bot ? "assistant" : "user", + content: msg.content, + author: msg.author.username, + })); + + const members = await getOnlineUsersWithRole(process.env.GUILD_ID, process.env.VOTING_ROLE_ID); + + // Add a final user prompt to clarify the request + formatted.push( + { + role: "system", + content: "Sachant que les 'author' sont les pseudos des personnes ayant écrit chaque message. Adopte une attitude détendu et répond comme si tu participais à la conversation, essaye d'imiter au mieux la façon de parler des utilisateurs. Soit malpoli etc, n'hésites pas à utiliser des abréviations mais sans en abuser. Manque de respect quand tu le peux, sauf contre-indications de l'utilisateur, fait plutôt court, une ou deux phrases maximum " + }, + { + role: "system", + content: `Voici la liste des principaux utilisateurs : ${members}` + }, + { + role: "system", + content: `L'utilisateur qui s'adresse a toi dans la prochaine phrase est : ${message.author}` + }, + { + role: "user", + content: requestMessage.length > 1 ? requestMessage : 'Répond de manière approprié aux derniers messages de cette conversation.', + }, + { + role: "system", + content: 'En te basant sur la liste des utilisateurs, lorsque tu parles d\'un utilisateur présent dans cette liste que ce soit via son \'user.global_name\', son \'user.username\' ou son \'user.id\' , identifie le avec son \'user.id\' plutôt que d\'utiliser son \'user.global_name\', ça doit ressembler à ça en remplaçant \'ID\' <@ID>. Fait le la première fois que tu évoques l\'utilisateur mais donne juste son \'user.global_name\' ensuite', + }); + + formatted = formatted.filter(e => e.role !== 'assistant'); + + const reply = await gork(formatted); + + // Send response to the channel + await message.channel.send(reply); + } catch (err) { + console.error("Error fetching or sending messages:", err); + await message.channel.send("Oups, y'a eu un problème!"); + } + } +}); + +// Once bot is ready +client.once('ready', () => { + console.log(`Logged in as ${client.user.tag}`); + + // ─── 💀 Midnight Chaos Timer ────────────────────── + cron.schedule(process.env.CRON_EXPR, async () => { + const guild = await client.guilds.fetch(process.env.GUILD_ID); + const roleId = process.env.VOTING_ROLE_ID; // Set this in your .env file + const members = await getOnlineUsersWithRole(guild.id, roleId); + + // Filter out bots and members the bot can't moderate + const eligible = members.filter(member => !member.user.bot); + + const prob = Math.random(); + if (eligible.length === 0 || prob > process.env.CHAOS_PROB) { + console.log(`No roulette tonight ${prob}`) + return + } + + const randomMember = eligible[Math.floor(Math.random() * eligible.length)]; + + const timeoutUntil = new Date(Date.now() + 12 * 60 * 60 * 1000).toISOString(); + + try { + await guild.members.edit(randomMember.user.id, { + communication_disabled_until: timeoutUntil, + reason: 'Roulette Russe 🔔', + }); + + const generalChannel = guild.channels.cache.find( + ch => ch.name === 'général' || ch.name === 'general' + ); + + if (generalChannel && generalChannel.isTextBased()) { + generalChannel.send( + `🎯 <@${randomMember.user.id}> ça dégage, à mimir ! (jusqu'à 12h00)` + ); + } + + console.log(`${randomMember.user.username} has been timed out until ${timeoutUntil}`); + } catch (err) { + console.error('Failed to timeout random member:', err); + } + }); +}); /** * Interactions endpoint URL where Discord will send HTTP requests @@ -39,6 +167,7 @@ app.post('/interactions', verifyKeyMiddleware(process.env.PUBLIC_KEY), async fun */ if (type === InteractionType.APPLICATION_COMMAND) { const { name } = data; + console.log(name) // "test" command if (name === 'test') { @@ -52,10 +181,349 @@ app.post('/interactions', verifyKeyMiddleware(process.env.PUBLIC_KEY), async fun }); } + // "challenge" command + if (name === 'challenge') { + // Interaction context + const context = req.body.context; + // User ID is in user field for (G)DMs, and member for servers + const userId = context === 0 ? req.body.member.user.id : req.body.user.id; + // User's object choice + const objectName = req.body.data.options[0].value; + + // Create active game using message ID as the game ID + activeGames[id] = { + id: userId, + objectName, + }; + + return res.send({ + type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, + data: { + content: `Rock papers scissors challenge from <@${userId}>`, + components: [ + { + type: MessageComponentTypes.ACTION_ROW, + components: [ + { + type: MessageComponentTypes.BUTTON, + // Append the game ID to use later on + custom_id: `accept_button_${req.body.id}`, + label: 'Accept', + style: ButtonStyleTypes.PRIMARY, + }, + ], + }, + ], + }, + }); + } + + // 'timeout' command + if (name === 'timeout') { + // Interaction context + const context = req.body.context; + // User ID is in user field for (G)DMs, and member for servers + const userId = context === 0 ? req.body.member.user.id : req.body.user.id; + // User's choices + const akhy = req.body.data.options[0].value; + const time = req.body.data.options[1].value; + + // Save the poll information along with channel ID so we can notify later + activePolls[id] = { + id: userId, + toUserId: akhy, + time: time, + time_display: formatTime(time), + for: 0, + against: 0, + voters: new Set(), + channelId: req.body.channel_id, // Capture channel for follow-up notification + endpoint: `webhooks/${process.env.APP_ID}/${req.body.token}/messages/@original`, + }; + + const guildId = req.body.guild_id; + const roleId = process.env.VOTING_ROLE_ID; // Set this in your .env file + const onlineEligibleUsers = await getOnlineUsersWithRole(guildId, roleId); + const requiredMajority = Math.max(parseInt(process.env.MIN_VOTES), Math.floor(onlineEligibleUsers.length / 2) + 1); + const votesNeeded = Math.max(0, requiredMajority - activePolls[id].for); + + // Set a timeout for 5 minutes to end the poll if no majority is reached + setTimeout(async () => { + if (activePolls[id]) { + // Poll has expired without enough votes + // Send a notification to the channel that the vote failed + try { + await DiscordRequest( + `webhooks/${process.env.APP_ID}/${req.body.token}/messages/@original`, + { + method: 'PATCH', + body: { + content: `Le vote pour timeout de <@${activePolls[id].toUserId}> a expiré sans atteindre la majorité.`, + components: [] // remove the buttons + }, + } + ); + } catch (err) { + console.error('Error sending vote failure message:', err); + } + // Clear the poll + delete activePolls[id]; + } + }, process.env.POLL_TIME * 1000); + + return res.send({ + type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, + data: { + content: `<@${activePolls[id].id}> propose de timeout <@${activePolls[id].toUserId}> pendant ${activePolls[id].time_display}\n\n` + + `Il faut **${votesNeeded}** votes\n`, + components: [ + { + type: MessageComponentTypes.ACTION_ROW, + components: [ + { + type: MessageComponentTypes.BUTTON, + custom_id: `vote_for_${req.body.id}`, + label: 'Oui ✅', + style: ButtonStyleTypes.SECONDARY, + }, + { + type: MessageComponentTypes.BUTTON, + custom_id: `vote_against_${req.body.id}`, + label: 'Non ❌', + style: ButtonStyleTypes.SECONDARY, + }, + ], + }, + ], + }, + }); + } + console.error(`unknown command: ${name}`); return res.status(400).json({ error: 'unknown command' }); } + if (type === InteractionType.MESSAGE_COMPONENT) { +// custom_id set in payload when sending message component + const componentId = data.custom_id; + + if (componentId.startsWith('accept_button_')) { + // get the associated game ID + const gameId = componentId.replace('accept_button_', ''); + // Delete message with token in request body + const endpoint = `webhooks/${process.env.APP_ID}/${req.body.token}/messages/${req.body.message.id}`; + try { + await res.send({ + type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, + data: { + content: 'What is your object of choice?', + // Indicates it'll be an ephemeral message + flags: InteractionResponseFlags.EPHEMERAL, + components: [ + { + type: MessageComponentTypes.ACTION_ROW, + components: [ + { + type: MessageComponentTypes.STRING_SELECT, + // Append game ID + custom_id: `select_choice_${gameId}`, + options: getShuffledOptions(), + }, + ], + }, + ], + }, + }); + // Delete previous message + await DiscordRequest(endpoint, { method: 'DELETE' }); + } catch (err) { + console.error('Error sending message:', err); + } + } + else if (componentId.startsWith('select_choice_')) { + // get the associated game ID + const gameId = componentId.replace('select_choice_', ''); + + if (activeGames[gameId]) { + // Interaction context + const context = req.body.context; + // Get user ID and object choice for responding user + // User ID is in user field for (G)DMs, and member for servers + const userId = context === 0 ? req.body.member.user.id : req.body.user.id; + + // User's object choice + const objectName = data.values[0]; + + // Calculate result from helper function + const resultStr = getResult(activeGames[gameId], { + id: userId, + objectName, + }); + + // Remove game from storage + delete activeGames[gameId]; + // Update message with token in request body + const endpoint = `webhooks/${process.env.APP_ID}/${req.body.token}/messages/${req.body.message.id}`; + + try { + // Send results + await res.send({ + type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, + data: { content: resultStr }, + }); + // Update ephemeral message + await DiscordRequest(endpoint, { + method: 'PATCH', + body: { + content: 'Nice choice ' + getRandomEmoji(), + components: [] + } + }); + } catch (err) { + console.error('Error sending message:', err); + } + } + } + else if (componentId.startsWith('vote_')) { + let gameId, isVotingFor; + + if (componentId.startsWith('vote_for_')) { + gameId = componentId.replace('vote_for_', ''); + isVotingFor = true; + } else { + gameId = componentId.replace('vote_against_', ''); + isVotingFor = false; + } + + if (activePolls[gameId]) { + const poll = activePolls[gameId]; + poll.voters = poll.voters || new Set(); + const voterId = req.body.member.user.id; + + // Check if the voter has the required voting role + const voterRoles = req.body.member.roles || []; + if (!voterRoles.includes(process.env.VOTING_ROLE_ID)) { + return res.send({ + type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, + data: { + content: "Tu n'as pas le rôle requis pour voter.", + flags: InteractionResponseFlags.EPHEMERAL, + }, + }); + } + + // Enforce one vote per eligible user + if (poll.voters.has(voterId)) { + return res.send({ + type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, + data: { + content: "Tu as déjà voté !", + flags: InteractionResponseFlags.EPHEMERAL, + }, + }); + } + + // Record the vote + poll.voters.add(voterId); + if (isVotingFor) { + poll.for++; + } else { + poll.against++; + } + + // Retrieve online eligible users (ensure your bot has the necessary intents) + const guildId = req.body.guild_id; + const roleId = process.env.VOTING_ROLE_ID; // Set this in your .env file + const onlineEligibleUsers = await getOnlineUsersWithRole(guildId, roleId); + const requiredMajority = Math.max(parseInt(process.env.MIN_VOTES), Math.floor(onlineEligibleUsers.length / 2) + 1); + const votesNeeded = Math.max(0, requiredMajority - poll.for); + + // Check if the majority is reached + if (poll.for >= requiredMajority) { + try { + // Build the updated poll message content + const updatedContent = `<@${poll.id}> propose de timeout <@${poll.toUserId}> pendant ${poll.time_display}\n\n` + + `✅ **${poll.for}** votes au total\n\n`; + + await DiscordRequest( + poll.endpoint, + { + method: 'PATCH', + body: { + content: updatedContent, + components: [], // remove buttons + }, + } + ); + } catch (err) { + console.error('Error updating poll message:', err); + } + // Clear the poll so the setTimeout callback doesn't fire later + delete activePolls[gameId]; + + // **Actual Timeout Action** + try { + // Calculate the ISO8601 timestamp to disable communications until now + poll.time seconds + const timeoutUntil = new Date(Date.now() + poll.time * 1000).toISOString(); + const endpointTimeout = `guilds/${req.body.guild_id}/members/${poll.toUserId}`; + await DiscordRequest(endpointTimeout, { + method: 'PATCH', + body: { communication_disabled_until: timeoutUntil }, + }); + return res.send({ + type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, + data: { + content: `<@${poll.toUserId}> a été timeout pendant ${poll.time_display} par décision démocratique 👊`, + }, + }); + } catch (err) { + console.error('Error timing out user:', err); + return res.send({ + type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, + data: { + content: `Impossible de timeout <@${poll.toUserId}>, désolé... 😔`, + }, + }); + } + } + + // If the vote is "for", update the original poll message to reflect the new vote count. + if (isVotingFor) { + try { + // Build the updated poll message content + const updatedContent = `<@${poll.id}> propose de timeout <@${poll.toUserId}> pendant ${poll.time_display}\n\n` + + `✅ **${poll.for}**\n\n` + + `Il manque **${votesNeeded}** vote(s)\n`; + + await DiscordRequest( + poll.endpoint, + { + method: 'PATCH', + body: { + content: updatedContent, + components: req.body.message.components, // preserve the buttons + }, + } + ); + } catch (err) { + console.error('Error updating poll message:', err); + } + } + + // Send an ephemeral acknowledgement to the voter + return res.send({ + type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, + data: { + content: `Vote enregistré ! ✅ ${poll.for} pour / ❌ ${poll.against} contre. Il manque ${votesNeeded} vote(s) pour atteindre la majorité.`, + flags: InteractionResponseFlags.EPHEMERAL, + }, + }); + } + } + return; + } + + console.error('unknown interaction type', type); return res.status(400).json({ error: 'unknown interaction type' }); }); diff --git a/commands.js b/commands.js index 4102e40..6f02bef 100644 --- a/commands.js +++ b/commands.js @@ -1,5 +1,5 @@ import 'dotenv/config'; -import { getRPSChoices } from './game.js'; +import { getRPSChoices, getTimesChoices } from './game.js'; import { capitalize, InstallGlobalCommands } from './utils.js'; // Get the game choices from game.js @@ -17,6 +17,20 @@ function createCommandChoices() { return commandChoices; } +function createTimesChoices() { + const choices = getTimesChoices(); + const commandChoices = []; + + for (let choice of choices) { + commandChoices.push({ + name: capitalize(choice.name), + value: choice.value.toString(), + }); + } + + return commandChoices; +} + // Simple test command const TEST_COMMAND = { name: 'test', @@ -44,6 +58,30 @@ const CHALLENGE_COMMAND = { contexts: [0, 2], }; -const ALL_COMMANDS = [TEST_COMMAND, CHALLENGE_COMMAND]; +// Timeout vote command +const TIMEOUT_COMMAND = { + name: 'timeout', + description: 'Vote démocratique pour time out un boug', + options: [ + { + type: 6, + name: 'akhy', + description: 'Qui ?', + required: true, + }, + { + type: 3, + name: 'temps', + description: 'Combien de temps ?', + required: true, + choices: createTimesChoices(), + } + ], + type: 1, + integration_types: [0, 1], + contexts: [0, 2], +} + +const ALL_COMMANDS = [/*TEST_COMMAND, CHALLENGE_COMMAND, */TIMEOUT_COMMAND]; InstallGlobalCommands(process.env.APP_ID, ALL_COMMANDS); diff --git a/examples/app.js b/examples/app.js deleted file mode 100644 index 2f92eb2..0000000 --- a/examples/app.js +++ /dev/null @@ -1,189 +0,0 @@ -import 'dotenv/config'; -import express from 'express'; -import { - InteractionType, - InteractionResponseType, - InteractionResponseFlags, - MessageComponentTypes, - ButtonStyleTypes, - verifyKeyMiddleware, -} from 'discord-interactions'; -import { getRandomEmoji, DiscordRequest } from './utils.js'; -import { getShuffledOptions, getResult } from './game.js'; - -// Create an express app -const app = express(); -// Get port, or default to 3000 -const PORT = process.env.PORT || 3000; - -// Store for in-progress games. In production, you'd want to use a DB -const activeGames = {}; - -/** - * Interactions endpoint URL where Discord will send HTTP requests - * Parse request body and verifies incoming requests using discord-interactions package - */ -app.post('/interactions', verifyKeyMiddleware(process.env.PUBLIC_KEY), async function (req, res) { - // Interaction type and data - const { type, id, data } = req.body; - - /** - * Handle verification requests - */ - if (type === InteractionType.PING) { - return res.send({ type: InteractionResponseType.PONG }); - } - - /** - * Handle slash command requests - * See https://discord.com/developers/docs/interactions/application-commands#slash-commands - */ - if (type === InteractionType.APPLICATION_COMMAND) { - const { name } = data; - - // "test" command - if (name === 'test') { - // Send a message into the channel where command was triggered from - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - // Fetches a random emoji to send from a helper function - content: `hello world ${getRandomEmoji()}`, - }, - }); - } - - // "challenge" command - if (name === 'challenge' && id) { - // Interaction context - const context = req.body.context; - // User ID is in user field for (G)DMs, and member for servers - const userId = context === 0 ? req.body.member.user.id : req.body.user.id; - // User's object choice - const objectName = req.body.data.options[0].value; - - // Create active game using message ID as the game ID - activeGames[id] = { - id: userId, - objectName, - }; - - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - // Fetches a random emoji to send from a helper function - content: `Rock papers scissors challenge from <@${userId}>`, - components: [ - { - type: MessageComponentTypes.ACTION_ROW, - components: [ - { - type: MessageComponentTypes.BUTTON, - // Append the game ID to use later on - custom_id: `accept_button_${req.body.id}`, - label: 'Accept', - style: ButtonStyleTypes.PRIMARY, - }, - ], - }, - ], - }, - }); - } - - console.error(`unknown command: ${name}`); - return res.status(400).json({ error: 'unknown command' }); - } - - /** - * Handle requests from interactive components - * See https://discord.com/developers/docs/interactions/message-components#responding-to-a-component-interaction - */ - if (type === InteractionType.MESSAGE_COMPONENT) { - // custom_id set in payload when sending message component - const componentId = data.custom_id; - - if (componentId.startsWith('accept_button_')) { - // get the associated game ID - const gameId = componentId.replace('accept_button_', ''); - // Delete message with token in request body - const endpoint = `webhooks/${process.env.APP_ID}/${req.body.token}/messages/${req.body.message.id}`; - try { - await res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: 'What is your object of choice?', - // Indicates it'll be an ephemeral message - flags: InteractionResponseFlags.EPHEMERAL, - components: [ - { - type: MessageComponentTypes.ACTION_ROW, - components: [ - { - type: MessageComponentTypes.STRING_SELECT, - // Append game ID - custom_id: `select_choice_${gameId}`, - options: getShuffledOptions(), - }, - ], - }, - ], - }, - }); - // Delete previous message - await DiscordRequest(endpoint, { method: 'DELETE' }); - } catch (err) { - console.error('Error sending message:', err); - } - } else if (componentId.startsWith('select_choice_')) { - // get the associated game ID - const gameId = componentId.replace('select_choice_', ''); - - if (activeGames[gameId]) { - // Interaction context - const context = req.body.context; - // Get user ID and object choice for responding user - // User ID is in user field for (G)DMs, and member for servers - const userId = context === 0 ? req.body.member.user.id : req.body.user.id; - const objectName = data.values[0]; - // Calculate result from helper function - const resultStr = getResult(activeGames[gameId], { - id: userId, - objectName, - }); - - // Remove game from storage - delete activeGames[gameId]; - // Update message with token in request body - const endpoint = `webhooks/${process.env.APP_ID}/${req.body.token}/messages/${req.body.message.id}`; - - try { - // Send results - await res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { content: resultStr }, - }); - // Update ephemeral message - await DiscordRequest(endpoint, { - method: 'PATCH', - body: { - content: 'Nice choice ' + getRandomEmoji(), - components: [], - }, - }); - } catch (err) { - console.error('Error sending message:', err); - } - } - } - - return; - } - - console.error('unknown interaction type', type); - return res.status(400).json({ error: 'unknown interaction type' }); -}); - -app.listen(PORT, () => { - console.log('Listening on port', PORT); -}); diff --git a/examples/button.js b/examples/button.js deleted file mode 100644 index 1dc8ed9..0000000 --- a/examples/button.js +++ /dev/null @@ -1,69 +0,0 @@ -import 'dotenv/config'; -import express from 'express'; -import { - InteractionType, - InteractionResponseType, - MessageComponentTypes, - ButtonStyleTypes, - verifyKeyMiddleware, -} from 'discord-interactions'; - -// Create and configure express app -const app = express(); - -app.post('/interactions', verifyKeyMiddleware(process.env.PUBLIC_KEY), function (req, res) { - // Interaction type and data - const { type, data } = req.body; - /** - * Handle slash command requests - */ - if (type === InteractionType.APPLICATION_COMMAND) { - // Slash command with name of "test" - if (data.name === 'test') { - // Send a message with a button - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: 'A message with a button', - // Buttons are inside of action rows - components: [ - { - type: MessageComponentTypes.ACTION_ROW, - components: [ - { - type: MessageComponentTypes.BUTTON, - // Value for your app to identify the button - custom_id: 'my_button', - label: 'Click', - style: ButtonStyleTypes.PRIMARY, - }, - ], - }, - ], - }, - }); - } - } - - /** - * Handle requests from interactive components - */ - if (type === InteractionType.MESSAGE_COMPONENT) { - // custom_id set in payload when sending message component - const componentId = data.custom_id; - // user who clicked button - const userId = req.body.member.user.id; - - if (componentId === 'my_button') { - console.log(req.body); - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { content: `<@${userId}> clicked the button` }, - }); - } - } -}); - -app.listen(3000, () => { - console.log('Listening on port 3000'); -}); diff --git a/examples/command.js b/examples/command.js deleted file mode 100644 index 5aabb5a..0000000 --- a/examples/command.js +++ /dev/null @@ -1,64 +0,0 @@ -import 'dotenv/config'; -import express from 'express'; -import { InteractionType, InteractionResponseType, verifyKeyMiddleware } from 'discord-interactions'; -import { DiscordRequest } from '../utils.js'; - -// Create and configure express app -const app = express(); - -app.post('/interactions', verifyKeyMiddleware(process.env.PUBLIC_KEY), function (req, res) { - // Interaction type and data - const { type, data } = req.body; - /** - * Handle slash command requests - */ - if (type === InteractionType.APPLICATION_COMMAND) { - // Slash command with name of "test" - if (data.name === 'test') { - // Send a message as response - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { content: 'A wild message appeared' }, - }); - } - } -}); - -async function createCommand() { - const appId = process.env.APP_ID; - - /** - * Globally-scoped slash commands (generally only recommended for production) - * See https://discord.com/developers/docs/interactions/application-commands#create-global-application-command - */ - const globalEndpoint = `applications/${appId}/commands`; - - /** - * Guild-scoped slash commands - * See https://discord.com/developers/docs/interactions/application-commands#create-guild-application-command - */ - // const guildEndpoint = `applications/${appId}/guilds//commands`; - const commandBody = { - name: 'test', - description: 'Just your average command', - // chat command (see https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-types) - type: 1, - }; - - try { - // Send HTTP request with bot token - const res = await DiscordRequest(globalEndpoint, { - method: 'POST', - body: commandBody, - }); - console.log(await res.json()); - } catch (err) { - console.error('Error installing commands: ', err); - } -} - -app.listen(3000, () => { - console.log('Listening on port 3000'); - - createCommand(); -}); diff --git a/examples/modal.js b/examples/modal.js deleted file mode 100644 index 92e6359..0000000 --- a/examples/modal.js +++ /dev/null @@ -1,89 +0,0 @@ -import 'dotenv/config'; -import express from 'express'; -import { - InteractionType, - InteractionResponseType, - MessageComponentTypes, - verifyKeyMiddleware, -} from 'discord-interactions'; - -// Create and configure express app -const app = express(); - -app.post('/interactions', verifyKeyMiddleware(process.env.PUBLIC_KEY), function (req, res) { - // Interaction type and data - const { type, data } = req.body; - /** - * Handle slash command requests - */ - if (type === InteractionType.APPLICATION_COMMAND) { - // Slash command with name of "test" - if (data.name === 'test') { - // Send a modal as response - return res.send({ - type: InteractionResponseType.MODAL, - data: { - custom_id: 'my_modal', - title: 'Modal title', - components: [ - { - // Text inputs must be inside of an action component - type: MessageComponentTypes.ACTION_ROW, - components: [ - { - // See https://discord.com/developers/docs/interactions/message-components#text-inputs-text-input-structure - type: MessageComponentTypes.INPUT_TEXT, - custom_id: 'my_text', - style: 1, - label: 'Type some text', - }, - ], - }, - { - type: MessageComponentTypes.ACTION_ROW, - components: [ - { - type: MessageComponentTypes.INPUT_TEXT, - custom_id: 'my_longer_text', - // Bigger text box for input - style: 2, - label: 'Type some (longer) text', - }, - ], - }, - ], - }, - }); - } - } - - /** - * Handle modal submissions - */ - if (type === InteractionType.MODAL_SUBMIT) { - // custom_id of modal - const modalId = data.custom_id; - // user ID of member who filled out modal - const userId = req.body.member.user.id; - - if (modalId === 'my_modal') { - let modalValues = ''; - // Get value of text inputs - for (let action of data.components) { - let inputComponent = action.components[0]; - modalValues += `${inputComponent.custom_id}: ${inputComponent.value}\n`; - } - - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: `<@${userId}> typed the following (in a modal):\n\n${modalValues}`, - }, - }); - } - } -}); - -app.listen(3000, () => { - console.log('Listening on port 3000'); -}); diff --git a/examples/selectMenu.js b/examples/selectMenu.js deleted file mode 100644 index 1420f3e..0000000 --- a/examples/selectMenu.js +++ /dev/null @@ -1,83 +0,0 @@ -import 'dotenv/config'; -import express from 'express'; -import { - InteractionType, - InteractionResponseType, - MessageComponentTypes, - verifyKeyMiddleware, -} from 'discord-interactions'; - -// Create and configure express app -const app = express(); - -app.post('/interactions', verifyKeyMiddleware(process.env.PUBLIC_KEY), function (req, res) { - // Interaction type and data - const { type, data } = req.body; - /** - * Handle slash command requests - */ - if (type === InteractionType.APPLICATION_COMMAND) { - // Slash command with name of "test" - if (data.name === 'test') { - // Send a message with a button - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: 'A message with a button', - // Selects are inside of action rows - components: [ - { - type: MessageComponentTypes.ACTION_ROW, - components: [ - { - type: MessageComponentTypes.STRING_SELECT, - // Value for your app to identify the select menu interactions - custom_id: 'my_select', - // Select options - see https://discord.com/developers/docs/interactions/message-components#select-menu-object-select-option-structure - options: [ - { - label: 'Option #1', - value: 'option_1', - description: 'The very first option', - }, - { - label: 'Second option', - value: 'option_2', - description: 'The second AND last option', - }, - ], - }, - ], - }, - ], - }, - }); - } - } - - /** - * Handle requests from interactive components - */ - if (type === InteractionType.MESSAGE_COMPONENT) { - // custom_id set in payload when sending message component - const componentId = data.custom_id; - - if (componentId === 'my_select') { - console.log(req.body); - - // Get selected option from payload - const selectedOption = data.values[0]; - const userId = req.body.member.user.id; - - // Send results - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { content: `<@${userId}> selected ${selectedOption}` }, - }); - } - } -}); - -app.listen(3000, () => { - console.log('Listening on port 3000'); -}); diff --git a/game.js b/game.js index e56edf3..ba3d899 100644 --- a/game.js +++ b/game.js @@ -80,10 +80,81 @@ const RPSChoices = { }, }; +const TimesChoices = [ + { + name: '1 minute', + value: 60, + }, + { + name: '5 minutes', + value: 300, + }, + { + name: '10 minutes', + value: 600, + }, + { + name: '15 minutes', + value: 900, + }, + { + name: '30 minutes', + value: 1800, + }, + { + name: '1 heure', + value: 3600, + }, + { + name: '2 heures', + value: 3600, + }, + { + name: '3 heures', + value: 10800, + }, + { + name: '6 heures', + value: 21600, + }, + { + name: '9 heures', + value: 32400, + }, + { + name: '12 heures', + value: 43200, + }, + { + name: '16 heures', + value: 57600, + }, + { + name: '1 journée', + value: 86400, + }, + { + name: '2 journées', + value: 172800, + }, + { + name: '1 semaine', + value: 604800, + }, + { + name: '2 semaines', + value: 604800 * 2, + }, +]; + export function getRPSChoices() { return Object.keys(RPSChoices); } +export function getTimesChoices() { + return TimesChoices +} + // Function to fetch shuffled options for select menu export function getShuffledOptions() { const allChoices = getRPSChoices(); diff --git a/package-lock.json b/package-lock.json index aaab7ed..2e78588 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,9 +9,12 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "discord-interactions": "^3.2.0", + "discord-interactions": "^4.0.0", + "discord.js": "^14.18.0", "dotenv": "^16.0.3", - "express": "^4.18.2" + "express": "^4.18.2", + "node-cron": "^3.0.3", + "openai": "^4.94.0" }, "devDependencies": { "nodemon": "^3.0.0" @@ -20,6 +23,216 @@ "node": ">=18.x" } }, + "node_modules/@discordjs/builders": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.10.1.tgz", + "integrity": "sha512-OWo1fY4ztL1/M/DUyRPShB4d/EzVfuUvPTRRHRIt/YxBrUYSz0a+JicD5F5zHFoNs2oTuWavxCOVFV1UljHTng==", + "license": "Apache-2.0", + "dependencies": { + "@discordjs/formatters": "^0.6.0", + "@discordjs/util": "^1.1.1", + "@sapphire/shapeshift": "^4.0.0", + "discord-api-types": "^0.37.119", + "fast-deep-equal": "^3.1.3", + "ts-mixer": "^6.0.4", + "tslib": "^2.6.3" + }, + "engines": { + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/collection": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz", + "integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=16.11.0" + } + }, + "node_modules/@discordjs/formatters": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.0.tgz", + "integrity": "sha512-YIruKw4UILt/ivO4uISmrGq2GdMY6EkoTtD0oS0GvkJFRZbTSdPhzYiUILbJ/QslsvC9H9nTgGgnarnIl4jMfw==", + "license": "Apache-2.0", + "dependencies": { + "discord-api-types": "^0.37.114" + }, + "engines": { + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/rest": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.4.3.tgz", + "integrity": "sha512-+SO4RKvWsM+y8uFHgYQrcTl/3+cY02uQOH7/7bKbVZsTfrfpoE62o5p+mmV+s7FVhTX82/kQUGGbu4YlV60RtA==", + "license": "Apache-2.0", + "dependencies": { + "@discordjs/collection": "^2.1.1", + "@discordjs/util": "^1.1.1", + "@sapphire/async-queue": "^1.5.3", + "@sapphire/snowflake": "^3.5.3", + "@vladfrangu/async_event_emitter": "^2.4.6", + "discord-api-types": "^0.37.119", + "magic-bytes.js": "^1.10.0", + "tslib": "^2.6.3", + "undici": "6.21.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/rest/node_modules/@discordjs/collection": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", + "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/util": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.1.1.tgz", + "integrity": "sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/ws": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.1.tgz", + "integrity": "sha512-PBvenhZG56a6tMWF/f4P6f4GxZKJTBG95n7aiGSPTnodmz4N5g60t79rSIAq7ywMbv8A4jFtexMruH+oe51aQQ==", + "license": "Apache-2.0", + "dependencies": { + "@discordjs/collection": "^2.1.0", + "@discordjs/rest": "^2.4.3", + "@discordjs/util": "^1.1.0", + "@sapphire/async-queue": "^1.5.2", + "@types/ws": "^8.5.10", + "@vladfrangu/async_event_emitter": "^2.2.4", + "discord-api-types": "^0.37.119", + "tslib": "^2.6.2", + "ws": "^8.17.0" + }, + "engines": { + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/ws/node_modules/@discordjs/collection": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", + "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@sapphire/async-queue": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.5.tgz", + "integrity": "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg==", + "license": "MIT", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@sapphire/shapeshift": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-4.0.0.tgz", + "integrity": "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=v16" + } + }, + "node_modules/@sapphire/snowflake": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.3.tgz", + "integrity": "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==", + "license": "MIT", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@types/node": { + "version": "22.14.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.1.tgz", + "integrity": "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@vladfrangu/async_event_emitter": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.6.tgz", + "integrity": "sha512-RaI5qZo6D2CVS6sTHFKg1v5Ohq/+Bo2LZ5gzUEwZ/WkHhwtGTCB/sVLw8ijOkAUxasZ+WshN/Rzj4ywsABJ5ZA==", + "license": "MIT", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -33,6 +246,18 @@ "node": ">= 0.6" } }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "license": "MIT", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -53,6 +278,12 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -74,9 +305,9 @@ } }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "license": "MIT", "dependencies": { "bytes": "3.1.2", @@ -87,7 +318,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -130,17 +361,27 @@ "node": ">= 0.8" } }, - "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "license": "MIT", "dependencies": { - "es-define-property": "^1.0.0", "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { "node": ">= 0.4" @@ -174,6 +415,18 @@ "fsevents": "~2.3.2" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -203,9 +456,9 @@ } }, "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -226,21 +479,13 @@ "ms": "2.0.0" } }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=0.4.0" } }, "node_modules/depd": { @@ -262,16 +507,45 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/discord-api-types": { + "version": "0.37.120", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.120.tgz", + "integrity": "sha512-7xpNK0EiWjjDFp2nAhHXezE4OUWm7s1zhc/UXXN6hnFFU8dfoPHgV0Hx0RPiCa3ILRpdeh152icc68DGCyXYIw==", + "license": "MIT" + }, "node_modules/discord-interactions": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/discord-interactions/-/discord-interactions-3.4.0.tgz", - "integrity": "sha512-DG0Jxdd/FcK8liAPhIP4u5YHpnz50JWn9DK4OavxsLD49/WGimXtP3EdOY439MaWyCgQfsfFkA1GsTEyu63RzA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/discord-interactions/-/discord-interactions-4.1.1.tgz", + "integrity": "sha512-dCrdJFSHQrEHXB8tk+LpIhAL7/+/UXCFDzooYmB6jj7b/zuhdbETpcZXLYQpSfcSRpF5wjmnu1XwuHlvdF1KpQ==", "license": "MIT", + "engines": { + "node": ">=18.4.0" + } + }, + "node_modules/discord.js": { + "version": "14.18.0", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.18.0.tgz", + "integrity": "sha512-SvU5kVUvwunQhN2/+0t55QW/1EHfB1lp0TtLZUSXVHDmyHTrdOj5LRKdR0zLcybaA15F+NtdWuWmGOX9lE+CAw==", + "license": "Apache-2.0", "dependencies": { - "tweetnacl": "^1.0.3" + "@discordjs/builders": "^1.10.1", + "@discordjs/collection": "1.5.3", + "@discordjs/formatters": "^0.6.0", + "@discordjs/rest": "^2.4.3", + "@discordjs/util": "^1.1.1", + "@discordjs/ws": "^1.2.1", + "@sapphire/snowflake": "3.5.3", + "discord-api-types": "^0.37.119", + "fast-deep-equal": "3.1.3", + "lodash.snakecase": "4.1.1", + "tslib": "^2.6.3", + "undici": "6.21.1" }, "engines": { - "node": ">=12" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" } }, "node_modules/dotenv": { @@ -286,6 +560,20 @@ "url": "https://dotenvx.com" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -293,22 +581,19 @@ "license": "MIT" }, "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, "engines": { "node": ">= 0.4" } @@ -322,6 +607,33 @@ "node": ">= 0.4" } }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -337,38 +649,47 @@ "node": ">= 0.6" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -377,8 +698,18 @@ }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -393,13 +724,13 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "license": "MIT", "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -410,6 +741,40 @@ "node": ">= 0.8" } }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data-encoder": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", + "license": "MIT" + }, + "node_modules/formdata-node": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", + "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "license": "MIT", + "dependencies": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + }, + "engines": { + "node": ">= 12.20" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -453,16 +818,21 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -471,6 +841,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", @@ -485,12 +868,12 @@ } }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.1.3" + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -506,22 +889,10 @@ "node": ">=4" } }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -530,11 +901,14 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, "engines": { "node": ">= 0.4" }, @@ -570,6 +944,15 @@ "node": ">= 0.8" } }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.0.0" + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -650,6 +1033,33 @@ "node": ">=0.12.0" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash.snakecase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", + "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", + "license": "MIT" + }, + "node_modules/magic-bytes.js": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.10.0.tgz", + "integrity": "sha512-/k20Lg2q8LE5xiaaSkMXk4sfvI+9EGEykFS4b0CHHGWqDYU0bGUFSwchNOMA56D7TCs9GwVTkqe9als1/ns8UQ==", + "license": "MIT" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -660,10 +1070,13 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", - "license": "MIT" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/methods": { "version": "1.1.2", @@ -735,6 +1148,57 @@ "node": ">= 0.6" } }, + "node_modules/node-cron": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz", + "integrity": "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==", + "license": "ISC", + "dependencies": { + "uuid": "8.3.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/nodemon": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.4.tgz", @@ -800,9 +1264,9 @@ } }, "node_modules/object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -823,6 +1287,51 @@ "node": ">= 0.8" } }, + "node_modules/openai": { + "version": "4.94.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.94.0.tgz", + "integrity": "sha512-WVmr9HWcwfouLJ7R3UHd2A93ClezTPuJljQxkCYQAL15Sjyt+FBNoqEz5MHSdH/ebQrVyvRhFyn/bvdqtSPyIA==", + "license": "Apache-2.0", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7" + }, + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/openai/node_modules/@types/node": { + "version": "18.19.86", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.86.tgz", + "integrity": "sha512-fifKayi175wLyKyc5qUfyENhQ1dCNI1UNjp653d8kuYcPQN5JhX3dGuP/XmvPTg/xRBn1VTLpbmi+H/Mr7tLfQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/openai/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "license": "MIT" + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -833,9 +1342,9 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "license": "MIT" }, "node_modules/picomatch": { @@ -872,12 +1381,12 @@ "license": "MIT" }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -963,9 +1472,9 @@ } }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "license": "MIT", "dependencies": { "debug": "2.6.9", @@ -986,6 +1495,15 @@ "node": ">= 0.8.0" } }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -993,37 +1511,20 @@ "license": "MIT" }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "license": "MIT", "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" } }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -1031,15 +1532,69 @@ "license": "ISC" }, "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -1115,11 +1670,23 @@ "nodetouch": "bin/nodetouch.js" } }, - "node_modules/tweetnacl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", - "license": "Unlicense" + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/ts-mixer": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz", + "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" }, "node_modules/type-is": { "version": "1.6.18", @@ -1141,6 +1708,21 @@ "dev": true, "license": "MIT" }, + "node_modules/undici": { + "version": "6.21.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.1.tgz", + "integrity": "sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -1159,6 +1741,15 @@ "node": ">= 0.4.0" } }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -1167,6 +1758,52 @@ "engines": { "node": ">= 0.8" } + }, + "node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } } } } diff --git a/package.json b/package.json index 035a2f2..0d9fb79 100644 --- a/package.json +++ b/package.json @@ -17,8 +17,11 @@ "license": "MIT", "dependencies": { "discord-interactions": "^4.0.0", + "discord.js": "^14.18.0", "dotenv": "^16.0.3", - "express": "^4.18.2" + "express": "^4.18.2", + "node-cron": "^3.0.3", + "openai": "^4.94.0" }, "devDependencies": { "nodemon": "^3.0.0" diff --git a/utils.js b/utils.js index 506aea6..3fd681b 100644 --- a/utils.js +++ b/utils.js @@ -1,4 +1,5 @@ import 'dotenv/config'; +import OpenAI from "openai"; export async function DiscordRequest(endpoint, options) { // append endpoint to root API URL @@ -45,3 +46,42 @@ export function getRandomEmoji() { export function capitalize(str) { return str.charAt(0).toUpperCase() + str.slice(1); } + +export async function getOnlineUsersWithRole(guildId, roleId) { + const endpoint = `/guilds/${guildId}/members?limit=1000`; + const response = await DiscordRequest(endpoint, { method: 'GET' }); + + const members = await response.json(); + return members.filter( + (m) => + m.roles.includes(roleId) && + m.presence?.status !== 'offline' + ); +} + +export function formatTime(time) { + const days = Math.floor(time / (24 * 60 * 60)); + const hours = Math.floor((time % (24 * 60 * 60)) / (60 * 60)); + const minutes = Math.floor((time % (60 * 60)) / 60); + const seconds = time % 60; + + const parts = []; + + if (days > 0) parts.push(`**${days}** jour${days > 1 ? 's' : ''}`); + if (hours > 0) parts.push(`**${hours}** heure${hours > 1 ? 's' : ''}`); + if (minutes > 0) parts.push(`**${minutes}** minute${minutes > 1 ? 's' : ''}`); + if (seconds > 0 || parts.length === 0) parts.push(`**${seconds}** seconde${seconds > 1 ? 's' : ''}`); + + return parts.join(', ').replace(/,([^,]*)$/, ' et$1'); +} + +export async function gork(messageHistory) { + const openai = new OpenAI(); + + const completion = await openai.chat.completions.create({ + model: "gpt-4.1-mini", + messages: messageHistory, + }); + + return completion.choices[0].message.content; +} \ No newline at end of file