├── .env.example ├── .github └── FUNDING.yml ├── LICENSE ├── README.md ├── commands ├── developer │ ├── exit.js │ ├── reload.js │ └── set-status.js └── gpt │ ├── ask.js │ ├── clear.js │ ├── start.js │ ├── stop.js │ └── voice.js ├── events ├── errorManager.js ├── interactionCreate.js ├── messageCreate.js └── ready.js ├── handlers ├── gpt-handler.js ├── index.js └── transcriber.js ├── package-lock.json └── package.json /.env.example: -------------------------------------------------------------------------------- 1 | TOKEN="" 2 | OPENAI_API_KEY="" 3 | WITAI_API_KEY="" 4 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: Vortrix5 4 | custom: https://paypal.me/Vortrix5 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Amine Zouaoui 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🤖 Discord AI Bot 2 | 3 | [![Node.js](https://img.shields.io/badge/Node.js-14.x-green?style=flat-square)](https://nodejs.org/) 4 | [![npm](https://img.shields.io/badge/npm-6.x-red?style=flat-square)](https://www.npmjs.com/) 5 | [![Discord.js](https://img.shields.io/badge/Discord.js-14.x-blue?style=flat-square)](https://discord.js.org/) 6 | [![OpenAI API](https://img.shields.io/badge/OpenAI%20API-v2-yellow?style=flat-square)](https://beta.openai.com/docs/api-overview/getting-started) 7 | [![wit.ai API](https://img.shields.io/badge/wit.ai%20API-v2.x-brightgreen?style=flat-square)](https://wit.ai/) 8 | [![GitHub license](https://img.shields.io/github/license/Vortrix5/discord-ai-bot?style=flat-square)](https://github.com/Vortrix/discord-ai-bot/blob/master/LICENSE) 9 | 10 | A Discord bot that allows users to communicate with an AI using the OpenAI API. 11 | 12 | ## 🚀 Features 13 | 14 | - `/start [text/voice]`: Creates a text/voice channel to start a conversation with the bot 15 | - `/stop [text/voice]`: Destroys the connection with the bot 16 | - `/clear`: Clears the conversation prompt 17 | - `/voice [voice]`: Changes the TTS voice by choosing one from the options 18 | - `/ask [question]`: Asks one question to the bot, no conversation 19 | 20 | ## 💾 Installation 21 | 22 | 1. Clone the repository: `git clone https://github.com/vortrix5/discord-ai-bot.git` 23 | 2. Navigate to the project directory: `cd discord-ai-bot` 24 | 3. Install the dependencies: `npm install` 25 | 4. Create a file named `.env` based on the example provided: `cp .env.example .env` 26 | 5. Edit the `.env` file and fill in the details: 27 | 28 | ``` 29 | TOKEN= 30 | OPENAI_API_KEY= 31 | WITAI_API_KEY= 32 | ``` 33 | 34 | ## 🔑 Getting a Discord Bot Token 35 | 36 | 1. Go to the [Discord Developer Portal](https://discord.com/developers/applications) and log in. 37 | 2. Click the "New Application" button. 38 | 3. Give your application a name and click "Create". 39 | 4. Click on the "Bot" tab on the left side of the screen. 40 | 5. Click the "Add Bot" button. 41 | 6. Click the "Copy" button next to the "Token" field to copy the token to your clipboard. 42 | 43 | ## 🔑 Getting an OpenAI API Key 44 | 45 | 1. Go to the [OpenAI API documentation](https://beta.openai.com/docs/api-overview/getting-started) and click the "Sign up for an API key" button. 46 | 2. Follow the instructions to sign up for an API key. 47 | 48 | ## 🔑 Getting a wit.ai API Key 49 | 50 | 1. Go to the [wit.ai website](https://wit.ai/) and click the "Sign up" button. 51 | 2. Follow the instructions to sign up for an API key. 52 | 53 | ## 🤖 Usage 54 | 55 | 1. Run the bot: `npm start` 56 | 2. Invite the bot to your Discord server by following these steps: 57 | 58 | - Go to the [Discord Developer Portal](https://discord.com/developers/applications) and log in. 59 | - Click on the application that represents your bot. 60 | - Click on the "**OAuth2**" tab on the left side of the screen. 61 | - Under the "**Scopes**" section, check the boxes for "**bot**" and "**application.commands**". 62 | - Under the "Permissions" section, check the box for "**Administrator**". 63 | - Click the "Copy" button next to the generated link to copy the invite link to your clipboard. 64 | - Open the invite link in your browser to add the bot to your Discord server. 65 | 66 | ## 📝 License 67 | 68 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 69 | -------------------------------------------------------------------------------- /commands/developer/exit.js: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Amine Zouaoui 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | const description = 'Stop the bot.' 26 | 27 | const run = async (interaction, client) => { 28 | await interaction.reply({content: 'Shutting down the bot...', ephemeral: true}); 29 | await process.exit(1); 30 | } 31 | 32 | const developer = true; // This is a developer command. Only the developer can use it. 33 | 34 | module.exports= {run, description, developer}; -------------------------------------------------------------------------------- /commands/developer/reload.js: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Amine Zouaoui 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | const {loadCommands} = require('../../events/ready.js'); 26 | const description = 'Reloads all commands.'; 27 | 28 | const run = async (message, client) => { 29 | await message.reply({content: "Reloading all commands...", ephemeral: true}); 30 | try { 31 | await loadCommands(client); 32 | }catch (error) { 33 | await message.editReply({content: `There was a problem while running this message.`, ephemeral: true}); 34 | await console.log(error); 35 | } 36 | await message.editReply({content: `All commands have been reloaded!`, ephemeral: true}); 37 | } 38 | 39 | const developer = true; // This is a developer command. Only the developer can use it. 40 | 41 | module.exports= {run, description, developer}; -------------------------------------------------------------------------------- /commands/developer/set-status.js: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Amine Zouaoui 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | const {ApplicationCommandOptionType, ActivityType} = require("discord.js"); 26 | const description = 'Sets the bot\'s status.'; 27 | const STRING = ApplicationCommandOptionType.String 28 | 29 | const options = [ 30 | { 31 | name: 'status', 32 | description: 'The status to set.', 33 | required: true, 34 | type: STRING, 35 | choices: [ 36 | { 37 | name: 'Online', 38 | value: 'online', 39 | }, 40 | { 41 | name: 'Idle', 42 | value: 'idle', 43 | }, 44 | { 45 | name: 'Do Not Disturb', 46 | value: 'dnd', 47 | }, 48 | { 49 | name: 'Invisible', 50 | value: 'invisible', 51 | }, 52 | ], 53 | }, 54 | { 55 | name: 'activity', 56 | description: 'The activity to set.', 57 | required: false, 58 | type: STRING, 59 | choices: [ 60 | { 61 | name: 'Playing', 62 | value: 'playing', 63 | }, 64 | { 65 | name: 'Streaming', 66 | value: 'streaming', 67 | }, 68 | { 69 | name: 'Listening to', 70 | value: 'listening', 71 | }, 72 | { 73 | name: 'Watching', 74 | value: 'watching', 75 | }, 76 | ], 77 | }, 78 | { 79 | name: 'name', 80 | description: 'The name of the activity.', 81 | required: false, 82 | type: STRING, 83 | }, 84 | { 85 | name: 'url', 86 | description: 'The url of the activity.', 87 | required: false, 88 | type: STRING, 89 | }, 90 | ]; 91 | 92 | const run = async (message, client) => { 93 | const status = message.options.getString('status'); 94 | const activity = message.options.getString('activity'); 95 | const name = message.options.getString('name') || ''; 96 | const url = message.options.getString('url'); 97 | const activities = { 98 | playing: ActivityType.Playing, 99 | streaming: ActivityType.Streaming, 100 | listening: ActivityType.Listening, 101 | watching: ActivityType.Watching, 102 | }; 103 | const activityType = activities[activity]; // This is the activity type. 104 | 105 | await client.user.setPresence({ 106 | activities: [{ 107 | name: name, 108 | type: activityType, 109 | url: url 110 | }], 111 | status: status, 112 | }); 113 | 114 | message.reply({content: 'Status set.', ephemeral: true}); 115 | 116 | } 117 | 118 | const developer = true; // This is a developer command. Only the developer can use it. 119 | 120 | module.exports= {run, description, developer, options}; -------------------------------------------------------------------------------- /commands/gpt/ask.js: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Amine Zouaoui 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | const {clearPrompt, runGPT} = require("../../handlers/gpt-handler.js"); 26 | const {ApplicationCommandOptionType} = require('discord.js'); 27 | const fs = require("fs"); 28 | 29 | const STRING = ApplicationCommandOptionType.String 30 | require('dotenv').config(); 31 | 32 | const description = 'Speak to an awesome AI' 33 | const options = [ 34 | { 35 | name: 'message', 36 | description: 'Your message to the bot', 37 | required: true, 38 | type: STRING, 39 | }, 40 | 41 | ] 42 | 43 | 44 | 45 | const run = async (interaction, client) => { 46 | let config= JSON.parse(fs.readFileSync(require.resolve('../../config.json'))); 47 | const guildID = interaction.guildId; 48 | const msg = interaction.options.getString('message'); 49 | await interaction.deferReply(); 50 | try { 51 | config[guildID].gpt.prompt = ''; 52 | config[guildID].readMode = false; 53 | await fs.writeFileSync(require.resolve('../../config.json'), JSON.stringify(config, null, 4)); 54 | const completion = await runGPT(guildID,msg); 55 | await interaction.editReply(completion); 56 | } catch (error) { 57 | await interaction.editReply("There was an error while speaking to ChatGPT. Please try again later."); 58 | await console.log(error); 59 | } 60 | 61 | 62 | } 63 | 64 | module.exports= {run, description, options} 65 | -------------------------------------------------------------------------------- /commands/gpt/clear.js: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Amine Zouaoui 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | const {clearPrompt} = require("../../handlers/gpt-handler.js"); 26 | const fs = require("fs"); 27 | 28 | 29 | const description = 'Clear the AI Buffer.'; 30 | const options = [] 31 | 32 | const run = async (interaction, client) => { 33 | let config= await JSON.parse(fs.readFileSync(require.resolve('../../config.json'))); 34 | config[interaction.guildId].gpt.prompt = ''; 35 | await fs.writeFileSync(require.resolve('../../config.json'), JSON.stringify(config, null, 4)); 36 | await interaction.reply("The buffer has been successfully cleared."); 37 | } 38 | 39 | module.exports={run, description}; 40 | -------------------------------------------------------------------------------- /commands/gpt/start.js: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Amine Zouaoui 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | const {ChannelType, ApplicationCommandOptionType} = require('discord.js'); 26 | const { 27 | AudioPlayer, 28 | VoiceConnectionStatus, 29 | joinVoiceChannel, 30 | entersState, 31 | createAudioResource, 32 | StreamType, getVoiceConnection, AudioPlayerStatus 33 | } = require("@discordjs/voice"); 34 | const Transcriber = require("../../handlers/transcriber"); 35 | const {runGPT} = require("../../handlers/gpt-handler"); 36 | const fs = require("fs"); 37 | 38 | const STRING = ApplicationCommandOptionType.String 39 | require('dotenv').config(); 40 | 41 | 42 | const description = 'Start the AI chat'; 43 | 44 | const options = [ 45 | { 46 | name: 'type', 47 | description: 'The type of chat to start.', 48 | required: true, 49 | type: STRING, 50 | choices: [ 51 | { 52 | name: 'Text', 53 | value: 'text', 54 | }, 55 | { 56 | name: 'Voice', 57 | value: 'voice', 58 | }, 59 | ], 60 | } 61 | ] 62 | //Voice Chat 63 | const transcriber = new Transcriber(process.env.WITAI_API_KEY); 64 | 65 | async function setCredentials(config, guildID, userID, channelID) { 66 | config[guildID].readMode = true; 67 | config[guildID].gpt.userID = userID; 68 | config[guildID].gpt.channelID = channelID; 69 | await fs.writeFileSync(require.resolve('../../config.json'), JSON.stringify(config, null, 4)); 70 | } 71 | 72 | const run = async (interaction, client) => { 73 | let config = JSON.parse(fs.readFileSync(require.resolve('../../config.json'))); 74 | 75 | let userID, channelID, guildID = interaction.guildId; 76 | const type = interaction.options.getString('type'); 77 | await interaction.deferReply(); 78 | 79 | const category = client.channels.cache.find( 80 | channel => channel.type === ChannelType.GuildCategory 81 | && channel.guildId === interaction.guild.id 82 | && channel.name === 'chats'); 83 | if (!category) { 84 | await interaction.guild.channels.create({ 85 | name: 'ai-chats', 86 | type: ChannelType.GuildCategory 87 | }) 88 | } 89 | if (type === 'text') { 90 | userID = interaction.member.id; 91 | if (config[guildID].readMode) { 92 | await interaction.editReply("AI Chat already running in <#" + config[guildID].gpt.channelID + ">") 93 | } else { 94 | await interaction.guild.channels.create({ 95 | name: 'ai-chat', 96 | type: ChannelType.GuildText, 97 | parent: category.id 98 | }) 99 | .then(async channel => { 100 | channelID = channel.id; 101 | await console.log(`Created new channel: ${channel}`) 102 | await channel.send(`Hello and welcome to your chat <@${interaction.member.id}>. Please feel free to ask me any question.`); 103 | }) 104 | .catch(console.error); 105 | await setCredentials(config, interaction.guildId, userID, channelID); 106 | await interaction.editReply("AI Chat channel successfully created."); 107 | } 108 | } 109 | 110 | //Voice Chat 111 | else if (type === 'voice') { 112 | let audioPlayer = new AudioPlayer(); 113 | let stopWords=["leave","stop","exit","quit","bye","goodbye","ciao"]; 114 | let response; 115 | let voiceConnection = getVoiceConnection(interaction.guildId); 116 | let channel = client.channels.cache.find((channel) => 117 | channel.guildId === interaction.guild.id 118 | && channel.type === ChannelType.GuildVoice 119 | && channel.members.has(interaction.member.id)); 120 | if (!channel) { 121 | return interaction.editReply("You must be in a voice channel to start a voice chat."); 122 | } 123 | 124 | if (!voiceConnection || voiceConnection?.state.status === VoiceConnectionStatus.Disconnected) { 125 | voiceConnection = joinVoiceChannel({ 126 | channelId: channel.id, 127 | guildId: interaction.guildId, 128 | adapterCreator: interaction.guild.voiceAdapterCreator, 129 | selfDeaf: false, 130 | selfMute: false 131 | }); 132 | voiceConnection = await entersState(voiceConnection, VoiceConnectionStatus.Connecting, 5_000); 133 | await interaction.editReply("Successfully started listening."); 134 | } else { 135 | await interaction.editReply("AI Chat already running"); 136 | } 137 | voiceConnection.receiver.speaking.on("start", (userId) => { 138 | if (audioPlayer.state.status === AudioPlayerStatus.Playing) return; 139 | transcriber.listen(voiceConnection.receiver, userId, client.users.cache.get(userId)).then(async (data) => { 140 | if (!data.transcript.text) return; 141 | let text = data.transcript.text; 142 | let user = client.users.cache.get(userId); 143 | interaction.channel.send(`${user.username}: `+text); 144 | if (stopWords.some(word => text.toLowerCase().includes(word))) { 145 | config[guildID].gpt.prompt = ""; 146 | await voiceConnection.destroy(); 147 | await interaction.channel.send("Successfully stopped listening."); 148 | await fs.writeFileSync(require.resolve('../../config.json'), JSON.stringify(config, null, 4)); 149 | return; 150 | } 151 | try { 152 | response = await runGPT(guildID, text); 153 | } catch (error) { 154 | await interaction.editReply("There was an error while speaking to ChatGPT. Please try again later."); 155 | await console.log(error); 156 | } 157 | let url; 158 | if (response.length > 600) { 159 | url = `https://api.streamelements.com/kappa/v2/speech?voice=${encodeURIComponent(config[guildID].gpt.voice)}&text=${encodeURIComponent("I am sorry, the response is larger than 600 characters.")}`; 160 | } else { 161 | url = `https://api.streamelements.com/kappa/v2/speech?voice=${encodeURIComponent(config[guildID].gpt.voice)}&text=${encodeURIComponent(response)}`; 162 | } 163 | const audioResource = await createAudioResource(url, { 164 | inputType: StreamType.Arbitrary, 165 | inlineVolume: true 166 | }); 167 | 168 | if (voiceConnection.state.status !== VoiceConnectionStatus.Disconnected && voiceConnection.joinConfig.guildId === interaction.guildId) { 169 | await voiceConnection.subscribe(audioPlayer); 170 | await audioPlayer.play(audioResource); 171 | } 172 | }); 173 | }); 174 | } 175 | } 176 | 177 | module.exports = {run, description, options} -------------------------------------------------------------------------------- /commands/gpt/stop.js: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Amine Zouaoui 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | const {clearPrompt} = require("../../handlers/gpt-handler.js"); 26 | const {ApplicationCommandOptionType} = require("discord.js"); 27 | const {getVoiceConnection} = require("@discordjs/voice"); 28 | const fs = require("fs"); 29 | const {setReadMode} = require("../../handlers"); 30 | const wait = require('node:timers/promises').setTimeout; 31 | const STRING = ApplicationCommandOptionType.String 32 | 33 | require('dotenv').config(); 34 | 35 | 36 | const description = 'Stop the text/voice chat.'; 37 | 38 | const options = [ 39 | { 40 | name: 'type', 41 | description: 'The type of chat to stop.', 42 | required: true, 43 | type: STRING, 44 | choices: [ 45 | { 46 | name: 'Text', 47 | value: 'text', 48 | }, 49 | { 50 | name: 'Voice', 51 | value: 'voice', 52 | }, 53 | ], 54 | } 55 | 56 | ]; 57 | 58 | const run = async (interaction, client) => { 59 | let config= JSON.parse(fs.readFileSync(require.resolve('../../config.json'))); 60 | const guildID = interaction.guildId; 61 | const type = interaction.options.getString('type'); 62 | await interaction.deferReply(); 63 | if(type === 'text') { 64 | if (!config[guildID].readMode) { 65 | await interaction.editReply("AI Chat not running or already stopped.") 66 | } else if (config[guildID].gpt.channelID !== interaction.channel.id) { 67 | await interaction.editReply("No AI Chat in this channel. Please run this command in the appropriate channel.") 68 | } else { 69 | config[guildID].gpt.channelID = null; 70 | config[guildID].gpt.userID = null; 71 | config[guildID].gpt.prompt = ''; 72 | config[guildID].readMode = false; 73 | await fs.writeFileSync(require.resolve('../../config.json'), JSON.stringify(config, null, 4)); 74 | await interaction.editReply("AI Chat successfully stopped. This channel will be deleted in 5 seconds."); 75 | await wait(5000); 76 | await interaction.channel.delete(); 77 | } 78 | } else if(type === 'voice') { 79 | const voiceConnection = getVoiceConnection(interaction.guild.id); 80 | if (!voiceConnection) { 81 | await interaction.editReply("Voice Chat not running or already stopped.") 82 | } else { 83 | config[guildID].gpt.prompt = ''; 84 | await fs.writeFileSync(require.resolve('../../config.json'), JSON.stringify(config, null, 4)); 85 | await voiceConnection.destroy(); 86 | await interaction.editReply("Voice Chat successfully stopped."); 87 | } 88 | } 89 | 90 | } 91 | 92 | module.exports = {run, description, options}; -------------------------------------------------------------------------------- /commands/gpt/voice.js: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Amine Zouaoui 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | const fs = require("fs"); 25 | 26 | 27 | const description = 'Change the bot\'s voice.'; 28 | const options = [ 29 | { 30 | name: 'voice', 31 | description: 'The voice you want to change to.', 32 | type: 3, 33 | required: true, 34 | choices: [ 35 | { 36 | name: 'Emma', 37 | value: 'Emma', 38 | }, 39 | { 40 | name: 'Brian', 41 | value: 'Brian', 42 | }, 43 | { 44 | name: 'Amy', 45 | value: 'Amy', 46 | }, 47 | 48 | { 49 | name: 'Justin', 50 | value: 'Justin', 51 | }, 52 | ], 53 | } 54 | ] 55 | 56 | const run = async (interaction, client) => { 57 | const voice = interaction.options.getString('voice'); 58 | let config= await JSON.parse(fs.readFileSync(require.resolve('../../config.json'))); 59 | if(config[interaction.guildId].gpt.voice !== voice) { 60 | config[interaction.guildId].gpt.voice = voice; 61 | await fs.writeFileSync(require.resolve('../../config.json'), JSON.stringify(config, null, 4)); 62 | await interaction.reply(`The voice has been successfully changed to ${voice}.`); 63 | }else{ 64 | await interaction.reply(`The voice is already set to ${voice}.`); 65 | } 66 | 67 | } 68 | 69 | module.exports={run, description, options}; 70 | -------------------------------------------------------------------------------- /events/errorManager.js: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Amine Zouaoui 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | const init= async() => { 26 | process.on('unhandledRejection', error => { 27 | console.log(error) 28 | }) 29 | process.on('uncaughtException', error => { 30 | console.log(error) 31 | }) 32 | } 33 | 34 | module.exports={init}; -------------------------------------------------------------------------------- /events/interactionCreate.js: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Amine Zouaoui 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | const {client} = require('../handlers/index.js'); 26 | require('dotenv').config(); 27 | 28 | 29 | const init = async (interaction) => { 30 | //Command Listener 31 | if (interaction.isCommand()) { 32 | const {commandName} = interaction; 33 | const selectedCommand = client.commands.get(commandName); 34 | if (selectedCommand.developer && interaction.user.id !== process.env.DEVELOPER_ID) { 35 | return interaction.reply({content: "This command is only available to the developer.", ephemeral: true}); 36 | } else { 37 | try { 38 | await selectedCommand.run(interaction, client); 39 | } catch (error) { 40 | interaction.reply({content: 'There was an error while executing this command!', ephemeral: true}); 41 | console.error(`Error executing ${commandName}`); 42 | console.error(error); 43 | } 44 | } 45 | } 46 | 47 | //Button Listener 48 | else if (interaction.isButton()) { 49 | if (interaction.customId === 'primary') { 50 | await interaction.update({content: 'A primary button was clicked!', components: []}); 51 | } else { 52 | await interaction.update({content: 'Another button was clicked!', components: []}); 53 | } 54 | } 55 | //Modal Listener 56 | else if (interaction.isModalSubmit()) { 57 | if (interaction.customId === 'myModal') { 58 | await interaction.reply({content: 'Your submission was received successfully!'}); 59 | } 60 | } 61 | //Context Menu 62 | else if (interaction.isUserContextMenuCommand()) { 63 | console.log(interaction); 64 | } 65 | } 66 | 67 | module.exports = {init}; 68 | -------------------------------------------------------------------------------- /events/messageCreate.js: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Amine Zouaoui 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | const {client} = require( '../handlers/index.js'); 26 | const {runGPT} = require( "../handlers/gpt-handler.js"); 27 | const fs = require("fs"); 28 | 29 | 30 | const init = async (message) => { 31 | const guildID = message.guildId; 32 | let config= await JSON.parse(fs.readFileSync(require.resolve('../config.json')))[guildID]; 33 | //console.log(config); 34 | if (!config.readMode 35 | || message.member.id!==config.gpt.userID 36 | || message.channel.id!==config.gpt.channelID 37 | || message.member.id === client.application.id ) return; 38 | try { 39 | const completion = await runGPT(guildID,message.content); 40 | await message.reply(completion); 41 | } catch (error) { 42 | await message.reply("There was an error while speaking to ChatGPT. Please try again later."); 43 | await console.log(error); 44 | } 45 | } 46 | 47 | module.exports={init} -------------------------------------------------------------------------------- /events/ready.js: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Amine Zouaoui 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | const {promisify} = require("util"); 26 | const glob = require("glob"); 27 | const fs = require("fs"); 28 | const globPromise = promisify(glob); 29 | const { AutoPoster } = require('topgg-autoposter') 30 | 31 | require('dotenv').config(); 32 | 33 | 34 | const loadCommands = async (client) => { 35 | await client.commands.clear(); 36 | const commandFiles = await globPromise(`${process.cwd()}/commands/*/**.js`); 37 | await Promise.all(commandFiles.map(async (value) => { 38 | delete require.cache[require.resolve(value)]; 39 | const file = require(value); 40 | const splitted = value.split("/"); 41 | const directory = splitted[splitted.length - 1]; 42 | const name = directory.split(".")[0]; 43 | if (file.run) { 44 | const properties = {name, directory, ...file}; 45 | await client.commands.set(name, properties); 46 | } 47 | })); 48 | 49 | console.log(`Started refreshing ${client.commands.size} application (/) commands.`); 50 | await client.application.commands.set(client.commands) 51 | .then(() => { 52 | console.log("\x1b[32m%s\x1b[0m", `Successfully reloaded ${client.commands.size} application (/) commands.`) 53 | }) 54 | .catch(console.error) 55 | 56 | } 57 | 58 | const init = async (client) => { 59 | console.log("\x1b[35m%s\x1b[0m", `Ready! Logged in as ${client.user.username}.`) 60 | //const ap = AutoPoster(process.env.TOGG_TOKEN, client) 61 | /*await ap.on('posted', () => { 62 | console.log(`Working in ${client.guilds.cache.size} guilds.`) 63 | });*/ 64 | await client.user.setPresence({ 65 | status: 'idle', 66 | }); 67 | const Guilds = client.guilds.cache.map(guild => guild.id); 68 | let config = JSON.parse(fs.readFileSync(require.resolve('../config.json'))); 69 | for (let i = 0; i < Guilds.length; i++) { 70 | config[Guilds[i]] = { 71 | readMode: false, 72 | gpt: { 73 | voice:"Emma", 74 | prompt: "", 75 | userID: null, 76 | channelID: null 77 | } 78 | } 79 | 80 | } 81 | await fs.writeFileSync("config.json", JSON.stringify(config, null, 4)); 82 | 83 | await loadCommands(client); 84 | await client.user.setPresence({ 85 | activities: [{ 86 | name: 'your commands', 87 | type: 2, 88 | }], 89 | status: 'online', 90 | }); 91 | } 92 | 93 | module.exports = {init, loadCommands} -------------------------------------------------------------------------------- /handlers/gpt-handler.js: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Amine Zouaoui 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | const {Configuration, OpenAIApi} = require("openai"); 26 | const fs = require("fs"); 27 | 28 | require('dotenv').config(); 29 | 30 | 31 | const configuration = new Configuration({ 32 | apiKey: process.env.OPENAI_API_KEY, 33 | }); 34 | 35 | const openai = new OpenAIApi(configuration); 36 | 37 | let guildID; 38 | 39 | async function runGPT(guild,input) { 40 | let config= JSON.parse(fs.readFileSync(require.resolve('../config.json'))); 41 | guildID = guild; 42 | 43 | let prompt = config[guild].gpt.prompt; 44 | const humanIndex = prompt.indexOf("Human: "); 45 | 46 | const aiIndex = prompt.indexOf("AI: ", humanIndex) + 3; // Find the indexes of the next human and AI messages in the prompt 47 | 48 | const nextMessageIndex = prompt.indexOf("Human: ", aiIndex + 1); 49 | 50 | prompt = prompt.substring(0, humanIndex) + prompt.substring(nextMessageIndex, prompt.length); // Remove the oldest messages from the prompt until it is under the maximum length, it will not remove the top description in the prompt 51 | prompt += `${input}\n`; //Add the received message to the prompt 52 | const gptResponse = await openai.createCompletion({ 53 | model: "text-davinci-003", 54 | prompt: prompt, 55 | max_tokens: 256, 56 | temperature: 0.5, 57 | top_p: 0.4, 58 | presence_penalty: 0.7, 59 | frequency_penalty: 0.7, 60 | stop: [" Human:", " AI:"], 61 | }); 62 | prompt += `${gptResponse.data.choices[0].text.substring(0)}\n`; 63 | prompt += `Human: `; // add Human: to let the AI better understand that this is a message sent by human 64 | config[guildID].gpt.prompt = prompt; 65 | fs.writeFileSync(require.resolve('../config.json'), JSON.stringify(config, null, 4)); 66 | return `${gptResponse.data.choices[0].text}`; // add the response to the prompt, which will automatically start with AI: 67 | 68 | } 69 | 70 | 71 | module.exports={runGPT} -------------------------------------------------------------------------------- /handlers/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Amine Zouaoui 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | const fs = require('fs') 26 | const path = require('path') 27 | const { Client, GatewayIntentBits, Partials, Collection } = require('discord.js'); 28 | 29 | 30 | require('dotenv').config(); 31 | 32 | async function setReadMode(value) { 33 | 34 | await fs.writeFileSync("config.json", JSON.stringify({"readMode": value}, null, 4)); 35 | } 36 | const client = new Client({ 37 | intents: [ 38 | GatewayIntentBits.Guilds, 39 | GatewayIntentBits.GuildMessageReactions, 40 | GatewayIntentBits.GuildMessages, 41 | GatewayIntentBits.GuildVoiceStates, 42 | GatewayIntentBits.MessageContent, 43 | ], 44 | partials: [Partials.Channel] 45 | }); 46 | 47 | client.commands = new Collection(); 48 | client.events = new Collection(); 49 | 50 | //Event Handler 51 | let eventTmp = [] 52 | let eventFiles = fs.readdirSync(path.join(__dirname, '../events')) 53 | 54 | eventFiles.forEach(async (file, i) => { 55 | eventTmp[i] = await import(`../events/${file}`); 56 | let eventName=file.split('.')[0]; 57 | client.on(eventName, eventTmp[i].init); 58 | }) 59 | 60 | 61 | client.login(process.env.TOKEN) 62 | 63 | module.exports={client,setReadMode} 64 | -------------------------------------------------------------------------------- /handlers/transcriber.js: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Amine Zouaoui 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | const { EndBehaviorType, VoiceReceiver } = require('@discordjs/voice'); 26 | const { pipeline, Readable } = require("stream"); 27 | const witClient = require('node-witai-speech'); 28 | const prism = require("prism-media"); 29 | const util = require('util'); 30 | const fs = require("fs"); 31 | 32 | class Transcriber { 33 | constructor(apiKey) { 34 | this.WITAPIKEY = apiKey; 35 | this.witAI_lastcallTS = null; 36 | 37 | return this; 38 | } 39 | 40 | sleep(ms) { 41 | return new Promise((resolve) => { 42 | setTimeout(resolve, ms); 43 | }); 44 | } 45 | 46 | async convert_audio(input) { 47 | try { 48 | // stereo to mono channel 49 | const data = new Int16Array(input) 50 | const ndata = new Int16Array(data.length/2) 51 | for (let i = 0, j = 0; i < data.length; i+=4) { 52 | ndata[j++] = data[i] 53 | ndata[j++] = data[i+1] 54 | } 55 | return Buffer.from(ndata); 56 | } catch (e) { 57 | console.log('convert_audio: ', e) 58 | throw e; 59 | } 60 | } 61 | 62 | async transcribe(buffer, raw) { 63 | try { 64 | if (this.witAI_lastcallTS != null) { 65 | let now = Math.floor(new Date()); 66 | while (now - this.witAI_lastcallTS < 1000) { 67 | await this.sleep(100); 68 | now = Math.floor(new Date()); 69 | } 70 | } 71 | const extractSpeechIntent = util.promisify(witClient.extractSpeechIntent); 72 | var stream = Readable.from(buffer); 73 | const contenttype = "audio/raw;encoding=signed-integer;bits=16;rate=48k;endian=little" 74 | var output = await extractSpeechIntent(this.WITAPIKEY, stream, contenttype); 75 | this.witAI_lastcallTS = Math.floor(new Date()); 76 | if (raw) return output; 77 | if (typeof output == "object") return output; 78 | output = output.split("\n").map((item) => item.trim()).join(""); 79 | let idx = output.lastIndexOf("}{"); 80 | let idx0 = output.lastIndexOf("}"); 81 | output = JSON.parse(output.substring(idx + 1, idx0 + 1).trim().replace(/\n/g, "").trim()); 82 | output.text = output.text.replace(/\./g, ""); 83 | stream.destroy(); 84 | return output; 85 | } catch(e) { 86 | console.log("Transcriber-error: ", e); 87 | return {} 88 | } 89 | } 90 | 91 | listen(receiver, userId, user) { 92 | return new Promise(async (res, rej) => { 93 | const stream = receiver.subscribe(userId, { 94 | end: { 95 | behavior: EndBehaviorType.AfterSilence, 96 | duration: 300, 97 | } 98 | }); 99 | 100 | const decoder = new prism.opus.Decoder({ frameSize: 960, channels: 2, rate: 48000 }); 101 | stream.pipe(decoder); 102 | 103 | let buffer = []; 104 | decoder.on("data", (data) => { 105 | buffer.push(data); 106 | }); 107 | decoder.on("end", async () => { 108 | buffer = Buffer.concat(buffer); 109 | const duration = buffer.length / 48000 / 2; 110 | if (duration > 1.0 || duration < 19) { 111 | let transcript = await this.transcribe(await this.convert_audio(buffer)); 112 | res({user: user, transcript: transcript }); 113 | } 114 | }) 115 | }); 116 | } 117 | } 118 | 119 | module.exports = Transcriber; 120 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "discord-bot-boilerplate", 3 | "version": "1.2.0", 4 | "main": "handlers/index.js", 5 | "bin": "index.js", 6 | "scripts": { 7 | "start": "cross-env NODE_ENV=production NODE_NO_WARNINGS=1 node . ", 8 | "dev": "cross-env NODE_ENV=development NODE_NO_WARNINGS=1 node .", 9 | "dev:watch": "cross-env NODE_ENV=development NODE_NO_WARNINGS=1 nodemon .", 10 | "build": "pkg ." 11 | }, 12 | "author": "Joaquin Giordano ", 13 | "license": "ISC", 14 | "description": "", 15 | "dependencies": { 16 | "@discordjs/rest": "^1.5.0", 17 | "@discordjs/voice": "^0.14.0", 18 | "@google-cloud/text-to-speech": "^4.0.4", 19 | "@top-gg/sdk": "^3.1.3", 20 | "async": "^3.2.4", 21 | "discord-api-types": "^0.37.23", 22 | "discord.js": "^14.7.1", 23 | "discord.js-akinator": "^4.0.4", 24 | "dotenv": "^16.0.2", 25 | "ffmpeg-static": "^5.1.0", 26 | "glob": "^7.2.3", 27 | "libsodium-wrappers": "^0.7.10", 28 | "moment": "^2.29.4", 29 | "node": "^19.2.0", 30 | "node-fetch": "^2.6.7", 31 | "node-witai-speech": "^1.0.2", 32 | "openai": "^3.1.0", 33 | "openai-api": "^1.3.1", 34 | "opusscript": "^0.0.8", 35 | "prism-media": "^1.3.4", 36 | "puppeteer": "^19.4.1", 37 | "pyodide": "^0.21.3", 38 | "request": "^2.88.2", 39 | "topgg-autoposter": "^2.0.1" 40 | }, 41 | "devDependencies": { 42 | "cross-env": "^7.0.3", 43 | "nodemon": "^2.0.19", 44 | "pkg": "^5.7.0" 45 | }, 46 | "pkg": { 47 | "outputPath": "dist", 48 | "scripts": "commands/**/*.js", 49 | "assets": [ 50 | ".env" 51 | ] 52 | } 53 | } 54 | --------------------------------------------------------------------------------