├── src ├── db │ ├── user_chats.db │ └── queries │ │ ├── get_user_chat.sql │ │ ├── init_user_chat.sql │ │ ├── insert_new_user.sql │ │ ├── update_user_chat.sql │ │ ├── get_user_chat_status.sql │ │ ├── set_user_chat_status.sql │ │ ├── is_user_inserted.sql │ │ └── schema.sql ├── assets │ ├── openai-logo.png │ ├── openai-logo-green.png │ └── openai-logo-small.png ├── commands │ ├── ask.js │ ├── image.js │ └── chat.js ├── config │ └── ai-models.js ├── utils │ ├── commands-manager.js │ ├── database-manager.js │ └── models-managers.js └── index.js ├── .gitignore ├── .env ├── package.json ├── LICENSE └── README.md /src/db/user_chats.db: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/db/queries/get_user_chat.sql: -------------------------------------------------------------------------------- 1 | SELECT chat 2 | FROM chats 3 | WHERE user_id = ? -------------------------------------------------------------------------------- /src/db/queries/init_user_chat.sql: -------------------------------------------------------------------------------- 1 | UPDATE chats 2 | SET chat = ? 3 | WHERE user_id = ? -------------------------------------------------------------------------------- /src/db/queries/insert_new_user.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO chats (user_id, chat) 2 | VALUES (?, ?) -------------------------------------------------------------------------------- /src/db/queries/update_user_chat.sql: -------------------------------------------------------------------------------- 1 | UPDATE chats 2 | SET chat = ? 3 | WHERE user_id = ? -------------------------------------------------------------------------------- /src/db/queries/get_user_chat_status.sql: -------------------------------------------------------------------------------- 1 | SELECT chat_status 2 | FROM chats 3 | WHERE user_id = ? -------------------------------------------------------------------------------- /src/db/queries/set_user_chat_status.sql: -------------------------------------------------------------------------------- 1 | UPDATE chats 2 | SET chat_status = ? 3 | WHERE user_id = ? -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependency directories 2 | node_modules/ 3 | 4 | # dotenv environment variables 5 | .env 6 | .env-test -------------------------------------------------------------------------------- /src/assets/openai-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InDiagonal/OpenAI-Discord-Bot/HEAD/src/assets/openai-logo.png -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # Discord variables 2 | DISCORD_TOKEN= 3 | CLIENT_ID= 4 | GUILD_ID= 5 | 6 | # OpenAI variables 7 | OPENAI_API_KEY= 8 | -------------------------------------------------------------------------------- /src/db/queries/is_user_inserted.sql: -------------------------------------------------------------------------------- 1 | SELECT EXISTS( 2 | SELECT 1 3 | FROM chats 4 | WHERE user_id = ? 5 | ) AS foo -------------------------------------------------------------------------------- /src/assets/openai-logo-green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InDiagonal/OpenAI-Discord-Bot/HEAD/src/assets/openai-logo-green.png -------------------------------------------------------------------------------- /src/assets/openai-logo-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InDiagonal/OpenAI-Discord-Bot/HEAD/src/assets/openai-logo-small.png -------------------------------------------------------------------------------- /src/db/queries/schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS chats ( 2 | id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, 3 | user_id TEXT UNIQUE NOT NULL, 4 | chat TEXT NOT NULL, 5 | chat_status INTEGER NOT NULL DEFAULT 0 6 | ) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "openai-bot", 3 | "version": "0.0.3", 4 | "description": "Interact with OpenAI Models using OpenAI API", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "start": "node src/index.js" 8 | }, 9 | "author": "InDiagonal", 10 | "license": "MIT", 11 | "dependencies": { 12 | "discord.js": "^14.7.1", 13 | "dotenv": "^16.0.3", 14 | "openai": "^4.19.0", 15 | "sqlite3": "^5.1.4" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/InDiagonal/OpenAI-Discord-Bot.git" 20 | }, 21 | "bugs": { 22 | "url": "https://github.com/InDiagonal/OpenAI-Discord-Bot/issues" 23 | }, 24 | "homepage": "https://github.com/InDiagonal/OpenAI-Discord-Bot#readme" 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Lorenzo Indiano Confortin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/commands/ask.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder } = require('discord.js'); 2 | const { AskManager } = require('../utils/models-managers.js'); 3 | 4 | 5 | const ask = new AskManager(); 6 | 7 | // export an object with the data and execute properties 8 | module.exports = { 9 | data: new SlashCommandBuilder() 10 | .setName('ask') 11 | .setDescription('Send a specific/long answer generated by GPT-3!') 12 | .addStringOption(option => 13 | option.setName('prompt') 14 | .setDescription('Type your question') 15 | .setRequired(true)), 16 | 17 | async execute(interaction) { 18 | // defer reply until the command finishes executing 19 | await interaction.deferReply(); 20 | // get input option from the interaction object 21 | const input = interaction.options.getString('prompt'); 22 | // generate answer from prompt 23 | const output = await ask.generate_answer(input); 24 | if (!output) { 25 | await interaction.editReply("Error generating answer"); 26 | return; 27 | } 28 | // send the reply with the generated output 29 | await interaction.editReply(output); 30 | console.log('Answer sent!'); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /src/config/ai-models.js: -------------------------------------------------------------------------------- 1 | // AI custom models object and their respective configurations 2 | // Check OpenAI models here: https://platform.openai.com/docs/api-reference/chat/create 3 | 4 | const aiModels = { 5 | ask: { 6 | body: { 7 | model: 'text-davinci-003', 8 | prompt: 'The following is a question from a USER. As GPT, your goal is to provide a clever answer.\n\n', 9 | max_tokens: 2000, 10 | temperature: 0.5, 11 | n: 1, 12 | stream: false, 13 | stop: ['USER:', 'GPT:'] 14 | } 15 | }, 16 | chat: { 17 | body: { 18 | model: "gpt-4", 19 | messages: [{ 20 | role: "system", 21 | content: "The following is a friendly conversation. You are creative, clever, smart, funny and sometimes sarcastic. You talk in American slang and use emojis occasionally. You should introduce to the user. Do not be afraid to initiate a discussion with a topic of your choice.", 22 | }], 23 | max_tokens: 150, 24 | temperature: 0.9, 25 | n: 1, 26 | stream: false, 27 | presence_penalty: 0.6 28 | } 29 | }, 30 | image: { 31 | body: { 32 | prompt: ', high quality, digital art, photorealistic style, very detailed', 33 | n: 1, 34 | size: '1024x1024' 35 | } 36 | } 37 | } 38 | 39 | 40 | module.exports = { aiModels }; 41 | -------------------------------------------------------------------------------- /src/commands/image.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder, EmbedBuilder, AttachmentBuilder } = require('discord.js'); 2 | const { ImageManager } = require('../utils/models-managers.js'); 3 | 4 | 5 | const image = new ImageManager(); 6 | 7 | // export an object with the data and execute properties 8 | module.exports = { 9 | data: new SlashCommandBuilder() 10 | .setName('image') 11 | .setDescription('Send image generated by DALL-E Artificial Intelligence!') 12 | .addStringOption(option => 13 | option.setName('prompt') 14 | .setDescription('The prompt to generate the image from') 15 | .setRequired(true)), 16 | 17 | async execute(interaction) { 18 | // defer reply until the command finishes executing 19 | await interaction.deferReply(); 20 | // create logo icon attachments 21 | const icon = new AttachmentBuilder('./src/assets/openai-logo-small.png'); 22 | // get the prompt option from the interaction object 23 | const input = interaction.options.getString('prompt'); 24 | // generate an image from the prompt 25 | const output = await image.generate_image(input); 26 | // send reply with the embed of the generated image 27 | await interaction.editReply({ embeds: [this.embedImage(input, output)], files: [icon] }); 28 | console.log('Image sent!'); 29 | }, 30 | 31 | embedImage(title, url) { 32 | return new EmbedBuilder() 33 | .setColor(0x19C37D) 34 | .setTitle(title.toUpperCase()) 35 | .setAuthor({ 36 | name: 'OpenAI', 37 | iconURL: 'attachment://openai-logo-small.png', 38 | url: 'https://openai.com' 39 | }) 40 | .setImage(url) 41 | .setFooter({ text: 'Image generated by DALL·E Artificial Intelligence'}); 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /src/utils/commands-manager.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const { Collection, REST, Routes } = require('discord.js'); 3 | const fs = require('node:fs'); 4 | const path = require('node:path'); 5 | 6 | 7 | const discordToken = process.env.DISCORD_TOKEN; 8 | const rest = new REST().setToken(discordToken); 9 | 10 | // Instructions here: https://discordjs.guide/creating-your-bot/command-deployment.html#guild-commands 11 | 12 | // Custom subclass for storing and managing commands 13 | class CommandsManager extends Collection { 14 | constructor() { 15 | super(); 16 | const commandsPath = path.join(process.cwd(), 'src/commands'); 17 | const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js')); 18 | 19 | for (const file of commandFiles) { 20 | const filePath = path.join(commandsPath, file); 21 | const command = require(filePath); 22 | 23 | if ('data' in command && 'execute' in command) { 24 | super.set(command.data.name, command); 25 | } else { 26 | console.log(`The command at ${filePath} is missing 'data' or 'execute' property.`); 27 | } 28 | } 29 | } 30 | 31 | // Deploy bot commands (clientId) to a Discord guild (guildId) 32 | async deploy(clientId, guildId) { 33 | const commands_data = []; 34 | 35 | for (const command of super.values()) { 36 | commands_data.push(command.data.toJSON()); 37 | } 38 | try { 39 | console.log(`Started refreshing ${commands_data.length} application (/) commands.`); 40 | const data = await rest.put( 41 | guildId !== undefined 42 | ? Routes.applicationGuildCommands(clientId, guildId) 43 | : Routes.applicationCommands(clientId), 44 | { body: commands_data }, 45 | ); 46 | console.log(`Successfully reloaded ${data.length} application (/) commands.`); 47 | } catch (error) { 48 | console.error(error); 49 | } 50 | } 51 | 52 | // Deploy bot commands (clientId) to a Discord guild (guildId) 53 | async deleteAll(clientId, guildId) { 54 | try { 55 | console.log(`Started deleting application (/) commands.`); 56 | await rest.put( 57 | guildId !== undefined 58 | ? Routes.applicationGuildCommands(clientId, guildId) 59 | : Routes.applicationCommands(clientId), 60 | { body: [] }, 61 | ); 62 | console.log(`Successfully deleted all application (/) commands.`); 63 | } catch (error) { 64 | console.error(error); 65 | } 66 | } 67 | } 68 | 69 | 70 | module.exports = { CommandsManager }; 71 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const { Client, Events, GatewayIntentBits } = require('discord.js'); 3 | const { CommandsManager } = require('./utils/commands-manager.js'); 4 | 5 | // Intructions here: https://discordjs.guide/creating-your-bot/ 6 | // Env variables 7 | const clientId = process.env.CLIENT_ID; 8 | const guildId = process.env.GUILD_ID; 9 | const discordToken = process.env.DISCORD_TOKEN; 10 | 11 | // Create a new Discord client with specified intents 12 | const client = new Client({ 13 | intents: [ 14 | GatewayIntentBits.Guilds, 15 | GatewayIntentBits.GuildMessages, 16 | GatewayIntentBits.GuildMembers, 17 | GatewayIntentBits.MessageContent 18 | ], 19 | }); 20 | 21 | // Client ready event 22 | client.once(Events.ClientReady, client => { 23 | console.log('CLIENT: Logged in as ->', client.user.tag ); 24 | }); 25 | 26 | // Loads command files 27 | client.commands = new CommandsManager(); 28 | 29 | // Deploys commands globally 30 | client.commands.deploy(clientId); 31 | 32 | // Deploys commands on a specific guild 33 | // client.commands.deploy(clientId, guildId); 34 | 35 | // Delete all guild commands 36 | // client.commands.deleteAll(clientId, guildId); 37 | 38 | // Delete all global commands 39 | // client.commands.deleteAll(clientId); 40 | 41 | // User messages event 42 | client.on(Events.MessageCreate, async interaction => { 43 | if (interaction.author.bot) return; 44 | console.log('CLIENT: MessageCreate'); 45 | // Process the message 46 | messageResponse(interaction); 47 | }); 48 | 49 | // Slash command event 50 | client.on(Events.InteractionCreate, async interaction => { 51 | if (!interaction.isChatInputCommand()) return; 52 | console.log('CLIENT: InteractionCreate'); 53 | // Process the chat command 54 | commandResponse(interaction); 55 | }); 56 | 57 | // Error events 58 | client.on(Events.Error, err => { 59 | console.error('CLIENT: Error ->', err); 60 | }) 61 | 62 | // Function to handle channel messages 63 | async function messageResponse(interaction) { 64 | const commandName = 'chat'; 65 | const command = client.commands.get(commandName); 66 | 67 | if (!command) { 68 | console.error(`No command matching ${commandName} was found.`); 69 | return; 70 | } 71 | 72 | try { 73 | await command.reply(interaction); 74 | } catch (err) { 75 | console.error(err); 76 | } 77 | } 78 | 79 | // Function to handle slash commands 80 | async function commandResponse(interaction) { 81 | const commandName = interaction.commandName; 82 | const command = client.commands.get(commandName); 83 | 84 | if (!command) { 85 | console.error(`No command matching ${commandName} was found.`); 86 | return; 87 | } 88 | 89 | try { 90 | await command.execute(interaction); 91 | } catch (err) { 92 | console.error(err); 93 | } 94 | } 95 | 96 | // Log the client in using the DISCORD_TOKEN environment variable 97 | client.login(discordToken); -------------------------------------------------------------------------------- /src/utils/database-manager.js: -------------------------------------------------------------------------------- 1 | const fs = require('node:fs'); 2 | const path = require('node:path'); 3 | const sqlite3 = require('sqlite3').verbose(); 4 | 5 | // Class for interacting with a SQLite database to store chats (NOT ENCRYPTED!) 6 | class DatabaseManager { 7 | constructor() { 8 | const queriesPath = path.join(process.cwd(), 'src/db/queries'); 9 | const databasePath = path.join(process.cwd(), 'src/db/user_chats.db'); 10 | // Queries object 11 | this.queries = {}; 12 | // Store SQLite queries in queries dict, file name as key and file content as value 13 | fs.readdirSync(queriesPath) 14 | .filter(fileName => fileName.endsWith('.sql')) 15 | .map(fileName => { 16 | // remove file extension 17 | this.queries[fileName.split('.')[0]] = fs.readFileSync(path.join(queriesPath, fileName), 'utf8') 18 | // replace carriage return characters 19 | .replace(/\r/g, ' '); 20 | }); 21 | // Database connection 22 | this.db = new sqlite3.Database(databasePath); 23 | } 24 | 25 | // Database schema creation 26 | async init() { 27 | return new Promise((resolve, reject) => { 28 | this.db.run(this.queries.schema, (err) => { 29 | if (err) { 30 | reject(err); 31 | } 32 | resolve(this); 33 | }); 34 | }); 35 | } 36 | 37 | // Insert user in database 38 | async insert_new_user(user_id) { 39 | return new Promise((resolve, reject) => { 40 | this.db.run(this.queries.insert_new_user, user_id, '', (err) => { 41 | if (err) { 42 | reject(err); 43 | } 44 | resolve(/*`Insertion of user: ${user_id}`*/); 45 | }); 46 | }); 47 | } 48 | 49 | // Return true if user already in database and false if not 50 | async is_user_inserted(user_id) { 51 | return new Promise((resolve, reject) => { 52 | this.db.get(this.queries.is_user_inserted, user_id, (err, row) => { 53 | if (err) { 54 | reject(err); 55 | } 56 | resolve(Boolean(row['foo'])); 57 | }); 58 | }); 59 | } 60 | 61 | // Init user chat in database 62 | async init_user_chat(user_id, text) { 63 | return new Promise((resolve, reject) => { 64 | this.db.run(this.queries.init_user_chat, text, user_id, (err) => { 65 | if (err) { 66 | reject(err); 67 | } 68 | resolve(/*`Chat reset for user: ${user_id}`*/); 69 | }); 70 | }); 71 | } 72 | 73 | // Update user chat in database 74 | async update_user_chat(user_id, text) { 75 | return new Promise((resolve, reject) => { 76 | this.db.run(this.queries.update_user_chat, text, user_id, (err) => { 77 | if (err) { 78 | reject(err); 79 | } 80 | resolve(/*`Chat updated for user: ${user_id}`*/); 81 | }); 82 | }); 83 | } 84 | 85 | // Get user chat from database 86 | async get_user_chat(user_id) { 87 | return new Promise((resolve, reject) => { 88 | this.db.get(this.queries.get_user_chat, user_id, (err, row) => { 89 | if (err || !row) { 90 | reject(err || 'row undefined'); 91 | } 92 | resolve(row['chat']); 93 | }); 94 | }); 95 | } 96 | 97 | // Set user chat status (true/false) in database 98 | async set_user_chat_status(user_id, foo) { 99 | return new Promise((resolve, reject) => { 100 | this.db.run(this.queries.set_user_chat_status, foo, user_id, (err) => { 101 | if (err) { 102 | reject(err); 103 | } 104 | resolve(/*`Chat open/closed for user: ${user_id}`*/); 105 | }); 106 | }); 107 | } 108 | 109 | // Get user chat status (true/false) from database 110 | async get_user_chat_status(user_id) { 111 | return new Promise((resolve, reject) => { 112 | this.db.get(this.queries.get_user_chat_status, user_id, (err, row) => { 113 | if (err || !row) { 114 | reject(err || 'row undefined'); 115 | } 116 | resolve(Boolean(row['chat_status'])); 117 | }); 118 | }); 119 | } 120 | } 121 | 122 | 123 | module.exports = { DatabaseManager }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenAI Bot (Beta v0.0.3) 2 | 3 |       4 | 5 | Features 6 | • 7 | Installation 8 | • 9 | Support 10 | • 11 | Dependencies 12 | • 13 | License 14 | 15 | #### Video Demo: 16 | 17 | > [https://youtu.be/jCkin9aoEvw](https://youtu.be/jCkin9aoEvw) 18 | 19 | #### Description: 20 | 21 | OpenAI Bot allows users to access the capabilities of advanced artificial intelligence technologies right from a Discord server. With this bot, you can use the GPT-4 language model to generate human-like text. Whether you're looking to retrieve creative ideas, write compelling content, or simply have some fun, the GPT-4 model is up to the task. 22 | 23 | In addition to text generation, OpenAI Bot also allows users to access the DALL-E image generation model. This model can create unique and original images based on text input, allowing you to create visually striking graphics and artwork with ease. Whether you're an artist looking to generate new ideas, a designer looking to create eye-catching graphics, or just someone who enjoys creating and experimenting, the DALL-E model is a great resource. 24 | 25 | ## ⚡ Features 26 | 27 | ### `/ask` command 28 | 29 | The `ask` slash command allows users to ask any question and receive an exhaustive answer. Simply type `/ask` followed by your question in a Discord channel where the bot is present, and the bot will use OpenAI's AI models to provide an answer. 30 | 31 | ### `/image` command 32 | 33 | The `image` slash command allows users to generate an image using OpenAI's DALL·E technology. Simply type `/image` followed by a description of the image you want to generate in a Discord channel where the bot is present, and the bot will send you an embedded message with the generated image. 34 | 35 | ### `/chat` command 36 | 37 | The `chat` slash command allows users to chat with OpenAI's GPT chatbot. Type `/chat` followed by `open` in a Discord channel where the bot is present, and the bot will begin a chat session with you. All chat sessions are saved in a local SQL database. To temporarily close a chat session type `/chat` followed by `close`. If you want to permanently delete your chat history from the database, type `/chat` followed by `delete`. _[ NOTE: You will be asked to confirm your choice since deletion is IRREVERSIBLE ]_ 38 | 39 | ## 🛠 Installation 40 | 41 | To install and run OpenAI Bot on your own Discord server, follow these steps: 42 | 43 | 1. Download the source code by cloning this repository or downloading a zip file. 44 | 2. Install the dependencies by running `npm install` in the root directory of the project. 45 | 3. In the `.env` file located in the root directory of the project insert the following values: 46 | - **Discord API token** as the value for the `DISCORD_TOKEN` variable. (You can sign up for a Discord API Token at [https://discord.com/developers/applications/](https://discord.com/developers/applications/)) 47 | - **Application ID** as the value for the `CLIENT_ID` variable. 48 | - **Your Development Server ID** as the value for the `GUILD_ID` variable. 49 | - **OpenAI API key** as the value for the `OPENAI_API_KEY` variable. (You can sign up for an OpenAI API key at [https://beta.openai.com/signup/](https://beta.openai.com/signup/)) 50 | 4. Start the bot by running `npm start` or `node .` in the root directory of the project. 51 | 52 | ## 🆘 Support 53 | 54 | If you need any support while setting up your own bot, feel free to join the __[Development Server](https://discord.gg/ndCY5x8Dpq)__. 55 | 56 | ## ⛓️ Dependencies 57 | 58 | OpenAI Bot has the following dependencies: 59 | 60 | - **[discord.js](https://www.npmjs.com/package/discord.js)**: A JavaScript library for interacting with the Discord API. 61 | - **[openai](https://www.npmjs.com/package/openai)**: A JavaScript library for interacting with the OpenAI API. 62 | - **[sqlite3](https://www.npmjs.com/package/sqlite3)**: A JavaScript library that provides a self-contained, serverless, zero-configuration, transactional SQL database engine. 63 | - **[dotenv](https://www.npmjs.com/package/dotenv)**: A JavaScript library that loads environment variables from a .env file and makes them accessible via process.env object in Node.js applications 64 | 65 | ## 📖 License 66 | 67 | OpenAI Bot is open source and released under the [MIT License](https://opensource.org/licenses/MIT). 68 | -------------------------------------------------------------------------------- /src/commands/chat.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder } = require('discord.js'); 2 | const { ChatManager } = require('../utils/models-managers.js'); 3 | const { DatabaseManager } = require('../utils/database-manager.js'); 4 | 5 | 6 | const chat = new ChatManager(); 7 | let db; 8 | (async () => { 9 | // Initialize database 10 | db = await new DatabaseManager().init(); 11 | })(); 12 | 13 | // export an object with the data and execute properties 14 | module.exports = { 15 | data: new SlashCommandBuilder() 16 | .setName('chat') 17 | .setDescription('Manage chat with OpenAI GPT') 18 | .addSubcommand(subcommand => 19 | subcommand 20 | .setName('open') 21 | .setDescription('Open/Continue chat session') 22 | ) 23 | .addSubcommand(subcommand => 24 | subcommand 25 | .setName('close') 26 | .setDescription('Close chat session (conversation will be saved)') 27 | ) 28 | .addSubcommand(subcommand => 29 | subcommand 30 | .setName('delete') 31 | .setDescription('Delete conversation PERMANENTLY') 32 | .addStringOption(option => 33 | option.setName('confirm') 34 | .setDescription('This action is IRREVERSIBLE. Are you sure?') 35 | .setRequired(true) 36 | .addChoices( 37 | { name: 'No', value: 'no' }, 38 | { name: 'Yes', value: 'yes' } 39 | )), 40 | ), 41 | 42 | async execute(interaction) { 43 | // defer reply until the command finishes executing 44 | await interaction.deferReply(); 45 | // get subcommand from interaction object 46 | const input = interaction.options.getSubcommand(); 47 | // get user id from interaction object 48 | const user_id = interaction.user.id; 49 | // insert user in database if not already in 50 | if (! await db.is_user_inserted(user_id)) { 51 | await db.insert_new_user(user_id); 52 | await db.init_user_chat(user_id, chat.defaultPrompt); 53 | } 54 | if (input === 'open') { 55 | if (! await db.get_user_chat_status(user_id)) { 56 | // set user chat status to true 57 | await db.set_user_chat_status(user_id, true); 58 | await interaction.editReply('`Chat session open!\nSend a message and OpenAI will reply.`'); 59 | } else { 60 | await interaction.editReply('`Chat session already open!`'); 61 | } 62 | } else if (input === 'close') { 63 | if (await db.get_user_chat_status(user_id)) { 64 | // set user chat status to false 65 | await db.set_user_chat_status(user_id, false); 66 | await interaction.editReply('`Chat session closed!`'); 67 | } else { 68 | await interaction.editReply('`Chat session already closed!`'); 69 | } 70 | } else if (input === 'delete') { 71 | // get confirmation from the user 72 | const confirmation = interaction.options.getString('confirm'); 73 | if (confirmation === 'yes') { 74 | // permanently delete chat history of the user from database 75 | await db.init_user_chat(user_id, chat.defaultPrompt); 76 | await interaction.editReply('`Chat DELETED!`'); 77 | console.log(`Chat DELETED from database for user ${user_id}.`); 78 | } else { 79 | await interaction.editReply('`Action canceled. Phew!`'); 80 | } 81 | } 82 | }, 83 | 84 | async reply(interaction) { 85 | // get user id from interaction object 86 | const user_id = interaction.author.id; 87 | // if user not in database return 88 | if (! await db.is_user_inserted(user_id)) { 89 | return; 90 | } 91 | // if user chat status is false return 92 | if (! await db.get_user_chat_status(user_id)) { 93 | return; 94 | } 95 | // get user chat from database 96 | const chat_text = await db.get_user_chat(user_id); 97 | // generate a reply from the user message 98 | const response = await chat.generate_reply(chat_text, interaction.content); 99 | if (!response) { 100 | await interaction.reply("Error generating reply"); 101 | return; 102 | } 103 | const chat_updated = response[0]; 104 | const reply = response[1]; 105 | // update user chat in database 106 | await db.update_user_chat(user_id, JSON.stringify(chat_updated)); 107 | // send generated reply 108 | await interaction.reply(reply.content); 109 | console.log('Reply sent!'); 110 | }, 111 | }; 112 | 113 | -------------------------------------------------------------------------------- /src/utils/models-managers.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const { OpenAI } = require('openai'); 3 | const { aiModels } = require('../config/ai-models.js'); 4 | 5 | 6 | // Openai configuration with apiKey 7 | const apiKey = process.env.OPENAI_API_KEY; 8 | const openai = new OpenAI({ apiKey: apiKey }); 9 | // Discord max message length 10 | const maxMessageLength = 2000; 11 | 12 | // The AskAI class extends the OpenAIApi class and is used to generate answers to user prompts 13 | class AskManager { 14 | constructor() { 15 | // Set the name, body and defaultPrompt properties based on the specified AI model 16 | this.name = 'ask'; 17 | this.body = aiModels[this.name].body; 18 | this.defaultPrompt = this.body.prompt; 19 | } 20 | 21 | // This method generates an answer to a user question (prompt) 22 | async generate_answer(prompt) { 23 | console.log(`User prompt: ${prompt}`); 24 | console.log(`Generating answer...`); 25 | // Concatenate the first stop, the prompt, and the second stop to format the question 26 | const question = `${this.body.stop[0]} ${prompt}\n${this.body.stop[1]} `; 27 | // Concatenate the user prompt with the model default prompt 28 | this.body.prompt = this.defaultPrompt + question; 29 | 30 | try { 31 | const response = await openai.completions.create(this.body); 32 | console.log('Status:', response.status, response.statusText); 33 | // Extract the response text and trim leading and trailing whitespace 34 | const answer = response.choices[0].text.trim(); 35 | if (!answer || answer.length > maxMessageLength) { 36 | throw new Error; 37 | } 38 | 39 | return answer; 40 | 41 | } catch (err) { 42 | console.error(err); 43 | return; 44 | } 45 | } 46 | } 47 | 48 | 49 | // The ChatAI class extends the OpenAIApi class and is used to hold a conversation with a user 50 | class ChatManager { 51 | constructor() { 52 | // Set the name, body and defaultPrompt properties based on the specified AI model 53 | this.name = 'chat'; 54 | this.body = aiModels[this.name].body; 55 | this.defaultPrompt = JSON.stringify(this.body.messages); 56 | } 57 | 58 | // This method generates an answer to a user message (prompt) 59 | async generate_reply(chat, prompt) { 60 | console.log(`User prompt: ${prompt}`); 61 | console.log(`Generating reply...`); 62 | // Concatenate the first stop, the prompt, and the second stop to format the message 63 | const messages = JSON.parse(chat); 64 | const message = { role: "user", content: prompt }; 65 | // Concatenate the conversation and message to create the body's prompt 66 | messages.push(message); 67 | this.body.messages = messages; 68 | 69 | try { 70 | const response = await openai.chat.completions.create(this.body); 71 | console.log('Status:', response.status, response.statusText); 72 | 73 | // Extract the response text and trim leading and trailing whitespace 74 | const reply = response.choices[0].message; 75 | if (!reply.content || reply.content.length > maxMessageLength) { 76 | throw new Error; 77 | } 78 | 79 | messages.push(reply); 80 | return [messages, reply]; 81 | 82 | } catch (err) { 83 | console.error(err); 84 | return; 85 | } 86 | } 87 | } 88 | 89 | // The ImageAI class extends the OpenAIApi class and is used to generate an image using DALL-E Model 90 | class ImageManager { 91 | constructor() { 92 | // Set the name, body and defaultPrompt properties based on the specified AI model 93 | this.name = 'image'; 94 | this.body = aiModels[this.name].body; 95 | this.defaultPrompt = this.body.prompt; 96 | } 97 | 98 | async generate_image(prompt) { 99 | console.log(`User prompt: ${prompt}`); 100 | console.log('Generating image...'); 101 | // Concatenate the user prompt with the model default prompt 102 | this.body.prompt = prompt + this.defaultPrompt; 103 | 104 | try { 105 | const response = await openai.images.generate(this.body); 106 | console.log('Status:', response.status, response.statusText); 107 | // Extract the image URL frome response data 108 | const url = response.data[0].url; 109 | if (!url) { 110 | throw new Error; 111 | } 112 | 113 | return url; 114 | 115 | } catch (err) { 116 | console.error(err); 117 | return; 118 | } 119 | } 120 | } 121 | 122 | 123 | module.exports = { AskManager, ChatManager, ImageManager }; 124 | --------------------------------------------------------------------------------