diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..ab3c681 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +.github +package.json \ No newline at end of file diff --git a/README.md b/README.md index df56bac..1d86f3b 100644 --- a/README.md +++ b/README.md @@ -3,29 +3,32 @@

## 🤔 What is this bot? + The **Taurus Discord Bot** is a small bot, it can be used for several things such as: + - Image Generation - AI GPT chats and personality customisations ## 💾 Install -1. fill in the token and other values in ``config.json.example`` +1. fill in the token and other values in `config.json.example` -2. Rename the file to ``config.json`` +2. Rename the file to `config.json` -3. Run ``npm i`` +3. Run `npm i` -4. Run ``node bot`` +4. Run `node bot` **Note** - Alternatively **instead of steps 3 & 4** you can **run one of the startbot/start scripts.** - All PR's are welcome for improvements. ### ℹ️ Getting API Keys: + - https://ai.google.dev - https://app.prodia.com/api ### 🏗️ Additional Credits - This bot utilizes a modified version of the [discord bot template](https://github.com/NamVr/DiscordBot-Template) made - by [Naman Vrati](https://github.com/NamVr)! [\[Apache License 2.0\]](https://github.com/NamVr/DiscordBot-Template/blob/master/LICENSE) \ No newline at end of file + by [Naman Vrati](https://github.com/NamVr)! [\[Apache License 2.0\]](https://github.com/NamVr/DiscordBot-Template/blob/master/LICENSE) diff --git a/bot.js b/bot.js index 63bf91a..b4b3c8e 100644 --- a/bot.js +++ b/bot.js @@ -16,7 +16,7 @@ const { Partials, REST, Routes, - SlashCommandBuilder + SlashCommandBuilder, } = require("discord.js"); const { token, client_id } = require("./config.json"); @@ -58,7 +58,7 @@ for (const file of eventFiles) { } else { client.on( event.name, - async (...args) => await event.execute(...args, client) + async (...args) => await event.execute(...args, client), ); } } @@ -114,7 +114,9 @@ for (const module of autocompleteInteractions) { .filter((file) => file.endsWith(".js")); for (const interactionFile of files) { - const interaction = require(`./interactions/autocomplete/${module}/${interactionFile}`); + const interaction = require( + `./interactions/autocomplete/${module}/${interactionFile}`, + ); client.autocompleteInteractions.set(interaction.name, interaction); } } @@ -142,7 +144,6 @@ for (const folder of contextMenus) { } } - /**********************************************************************/ // Registration of Modal-Command Interactions. @@ -179,11 +180,11 @@ const functionFiles = fs.readdirSync("./functions"); // Loop through all files and store functions in functions collection. for (const functionFile of functionFiles) { - if (functionFile.endsWith(".js")) { - const func = require(`./functions/${functionFile}`); - client.functions.set(functionFile.replace('.js', ''), func); - func(client); - } + if (functionFile.endsWith(".js")) { + const func = require(`./functions/${functionFile}`); + client.functions.set(functionFile.replace(".js", ""), func); + func(client); + } } /**********************************************************************/ @@ -192,18 +193,19 @@ for (const functionFile of functionFiles) { const rest = new REST({ version: "9" }).setToken(token); const commandJsonData = [ - ...Array.from(client.slashCommands.values()).map((c) => { - const commandData = c.data instanceof SlashCommandBuilder ? c.data.toJSON() : c.data; - commandData.integration_types = [1]; - commandData.contexts = [0, 1, 2]; - return commandData; - }), + ...Array.from(client.slashCommands.values()).map((c) => { + const commandData = + c.data instanceof SlashCommandBuilder ? c.data.toJSON() : c.data; + commandData.integration_types = [1]; + commandData.contexts = [0, 1, 2]; + return commandData; + }), ...Array.from(client.contextCommands.values()).map((c) => { - const commandData = c.data; - commandData.integration_types = [1]; - commandData.contexts = [0, 1, 2]; - return commandData; - }) + const commandData = c.data; + commandData.integration_types = [1]; + commandData.contexts = [0, 1, 2]; + return commandData; + }), ]; (async () => { @@ -213,7 +215,7 @@ const commandJsonData = [ await rest.put( Routes.applicationCommands(client_id), - { body: commandJsonData } + { body: commandJsonData }, ); console.log("Successfully reloaded application (/) commands."); @@ -233,15 +235,15 @@ process.on("unhandledRejection", (reason, promise) => { // Uncomment the below lines below to see the full error details. - ADVANCED DEBUGGING // - // console.dir(reason, { showHidden: true, depth: null }); - // console.log("Promise: ", promise); + // console.dir(reason, { showHidden: true, depth: null }); + // console.log("Promise: ", promise); }); process.on("uncaughtException", (error, origin) => { console.error(`🚫 Critical Error detected:\n\n`, error, origin); - + // Uncomment the below lines below to see the full error details. - ADVANCED DEBUGGING // - // console.dir(error, { showHidden: true, depth: null }); - // console.log("Origin: ", origin); -}); \ No newline at end of file + // console.dir(error, { showHidden: true, depth: null }); + // console.log("Origin: ", origin); +}); diff --git a/events/autocompleteInteraction.js b/events/autocompleteInteraction.js index 1e23c64..9349331 100644 --- a/events/autocompleteInteraction.js +++ b/events/autocompleteInteraction.js @@ -27,7 +27,7 @@ module.exports = { // Checks if the request is available in our code. const request = client.autocompleteInteractions.get( - interaction.commandName + interaction.commandName, ); // If the interaction is not a request in cache return. @@ -43,4 +43,4 @@ module.exports = { return Promise.reject(err); } }, -}; \ No newline at end of file +}; diff --git a/events/contextInteraction.js b/events/contextInteraction.js index 3109c7d..da2355d 100644 --- a/events/contextInteraction.js +++ b/events/contextInteraction.js @@ -29,7 +29,7 @@ module.exports = { if (interaction.isUserContextMenuCommand()) { const command = client.contextCommands.get( - "USER " + interaction.commandName + "USER " + interaction.commandName, ); // A try to execute the interaction. @@ -39,15 +39,16 @@ module.exports = { } catch (err) { console.error(err); await interaction.reply({ - content: "There was an issue while executing that context command! If the issue persists please contact the bot owners.", - ephemeral: true + content: + "There was an issue while executing that context command! If the issue persists please contact the bot owners.", + ephemeral: true, }); } } // Checks if the interaction target was a message else if (interaction.isMessageContextMenuCommand()) { const command = client.contextCommands.get( - "MESSAGE " + interaction.commandName + "MESSAGE " + interaction.commandName, ); // A try to execute the interaction. @@ -55,10 +56,11 @@ module.exports = { try { return await command.execute(interaction); } catch (err) { - console.dir(err, { showHidden: true }); + console.error(err); await interaction.reply({ - content: "There was an issue while executing that context command! If the issue persists please contact the bot owners.", - ephemeral: true + content: + "There was an issue while executing that context command! If the issue persists please contact the bot owners.", + ephemeral: true, }); } } @@ -67,8 +69,8 @@ module.exports = { // Possible Fix is a restart! else { return console.log( - "Something weird happening in context menu. Received a context menu of unknown type. If the issue persists please contact the bot owners." + "Something weird happening in context menu. Received a context menu of unknown type. If the issue persists please contact the bot owners.", ); } - } -}; \ No newline at end of file + }, +}; diff --git a/events/modalInteraction.js b/events/modalInteraction.js index 828425a..a038e3e 100644 --- a/events/modalInteraction.js +++ b/events/modalInteraction.js @@ -24,7 +24,7 @@ module.exports = { if (!interaction.isModalSubmit()) return; - if (interaction.customId === 'taurus_ai_personality') return; + if (interaction.customId === "taurus_ai_personality") return; const command = client.modalCommands.get(interaction.customId); @@ -32,13 +32,17 @@ module.exports = { // You can modify the error message at ./messages/defaultModalError.js file! if (!command) { - return await require("../messages/defaultModalError").execute(interaction); + return await require("../messages/defaultModalError").execute( + interaction, + ); } // A try to execute the interaction. const error = new EmbedBuilder() - .setDescription("**There was an issue while understanding this modal!\n\nPlease contact the Developers.**") + .setDescription( + "**There was an issue while understanding this modal!\n\nPlease contact the Developers.**", + ) .setColor("Red"); try { diff --git a/events/onMention.js b/events/onMention.js index 2a31b72..3a315b8 100644 --- a/events/onMention.js +++ b/events/onMention.js @@ -5,24 +5,23 @@ const { Events, EmbedBuilder } = require("discord.js"); - module.exports = { name: Events.MessageCreate, - async execute(message) { - - const {client} = message; + const { client } = message; if ( message.content == `<@${client.user.id}>` || message.content == `<@!${client.user.id}>` ) { const bot_message = new EmbedBuilder() - .setDescription(`Hi ${message.author}! I am Taurus. Chat to me by mentioning me and typing your message! Or alternatively run \`/taurus\`!`) + .setDescription( + `Hi ${message.author}! I am Taurus. Chat to me by mentioning me and typing your message! Or alternatively run \`/taurus\`!`, + ) .setColor("Gold"); - return message.reply({embeds: [bot_message]}); + return message.reply({ embeds: [bot_message] }); } }, }; diff --git a/events/onReady.js b/events/onReady.js index da24715..292979c 100644 --- a/events/onReady.js +++ b/events/onReady.js @@ -16,14 +16,15 @@ module.exports = { * @param {import('../typings').Client} client Main Application Client. */ execute(client) { - client.user.setPresence({ - activities: [{ - type: ActivityType.Custom, - name: "Status", - state: "💾 Chilling on my owners computer!" - }] - }) + activities: [ + { + type: ActivityType.Custom, + name: "Status", + state: "💾 Chilling on my owners computer!", + }, + ], + }); console.log(`Ready! Logged in as ${client.user.tag}`); }, diff --git a/events/slashCreate.js b/events/slashCreate.js index 1f9405f..05d66d4 100644 --- a/events/slashCreate.js +++ b/events/slashCreate.js @@ -7,8 +7,8 @@ */ const { Collection, EmbedBuilder, Events } = require("discord.js"), -{ botInGuild } = require("../utils"), -{ owner } = require("../config.json"); + { botInGuild } = require("../utils"), + { owner } = require("../config.json"); module.exports = { name: Events.InteractionCreate, @@ -45,7 +45,11 @@ module.exports = { if (interaction.inGuild()) { if (botInGuild(interaction)) { allowedRoleIds = ["...", "..."]; - if (interaction.member.roles.cache.some(role => allowedRoleIds.includes(role.id))) { + if ( + interaction.member.roles.cache.some((role) => + allowedRoleIds.includes(role.id), + ) + ) { const cooldownPercentage = 0.5; cooldownAmount = Math.floor(cooldownAmount * cooldownPercentage); } @@ -54,10 +58,13 @@ module.exports = { const isOwner = owner.includes(interaction.user.id); if (!isOwner && timestamps.has(interaction.user.id)) { - const expirationTime = timestamps.get(interaction.user.id) + cooldownAmount * 1000; + const expirationTime = + timestamps.get(interaction.user.id) + cooldownAmount * 1000; const timeLeft = (expirationTime - now) / 1000; const embed = new EmbedBuilder() - .setDescription(`Please wait \`\`${timeLeft.toFixed(1)}\`\` more second(s) before reusing the \`${interaction.commandName}\` command.`) + .setDescription( + `Please wait \`\`${timeLeft.toFixed(1)}\`\` more second(s) before reusing the \`${interaction.commandName}\` command.`, + ) .setColor("Orange"); if (now < expirationTime) { const expiredTimestamp = Math.round(expirationTime / 1000); @@ -66,10 +73,15 @@ module.exports = { } timestamps.set(interaction.user.id, now); - setTimeout(() => timestamps.delete(interaction.user.id), cooldownAmount * 1000); + setTimeout( + () => timestamps.delete(interaction.user.id), + cooldownAmount * 1000, + ); const error = new EmbedBuilder() - .setDescription("**There was an issue while executing that command!\n\nPlease contact the Developers.**") + .setDescription( + "**There was an issue while executing that command!\n\nPlease contact the Developers.**", + ) .setColor("Red"); try { @@ -77,8 +89,8 @@ module.exports = { } catch (err) { await interaction.reply({ embeds: [error], - ephemeral: true + ephemeral: true, }); } - } + }, }; diff --git a/events/taurusai.js b/events/taurusai.js index 5844cda..ab23244 100644 --- a/events/taurusai.js +++ b/events/taurusai.js @@ -3,123 +3,152 @@ * @author TechyGiraffe999 */ - const { Events, EmbedBuilder } = require("discord.js"); -const fs = require('fs').promises; -const path = require('path'); +const fs = require("fs").promises; +const path = require("path"); const { GoogleGenerativeAI } = require("@google/generative-ai"); -const { Gemini_API_KEY } = require("../config.json"); -const { safetySettings, handleGeminiError, handleResponse, checkGeminiApiKey, fetchThreadMessages } = require("../utils"); +const { Gemini_API_KEY } = require("../config.json"); +const { + safetySettings, + handleGeminiError, + handleResponse, + checkGeminiApiKey, + fetchThreadMessages, +} = require("../utils"); const genAI = new GoogleGenerativeAI(Gemini_API_KEY); module.exports = { - name: Events.MessageCreate, - - async execute(message) { - if (message.author.bot || message.author.id === message.client.user.id) return; - if ([18, 21].includes(message.type)) return; - - let userQuestion - let threadMessages = []; - - if (message.reference) { - const { userQuestion: fetchedUserQuestion, threadMessages: fetchedThreadMessages } = await fetchThreadMessages(Gemini_API_KEY, message); - if (fetchedUserQuestion === null && fetchedThreadMessages === null) return; - threadMessages = fetchedThreadMessages; - userQuestion = fetchedUserQuestion - } else if (!message.reference) { - const botMention = `<@${message.client.user.id}>`; - const regex = new RegExp(`^${botMention}\\s+.+`); - - if (!regex.test(message.content)) return; - if (await checkGeminiApiKey(Gemini_API_KEY, false, message)) return; - userQuestion = message.content - .replace(botMention, "") - .trim(); - } - - const user = message.author; - const sendTypingInterval = setInterval(() => { - message.channel.sendTyping(); - }, 5000); - - const loadingEmbed = new EmbedBuilder() - .setTitle("**⌛Loading your response**") - .setDescription("*TaurusAI may display innacurate/offensive info.*\n\n> *I am powered by Google's Generative AI, [Gemini](https://gemini.google.com) and was integrated by <@719815864135712799>.*") - .setFooter({text: "This may take a while", iconURL: `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png?size=256`}) - .setTimestamp() - const loadingMsg = await message.reply({ embeds: [loadingEmbed] }); - const loadingDots = [" ⌛ ", " ⏳ "]; - let i = 0; - const loadingInterval = setInterval(() => { - loadingEmbed.setTitle(`**${loadingDots[i]} Loading your response**`); - loadingMsg.edit({ embeds: [loadingEmbed] }); - i = (i + 1) % loadingDots.length; - }, 2000); - - const user_status = message.member?.presence.clientStatus || {} - const status_devices = Object.entries(user_status) - .map(([platform, status]) => `${platform}: ${status}`) - .join("\n"); - - const personalityFilePath = path.join(__dirname, '../personality.txt'); - const personalityContent = await fs.readFile(personalityFilePath, 'utf-8'); - const personalityLines = personalityContent.split('\n'); - - parts1 = `${personalityLines}\n Please greet the user with a greeting and then their name which is: <@${message.author.id}>.` - - if (Object.keys(user_status).length) { - parts1 += ` The user's presence is currently:\n${status_devices}`; - } - - async function run() { - const generationConfig = { - maxOutputTokens: 750, - }; - - const model = genAI.getGenerativeModel({ model: "gemini-1.5-pro-latest" }, { - apiVersion: 'v1beta', - safetySettings, - generationConfig - }); - - var history = [ - { - role: "user", - parts: [{text: parts1}], - }, - { - role: "model", - parts: [{text:`I will greet the user with their name: <@${message.author.id}>. I will also limit all of my responses to 2000 characters or less, regardless of what you say. Feel feel free to ask me anything! 😊`}], - }, - ]; - - if (history.length > 0 && threadMessages && threadMessages.length > 0 && history[history.length - 1].role === 'model' && threadMessages[0].role === 'model' && Array.isArray(history[history.length - 1].parts) && Array.isArray(threadMessages[0].parts)) { - history[history.length - 1].parts = history[history.length - 1].parts.concat(threadMessages[0].parts); - threadMessages.shift(); - } - history.push(...threadMessages); - - const chat = model.startChat({ - history, - generationConfig: { - maxOutputTokens: 750, - }, - }); - - clearInterval(loadingInterval); - clearInterval(sendTypingInterval); - await handleResponse(chat, userQuestion, false, message, loadingMsg) - } - - try{ - await run(); - } catch (err) { - clearInterval(loadingInterval); - sendTypingInterval && clearInterval(sendTypingInterval); - - handleGeminiError(err, loadingMsg); - - } - }, -}; \ No newline at end of file + name: Events.MessageCreate, + + async execute(message) { + if (message.author.bot || message.author.id === message.client.user.id) + return; + if ([18, 21].includes(message.type)) return; + + let userQuestion; + let threadMessages = []; + + if (message.reference) { + const { + userQuestion: fetchedUserQuestion, + threadMessages: fetchedThreadMessages, + } = await fetchThreadMessages(Gemini_API_KEY, message); + if (fetchedUserQuestion === null && fetchedThreadMessages === null) + return; + threadMessages = fetchedThreadMessages; + userQuestion = fetchedUserQuestion; + } else if (!message.reference) { + const botMention = `<@${message.client.user.id}>`; + const regex = new RegExp(`^${botMention}\\s+.+`); + + if (!regex.test(message.content)) return; + if (await checkGeminiApiKey(Gemini_API_KEY, false, message)) return; + userQuestion = message.content.replace(botMention, "").trim(); + } + + const user = message.author; + const sendTypingInterval = setInterval(() => { + message.channel.sendTyping(); + }, 5000); + + const loadingEmbed = new EmbedBuilder() + .setTitle("**⌛Loading your response**") + .setDescription( + "*TaurusAI may display innacurate/offensive info.*\n\n> *I am powered by Google's Generative AI, [Gemini](https://gemini.google.com) and was integrated by <@719815864135712799>.*", + ) + .setFooter({ + text: "This may take a while", + iconURL: `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png?size=256`, + }) + .setTimestamp(); + const loadingMsg = await message.reply({ embeds: [loadingEmbed] }); + const loadingDots = [" ⌛ ", " ⏳ "]; + let i = 0; + const loadingInterval = setInterval(() => { + loadingEmbed.setTitle(`**${loadingDots[i]} Loading your response**`); + loadingMsg.edit({ embeds: [loadingEmbed] }); + i = (i + 1) % loadingDots.length; + }, 2000); + + const user_status = message.member?.presence.clientStatus || {}; + const status_devices = Object.entries(user_status) + .map(([platform, status]) => `${platform}: ${status}`) + .join("\n"); + + const personalityFilePath = path.join(__dirname, "../personality.txt"); + const personalityContent = await fs.readFile(personalityFilePath, "utf-8"); + const personalityLines = personalityContent.split("\n"); + + parts1 = `${personalityLines}\n Please greet the user with a greeting and then their name which is: <@${message.author.id}>.`; + + if (Object.keys(user_status).length) { + parts1 += ` The user's presence is currently:\n${status_devices}`; + } + + async function run() { + const generationConfig = { + maxOutputTokens: 750, + }; + + const model = genAI.getGenerativeModel( + { model: "gemini-1.5-pro-latest" }, + { + apiVersion: "v1beta", + safetySettings, + generationConfig, + }, + ); + + var history = [ + { + role: "user", + parts: [{ text: parts1 }], + }, + { + role: "model", + parts: [ + { + text: `I will greet the user with their name: <@${message.author.id}>. I will also limit all of my responses to 2000 characters or less, regardless of what you say. Feel feel free to ask me anything! 😊`, + }, + ], + }, + ]; + + if ( + history.length > 0 && + threadMessages && + threadMessages.length > 0 && + history[history.length - 1].role === "model" && + threadMessages[0].role === "model" && + Array.isArray(history[history.length - 1].parts) && + Array.isArray(threadMessages[0].parts) + ) { + history[history.length - 1].parts = history[ + history.length - 1 + ].parts.concat(threadMessages[0].parts); + threadMessages.shift(); + } + history.push(...threadMessages); + + const chat = model.startChat({ + history, + generationConfig: { + maxOutputTokens: 750, + }, + }); + + clearInterval(loadingInterval); + clearInterval(sendTypingInterval); + await handleResponse(chat, userQuestion, false, message, loadingMsg); + } + + try { + await run(); + } catch (err) { + clearInterval(loadingInterval); + sendTypingInterval && clearInterval(sendTypingInterval); + + handleGeminiError(err, loadingMsg); + } + }, +}; diff --git a/functions/logConsole.js b/functions/logConsole.js index 5974c7c..0ffb289 100644 --- a/functions/logConsole.js +++ b/functions/logConsole.js @@ -1,61 +1,75 @@ module.exports = (client) => { - const { WebhookClient, EmbedBuilder } = require('discord.js'); - const {webhook_url_console_logs} = require('../config.json'); - const webhookURL = webhook_url_console_logs; - - let webhookClient; - try { - webhookClient = new WebhookClient({ url: webhookURL }); - } catch (error) { - console.log('\x1b[31m\x1b[1m%s\x1b[0m', 'CONSOLE LOGGING IN DISCORD DISABLED. WEBHOOK URL NOT PROVIDED OR INVALID.'); - return; - } - - function customLogger(type, ...messages) { - const combinedMessage = messages.map(m => (typeof m === 'object' ? JSON.stringify(m, null, 2) : m)).join(' '); - - if (combinedMessage === "%cBy not specifying 'modelOrUrl' parameter, you're using the default model: 'MobileNetV2'. See NSFWJS docs for instructions on hosting your own model (https://github.com/infinitered/nsfwjs?tab=readme-ov-file#host-your-own-model). color: lightblue") return; - - let messageToSend = combinedMessage; - if (combinedMessage.length > 4070) { - messageToSend = `${combinedMessage.slice(0, 4067)}...`; - } - - const embed = new EmbedBuilder() - .setDescription(`\`\`\`console\n${messageToSend}\`\`\``) - .setColor(0x3498DB) - - if (type === 'error') { - embed.setColor('Red'); - } else if (type === 'warn') { - embed.setColor('Orange'); - } else if (combinedMessage === 'Started refreshing application (/) commands.' || - combinedMessage === 'Successfully reloaded application (/) commands.' || - combinedMessage.startsWith('Ready! Logged in as')) { - embed.setColor('Green'); - if (combinedMessage === 'Started refreshing application (/) commands.') { - embed.setTitle('💾 Console Log'); - } - } - - webhookClient.send({ - username: 'Taurus Console', - avatarURL: 'https://github.com/TecEash1/TecEash1/assets/92249532/bd4aca7e-daab-4eeb-9265-e53cc1925e8c', - embeds: [embed] - }).catch(console.error); - - console.originalLog(combinedMessage); - } - - console.originalLog = console.log; - console.log = customLogger.bind(null, 'log'); - - console.originalError = console.error; - console.error = customLogger.bind(null, 'error'); - - console.originalWarn = console.warn; - console.warn = customLogger.bind(null, 'warn'); - - console.originalInfo = console.info; - console.info = customLogger.bind(null, 'info'); -}; \ No newline at end of file + const { WebhookClient, EmbedBuilder } = require("discord.js"); + const { webhook_url_console_logs } = require("../config.json"); + const webhookURL = webhook_url_console_logs; + + let webhookClient; + try { + webhookClient = new WebhookClient({ url: webhookURL }); + } catch (error) { + console.log( + "\x1b[31m\x1b[1m%s\x1b[0m", + "CONSOLE LOGGING IN DISCORD DISABLED. WEBHOOK URL NOT PROVIDED OR INVALID.", + ); + return; + } + + function customLogger(type, ...messages) { + const combinedMessage = messages + .map((m) => (typeof m === "object" ? JSON.stringify(m, null, 2) : m)) + .join(" "); + + if ( + combinedMessage === + "%cBy not specifying 'modelOrUrl' parameter, you're using the default model: 'MobileNetV2'. See NSFWJS docs for instructions on hosting your own model (https://github.com/infinitered/nsfwjs?tab=readme-ov-file#host-your-own-model). color: lightblue" + ) + return; + + let messageToSend = combinedMessage; + if (combinedMessage.length > 4070) { + messageToSend = `${combinedMessage.slice(0, 4067)}...`; + } + + const embed = new EmbedBuilder() + .setDescription(`\`\`\`console\n${messageToSend}\`\`\``) + .setColor(0x3498db); + + if (type === "error") { + embed.setColor("Red"); + } else if (type === "warn") { + embed.setColor("Orange"); + } else if ( + combinedMessage === "Started refreshing application (/) commands." || + combinedMessage === "Successfully reloaded application (/) commands." || + combinedMessage.startsWith("Ready! Logged in as") + ) { + embed.setColor("Green"); + if (combinedMessage === "Started refreshing application (/) commands.") { + embed.setTitle("💾 Console Log"); + } + } + + webhookClient + .send({ + username: "Taurus Console", + avatarURL: + "https://github.com/TecEash1/TecEash1/assets/92249532/bd4aca7e-daab-4eeb-9265-e53cc1925e8c", + embeds: [embed], + }) + .catch(console.error); + + console.originalLog(combinedMessage); + } + + console.originalLog = console.log; + console.log = customLogger.bind(null, "log"); + + console.originalError = console.error; + console.error = customLogger.bind(null, "error"); + + console.originalWarn = console.warn; + console.warn = customLogger.bind(null, "warn"); + + console.originalInfo = console.info; + console.info = customLogger.bind(null, "info"); +}; diff --git a/interactions/autocomplete/category/image_model.js b/interactions/autocomplete/category/image_model.js index fed6ace..ad59571 100644 --- a/interactions/autocomplete/category/image_model.js +++ b/interactions/autocomplete/category/image_model.js @@ -7,39 +7,40 @@ * @type {import("../../../typings").AutocompleteInteraction} */ -const {XProdiaKey} = require('../../../config.json') +const { XProdiaKey } = require("../../../config.json"); module.exports = { name: "image", async execute(interaction) { - const focusedOption = interaction.options.getFocused(); - const sdk = require('api')('@prodia/v1.3.0#6fdmny2flsvwyf65'); + const sdk = require("api")("@prodia/v1.3.0#6fdmny2flsvwyf65"); sdk.auth(XProdiaKey); - + async function fetchAndFormatModels(apiMethod) { try { const { data } = await apiMethod(); const models = JSON.parse(data); return models; } catch (e) { - console.error("Error fetching models: ", e) + console.error("Error fetching models: ", e); } } - + const sdModels = await fetchAndFormatModels(sdk.listModels); const sdxlModels = await fetchAndFormatModels(sdk.listSdxlModels); - + const allModels = sdModels.concat(sdxlModels); - const filteredModels = allModels.filter(model => model.toLowerCase().startsWith(focusedOption.toLowerCase())); - - const results = filteredModels.map(model => ({ + const filteredModels = allModels.filter((model) => + model.toLowerCase().startsWith(focusedOption.toLowerCase()), + ); + + const results = filteredModels.map((model) => ({ name: model, - value: model + value: model, })); - + await interaction.respond(results.slice(0, 25)).catch(() => {}); return; - } -}; \ No newline at end of file + }, +}; diff --git a/interactions/context-menus/message/taurusai.js b/interactions/context-menus/message/taurusai.js index 712640f..5de91e2 100644 --- a/interactions/context-menus/message/taurusai.js +++ b/interactions/context-menus/message/taurusai.js @@ -8,18 +8,23 @@ */ const { Collection, ChannelType, Events, EmbedBuilder } = require("discord.js"); -const fs = require('fs').promises; -const path = require('path'); +const fs = require("fs").promises; +const path = require("path"); const { GoogleGenerativeAI } = require("@google/generative-ai"); -const { safetySettings, handleGeminiError, handleResponse, checkGeminiApiKey, fetchThreadMessages } = require("../../../utils"); -const { Gemini_API_KEY } = require("../../../config.json"); +const { + safetySettings, + handleGeminiError, + handleResponse, + checkGeminiApiKey, + fetchThreadMessages, +} = require("../../../utils"); +const { Gemini_API_KEY } = require("../../../config.json"); const genAI = new GoogleGenerativeAI(Gemini_API_KEY); - module.exports = { data: { name: "Taurus", - type: 3 + type: 3, }, async execute(interaction) { @@ -27,120 +32,153 @@ module.exports = { const message = await channel.messages.fetch(targetId); - if (await checkGeminiApiKey(Gemini_API_KEY, interaction, false)) return; - - if (message.author.bot || message.author.id === message.client.user.id){ - return interaction.reply({ content: "I cant reply to myself or another bot!", ephemeral: true}); - } - - - let userQuestion - let threadMessages = []; - - if (message.reference) { - const { userQuestion: fetchedUserQuestion, threadMessages: fetchedThreadMessages } = await fetchThreadMessages(Gemini_API_KEY, message); - if (fetchedUserQuestion === null && fetchedThreadMessages === null) return; - threadMessages = fetchedThreadMessages; - userQuestion = fetchedUserQuestion - } else if (!message.reference) { - const botMention = `<@${message.client.user.id}>`; - const regex = new RegExp(`^${botMention}\\s+.+`); - - if (await checkGeminiApiKey(Gemini_API_KEY, false, message)) return; - userQuestion = message.content - .replace(botMention, "") - .trim(); - } - - const sendTypingInterval = setInterval(() => { - interaction.channel.sendTyping(); - }, 5000); - - const user = message.author; - - const loadingEmbed = new EmbedBuilder() - .setTitle("**⌛Loading your response**") - .setDescription("*TaurusAI may display innacurate/offensive info.*\n\n> *I am powered by Google's Generative AI, [Gemini](https://gemini.google.com) and was integrated by <@719815864135712799>.*") - .setFooter({text: "This may take a while", iconURL: `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png?size=256`}) - .setTimestamp() - const loadingMsg = await interaction.reply({ embeds: [loadingEmbed] }); - const loadingDots = [" ⌛ ", " ⏳ "]; - let i = 0; - const loadingInterval = setInterval(() => { - loadingEmbed.setTitle(`**${loadingDots[i]} Loading your response**`); - loadingMsg.edit({ embeds: [loadingEmbed] }); - i = (i + 1) % loadingDots.length; - }, 2000); - - const personalityFilePath = path.join(__dirname + '../../../../personality.txt'); - const personalityContent = await fs.readFile(personalityFilePath, 'utf-8'); - const personalityLines = personalityContent.split('\n'); - - const botMention = `<@${message.client.user.id}>`; - - if (await checkGeminiApiKey(Gemini_API_KEY, false, message)) return; - userQuestion = message.content - .replace(botMention, "") - .trim(); - - const user_status = message.member?.presence.clientStatus || {} - const status_devices = Object.entries(user_status) - .map(([platform, status]) => `${platform}: ${status}`) - .join("\n"); - - parts1 = `${personalityLines}\n Please greet the user with a greeting and then their name which is: <@${message.author.id}>.` - - if (Object.keys(user_status).length) { - parts1 += ` The user's presence is currently:\n${status_devices}`; - } - - async function run() { - const generationConfig = { - maxOutputTokens: 750, - }; - - const model = genAI.getGenerativeModel({ model: "gemini-1.5-pro-latest" }, { - apiVersion: 'v1beta', - safetySettings, - generationConfig - }); - - var history = [ - { - role: "user", - parts: [{text: parts1}], - }, - { - role: "model", - parts: [{text:`I will greet the user with their name: <@${message.author.id}>. I will also limit all of my responses to 2000 characters or less, regardless of what you say. Feel feel free to ask me anything! 😊`}], - }, - ]; - - if (history.length > 0 && threadMessages && threadMessages.length > 0 && history[history.length - 1].role === 'model' && threadMessages[0].role === 'model' && Array.isArray(history[history.length - 1].parts) && Array.isArray(threadMessages[0].parts)) { - history[history.length - 1].parts = history[history.length - 1].parts.concat(threadMessages[0].parts); - threadMessages.shift(); - } - history.push(...threadMessages); - - const chat = model.startChat({ - history, - generationConfig: { - maxOutputTokens: 750, - }, - }); - - clearInterval(loadingInterval); - sendTypingInterval && clearInterval(sendTypingInterval); - await handleResponse(chat, userQuestion, interaction, message, loadingMsg, true) - } - - try{ - await run(); - } catch (err) { - clearInterval(loadingInterval); - sendTypingInterval && clearInterval(sendTypingInterval); - - handleGeminiError(err, loadingMsg); - } - } -}; \ No newline at end of file + if (await checkGeminiApiKey(Gemini_API_KEY, interaction, false)) return; + + if (message.author.bot || message.author.id === message.client.user.id) { + return interaction.reply({ + content: "I cant reply to myself or another bot!", + ephemeral: true, + }); + } + + let userQuestion; + let threadMessages = []; + + if (message.reference) { + const { + userQuestion: fetchedUserQuestion, + threadMessages: fetchedThreadMessages, + } = await fetchThreadMessages(Gemini_API_KEY, message); + if (fetchedUserQuestion === null && fetchedThreadMessages === null) + return; + threadMessages = fetchedThreadMessages; + userQuestion = fetchedUserQuestion; + } else if (!message.reference) { + const botMention = `<@${message.client.user.id}>`; + const regex = new RegExp(`^${botMention}\\s+.+`); + + if (await checkGeminiApiKey(Gemini_API_KEY, false, message)) return; + userQuestion = message.content.replace(botMention, "").trim(); + } + + const sendTypingInterval = setInterval(() => { + interaction.channel.sendTyping(); + }, 5000); + + const user = message.author; + + const loadingEmbed = new EmbedBuilder() + .setTitle("**⌛Loading your response**") + .setDescription( + "*TaurusAI may display innacurate/offensive info.*\n\n> *I am powered by Google's Generative AI, [Gemini](https://gemini.google.com) and was integrated by <@719815864135712799>.*", + ) + .setFooter({ + text: "This may take a while", + iconURL: `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png?size=256`, + }) + .setTimestamp(); + const loadingMsg = await interaction.reply({ embeds: [loadingEmbed] }); + const loadingDots = [" ⌛ ", " ⏳ "]; + let i = 0; + const loadingInterval = setInterval(() => { + loadingEmbed.setTitle(`**${loadingDots[i]} Loading your response**`); + loadingMsg.edit({ embeds: [loadingEmbed] }); + i = (i + 1) % loadingDots.length; + }, 2000); + + const personalityFilePath = path.join( + __dirname + "../../../../personality.txt", + ); + const personalityContent = await fs.readFile(personalityFilePath, "utf-8"); + const personalityLines = personalityContent.split("\n"); + + const botMention = `<@${message.client.user.id}>`; + + if (await checkGeminiApiKey(Gemini_API_KEY, false, message)) return; + userQuestion = message.content.replace(botMention, "").trim(); + + const user_status = message.member?.presence.clientStatus || {}; + const status_devices = Object.entries(user_status) + .map(([platform, status]) => `${platform}: ${status}`) + .join("\n"); + + parts1 = `${personalityLines}\n Please greet the user with a greeting and then their name which is: <@${message.author.id}>.`; + + if (Object.keys(user_status).length) { + parts1 += ` The user's presence is currently:\n${status_devices}`; + } + + async function run() { + const generationConfig = { + maxOutputTokens: 750, + }; + + const model = genAI.getGenerativeModel( + { model: "gemini-1.5-pro-latest" }, + { + apiVersion: "v1beta", + safetySettings, + generationConfig, + }, + ); + + var history = [ + { + role: "user", + parts: [{ text: parts1 }], + }, + { + role: "model", + parts: [ + { + text: `I will greet the user with their name: <@${message.author.id}>. I will also limit all of my responses to 2000 characters or less, regardless of what you say. Feel feel free to ask me anything! 😊`, + }, + ], + }, + ]; + + if ( + history.length > 0 && + threadMessages && + threadMessages.length > 0 && + history[history.length - 1].role === "model" && + threadMessages[0].role === "model" && + Array.isArray(history[history.length - 1].parts) && + Array.isArray(threadMessages[0].parts) + ) { + history[history.length - 1].parts = history[ + history.length - 1 + ].parts.concat(threadMessages[0].parts); + threadMessages.shift(); + } + history.push(...threadMessages); + + const chat = model.startChat({ + history, + generationConfig: { + maxOutputTokens: 750, + }, + }); + + clearInterval(loadingInterval); + sendTypingInterval && clearInterval(sendTypingInterval); + await handleResponse( + chat, + userQuestion, + interaction, + message, + loadingMsg, + true, + ); + } + + try { + await run(); + } catch (err) { + clearInterval(loadingInterval); + sendTypingInterval && clearInterval(sendTypingInterval); + + handleGeminiError(err, loadingMsg); + } + }, +}; diff --git a/interactions/modals/category/ask.js b/interactions/modals/category/ask.js index 349670e..9d57fc3 100644 --- a/interactions/modals/category/ask.js +++ b/interactions/modals/category/ask.js @@ -6,100 +6,122 @@ /** * @type {import("../../../../typings").ModalInteractionCommand} */ -const fs = require('fs').promises; -const path = require('path'); -const { EmbedBuilder } = require("discord.js"); +const fs = require("fs").promises; +const path = require("path"); +const { EmbedBuilder } = require("discord.js"); const { GoogleGenerativeAI } = require("@google/generative-ai"); -const { Gemini_API_KEY } = require("../../../config.json"); -const { botInGuild, safetySettings, handleGeminiError, handleResponse, checkGeminiApiKey } = require("../../../utils"); +const { Gemini_API_KEY } = require("../../../config.json"); +const { + botInGuild, + safetySettings, + handleGeminiError, + handleResponse, + checkGeminiApiKey, +} = require("../../../utils"); const genAI = new GoogleGenerativeAI(Gemini_API_KEY); - module.exports = { - id: "taurus_ai", - - async execute(interaction) { - if (await checkGeminiApiKey(Gemini_API_KEY, interaction, false)) return; - - const personalityFilePath = path.join(__dirname + '../../../../personality.txt'); - const personalityContent = await fs.readFile(personalityFilePath, 'utf-8'); - const personalityLines = personalityContent.split('\n'); - - const userQuestion = interaction.fields.getTextInputValue("question_taurusai"); - - const sendTypingInterval = interaction.inGuild() && botInGuild(interaction) - ? setInterval(() => { - interaction.channel.sendTyping(); - }, 5000) - : null; - - const loadingEmbed = new EmbedBuilder() - .setTitle("**Loading your response . . .**") - .setDescription("*TaurusAI may display innacurate/offensive info.*\n\n> *I am powered by Google's Generative AI, [Gemini](https://gemini.google.com) and was integrated by <@719815864135712799>.*") - .setFooter({text: "⏳ This may take a while", iconURL: interaction.user.displayAvatarURL()}) - .setTimestamp() - const loadingMsg = await interaction.reply({ embeds: [loadingEmbed] }); - const loadingDots = [""," . ", " . . ", " . . ."]; - let i = 0; - const loadingInterval = setInterval(async () => { - loadingEmbed.setTitle(`**Loading your response ${loadingDots[i]}**`); - await loadingMsg.edit({ embeds: [loadingEmbed] }); - i = (i + 1) % loadingDots.length; - }, 500); - - const user_status = interaction.inGuild() && botInGuild(interaction) - ? interaction.member?.presence.clientStatus - : {}; - - const status_devices = Object.entries(user_status) - .map(([platform, status]) => `${platform}: ${status}`) - .join("\n"); - - parts1 = `${personalityLines}\n Please greet the user with a greeting and then their name which is: <@${interaction.user.id}>.` - - if (Object.keys(user_status).length) { - parts1 += ` The user's presence is currently:\n${status_devices}`; - } - - async function run() { - const generationConfig = { - maxOutputTokens: 750, - }; - - const model = genAI.getGenerativeModel({ model: "gemini-1.5-pro-latest" }, { - apiVersion: 'v1beta', - safetySettings, - generationConfig - }); - - const chat = model.startChat({ - history: [ - { - role: "user", - parts: [{text: parts1}], - }, - { - role: "model", - parts: [{text: `I will greet the user with their name: <@${interaction.user.id}>. I will also limit all of my responses to 2000 characters or less, regardless of what you say. Feel feel free to ask me anything! 😊`}], - }, - ], - generationConfig: { - maxOutputTokens: 750, - }, - }); - - clearInterval(loadingInterval); - clearInterval(sendTypingInterval); - await handleResponse(chat, userQuestion, interaction, false, loadingMsg); - } - - try{ - await run(); - } catch (err) { - clearInterval(loadingInterval); - sendTypingInterval && clearInterval(sendTypingInterval); - - handleGeminiError(err, loadingMsg); - } - } -}; \ No newline at end of file + id: "taurus_ai", + + async execute(interaction) { + if (await checkGeminiApiKey(Gemini_API_KEY, interaction, false)) return; + + const personalityFilePath = path.join( + __dirname + "../../../../personality.txt", + ); + const personalityContent = await fs.readFile(personalityFilePath, "utf-8"); + const personalityLines = personalityContent.split("\n"); + + const userQuestion = + interaction.fields.getTextInputValue("question_taurusai"); + + const sendTypingInterval = + interaction.inGuild() && botInGuild(interaction) + ? setInterval(() => { + interaction.channel.sendTyping(); + }, 5000) + : null; + + const loadingEmbed = new EmbedBuilder() + .setTitle("**Loading your response . . .**") + .setDescription( + "*TaurusAI may display innacurate/offensive info.*\n\n> *I am powered by Google's Generative AI, [Gemini](https://gemini.google.com) and was integrated by <@719815864135712799>.*", + ) + .setFooter({ + text: "⏳ This may take a while", + iconURL: interaction.user.displayAvatarURL(), + }) + .setTimestamp(); + const loadingMsg = await interaction.reply({ embeds: [loadingEmbed] }); + const loadingDots = ["", " . ", " . . ", " . . ."]; + let i = 0; + const loadingInterval = setInterval(async () => { + loadingEmbed.setTitle(`**Loading your response ${loadingDots[i]}**`); + await loadingMsg.edit({ embeds: [loadingEmbed] }); + i = (i + 1) % loadingDots.length; + }, 500); + + const user_status = + interaction.inGuild() && botInGuild(interaction) + ? interaction.member?.presence.clientStatus + : {}; + + const status_devices = Object.entries(user_status) + .map(([platform, status]) => `${platform}: ${status}`) + .join("\n"); + + parts1 = `${personalityLines}\n Please greet the user with a greeting and then their name which is: <@${interaction.user.id}>.`; + + if (Object.keys(user_status).length) { + parts1 += ` The user's presence is currently:\n${status_devices}`; + } + + async function run() { + const generationConfig = { + maxOutputTokens: 750, + }; + + const model = genAI.getGenerativeModel( + { model: "gemini-1.5-pro-latest" }, + { + apiVersion: "v1beta", + safetySettings, + generationConfig, + }, + ); + + const chat = model.startChat({ + history: [ + { + role: "user", + parts: [{ text: parts1 }], + }, + { + role: "model", + parts: [ + { + text: `I will greet the user with their name: <@${interaction.user.id}>. I will also limit all of my responses to 2000 characters or less, regardless of what you say. Feel feel free to ask me anything! 😊`, + }, + ], + }, + ], + generationConfig: { + maxOutputTokens: 750, + }, + }); + + clearInterval(loadingInterval); + clearInterval(sendTypingInterval); + await handleResponse(chat, userQuestion, interaction, false, loadingMsg); + } + + try { + await run(); + } catch (err) { + clearInterval(loadingInterval); + sendTypingInterval && clearInterval(sendTypingInterval); + + handleGeminiError(err, loadingMsg); + } + }, +}; diff --git a/interactions/slash/misc/help.js b/interactions/slash/misc/help.js index f376bca..f04bc30 100644 --- a/interactions/slash/misc/help.js +++ b/interactions/slash/misc/help.js @@ -3,25 +3,28 @@ * @author TechyGiraffe999 */ - const { EmbedBuilder, SlashCommandBuilder, Embed } = require("discord.js"); /** * @type {import('../../../typings').SlashInteractionCommand} */ module.exports = { - data: new SlashCommandBuilder() - .setName('help') - .setDescription('Display available slash commands'), - async execute(interaction, client) { - const commands = interaction.client.slashCommands; - const commandList = commands.map((command) => `**/${command.data.name}**: ${command.data.description}`).join('\n'); + data: new SlashCommandBuilder() + .setName("help") + .setDescription("Display available slash commands"), + async execute(interaction, client) { + const commands = interaction.client.slashCommands; + const commandList = commands + .map( + (command) => `**/${command.data.name}**: ${command.data.description}`, + ) + .join("\n"); - const embed = new EmbedBuilder() - .setTitle("⚙️ Available Commands") - .setDescription(commandList) - .setColor("Blurple") + const embed = new EmbedBuilder() + .setTitle("⚙️ Available Commands") + .setDescription(commandList) + .setColor("Blurple"); - await interaction.reply({ embeds: [embed] }); - }, -}; \ No newline at end of file + await interaction.reply({ embeds: [embed] }); + }, +}; diff --git a/interactions/slash/misc/image.js b/interactions/slash/misc/image.js index 0cf24fe..fb51c4f 100644 --- a/interactions/slash/misc/image.js +++ b/interactions/slash/misc/image.js @@ -7,245 +7,280 @@ * @type {import('../../../typings').SlashInteractionCommand} */ -const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); -const {XProdiaKey, Block_NSFW_Images} = require('../../../config.json') +const { SlashCommandBuilder, EmbedBuilder } = require("discord.js"); +const { XProdiaKey, Block_NSFW_Images } = require("../../../config.json"); const translate = require("@iamtraction/google-translate"); -const axios = require('axios'); -const tf = require('@tensorflow/tfjs-node'); -const nsfw = require('nsfwjs'); +const axios = require("axios"); +const tf = require("@tensorflow/tfjs-node"); +const nsfw = require("nsfwjs"); module.exports = { - data: new SlashCommandBuilder() - .setName('image') - .setDescription('Have AI generate an image!') - .addStringOption(o => o.setName('prompt').setDescription('The description of the image to generate!').setRequired(true)) - .addStringOption(o => o.setName('model').setDescription('The Model to Use').setAutocomplete(true)) - .addStringOption(o => o.setName('negative-prompt').setDescription('The Negative Prompt to Use')) - .addIntegerOption(option => - option.setName("steps") - .setDescription("The Number of Steps to Use") - .setMinValue(1) - .setMaxValue(30) - ) - .addIntegerOption(option => - option.setName("cfg-scale") - .setDescription("The CFG Scale") - .setMinValue(1) - .setMaxValue(30) - ) - .addIntegerOption(option => - option.setName("seed") - .setDescription("The Seed") - .setMinValue(-1) - ) - .addStringOption(option => - option.setName("style-preset") + data: new SlashCommandBuilder() + .setName("image") + .setDescription("Have AI generate an image!") + .addStringOption((o) => + o + .setName("prompt") + .setDescription("The description of the image to generate!") + .setRequired(true), + ) + .addStringOption((o) => + o + .setName("model") + .setDescription("The Model to Use") + .setAutocomplete(true), + ) + .addStringOption((o) => + o.setName("negative-prompt").setDescription("The Negative Prompt to Use"), + ) + .addIntegerOption((option) => + option + .setName("steps") + .setDescription("The Number of Steps to Use") + .setMinValue(1) + .setMaxValue(30), + ) + .addIntegerOption((option) => + option + .setName("cfg-scale") + .setDescription("The CFG Scale") + .setMinValue(1) + .setMaxValue(30), + ) + .addIntegerOption((option) => + option.setName("seed").setDescription("The Seed").setMinValue(-1), + ) + .addStringOption((option) => + option + .setName("style-preset") .setDescription("The Image Style Prese") .addChoices( { name: "3d Model", value: "3d-model" }, { name: "Analog Film", value: "analog-film" }, - { name: "Anime", value: "anime" }, - { name: "Cinematic", value: "cinematic" }, - { name: "Comic Book", value: "comic-book" }, - { name: "Digital Art", value: "digital-art" }, - { name: "Enhance", value: "enhance" }, - { name: "Fantasy Art", value: "fantasy-art" }, - { name: "Isometric", value: "isometric" }, - { name: "Line Art", value: "line-art" }, - { name: "Low Poly", value: "low-poly" }, - { name: "Neon Punk", value: "neon-punk" }, - { name: "Origami", value: "origami" }, - { name: "Photographic", value: "photographic" }, - { name: "Pixel Art", value: "pixel-art" }, - { name: "Texture", value: "texture" }, - { name: "Craft Clay", value: "craft-clay" }, - )) - .addStringOption(option => - option.setName("sampler") - .setDescription("The Image Sampler") - .addChoices( - { name: "Euler a", value: "Euler a" }, - { name: "LMS", value: "LMS" }, - { name: "Heun", value: "Heun" }, - { name: "DPM2", value: "DPM2" }, - { name: "DPM2 a", value: "DPM2 a" }, - { name: "DPM++ 2S a", value: "DPM++ 2S a" }, - { name: "DPM++ 2M", value: "DPM++ 2M" }, - { name: "DPM++ 2M SDE", value: "DPM++ 2M SDE" }, - { name: "DPM++ 2M SDE Heun Karras", value: "DPM++ 2M SDE Heun Karras" }, - { name: "DPM++ 3M SD", value: "DPM++ 3M SD" }, - { name: "DPM++ 3M SD Karras", value: "DPM++ 3M SD Karras" }, - { name: "DPM++ 3M SD Exponential", value: "DPM++ 3M SD Exponential" }, - { name: "DPM fast", value: "DPM fast" }, - { name: "DPM adaptive", value: "DPM adaptive" }, - { name: "LMS Karras", value: "LMS Karras" }, - { name: "DPM2 Karras", value: "DPM2 Karras" }, - { name: "DPM2 a Karras", value: "DPM2 a Karras" }, - { name: "DPM++ 2S a Karras", value: "DPM++ 2S a Karras" }, - { name: "DPM++ 2M Karras", value: "DPM++ 2M Karras" }, - { name: "DPM++ SDE Karras", value: "DPM++ SDE Karras" }, - { name: "DPM++ SDE Karras", value: "DPM++ SDE Karras" }, - { name: "DPM++ 2M SDE Exponential", value: "DPM++ 2M SDE Exponential" }, - { name: "DDIM", value: "DDIM" }, - { name: "PLMS", value: "PLMS" }, - { name: "UniPC", value: "UniPC" }, - )), - - async execute(interaction, client) { - - if (!XProdiaKey || XProdiaKey.length < 4) { - invalid_api = new EmbedBuilder() - .setTitle("⚠️ Invalid API Key") - .setDescription("> **The API Key for Prodia is invalid or not provided.**") - .setColor("Red") - return interaction.reply({ embeds: [invalid_api] }); - } - - const error = new EmbedBuilder() - .setTitle('⚠️ Error!') - .setDescription('**An Error Occured, please try again later!**\n\n> - API Monthly Credits may have been used up\n> - Might be a problem with the API at the moment\n> - Or the Model/Sampler/Style is not available.') - .setColor('Red'); - - await interaction.deferReply(); - - const style_preset = interaction.options.getString('style-preset'); - const steps = interaction.options.getInteger('steps'); - const cfg_scale = interaction.options.getInteger('cfg-scale'); - const seed = interaction.options.getInteger('seed'); - const sampler = interaction.options.getString('sampler'); - - let prompt = interaction.options.getString('prompt'); - let negative_prompt = interaction.options.getString('negative-prompt'); - let model = interaction.options.getString('model'); - - prompt = (await translate(prompt, { to: 'en' })).text; - negative_prompt = (await translate(negative_prompt, { to: 'en' })).text; - - const nsfw_embed = new EmbedBuilder() - .setDescription(`**⚠️ NSFW content detected!**`) - .setColor('Red'); - - if (Block_NSFW_Images) { - try { - const response = await fetch('https://raw.githubusercontent.com/LDNOOBW/List-of-Dirty-Naughty-Obscene-and-Otherwise-Bad-Words/master/en'); - const data = await response.text(); - let nsfw_words = data.split('\n'); - nsfw_words = nsfw_words.filter(word => word !== 'suck' && word !== 'sucks'); - - let promptWords = prompt.split(' '); - - for (let word of nsfw_words) { - if (promptWords.includes(word)) { - await interaction.followUp({ embeds: [nsfw_embed] }); - return; - } - } - } catch (err) { - console.error(err); - } - } - - async function nsfw_getPic(image) { - const pic = await axios.get(image, { responseType: 'arraybuffer' }); - const model = await nsfw.load(); - image_analyse = await tf.node.decodeImage(pic.data, 3); - const predictions = await model.classify(image_analyse); - image_analyse.dispose(); - - if (predictions[0].probability > 0.5 && (predictions[0].className === "Porn") || (predictions[0].className === "Hentai") || (predictions[0].className === "Sexy")) { - await interaction.followUp({ embeds: [nsfw_embed] }); - return true; - } - return image - } - - const sdk = require('api')('@prodia/v1.3.0#6fdmny2flsvwyf65'); - - sdk.auth(XProdiaKey); - - async function fetchModels(apiMethod) { - try { - const { data } = await apiMethod(); - return JSON.parse(data); - } catch (e) { - return interaction.followUp({embeds: [error]}); - } - } - - const choices = await fetchModels(sdk.listModels); - const sdxlChoices = await fetchModels(sdk.listSdxlModels); - - const allModels = [...choices, ...sdxlChoices]; - - if (model && !allModels.includes(model)) { - const no_model = new EmbedBuilder() - .setDescription(`**${model}** is not a valid model!\n\n> Run \`/models\` to see the available models!`) - .setColor('Red'); - - return interaction.followUp({embeds: [no_model]}); - } - - if (!model) { - model = "absolutereality_V16.safetensors [37db0fc3]"; - } - - let generateParams = { - model: model, - prompt: prompt, - ...(style_preset && { style_preset: style_preset }), - ...(negative_prompt && { negative_prompt: negative_prompt }), - ...(steps && { steps: steps }), - ...(sampler && { sampler: sampler }), - ...(cfg_scale && { cfg_scale: cfg_scale }), - ...(seed && { seed: seed }) - }; - - - - try{ - const generateMethod = sdxlChoices.includes(model) ? sdk.sdxlGenerate : sdk.generate; - generateMethod(generateParams) - .then(({ data }) => { - const jobId = data.job; - const intervalId = setInterval(() => { - sdk.getJob({jobId: jobId}) - .then(({ data }) => { - if (data.status === 'succeeded') { - clearInterval(intervalId); - - let image = data.imageUrl; - - (async () => { - if (Block_NSFW_Images) { - const newImage = await nsfw_getPic(image); - image = newImage; - if (image === true) { - return; - } - } - - const success = new EmbedBuilder() - .setImage(image) - .setTitle('🖼️ Generated Image!') - .setDescription(`> **${prompt}**`) - .setColor('Random') - .setFooter({ text: `Requested by ${interaction.user.tag}, Powered By Prodia`, iconURL: interaction.user.displayAvatarURL() }) - - return interaction.followUp({embeds: [success]}); - - })(); - - } else if (data.status === 'failed') { - clearInterval(intervalId); - return interaction.followUp({embeds: [error]}); - } - }) - .catch(err => console.error(err)); - }, 2000); - }) - .catch(err => console.error(err)); - } catch(err) { - return interaction.followUp({embeds: [error]}); - } - } -} \ No newline at end of file + { name: "Anime", value: "anime" }, + { name: "Cinematic", value: "cinematic" }, + { name: "Comic Book", value: "comic-book" }, + { name: "Digital Art", value: "digital-art" }, + { name: "Enhance", value: "enhance" }, + { name: "Fantasy Art", value: "fantasy-art" }, + { name: "Isometric", value: "isometric" }, + { name: "Line Art", value: "line-art" }, + { name: "Low Poly", value: "low-poly" }, + { name: "Neon Punk", value: "neon-punk" }, + { name: "Origami", value: "origami" }, + { name: "Photographic", value: "photographic" }, + { name: "Pixel Art", value: "pixel-art" }, + { name: "Texture", value: "texture" }, + { name: "Craft Clay", value: "craft-clay" }, + ), + ) + .addStringOption((option) => + option.setName("sampler").setDescription("The Image Sampler").addChoices( + { name: "Euler a", value: "Euler a" }, + { name: "LMS", value: "LMS" }, + { name: "Heun", value: "Heun" }, + { name: "DPM2", value: "DPM2" }, + { name: "DPM2 a", value: "DPM2 a" }, + { name: "DPM++ 2S a", value: "DPM++ 2S a" }, + { name: "DPM++ 2M", value: "DPM++ 2M" }, + { name: "DPM++ 2M SDE", value: "DPM++ 2M SDE" }, + { + name: "DPM++ 2M SDE Heun Karras", + value: "DPM++ 2M SDE Heun Karras", + }, + { name: "DPM++ 3M SD", value: "DPM++ 3M SD" }, + { name: "DPM++ 3M SD Karras", value: "DPM++ 3M SD Karras" }, + { name: "DPM++ 3M SD Exponential", value: "DPM++ 3M SD Exponential" }, + { name: "DPM fast", value: "DPM fast" }, + { name: "DPM adaptive", value: "DPM adaptive" }, + { name: "LMS Karras", value: "LMS Karras" }, + { name: "DPM2 Karras", value: "DPM2 Karras" }, + { name: "DPM2 a Karras", value: "DPM2 a Karras" }, + { name: "DPM++ 2S a Karras", value: "DPM++ 2S a Karras" }, + { name: "DPM++ 2M Karras", value: "DPM++ 2M Karras" }, + { name: "DPM++ SDE Karras", value: "DPM++ SDE Karras" }, + { name: "DPM++ SDE Karras", value: "DPM++ SDE Karras" }, + { + name: "DPM++ 2M SDE Exponential", + value: "DPM++ 2M SDE Exponential", + }, + { name: "DDIM", value: "DDIM" }, + { name: "PLMS", value: "PLMS" }, + { name: "UniPC", value: "UniPC" }, + ), + ), + + async execute(interaction, client) { + if (!XProdiaKey || XProdiaKey.length < 4) { + invalid_api = new EmbedBuilder() + .setTitle("⚠️ Invalid API Key") + .setDescription( + "> **The API Key for Prodia is invalid or not provided.**", + ) + .setColor("Red"); + return interaction.reply({ embeds: [invalid_api] }); + } + + const error = new EmbedBuilder() + .setTitle("⚠️ Error!") + .setDescription( + "**An Error Occured, please try again later!**\n\n> - API Monthly Credits may have been used up\n> - Might be a problem with the API at the moment\n> - Or the Model/Sampler/Style is not available.", + ) + .setColor("Red"); + + await interaction.deferReply(); + + const style_preset = interaction.options.getString("style-preset"); + const steps = interaction.options.getInteger("steps"); + const cfg_scale = interaction.options.getInteger("cfg-scale"); + const seed = interaction.options.getInteger("seed"); + const sampler = interaction.options.getString("sampler"); + + let prompt = interaction.options.getString("prompt"); + let negative_prompt = interaction.options.getString("negative-prompt"); + let model = interaction.options.getString("model"); + + prompt = (await translate(prompt, { to: "en" })).text; + negative_prompt = (await translate(negative_prompt, { to: "en" })).text; + + const nsfw_embed = new EmbedBuilder() + .setDescription(`**⚠️ NSFW content detected!**`) + .setColor("Red"); + + if (Block_NSFW_Images) { + try { + const response = await fetch( + "https://raw.githubusercontent.com/LDNOOBW/List-of-Dirty-Naughty-Obscene-and-Otherwise-Bad-Words/master/en", + ); + const data = await response.text(); + let nsfw_words = data.split("\n"); + nsfw_words = nsfw_words.filter( + (word) => word !== "suck" && word !== "sucks", + ); + + let promptWords = prompt.split(" "); + + for (let word of nsfw_words) { + if (promptWords.includes(word)) { + await interaction.followUp({ embeds: [nsfw_embed] }); + return; + } + } + } catch (err) { + console.error(err); + } + } + + async function nsfw_getPic(image) { + const pic = await axios.get(image, { responseType: "arraybuffer" }); + const model = await nsfw.load(); + image_analyse = await tf.node.decodeImage(pic.data, 3); + const predictions = await model.classify(image_analyse); + image_analyse.dispose(); + + if ( + (predictions[0].probability > 0.5 && + predictions[0].className === "Porn") || + predictions[0].className === "Hentai" || + predictions[0].className === "Sexy" + ) { + await interaction.followUp({ embeds: [nsfw_embed] }); + return true; + } + return image; + } + + const sdk = require("api")("@prodia/v1.3.0#6fdmny2flsvwyf65"); + + sdk.auth(XProdiaKey); + + async function fetchModels(apiMethod) { + try { + const { data } = await apiMethod(); + return JSON.parse(data); + } catch (e) { + return interaction.followUp({ embeds: [error] }); + } + } + + const choices = await fetchModels(sdk.listModels); + const sdxlChoices = await fetchModels(sdk.listSdxlModels); + + const allModels = [...choices, ...sdxlChoices]; + + if (model && !allModels.includes(model)) { + const no_model = new EmbedBuilder() + .setDescription( + `**${model}** is not a valid model!\n\n> Run \`/models\` to see the available models!`, + ) + .setColor("Red"); + + return interaction.followUp({ embeds: [no_model] }); + } + + if (!model) { + model = "absolutereality_V16.safetensors [37db0fc3]"; + } + + let generateParams = { + model: model, + prompt: prompt, + ...(style_preset && { style_preset: style_preset }), + ...(negative_prompt && { negative_prompt: negative_prompt }), + ...(steps && { steps: steps }), + ...(sampler && { sampler: sampler }), + ...(cfg_scale && { cfg_scale: cfg_scale }), + ...(seed && { seed: seed }), + }; + + try { + const generateMethod = sdxlChoices.includes(model) + ? sdk.sdxlGenerate + : sdk.generate; + generateMethod(generateParams) + .then(({ data }) => { + const jobId = data.job; + const intervalId = setInterval(() => { + sdk + .getJob({ jobId: jobId }) + .then(({ data }) => { + if (data.status === "succeeded") { + clearInterval(intervalId); + + let image = data.imageUrl; + + (async () => { + if (Block_NSFW_Images) { + const newImage = await nsfw_getPic(image); + image = newImage; + if (image === true) { + return; + } + } + + const success = new EmbedBuilder() + .setImage(image) + .setTitle("🖼️ Generated Image!") + .setDescription(`> **${prompt}**`) + .setColor("Random") + .setFooter({ + text: `Requested by ${interaction.user.tag}, Powered By Prodia`, + iconURL: interaction.user.displayAvatarURL(), + }); + + return interaction.followUp({ embeds: [success] }); + })(); + } else if (data.status === "failed") { + clearInterval(intervalId); + return interaction.followUp({ embeds: [error] }); + } + }) + .catch((err) => console.error(err)); + }, 2000); + }) + .catch((err) => console.error(err)); + } catch (err) { + return interaction.followUp({ embeds: [error] }); + } + }, +}; diff --git a/interactions/slash/misc/info.js b/interactions/slash/misc/info.js index 899eeb0..4093810 100644 --- a/interactions/slash/misc/info.js +++ b/interactions/slash/misc/info.js @@ -3,45 +3,57 @@ * @author TechyGiraffe999 */ - -const { EmbedBuilder, SlashCommandBuilder, ButtonBuilder, ButtonStyle, ActionRowBuilder } = require("discord.js"); +const { + EmbedBuilder, + SlashCommandBuilder, + ButtonBuilder, + ButtonStyle, + ActionRowBuilder, +} = require("discord.js"); /** * @type {import('../../../typings').SlashInteractionCommand} */ module.exports = { - data: new SlashCommandBuilder() - .setName('info') - .setDescription('Displays info on me!'), - async execute(interaction) { - - const github = new ButtonBuilder() - .setURL('https://github.com/TecEash1/Taurus') - .setLabel('GitHub Repository') - .setEmoji("⚒️") - .setStyle(ButtonStyle.Link); - - const buttons = new ActionRowBuilder() - .addComponents(github); - - let totalSecs = (interaction.client.uptime / 1000); - let days = Math.floor(totalSecs / 86400);totalSecs %= 86400; - let hrs = Math.floor(totalSecs / 3600);totalSecs %= 3600; - let mins = Math.floor(totalSecs / 60); - let seconds = Math.floor(totalSecs % 60); - let uptime = `**${days}**d **${hrs}**h **${mins}**m **${seconds}**s`; - - const embed = new EmbedBuilder() - .setTitle("🤖 Info") - .setDescription("I am a bot created by TechyGiraffe999. I was made to be a specialised AI Bot.") - .addFields( - { name: `**⌛ UPTIME**`, value: `${uptime}`}, - { name: `**📡 PING**`, value: `Responded in \`\`${interaction.client.ws.ping}ms\`\``} - ) - .setThumbnail("https://github.com/TecEash1/TecEash1/assets/92249532/bd4aca7e-daab-4eeb-9265-e53cc1925e8c") - .setColor("Blurple"); - - await interaction.reply({ embeds: [embed], components: [buttons]}); - }, -}; \ No newline at end of file + data: new SlashCommandBuilder() + .setName("info") + .setDescription("Displays info on me!"), + async execute(interaction) { + const github = new ButtonBuilder() + .setURL("https://github.com/TecEash1/Taurus") + .setLabel("GitHub Repository") + .setEmoji("⚒️") + .setStyle(ButtonStyle.Link); + + const buttons = new ActionRowBuilder().addComponents(github); + + let totalSecs = interaction.client.uptime / 1000; + let days = Math.floor(totalSecs / 86400); + totalSecs %= 86400; + let hrs = Math.floor(totalSecs / 3600); + totalSecs %= 3600; + let mins = Math.floor(totalSecs / 60); + let seconds = Math.floor(totalSecs % 60); + let uptime = `**${days}**d **${hrs}**h **${mins}**m **${seconds}**s`; + + const embed = new EmbedBuilder() + .setTitle("🤖 Info") + .setDescription( + "I am a bot created by TechyGiraffe999. I was made to be a specialised AI Bot.", + ) + .addFields( + { name: `**⌛ UPTIME**`, value: `${uptime}` }, + { + name: `**📡 PING**`, + value: `Responded in \`\`${interaction.client.ws.ping}ms\`\``, + }, + ) + .setThumbnail( + "https://github.com/TecEash1/TecEash1/assets/92249532/bd4aca7e-daab-4eeb-9265-e53cc1925e8c", + ) + .setColor("Blurple"); + + await interaction.reply({ embeds: [embed], components: [buttons] }); + }, +}; diff --git a/interactions/slash/misc/models.js b/interactions/slash/misc/models.js index a1d4e39..7ee5866 100644 --- a/interactions/slash/misc/models.js +++ b/interactions/slash/misc/models.js @@ -7,55 +7,58 @@ * @type {import('../../../typings').SlashInteractionCommand} */ -const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); -const {XProdiaKey} = require('../../../config.json') - +const { SlashCommandBuilder, EmbedBuilder } = require("discord.js"); +const { XProdiaKey } = require("../../../config.json"); module.exports = { - data: new SlashCommandBuilder() - .setName('models') - .setDescription('List the available models for image generation!'), - - async execute(interaction, client) { - - if (!XProdiaKey || XProdiaKey.length < 4) { - invalid_api = new EmbedBuilder() - .setTitle("⚠️ Invalid API Key") - .setDescription("> **The API Key for Prodia is invalid or not provided.**") - .setColor("Red") - return interaction.reply({ embeds: [invalid_api] }); - } - - const error = new EmbedBuilder() - .setTitle("⚠️ An Unknown Error Occured") - .setDescription("> **The Prodia API Key usage may have been used up, or the API is invalid or not working at the moment.**\n\n> Please try again later or contact the developers.") - .setColor("Red") - - await interaction.deferReply(); - - const sdk = require('api')('@prodia/v1.3.0#6fdmny2flsvwyf65'); - - sdk.auth(XProdiaKey); - - async function fetchAndFormatModels(apiMethod) { - try { - const { data } = await apiMethod(); - const models = JSON.parse(data); - return "```\n- " + models.join("\n- ") + "\n```"; - } catch (e) { - return interaction.followUp({embeds: [error]}); - } - } - - const choices_string = await fetchAndFormatModels(sdk.listModels); - const sdxlChoices_string = await fetchAndFormatModels(sdk.listSdxlModels); - - const models = new EmbedBuilder() - .setTitle('🖼️ Available Models') - .setDescription(`**\n🌟 SD Models:**\n\n ${choices_string}\n\n**🚀 SDXL Models:**\n\n ${sdxlChoices_string}`) - .setColor('Random') - - return interaction.followUp({embeds: [models]}); - }, -}; + data: new SlashCommandBuilder() + .setName("models") + .setDescription("List the available models for image generation!"), + + async execute(interaction, client) { + if (!XProdiaKey || XProdiaKey.length < 4) { + invalid_api = new EmbedBuilder() + .setTitle("⚠️ Invalid API Key") + .setDescription( + "> **The API Key for Prodia is invalid or not provided.**", + ) + .setColor("Red"); + return interaction.reply({ embeds: [invalid_api] }); + } + + const error = new EmbedBuilder() + .setTitle("⚠️ An Unknown Error Occured") + .setDescription( + "> **The Prodia API Key usage may have been used up, or the API is invalid or not working at the moment.**\n\n> Please try again later or contact the developers.", + ) + .setColor("Red"); + + await interaction.deferReply(); + + const sdk = require("api")("@prodia/v1.3.0#6fdmny2flsvwyf65"); + sdk.auth(XProdiaKey); + + async function fetchAndFormatModels(apiMethod) { + try { + const { data } = await apiMethod(); + const models = JSON.parse(data); + return "```\n- " + models.join("\n- ") + "\n```"; + } catch (e) { + return interaction.followUp({ embeds: [error] }); + } + } + + const choices_string = await fetchAndFormatModels(sdk.listModels); + const sdxlChoices_string = await fetchAndFormatModels(sdk.listSdxlModels); + + const models = new EmbedBuilder() + .setTitle("🖼️ Available Models") + .setDescription( + `**\n🌟 SD Models:**\n\n ${choices_string}\n\n**🚀 SDXL Models:**\n\n ${sdxlChoices_string}`, + ) + .setColor("Random"); + + return interaction.followUp({ embeds: [models] }); + }, +}; diff --git a/interactions/slash/misc/personalise.js b/interactions/slash/misc/personalise.js index 4f17e30..52c5411 100644 --- a/interactions/slash/misc/personalise.js +++ b/interactions/slash/misc/personalise.js @@ -7,284 +7,355 @@ * @type {import('../../../typings').SlashInteractionCommand} */ -const { SlashCommandBuilder, ModalBuilder, TextInputBuilder, TextInputStyle, ActionRowBuilder, EmbedBuilder, ButtonBuilder } = require("discord.js"); -const axios = require('axios'); -const fs = require('fs').promises; -const path = require('path'); -const {guild_id_logs, channel_id_logs, owner} = require("../../../config.json"); +const { + SlashCommandBuilder, + ModalBuilder, + TextInputBuilder, + TextInputStyle, + ActionRowBuilder, + EmbedBuilder, + ButtonBuilder, +} = require("discord.js"); +const axios = require("axios"); +const fs = require("fs").promises; +const path = require("path"); +const { + guild_id_logs, + channel_id_logs, + owner, +} = require("../../../config.json"); const serverId = guild_id_logs; const channelId = channel_id_logs; const no_access = new EmbedBuilder() - .setDescription("**Only my developers can directly update my global personality prompt!**\n\nIf you want to suggest a change, please let us know!") + .setDescription( + "**Only my developers can directly update my global personality prompt!**\n\nIf you want to suggest a change, please let us know!", + ) .setColor("Red"); const link_error = new EmbedBuilder() - .setDescription("**The file link is not a valid URL!**") - .setColor("Red"); + .setDescription("**The file link is not a valid URL!**") + .setColor("Red"); const error_null = new EmbedBuilder() - .setDescription("**You must select at least one option!**") - .setColor("Red"); + .setDescription("**You must select at least one option!**") + .setColor("Red"); const error_multiple = new EmbedBuilder() - .setDescription("**You can only select one option!**") - .setColor("Red"); + .setDescription("**You can only select one option!**") + .setColor("Red"); const file_read_error = new EmbedBuilder() - .setDescription("**There was an error while reading the file!**") - .setColor("Red"); + .setDescription("**There was an error while reading the file!**") + .setColor("Red"); const txt_only = new EmbedBuilder() - .setDescription("**You can only upload a .txt file!**") - .setColor("Red"); + .setDescription("**You can only upload a .txt file!**") + .setColor("Red"); module.exports = { data: new SlashCommandBuilder() .setName("personalise") .setDescription("Customise my Personality!") - .addAttachmentOption(option => option - .setRequired(false) - .setName("file") - .setDescription("The personality text file")) - .addStringOption(option => option - .setRequired(false) - .setName("file-link") - .setDescription("The personality text file link")) - .addStringOption(option => - option.setName("other") + .addAttachmentOption((option) => + option + .setRequired(false) + .setName("file") + .setDescription("The personality text file"), + ) + .addStringOption((option) => + option + .setRequired(false) + .setName("file-link") + .setDescription("The personality text file link"), + ) + .addStringOption((option) => + option + .setName("other") .setDescription("Other Options") .addChoices( { name: "Get", value: "get" }, { name: "Modal", value: "modal" }, - )), + ), + ), async execute(interaction) { const file = interaction.options.getAttachment("file"); - const file_link = interaction.options.getString("file-link"); - const other = interaction.options.getString("other"); - - if (!owner.includes(interaction.user.id)) { - return await interaction.reply({ embeds: [no_access], ephemeral: true }); - } - - const selectedOptions = [file, file_link, other].filter(option => option != null).length; - - if (selectedOptions === 0 || selectedOptions > 1) { - const errorMessage = selectedOptions === 0 ? error_null : error_multiple; - return await interaction.reply({ embeds: [errorMessage], ephemeral: true }); - } - - const urlRegex = /^(ftp|http|https):\/\/[^ "]+$/; - if (file_link && !urlRegex.test(file_link)) { - return interaction.reply({ embeds: [link_error], ephemeral: true }); - } - - let personalityPrompt; - - async function getPersonalityPrompt(file, file_link) { - const url = file?.url || file_link; - - try { - const response = await axios.get(url); - return response.data; - } catch (error) { - return error - } - } - - if (file || file_link) { - const url = file?.url || file_link; - - const txtRegex = /\.txt(?=\?|$)/; - if (!txtRegex.test(url)) { - return await interaction.reply({ embeds: [txt_only], ephemeral: true }); - } - - const result = await getPersonalityPrompt(file, file_link); - - if (result instanceof Error) { - return await interaction.reply({ embeds: [file_read_error], ephemeral: true }); - } else { - personalityPrompt = result; - } - } - - - const personalityFilePath = path.join(__dirname + '../../../../personality.txt'); - - - let personalityContent; - try { - personalityContent = await fs.readFile(personalityFilePath, 'utf-8'); - } catch (err) { - console.error(err); - } - - let submitted - let modal_error - - if (other) { - switch(other){ - case "get": - return await interaction.reply({ files: [{ attachment: personalityFilePath, name: 'personality.txt' }], ephemeral: true }); - case "modal": - const personalise = new ModalBuilder() - .setTitle("Customise how Taurus responds") - .setCustomId("taurus_ai_personality"); - - const prompt = new TextInputBuilder() - .setCustomId("personalise_taurusai") - .setRequired(true) - .setLabel("Personality Prompt:") - .setStyle(TextInputStyle.Paragraph); - const prompt2 = new TextInputBuilder() - .setCustomId("personalise_taurusai2") - .setRequired(false) - .setLabel("Extra Space:") - .setStyle(TextInputStyle.Paragraph); - const prompt3 = new TextInputBuilder() - .setCustomId("personalise_taurusai3") - .setRequired(false) - .setLabel("Extra Space:") - .setStyle(TextInputStyle.Paragraph); - - - const taurusai_personality_ActionRow = new ActionRowBuilder().addComponents(prompt); - const taurusai_personality_ActionRow2 = new ActionRowBuilder().addComponents(prompt2); - const taurusai_personality_ActionRow3 = new ActionRowBuilder().addComponents(prompt3); - - personalise.addComponents(taurusai_personality_ActionRow, taurusai_personality_ActionRow2, taurusai_personality_ActionRow3); - await interaction.showModal(personalise); - - submitted = await interaction.awaitModalSubmit({ - time: 300000, - filter: i => i.user.id === interaction.user.id, - }).catch(error => { - modal_error = true; - return false - }) - - if (submitted) { - personalityPrompt = await submitted.fields.getTextInputValue("personalise_taurusai"); - const personalityPrompt2 = await submitted.fields.getTextInputValue("personalise_taurusai2"); - const personalityPrompt3 = await submitted.fields.getTextInputValue("personalise_taurusai3"); - if (personalityPrompt2) { - personalityPrompt += personalityPrompt2; - } - if (personalityPrompt3) { - personalityPrompt += personalityPrompt3; - } - } - } - } - - if (modal_error) return; - - success = new EmbedBuilder() - .setDescription("✅ **Personality prompt updated successfully!**") - .setColor("Green"); - - cancel = new EmbedBuilder() - .setDescription("❌ **Operation cancelled.**") - .setColor("Red"); - - const error = new EmbedBuilder() - .setDescription("⚠️ There was an error while fetching the TaurusAI Log channel, please contact the Developers.") - .setColor("Red"); - - const row = new ActionRowBuilder() - .addComponents( - new ButtonBuilder() - .setCustomId('yes_botai_personality') - .setLabel('Update') - .setEmoji('✅') - .setStyle('Primary'), - new ButtonBuilder() - .setCustomId('no_botai_personality') - .setLabel('Cancel') - .setEmoji('❌') - .setStyle('Primary'), - ); - - let personalityContent_truncate = personalityContent; - if (personalityContent.length > 2002) { - personalityContent_truncate = personalityContent.substring(0, 2002) + '...'; - } - - let personalityPrompt_truncate = personalityPrompt; - if (personalityPrompt.length > 2002) { - personalityPrompt_truncate = personalityPrompt.substring(0, 2002) + '...'; - } - - let description = `**Current personality prompt:**\n\`\`\`${personalityContent_truncate}\`\`\`\n\n**New personality prompt:**\n\`\`\`${personalityPrompt_truncate}\`\`\``; - - embed = new EmbedBuilder() - .setTitle("Are you sure you want to update the personality prompt?") - .setDescription(description) - .setFooter({ text: `⚠️ This will wipe the old Prompt, resetting it with the new one.`, iconURL: interaction.user.displayAvatarURL() }) - .setColor("Orange") - - await (submitted ?? interaction).reply({ - embeds: [embed], - components: [row], - files: [{ attachment: personalityFilePath, name: 'current_personality.txt' }], - ephemeral: true - }); - - const filter = i => i.customId === 'yes_botai_personality' || i.customId === 'no_botai_personality'; - const collector = interaction.channel.createMessageComponentCollector({ filter, time: 60000 }); - - collector.on('collect', async i => { - collector.stop(); - if (i.customId === 'yes_botai_personality') { - - const personalityFilePath = path.join(__dirname, '../../../personality.txt'); - const tempFilePath = path.join(__dirname, 'temp.txt'); - - let oldPersonalityContent; - try { - oldPersonalityContent = await fs.readFile(personalityFilePath, 'utf-8'); - await fs.writeFile(tempFilePath, oldPersonalityContent); - } catch (err) { - console.error(err); - } - - await fs.writeFile(personalityFilePath, personalityPrompt); - - try{ - const guild = interaction.client.guilds.cache.get(serverId); - const channel = guild.channels.cache.get(channelId); - - - update = new EmbedBuilder() - .setDescription(`**Personality prompt updated by <@${interaction.user.id}>**`) - .setColor("Orange") - .setFooter({ text: `ID: ${interaction.user.id}`, iconURL: interaction.user.displayAvatarURL() }) - .setTimestamp(); - - await channel.send({ - embeds: [update], - files: [ - { attachment: personalityFilePath, name: 'new_personality.txt' }, - { attachment: tempFilePath, name: 'old_personality.txt' } - ] - }); - await i.update({ embeds: [success], components: [], files: [] }); - } catch (err) { - await i.update({ embeds: [success,error], components: [], files: [] }); - } - - try { - await fs.unlink(tempFilePath); - } catch (err) { - console.error(err); - } - - } else { - await i.update({ embeds: [cancel], components: [], files: [] }); - } - }); - } -} \ No newline at end of file + const file_link = interaction.options.getString("file-link"); + const other = interaction.options.getString("other"); + + if (!owner.includes(interaction.user.id)) { + return await interaction.reply({ embeds: [no_access], ephemeral: true }); + } + + const selectedOptions = [file, file_link, other].filter( + (option) => option != null, + ).length; + + if (selectedOptions === 0 || selectedOptions > 1) { + const errorMessage = selectedOptions === 0 ? error_null : error_multiple; + return await interaction.reply({ + embeds: [errorMessage], + ephemeral: true, + }); + } + + const urlRegex = /^(ftp|http|https):\/\/[^ "]+$/; + if (file_link && !urlRegex.test(file_link)) { + return interaction.reply({ embeds: [link_error], ephemeral: true }); + } + + let personalityPrompt; + + async function getPersonalityPrompt(file, file_link) { + const url = file?.url || file_link; + + try { + const response = await axios.get(url); + return response.data; + } catch (error) { + return error; + } + } + + if (file || file_link) { + const url = file?.url || file_link; + + const txtRegex = /\.txt(?=\?|$)/; + if (!txtRegex.test(url)) { + return await interaction.reply({ embeds: [txt_only], ephemeral: true }); + } + + const result = await getPersonalityPrompt(file, file_link); + + if (result instanceof Error) { + return await interaction.reply({ + embeds: [file_read_error], + ephemeral: true, + }); + } else { + personalityPrompt = result; + } + } + + const personalityFilePath = path.join( + __dirname + "../../../../personality.txt", + ); + + let personalityContent; + try { + personalityContent = await fs.readFile(personalityFilePath, "utf-8"); + } catch (err) { + console.error(err); + } + + let submitted; + let modal_error; + + if (other) { + switch (other) { + case "get": + return await interaction.reply({ + files: [ + { attachment: personalityFilePath, name: "personality.txt" }, + ], + ephemeral: true, + }); + case "modal": + const personalise = new ModalBuilder() + .setTitle("Customise how Taurus responds") + .setCustomId("taurus_ai_personality"); + + const prompt = new TextInputBuilder() + .setCustomId("personalise_taurusai") + .setRequired(true) + .setLabel("Personality Prompt:") + .setStyle(TextInputStyle.Paragraph); + const prompt2 = new TextInputBuilder() + .setCustomId("personalise_taurusai2") + .setRequired(false) + .setLabel("Extra Space:") + .setStyle(TextInputStyle.Paragraph); + const prompt3 = new TextInputBuilder() + .setCustomId("personalise_taurusai3") + .setRequired(false) + .setLabel("Extra Space:") + .setStyle(TextInputStyle.Paragraph); + + const taurusai_personality_ActionRow = + new ActionRowBuilder().addComponents(prompt); + const taurusai_personality_ActionRow2 = + new ActionRowBuilder().addComponents(prompt2); + const taurusai_personality_ActionRow3 = + new ActionRowBuilder().addComponents(prompt3); + + personalise.addComponents( + taurusai_personality_ActionRow, + taurusai_personality_ActionRow2, + taurusai_personality_ActionRow3, + ); + await interaction.showModal(personalise); + + submitted = await interaction + .awaitModalSubmit({ + time: 300000, + filter: (i) => i.user.id === interaction.user.id, + }) + .catch((error) => { + modal_error = true; + return false; + }); + + if (submitted) { + personalityPrompt = await submitted.fields.getTextInputValue( + "personalise_taurusai", + ); + const personalityPrompt2 = await submitted.fields.getTextInputValue( + "personalise_taurusai2", + ); + const personalityPrompt3 = await submitted.fields.getTextInputValue( + "personalise_taurusai3", + ); + if (personalityPrompt2) { + personalityPrompt += personalityPrompt2; + } + if (personalityPrompt3) { + personalityPrompt += personalityPrompt3; + } + } + } + } + + if (modal_error) return; + + success = new EmbedBuilder() + .setDescription("✅ **Personality prompt updated successfully!**") + .setColor("Green"); + + cancel = new EmbedBuilder() + .setDescription("❌ **Operation cancelled.**") + .setColor("Red"); + + const error = new EmbedBuilder() + .setDescription( + "⚠️ There was an error while fetching the TaurusAI Log channel, please contact the Developers.", + ) + .setColor("Red"); + + const row = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId("yes_botai_personality") + .setLabel("Update") + .setEmoji("✅") + .setStyle("Primary"), + new ButtonBuilder() + .setCustomId("no_botai_personality") + .setLabel("Cancel") + .setEmoji("❌") + .setStyle("Primary"), + ); + + let personalityContent_truncate = personalityContent; + if (personalityContent.length > 2002) { + personalityContent_truncate = + personalityContent.substring(0, 2002) + "..."; + } + + let personalityPrompt_truncate = personalityPrompt; + if (personalityPrompt.length > 2002) { + personalityPrompt_truncate = personalityPrompt.substring(0, 2002) + "..."; + } + + let description = `**Current personality prompt:**\n\`\`\`${personalityContent_truncate}\`\`\`\n\n**New personality prompt:**\n\`\`\`${personalityPrompt_truncate}\`\`\``; + + embed = new EmbedBuilder() + .setTitle("Are you sure you want to update the personality prompt?") + .setDescription(description) + .setFooter({ + text: `⚠️ This will wipe the old Prompt, resetting it with the new one.`, + iconURL: interaction.user.displayAvatarURL(), + }) + .setColor("Orange"); + + await (submitted ?? interaction).reply({ + embeds: [embed], + components: [row], + files: [ + { attachment: personalityFilePath, name: "current_personality.txt" }, + ], + ephemeral: true, + }); + + const filter = (i) => + i.customId === "yes_botai_personality" || + i.customId === "no_botai_personality"; + const collector = interaction.channel.createMessageComponentCollector({ + filter, + time: 60000, + }); + + collector.on("collect", async (i) => { + collector.stop(); + if (i.customId === "yes_botai_personality") { + const personalityFilePath = path.join( + __dirname, + "../../../personality.txt", + ); + const tempFilePath = path.join(__dirname, "temp.txt"); + + let oldPersonalityContent; + try { + oldPersonalityContent = await fs.readFile( + personalityFilePath, + "utf-8", + ); + await fs.writeFile(tempFilePath, oldPersonalityContent); + } catch (err) { + console.error(err); + } + + await fs.writeFile(personalityFilePath, personalityPrompt); + + try { + const guild = interaction.client.guilds.cache.get(serverId); + const channel = guild.channels.cache.get(channelId); + + update = new EmbedBuilder() + .setDescription( + `**Personality prompt updated by <@${interaction.user.id}>**`, + ) + .setColor("Orange") + .setFooter({ + text: `ID: ${interaction.user.id}`, + iconURL: interaction.user.displayAvatarURL(), + }) + .setTimestamp(); + + await channel.send({ + embeds: [update], + files: [ + { attachment: personalityFilePath, name: "new_personality.txt" }, + { attachment: tempFilePath, name: "old_personality.txt" }, + ], + }); + await i.update({ embeds: [success], components: [], files: [] }); + } catch (err) { + await i.update({ + embeds: [success, error], + components: [], + files: [], + }); + } + + try { + await fs.unlink(tempFilePath); + } catch (err) { + console.error(err); + } + } else { + await i.update({ embeds: [cancel], components: [], files: [] }); + } + }); + }, +}; diff --git a/interactions/slash/misc/taurusai.js b/interactions/slash/misc/taurusai.js index 0bc4d16..53890c3 100644 --- a/interactions/slash/misc/taurusai.js +++ b/interactions/slash/misc/taurusai.js @@ -3,27 +3,34 @@ * @author TechyGiraffe999 */ -const { SlashCommandBuilder, ModalBuilder, TextInputBuilder, TextInputStyle, ActionRowBuilder, EmbedBuilder } = require("discord.js"); - +const { + SlashCommandBuilder, + ModalBuilder, + TextInputBuilder, + TextInputStyle, + ActionRowBuilder, + EmbedBuilder, +} = require("discord.js"); module.exports = { - data: new SlashCommandBuilder() - .setName('taurus') - .setDescription('Ask Taurus a question!'), - async execute(interaction) { - - const taurus = new ModalBuilder() - .setTitle("Ask TaurusAI something") - .setCustomId("taurus_ai"); + data: new SlashCommandBuilder() + .setName("taurus") + .setDescription("Ask Taurus a question!"), + async execute(interaction) { + const taurus = new ModalBuilder() + .setTitle("Ask TaurusAI something") + .setCustomId("taurus_ai"); - const question = new TextInputBuilder() - .setCustomId("question_taurusai") - .setRequired(true) - .setLabel("Question:") - .setStyle(TextInputStyle.Paragraph); + const question = new TextInputBuilder() + .setCustomId("question_taurusai") + .setRequired(true) + .setLabel("Question:") + .setStyle(TextInputStyle.Paragraph); - const taurusai_question_ActionRow = new ActionRowBuilder().addComponents(question); - taurus.addComponents(taurusai_question_ActionRow); - await interaction.showModal(taurus); - } -}; \ No newline at end of file + const taurusai_question_ActionRow = new ActionRowBuilder().addComponents( + question, + ); + taurus.addComponents(taurusai_question_ActionRow); + await interaction.showModal(taurus); + }, +}; diff --git a/messages/defaultModalError.js b/messages/defaultModalError.js index 284fce2..6a98784 100644 --- a/messages/defaultModalError.js +++ b/messages/defaultModalError.js @@ -5,7 +5,9 @@ const { EmbedBuilder } = require("discord.js"); const error = new EmbedBuilder() - .setDescription("**There was an issue while fetching this modal!\n\nPlease contact the Developers.**") + .setDescription( + "**There was an issue while fetching this modal!\n\nPlease contact the Developers.**", + ) .setColor("Red"); module.exports = { diff --git a/package.json b/package.json index 672d495..9ea014b 100644 --- a/package.json +++ b/package.json @@ -36,5 +36,8 @@ "moment": "^2.30.1", "nsfwjs": "^4.1.0", "os": "^0.1.2" + }, + "devDependencies": { + "prettier": "3.2.5" } } diff --git a/start.mjs b/start.mjs index bb06e74..40738c3 100644 --- a/start.mjs +++ b/start.mjs @@ -8,7 +8,7 @@ import figlet from "figlet"; // Create a screen object. const screen = blessed.screen({ smartCSR: true, - title: "Taurus Bot Console" + title: "Taurus Bot Console", }); // Create a box that fills the entire screen. @@ -20,15 +20,15 @@ const box = blessed.box({ content: "", tags: true, border: { - type: "line" + type: "line", }, style: { fg: "white", bg: "black", border: { - fg: "#ff0000" // Red - } - } + fg: "#ff0000", // Red + }, + }, }); // Create a console box at the top right. @@ -40,15 +40,15 @@ const consoleBox = blessed.box({ content: "{underline}Taurus Console{/underline}", tags: true, border: { - type: "line" + type: "line", }, style: { fg: "white", bg: "black", border: { - fg: "#ff0000" // Red - } - } + fg: "#ff0000", // Red + }, + }, }); // Append our boxes to the screen. @@ -62,13 +62,15 @@ let botStatus = "Offline"; // Function to start the Discord bot. function startBot() { if (bot) { - writeToConsole(chalk.red("Bot is already running. Please stop the bot first.")); + writeToConsole( + chalk.red("Bot is already running. Please stop the bot first."), + ); return; } // Modify this line to start your bot with the correct command and arguments bot = spawn("node", ["bot.js"], { - stdio: ["pipe", "pipe", "pipe", "pipe", "pipe", "pipe", process.stderr] + stdio: ["pipe", "pipe", "pipe", "pipe", "pipe", "pipe", process.stderr], }); botStartTime = moment(); @@ -93,7 +95,9 @@ function startBot() { // Function to stop the Discord bot. function stopBot() { if (!bot) { - writeToConsole(chalk.red("Bot is not running. Please start the bot first.")); + writeToConsole( + chalk.red("Bot is not running. Please start the bot first."), + ); return; } @@ -111,24 +115,26 @@ function stopBot() { // Function to restart the Discord bot. function restartBot() { - stopBot(); - const gitPull = spawn('git', ['pull']); - - gitPull.stdout.on('data', (data) => { - writeToConsole(chalk.italic(`[GIT] stdout: ${data}`)); - }); - - gitPull.stderr.on('data', (data) => { - writeToConsole(chalk.italic(`[GIT] stderr: ${data}`)); - }); - - gitPull.on('close', (code) => { - writeToConsole(chalk.italic(`[GIT] child process exited with code ${code}`)); - setTimeout(() => { - startBot(); - writeToConsole(chalk.red("Bot restarted.")); - }, 1000); - }); + stopBot(); + const gitPull = spawn("git", ["pull"]); + + gitPull.stdout.on("data", (data) => { + writeToConsole(chalk.italic(`[GIT] stdout: ${data}`)); + }); + + gitPull.stderr.on("data", (data) => { + writeToConsole(chalk.italic(`[GIT] stderr: ${data}`)); + }); + + gitPull.on("close", (code) => { + writeToConsole( + chalk.italic(`[GIT] child process exited with code ${code}`), + ); + setTimeout(() => { + startBot(); + writeToConsole(chalk.red("Bot restarted.")); + }, 1000); + }); } // Function to refresh the console. @@ -139,20 +145,24 @@ function refreshConsole() { } let title = ""; -figlet.text("Taurus", { - font: "Standard", - horizontalLayout: "default", - verticalLayout: "default", - width: 100, - whitespaceBreak: true -}, function(err, data) { - if (err) { - console.log("Something went wrong..."); - console.dir(err); - return; - } - title = chalk.red(data); // Red -}); +figlet.text( + "Taurus", + { + font: "Standard", + horizontalLayout: "default", + verticalLayout: "default", + width: 100, + whitespaceBreak: true, + }, + function (err, data) { + if (err) { + console.log("Something went wrong..."); + console.dir(err); + return; + } + title = chalk.red(data); // Red + }, +); // Function to update the box content with system and bot stats function updateStats() { @@ -162,25 +172,27 @@ function updateStats() { const cpuCores = os.cpus().length.toString(); const osInfo = `${os.type()} (${os.release()})`; - const botUptime = botStartTime ? moment.duration(moment().diff(botStartTime)).humanize() : "Not available"; - const botMemoryUsage = (process.memoryUsage().heapUsed / 1024 / 1024 / 1024).toFixed(2) + " GB"; - - box.setContent(`${title}\n\n` + - `${chalk.red("Bot Status:")} ${botStatus === "Online" ? chalk.green(botStatus) : chalk.red(botStatus)}\n\n` + - `${chalk.red("VPS Uptime:")} ${chalk.white(serverUptime)}\n` + - `${chalk.red("Total Memory:")} ${chalk.white(totalMemory)}\n` + - `${chalk.red("Bot Uptime:")} ${chalk.white(botUptime)}\n` + - `${chalk.red("Bot Memory Usage:")} ${chalk.white(botMemoryUsage)}\n` + - `${chalk.red("CPU Threads:")} ${chalk.white(cpuCores)}\n` + - `${chalk.red("OS Info:")} ${chalk.white(osInfo)}\n\n` + - `${chalk.red("Commands:")}\n` + - - `${chalk.green("S")} - ${chalk.white("Start Bot")}\n` + - `${chalk.green("X")} - ${chalk.white("Stop Bot")}\n` + - `${chalk.green("R")} - ${chalk.white("Restart & Update Bot")}\n` + - `${chalk.green("L")} - ${chalk.white("Refresh Console")}\n\n` + - - `${chalk.red("Press")} ${chalk.white("Ctrl+C")} ${chalk.red("to stop the bot and exit.")}\n\n\n` + const botUptime = botStartTime + ? moment.duration(moment().diff(botStartTime)).humanize() + : "Not available"; + const botMemoryUsage = + (process.memoryUsage().heapUsed / 1024 / 1024 / 1024).toFixed(2) + " GB"; + + box.setContent( + `${title}\n\n` + + `${chalk.red("Bot Status:")} ${botStatus === "Online" ? chalk.green(botStatus) : chalk.red(botStatus)}\n\n` + + `${chalk.red("VPS Uptime:")} ${chalk.white(serverUptime)}\n` + + `${chalk.red("Total Memory:")} ${chalk.white(totalMemory)}\n` + + `${chalk.red("Bot Uptime:")} ${chalk.white(botUptime)}\n` + + `${chalk.red("Bot Memory Usage:")} ${chalk.white(botMemoryUsage)}\n` + + `${chalk.red("CPU Threads:")} ${chalk.white(cpuCores)}\n` + + `${chalk.red("OS Info:")} ${chalk.white(osInfo)}\n\n` + + `${chalk.red("Commands:")}\n` + + `${chalk.green("S")} - ${chalk.white("Start Bot")}\n` + + `${chalk.green("X")} - ${chalk.white("Stop Bot")}\n` + + `${chalk.green("R")} - ${chalk.white("Restart & Update Bot")}\n` + + `${chalk.green("L")} - ${chalk.white("Refresh Console")}\n\n` + + `${chalk.red("Press")} ${chalk.white("Ctrl+C")} ${chalk.red("to stop the bot and exit.")}\n\n\n`, ); screen.render(); } @@ -208,7 +220,6 @@ process.on("SIGINT", () => { // Start the bot when script is run initially. startBot(); - // Listen for keystrokes and map them to commands. screen.key(["S", "s"], () => { startBot(); @@ -226,9 +237,9 @@ screen.key(["L", "l"], () => { refreshConsole(); }); -screen.key(["escape", "q", "C-c"], function(ch, key) { +screen.key(["escape", "q", "C-c"], function (ch, key) { return process.exit(0); }); // Render the screen. -screen.render(); \ No newline at end of file +screen.render(); diff --git a/typings.d.ts b/typings.d.ts index 85c0395..4d18b5d 100644 --- a/typings.d.ts +++ b/typings.d.ts @@ -56,7 +56,7 @@ export interface LegacyCommand { */ execute( message: Discord.Message & { client: Client }, - args: string[] + args: string[], ): void | Promise; } @@ -84,7 +84,7 @@ export interface SlashInteractionCommand { * @param interaction The interaction that triggered this command. */ execute( - interaction: Discord.ChatInputCommandInteraction & { client: Client } + interaction: Discord.ChatInputCommandInteraction & { client: Client }, ): void | Promise; } @@ -102,7 +102,7 @@ export interface ButtonInteractionCommand { * @param interaction The interaction that triggered this command. */ execute( - interaction: Discord.ButtonInteraction & { client: Client } + interaction: Discord.ButtonInteraction & { client: Client }, ): void | Promise; } @@ -120,7 +120,7 @@ export interface SelectInteractionCommand { * @param interaction The interaction that triggered this command. */ execute( - interaction: Discord.SelectMenuInteraction & { client: Client } + interaction: Discord.SelectMenuInteraction & { client: Client }, ): void | Promise; } @@ -155,7 +155,7 @@ export interface ContextInteractionCommand { * @param interaction The interaction that triggered this command. */ execute( - interaction: Discord.ContextMenuCommandInteraction & { client: Client } + interaction: Discord.ContextMenuCommandInteraction & { client: Client }, ): void | Promise; } @@ -173,7 +173,7 @@ export interface ModalInteractionCommand { * @param interaction The interaction that triggered this command. */ execute( - interaction: Discord.ModalSubmitInteraction & { client: Client } + interaction: Discord.ModalSubmitInteraction & { client: Client }, ): void | Promise; } @@ -192,7 +192,7 @@ export interface TriggerCommand { */ execute( message: Discord.Message & { client: Client }, - args: string[] + args: string[], ): void | Promise; } @@ -210,7 +210,7 @@ export interface AutocompleteInteraction { * @param interaction The interaction that triggered this command. */ execute( - interaction: Discord.AutocompleteInteraction & { client: Client } + interaction: Discord.AutocompleteInteraction & { client: Client }, ): void | Promise; } diff --git a/utils.js b/utils.js index dec91c6..fb2eeac 100644 --- a/utils.js +++ b/utils.js @@ -1,148 +1,188 @@ -const { HarmCategory, HarmBlockThreshold } = require('@google/generative-ai'); +const { HarmCategory, HarmBlockThreshold } = require("@google/generative-ai"); const { EmbedBuilder } = require("discord.js"); function botInGuild(interaction) { - const botGuilds = interaction.client.guilds.cache; - return botGuilds.has(interaction.guildId); + const botGuilds = interaction.client.guilds.cache; + return botGuilds.has(interaction.guildId); } const safetySettings = [ - { - category: HarmCategory.HARM_CATEGORY_HARASSMENT, - threshold: HarmBlockThreshold.BLOCK_ONLY_HIGH, - }, - { - category: HarmCategory.HARM_CATEGORY_HATE_SPEECH, - threshold: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE, - }, - { - category: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT, - threshold: HarmBlockThreshold.BLOCK_LOW_AND_ABOVE, - }, - { - category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT, - threshold: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE, - }, + { + category: HarmCategory.HARM_CATEGORY_HARASSMENT, + threshold: HarmBlockThreshold.BLOCK_ONLY_HIGH, + }, + { + category: HarmCategory.HARM_CATEGORY_HATE_SPEECH, + threshold: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE, + }, + { + category: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT, + threshold: HarmBlockThreshold.BLOCK_LOW_AND_ABOVE, + }, + { + category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT, + threshold: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE, + }, ]; async function handleGeminiError(err, loadingMsg) { - switch (err.message) { - case "[GoogleGenerativeAI Error]: Candidate was blocked due to SAFETY": - const safety_error = new EmbedBuilder() - .setTitle("⚠️ An Error Occurred") - .setDescription("> *The response was blocked due to **SAFETY**.* \n- *Result based on your input. Safety Blocking may not be 100% correct.*") - .setColor("Red") - - return await loadingMsg.edit({ embeds: [safety_error]}); - case "[GoogleGenerativeAI Error]: Error fetching from https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-pro-latest:generateContent: [400 Bad Request] User location is not supported for the API use.": - const location_error = new EmbedBuilder() - .setTitle("⚠️ An Error Occurred") - .setDescription("> *The user location is not supported for Gemini API use. Please contact the Developers.*") - .setColor("Red") - - return await loadingMsg.edit({ embeds: [location_error]}); - case "[GoogleGenerativeAI Error]: Error fetching from https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-pro-latest:generateContent: [429 Too Many Requests] Resource has been exhausted (e.g. check quota).": - const quota_error = new EmbedBuilder() - .setTitle("⚠️ An Error Occurred") - .setDescription("There are alot of requests at the moment Please try again later, or in a few minutes. \n*If this issue persists, please contact the Developers.* \n\n> - Token Limit for this minute has been reached.") - .setColor("Red") - - return await loadingMsg.edit({ embeds: [quota_error]}); - case "Cannot send an empty message": - case "response.text is not a function": - const error = new EmbedBuilder() - .setTitle("⚠️ An Error Occurred") - .setDescription("An error occurred while processing your request. Please try again later, or in a few minutes. \n*If this issue persists, please contact the Developers.* \n\n> - Generated response may be too long. *(Fix this by specifying for the generated response to be smaller, e.g. 10 Lines)*\n> - Token Limit for this minute may have been reached.") - .setColor("Red") - - return await loadingMsg.edit({ embeds: [error]}); - default: - const error_unknown = new EmbedBuilder() - .setTitle("⚠️ An Error Occurred") - .setDescription("An unknown error occurred while processing your request. Please try again later, or in a few minutes. \n*If this issue persists, please contact the Developers.*\n> - Token Limit for this minute may have been reached.") - .setColor("Red"); - - return await loadingMsg.edit({embeds: [error_unknown]}); - } + switch (err.message) { + case "[GoogleGenerativeAI Error]: Candidate was blocked due to SAFETY": + const safety_error = new EmbedBuilder() + .setTitle("⚠️ An Error Occurred") + .setDescription( + "> *The response was blocked due to **SAFETY**.* \n- *Result based on your input. Safety Blocking may not be 100% correct.*", + ) + .setColor("Red"); + + return await loadingMsg.edit({ embeds: [safety_error] }); + case "[GoogleGenerativeAI Error]: Error fetching from https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-pro-latest:generateContent: [400 Bad Request] User location is not supported for the API use.": + const location_error = new EmbedBuilder() + .setTitle("⚠️ An Error Occurred") + .setDescription( + "> *The user location is not supported for Gemini API use. Please contact the Developers.*", + ) + .setColor("Red"); + + return await loadingMsg.edit({ embeds: [location_error] }); + case "[GoogleGenerativeAI Error]: Error fetching from https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-pro-latest:generateContent: [429 Too Many Requests] Resource has been exhausted (e.g. check quota).": + const quota_error = new EmbedBuilder() + .setTitle("⚠️ An Error Occurred") + .setDescription( + "There are alot of requests at the moment Please try again later, or in a few minutes. \n*If this issue persists, please contact the Developers.* \n\n> - Token Limit for this minute has been reached.", + ) + .setColor("Red"); + + return await loadingMsg.edit({ embeds: [quota_error] }); + case "Cannot send an empty message": + case "response.text is not a function": + const error = new EmbedBuilder() + .setTitle("⚠️ An Error Occurred") + .setDescription( + "An error occurred while processing your request. Please try again later, or in a few minutes. \n*If this issue persists, please contact the Developers.* \n\n> - Generated response may be too long. *(Fix this by specifying for the generated response to be smaller, e.g. 10 Lines)*\n> - Token Limit for this minute may have been reached.", + ) + .setColor("Red"); + + return await loadingMsg.edit({ embeds: [error] }); + default: + console.error(err.message); + const error_unknown = new EmbedBuilder() + .setTitle("⚠️ An Error Occurred") + .setDescription( + "An unknown error occurred while processing your request. Please try again later, or in a few minutes. \n*If this issue persists, please contact the Developers.*\n> - Token Limit for this minute may have been reached.", + ) + .setColor("Red"); + + return await loadingMsg.edit({ embeds: [error_unknown] }); + } } -async function handleResponse(chat, userQuestion, interaction, message, loadingMsg, isContextMenuCommand) { - const result = await chat.sendMessage(userQuestion); - const response = await result.response; - - const responseLength = response.text().length; - if (responseLength > 2000) { - response.text = response.text().substring(0, 1928 - "... \n\n".length) + "... \n\n*Response was cut short due to Discords character limit of 2000*"; - } - - let responseText = response.text(); - const regex = /<@&?\d+>/g; - let match; - - while ((match = regex.exec(responseText)) !== null) { - const id = interaction ? interaction.user.id : message.author.id; - - if (match[0] !== `<@${id}>`) { - const ping_error = new EmbedBuilder() - .setTitle("⚠️ Response Cannot Be Sent") - .setDescription("> *The generated message contains a mention of a Role or different User to the one that sent the original message/command.*") - .setColor("Red") - return await loadingMsg.edit({ embeds: [ping_error] }); - } - } - - let info_embed = []; - if (isContextMenuCommand) { - const footerText = `Response to message by ${message.author.tag}\n\n${message.content}`; - const truncatedFooterText = footerText.length > 2030 ? `${footerText.slice(0, 2027)}...` : footerText; - - const info = new EmbedBuilder() - .setFooter({text: truncatedFooterText}) - .setColor("Blue"); - - info_embed.push(info); - } - - // responseText = responseText.replace(/(https?:\/\/(?!media\.discordapp\.net\/attachments\/)[^\s\)]+)/g, "<$1>"); - return await loadingMsg.edit({ content: responseText, embeds: info_embed }); +async function handleResponse( + chat, + userQuestion, + interaction, + message, + loadingMsg, + isContextMenuCommand, +) { + const result = await chat.sendMessage(userQuestion); + const response = await result.response; + + const responseLength = response.text().length; + if (responseLength > 2000) { + response.text = + response.text().substring(0, 1928 - "... \n\n".length) + + "... \n\n*Response was cut short due to Discords character limit of 2000*"; + } + + let responseText = response.text(); + const regex = /<@&?\d+>/g; + let match; + + while ((match = regex.exec(responseText)) !== null) { + const id = interaction ? interaction.user.id : message.author.id; + + if (match[0] !== `<@${id}>`) { + const ping_error = new EmbedBuilder() + .setTitle("⚠️ Response Cannot Be Sent") + .setDescription( + "> *The generated message contains a mention of a Role or different User to the one that sent the original message/command.*", + ) + .setColor("Red"); + return await loadingMsg.edit({ embeds: [ping_error] }); + } + } + + let info_embed = []; + if (isContextMenuCommand) { + const footerText = `Response to message by ${message.author.tag}\n\n${message.content}`; + const truncatedFooterText = + footerText.length > 2030 ? `${footerText.slice(0, 2027)}...` : footerText; + + const info = new EmbedBuilder() + .setFooter({ text: truncatedFooterText }) + .setColor("Blue"); + + info_embed.push(info); + } + + // responseText = responseText.replace(/(https?:\/\/(?!media\.discordapp\.net\/attachments\/)[^\s\)]+)/g, "<$1>"); + return await loadingMsg.edit({ content: responseText, embeds: info_embed }); } async function checkGeminiApiKey(Gemini_API_KEY, interaction, message) { - if (!Gemini_API_KEY || Gemini_API_KEY.length < 4) { - const invalid_api = new EmbedBuilder() - .setTitle("⚠️ Invalid API Key") - .setDescription("> **The API Key for Gemini is invalid or not provided.**") - .setColor("Red"); - - return (interaction ? interaction.reply({ embeds: [invalid_api] }) : message.reply({ embeds: [invalid_api] })); - } + if (!Gemini_API_KEY || Gemini_API_KEY.length < 4) { + const invalid_api = new EmbedBuilder() + .setTitle("⚠️ Invalid API Key") + .setDescription( + "> **The API Key for Gemini is invalid or not provided.**", + ) + .setColor("Red"); + + return interaction + ? interaction.reply({ embeds: [invalid_api] }) + : message.reply({ embeds: [invalid_api] }); + } } async function fetchThreadMessages(Gemini_API_KEY, message) { - let userQuestion; - let threadMessages = []; - - if (await checkGeminiApiKey(Gemini_API_KEY, false, message)) return; - const originalMessage = await message.channel.messages.fetch(message.reference.messageId); - - if (originalMessage.author.id !== message.client.user.id) return { userQuestion: null, threadMessages: null }; - - if (originalMessage.author.id === message.client.user.id) { - let currentMessage = message; - - while (currentMessage.reference) { - currentMessage = await message.channel.messages.fetch(currentMessage.reference.messageId); - const sender = currentMessage.author.id === message.client.user.id ? 'model' : 'user'; - let content = currentMessage.content; - if (sender === 'user') { - content = content.replace(/<@\d+>\s*/, ''); - } - threadMessages.unshift({ role: sender, parts: [{text: content}] }); - } - } - userQuestion = message.content - - return { userQuestion, threadMessages }; + let userQuestion; + let threadMessages = []; + + if (await checkGeminiApiKey(Gemini_API_KEY, false, message)) return; + const originalMessage = await message.channel.messages.fetch( + message.reference.messageId, + ); + + if (originalMessage.author.id !== message.client.user.id) + return { userQuestion: null, threadMessages: null }; + + if (originalMessage.author.id === message.client.user.id) { + let currentMessage = message; + + while (currentMessage.reference) { + currentMessage = await message.channel.messages.fetch( + currentMessage.reference.messageId, + ); + const sender = + currentMessage.author.id === message.client.user.id ? "model" : "user"; + let content = currentMessage.content; + if (sender === "user") { + content = content.replace(/<@\d+>\s*/, ""); + } + threadMessages.unshift({ role: sender, parts: [{ text: content }] }); + } + } + userQuestion = message.content; + + return { userQuestion, threadMessages }; } -module.exports = { botInGuild, safetySettings, handleGeminiError, handleResponse, checkGeminiApiKey, fetchThreadMessages }; \ No newline at end of file +module.exports = { + botInGuild, + safetySettings, + handleGeminiError, + handleResponse, + checkGeminiApiKey, + fetchThreadMessages, +};