├── 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 |
--------------------------------------------------------------------------------