From 0ec7419cb714577918292c6ffa21fafceb0c471d Mon Sep 17 00:00:00 2001 From: Raven Scott Date: Sat, 25 Mar 2023 19:09:44 +0200 Subject: [PATCH] First Commit --- .gitignore | 4 ++ README.md | 38 +++++++++++ commands/Info/about.js | 21 ++++++ commands/Info/chat.js | 58 +++++++++++++++++ commands/Info/new-chat.js | 38 +++++++++++ commands/Info/session-id.js | 27 ++++++++ commands/Info/view-session-history.js | 56 ++++++++++++++++ events/interactionCreate.js | 94 +++++++++++++++++++++++++++ events/ready.js | 5 ++ handler/index.js | 37 +++++++++++ package.json | 24 +++++++ rai.js | 14 ++++ 12 files changed, 416 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 commands/Info/about.js create mode 100644 commands/Info/chat.js create mode 100644 commands/Info/new-chat.js create mode 100644 commands/Info/session-id.js create mode 100644 commands/Info/view-session-history.js create mode 100644 events/interactionCreate.js create mode 100644 events/ready.js create mode 100644 handler/index.js create mode 100644 package.json create mode 100644 rai.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2bf877b --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules +.env +package-lock.json +cache diff --git a/README.md b/README.md new file mode 100644 index 0000000..88fb99b --- /dev/null +++ b/README.md @@ -0,0 +1,38 @@ +# rAI - Serge Discord Bot + +This readme is incomplete. + + +Message intents are NOT supported in this bot, this is due to the verification that Discord is enabling. + +Structure: + +**commands** - This folder contains commands + +**event** - This folder contains files related to discord.js events. (Like "ready", "interactionCreate") + +**handler** - This folder contains files that read the commands folders contents. + +**index.js** - This is the main file to run the bot. + + + +1) Use ```npm i ``` + +2) Create a .env file ``` touch .env``` + +3) Edit .env +``` +TOKEN = token +INTERNAL_IP = +PUBLIC_URL = +``` + +4) Go to Handler -- > index.js and change "GUIDIDHERE" to your Discord Server's Guild ID + +5) Go into https://discord.com/developers/applications and enable Privileged Intents. + +6) Run the bot ```node index.js``` + + +Want to make this better? Issue a pull request! diff --git a/commands/Info/about.js b/commands/Info/about.js new file mode 100644 index 0000000..7de69ef --- /dev/null +++ b/commands/Info/about.js @@ -0,0 +1,21 @@ +const { EmbedBuilder } = require('discord.js'); +let cpu = require('cpu-load') + +module.exports = { + name: "about", + description: "Info about the bot", + + run: async (client, interaction) => { + cpu(1000, function (load) { + + const embed = new EmbedBuilder() + .setColor("#FF0000") + .setTitle("About rAI") + .setDescription(`Latency : ${client.ws.ping}ms\n\nrAI is a bot managed by \`snxraven#8205\` running an LLM called Alpaca.\n\nSpecs: Intel i7-1065G7 (8) @ 3.900GHz with 11 GB of RAM\nChat is set to 7 Threads\nConfigured Memory Speed: 3733 MT/s\nUSB Liveboot\n\nSingle Task Only - 256 Max Token Output`) + .setTimestamp() + .setFooter({ text: `Requested by ${interaction.user.tag}`, iconURL: `${interaction.user.displayAvatarURL()}` }); + interaction.followUp({ embeds: [embed] }); + }) + + }, +}; \ No newline at end of file diff --git a/commands/Info/chat.js b/commands/Info/chat.js new file mode 100644 index 0000000..0a21905 --- /dev/null +++ b/commands/Info/chat.js @@ -0,0 +1,58 @@ +const { MessageEmbed } = require('discord.js'); +const axios = require('axios'); +const jsonfile = require('jsonfile'); +let session2; +let isProcessing = false; // Semaphore variable + +module.exports = { + name: 'chat', + description: 'Chat with Alpaca using rAI', + options: [{ + "name": "query", + "description": "The thing you want to ask", + "required": true, + "type": 3 // 6 is type USER + }], + run: async (client, interaction) => { + const file = './cache/' + interaction.user.id; + let chatToSend = interaction.options._hoistedOptions[0].value; + + // Check if another request is already being processed + if (isProcessing) { + interaction.editReply('Sorry, another request is already being processed. Please try again in a few minutes.'); + return; + } + + // Set the semaphore to true + isProcessing = true; + + await jsonfile.readFile(file, async function (err, session) { + if (err) return interaction.editReply('Please create a session using /create-session.'); + + session2 = session.id; + + if (!session2) { + console.log("No Session!"); + isProcessing = false; + return; + } + + let prompt = interaction.options._hoistedOptions[0].value.replace(" ", "+"); + const headers = { + 'authority': 'rai.snxraven.me', + 'accept': 'text/event-stream', + }; + console.log(session); + const response = await axios.post(`http://${process.env.INTERNAL_IP}:8008/api/chat/` + session2 + '/question?prompt=' + prompt, { + headers + }); + + console.log(response.data.answer); + + interaction.editReply(response.data.answer); + + // Set the semaphore to false + isProcessing = false; + }); + }, +}; diff --git a/commands/Info/new-chat.js b/commands/Info/new-chat.js new file mode 100644 index 0000000..70996b4 --- /dev/null +++ b/commands/Info/new-chat.js @@ -0,0 +1,38 @@ +const { EmbedBuilder } = require('discord.js'); +var unirest = require('unirest'); +const jsonfile = require('jsonfile') + +module.exports = { + name: "create-session", + description: "create a new session chat", + private: true, + run: async (client, interaction) => { + const file = './cache/' + interaction.user.id + + + unirest + .post(`https://${process.env.PUBLIC_URL}/api/chat`) + .headers({ 'Accept': 'application/json', 'Content-Type': 'application/json' }) + .auth({ + user: 'rai', + pass: 'ilikeai', + sendImmediately: true + }) + .then((response) => { + console.log(response) + const obj = { id: response.body } + + jsonfile.writeFile(file, obj, function (err) { + if (err) console.error(err) + }) + + const embed = new EmbedBuilder() + .setColor("#FF0000") + .setTitle("New Chat Session Started!") + .setDescription(`New Chat Session ID: \n` + response.body) + .setTimestamp() + .setFooter({ text: `Requested by ${interaction.user.tag}`, iconURL: `${interaction.user.displayAvatarURL()}` }); + interaction.followUp({ embeds: [embed] }); + }) + }, +}; \ No newline at end of file diff --git a/commands/Info/session-id.js b/commands/Info/session-id.js new file mode 100644 index 0000000..24f7e4d --- /dev/null +++ b/commands/Info/session-id.js @@ -0,0 +1,27 @@ +const { EmbedBuilder } = require('discord.js'); +var unirest = require('unirest'); +const jsonfile = require('jsonfile') + +module.exports = { + name: "view-session-id", + description: "View your currently assigned session ID", + private: true, + run: async (client, interaction) => { + const file = './cache/' + interaction.user.id + + jsonfile.readFile(file, function (err, obj) { + if (err) return interaction.editReply('Please create a session using /create-session.'); + console.dir(obj) + + + const embed = new EmbedBuilder() + .setColor("#FF0000") + .setTitle("New Chat Session Started!") + .setDescription(`Current Session ID: \n` + obj.id) + .setTimestamp() + .setFooter({ text: `Requested by ${interaction.user.tag}`, iconURL: `${interaction.user.displayAvatarURL()}` }); + interaction.followUp({ embeds: [embed] }); + }) + + }, +}; \ No newline at end of file diff --git a/commands/Info/view-session-history.js b/commands/Info/view-session-history.js new file mode 100644 index 0000000..bb39da1 --- /dev/null +++ b/commands/Info/view-session-history.js @@ -0,0 +1,56 @@ +const axios = require('axios'); +const jsonfile = require('jsonfile'); +let session2; + + +function parseQuestionsAndAnswers(jsonStr) { + const obj = JSON.parse(jsonStr); + const questions = obj.questions; + const answers = questions.map(q => q.answer); + return { questions, answers }; +} + +module.exports = { + name: "view-session-history", + description: "View your current conversation.", + run: async (client, interaction) => { + const file = './cache/' + interaction.user.id; + + + await jsonfile.readFile(file, async function (err, session) { + if (err) return interaction.editReply('Please create a session using /create-session.'); + + session2 = session.id; + + if (!session2) { + console.log("No Session!"); + isProcessing = false; + return; + } + const headers = { + 'authority': 'rai.snxraven.me', + 'accept': 'text/event-stream', + }; + const response = await axios.get(`http://${process.env.INTERNAL_IP}:8008/api/chat/` + session2, { + headers, + auth: { + username: "rai", + password: "ilikeai" + } + }); + + console.log(response.data) + + if (!response.data.questions) return interaction.editReply("You have no history in this session yet :) "); + + const result = parseQuestionsAndAnswers(JSON.stringify(response.data)); + let pairsString = ""; + for (const question of result.questions) { + pairsString += `***Question:*** ${question.question}\n***Answer:*** ${question.answer}\n`; + } + interaction.editReply(pairsString); + }) + if (err) console.error("woo" + err) + + }, +}; \ No newline at end of file diff --git a/events/interactionCreate.js b/events/interactionCreate.js new file mode 100644 index 0000000..85b0c04 --- /dev/null +++ b/events/interactionCreate.js @@ -0,0 +1,94 @@ +const client = require("../rai"); +require("dotenv").config(); +const { glob } = require("glob"); +const { promisify } = require("util"); +const globPromise = promisify(glob); + +client.on("interactionCreate", async (interaction) => { + + // Slash Commands + const slashCommands = await globPromise(`${process.cwd()}/commands/*/*.js`); + const arrayOfSlashCommands = []; + + // Map the slash commands into data to be processed + slashCommands.map((value) => { + const file = require(value); + const splitted = value.split("/"); + const directory = splitted[splitted.length - 2]; + + if (!file?.name) return; + + const properties = { + directory, + ...file + }; + client.slashCommands.set(file.name, properties); + + if (["MESSAGE", "USER"].includes(file.type)) delete file.description; + + // Push the data + arrayOfSlashCommands.push(file); + }); + + + // Slash Command Handling + if (interaction.isChatInputCommand()) { + + // Grabbing Command Data for this interaction + let commandData = [] + + // We use ForEach here to filter our array into the single commands info. + await arrayOfSlashCommands.forEach(command => { + if (command.name == interaction.commandName) { + commandData.push(command) + } + }); + + // Process and Parse Data + let dataToProcess = JSON.stringify(commandData[0]) + let parsedData = JSON.parse(dataToProcess) + + // If the command is private, set ephemeral true else, set false + console.log(parsedData) + if (parsedData.private == true) { + await interaction.deferReply({ + ephemeral: true + }).catch(() => {}); + + } else { + await interaction.deferReply({ + ephemeral: false + }).catch(() => {}); + } + + + const cmd = client.slashCommands.get(interaction.commandName); + if (!cmd) + return interaction.followUp({ + content: "An error has occurred " + }); + + const args = []; + + for (let option of interaction.options.data) { + if (option.type === "SUB_COMMAND") { + if (option.name) args.push(option.name); + option.options?.forEach((x) => { + if (x.value) args.push(x.value); + }); + } else if (option.value) args.push(option.value); + } + interaction.member = interaction.guild.members.cache.get(interaction.user.id); + + cmd.run(client, interaction, args); + } + + // Context Menu Handling + if (interaction.isContextMenuCommand()) { + await interaction.deferReply({ + ephemeral: false + }); + const command = client.slashCommands.get(interaction.commandName); + if (command) command.run(client, interaction); + } +}); diff --git a/events/ready.js b/events/ready.js new file mode 100644 index 0000000..1193e55 --- /dev/null +++ b/events/ready.js @@ -0,0 +1,5 @@ +const client = require("../rai"); + +client.on("ready", () => { + console.log(`${client.user.tag} is up and ready to go!`); +}); diff --git a/handler/index.js b/handler/index.js new file mode 100644 index 0000000..3cff50e --- /dev/null +++ b/handler/index.js @@ -0,0 +1,37 @@ +require("dotenv").config(); +const { glob } = require("glob"); +const { promisify } = require("util"); +const globPromise = promisify(glob); + +module.exports = async (client) => { + // Slash Commands + const slashCommands = await globPromise(`${process.cwd()}/commands/*/*.js`); + const arrayOfSlashCommands = []; + slashCommands.map((value) => { + const file = require(value); + const splitted = value.split("/"); + const directory = splitted[splitted.length - 2]; + + if (!file?.name) return; + + const properties = { directory, ...file }; + client.slashCommands.set(file.name, properties); + + if (["MESSAGE", "USER"].includes(file.type)) delete file.description; + arrayOfSlashCommands.push(file); + }); + + // Events + const eventFiles = await globPromise(`${process.cwd()}/events/*.js`); + eventFiles.map((value) => require(value)); + + // Slash Commands Register + client.on("ready", async () => { + // // Register for a single guild + // await client.guilds.cache.get("GUIDIDHERE").commands.set(arrayOfSlashCommands); + + // Register for all the guilds the bot is in + await client.application.commands.set(arrayOfSlashCommands); + }); + +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..cdab2d8 --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "dlinuxtemplatev14", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "axios": "^1.3.4", + "cpu-load": "^1.0.0", + "discord.js": "^14.0.3", + "dotenv": "^16.0.3", + "glob": "^7.2.0", + "jsonfile": "^6.1.0", + "node-fetch": "^2.6.9", + "node-free": "^1.0.0", + "sysstats": "^1.0.1", + "unirest": "^0.6.0" + } +} diff --git a/rai.js b/rai.js new file mode 100644 index 0000000..9af5771 --- /dev/null +++ b/rai.js @@ -0,0 +1,14 @@ +require("dotenv").config(); +const { Client, Collection } = require("discord.js"); + +const client = new Client({ intents: 4097 }); +module.exports = client; + +// Global Variables +client.commands = new Collection(); +client.slashCommands = new Collection(); + +// Initializing the project +require("./handler")(client); + +client.login(process.env.TOKEN);