├── .env.template ├── .gitattributes ├── .gitignore ├── .replit ├── README.md ├── app.js ├── commands ├── hello.js ├── image.js ├── ping.js └── question.js ├── events ├── interactionCreate.js ├── messageCreate.js └── ready.js ├── images ├── 200x200.png ├── CleanShot_2023-05-25_at_13.12.22.png ├── CleanShot_2023-05-25_at_13.12.46.png ├── CleanShot_2023-05-25_at_13.20.54.png ├── CleanShot_2023-05-25_at_13.21.58.png ├── CleanShot_2023-05-25_at_13.23.44.png ├── CleanShot_2023-05-25_at_13.24.20.png ├── CleanShot_2023-05-25_at_13.28.23.png ├── CleanShot_2023-05-25_at_13.30.13.png ├── CleanShot_2023-05-25_at_13.33.26.png ├── CleanShot_2023-05-25_at_13.34.21.png ├── CleanShot_2023-05-25_at_13.34.53.png ├── CleanShot_2023-05-25_at_13.39.25.png ├── CleanShot_2023-05-25_at_13.41.28.png ├── CleanShot_2023-05-25_at_13.45.32.png ├── CleanShot_2023-05-25_at_14.21.16.png ├── CleanShot_2023-05-25_at_14.21.43.png ├── CleanShot_2023-05-25_at_14.32.30.png ├── discord-logo.jpg ├── logotype.webp ├── test ├── voiceflow-copy.png └── voiceflow-integration.png ├── package-lock.json ├── package.json ├── replit.nix └── utils └── dialogapi.js /.env.template: -------------------------------------------------------------------------------- 1 | DISCORD_TOKEN="XXX" 2 | VOICEFLOW_API_URL="https://general-runtime.voiceflow.com" 3 | VOICEFLOW_API_KEY="VF.DM.XXX" 4 | VOICEFLOW_VERSION_ID="production" 5 | VOICEFLOW_PROJECT_ID="" 6 | APP_ID="XXX" 7 | SERVER_ID="XXX" 8 | LIVEANSWERS_CHANNELS="XXX,XXX,XXX" 9 | THREADS="true" 10 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Serverless directories 108 | .serverless/ 109 | 110 | # FuseBox cache 111 | .fusebox/ 112 | 113 | # DynamoDB Local files 114 | .dynamodb/ 115 | 116 | # TernJS port file 117 | .tern-port 118 | 119 | # Stores VSCode versions used for testing VSCode extensions 120 | .vscode-test 121 | 122 | # yarn v2 123 | .yarn/cache 124 | .yarn/unplugged 125 | .yarn/build-state.yml 126 | .yarn/install-state.gz 127 | .pnp.* 128 | .DS_Store 129 | -------------------------------------------------------------------------------- /.replit: -------------------------------------------------------------------------------- 1 | run = "npm install && npm start" 2 | entrypoint = "app.js" 3 | hidden = [".config", "package-lock.json"] 4 | 5 | [languages] 6 | 7 | [languages.javascript] 8 | pattern = "**/{*.js,*.jsx,*.ts,*.tsx,*.json}" 9 | 10 | [languages.javascript.languageServer] 11 | start = "typescript-language-server --stdio" 12 | 13 | [debugger] 14 | support = true 15 | 16 | [debugger.interactive] 17 | transport = "localhost:0" 18 | startCommand = ["dap-node"] 19 | 20 | [debugger.interactive.initializeMessage] 21 | command = "initialize" 22 | type = "request" 23 | 24 | [debugger.interactive.initializeMessage.arguments] 25 | clientID = "replit" 26 | clientName = "replit.com" 27 | columnsStartAt1 = true 28 | linesStartAt1 = true 29 | locale = "en-us" 30 | pathFormat = "path" 31 | supportsInvalidatedEvent = true 32 | supportsProgressReporting = true 33 | supportsRunInTerminalRequest = true 34 | supportsVariablePaging = true 35 | supportsVariableType = true 36 | 37 | [debugger.interactive.launchMessage] 38 | command = "launch" 39 | type = "request" 40 | 41 | [debugger.interactive.launchMessage.arguments] 42 | console = "externalTerminal" 43 | cwd = "." 44 | pauseForSourceMap = false 45 | program = "./app.js" 46 | request = "launch" 47 | sourceMaps = true 48 | stopOnEntry = false 49 | type = "pwa-node" 50 | 51 | [packager] 52 | language = "nodejs" 53 | 54 | [packager.features] 55 | packageSearch = true 56 | guessImports = true 57 | enabledForHosting = false 58 | 59 | [interpreter] 60 | command = ["prybar-nodejs", "-q", "--ps1", "\u0001\u001B[33m\u0002\u0001\u001B[00m\u0002 ", "-i"] 61 | 62 | [unitTest] 63 | language = "nodejs" 64 | 65 | [nix] 66 | channel = "stable-22_11" 67 | 68 | [env] 69 | XDG_CONFIG_HOME = "/home/runner/$REPL_SLUG/.config" 70 | PATH = "/home/runner/$REPL_SLUG/.config/npm/node_global/bin:/home/runner/$REPL_SLUG/node_modules/.bin" 71 | npm_config_prefix = "/home/runner/$REPL_SLUG/.config/npm/node_global" 72 | 73 | [gitHubImport] 74 | requiredFiles = [".replit", "replit.nix", ".config", "package.json"] 75 | 76 | [[hints]] 77 | regex = 'Error \[ERR_REQUIRE_ESM\]' 78 | message = "We see that you are using require(...) inside your code. We currently do not support this syntax. Please use 'import' instead when using external modules. (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import)" 79 | 80 | [deployment] 81 | run = ["sh", "-c", "node app.js"] 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Voiceflow Discord 2 | 3 | ### Use Voiceflow Dialog Manager API to run a Discord Bot 4 | 5 | # Prerequisite 6 | 7 | - [Replit](https://www.replit.com/) account 8 | - [Discord App](https://discord.com/developers/applications) 9 | - [Voiceflow](https://www.voiceflow.com/) **Chat Assistant** project 10 | 11 | # Setup 12 | 13 | ![discord-logo.jpg](images/discord-logo.jpg) 14 | 15 | ### Create your Discord App 16 | 17 | Go to to [https://discord.com/developers/applications](https://discord.com/developers/applications) to create your Discord app 18 | 19 | ![CleanShot 2023-05-25 at 13.12.22.png](images/CleanShot_2023-05-25_at_13.12.22.png) 20 | 21 | Name your application and click “**Create**” 22 | 23 | ![CleanShot 2023-05-25 at 13.12.46.png](images/CleanShot_2023-05-25_at_13.12.46.png) 24 | 25 | From the General Information tab, copy the **APPLICATION ID** and **save it for later** 26 | 27 | ![CleanShot 2023-05-25 at 13.20.54.png](images/CleanShot_2023-05-25_at_13.20.54.png) 28 | 29 | On the Bot tab, generate a Token by clicking on **Reset Token** button 30 | 31 | ![CleanShot 2023-05-25 at 13.21.58.png](images/CleanShot_2023-05-25_at_13.21.58.png) 32 | 33 | Copy the newly created token and **save it for later** 34 | 35 | ![CleanShot 2023-05-25 at 13.23.44.png](images/CleanShot_2023-05-25_at_13.23.44.png) 36 | 37 | Scroll down and toggle **PRESENCE INTENT**, **SERVER MEMBERS INTENT** and **MESSAGE CONTENT INTENT**. Do not forget to save your changes. 38 | 39 | ![CleanShot 2023-05-25 at 13.24.20.png](images/CleanShot_2023-05-25_at_13.24.20.png) 40 | 41 | Now, on the OAuth2 tab, select bot for the **SCOPES** and give the **BOT PERMISSIONS** you need. 42 | 43 | Once it’s done, click the **Copy** button at the bottom of the page. 44 | 45 | ![CleanShot 2023-05-25 at 13.28.23.png](images/CleanShot_2023-05-25_at_13.28.23.png) 46 | 47 | 48 | 49 | Open this link in a new tab and add the bot to **your Discord server** 50 | 51 | ![CleanShot 2023-05-25 at 13.30.13.png](images/CleanShot_2023-05-25_at_13.30.13.png) 52 | 53 | ![CleanShot 2023-05-25 at 13.33.26.png](images/CleanShot_2023-05-25_at_13.33.26.png) 54 | 55 | ![CleanShot 2023-05-25 at 13.34.21.png](images/CleanShot_2023-05-25_at_13.34.21.png) 56 | 57 | On your Discord server you should now see the bot in the Users tab and a new message 58 | 59 | ![CleanShot 2023-05-25 at 13.34.53.png](images/CleanShot_2023-05-25_at_13.34.53.png) 60 | 61 | ![CleanShot 2023-05-25 at 13.45.32.png](images/CleanShot_2023-05-25_at_13.45.32.png) 62 | 63 | If you haven’t activated the Developer Mode already, do it by going to the settings: **APP SETTINGS** > **Advanced** 64 | 65 | ![CleanShot 2023-05-25 at 13.39.25.png](images/CleanShot_2023-05-25_at_13.39.25.png) 66 | 67 | **Right click** on your server icon in the left sidebar, click on **Copy Server ID** and **save it for later**. 68 | 69 | ![CleanShot 2023-05-25 at 13.41.28.png](images/CleanShot_2023-05-25_at_13.41.28.png) 70 | 71 | > You should now have: 72 | an app key 73 | a bot token 74 | a server id 75 | > 76 | 77 | ![200x200.png](images/200x200.png) 78 | 79 | ### Get your project Dialog API key 80 | 81 | Go to **Voiceflow Creator** and open the Chat Assistant project you want to use. 82 | Click on **Integration** from the left sidebar (or press the 6 key) 83 | 84 | 85 | ![voiceflow-integration.png](images/voiceflow-integration.png) 86 | 87 | Select the **Dialog API** integration, click **Copy API Key** to copy your Voiceflow Dialog API Key and **save it for later** 88 | 89 | 90 | ![voiceflow-copy.png](images/voiceflow-copy.png) 91 | 92 | ![logotype.webp](images/logotype.webp) 93 | 94 | [Fork on Replit](https://replit.com/@niko-voiceflow/voiceflow-discord?v=1) 95 | 96 | ### Setup the Replit secrets 97 | 98 | Set new Secrets with the following info 99 | 100 | 101 | **DISCORD_TOKEN** 102 | Discord bot token 103 | 104 | **APP_ID** 105 | Discord App ID 106 | 107 | **SERVER_ID** 108 | Discord server ID 109 | 110 | **VOICEFLOW_API_URL** 111 | Voiceflow Dialog API endpoint (default to general runtime) 112 | 113 | **VOICEFLOW_API_KEY** 114 | Voiceflow project API key (from the Integration section) 115 | 116 | **VOICEFLOW_VERSION_ID** 117 | Voiceflow project version ID (only for transcripts, default to 'production') 118 | 119 | **VOICEFLOW_PROJECT_ID** 120 | Voiceflow project ID (only for transcripts, default to null) 121 | 122 | On the **Secrets tab**, you can click the **Edit as JSON** button and paste the following JSON (do not forget to update the keys values): 123 | 124 | ![CleanShot 2023-05-25 at 14.32.30.png](images/CleanShot_2023-05-25_at_14.32.30.png) 125 | 126 | ``` 127 | { 128 | "DISCORD_TOKEN": "XXX", 129 | "APP_ID": "XXX", 130 | "SERVER_ID": "XXX", 131 | "VOICEFLOW_API_URL": "https://general-runtime.voiceflow.com", 132 | "VOICEFLOW_API_KEY": "VF.DM.XXX", 133 | "VOICEFLOW_VERSION_ID": "XXX", 134 | "VOICEFLOW_PROJECT_ID": "XXX", 135 | "LIVEANSWERS_CHANNELS": "XXX,XXX,XXX", 136 | "THREADS": "true" 137 | } 138 | 139 | ``` 140 | 141 | ### Run your app on Replit 142 | 143 | Once forked and updated with the Secrets, run your app and check the Console 144 | 145 | 146 | ![CleanShot 2023-05-25 at 14.21.43.png](images/CleanShot_2023-05-25_at_14.21.43.png) 147 | 148 | ![CleanShot 2023-05-25 at 14.21.16.png](images/CleanShot_2023-05-25_at_14.21.16.png) 149 | 150 | ## Videos 151 | 152 | If you are a bit curious and want to dive in the code, we’ve made a video to go over the Node JS app and the different ways to interact with the Discord bot. 153 | 154 | [https://www.loom.com/share/58af327708104dfaaec6ffb11d62b271](https://www.loom.com/share/58af327708104dfaaec6ffb11d62b271) 155 | 156 | The following Youtube video is about the Live Answer update and some details on how we are using this custom integration on our end on Discord with Tico. 157 | 158 | [https://youtu.be/H1qtBgrE8g8](https://youtu.be/H1qtBgrE8g8) 159 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config() 2 | const fs = require('node:fs') 3 | const path = require('node:path') 4 | const { 5 | Client, 6 | Routes, 7 | Collection, 8 | GatewayIntentBits, 9 | Partials, 10 | } = require('discord.js') 11 | const { DISCORD_TOKEN, APP_ID, SERVER_ID } = process.env 12 | 13 | const client = new Client({ 14 | intents: [ 15 | GatewayIntentBits.GuildMessages, 16 | GatewayIntentBits.Guilds, 17 | GatewayIntentBits.GuildPresences, 18 | GatewayIntentBits.MessageContent, 19 | GatewayIntentBits.GuildMembers, 20 | GatewayIntentBits.DirectMessages, 21 | ], 22 | partials: [Partials.Channel], 23 | rest: { version: '10' }, 24 | }) 25 | 26 | const commands = [] 27 | client.commands = new Collection() 28 | const commandsPath = path.join(__dirname, 'commands') 29 | const commandFiles = fs 30 | .readdirSync(commandsPath) 31 | .filter((file) => file.endsWith('.js')) 32 | 33 | for (const file of commandFiles) { 34 | const filePath = path.join(commandsPath, file) 35 | const command = require(filePath) 36 | commands.push(command.data.toJSON()) 37 | if ('data' in command && 'execute' in command) { 38 | client.commands.set(command.data.name, command) 39 | } else { 40 | console.log( 41 | `[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.` 42 | ) 43 | } 44 | } 45 | 46 | const eventsPath = path.join(__dirname, 'events') 47 | const eventFiles = fs 48 | .readdirSync(eventsPath) 49 | .filter((file) => file.endsWith('.js')) 50 | 51 | for (const file of eventFiles) { 52 | const filePath = path.join(eventsPath, file) 53 | const event = require(filePath) 54 | if (event.once) { 55 | client.once(event.name, (...args) => event.execute(...args)) 56 | } else { 57 | client.on(event.name, (...args) => event.execute(...args)) 58 | } 59 | } 60 | 61 | client.rest.setToken(DISCORD_TOKEN) 62 | 63 | async function main() { 64 | try { 65 | console.log( 66 | `Started refreshing ${commands.length} application (/) commands.` 67 | ) 68 | 69 | const data = await client.rest.put( 70 | Routes.applicationGuildCommands(APP_ID, SERVER_ID), 71 | { body: commands } 72 | ) 73 | 74 | console.log( 75 | `Successfully reloaded ${data.length} application (/) commands.` 76 | ) 77 | await client.login(DISCORD_TOKEN) 78 | } catch (err) { 79 | console.log(err) 80 | } 81 | } 82 | 83 | main() 84 | -------------------------------------------------------------------------------- /commands/hello.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder } = require('discord.js') 2 | 3 | module.exports = { 4 | data: new SlashCommandBuilder() 5 | .setName('hello') 6 | .setDescription('Say Hi and provides information about the user.'), 7 | async execute(interaction) { 8 | await interaction.deferReply({ ephemeral: true }) 9 | await interaction.user 10 | .send(`Hey ${interaction.user.username}!`) 11 | .catch((error) => { 12 | console.log(`Could not send DM to ${interaction.user.tag}.`) 13 | console.error(error) 14 | }) 15 | await interaction.editReply({ 16 | content: `You can start talking with me in DM.`, 17 | ephemeral: true, 18 | }) 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /commands/image.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder } = require('discord.js') 2 | const { interact } = require('../utils/dialogapi.js') 3 | 4 | module.exports = { 5 | data: new SlashCommandBuilder() 6 | .setName('image') 7 | .setDescription('Use DALL-E to generate a random image.'), 8 | 9 | async execute(interaction) { 10 | await interaction.reply({ 11 | content: 'Generating your image...', 12 | ephemeral: true, 13 | fetchReply: true, 14 | }) 15 | await interact(interaction, interaction.user.id, true) 16 | await interaction.editReply('Done!') 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /commands/ping.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder } = require('discord.js') 2 | 3 | module.exports = { 4 | data: new SlashCommandBuilder() 5 | .setName('ping') 6 | .setDescription('Provides information about the bot.'), 7 | async execute(interaction) { 8 | const sent = await interaction.reply({ 9 | content: 'Pinging...', 10 | fetchReply: true, 11 | }) 12 | interaction.editReply( 13 | `Roundtrip latency: ${ 14 | sent.createdTimestamp - interaction.createdTimestamp 15 | }ms` 16 | ) 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /commands/question.js: -------------------------------------------------------------------------------- 1 | const { SlashCommandBuilder } = require('discord.js') 2 | const { interact } = require('../utils/dialogapi.js') 3 | 4 | module.exports = { 5 | data: new SlashCommandBuilder() 6 | .setName('ask') 7 | .setDescription('Ask Voiceflow Bot a question.') 8 | .addStringOption((option) => 9 | option 10 | .setName('question') 11 | .setDescription('User question to Voiceflow Bot.') 12 | .setRequired(true) 13 | ), 14 | 15 | async execute(interaction) { 16 | await interaction.deferReply({ ephemeral: true }) 17 | await interact(interaction, interaction.user.id, true) 18 | }, 19 | } 20 | -------------------------------------------------------------------------------- /events/interactionCreate.js: -------------------------------------------------------------------------------- 1 | const { Events } = require('discord.js') 2 | const { interact } = require('../utils/dialogapi.js') 3 | 4 | module.exports = { 5 | name: Events.InteractionCreate, 6 | async execute(interaction) { 7 | if (interaction.isChatInputCommand()) { 8 | const command = interaction.client.commands.get(interaction.commandName) 9 | 10 | if (!command) { 11 | console.error( 12 | `No command matching ${interaction.commandName} was found.` 13 | ) 14 | return 15 | } 16 | 17 | try { 18 | await command.execute(interaction) 19 | } catch (error) { 20 | console.error(`Error executing ${interaction.commandName}`) 21 | console.error(error) 22 | } 23 | } 24 | 25 | if (interaction.isButton()) { 26 | console.log('Button interaction!') 27 | 28 | await interaction.deferReply({ ephemeral: true }) 29 | 30 | try { 31 | await interact(interaction, interaction.user.id, true) 32 | } catch (error) { 33 | console.error('Error interacting with the API:', error) 34 | await interaction.followUp( 35 | 'There was an error interacting with the API.' 36 | ) 37 | } 38 | } 39 | }, 40 | } 41 | -------------------------------------------------------------------------------- /events/messageCreate.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config() 2 | const { Events } = require('discord.js') 3 | const { interact } = require('../utils/dialogapi.js') 4 | 5 | module.exports = { 6 | name: Events.MessageCreate, 7 | async execute(message) { 8 | if (message.author.bot) { 9 | return 10 | } 11 | 12 | if ( 13 | message.client.user.id === process.env.APP_ID && 14 | message.channel.type === 1 15 | ) { 16 | console.log('DM BOT') 17 | await interact(message, message.author.id, false) 18 | return 19 | } 20 | 21 | const mentionedUser = message.mentions.users.get(process.env.APP_ID) 22 | if (mentionedUser && mentionedUser.bot) { 23 | console.log('Mention BOT') 24 | await interact(message, message.author.id, false) 25 | return 26 | } 27 | 28 | if (message.content.includes('##secret##')) { 29 | message.author.send(`Hey ${message.author.username}!`).catch((error) => { 30 | console.log(`Could not send DM to ${message.author.tag}.`) 31 | console.error(error) 32 | }) 33 | return 34 | } 35 | 36 | if (process.env.LIVEANSWERS_CHANNELS.includes(message.channel.id)) { 37 | let liveAnswer = message 38 | liveAnswer.isLive = true 39 | const messageWithoutMention = message.content 40 | .replace(/^<@\!?(\d+)>/, '') 41 | .trim() 42 | 43 | await interact( 44 | liveAnswer, 45 | message.author.id, 46 | false, 47 | false, 48 | true, 49 | messageWithoutMention 50 | ) 51 | } 52 | }, 53 | } 54 | -------------------------------------------------------------------------------- /events/ready.js: -------------------------------------------------------------------------------- 1 | const { Events } = require('discord.js') 2 | 3 | module.exports = { 4 | name: Events.ClientReady, 5 | once: true, 6 | execute(client) { 7 | console.log(`Ready! Logged in as ${client.user.tag}`) 8 | }, 9 | } 10 | -------------------------------------------------------------------------------- /images/200x200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voiceflow-gallagan/voiceflow-discord/a06d35bd66840975c050f8d99dac8ada21baf80a/images/200x200.png -------------------------------------------------------------------------------- /images/CleanShot_2023-05-25_at_13.12.22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voiceflow-gallagan/voiceflow-discord/a06d35bd66840975c050f8d99dac8ada21baf80a/images/CleanShot_2023-05-25_at_13.12.22.png -------------------------------------------------------------------------------- /images/CleanShot_2023-05-25_at_13.12.46.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voiceflow-gallagan/voiceflow-discord/a06d35bd66840975c050f8d99dac8ada21baf80a/images/CleanShot_2023-05-25_at_13.12.46.png -------------------------------------------------------------------------------- /images/CleanShot_2023-05-25_at_13.20.54.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voiceflow-gallagan/voiceflow-discord/a06d35bd66840975c050f8d99dac8ada21baf80a/images/CleanShot_2023-05-25_at_13.20.54.png -------------------------------------------------------------------------------- /images/CleanShot_2023-05-25_at_13.21.58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voiceflow-gallagan/voiceflow-discord/a06d35bd66840975c050f8d99dac8ada21baf80a/images/CleanShot_2023-05-25_at_13.21.58.png -------------------------------------------------------------------------------- /images/CleanShot_2023-05-25_at_13.23.44.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voiceflow-gallagan/voiceflow-discord/a06d35bd66840975c050f8d99dac8ada21baf80a/images/CleanShot_2023-05-25_at_13.23.44.png -------------------------------------------------------------------------------- /images/CleanShot_2023-05-25_at_13.24.20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voiceflow-gallagan/voiceflow-discord/a06d35bd66840975c050f8d99dac8ada21baf80a/images/CleanShot_2023-05-25_at_13.24.20.png -------------------------------------------------------------------------------- /images/CleanShot_2023-05-25_at_13.28.23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voiceflow-gallagan/voiceflow-discord/a06d35bd66840975c050f8d99dac8ada21baf80a/images/CleanShot_2023-05-25_at_13.28.23.png -------------------------------------------------------------------------------- /images/CleanShot_2023-05-25_at_13.30.13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voiceflow-gallagan/voiceflow-discord/a06d35bd66840975c050f8d99dac8ada21baf80a/images/CleanShot_2023-05-25_at_13.30.13.png -------------------------------------------------------------------------------- /images/CleanShot_2023-05-25_at_13.33.26.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voiceflow-gallagan/voiceflow-discord/a06d35bd66840975c050f8d99dac8ada21baf80a/images/CleanShot_2023-05-25_at_13.33.26.png -------------------------------------------------------------------------------- /images/CleanShot_2023-05-25_at_13.34.21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voiceflow-gallagan/voiceflow-discord/a06d35bd66840975c050f8d99dac8ada21baf80a/images/CleanShot_2023-05-25_at_13.34.21.png -------------------------------------------------------------------------------- /images/CleanShot_2023-05-25_at_13.34.53.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voiceflow-gallagan/voiceflow-discord/a06d35bd66840975c050f8d99dac8ada21baf80a/images/CleanShot_2023-05-25_at_13.34.53.png -------------------------------------------------------------------------------- /images/CleanShot_2023-05-25_at_13.39.25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voiceflow-gallagan/voiceflow-discord/a06d35bd66840975c050f8d99dac8ada21baf80a/images/CleanShot_2023-05-25_at_13.39.25.png -------------------------------------------------------------------------------- /images/CleanShot_2023-05-25_at_13.41.28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voiceflow-gallagan/voiceflow-discord/a06d35bd66840975c050f8d99dac8ada21baf80a/images/CleanShot_2023-05-25_at_13.41.28.png -------------------------------------------------------------------------------- /images/CleanShot_2023-05-25_at_13.45.32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voiceflow-gallagan/voiceflow-discord/a06d35bd66840975c050f8d99dac8ada21baf80a/images/CleanShot_2023-05-25_at_13.45.32.png -------------------------------------------------------------------------------- /images/CleanShot_2023-05-25_at_14.21.16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voiceflow-gallagan/voiceflow-discord/a06d35bd66840975c050f8d99dac8ada21baf80a/images/CleanShot_2023-05-25_at_14.21.16.png -------------------------------------------------------------------------------- /images/CleanShot_2023-05-25_at_14.21.43.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voiceflow-gallagan/voiceflow-discord/a06d35bd66840975c050f8d99dac8ada21baf80a/images/CleanShot_2023-05-25_at_14.21.43.png -------------------------------------------------------------------------------- /images/CleanShot_2023-05-25_at_14.32.30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voiceflow-gallagan/voiceflow-discord/a06d35bd66840975c050f8d99dac8ada21baf80a/images/CleanShot_2023-05-25_at_14.32.30.png -------------------------------------------------------------------------------- /images/discord-logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voiceflow-gallagan/voiceflow-discord/a06d35bd66840975c050f8d99dac8ada21baf80a/images/discord-logo.jpg -------------------------------------------------------------------------------- /images/logotype.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voiceflow-gallagan/voiceflow-discord/a06d35bd66840975c050f8d99dac8ada21baf80a/images/logotype.webp -------------------------------------------------------------------------------- /images/test: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /images/voiceflow-copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voiceflow-gallagan/voiceflow-discord/a06d35bd66840975c050f8d99dac8ada21baf80a/images/voiceflow-copy.png -------------------------------------------------------------------------------- /images/voiceflow-integration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voiceflow-gallagan/voiceflow-discord/a06d35bd66840975c050f8d99dac8ada21baf80a/images/voiceflow-integration.png -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "voiceflow-discord", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "voiceflow-discord", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@discordjs/voice": "^0.16.0", 13 | "axios": "^1.4.0", 14 | "discord.js": "^14.11.0", 15 | "dotenv": "^16.0.3" 16 | } 17 | }, 18 | "node_modules/@discordjs/builders": { 19 | "version": "1.6.5", 20 | "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.6.5.tgz", 21 | "integrity": "sha512-SdweyCs/+mHj+PNhGLLle7RrRFX9ZAhzynHahMCLqp5Zeq7np7XC6/mgzHc79QoVlQ1zZtOkTTiJpOZu5V8Ufg==", 22 | "dependencies": { 23 | "@discordjs/formatters": "^0.3.2", 24 | "@discordjs/util": "^1.0.1", 25 | "@sapphire/shapeshift": "^3.9.2", 26 | "discord-api-types": "0.37.50", 27 | "fast-deep-equal": "^3.1.3", 28 | "ts-mixer": "^6.0.3", 29 | "tslib": "^2.6.1" 30 | }, 31 | "engines": { 32 | "node": ">=16.11.0" 33 | } 34 | }, 35 | "node_modules/@discordjs/builders/node_modules/discord-api-types": { 36 | "version": "0.37.50", 37 | "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.50.tgz", 38 | "integrity": "sha512-X4CDiMnDbA3s3RaUXWXmgAIbY1uxab3fqe3qwzg5XutR3wjqi7M3IkgQbsIBzpqBN2YWr/Qdv7JrFRqSgb4TFg==" 39 | }, 40 | "node_modules/@discordjs/collection": { 41 | "version": "1.5.3", 42 | "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz", 43 | "integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==", 44 | "engines": { 45 | "node": ">=16.11.0" 46 | } 47 | }, 48 | "node_modules/@discordjs/formatters": { 49 | "version": "0.3.2", 50 | "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.3.2.tgz", 51 | "integrity": "sha512-lE++JZK8LSSDRM5nLjhuvWhGuKiXqu+JZ/DsOR89DVVia3z9fdCJVcHF2W/1Zxgq0re7kCzmAJlCMMX3tetKpA==", 52 | "dependencies": { 53 | "discord-api-types": "0.37.50" 54 | }, 55 | "engines": { 56 | "node": ">=16.11.0" 57 | } 58 | }, 59 | "node_modules/@discordjs/formatters/node_modules/discord-api-types": { 60 | "version": "0.37.50", 61 | "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.50.tgz", 62 | "integrity": "sha512-X4CDiMnDbA3s3RaUXWXmgAIbY1uxab3fqe3qwzg5XutR3wjqi7M3IkgQbsIBzpqBN2YWr/Qdv7JrFRqSgb4TFg==" 63 | }, 64 | "node_modules/@discordjs/rest": { 65 | "version": "2.0.1", 66 | "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.0.1.tgz", 67 | "integrity": "sha512-/eWAdDRvwX/rIE2tuQUmKaxmWeHmGealttIzGzlYfI4+a7y9b6ZoMp8BG/jaohs8D8iEnCNYaZiOFLVFLQb8Zg==", 68 | "dependencies": { 69 | "@discordjs/collection": "^1.5.3", 70 | "@discordjs/util": "^1.0.1", 71 | "@sapphire/async-queue": "^1.5.0", 72 | "@sapphire/snowflake": "^3.5.1", 73 | "@vladfrangu/async_event_emitter": "^2.2.2", 74 | "discord-api-types": "0.37.50", 75 | "magic-bytes.js": "^1.0.15", 76 | "tslib": "^2.6.1", 77 | "undici": "5.22.1" 78 | }, 79 | "engines": { 80 | "node": ">=16.11.0" 81 | } 82 | }, 83 | "node_modules/@discordjs/rest/node_modules/discord-api-types": { 84 | "version": "0.37.50", 85 | "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.50.tgz", 86 | "integrity": "sha512-X4CDiMnDbA3s3RaUXWXmgAIbY1uxab3fqe3qwzg5XutR3wjqi7M3IkgQbsIBzpqBN2YWr/Qdv7JrFRqSgb4TFg==" 87 | }, 88 | "node_modules/@discordjs/util": { 89 | "version": "1.0.1", 90 | "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.0.1.tgz", 91 | "integrity": "sha512-d0N2yCxB8r4bn00/hvFZwM7goDcUhtViC5un4hPj73Ba4yrChLSJD8fy7Ps5jpTLg1fE9n4K0xBLc1y9WGwSsA==", 92 | "engines": { 93 | "node": ">=16.11.0" 94 | } 95 | }, 96 | "node_modules/@discordjs/voice": { 97 | "version": "0.16.0", 98 | "resolved": "https://registry.npmjs.org/@discordjs/voice/-/voice-0.16.0.tgz", 99 | "integrity": "sha512-ToGCvHD1cBscuW3p+C7zOF5+L7MJmU4GjdOARfNk9mkHyFFZq4grK+Sxr3QXKbp27DtfDBc9uqD4GUOYgxngfA==", 100 | "dependencies": { 101 | "@types/ws": "^8.5.4", 102 | "discord-api-types": "^0.37.37", 103 | "prism-media": "^1.3.5", 104 | "tslib": "^2.5.0", 105 | "ws": "^8.13.0" 106 | }, 107 | "engines": { 108 | "node": ">=16.9.0" 109 | } 110 | }, 111 | "node_modules/@discordjs/ws": { 112 | "version": "1.0.1", 113 | "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.0.1.tgz", 114 | "integrity": "sha512-avvAolBqN3yrSvdBPcJ/0j2g42ABzrv3PEL76e3YTp2WYMGH7cuspkjfSyNWaqYl1J+669dlLp+YFMxSVQyS5g==", 115 | "dependencies": { 116 | "@discordjs/collection": "^1.5.3", 117 | "@discordjs/rest": "^2.0.1", 118 | "@discordjs/util": "^1.0.1", 119 | "@sapphire/async-queue": "^1.5.0", 120 | "@types/ws": "^8.5.5", 121 | "@vladfrangu/async_event_emitter": "^2.2.2", 122 | "discord-api-types": "0.37.50", 123 | "tslib": "^2.6.1", 124 | "ws": "^8.13.0" 125 | }, 126 | "engines": { 127 | "node": ">=16.11.0" 128 | } 129 | }, 130 | "node_modules/@discordjs/ws/node_modules/discord-api-types": { 131 | "version": "0.37.50", 132 | "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.50.tgz", 133 | "integrity": "sha512-X4CDiMnDbA3s3RaUXWXmgAIbY1uxab3fqe3qwzg5XutR3wjqi7M3IkgQbsIBzpqBN2YWr/Qdv7JrFRqSgb4TFg==" 134 | }, 135 | "node_modules/@sapphire/async-queue": { 136 | "version": "1.5.0", 137 | "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.0.tgz", 138 | "integrity": "sha512-JkLdIsP8fPAdh9ZZjrbHWR/+mZj0wvKS5ICibcLrRI1j84UmLMshx5n9QmL8b95d4onJ2xxiyugTgSAX7AalmA==", 139 | "engines": { 140 | "node": ">=v14.0.0", 141 | "npm": ">=7.0.0" 142 | } 143 | }, 144 | "node_modules/@sapphire/shapeshift": { 145 | "version": "3.9.2", 146 | "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-3.9.2.tgz", 147 | "integrity": "sha512-YRbCXWy969oGIdqR/wha62eX8GNHsvyYi0Rfd4rNW6tSVVa8p0ELiMEuOH/k8rgtvRoM+EMV7Csqz77YdwiDpA==", 148 | "dependencies": { 149 | "fast-deep-equal": "^3.1.3", 150 | "lodash": "^4.17.21" 151 | }, 152 | "engines": { 153 | "node": ">=v14.0.0", 154 | "npm": ">=7.0.0" 155 | } 156 | }, 157 | "node_modules/@sapphire/snowflake": { 158 | "version": "3.5.1", 159 | "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.1.tgz", 160 | "integrity": "sha512-BxcYGzgEsdlG0dKAyOm0ehLGm2CafIrfQTZGWgkfKYbj+pNNsorZ7EotuZukc2MT70E0UbppVbtpBrqpzVzjNA==", 161 | "engines": { 162 | "node": ">=v14.0.0", 163 | "npm": ">=7.0.0" 164 | } 165 | }, 166 | "node_modules/@types/node": { 167 | "version": "20.5.7", 168 | "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.7.tgz", 169 | "integrity": "sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA==" 170 | }, 171 | "node_modules/@types/ws": { 172 | "version": "8.5.5", 173 | "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.5.tgz", 174 | "integrity": "sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==", 175 | "dependencies": { 176 | "@types/node": "*" 177 | } 178 | }, 179 | "node_modules/@vladfrangu/async_event_emitter": { 180 | "version": "2.2.2", 181 | "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.2.2.tgz", 182 | "integrity": "sha512-HIzRG7sy88UZjBJamssEczH5q7t5+axva19UbZLO6u0ySbYPrwzWiXBcC0WuHyhKKoeCyneH+FvYzKQq/zTtkQ==", 183 | "engines": { 184 | "node": ">=v14.0.0", 185 | "npm": ">=7.0.0" 186 | } 187 | }, 188 | "node_modules/asynckit": { 189 | "version": "0.4.0", 190 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 191 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" 192 | }, 193 | "node_modules/axios": { 194 | "version": "1.5.0", 195 | "resolved": "https://registry.npmjs.org/axios/-/axios-1.5.0.tgz", 196 | "integrity": "sha512-D4DdjDo5CY50Qms0qGQTTw6Q44jl7zRwY7bthds06pUGfChBCTcQs+N743eFWGEd6pRTMd6A+I87aWyFV5wiZQ==", 197 | "dependencies": { 198 | "follow-redirects": "^1.15.0", 199 | "form-data": "^4.0.0", 200 | "proxy-from-env": "^1.1.0" 201 | } 202 | }, 203 | "node_modules/busboy": { 204 | "version": "1.6.0", 205 | "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", 206 | "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", 207 | "dependencies": { 208 | "streamsearch": "^1.1.0" 209 | }, 210 | "engines": { 211 | "node": ">=10.16.0" 212 | } 213 | }, 214 | "node_modules/combined-stream": { 215 | "version": "1.0.8", 216 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 217 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 218 | "dependencies": { 219 | "delayed-stream": "~1.0.0" 220 | }, 221 | "engines": { 222 | "node": ">= 0.8" 223 | } 224 | }, 225 | "node_modules/delayed-stream": { 226 | "version": "1.0.0", 227 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 228 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 229 | "engines": { 230 | "node": ">=0.4.0" 231 | } 232 | }, 233 | "node_modules/discord-api-types": { 234 | "version": "0.37.55", 235 | "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.55.tgz", 236 | "integrity": "sha512-XIf8AELxg76AH1WzrgB5ziFyIxSs4tLK1XGjFs4oE6t7M7r1B0Chdylaz76ALhvRXAUBGHGFXu/pKAa5Q+LbQg==" 237 | }, 238 | "node_modules/discord.js": { 239 | "version": "14.13.0", 240 | "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.13.0.tgz", 241 | "integrity": "sha512-Kufdvg7fpyTEwANGy9x7i4od4yu5c6gVddGi5CKm4Y5a6sF0VBODObI3o0Bh7TGCj0LfNT8Qp8z04wnLFzgnbA==", 242 | "dependencies": { 243 | "@discordjs/builders": "^1.6.5", 244 | "@discordjs/collection": "^1.5.3", 245 | "@discordjs/formatters": "^0.3.2", 246 | "@discordjs/rest": "^2.0.1", 247 | "@discordjs/util": "^1.0.1", 248 | "@discordjs/ws": "^1.0.1", 249 | "@sapphire/snowflake": "^3.5.1", 250 | "@types/ws": "^8.5.5", 251 | "discord-api-types": "0.37.50", 252 | "fast-deep-equal": "^3.1.3", 253 | "lodash.snakecase": "^4.1.1", 254 | "tslib": "^2.6.1", 255 | "undici": "5.22.1", 256 | "ws": "^8.13.0" 257 | }, 258 | "engines": { 259 | "node": ">=16.11.0" 260 | } 261 | }, 262 | "node_modules/discord.js/node_modules/discord-api-types": { 263 | "version": "0.37.50", 264 | "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.50.tgz", 265 | "integrity": "sha512-X4CDiMnDbA3s3RaUXWXmgAIbY1uxab3fqe3qwzg5XutR3wjqi7M3IkgQbsIBzpqBN2YWr/Qdv7JrFRqSgb4TFg==" 266 | }, 267 | "node_modules/dotenv": { 268 | "version": "16.3.1", 269 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", 270 | "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", 271 | "engines": { 272 | "node": ">=12" 273 | }, 274 | "funding": { 275 | "url": "https://github.com/motdotla/dotenv?sponsor=1" 276 | } 277 | }, 278 | "node_modules/fast-deep-equal": { 279 | "version": "3.1.3", 280 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 281 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" 282 | }, 283 | "node_modules/follow-redirects": { 284 | "version": "1.15.2", 285 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", 286 | "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", 287 | "funding": [ 288 | { 289 | "type": "individual", 290 | "url": "https://github.com/sponsors/RubenVerborgh" 291 | } 292 | ], 293 | "engines": { 294 | "node": ">=4.0" 295 | }, 296 | "peerDependenciesMeta": { 297 | "debug": { 298 | "optional": true 299 | } 300 | } 301 | }, 302 | "node_modules/form-data": { 303 | "version": "4.0.0", 304 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", 305 | "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", 306 | "dependencies": { 307 | "asynckit": "^0.4.0", 308 | "combined-stream": "^1.0.8", 309 | "mime-types": "^2.1.12" 310 | }, 311 | "engines": { 312 | "node": ">= 6" 313 | } 314 | }, 315 | "node_modules/lodash": { 316 | "version": "4.17.21", 317 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 318 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" 319 | }, 320 | "node_modules/lodash.snakecase": { 321 | "version": "4.1.1", 322 | "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", 323 | "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==" 324 | }, 325 | "node_modules/magic-bytes.js": { 326 | "version": "1.0.15", 327 | "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.0.15.tgz", 328 | "integrity": "sha512-bpRmwbRHqongRhA+mXzbLWjVy7ylqmfMBYaQkSs6pac0z6hBTvsgrH0r4FBYd/UYVJBmS6Rp/O+oCCQVLzKV1g==" 329 | }, 330 | "node_modules/mime-db": { 331 | "version": "1.52.0", 332 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 333 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 334 | "engines": { 335 | "node": ">= 0.6" 336 | } 337 | }, 338 | "node_modules/mime-types": { 339 | "version": "2.1.35", 340 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 341 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 342 | "dependencies": { 343 | "mime-db": "1.52.0" 344 | }, 345 | "engines": { 346 | "node": ">= 0.6" 347 | } 348 | }, 349 | "node_modules/prism-media": { 350 | "version": "1.3.5", 351 | "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.3.5.tgz", 352 | "integrity": "sha512-IQdl0Q01m4LrkN1EGIE9lphov5Hy7WWlH6ulf5QdGePLlPas9p2mhgddTEHrlaXYjjFToM1/rWuwF37VF4taaA==", 353 | "peerDependencies": { 354 | "@discordjs/opus": ">=0.8.0 <1.0.0", 355 | "ffmpeg-static": "^5.0.2 || ^4.2.7 || ^3.0.0 || ^2.4.0", 356 | "node-opus": "^0.3.3", 357 | "opusscript": "^0.0.8" 358 | }, 359 | "peerDependenciesMeta": { 360 | "@discordjs/opus": { 361 | "optional": true 362 | }, 363 | "ffmpeg-static": { 364 | "optional": true 365 | }, 366 | "node-opus": { 367 | "optional": true 368 | }, 369 | "opusscript": { 370 | "optional": true 371 | } 372 | } 373 | }, 374 | "node_modules/proxy-from-env": { 375 | "version": "1.1.0", 376 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", 377 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" 378 | }, 379 | "node_modules/streamsearch": { 380 | "version": "1.1.0", 381 | "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", 382 | "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", 383 | "engines": { 384 | "node": ">=10.0.0" 385 | } 386 | }, 387 | "node_modules/ts-mixer": { 388 | "version": "6.0.3", 389 | "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.3.tgz", 390 | "integrity": "sha512-k43M7uCG1AkTyxgnmI5MPwKoUvS/bRvLvUb7+Pgpdlmok8AoqmUaZxUUw8zKM5B1lqZrt41GjYgnvAi0fppqgQ==" 391 | }, 392 | "node_modules/tslib": { 393 | "version": "2.6.2", 394 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", 395 | "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" 396 | }, 397 | "node_modules/undici": { 398 | "version": "5.22.1", 399 | "resolved": "https://registry.npmjs.org/undici/-/undici-5.22.1.tgz", 400 | "integrity": "sha512-Ji2IJhFXZY0x/0tVBXeQwgPlLWw13GVzpsWPQ3rV50IFMMof2I55PZZxtm4P6iNq+L5znYN9nSTAq0ZyE6lSJw==", 401 | "dependencies": { 402 | "busboy": "^1.6.0" 403 | }, 404 | "engines": { 405 | "node": ">=14.0" 406 | } 407 | }, 408 | "node_modules/ws": { 409 | "version": "8.13.0", 410 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", 411 | "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", 412 | "engines": { 413 | "node": ">=10.0.0" 414 | }, 415 | "peerDependencies": { 416 | "bufferutil": "^4.0.1", 417 | "utf-8-validate": ">=5.0.2" 418 | }, 419 | "peerDependenciesMeta": { 420 | "bufferutil": { 421 | "optional": true 422 | }, 423 | "utf-8-validate": { 424 | "optional": true 425 | } 426 | } 427 | } 428 | } 429 | } 430 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "voiceflow-discord", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node app.js" 9 | }, 10 | "author": "NiKo | Voiceflow", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@discordjs/voice": "^0.16.0", 14 | "axios": "^1.4.0", 15 | "discord.js": "^14.11.0", 16 | "dotenv": "^16.0.3" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /replit.nix: -------------------------------------------------------------------------------- 1 | { pkgs }: { 2 | deps = [ 3 | pkgs.nodejs-18_x 4 | pkgs.nodePackages.typescript-language-server 5 | pkgs.yarn 6 | pkgs.replitPackages.jest 7 | ]; 8 | } 9 | -------------------------------------------------------------------------------- /utils/dialogapi.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios') 2 | require('dotenv').config() 3 | const { 4 | ActionRowBuilder, 5 | ButtonBuilder, 6 | ButtonStyle, 7 | EmbedBuilder, 8 | } = require('discord.js') 9 | 10 | let noReplyTimeout = null 11 | const versionID = process.env.VOICEFLOW_VERSION_ID || 'development' 12 | const projectID = process.env.VOICEFLOW_PROJECT_ID || null 13 | let session = `${versionID}.${createSession()}` 14 | 15 | module.exports = { 16 | interact: async (interaction, user, isFollow, isDM, isLive, threadTitle) => { 17 | let action 18 | if (interaction?.customId?.includes('path-')) { 19 | action = { 20 | type: interaction.customId, 21 | payload: { 22 | actions: [], 23 | }, 24 | } 25 | } else if (interaction?.customId?.includes('intent-')) { 26 | let intentName = interaction.customId.replace('intent-', '') 27 | action = { 28 | type: 'intent', 29 | payload: { 30 | actions: [], 31 | entities: [], 32 | intent: { 33 | name: `${intentName}`, 34 | }, 35 | }, 36 | } 37 | } else if (interaction?.isLive === true) { 38 | isLive = true 39 | 40 | const messageWithoutMention = interaction.content 41 | .replace(/^<@\!?(\d+)>/, '') 42 | .trim() 43 | 44 | action = { 45 | type: 'intent', 46 | payload: { 47 | intent: { 48 | name: 'live_answer', 49 | }, 50 | query: messageWithoutMention, 51 | entities: [], 52 | }, 53 | } 54 | } else if (interaction?.options?.getString('question')) { 55 | const question = interaction.options.getString('question') ?? '' 56 | console.log('Type QUESTION:', question) 57 | action = { 58 | type: 'text', 59 | payload: question, 60 | } 61 | } else if (interaction?.commandName == 'image') { 62 | console.log('DALL-E') 63 | action = { 64 | type: 'intent', 65 | payload: { 66 | actions: [], 67 | entities: [], 68 | intent: { 69 | name: 'Generate Image', 70 | }, 71 | }, 72 | } 73 | } else { 74 | console.log('Type TEXT') 75 | const messageWithoutMention = interaction.content 76 | .replace(/^<@\!?(\d+)>/, '') 77 | .trim() 78 | console.log(messageWithoutMention) 79 | action = { 80 | type: 'text', 81 | payload: messageWithoutMention, 82 | } 83 | } 84 | await dialogAPI( 85 | interaction, 86 | user, 87 | isFollow, 88 | isDM, 89 | action, 90 | isLive, 91 | threadTitle 92 | ) 93 | }, 94 | } 95 | 96 | async function saveData(user, username, isDM) { 97 | const response = await axios.patch( 98 | `${process.env.VOICEFLOW_API_URL}/state/user/${user}/variables`, 99 | { userName: username, isDM: isDM }, 100 | { 101 | headers: { 102 | Authorization: process.env.VOICEFLOW_API_KEY, 103 | 'Content-Type': 'application/json', 104 | versionID: versionID, 105 | }, 106 | } 107 | ) 108 | return 109 | } 110 | 111 | async function dialogAPI( 112 | interaction, 113 | user, 114 | isFollow, 115 | isDM, 116 | action, 117 | isLive, 118 | threadTitle 119 | ) { 120 | clearTimeout(noReplyTimeout) 121 | let username = '' 122 | if (!isDM) { 123 | isDM = false 124 | } 125 | try { 126 | username = interaction.user.username 127 | } catch (error) { 128 | username = interaction.author.username 129 | } 130 | 131 | await saveData(user, username, isDM) 132 | 133 | const response = await axios.post( 134 | `${process.env.VOICEFLOW_API_URL}/state/user/${user}/interact`, 135 | { 136 | action: action, 137 | config: { 138 | tts: false, 139 | stripSSML: true, 140 | stopAll: true, 141 | excludeTypes: ['path', 'debug', 'flow', 'block'], 142 | }, 143 | }, 144 | { 145 | headers: { 146 | Authorization: process.env.VOICEFLOW_API_KEY, 147 | 'Content-Type': 'application/json', 148 | sessionid: session, 149 | }, 150 | } 151 | ) 152 | 153 | if (response.data.length != 0) { 154 | let isEnding = response.data.some((item) => item.type === 'end') 155 | const noReply = getNoReplyTimeout(response.data) 156 | 157 | for (const trace of response.data) { 158 | if (trace.type === 'text') { 159 | if (isFollow) { 160 | await interaction.followUp({ 161 | content: trace.payload.message, 162 | ephemeral: true, 163 | }) 164 | } else if (isLive == true && process.env.THREADS == 'true') { 165 | // Create a new thread from the message 166 | interaction 167 | .startThread({ 168 | name: threadTitle, 169 | autoArchiveDuration: 10080, // Duration until the thread is archived in minutes, it can be '60', '1440', '4320', '10080' 170 | }) 171 | .then((newThread) => { 172 | newThread.send(trace.payload.message) 173 | }) 174 | .catch(console.error) 175 | } else { 176 | await interaction.reply({ 177 | content: trace.payload.message, 178 | ephemeral: true, 179 | }) 180 | } 181 | } else if (trace.type === 'choice') { 182 | const actionRow = new ActionRowBuilder() 183 | trace.payload.buttons.forEach((button, index) => { 184 | if (index < 5) { 185 | let customId = button.request.type 186 | if (button.request.type == 'intent') { 187 | customId = `intent-${button.request.payload.intent.name}` 188 | } 189 | actionRow.addComponents( 190 | new ButtonBuilder() 191 | .setCustomId(customId) 192 | .setLabel(button.name) 193 | .setStyle(ButtonStyle.Primary) 194 | .setDisabled(false) 195 | ) 196 | } 197 | }) 198 | if (isFollow) { 199 | await interaction.followUp({ 200 | components: [actionRow], 201 | ephemeral: true, 202 | }) 203 | } else { 204 | await interaction.reply({ 205 | components: [actionRow], 206 | ephemeral: true, 207 | }) 208 | } 209 | } else if ( 210 | trace.type === 'visual' && 211 | trace.payload.visualType === 'image' 212 | ) { 213 | const embed = new EmbedBuilder() 214 | .setImage(trace.payload.image) 215 | .setTitle('Image') 216 | 217 | if (isFollow) { 218 | await interaction.followUp({ embeds: [embed], ephemeral: true }) 219 | } else { 220 | await interaction.reply({ embeds: [embed], ephemeral: true }) 221 | } 222 | } 223 | } 224 | if (noReply && isEnding === false) { 225 | noReplyTimeout = setTimeout(async () => { 226 | action = { 227 | type: 'no-reply', 228 | } 229 | await dialogAPI(interaction, user, true, action) 230 | }, noReply) 231 | } 232 | if (isEnding === true) { 233 | // an end trace means the the Voiceflow dialog has ended 234 | 235 | let userName = null 236 | let avatarURL = 237 | 'https://s3.amazonaws.com/com.voiceflow.studio/share/200x200/200x200.png' 238 | try { 239 | userName = interaction.user.username 240 | avatarURL = interaction.user.displayAvatarURL({ 241 | format: 'png', 242 | dynamic: true, 243 | size: 1024, 244 | }) 245 | } catch { 246 | userName = interaction.author.username 247 | avatarURL = interaction.author.displayAvatarURL({ 248 | format: 'png', 249 | dynamic: true, 250 | size: 1024, 251 | }) 252 | } 253 | await saveTranscript(userName, avatarURL) 254 | } 255 | } 256 | } 257 | 258 | function getNoReplyTimeout(arr) { 259 | const noReplyItem = arr.find((item) => item.type === 'no-reply') 260 | if (noReplyItem) { 261 | return noReplyItem.payload.timeout * 1000 262 | } else { 263 | return null 264 | } 265 | } 266 | 267 | function createSession() { 268 | // Random Number Generator 269 | var randomNo = Math.floor(Math.random() * 1000 + 1) 270 | // get Timestamp 271 | var timestamp = Date.now() 272 | // get Day 273 | var date = new Date() 274 | var weekday = new Array(7) 275 | weekday[0] = 'Sunday' 276 | weekday[1] = 'Monday' 277 | weekday[2] = 'Tuesday' 278 | weekday[3] = 'Wednesday' 279 | weekday[4] = 'Thursday' 280 | weekday[5] = 'Friday' 281 | weekday[6] = 'Saturday' 282 | var day = weekday[date.getDay()] 283 | // Join random number+day+timestamp 284 | var session_id = randomNo + day + timestamp 285 | return session_id 286 | } 287 | 288 | async function saveTranscript(username, userpix) { 289 | if (projectID) { 290 | console.log('SAVE TRANSCRIPT') 291 | if (!username || username == '' || username == undefined) { 292 | username = 'Anonymous' 293 | } 294 | axios({ 295 | method: 'put', 296 | url: 'https://api.voiceflow.com/v2/transcripts', 297 | data: { 298 | browser: 'Discord', 299 | device: 'desktop', 300 | os: 'macOS', 301 | sessionID: session, 302 | unread: true, 303 | versionID: versionID, 304 | projectID: projectID, 305 | user: { 306 | name: username, 307 | image: userpix, 308 | }, 309 | }, 310 | headers: { 311 | Authorization: process.env.VOICEFLOW_API_KEY, 312 | }, 313 | }) 314 | .then(function (response) { 315 | console.log('Saved!') 316 | session = `${process.env.VOICEFLOW_VERSION_ID}.${createSession()}` 317 | }) 318 | .catch((err) => console.log(err)) 319 | } 320 | } 321 | --------------------------------------------------------------------------------