diff --git a/.gitignore b/.gitignore index 97aca2e..a9022ad 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ -.env -node_modules \ No newline at end of file +node_modules +package-lock.json +dist +.env \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 3ebfd2d..da34478 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ FROM node:latest WORKDIR /app COPY . /app -CMD ["node", "bot.js"] \ No newline at end of file +CMD ["npm", "run", "start"] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..0dcec2c --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +# Discord JS template in Typescript + +## Installation + +- Clone the repository +- Run `npm i` and `npm i -g typescript tsx` to install all dependences +- Change `.envExemple` file to `.env` +- Enter your bot token and bot id in `.env` file + +## Developpement + +- You can copy the ping.ts file in `/src/commands/default` to start creating your own commands +- You can modify the files in `/src/events` to use events as you would + +## NPM commands + +- Run `npm run dev` to run the dev mode +- Tun `npm run build` to build the project +- Run `npm run start` to run the compiled code + +## Docker configuration + +- Change the service name and the full path to your bot in `docker-compose.yml` file +- Run `docker compose up` to run the container \ No newline at end of file diff --git a/bot.js b/bot.js deleted file mode 100644 index 076d6d4..0000000 --- a/bot.js +++ /dev/null @@ -1,87 +0,0 @@ -const fs = require("node:fs"); -const path = require("node:path"); -const { - Client, - Collection, - GatewayIntentBits, - Partials, - REST, - Routes, -} = require("discord.js"); -require("dotenv").config(); -const { sendLog } = require("./libs/logs.js"); - -const client = new Client({ - intents: [ - GatewayIntentBits.Guilds, - GatewayIntentBits.GuildMessages, - GatewayIntentBits.DirectMessages, - GatewayIntentBits.MessageContent, - ], - partials: [Partials.Channel, Partials.Message], -}); - -client.commands = new Collection(); -const commands = []; -const foldersPath = path.join(__dirname, "commands"); -const commandFolders = fs.readdirSync(foldersPath); - -for (const folder of commandFolders) { - const commandsPath = path.join(foldersPath, folder); - const commandFiles = fs - .readdirSync(commandsPath) - .filter((file) => file.endsWith(".js")); - - for (const file of commandFiles) { - const filePath = path.join(commandsPath, file); - const command = require(filePath); - if ("data" in command && "execute" in command) { - client.commands.set(command.data.name, command); - commands.push(command.data.toJSON()); - } else { - console.log( - `[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.` - ); - } - } -} - -const rest = new REST().setToken(process.env.DISCORD_TOKEN); - -(async () => { - try { - console.log( - `Started refreshing ${commands.length} application (/) commands.` - ); - sendLog(`Started refreshing ${commands.length} application (/) commands.`); - - const data = await rest.put( - Routes.applicationCommands(process.env.BOT_ID), - { body: commands } - ); - - console.log( - `Successfully reloaded ${data.length} application (/) commands.` - ); - sendLog(`Successfully reloaded ${data.length} application (/) commands.`); - } catch (error) { - console.error(error); - } -})(); - -const eventsPath = path.join(__dirname, "events"); -const eventFiles = fs - .readdirSync(eventsPath) - .filter((file) => file.endsWith(".js")); - -for (const file of eventFiles) { - const filePath = path.join(eventsPath, file); - const event = require(filePath); - if (event.once) { - client.once(event.name, (...args) => event.execute(...args)); - } else { - client.on(event.name, (...args) => event.execute(...args)); - } -} - -client.login(process.env.DISCORD_TOKEN); diff --git a/commands/fun/ping.js b/commands/fun/ping.js deleted file mode 100644 index 2c5a733..0000000 --- a/commands/fun/ping.js +++ /dev/null @@ -1,11 +0,0 @@ -const { SlashCommandBuilder } = require("discord.js"); - -module.exports = { - data: new SlashCommandBuilder() - .setName("ping") - .setDescription("Replies with Pong!"), - async execute(interaction) { - console.log("ping command executed"); - await interaction.reply("Pong!"); - }, -}; diff --git a/commands/hubs/addhub.js b/commands/hubs/addhub.js deleted file mode 100644 index 905de73..0000000 --- a/commands/hubs/addhub.js +++ /dev/null @@ -1,43 +0,0 @@ -const { - SlashCommandBuilder, - EmbedBuilder, - ButtonBuilder, - ActionRowBuilder, - ButtonStyle, - PermissionsBitField, -} = require("discord.js"); -const { errorEmbed } = require("../../libs/embeds.js"); - -module.exports = { - data: new SlashCommandBuilder() - .setName("addhub") - .setDescription("Add a conversation hub to the database.") - .setDMPermission(false), - async execute(interaction) { - if ( - !interaction.member.permissions.has( - PermissionsBitField.Flags.Administrator - ) - ) { - const embed = errorEmbed( - "You need the administrator permission to use this command." - ); - return interaction.reply({ embeds: [embed] }); - } - - const embed = new EmbedBuilder() - .setTitle("Conversation hub") - .setDescription("Click on the button below to create a conversation.") - .setColor("#F6C6F9") - .setFooter({ text: "Bot by @ninja_jambon" }); - - const button = new ButtonBuilder() - .setCustomId("create_conversation") - .setLabel("Create conversation") - .setStyle(ButtonStyle.Success); - - const actionRow = new ActionRowBuilder().addComponents(button); - - await interaction.reply({ embeds: [embed], components: [actionRow] }); - }, -}; diff --git a/commands/hubs/closeconv.js b/commands/hubs/closeconv.js deleted file mode 100644 index cf77be4..0000000 --- a/commands/hubs/closeconv.js +++ /dev/null @@ -1,37 +0,0 @@ -const { SlashCommandBuilder } = require("discord.js"); -const { getConv, removeConv } = require("../../libs/mysql.js"); -const { errorEmbed } = require("../../libs/embeds.js"); -const { sendLog } = require("../../libs/logs.js"); - -module.exports = { - data: new SlashCommandBuilder() - .setName("closeconv") - .setDescription("Close the current conversation.") - .setDMPermission(false), - async execute(interaction) { - const conv = await getConv(interaction.channelId).catch((err) => { - sendLog(err); - - const embed = errorEmbed( - "An error occured while trying to get your conversation data." - ); - - return interaction.reply({ embeds: [embed], ephemeral: true }); - }); - - if (!conv[0]) { - const embed = errorEmbed("This channel is not a conversation."); - return interaction.reply({ embeds: [embed], ephemeral: true }); - } - - if (conv[0].userid != interaction.user.id) { - const embed = errorEmbed("You are not the owner of this conversation."); - return interaction.reply({ embeds: [embed], ephemeral: true }); - } - - var channel = interaction.guild.channels.cache.get(interaction.channelId); - - await removeConv(channel.id); - await channel.delete(); - }, -}; diff --git a/commands/mistral/mistral.js b/commands/mistral/mistral.js deleted file mode 100644 index ac4cbb4..0000000 --- a/commands/mistral/mistral.js +++ /dev/null @@ -1,32 +0,0 @@ -const { SlashCommandBuilder } = require("discord.js"); -//const { sendConv } = require("../../libs/mistralAiFunctions"); -//const data = require("../../data.json"); - -module.exports = { - data: new SlashCommandBuilder() - .setName("mistral") - .setDescription("Talk to Mistral AI") - .addSubcommand((subcommand) => - subcommand - .setName("medium") - .setDescription("Talk to Mistral AI using the medium model") - .addStringOption((option) => - option - .setName("message") - .setDescription("What do you want to say to Mistral AI?") - .setRequired(true) - ) - ), - async execute(interaction) { - /*if (interaction.options.getSubcommand() === "medium") { - const message = interaction.options.getString("message"); - messages = [ - { role: system, text: data.prompt }, - { role: user, text: message }, - ]; - const chatResponse = await sendConv(messages); - console.log(chatResponse); - await interaction.reply(chatResponse); - }*/ - }, -}; diff --git a/commands/others/botinfo.js b/commands/others/botinfo.js deleted file mode 100644 index cd4898f..0000000 --- a/commands/others/botinfo.js +++ /dev/null @@ -1,31 +0,0 @@ -const { SlashCommandBuilder, EmbedBuilder } = require("discord.js"); -const { getQuotasSum } = require("../../libs/mysql.js"); - -module.exports = { - data: new SlashCommandBuilder() - .setName("botinfo") - .setDescription("Get information about the bot."), - async execute(interaction) { - const quotasSum = await getQuotasSum(); - - const embed = new EmbedBuilder() - .setColor("#F6C6F9") - .setTitle("Bot Info") - .setDescription("Information about the bot.") - .addFields( - { - name: "Guilds", - value: interaction.client.guilds.cache.size.toString(), - inline: false, - }, - { - name: "Total quota", - value: `${quotasSum[0]["SUM(quota)"]}$`, - inline: false, - } - ) - .setFooter({ text: "Bot by @ninja_jambon" }); - - interaction.reply({ embeds: [embed] }); - }, -}; diff --git a/commands/others/getquota.js b/commands/others/getquota.js deleted file mode 100644 index 75ba477..0000000 --- a/commands/others/getquota.js +++ /dev/null @@ -1,36 +0,0 @@ -const { SlashCommandBuilder, EmbedBuilder } = require("discord.js"); -const { getUser } = require("../../libs/mysql.js"); -const { errorEmbed } = require("../../libs/embeds.js"); -const { sendLog } = require("../../libs/logs.js"); - -module.exports = { - data: new SlashCommandBuilder() - .setName("getquota") - .setDescription("Get your current quota.") - .setDMPermission(false), - async execute(interaction) { - const user = await getUser(interaction.user.id).catch((err) => { - sendLog(err); - - const embed = errorEmbed( - "An error occured while trying to get your user data." - ); - - return interaction.reply({ embeds: [embed], ephemeral: true }); - }); - - if (!user[0]) { - const embed = errorEmbed("You don't have any quota yet."); - - return interaction.reply({ embeds: [embed], ephemeral: true }); - } - - const embed = new EmbedBuilder() - .setColor("#F6C6F9") - .setTitle("Quota") - .setDescription(`You have ${0.4 - user[0].quota}$ of credits left.`) - .setFooter({ text: "Bot by @ninja_jambon" }); - - await interaction.reply({ embeds: [embed] }); - }, -}; diff --git a/commands/others/help.js b/commands/others/help.js deleted file mode 100644 index 99384a2..0000000 --- a/commands/others/help.js +++ /dev/null @@ -1,34 +0,0 @@ -const { SlashCommandBuilder, EmbedBuilder } = require("discord.js"); - -module.exports = { - data: new SlashCommandBuilder() - .setName("help") - .setDescription("Get help about the bot."), - async execute(interaction) { - const helpMessage = ` -**Single requests:** -- **/quickgpt**: Make a single request to the GPT-3.5 Turbo model. -- **/gptrequest**: Make a single request to the GPT-4 model. - -**Conversations:** -- **/addhub**: Add a conversation hub to the channel, user needs to have the ADMINISTRATOR permission and the channel needs to be a forum channel or a normal text channel. -- **/closeconv**: Close the conversation in your channel, user needs to be the creator of the conversation. - -**Others:** -- **/getquota**: Display your quota, you can use a total of 0.4$ of quota per month. -- **/botinfo**: Display information about the bot. - -**Links:** -- [Invite the bot](https://discord.com/api/oauth2/authorize?client_id=1059559067846189067&permissions=326417632256&scope=bot) -- [Support server](https://discord.gg/WcZPz3nm5p) -`; - - const embed = new EmbedBuilder() - .setColor("#F6C6F9") - .setTitle("Help") - .setDescription(helpMessage) - .setFooter({ text: "Bot by @ninja_jambon" }); - - interaction.reply({ embeds: [embed] }); - }, -}; diff --git a/commands/single requests/gptrequest.js b/commands/single requests/gptrequest.js deleted file mode 100644 index f9076a6..0000000 --- a/commands/single requests/gptrequest.js +++ /dev/null @@ -1,107 +0,0 @@ -const { SlashCommandBuilder } = require("discord.js"); -const { - getUser, - registerUser, - incrementQuota, -} = require("../../libs/mysql.js"); -const { answerQuestion } = require("../../libs/openAi.js"); -const { checkLastResetDate } = require("../../libs/quotaReset.js"); -const { requestResponseEmbed, errorEmbed } = require("../../libs/embeds.js"); -const { sendLog } = require("../../libs/logs.js"); - -module.exports = { - data: new SlashCommandBuilder() - .setName("gptrequest") - .setDescription("Make a single request to the GPT-4 API.") - .addStringOption((option) => - option - .setName("prompt") - .setDescription("The prompt to send to the API.") - .setRequired(true) - ), - async execute(interaction) { - interaction.deferReply(); - await checkLastResetDate(); - user = await getUser(interaction.user.id).catch((err) => { - sendLog(err); - - const embed = errorEmbed( - "An error occured while trying to get your user data." - ); - - return interaction.editReply({ embeds: [embed], ephemeral: true }); - }); - - if (!user[0]) { - await registerUser(interaction.user.username, interaction.user.id).catch( - (err) => { - sendLog(err); - - const embed = errorEmbed( - "An error occured while trying to register you in our database." - ); - - return interaction.editReply({ embeds: [embed], ephemeral: true }); - } - ); - - user = await getUser(interaction.user.id).catch((err) => { - sendLog(err); - - const embed = errorEmbed( - "An error occured while trying to get your user data." - ); - - return interaction.editReply({ embeds: [embed], ephemeral: true }); - }); - } - - if (user[0].quota >= 0.4) { - const embed = errorEmbed( - "You don't have enough quota to use this command." - ); - - return await interaction.editReply({ embeds: [embed], ephemeral: true }); - } - - response = await answerQuestion( - interaction.options.getString("prompt") - ).catch((err) => { - sendLog(err); - - const embed = errorEmbed( - "An error occured while trying to send the request to the API." - ); - - return interaction.editReply({ embeds: [embed], ephemeral: true }); - }); - - const prompt_usage = (response.data.usage.prompt_tokens * 0.01) / 1000; - const completion_usage = - (response.data.usage.completion_tokens * 0.03) / 1000; - - await incrementQuota( - interaction.user.id, - prompt_usage + completion_usage - ).catch((err) => { - sendLog(err); - - const embed = errorEmbed( - "An error occured while trying to increment your quota." - ); - - return interaction.editReply({ embeds: [embed], ephemeral: true }); - }); - - const embed = requestResponseEmbed( - interaction.user, - interaction.options.getString("prompt"), - response.data.choices[0].message.content, - user[0].quota, - prompt_usage, - completion_usage - ); - - await interaction.editReply({ embeds: [embed] }); - }, -}; diff --git a/commands/single requests/quickgpt.js b/commands/single requests/quickgpt.js deleted file mode 100644 index 96bb18d..0000000 --- a/commands/single requests/quickgpt.js +++ /dev/null @@ -1,107 +0,0 @@ -const { SlashCommandBuilder } = require("discord.js"); -const { - getUser, - registerUser, - incrementQuota, -} = require("../../libs/mysql.js"); -const { quickAnswer } = require("../../libs/openAi.js"); -const { checkLastResetDate } = require("../../libs/quotaReset.js"); -const { requestResponseEmbed, errorEmbed } = require("../../libs/embeds.js"); -const { sendLog } = require("../../libs/logs.js"); - -module.exports = { - data: new SlashCommandBuilder() - .setName("quickgpt") - .setDescription("Make a single request to the GPT-3.5-turbo API.") - .addStringOption((option) => - option - .setName("prompt") - .setDescription("The prompt to send to the API.") - .setRequired(true) - ), - async execute(interaction) { - await interaction.deferReply(); - await checkLastResetDate(); - user = await getUser(interaction.user.id).catch((err) => { - sendLog(err); - - const embed = errorEmbed( - "An error occured while trying to get your user data." - ); - - return interaction.editReply({ embeds: [embed], ephemeral: true }); - }); - - if (!user[0]) { - await registerUser(interaction.user.username, interaction.user.id).catch( - (err) => { - sendLog(err); - - const embed = errorEmbed( - "An error occured while trying to register you in our database." - ); - - return interaction.editReply({ embeds: [embed], ephemeral: true }); - } - ); - - user = await getUser(interaction.user.id).catch((err) => { - sendLog(err); - - const embed = errorEmbed( - "An error occured while trying to get your user data." - ); - - return interaction.editReply({ embeds: [embed], ephemeral: true }); - }); - } - - if (user[0].quota >= 0.4) { - const embed = errorEmbed( - "You don't have enough quota to use this command." - ); - - return await interaction.editReply({ embeds: [embed], ephemeral: true }); - } - - response = await quickAnswer(interaction.options.getString("prompt")).catch( - (err) => { - sendLog(err); - - const embed = errorEmbed( - "An error occured while trying to send the request to the API." - ); - - return interaction.editReply({ embeds: [embed], ephemeral: true }); - } - ); - - const prompt_usage = (response.data.usage.prompt_tokens * 0.001) / 1000; - const completion_usage = - (response.data.usage.completion_tokens * 0.002) / 1000; - - await incrementQuota( - interaction.user.id, - prompt_usage + completion_usage - ).catch((err) => { - sendLog(err); - - const embed = errorEmbed( - "An error occured while trying to increment your quota." - ); - - return interaction.editReply({ embeds: [embed], ephemeral: true }); - }); - - const embed = requestResponseEmbed( - interaction.user, - interaction.options.getString("prompt"), - response.data.choices[0].message.content, - user[0].quota, - prompt_usage, - completion_usage - ); - - await interaction.editReply({ embeds: [embed] }); - }, -}; diff --git a/config.json b/config.json new file mode 100644 index 0000000..78601fe --- /dev/null +++ b/config.json @@ -0,0 +1,3 @@ +{ + "defaultPrompt": "You are an helpful assistant, you always answer in the language of the user." +} \ No newline at end of file diff --git a/data/prompt.json b/data/prompt.json deleted file mode 100644 index fa761e4..0000000 --- a/data/prompt.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "prompt": "Tu es un bot discord s'appelant NekoSuru, tu es une catgirl futuriste. Tu es amicale et tutois les utilisateurs." -} \ No newline at end of file diff --git a/events/interactionCreate.js b/events/interactionCreate.js deleted file mode 100644 index 8208872..0000000 --- a/events/interactionCreate.js +++ /dev/null @@ -1,120 +0,0 @@ -const { - Events, - ButtonBuilder, - ActionRowBuilder, - ButtonStyle, -} = require("discord.js"); -const { addConv, removeConv, getConv } = require("../libs/mysql.js"); -const { - errorEmbed, - convBeginEmbed, - convCreatedEmbed, -} = require("../libs/embeds.js"); -const { sendLog } = require("../libs/logs.js"); - -module.exports = { - name: Events.InteractionCreate, - async execute(interaction) { - if (interaction.isChatInputCommand()) { - const command = interaction.client.commands.get(interaction.commandName); - - if (!command) { - console.error( - `No command matching ${interaction.commandName} was found.` - ); - return; - } - - try { - await command.execute(interaction); - } catch (error) { - sendLog(error); - } - } - - if (interaction.isButton()) { - if (interaction.customId == "create_conversation") { - var channel = interaction.guild.channels.cache.get( - interaction.message.channelId - ); - - if (channel.type == 11) { - channel = interaction.guild.channels.cache.get(channel.parentId); - } - - const embed = convBeginEmbed(); - - const button = new ButtonBuilder() - .setCustomId("close_conversation") - .setLabel("Close conversation") - .setStyle(ButtonStyle.Danger); - - const actionRow = new ActionRowBuilder().addComponents(button); - - channel = await channel.threads - .create({ - name: interaction.user.username + "'s conversation", - message: { - embeds: [embed], - components: [actionRow], - }, - }) - .catch((err) => { - sendLog(err); - }); - - const embed2 = convCreatedEmbed(channel.id); - - await addConv( - interaction.user.id, - channel.id, - interaction.guild.id - ).catch((err) => { - sendLog(err); - const embed = errorEmbed( - "An error occured while creating the conversation." - ); - return interaction.reply({ embeds: [embed], ephemeral: true }); - }); - - await interaction.reply({ embeds: [embed2], ephemeral: true }); - } else if (interaction.customId == "close_conversation") { - const conv = await getConv(interaction.message.channelId).catch( - (err) => { - sendLog(err); - const embed = errorEmbed( - "An error occured while closing the conversation." - ); - return interaction.reply({ embeds: [embed], ephemeral: true }); - } - ); - - if (conv[0].userid != interaction.user.id) { - const embed = errorEmbed("You can't close this conversation."); - - return interaction.reply({ embeds: [embed], ephemeral: true }); - } - - var channel = interaction.guild.channels.cache.get( - interaction.message.channelId - ); - - await removeConv(channel.id).catch((err) => { - sendLog(err); - const embed = errorEmbed( - "An error occured while closing the conversation." - ); - return interaction.reply({ embeds: [embed], ephemeral: true }); - }); - - await channel.delete().catch((err) => { - sendLog(err); - const embed = errorEmbed( - "An error occured while closing the conversation." - ); - return interaction.reply({ embeds: [embed], ephemeral: true }); - }); - } - } - }, -}; diff --git a/events/messageCreate.js b/events/messageCreate.js deleted file mode 100644 index aa240c0..0000000 --- a/events/messageCreate.js +++ /dev/null @@ -1,315 +0,0 @@ -const { Events, EmbedBuilder } = require("discord.js"); -const { - getConv, - getUser, - registerUser, - incrementQuota, -} = require("../libs/mysql.js"); -const { sendQuickConv, quickAnswer } = require("../libs/openAi.js"); -const { checkLastResetDate } = require("../libs/quotaReset.js"); -const prompt = require("../data/prompt.json").prompt; -require("dotenv").config(); -const { errorEmbed } = require("../libs/embeds.js"); -const { sendLog } = require("../libs/logs.js"); - -module.exports = { - name: Events.MessageCreate, - async execute(message) { - conv = await getConv(message.channel.id); - - if (!message.guildId && message.author.id != "1059559067846189067") { - await checkLastResetDate(); - user = await getUser(message.author.id).catch((err) => { - sendLog(err); - const embed = errorEmbed( - "An error occured while trying to get your data from the database." - ); - return message.reply({ embeds: [embed] }); - }); - - if (!user[0]) { - await registerUser(message.author.username, message.author.id).catch( - (err) => { - sendLog(err); - const embed = errorEmbed( - "An error occured while trying to register you in the database." - ); - return message.reply({ embeds: [embed] }); - } - ); - - user = await getUser(message.author.id).catch((err) => { - sendLog(err); - const embed = errorEmbed( - "An error occured while trying to get your data from the database." - ); - return message.reply({ embeds: [embed] }); - }); - } - - if (user[0].quota >= 0.4) - return await message.reply({ - content: "You don't have enough quota to talk with the bot.", - ephemeral: true, - }); - - discordMessages = await message.channel.messages.fetch().catch((err) => { - sendLog(err); - const embed = errorEmbed( - "An error occured while trying to fetch the messages from the channel." - ); - return message.reply({ embeds: [embed] }); - }); - - discordMessages.filter((message) => message.content); - messages = []; - var i = 0; - discordMessages.forEach(async (message) => { - if (i == 6) return; - if (message.author.id == "1059559067846189067") { - messages.push({ role: "assistant", content: message.content }); - i++; - } else { - messages.push({ role: "user", content: message.content }); - i++; - } - }); - messages.reverse(); - messages.unshift({ role: "system", content: prompt }); - - message.channel.sendTyping().catch((err) => { - sendLog(err); - }); - - const response = await sendQuickConv(messages).catch((err) => { - sendLog(err); - const embed = errorEmbed( - "An error occured while trying to send the request to the API." - ); - return message.reply({ embeds: [embed] }); - }); - - const prompt_usage = (response.data.usage.prompt_tokens * 0.001) / 1000; - const completion_usage = - (response.data.usage.completion_tokens * 0.002) / 1000; - - await incrementQuota( - message.author.id, - prompt_usage + completion_usage - ).catch((err) => { - sendLog(err); - const embed = errorEmbed( - "An error occured while trying to increment your quota." - ); - return message.reply({ embeds: [embed] }); - }); - - if (response.data.choices[0].message.content.length <= 2000) { - await message.reply(response.data.choices[0].message.content); - } else { - let paragraphs = response.data.choices[0].message.content.split("\n"); - messageText = ""; - - paragraphs.forEach((paragraph) => { - if (`${messageText}${paragraph}`.length > 2000) { - message.reply(messageText); - messageText = `${paragraph}\n`; - } else { - messageText += `${paragraph}\n`; - } - }); - } - } else if ( - message.content.includes(`<@${process.env.BOT_ID}>`) || - (message.mentions.repliedUser && - message.mentions.repliedUser.id == process.env.BOT_ID) - ) { - await checkLastResetDate(); - user = await getUser(message.author.id).catch((err) => { - sendLog(err); - const embed = errorEmbed( - "An error occured while trying to get your data from the database." - ); - return message.reply({ embeds: [embed] }); - }); - - if (!user[0]) { - await registerUser(message.author.username, message.author.id).catch( - (err) => { - sendLog(err); - const embed = errorEmbed( - "An error occured while trying to register you in the database." - ); - return message.reply({ embeds: [embed] }); - } - ); - - user = await getUser(message.author.id).catch((err) => { - sendLog(err); - const embed = errorEmbed( - "An error occured while trying to get your data from the database." - ); - return message.reply({ embeds: [embed] }); - }); - } - - if (user[0].quota >= 0.4) - return await message.reply({ - content: "You don't have enough quota to use this command.", - ephemeral: true, - }); - - message.channel.sendTyping().catch((err) => { - sendLog(err); - }); - - response = await quickAnswer(message.content).catch((err) => { - sendLog(err); - const embed = errorEmbed( - "An error occured while trying to send the request to the API." - ); - return message.reply({ embeds: [embed] }); - }); - - const prompt_usage = (response.data.usage.prompt_tokens * 0.01) / 1000; - const completion_usage = - (response.data.usage.completion_tokens * 0.03) / 1000; - - await incrementQuota( - message.author.id, - prompt_usage + completion_usage - ).catch((err) => { - sendLog(err); - const embed = errorEmbed( - "An error occured while trying to increment your quota." - ); - return message.reply({ embeds: [embed] }); - }); - - if (response.data.choices[0].message.content.length <= 2000) { - await message.reply({ - content: response.data.choices[0].message.content, - }); - } else { - const embed = new EmbedBuilder() - .setTitle("Answer") - .setDescription(response.data.choices[0].message.content) - .setColor("#F6C6F9") - .setFooter({ text: "Bot by @ninja_jambon" }); - - await message.reply({ embeds: [embed] }); - } - } else if ( - conv[0] && - message.author.id != "1059559067846189067" && - conv[0].userid == message.author.id - ) { - await checkLastResetDate(); - user = await getUser(message.author.id).catch((err) => { - sendLog(err); - const embed = errorEmbed( - "An error occured while trying to get your data from the database." - ); - return message.reply({ embeds: [embed] }); - }); - - if (!user[0]) { - await registerUser(message.author.username, message.author.id).catch( - (err) => { - sendLog(err); - const embed = errorEmbed( - "An error occured while trying to register you in the database." - ); - return message.reply({ embeds: [embed] }); - } - ); - - user = await getUser(message.author.id).catch((err) => { - sendLog(err); - const embed = errorEmbed( - "An error occured while trying to get your data from the database." - ); - return message.reply({ embeds: [embed] }); - }); - } - - if (user[0].quota >= 0.4) - return await message.reply({ - content: "You don't have enough quota to talk with the bot.", - ephemeral: true, - }); - - discordMessages = await message.channel.messages.fetch().catch((err) => { - sendLog(err); - const embed = errorEmbed( - "An error occured while trying to fetch the messages from the channel." - ); - return message.reply({ embeds: [embed] }); - }); - - discordMessages.filter( - (message) => - (message.author.id == "1059559067846189067" || - message.author.id == conv[0].userid) && - message.content - ); - messages = []; - var i = 0; - discordMessages.forEach(async (message) => { - if (i == 6) return; - if (message.author.id == "1059559067846189067") { - messages.push({ role: "assistant", content: message.content }); - i++; - } else if (message.author.id == conv[0].userid) { - messages.push({ role: "user", content: message.content }); - i++; - } - }); - messages.reverse(); - messages.unshift({ role: "system", content: prompt }); - - message.channel.sendTyping().catch((err) => { - sendLog(err); - }); - - const response = await sendQuickConv(messages).catch((err) => { - sendLog(err); - const embed = errorEmbed( - "An error occured while trying to send the request to the API." - ); - return message.reply({ embeds: [embed] }); - }); - - const prompt_usage = (response.data.usage.prompt_tokens * 0.001) / 1000; - const completion_usage = - (response.data.usage.completion_tokens * 0.002) / 1000; - - await incrementQuota( - message.author.id, - prompt_usage + completion_usage - ).catch((err) => { - sendLog(err); - const embed = errorEmbed( - "An error occured while trying to increment your quota." - ); - return message.reply({ embeds: [embed] }); - }); - - if (response.data.choices[0].message.content.length <= 2000) { - await message.reply(response.data.choices[0].message.content); - } else { - let paragraphs = response.data.choices[0].message.content.split("\n"); - messageText = ""; - - paragraphs.forEach((paragraph) => { - if (`${messageText}${paragraph}`.length > 2000) { - message.reply(messageText); - messageText = `${paragraph}\n`; - } else { - messageText += `${paragraph}\n`; - } - }); - } - } - }, -}; diff --git a/events/ready.js b/events/ready.js deleted file mode 100644 index 6e4bc4d..0000000 --- a/events/ready.js +++ /dev/null @@ -1,23 +0,0 @@ -const { Events } = require("discord.js"); -const { sendLog } = require("../libs/logs.js"); - -module.exports = { - name: Events.ClientReady, - once: true, - execute(client) { - console.log(`Ready! Logged in as ${client.user.tag}`); - sendLog(`Ready! Logged in as ${client.user.tag}`); - - client.user.setPresence({ - activities: [{ name: client.guilds.cache.size + " servers !", type: 3 }], - }); - - setInterval(() => { - client.user.setPresence({ - activities: [ - { name: client.guilds.cache.size + " servers !", type: 3 }, - ], - }); - }, 10000); - }, -}; diff --git a/libs/embeds.js b/libs/embeds.js deleted file mode 100644 index 6ba5629..0000000 --- a/libs/embeds.js +++ /dev/null @@ -1,62 +0,0 @@ -const { EmbedBuilder } = require("discord.js"); - -function errorEmbed(error) { - return new EmbedBuilder() - .setTitle("Error") - .setDescription(error) - .setColor("#F6C6F9") - .setFooter({ text: "Bot by @ninja_jambon" }); -} - -function convBeginEmbed() { - return new EmbedBuilder() - .setTitle("Conversation beginning") - .setDescription( - "Click on the button below or use the command **/closeconv** to close the conversation." - ) - .setColor("#F6C6F9") - .setFooter({ text: "Bot by @ninja_jambon" }); -} - -function convCreatedEmbed(channelId) { - return new EmbedBuilder() - .setTitle("Conversation created") - .setDescription(`Your conversation has been created at <#${channelId}>.`) - .setColor("#F6C6F9") - .setFooter({ text: "Bot by @ninja_jambon" }); -} - -function requestResponseEmbed( - user, - prompt, - response, - quota, - prompt_usage, - completion_usage -) { - return new EmbedBuilder() - .setAuthor({ - name: user.username, - iconURL: - "https://cdn.discordapp.com/avatars/" + - user.id + - "/" + - user.avatar + - ".jpeg", - }) - .setTitle(prompt) - .setDescription(response) - .setFooter({ - text: `Quota used ${prompt_usage + completion_usage}$ | New quota: ${ - quota + prompt_usage + completion_usage - }$ | Quota remaining : ${0.4 - prompt_usage - completion_usage}$`, - }) - .setColor("#F6C6F9"); -} - -module.exports = { - errorEmbed, - convBeginEmbed, - convCreatedEmbed, - requestResponseEmbed, -}; diff --git a/libs/logs.js b/libs/logs.js deleted file mode 100644 index 57257c7..0000000 --- a/libs/logs.js +++ /dev/null @@ -1,20 +0,0 @@ -const { EmbedBuilder, WebhookClient } = require("discord.js"); - -const webhookClient = new WebhookClient({ - url: `https://discord.com/api/webhooks/1187067107054202961/M7bsyOwFPMXQTMB8tvrWZu-gLT9rSjl1NASOBrz-z4lwvbwQ9To_yAywE_4aj5oGBP0D`, -}); - -function sendLog(message) { - const embed = new EmbedBuilder() - .setTitle("Log") - .setDescription(message) - .setColor(0x00ffff); - - webhookClient.send({ - embeds: [embed], - }); -} - -module.exports = { - sendLog, -}; diff --git a/libs/mistralAi.js b/libs/mistralAi.js deleted file mode 100644 index e69de29..0000000 diff --git a/libs/mysql.js b/libs/mysql.js deleted file mode 100644 index 6bf2660..0000000 --- a/libs/mysql.js +++ /dev/null @@ -1,110 +0,0 @@ -var mysql = require("mysql"); -require("dotenv").config(); - -var con = mysql.createConnection({ - host: process.env.MYSQL_HOST, - user: process.env.MYSQL_USER, - password: process.env.MYSQL_PASSWORD, - database: process.env.MYSQL_DATABASE, -}); - -function registerUser(username, userid) { - return new Promise((resolve, reject) => { - con.query( - `INSERT INTO users (username, userid, quota) VALUES ("${username}", "${userid}", 0)`, - (err, result) => { - if (err) reject(err); - resolve(result); - } - ); - }); -} - -function getUser(userid) { - return new Promise((resolve, reject) => { - con.query( - `SELECT * FROM users WHERE userid = "${userid}"`, - (err, result) => { - if (err) reject(err); - resolve(result); - } - ); - }); -} - -function incrementQuota(user, value) { - return new Promise((resolve, reject) => { - con.query( - `UPDATE users SET quota = quota + ${value} WHERE userid = "${user}"`, - (err, result) => { - if (err) reject(err); - resolve(result); - } - ); - }); -} - -function addConv(userid, channelid, guildid) { - return new Promise((resolve, reject) => { - con.query( - `INSERT INTO convs (userid, channelid, guildid) VALUES ("${userid}", "${channelid}", "${guildid}")`, - (err, result) => { - if (err) reject(err); - resolve(result); - } - ); - }); -} - -function removeConv(channelid) { - return new Promise((resolve, reject) => { - con.query( - `DELETE FROM convs WHERE channelid = "${channelid}"`, - (err, result) => { - if (err) reject(err); - resolve(result); - } - ); - }); -} - -function getConv(channelid) { - return new Promise((resolve, reject) => { - con.query( - `SELECT * FROM convs WHERE channelid = "${channelid}"`, - (err, result) => { - if (err) reject(err); - resolve(result); - } - ); - }); -} - -function getQuotasSum() { - return new Promise((resolve, reject) => { - con.query(`SELECT SUM(quota) FROM users`, (err, result) => { - if (err) reject(err); - resolve(result); - }); - }); -} - -function resetQuotas() { - return new Promise((resolve, reject) => { - con.query(`UPDATE users SET quota = 0`, (err, result) => { - if (err) reject(err); - resolve(result); - }); - }); -} - -module.exports = { - registerUser, - getUser, - incrementQuota, - addConv, - removeConv, - getConv, - getQuotasSum, - resetQuotas, -}; diff --git a/libs/openAi.js b/libs/openAi.js deleted file mode 100644 index 5379b4d..0000000 --- a/libs/openAi.js +++ /dev/null @@ -1,84 +0,0 @@ -const { Configuration, OpenAIApi } = require("openai"); -const prompt = require("../data/prompt.json").prompt; - -const configuration = new Configuration({ - apiKey: process.env.OPENAI, -}); - -const openai = new OpenAIApi(configuration); - -async function answerQuestion(query) { - return new Promise((resolve, reject) => { - openai - .createChatCompletion({ - model: "gpt-4-1106-preview", - messages: [ - { role: "system", content: prompt }, - { role: "user", content: query }, - ], - temperature: 0.9, - }) - .then((res) => { - resolve(res); - }) - .catch((err) => { - reject(err); - }); - }); -} - -async function quickAnswer(query) { - return new Promise((resolve, reject) => { - openai - .createChatCompletion({ - model: "gpt-3.5-turbo-1106", - messages: [ - { role: "system", content: prompt }, - { role: "user", content: query }, - ], - temperature: 0.9, - }) - .then((res) => { - resolve(res); - }) - .catch((err) => { - reject(err); - }); - }); -} - -async function sendConv(messages) { - return new Promise((resolve, reject) => { - openai - .createChatCompletion({ - model: "gpt-4-1106-preview", - messages: messages, - temperature: 0.9, - }) - .then((res) => { - resolve(res); - }) - .catch((err) => { - reject(err); - }); - }); -} - -async function sendQuickConv(messages) { - return new Promise((resolve, reject) => { - openai - .createChatCompletion({ - model: "gpt-3.5-turbo-1106", - messages: messages, - temperature: 0.9, - }) - .then((res) => { - resolve(res); - }) - .catch((err) => { - reject(err); - }); - }); -} - -module.exports = { answerQuestion, sendConv, quickAnswer, sendQuickConv }; diff --git a/libs/quotaReset.js b/libs/quotaReset.js deleted file mode 100644 index c31e8d7..0000000 --- a/libs/quotaReset.js +++ /dev/null @@ -1,23 +0,0 @@ -const fs = require("fs"); -const { resetQuotas } = require("./mysql.js"); - -function getLastResetDate() { - const data = fs.readFileSync("./data/lastReset", "utf8"); - return parseInt(data); -} - -function checkLastResetDate() { - const lastResetDate = getLastResetDate(); - const now = Date.now(); - - if (now - lastResetDate > 1000 * 60 * 60 * 24 * 30) { - fs.writeFileSync("./data/lastReset", now.toString()); - return resetQuotas(); - } else { - return false; - } -} - -module.exports = { - checkLastResetDate, -}; diff --git a/package-lock.json b/package-lock.json index b98b8af..49929b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,123 +1,154 @@ { - "name": "chaise_bot_2.0", - "version": "1.0.0", + "name": "chaise_bot_3.0", + "version": "3.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "chaise_bot_2.0", - "version": "1.0.0", + "name": "chaise_bot_3.0", + "version": "3.0.0", "license": "ISC", "dependencies": { - "axios": "^1.6.7", - "discord.js": "^14.12.1", - "dotenv": "^16.3.1", - "mysql": "^2.18.1", - "openai": "^3.3.0" + "@mistralai/mistralai": "^0.1.3", + "@types/mysql": "^2.15.26", + "discord.js": "^14.14.1", + "dotenv": "^16.4.5", + "mysql": "^2.18.1" } }, "node_modules/@discordjs/builders": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.6.4.tgz", - "integrity": "sha512-ARFKvmAkLhfkQQiNxqi0YIWqwUExvBRtvdtMFVJXvJoibsGkFrB/DWTf9byU7BTVUfsmW8w7NM55tYXR5S/iSg==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.7.0.tgz", + "integrity": "sha512-GDtbKMkg433cOZur8Dv6c25EHxduNIBsxeHrsRoIM8+AwmEZ8r0tEpckx/sHwTLwQPOF3e2JWloZh9ofCaMfAw==", "dependencies": { - "@discordjs/formatters": "^0.3.1", - "@discordjs/util": "^1.0.0", - "@sapphire/shapeshift": "^3.9.2", - "discord-api-types": "^0.37.50", + "@discordjs/formatters": "^0.3.3", + "@discordjs/util": "^1.0.2", + "@sapphire/shapeshift": "^3.9.3", + "discord-api-types": "0.37.61", "fast-deep-equal": "^3.1.3", "ts-mixer": "^6.0.3", - "tslib": "^2.6.1" + "tslib": "^2.6.2" }, "engines": { - "node": ">=16.9.0" + "node": ">=16.11.0" } }, "node_modules/@discordjs/collection": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.2.tgz", - "integrity": "sha512-LDplPy8SPbc8MYkuCdnLRGWqygAX97E8NH7gA9uz+NZ/hXknUKJHuxsOmhC6pmHnF9Zmg0kvfwrDjGsRIljt9g==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz", + "integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==", "engines": { - "node": ">=16.9.0" + "node": ">=16.11.0" } }, "node_modules/@discordjs/formatters": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.3.1.tgz", - "integrity": "sha512-M7X4IGiSeh4znwcRGcs+49B5tBkNDn4k5bmhxJDAUhRxRHTiFAOTVUNQ6yAKySu5jZTnCbSvTYHW3w0rAzV1MA==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.3.3.tgz", + "integrity": "sha512-wTcI1Q5cps1eSGhl6+6AzzZkBBlVrBdc9IUhJbijRgVjCNIIIZPgqnUj3ntFODsHrdbGU8BEG9XmDQmgEEYn3w==", "dependencies": { - "discord-api-types": "^0.37.41" + "discord-api-types": "0.37.61" }, "engines": { - "node": ">=16.9.0" + "node": ">=16.11.0" } }, "node_modules/@discordjs/rest": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.0.0.tgz", - "integrity": "sha512-CW9ldfzsRzUbHcS4Oqu5+Moo+yrQ5qQ9groKNxPOzcoq2nuXa/fXOXkuQtQHcTeSVXsC9cmJ56M8gBDBUyLgGA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.2.0.tgz", + "integrity": "sha512-nXm9wT8oqrYFRMEqTXQx9DUTeEtXUDMmnUKIhZn6O2EeDY9VCdwj23XCPq7fkqMPKdF7ldAfeVKyxxFdbZl59A==", "dependencies": { - "@discordjs/collection": "^1.5.2", - "@discordjs/util": "^1.0.0", + "@discordjs/collection": "^2.0.0", + "@discordjs/util": "^1.0.2", "@sapphire/async-queue": "^1.5.0", "@sapphire/snowflake": "^3.5.1", "@vladfrangu/async_event_emitter": "^2.2.2", - "discord-api-types": "^0.37.50", - "magic-bytes.js": "^1.0.15", - "tslib": "^2.6.1", - "undici": "^5.22.1" + "discord-api-types": "0.37.61", + "magic-bytes.js": "^1.5.0", + "tslib": "^2.6.2", + "undici": "5.27.2" }, "engines": { - "node": ">=16.9.0" + "node": ">=16.11.0" + } + }, + "node_modules/@discordjs/rest/node_modules/@discordjs/collection": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.0.0.tgz", + "integrity": "sha512-YTWIXLrf5FsrLMycpMM9Q6vnZoR/lN2AWX23/Cuo8uOOtS8eHB2dyQaaGnaF8aZPYnttf2bkLMcXn/j6JUOi3w==", + "engines": { + "node": ">=18" } }, "node_modules/@discordjs/util": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.0.0.tgz", - "integrity": "sha512-U2Iiab0mo8cFe+o4ZY4GROoAetGjFYA1PhhxiXEW82LuPUjOU/seHZDtVjDpOf6n3rz4IRm84wNtgHdpqRY5CA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.0.2.tgz", + "integrity": "sha512-IRNbimrmfb75GMNEjyznqM1tkI7HrZOf14njX7tCAAUetyZM1Pr8hX/EK2lxBCOgWDRmigbp24fD1hdMfQK5lw==", "engines": { - "node": ">=16.9.0" + "node": ">=16.11.0" } }, "node_modules/@discordjs/ws": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.0.0.tgz", - "integrity": "sha512-POiImjuQJzwCxjJs4JCtDcTjzvjVsVQbnsaoW/F03yTVdrj/xSpmgv4383AnpNEYXI+CA6ggkz37phZDsZQ1NQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.0.2.tgz", + "integrity": "sha512-+XI82Rm2hKnFwAySXEep4A7Kfoowt6weO6381jgW+wVdTpMS/56qCvoXyFRY0slcv7c/U8My2PwIB2/wEaAh7Q==", "dependencies": { - "@discordjs/collection": "^1.5.2", - "@discordjs/rest": "^2.0.0", - "@discordjs/util": "^1.0.0", + "@discordjs/collection": "^2.0.0", + "@discordjs/rest": "^2.1.0", + "@discordjs/util": "^1.0.2", "@sapphire/async-queue": "^1.5.0", - "@types/ws": "^8.5.5", + "@types/ws": "^8.5.9", "@vladfrangu/async_event_emitter": "^2.2.2", - "discord-api-types": "^0.37.50", - "tslib": "^2.6.1", - "ws": "^8.13.0" + "discord-api-types": "0.37.61", + "tslib": "^2.6.2", + "ws": "^8.14.2" }, "engines": { - "node": ">=16.9.0" + "node": ">=16.11.0" + } + }, + "node_modules/@discordjs/ws/node_modules/@discordjs/collection": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.0.0.tgz", + "integrity": "sha512-YTWIXLrf5FsrLMycpMM9Q6vnZoR/lN2AWX23/Cuo8uOOtS8eHB2dyQaaGnaF8aZPYnttf2bkLMcXn/j6JUOi3w==", + "engines": { + "node": ">=18" + } + }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "engines": { + "node": ">=14" + } + }, + "node_modules/@mistralai/mistralai": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-0.1.3.tgz", + "integrity": "sha512-WUHxC2xdeqX9PTXJEqdiNY54vT2ir72WSJrZTTBKRnkfhX6zIfCYA24faRlWjUB5WTpn+wfdGsTMl3ArijlXFA==", + "dependencies": { + "node-fetch": "^2.6.7" } }, "node_modules/@sapphire/async-queue": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.0.tgz", - "integrity": "sha512-JkLdIsP8fPAdh9ZZjrbHWR/+mZj0wvKS5ICibcLrRI1j84UmLMshx5n9QmL8b95d4onJ2xxiyugTgSAX7AalmA==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.2.tgz", + "integrity": "sha512-7X7FFAA4DngXUl95+hYbUF19bp1LGiffjJtu7ygrZrbdCSsdDDBaSjB7Akw0ZbOu6k0xpXyljnJ6/RZUvLfRdg==", "engines": { "node": ">=v14.0.0", "npm": ">=7.0.0" } }, "node_modules/@sapphire/shapeshift": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-3.9.2.tgz", - "integrity": "sha512-YRbCXWy969oGIdqR/wha62eX8GNHsvyYi0Rfd4rNW6tSVVa8p0ELiMEuOH/k8rgtvRoM+EMV7Csqz77YdwiDpA==", + "version": "3.9.6", + "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-3.9.6.tgz", + "integrity": "sha512-4+Na/fxu2SEepZRb9z0dbsVh59QtwPuBg/UVaDib3av7ZY14b14+z09z6QVn0P6Dv6eOU2NDTsjIi0mbtgP56g==", "dependencies": { "fast-deep-equal": "^3.1.3", "lodash": "^4.17.21" }, "engines": { - "node": ">=v14.0.0", - "npm": ">=7.0.0" + "node": ">=v18" } }, "node_modules/@sapphire/snowflake": { @@ -129,43 +160,39 @@ "npm": ">=7.0.0" } }, + "node_modules/@types/mysql": { + "version": "2.15.26", + "resolved": "https://registry.npmjs.org/@types/mysql/-/mysql-2.15.26.tgz", + "integrity": "sha512-DSLCOXhkvfS5WNNPbfn2KdICAmk8lLc+/PNvnPnF7gOdMZCxopXduqv0OQ13y/yA/zXTSikZZqVgybUxOEg6YQ==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/node": { - "version": "20.5.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.0.tgz", - "integrity": "sha512-Mgq7eCtoTjT89FqNoTzzXg2XvCi5VMhRV6+I2aYanc6kQCBImeNaAYRs/DyoVqk1YEUJK5gN9VO7HRIdz4Wo3Q==" + "version": "20.11.24", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.24.tgz", + "integrity": "sha512-Kza43ewS3xoLgCEpQrsT+xRo/EJej1y0kVYGiLFE1NEODXGzTfwiC6tXTLMQskn1X4/Rjlh0MQUvx9W+L9long==", + "dependencies": { + "undici-types": "~5.26.4" + } }, "node_modules/@types/ws": { - "version": "8.5.5", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.5.tgz", - "integrity": "sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==", + "version": "8.5.9", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.9.tgz", + "integrity": "sha512-jbdrY0a8lxfdTp/+r7Z4CkycbOFN8WX+IOchLJr3juT/xzbJ8URyTVSJ/hvNdadTgM1mnedb47n+Y31GsFnQlg==", "dependencies": { "@types/node": "*" } }, "node_modules/@vladfrangu/async_event_emitter": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.2.2.tgz", - "integrity": "sha512-HIzRG7sy88UZjBJamssEczH5q7t5+axva19UbZLO6u0ySbYPrwzWiXBcC0WuHyhKKoeCyneH+FvYzKQq/zTtkQ==", + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.2.4.tgz", + "integrity": "sha512-ButUPz9E9cXMLgvAW8aLAKKJJsPu1dY1/l/E8xzLFuysowXygs6GBcyunK9rnGC4zTsnIc2mQo71rGw9U+Ykug==", "engines": { "node": ">=v14.0.0", "npm": ">=7.0.0" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "node_modules/axios": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz", - "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==", - "dependencies": { - "follow-redirects": "^1.15.4", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, "node_modules/bignumber.js": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", @@ -174,79 +201,49 @@ "node": "*" } }, - "node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", - "dependencies": { - "streamsearch": "^1.1.0" - }, - "engines": { - "node": ">=10.16.0" - } - }, - "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==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/discord-api-types": { - "version": "0.37.53", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.53.tgz", - "integrity": "sha512-N6uUgv50OyP981Mfxrrt0uxcqiaNr0BDaQIoqfk+3zM2JpZtwU9v7ce1uaFAP53b2xSDvcbrk80Kneui6XJgGg==" + "version": "0.37.61", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.61.tgz", + "integrity": "sha512-o/dXNFfhBpYHpQFdT6FWzeO7pKc838QeeZ9d91CfVAtpr5XLK4B/zYxQbYgPdoMiTDvJfzcsLW5naXgmHGDNXw==" }, "node_modules/discord.js": { - "version": "14.12.1", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.12.1.tgz", - "integrity": "sha512-gGjhTkauIPgFXxpBl0UZgyehrKhDe90cIS8Hn1xFBYQ63EuUAkKoUqRNmc/pcla6DD16s4cUz5tAbdSpXivnxw==", + "version": "14.14.1", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.14.1.tgz", + "integrity": "sha512-/hUVzkIerxKHyRKopJy5xejp4MYKDPTszAnpYxzVVv4qJYf+Tkt+jnT2N29PIPschicaEEpXwF2ARrTYHYwQ5w==", "dependencies": { - "@discordjs/builders": "^1.6.4", - "@discordjs/collection": "^1.5.2", - "@discordjs/formatters": "^0.3.1", - "@discordjs/rest": "^2.0.0", - "@discordjs/util": "^1.0.0", - "@discordjs/ws": "^1.0.0", - "@sapphire/snowflake": "^3.5.1", - "@types/ws": "^8.5.5", - "discord-api-types": "^0.37.50", - "fast-deep-equal": "^3.1.3", - "lodash.snakecase": "^4.1.1", - "tslib": "^2.6.1", - "undici": "^5.22.1", - "ws": "^8.13.0" + "@discordjs/builders": "^1.7.0", + "@discordjs/collection": "1.5.3", + "@discordjs/formatters": "^0.3.3", + "@discordjs/rest": "^2.1.0", + "@discordjs/util": "^1.0.2", + "@discordjs/ws": "^1.0.2", + "@sapphire/snowflake": "3.5.1", + "@types/ws": "8.5.9", + "discord-api-types": "0.37.61", + "fast-deep-equal": "3.1.3", + "lodash.snakecase": "4.1.1", + "tslib": "2.6.2", + "undici": "5.27.2", + "ws": "8.14.2" }, "engines": { - "node": ">=16.9.0" + "node": ">=16.11.0" } }, "node_modules/dotenv": { - "version": "16.3.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", - "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", "engines": { "node": ">=12" }, "funding": { - "url": "https://github.com/motdotla/dotenv?sponsor=1" + "url": "https://dotenvx.com" } }, "node_modules/fast-deep-equal": { @@ -254,38 +251,6 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, - "node_modules/follow-redirects": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", - "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -307,28 +272,9 @@ "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==" }, "node_modules/magic-bytes.js": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.0.15.tgz", - "integrity": "sha512-bpRmwbRHqongRhA+mXzbLWjVy7ylqmfMBYaQkSs6pac0z6hBTvsgrH0r4FBYd/UYVJBmS6Rp/O+oCCQVLzKV1g==" - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.10.0.tgz", + "integrity": "sha512-/k20Lg2q8LE5xiaaSkMXk4sfvI+9EGEykFS4b0CHHGWqDYU0bGUFSwchNOMA56D7TCs9GwVTkqe9als1/ns8UQ==" }, "node_modules/mysql": { "version": "2.18.1", @@ -344,21 +290,23 @@ "node": ">= 0.6" } }, - "node_modules/openai": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/openai/-/openai-3.3.0.tgz", - "integrity": "sha512-uqxI/Au+aPRnsaQRe8CojU0eCR7I0mBiKjD3sNMzY6DaC1ZVrc85u98mtJW6voDug8fgGN+DIZmTDxTthxb7dQ==", + "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==", "dependencies": { - "axios": "^0.26.0", - "form-data": "^4.0.0" - } - }, - "node_modules/openai/node_modules/axios": { - "version": "0.26.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", - "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", - "dependencies": { - "follow-redirects": "^1.14.8" + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } } }, "node_modules/process-nextick-args": { @@ -366,11 +314,6 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, "node_modules/readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", @@ -398,14 +341,6 @@ "node": ">= 0.6" } }, - "node_modules/streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -414,36 +349,60 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, "node_modules/ts-mixer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.3.tgz", - "integrity": "sha512-k43M7uCG1AkTyxgnmI5MPwKoUvS/bRvLvUb7+Pgpdlmok8AoqmUaZxUUw8zKM5B1lqZrt41GjYgnvAi0fppqgQ==" + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz", + "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==" }, "node_modules/tslib": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz", - "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==" + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/undici": { - "version": "5.23.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.23.0.tgz", - "integrity": "sha512-1D7w+fvRsqlQ9GscLBwcAJinqcZGHUKjbOmXdlE/v8BvEGXjeWAax+341q44EuTcHXXnfyKNbKRq4Lg7OzhMmg==", + "version": "5.27.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.27.2.tgz", + "integrity": "sha512-iS857PdOEy/y3wlM3yRp+6SNQQ6xU0mmZcwRSriqk+et/cwWAtwmIGf6WkoDN2EK/AMdCO/dfXzIwi+rFMrjjQ==", "dependencies": { - "busboy": "^1.6.0" + "@fastify/busboy": "^2.0.0" }, "engines": { "node": ">=14.0" } }, + "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==" + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "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==" + }, + "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==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "version": "8.14.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", + "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", "engines": { "node": ">=10.0.0" }, diff --git a/package.json b/package.json index 0be50c5..7205e1e 100644 --- a/package.json +++ b/package.json @@ -1,19 +1,21 @@ { - "name": "chaise_bot", - "version": "2.0.0", + "name": "chaise_bot_3.0", + "version": "3.0.0", "description": "", "main": "index.js", "scripts": { - "dev": "nodemon bot.js", - "start": "node bot.js" + "dev": "tsx watch src", + "build": "tsc", + "start": "node dist" }, - "author": "Ninja Jambon", + "keywords": [], + "author": "", "license": "ISC", "dependencies": { - "axios": "^1.6.7", - "discord.js": "^14.12.1", - "dotenv": "^16.3.1", - "mysql": "^2.18.1", - "openai": "^3.3.0" + "@mistralai/mistralai": "^0.1.3", + "@types/mysql": "^2.15.26", + "discord.js": "^14.14.1", + "dotenv": "^16.4.5", + "mysql": "^2.18.1" } } diff --git a/src/@types/discord.d.ts b/src/@types/discord.d.ts new file mode 100644 index 0000000..ccd37b0 --- /dev/null +++ b/src/@types/discord.d.ts @@ -0,0 +1,7 @@ +import type { Client } from 'discord.js' + +declare module 'discord.js' { + export interface Client extends Client { + commands: Collection + } +} \ No newline at end of file diff --git a/src/commands/default/ping.ts b/src/commands/default/ping.ts new file mode 100644 index 0000000..e395445 --- /dev/null +++ b/src/commands/default/ping.ts @@ -0,0 +1,10 @@ +import { SlashCommandBuilder, CommandInteraction } from "discord.js"; + +module.exports = { + data: new SlashCommandBuilder() + .setName("ping") + .setDescription("Replies with Pong!"), + async execute(interaction: CommandInteraction) { + await interaction.reply("Pong!"); + }, +}; diff --git a/src/commands/singleRequests/ask.ts b/src/commands/singleRequests/ask.ts new file mode 100644 index 0000000..a825724 --- /dev/null +++ b/src/commands/singleRequests/ask.ts @@ -0,0 +1,57 @@ +import { SlashCommandBuilder, ChatInputCommandInteraction } from "discord.js"; +import { getChatResponse, MistralMessage, Models, InputPrice, OutputPrice, ReturnedValue } from "../../libs/mistralai"; +import { User, connectToDb, addUser, getUser, incrementQuota } from "../../libs/mysql"; + +const data = require("../../../config.json"); + +module.exports = { + data: new SlashCommandBuilder() + .setName("ask") + .setDescription("Make a single request to mistral API") + .setDMPermission(false) + .addStringOption(option => + option.setName("prompt").setDescription("The prompt to send to the API").setRequired(true) + ), + async execute(interaction: ChatInputCommandInteraction) { + if (interaction.member?.user.id != '372437660167438337') { + return interaction.reply("you are not allowed to use this command"); + } + + await interaction.deferReply(); + + const connection = await connectToDb(); + + var user: User[] = await getUser(connection, interaction.member?.user.id); + + if (!user[0]) { + await addUser(connection, interaction.member?.user.username, interaction.member?.user.id); + user = await getUser(connection, interaction.member?.user.id); + } + + if (user[0].quota > 0.4) { + interaction.editReply("You have exceed your quota.") + connection.end(); + return; + } + + const prompt: string | null = interaction.options.getString("prompt"); + + const messages: MistralMessage[] = [ + { + role: "system", + content: data.defaultPrompt + }, + { + role: "user", + content: prompt ? prompt : "" + }, + ] + + const response: ReturnedValue = await getChatResponse(messages, Models.multi_tiny); + + await incrementQuota(connection, interaction.member?.user.id, InputPrice.multi_tiny * response.promptUsage + OutputPrice.multi_tiny * response.responseUsage); + connection.end(); + + interaction.editReply(response.message); + }, +}; diff --git a/src/data/lastreset.txt b/src/data/lastreset.txt new file mode 100644 index 0000000..0139922 --- /dev/null +++ b/src/data/lastreset.txt @@ -0,0 +1 @@ +1709247600 \ No newline at end of file diff --git a/src/events/interactionCreate.ts b/src/events/interactionCreate.ts new file mode 100644 index 0000000..80dc5bc --- /dev/null +++ b/src/events/interactionCreate.ts @@ -0,0 +1,21 @@ +import { Events, Interaction } from "discord.js"; + +module.exports = { + name: Events.InteractionCreate, + async execute(interaction: Interaction) { + if (interaction.isChatInputCommand()) { + const command = interaction.client.commands.get(interaction.commandName); + + if (!command) { + return console.error(`No command matching ${interaction.commandName} was found.`); + } + + try { + await command.execute(interaction); + } + catch (error) { + console.error(error); + } + } + }, +}; diff --git a/src/events/messageCreate.ts b/src/events/messageCreate.ts new file mode 100644 index 0000000..f35b8a6 --- /dev/null +++ b/src/events/messageCreate.ts @@ -0,0 +1,43 @@ +import { Events, Message } from "discord.js"; +import { getChatResponse, MistralMessage, Models, InputPrice, OutputPrice, ReturnedValue } from "../libs/mistralai"; +import { User, connectToDb, addUser, getUser, incrementQuota } from "../libs/mysql"; +import { getMessages } from "../libs/discord"; + +module.exports = { + name: Events.MessageCreate, + async execute(message: Message) { + if (!message.guildId && message.author.id != process.env.BOT_ID) { + const prompt: string = message.content; + + if (message.author.id != '372437660167438337') { + return message.reply("you are not allowed to use this command"); + } + + const connection = await connectToDb(); + + var user: User[] = await getUser(connection, message.author.id); + + if (!user[0]) { + await addUser(connection, message.author.username, message.author.id); + user = await getUser(connection, message.author.id); + } + + if (user[0].quota > 0.4) { + message.reply("You have exceed your quota.") + connection.end(); + return; + } + + const messages: MistralMessage[] = await getMessages(message, message.channelId, message.author.id); + + await message.channel.sendTyping(); + + const response: ReturnedValue = await getChatResponse(messages, Models.multi_tiny); + + await incrementQuota(connection, message.author.id, InputPrice.multi_tiny * response.promptUsage + OutputPrice.multi_tiny * response.responseUsage); + connection.end(); + + message.reply(response.message); + } + }, +}; diff --git a/src/events/ready.ts b/src/events/ready.ts new file mode 100644 index 0000000..3506a70 --- /dev/null +++ b/src/events/ready.ts @@ -0,0 +1,14 @@ +import { Events, Client } from "discord.js"; +import { checkReset } from "../libs/quotaReset"; + +module.exports = { + name: Events.ClientReady, + once: true, + execute(client: Client) { + console.log(`Ready! Logged in as ${client.user?.tag}`); + + setInterval(async () => { + await checkReset(); + }, 10 * 60 * 1000); + }, +}; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..0c4b89b --- /dev/null +++ b/src/index.ts @@ -0,0 +1,77 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import "dotenv/config"; +import { Client, Collection, REST, Routes, RESTPutAPIApplicationCommandsResult, GatewayIntentBits, Partials } from 'discord.js'; + +const client: Client = new Client({ + intents: [ + GatewayIntentBits.Guilds, + GatewayIntentBits.GuildMessages, + GatewayIntentBits.MessageContent, + GatewayIntentBits.DirectMessages, + ], + partials: [ + Partials.Channel, + Partials.Message, + ] +}) + +client.commands = new Collection(); +const commands = []; +const foldersPath = path.join(__dirname, "commands"); +const commandFolders = fs.readdirSync(foldersPath); + +for (const folder of commandFolders) { + const commandsPath = path.join(foldersPath, folder); + const commandFiles = fs + .readdirSync(commandsPath) + .filter((file) => file.endsWith(".ts") || file.endsWith(".js")); + + for (const file of commandFiles) { + const filePath = path.join(commandsPath, file); + const command = require(filePath); + if ("data" in command && "execute" in command) { + client.commands.set(command.data.name, command); + commands.push(command.data.toJSON()); + } + else { + console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`); + } + } +} + +const rest = new REST().setToken(process.env.DISCORD_TOKEN ? process.env.DISCORD_TOKEN : ""); + +(async () => { + try { + console.log(`Started refreshing ${commands.length} application (/) commands.`); + + const data = await rest.put( + Routes.applicationCommands(process.env.BOT_ID ? process.env.BOT_ID : ""), + { body: commands } + ); + + console.log(`Successfully reloaded ${commands.length} application (/) commands.`); + } catch (error) { + console.error(error); + } +})(); + +const eventsPath = path.join(__dirname, "events"); +const eventFiles = fs + .readdirSync(eventsPath) + .filter((file) => file.endsWith(".ts") || file.endsWith(".js")); + +for (const file of eventFiles) { + const filePath = path.join(eventsPath, file); + const event = require(filePath); + + if (event.once) { + client.once(event.name, (...args) => event.execute(...args)); + } + else { + client.on(event.name, (...args) => event.execute(...args)); + } +} + +client.login(process.env.DISCORD_TOKEN); diff --git a/src/libs/discord.ts b/src/libs/discord.ts new file mode 100644 index 0000000..3e09a8e --- /dev/null +++ b/src/libs/discord.ts @@ -0,0 +1,27 @@ +import { Events, Message, Collection } from "discord.js"; +import { getChatResponse, MistralMessage, Models, InputPrice, OutputPrice, ReturnedValue } from "../libs/mistralai"; +import { User, connectToDb, addUser, getUser, incrementQuota } from "../libs/mysql"; + +const data = require("../../config.json"); + +export async function getMessages(message: Message, channelid: string, userid: string): Promise { + var discordMessages = await message.channel.messages.fetch({ limit: 7 }) + discordMessages.filter((m) => m.content && (m.author.id == message.author.id || m.author.id == process.env.BOT_ID)) + discordMessages.reverse(); + + var messages: MistralMessage[] = [ + { + role: "system", + content: data.defaultPrompt + } + ] + + discordMessages.forEach(discordMessage => { + messages.push({ + role: discordMessage.author.id == process.env.BOT_ID ? "assistant" : "user", + content: discordMessage.content, + }) + }) + + return messages; +} diff --git a/src/libs/mistralai.ts b/src/libs/mistralai.ts new file mode 100644 index 0000000..9609a9a --- /dev/null +++ b/src/libs/mistralai.ts @@ -0,0 +1,54 @@ +import MistralClient from '@mistralai/mistralai'; +import "dotenv/config"; + +export interface MistralMessage { + role: string, + content: string, +} + +export enum Models { + tiny = "open-mistral-7b", + multi_tiny = "open-mixtral-8x7b", + small = "mistral-small-latest", + medium = "mistral-medium-latest", + large = "mistral-large-latest", +} + +export enum InputPrice { + tiny = 0.25 / 1000000, + multi_tiny = 0.7 / 1000000, + small = 2 / 1000000, + medium = 2.7 / 1000000, + large = 8 / 1000000, +} + +export enum OutputPrice { + tiny = 0.25 / 1000000, + multi_tiny = 0.7 / 1000000, + small = 6 / 1000000, + medium = 8.1 / 1000000, + large = 24 / 1000000, +} + +export interface ReturnedValue { + message: string, + promptUsage: number, + responseUsage: number, +} + +const apiKey = process.env.MISTRAL_API_KEY; + +const client = new MistralClient(apiKey); + +export async function getChatResponse(messages: MistralMessage[], model: Models): Promise { + const chatResponse = await client.chat({ + model: model, + messages: messages, + }); + + return { + message: chatResponse.choices[0].message.content, + promptUsage: chatResponse.usage.prompt_tokens, + responseUsage: chatResponse.usage.completion_tokens, + }; +} \ No newline at end of file diff --git a/src/libs/mysql.ts b/src/libs/mysql.ts new file mode 100644 index 0000000..7f0bfc3 --- /dev/null +++ b/src/libs/mysql.ts @@ -0,0 +1,75 @@ +import * as mysql from "mysql"; + +export interface User { + id: number, + username: string, + userid: string, + quota: number, +} + +export async function connectToDb(): Promise { + return new Promise((resolve, reject) => { + const connection: mysql.Connection = mysql.createConnection({ + host: process.env.MYSQL_HOST, + user: process.env.MYSQL_USER, + password: process.env.MYSQL_PASSWORD, + database: process.env.MYSQL_DATABASE, + }) + + connection.connect((error) => { + if (error) { + reject(error) + } + + resolve(connection); + }); + }) +} + +export async function addUser(connection: mysql.Connection, username: string, userid: string): Promise { + return new Promise((resolve, reject) => { + connection.query(`INSERT INTO users (username, userid, quota) VALUES ("${username}", "${userid}", 0)`, (error, result) => { + if (error) { + reject(error); + } + + resolve(result); + }) + }) +} + +export async function getUser(connection: mysql.Connection, userid: string): Promise { + return new Promise((resolve, reject) => { + connection.query(`SELECT * FROM users WHERE userid = "${userid}"`, (error, result) => { + if (error) { + reject(error); + } + + resolve(result); + }) + }) +} + +export async function incrementQuota(connection: mysql.Connection, userid: string, value: number): Promise { + return new Promise((resolve, reject) => { + connection.query(`UPDATE users SET quota = quota + ${value} WHERE userid = "${userid}"`, (error, result) => { + if (error) { + reject(error); + } + + resolve(result); + }) + }) +} + +export function resetQuota(connection: mysql.Connection) { + return new Promise((resolve, reject) => { + connection.query(`UPDATE users SET quota = 0`, (error, result) => { + if (error) { + reject(error); + } + + resolve(result); + }) + }) +} diff --git a/src/libs/quotaReset.ts b/src/libs/quotaReset.ts new file mode 100644 index 0000000..e5ff5a1 --- /dev/null +++ b/src/libs/quotaReset.ts @@ -0,0 +1,26 @@ +import * as fs from "fs"; +import { connectToDb, resetQuota } from "./mysql"; + +function getLastResetDate(): number { + const data: string = fs.readFileSync("../data/lastreset.txt", "utf8"); + return parseInt(data); +} + +export async function checkReset() { + const lastResetDate = getLastResetDate(); + const now = Date.now() / 1000; + + if (now - lastResetDate > 1000 * 60 * 60 * 24 * 30) { + fs.writeFileSync("../data/lastreset.txt", now.toString()); + + const connection = await connectToDb(); + + await resetQuota(connection); + + connection.end(); + + return; + } else { + return false; + } +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..145223f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "strict": true, + "outDir": "./dist", + "skipLibCheck": true, + "noImplicitAny": true, + }, + "include": ["src"], +} \ No newline at end of file