├── .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 | 
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 | 
20 |
21 | Name your application and click “**Create**”
22 |
23 | 
24 |
25 | From the General Information tab, copy the **APPLICATION ID** and **save it for later**
26 |
27 | 
28 |
29 | On the Bot tab, generate a Token by clicking on **Reset Token** button
30 |
31 | 
32 |
33 | Copy the newly created token and **save it for later**
34 |
35 | 
36 |
37 | Scroll down and toggle **PRESENCE INTENT**, **SERVER MEMBERS INTENT** and **MESSAGE CONTENT INTENT**. Do not forget to save your changes.
38 |
39 | 
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 | 
46 |
47 |
48 |
49 | Open this link in a new tab and add the bot to **your Discord server**
50 |
51 | 
52 |
53 | 
54 |
55 | 
56 |
57 | On your Discord server you should now see the bot in the Users tab and a new message
58 |
59 | 
60 |
61 | 
62 |
63 | If you haven’t activated the Developer Mode already, do it by going to the settings: **APP SETTINGS** > **Advanced**
64 |
65 | 
66 |
67 | **Right click** on your server icon in the left sidebar, click on **Copy Server ID** and **save it for later**.
68 |
69 | 
70 |
71 | > You should now have:
72 | an app key
73 | a bot token
74 | a server id
75 | >
76 |
77 | 
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 | 
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 | 
91 |
92 | 
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 | 
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 | 
147 |
148 | 
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 |
--------------------------------------------------------------------------------