Building a Discord Bot with Node-RED and OpenAI for Seamless User Interaction

We'll walk you through how to create a smart and interactive Discord bot using Node-RED and OpenAI. Our bot listens to user commands, processes them with OpenAI's powerful language model, and responds intelligently in your Discord channel. Let's dive into how all of this comes together!

Extra node used:

  • @inductiv/node-red-openai-api
  • node-red-contrib-discord-advanced

Overview

Our flow is designed to accomplish these tasks:
  • Listen to messages in a specific Discord channel (e.g., "support").
  • Identify when users are addressing the bot.
  • Manage conversations intelligently, keeping track of new and ongoing sessions.
  • Use OpenAI to generate responses to user queries.
  • Handle different statuses of the conversation to ensure smooth interaction.
Nodes Breakdown

Here's a peek into the main components of our Node-RED flow:

Listening to Discord Messages:
  • discordMessage (My Discord Bot): This node picks up every message in the Discord server. It’s our bot's ears in the support channel.
Filtering Relevant Messages:
  • switch (name of the discord channel): This node verifies that messages are from the 'support' channel.
  • switch (Filter Messages for Bot Response): This smart filter ensures only messages directed at the bot (e.g., containing "Hey Bot") are processed further.
Processing Messages:
  • template (json for role and content): Prepares the message content for our AI assistant by creating a JSON structure.
  • change nodes: Assist in setting necessary fields and managing session IDs. OpenAI API Integration Nodes: listAssistants: Fetches available assistants from OpenAI. createThreadAndRun: Initiates a conversation thread with OpenAI. createMessage: Sends the user’s message to OpenAI. retrieveRun: Checks the status of the conversation.
Handling Conversations:
  • switch (session exist or not?) and switch (new session or continue?): These nodes manage whether the conversation is a new one or an ongoing session.
  • function nodes: These craft the necessary payloads for each step and manage conversation threads.
Responding to Users:
  • discordMessageManager: Sends OpenAI’s responses back to the user in Discord.
  • discordTyping: Simulates typing status to let users know the bot is processing their request.
How It All Connects

Initialization:

  • The flow starts when a message is detected in the Discord 'support' channel.
  • The switch nodes filter out irrelevant messages and ensure only commands for the bot are processed.
Session Management:
  • If it’s a brand-new conversation, the bot kicks off a new session with OpenAI using the createThreadAndRun node.
  • For ongoing conversations, the bot retrieves the existing thread and adds to it. This is managed by the switch (session exist or not?) node.
Creating and Sending Messages:
  • The template node structures the user’s input, and createMessage sends it to OpenAI. The status of this message is tracked using retrieveRun, and based on the status (queued, in progress, completed), appropriate actions are taken. Generating Responses:

  • OpenAI processes the input, and the bot fetches the response with listMessages. To handle Discord's message limits, messages longer than 2000 characters are split and sent in chunks by the splitLongMessages function.

User Feedback:
  • Finally, discordMessageManager ensures the bot’s response reaches the user, and discordTyping simulates real-time typing, enhancing the user experience.
Example Walkthrough

Imagine a user types "Hey Bot, what’s the weather today?" in the 'support' channel:

Listening and Filtering:
  • The bot hears the message and passes it through several filters to confirm it's a valid command.
Session Handling:
  • If the user has talked to the bot before, the session continues. If not, a new session starts.
Interaction with OpenAI:

The bot sends the query to OpenAI, and OpenAI processes the request.

Response Generation:
  • OpenAI's response is fetched and verified. If the message is too long, it's split into manageable parts.
Sending the Response:

The bot replies in the Discord channel, saying "The weather looks sunny today!" while showing typing status to keep the user in the loop.

Setting up the Discord Bot

More information how to setup Discord can be found here: https://apivis.com/discordbot

Conclusion

With this Node-RED flow, integrating a smart Discord bot with OpenAI becomes a breeze. It not only handles messages and manages sessions but also provides real-time responses, making your Discord server interactive and engaging. Give this setup a try and see how it transforms your user interactions!

[{"id":"95c94e0b20b62cc5","type":"tab","label":"My Discord Bot","disabled":false,"info":"","env":[]},{"id":"e35bc33174b516e1","type":"switch","z":"95c94e0b20b62cc5","name":"Run Status Loop\\n queued\\n in progress\\n completed\\n exception","property":"payload.status","propertyType":"msg","rules":[{"t":"eq","v":"queued","vt":"str"},{"t":"eq","v":"in_progress","vt":"str"},{"t":"eq","v":"completed","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":4,"x":1050,"y":820,"wires":[["03824a6fa797e561"],["03824a6fa797e561"],["ae0a1f2eb1dda4e2"],[]],"inputLabels":["Run Details"],"outputLabels":["Run Queued","Run In Progress","Run Complete","Run Exception"]},{"id":"f490e04616a4c1e3","type":"change","z":"95c94e0b20b62cc5","name":"Create Run Payload","rules":[{"t":"set","p":"payload","pt":"msg","to":"{}","tot":"json"},{"t":"set","p":"payload.thread_id","pt":"msg","to":"payload.thread_id","tot":"flow"},{"t":"set","p":"payload.assistant_id","pt":"msg","to":"payload.assistant_id","tot":"flow"}],"action":"","property":"","from":"","to":"","reg":false,"x":680,"y":780,"wires":[["88281ee04095186d"]]},{"id":"76bd5072b349fbc4","type":"function","z":"95c94e0b20b62cc5","name":"Create Run Payload","func":"let assistants = msg.payload;\nlet assistant = assistants.find(assistant => assistant.name === msg.assistant.name);\nmsg.payload = {};\nif (assistant) {\n  // Retrieved the assistant, now create the payload for the next node\n  msg.payload.assistant_id = assistant.id;\n  msg.payload.thread = msg.assistant.thread;\n} else {\n  // No assistant found with the given name\n  msg.payload = null;\n}\nreturn msg;\n","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":420,"y":520,"wires":[["f346367be7ea755c"]]},{"id":"03824a6fa797e561","type":"function","z":"95c94e0b20b62cc5","name":"Create Run Status Payload","func":"let thread_id = msg.payload.thread_id;\n\nlet run_id = msg.payload.id;\n\nmsg.payload = {\n  thread_id: thread_id,\n  run_id: run_id\n};\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":520,"y":620,"wires":[["faaa655ee247cd64"]]},{"id":"515d4baa1a9ff994","type":"function","z":"95c94e0b20b62cc5","name":"Extract Thread Id","func":"msg.payload = {\n  thread_id: msg.payload.thread_id\n}\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":230,"y":980,"wires":[["b390ac5f29990eeb"]]},{"id":"f351af28511642c5","type":"function","z":"95c94e0b20b62cc5","name":"Store Conversation Details","func":"flow.set(\"payload.thread_id\", msg.payload[0].thread_id);\n\nflow.set(\"payload.assistant_id\", msg.payload[0].assistant_id);\n\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":660,"y":1040,"wires":[[]]},{"id":"3e19767be4912027","type":"change","z":"95c94e0b20b62cc5","name":"","rules":[{"t":"set","p":"assistant.name","pt":"msg","to":"_node-red-assistant_","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":750,"y":80,"wires":[["fb67c5a72cc3d992"]]},{"id":"b6bed72d23b6907b","type":"template","z":"95c94e0b20b62cc5","name":"json for role and content","field":"assistant.thread","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"{\n  \"messages\": [\n    {\n      \"role\": \"user\",\n      \"content\": \"{{data.cleanContent}}\"\n    }\n  ]\n}","output":"json","x":250,"y":400,"wires":[["97b13084c09ae872"]]},{"id":"3a91799be127a7f6","type":"OpenAI API","z":"95c94e0b20b62cc5","name":"Create Thread & Run","property":"payload","propertyType":"msg","service":"155bf6b44322f868","method":"createThreadAndRun","x":240,"y":620,"wires":[["03824a6fa797e561"]]},{"id":"faaa655ee247cd64","type":"OpenAI API","z":"95c94e0b20b62cc5","name":"Retrieve Run","property":"payload","propertyType":"msg","service":"155bf6b44322f868","method":"getRun","x":730,"y":620,"wires":[["e35bc33174b516e1"]]},{"id":"b390ac5f29990eeb","type":"OpenAI API","z":"95c94e0b20b62cc5","name":"List Messages","property":"payload","propertyType":"msg","service":"155bf6b44322f868","method":"listMessages","x":420,"y":980,"wires":[["d1d86114340006d7","f351af28511642c5"]]},{"id":"0bdbf8ab7abc31c2","type":"OpenAI API","z":"95c94e0b20b62cc5","name":"Create Message","property":"payload","propertyType":"msg","service":"155bf6b44322f868","method":"createMessage","x":480,"y":780,"wires":[["f490e04616a4c1e3"]]},{"id":"88281ee04095186d","type":"OpenAI API","z":"95c94e0b20b62cc5","name":"Create Run","property":"payload","propertyType":"msg","service":"155bf6b44322f868","method":"createRun","x":870,"y":780,"wires":[["e35bc33174b516e1"]]},{"id":"35a08e5a7e88ca67","type":"OpenAI API","z":"95c94e0b20b62cc5","name":"List Assistants","property":"payload","propertyType":"msg","service":"155bf6b44322f868","method":"listAssistants","x":220,"y":520,"wires":[["76bd5072b349fbc4"]],"outputLabels":["Assistant Objects"]},{"id":"b02093886b6b7f48","type":"switch","z":"95c94e0b20b62cc5","name":"new session or continue?","property":"_session.id","propertyType":"msg","rules":[{"t":"eq","v":"_session.id","vt":"flow"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":710,"y":380,"wires":[["37395105b817f19b"],["ea7790779cdffc5f"]]},{"id":"f346367be7ea755c","type":"change","z":"95c94e0b20b62cc5","name":"set flow._session.id","rules":[{"t":"set","p":"_session.id","pt":"flow","to":"_session.id","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":630,"y":520,"wires":[["ba7ef04e22ee604f"]]},{"id":"152a9e9b11ef9d5e","type":"change","z":"95c94e0b20b62cc5","name":"set msg.payload.thread_id","rules":[{"t":"set","p":"payload.thread_id","pt":"msg","to":"payload.thread_id","tot":"flow"},{"t":"set","p":"payload.role","pt":"msg","to":"user","tot":"str"},{"t":"set","p":"payload.content","pt":"msg","to":"payload.textField","tot":"msg"},{"t":"delete","p":"payload.textField","pt":"msg"},{"t":"delete","p":"payload.host","pt":"msg"},{"t":"delete","p":"payload.kb","pt":"msg"},{"t":"delete","p":"payload.imageClassification","pt":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":260,"y":780,"wires":[["0bdbf8ab7abc31c2"]]},{"id":"81fcfd91c1a5a878","type":"discordMessage","z":"95c94e0b20b62cc5","name":"apivisBot","channelIdFilter":"","token":"","x":160,"y":100,"wires":[["9e623708288e743e"]]},{"id":"0e316ec9e15ae219","type":"switch","z":"95c94e0b20b62cc5","name":"Filter Messages for Bot Response","property":"data.content","propertyType":"msg","rules":[{"t":"cont","v":"Hey Bot","vt":"str"},{"t":"cont","v":"hey bot","vt":"str"},{"t":"regex","v":"\\!Hey\\sBot","vt":"str","case":true},{"t":"else"}],"checkall":"false","repair":true,"outputs":4,"x":280,"y":200,"wires":[["3d2796e8ed3ecac4"],["951e095ea6f2d1f4"],["adc550621be4d43b"],["a1ac66c1b29ba54e"]]},{"id":"d1d86114340006d7","type":"function","z":"95c94e0b20b62cc5","name":"function 9","func":"// Get the text content from the message payload\nconst text = msg.payload[0].content[0].text.value || '';\n\n// Initialize an array to hold the new messages\nconst newMessages = [];\n\n// Check the length of the text\nif (text.length > 2000) {\n  // Split the text into chunks of 2000 characters\n  const maxLength = 2000;\n  const chunks = [];\n  for (let i = 0; i < text.length; i += maxLength) {\n    chunks.push(text.substring(i, i + maxLength));\n  }\n\n  // Create new messages for each chunk\n  chunks.forEach((chunk, index) => {\n    // Create a deep copy of the original message to maintain all other data\n    const newMsg = RED.util.cloneMessage(msg);\n\n    // Update the _msgid to be unique for each chunk\n    newMsg._msgid = `${msg._msgid}_${index}`;\n\n    // Update the text content with the chunk\n    newMsg.payload[0].content[0].text.value = chunk;\n\n    // Send the new message\n    node.send(newMsg);\n  });\n\n  // Return null to indicate that the original message has been processed\n  return null;\n}\n\n// If the text is not longer than 2000 characters, return the original message\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":600,"y":980,"wires":[["1fe05e16e32b75eb"]]},{"id":"84d4199b0ac63d8b","type":"discordMessageManager","z":"95c94e0b20b62cc5","name":"Response","channel":"","token":"","x":940,"y":980,"wires":[["7202e3272bb4d901"]]},{"id":"7202e3272bb4d901","type":"debug","z":"95c94e0b20b62cc5","name":"debug 107","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1090,"y":980,"wires":[]},{"id":"020ad446c488be26","type":"discordTyping","z":"95c94e0b20b62cc5","name":"","channel":"","token":"","x":780,"y":180,"wires":[]},{"id":"97b13084c09ae872","type":"switch","z":"95c94e0b20b62cc5","name":"session exist or not?","property":"_session.id","propertyType":"msg","rules":[{"t":"nnull"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":480,"y":400,"wires":[["b02093886b6b7f48"],["e8956df02b5c50ff"]]},{"id":"ed639d51e07cc132","type":"template","z":"95c94e0b20b62cc5","name":"","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"{\n  \"content\": \"{{payload}}\"\n}","output":"json","x":560,"y":80,"wires":[["3e19767be4912027"]]},{"id":"1fe05e16e32b75eb","type":"change","z":"95c94e0b20b62cc5","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload[0].content[0].text.value","tot":"msg"},{"t":"set","p":"request","pt":"msg","to":"payload[1].content[0].text.value","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":760,"y":980,"wires":[["84d4199b0ac63d8b"]]},{"id":"37395105b817f19b","type":"link out","z":"95c94e0b20b62cc5","name":"continue","mode":"link","links":["37e930a01616d311"],"x":865,"y":360,"wires":[]},{"id":"ea7790779cdffc5f","type":"link out","z":"95c94e0b20b62cc5","name":"link out 2","mode":"link","links":["1e4b3f1d11b89198"],"x":865,"y":400,"wires":[]},{"id":"37e930a01616d311","type":"link in","z":"95c94e0b20b62cc5","name":"link in 1","links":["37395105b817f19b"],"x":115,"y":780,"wires":[["152a9e9b11ef9d5e"]]},{"id":"1e4b3f1d11b89198","type":"link in","z":"95c94e0b20b62cc5","name":"link in 2","links":["ea7790779cdffc5f","e8956df02b5c50ff"],"x":115,"y":520,"wires":[["35a08e5a7e88ca67"]]},{"id":"e8956df02b5c50ff","type":"link out","z":"95c94e0b20b62cc5","name":"link out 3","mode":"link","links":["76d2bbf2a56c9d07","1e4b3f1d11b89198"],"x":615,"y":420,"wires":[]},{"id":"7397aae6ede41b4b","type":"catch","z":"95c94e0b20b62cc5","name":"","scope":null,"uncaught":false,"x":160,"y":300,"wires":[["6d9ffb0b28d49c58"]]},{"id":"6d9ffb0b28d49c58","type":"debug","z":"95c94e0b20b62cc5","name":"CatchtestDiscordFlow","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":340,"y":300,"wires":[]},{"id":"9e623708288e743e","type":"switch","z":"95c94e0b20b62cc5","name":"name of the discord channel","property":"channel.name","propertyType":"msg","rules":[{"t":"eq","v":"support","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":360,"y":100,"wires":[["ed639d51e07cc132"],[]]},{"id":"3d2796e8ed3ecac4","type":"link out","z":"95c94e0b20b62cc5","name":"link out 369","mode":"link","links":["69dc4b4d1e01327d","fef2651f90accdc3"],"x":475,"y":180,"wires":[]},{"id":"951e095ea6f2d1f4","type":"link out","z":"95c94e0b20b62cc5","name":"link out 370","mode":"link","links":["69dc4b4d1e01327d"],"x":515,"y":200,"wires":[]},{"id":"a1ac66c1b29ba54e","type":"link out","z":"95c94e0b20b62cc5","name":"link out 371","mode":"link","links":["69dc4b4d1e01327d","fef2651f90accdc3"],"x":595,"y":240,"wires":[]},{"id":"69dc4b4d1e01327d","type":"link in","z":"95c94e0b20b62cc5","name":"link in 307","links":["3d2796e8ed3ecac4","951e095ea6f2d1f4","a1ac66c1b29ba54e"],"x":115,"y":400,"wires":[["b6bed72d23b6907b"]]},{"id":"fef2651f90accdc3","type":"link in","z":"95c94e0b20b62cc5","name":"link in 308","links":["3d2796e8ed3ecac4","a1ac66c1b29ba54e","adc550621be4d43b"],"x":675,"y":180,"wires":[["020ad446c488be26"]]},{"id":"adc550621be4d43b","type":"link out","z":"95c94e0b20b62cc5","name":"link out 372","mode":"link","links":["fef2651f90accdc3"],"x":555,"y":220,"wires":[]},{"id":"ba7ef04e22ee604f","type":"link out","z":"95c94e0b20b62cc5","name":"link out 373","mode":"link","links":["604ebf162e54cefb"],"x":755,"y":520,"wires":[]},{"id":"604ebf162e54cefb","type":"link in","z":"95c94e0b20b62cc5","name":"link in 309","links":["ba7ef04e22ee604f"],"x":115,"y":620,"wires":[["3a91799be127a7f6"]]},{"id":"ae0a1f2eb1dda4e2","type":"link out","z":"95c94e0b20b62cc5","name":"link out 374","mode":"link","links":["14c58d79b5dea4b9"],"x":1195,"y":840,"wires":[]},{"id":"14c58d79b5dea4b9","type":"link in","z":"95c94e0b20b62cc5","name":"link in 310","links":["ae0a1f2eb1dda4e2"],"x":115,"y":980,"wires":[["515d4baa1a9ff994"]]},{"id":"ba41b71f8868f9f1","type":"comment","z":"95c94e0b20b62cc5","name":"new session","info":"","x":170,"y":460,"wires":[]},{"id":"03e9d971972f6ae9","type":"comment","z":"95c94e0b20b62cc5","name":"continue session","info":"","x":180,"y":700,"wires":[]},{"id":"85854c5d6cd9e960","type":"link in","z":"95c94e0b20b62cc5","name":"link in 311","links":["fb67c5a72cc3d992"],"x":115,"y":200,"wires":[["0e316ec9e15ae219"]]},{"id":"fb67c5a72cc3d992","type":"link out","z":"95c94e0b20b62cc5","name":"link out 375","mode":"link","links":["85854c5d6cd9e960"],"x":885,"y":80,"wires":[]},{"id":"155bf6b44322f868","type":"Service Host","apiBase":"https://api.openai.com/v1","secureApiKeyHeaderOrQueryName":"Authorization","organizationId":"org-ldadft66yHlGaxjfue648","name":"mykey"}]

Collection Info

Flow Info

Created 1 year ago
Rating: 5 1

Owner

Actions

Rate:

Node Types

Core
  • catch (x1)
  • change (x5)
  • comment (x2)
  • debug (x2)
  • function (x5)
  • link in (x7)
  • link out (x10)
  • switch (x5)
  • template (x2)
Other

Tags

  • @inductiv/node-red-openai-api
  • node-red-contrib-discord-advanced
  • apivis
  • openai
  • chat
  • chatbot
  • discord
  • chatgpt
Copy this flow JSON to your clipboard and then import into Node-RED using the Import From > Clipboard (Ctrl-I) menu option