ChatGPT Assistant to Discord Integration Leveraging Threads

How it works:

You have to create a thread in Discord. It gets cluttered if everyone is getting questions in the main channel. It creates a thread with the api and ties that ChatGPT thread to the Discord Thread. This allows for continued conversations. To ask a question, type "!Hey Bot" and then your question. It grabs the message and processes the flow.

Guides I used:

Discord Bot Setup ChatGPT Assistants

Nodes required:

node-red-contrib-discord-advanced node-red-contrib-string

Steps to implement:

  1. Add your api key in the "Token" for each of the requests nodes. 6 in total.
  2. Add your Bot key to the Discord node
  3. Grab your Assistant ID from ChatGPT API and load it in 2 places "Change Nodes"
[{"id":"972a1c81326dfd4b","type":"tab","label":"Main Interaction","disabled":false,"info":"","env":[]},{"id":"86adf89f72e55c73","type":"group","z":"972a1c81326dfd4b","name":"Create Thread in ChatGPT","style":{"label":true},"nodes":["b718e11ebf735c84","534f3c424b7bc7bf","b9efd6a7fc8b39b2","861b2b62884bd15c","611d8e1f5d21633a"],"x":74,"y":339,"w":772,"h":122},{"id":"46e4f15a7d6ec8b9","type":"group","z":"972a1c81326dfd4b","name":"Caution: Clears Global Context (History of Threads)","style":{"label":true},"nodes":["bf000478cec86eac","946263f96062d865","1f56704a1c02c508"],"x":74,"y":1059,"w":572,"h":82},{"id":"7a593e1b73350e14","type":"group","z":"972a1c81326dfd4b","name":"Check to see if thread has been created","style":{"label":true},"nodes":["172e235aff5e13d6","f127a435e0330d9d","5b44a5a8c0d268cc","0026c7036c09bd34"],"x":74,"y":239,"w":792,"h":82},{"id":"78a81acd9f184bfb","type":"group","z":"972a1c81326dfd4b","name":"Load Message","style":{"label":true},"nodes":["2f88f9bde4fb6771","5df7b755a5adadbc","d28bfa17062a0d61","a89d0fdc742b7b63"],"x":74,"y":479,"w":812,"h":82},{"id":"46d73b700396b726","type":"group","z":"972a1c81326dfd4b","name":"Run Thread","style":{"label":true},"nodes":["2d9bb1dc28202c33","838152c2f6a65faf","2e36bd0f3141bd03","60323123e64aae3d"],"x":74,"y":579,"w":952,"h":82},{"id":"9b9f303ba7730b42","type":"group","z":"972a1c81326dfd4b","name":"Check if run is complete and return Value","style":{"label":true},"nodes":["a47c4ee7eb480af1","6f84308604837f16","35e774b0aa4b0750","2be90c588f59c7c6","f03937145ebb706c","d53b9c26b040306c","1b19ef58d36e27f4","c682fc757fe7574c","eb432cc942163abf"],"x":74,"y":679,"w":1164.999984741211,"h":237.99999475479126},{"id":"96953884ac7792ec","type":"group","z":"972a1c81326dfd4b","name":"Return message and split message to Discord","style":{"label":true},"nodes":["99a2d5a9efb6af77","e42b127a9804850f","541633940b7b26c8","ec1069cb3cb9616d"],"x":74,"y":939,"w":752,"h":82},{"id":"cc3e357592450664","type":"group","z":"972a1c81326dfd4b","name":"Process Incoming Messages","style":{"label":true},"nodes":["ccd7fcb006cb5b16","39cb736732ccbd66","e8e36df7d27b8e6f","71de3e6c0366c312","91dd307464172c19","73882d6f505120f0","f4f982d843d600f6"],"x":74,"y":79,"w":1112,"h":142},{"id":"be1bf8de3dc839c3","type":"junction","z":"972a1c81326dfd4b","x":20,"y":400,"wires":[["2f88f9bde4fb6771"]]},{"id":"99a2d5a9efb6af77","type":"discordMessageManager","z":"972a1c81326dfd4b","g":"96953884ac7792ec","name":"Response","channel":"","token":"","x":520,"y":980,"wires":[["e42b127a9804850f"]]},{"id":"b718e11ebf735c84","type":"http request","z":"972a1c81326dfd4b","g":"86adf89f72e55c73","name":"P v1/threads","method":"POST","ret":"obj","paytoqs":"ignore","url":"https://api.openai.com/v1/threads","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"bearer","senderr":false,"headers":[{"keyType":"Content-Type","keyValue":"","valueType":"other","valueValue":"application/json"},{"keyType":"Accept","keyValue":"","valueType":"other","valueValue":"application/json"},{"keyType":"other","keyValue":"OpenAI-Beta","valueType":"other","valueValue":"assistants=v2"}],"x":490,"y":420,"wires":[["611d8e1f5d21633a","534f3c424b7bc7bf"]]},{"id":"534f3c424b7bc7bf","type":"change","z":"972a1c81326dfd4b","g":"86adf89f72e55c73","name":"","rules":[{"t":"set","p":"thread","pt":"msg","to":"payload.id","tot":"msg"},{"t":"set","p":"assistant","pt":"msg","to":"assistant_id","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":720,"y":420,"wires":[["2f88f9bde4fb6771"]]},{"id":"2f88f9bde4fb6771","type":"template","z":"972a1c81326dfd4b","g":"78a81acd9f184bfb","name":"","field":"url","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"https://api.openai.com/v1/threads/{{thread}}/messages","output":"str","x":160,"y":520,"wires":[["5df7b755a5adadbc"]]},{"id":"5df7b755a5adadbc","type":"change","z":"972a1c81326dfd4b","g":"78a81acd9f184bfb","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"{}","tot":"json"},{"t":"set","p":"payload.role","pt":"msg","to":"user","tot":"str"},{"t":"set","p":"payload.content","pt":"msg","to":"message","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":340,"y":520,"wires":[["d28bfa17062a0d61"]]},{"id":"d28bfa17062a0d61","type":"string","z":"972a1c81326dfd4b","g":"78a81acd9f184bfb","name":"","methods":[{"name":"decodeHTMLEntities","params":[]}],"prop":"payload.content","propout":"payload.content","object":"msg","objectout":"msg","x":510,"y":520,"wires":[["a89d0fdc742b7b63"]]},{"id":"a89d0fdc742b7b63","type":"http request","z":"972a1c81326dfd4b","g":"78a81acd9f184bfb","name":"P v1/threads/thread_id/messages","method":"POST","ret":"obj","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"bearer","senderr":false,"headers":[{"keyType":"Content-Type","keyValue":"","valueType":"other","valueValue":"application/json"},{"keyType":"Accept","keyValue":"","valueType":"other","valueValue":"application/json"},{"keyType":"other","keyValue":"OpenAI-Beta","valueType":"other","valueValue":"assistants=v2"}],"x":720,"y":520,"wires":[["2d9bb1dc28202c33"]]},{"id":"2d9bb1dc28202c33","type":"template","z":"972a1c81326dfd4b","g":"46d73b700396b726","name":"","field":"url","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"https://api.openai.com/v1/threads/{{thread}}/runs","output":"str","x":160,"y":620,"wires":[["838152c2f6a65faf"]]},{"id":"838152c2f6a65faf","type":"http request","z":"972a1c81326dfd4b","g":"46d73b700396b726","name":"G v1/threads/thread_id/runs","method":"GET","ret":"obj","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"bearer","senderr":false,"headers":[{"keyType":"Content-Type","keyValue":"","valueType":"other","valueValue":"application/json"},{"keyType":"Accept","keyValue":"","valueType":"other","valueValue":"application/json"},{"keyType":"other","keyValue":"OpenAI-Beta","valueType":"other","valueValue":"assistants=v2"}],"x":380,"y":620,"wires":[["2e36bd0f3141bd03"]]},{"id":"2e36bd0f3141bd03","type":"change","z":"972a1c81326dfd4b","g":"46d73b700396b726","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"{}","tot":"json"},{"t":"set","p":"payload.assistant_id","pt":"msg","to":"assistant","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":620,"y":620,"wires":[["60323123e64aae3d"]]},{"id":"60323123e64aae3d","type":"http request","z":"972a1c81326dfd4b","g":"46d73b700396b726","name":"P /v1/threads/{thread_id}/runs","method":"POST","ret":"obj","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"bearer","senderr":false,"headers":[{"keyType":"Content-Type","keyValue":"","valueType":"other","valueValue":"application/json"},{"keyType":"Accept","keyValue":"","valueType":"other","valueValue":"application/json"},{"keyType":"other","keyValue":"OpenAI-Beta","valueType":"other","valueValue":"assistants=v2"}],"x":870,"y":620,"wires":[["a47c4ee7eb480af1"]]},{"id":"a47c4ee7eb480af1","type":"template","z":"972a1c81326dfd4b","g":"9b9f303ba7730b42","name":"","field":"url","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"https://api.openai.com/v1/threads/{{thread}}/runs/{{payload.id}}","output":"str","x":160,"y":780,"wires":[["6f84308604837f16"]]},{"id":"6f84308604837f16","type":"http request","z":"972a1c81326dfd4b","g":"9b9f303ba7730b42","name":"G  /v1/threads/{thread_ID}/runs/{run_id}","method":"GET","ret":"obj","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"bearer","senderr":false,"headers":[{"keyType":"Content-Type","keyValue":"","valueType":"other","valueValue":"application/json"},{"keyType":"Accept","keyValue":"","valueType":"other","valueValue":"application/json"},{"keyType":"other","keyValue":"OpenAI-Beta","valueType":"other","valueValue":"assistants=v2"}],"x":430,"y":780,"wires":[["2be90c588f59c7c6","1b19ef58d36e27f4"]]},{"id":"35e774b0aa4b0750","type":"delay","z":"972a1c81326dfd4b","g":"9b9f303ba7730b42","name":"Random Timer","pauseType":"random","timeout":"4","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"2","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":680,"y":720,"wires":[["6f84308604837f16"]]},{"id":"2be90c588f59c7c6","type":"switch","z":"972a1c81326dfd4b","g":"9b9f303ba7730b42","name":"","property":"payload.status","propertyType":"msg","rules":[{"t":"eq","v":"in_progress","vt":"str"},{"t":"eq","v":"completed","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":3,"x":670,"y":780,"wires":[["35e774b0aa4b0750"],["f03937145ebb706c"],["eb432cc942163abf"]]},{"id":"f03937145ebb706c","type":"template","z":"972a1c81326dfd4b","g":"9b9f303ba7730b42","name":"","field":"url","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"https://api.openai.com/v1/threads/{{thread}}/messages","output":"str","x":840,"y":780,"wires":[["d53b9c26b040306c"]]},{"id":"d53b9c26b040306c","type":"http request","z":"972a1c81326dfd4b","g":"9b9f303ba7730b42","name":"G /v1/threads/{thread_id}/messages","method":"GET","ret":"obj","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"bearer","senderr":false,"headers":[{"keyType":"Content-Type","keyValue":"","valueType":"other","valueValue":"application/json"},{"keyType":"Accept","keyValue":"","valueType":"other","valueValue":"application/json"},{"keyType":"other","keyValue":"OpenAI-Beta","valueType":"other","valueValue":"assistants=v2"}],"x":1072.999984741211,"y":775.9999947547913,"wires":[["541633940b7b26c8"]]},{"id":"e42b127a9804850f","type":"debug","z":"972a1c81326dfd4b","g":"96953884ac7792ec","name":"debug 83","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":720,"y":980,"wires":[]},{"id":"541633940b7b26c8","type":"function","z":"972a1c81326dfd4b","g":"96953884ac7792ec","name":"function 8","func":"// Get the text content from the message payload\nlet text = msg.payload.data[0].content[0].text.value;\n\n// Initialize an array to hold the new messages\nlet newMessages = [];\n\n// Check the length of the text\nif (text.length > 2000) {\n    // Split the text into chunks of 2000 characters\n    let chunks = [];\n    for (let i = 0; i < text.length; i += 2000) {\n        chunks.push(text.substring(i, i + 2000));\n    }\n\n    // Create new messages for each chunk\n    newMessages = chunks.map((chunk, index) => {\n        // Create a deep copy of the original message to maintain all other data\n        let newMsg = JSON.parse(JSON.stringify(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.data[0].content[0].text.value = chunk;\n\n        return newMsg;\n    });\n\n    // Send each new message as a separate event\n    for (let i = 0; i < newMessages.length; i++) {\n        node.send(newMessages[i]);\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":160,"y":980,"wires":[["ec1069cb3cb9616d"]]},{"id":"ec1069cb3cb9616d","type":"change","z":"972a1c81326dfd4b","g":"96953884ac7792ec","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.data[0].content[0].text.value","tot":"msg"},{"t":"set","p":"action","pt":"msg","to":"create","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":340,"y":980,"wires":[["99a2d5a9efb6af77"]]},{"id":"b9efd6a7fc8b39b2","type":"change","z":"972a1c81326dfd4b","g":"86adf89f72e55c73","name":"","rules":[{"t":"set","p":"assistant_id","pt":"msg","to":"asst_info_here","tot":"str"},{"t":"set","p":"payload","pt":"msg","to":"{}","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":320,"y":420,"wires":[["b718e11ebf735c84"]]},{"id":"ccd7fcb006cb5b16","type":"discordMessage","z":"972a1c81326dfd4b","g":"cc3e357592450664","name":"node-red-bot","channelIdFilter":"","token":"","x":170,"y":180,"wires":[["39cb736732ccbd66"]]},{"id":"39cb736732ccbd66","type":"switch","z":"972a1c81326dfd4b","g":"cc3e357592450664","name":"Filter Messages for Bot Response","property":"payload","propertyType":"msg","rules":[{"t":"regex","v":"\\!Hey\\sBot","vt":"str","case":true}],"checkall":"false","repair":true,"outputs":1,"x":400,"y":180,"wires":[["e8e36df7d27b8e6f","71de3e6c0366c312"]]},{"id":"e8e36df7d27b8e6f","type":"discordTyping","z":"972a1c81326dfd4b","g":"cc3e357592450664","name":"","channel":"","token":"","x":680,"y":120,"wires":[]},{"id":"861b2b62884bd15c","type":"template","z":"972a1c81326dfd4b","g":"86adf89f72e55c73","name":"","field":"message","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"{{payload}} From: {{member.nickname}} ","output":"str","x":160,"y":420,"wires":[["b9efd6a7fc8b39b2"]]},{"id":"172e235aff5e13d6","type":"function","z":"972a1c81326dfd4b","g":"7a593e1b73350e14","name":"Check if Thread Exists","func":"// Get the array from context storage\nlet myArray = global.get(\"myArray\") || [];\n\n// Retrieve the Discord_Channel_ID from the message payload\nlet discordChannelId = msg.channel.id;\n\n// Find the object with the given Discord_Channel_ID\nlet foundObject = myArray.find(obj => obj.Discord_Channel_ID === discordChannelId);\n\nif (foundObject) {\n    // If found, set the Assistant_Thread_ID in the message payload\n    msg.threadID = {\n        Assistant_Thread_ID: foundObject.Assistant_Thread_ID\n    };\n} else {\n    // If not found, set a message indicating the object was not found\n    msg.threadID = {\n        error: \"Object not found\"\n    };\n}\n\nmsg.threadID.ChannelId = msg.channel.id;\n\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":200,"y":280,"wires":[["f127a435e0330d9d"]]},{"id":"f127a435e0330d9d","type":"switch","z":"972a1c81326dfd4b","g":"7a593e1b73350e14","name":"1 = found","property":"threadID.error","propertyType":"msg","rules":[{"t":"null"},{"t":"nnull"}],"checkall":"true","repair":false,"outputs":2,"x":400,"y":280,"wires":[["5b44a5a8c0d268cc"],["861b2b62884bd15c"]]},{"id":"611d8e1f5d21633a","type":"function","z":"972a1c81326dfd4b","g":"86adf89f72e55c73","name":"Add to Global Context","func":"// Get the array from context storage\nlet myArray = global.get(\"myArray\") || [];\n\n// Define the new object you want to add\nlet newObject = {\n    Discord_Channel_ID: msg.threadID.ChannelId,\n    Assistant_Thread_ID: msg.payload.id //tbd\n};\n\n// Check if the object already exists in the array\nlet exists = myArray.some(obj => obj.Discord_Channel_ID === newObject.Discord_Channel_ID &&\n    obj.Assistant_Thread_ID === newObject.Assistant_Thread_ID);\n\nif (!exists) {\n    // If the object does not exist, add it to the array\n    myArray.push(newObject);\n    global.set(\"myArray\", myArray);\n    msg.threadID = { success: \"Object added successfully\" };\n} else {\n    // If the object already exists, indicate that in the message payload\n    msg.threadID = { error: \"Object already exists\" };\n}\n\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":720,"y":380,"wires":[[]]},{"id":"bf000478cec86eac","type":"inject","z":"972a1c81326dfd4b","g":"46e4f15a7d6ec8b9","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":180,"y":1100,"wires":[["946263f96062d865"]]},{"id":"946263f96062d865","type":"change","z":"972a1c81326dfd4b","g":"46e4f15a7d6ec8b9","name":"","rules":[{"t":"set","p":"myArray","pt":"global","to":"[]","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":360,"y":1100,"wires":[["1f56704a1c02c508"]]},{"id":"1f56704a1c02c508","type":"debug","z":"972a1c81326dfd4b","g":"46e4f15a7d6ec8b9","name":"debug 92","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":540,"y":1100,"wires":[]},{"id":"5b44a5a8c0d268cc","type":"change","z":"972a1c81326dfd4b","g":"7a593e1b73350e14","name":"","rules":[{"t":"set","p":"thread","pt":"msg","to":"threadID.Assistant_Thread_ID","tot":"msg"},{"t":"set","p":"assistant","pt":"msg","to":"asst_info_here","tot":"str"},{"t":"set","p":"assistant_id","pt":"msg","to":"assistant","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":620,"y":280,"wires":[["0026c7036c09bd34"]]},{"id":"0026c7036c09bd34","type":"template","z":"972a1c81326dfd4b","g":"7a593e1b73350e14","name":"","field":"message","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"{{payload}} From: {{member.nickname}} ","output":"str","x":780,"y":280,"wires":[["be1bf8de3dc839c3"]]},{"id":"1b19ef58d36e27f4","type":"switch","z":"972a1c81326dfd4b","g":"9b9f303ba7730b42","name":"","property":"payload.data[0].status","propertyType":"msg","rules":[{"t":"eq","v":"completed","vt":"str"},{"t":"eq","v":"in_progress","vt":"str"},{"t":"eq","v":"queued","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":4,"x":662.9999847412109,"y":855.9999947547913,"wires":[["f03937145ebb706c"],["35e774b0aa4b0750"],[],["c682fc757fe7574c"]]},{"id":"c682fc757fe7574c","type":"debug","z":"972a1c81326dfd4b","g":"9b9f303ba7730b42","name":"debug 93","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":812.9999847412109,"y":875.9999947547913,"wires":[]},{"id":"71de3e6c0366c312","type":"switch","z":"972a1c81326dfd4b","g":"cc3e357592450664","name":"Check if in Thread","property":"channel.type","propertyType":"msg","rules":[{"t":"eq","v":"11","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":690,"y":180,"wires":[["172e235aff5e13d6"],["73882d6f505120f0"]]},{"id":"91dd307464172c19","type":"discordMessageManager","z":"972a1c81326dfd4b","g":"cc3e357592450664","name":"Response","channel":"","token":"","x":1100,"y":180,"wires":[[]]},{"id":"73882d6f505120f0","type":"change","z":"972a1c81326dfd4b","g":"cc3e357592450664","name":"Not a Thread","rules":[{"t":"set","p":"payload","pt":"msg","to":"This action only works in Threads","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":910,"y":180,"wires":[["91dd307464172c19","f4f982d843d600f6"]]},{"id":"eb432cc942163abf","type":"debug","z":"972a1c81326dfd4b","g":"9b9f303ba7730b42","name":"debug 82","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":840,"y":820,"wires":[]},{"id":"f4f982d843d600f6","type":"debug","z":"972a1c81326dfd4b","g":"cc3e357592450664","name":"debug 102","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1040,"y":120,"wires":[]}]

Flow Info

Created 11 months ago
Rating: 1 1

Owner

Actions

Rate:

Node Types

Core
  • change (x8)
  • debug (x5)
  • delay (x1)
  • function (x3)
  • http request (x6)
  • inject (x1)
  • switch (x5)
  • template (x6)
Other

Tags

  • discord
  • chatgpt
  • assistants
  • chatgpt_assistant
Copy this flow JSON to your clipboard and then import into Node-RED using the Import From > Clipboard (Ctrl-I) menu option