Skip to main content
View source

Discord Bot

View as Markdown

A configurable, bidirectional Discord I/O source that routes messages, attachments, and optional reaction events into a pipeline and can post pipeline answers back to Discord.

What it does​

A source node (discord://) that authenticates with a bot you create in the Discord Developer Portal and listens for messages over the Discord Gateway. Text is routed to the text lane; image, audio, video, and document attachments are each downloaded (up to a configurable size limit) and routed to the matching lane by MIME type. The pipeline's first non-empty answer is sent back to the originating channel — as a reply, a channel message, or in a thread. Mentions are suppressed by default; explicit user and role allowlists can selectively enable them, while @everyone and @here always remain disabled. Every emitted object carries Discord message metadata and a correlation ID so downstream branches can store, evaluate, or merge related content without bot-specific logic.

The node uses discord.py to maintain a resilient Gateway connection with automatic heartbeating, resume, and reconnect. Attachments are downloaded through discord.py's Attachment.read() (which uses aiohttp transitively; it is not a direct dependency).

Lanes​

The node is a pipeline source: its _source lane emits one object per message and per attachment, routed by type.

Lane inLane outDescription
_sourcetextMessage text and configured text-like attachments, written as plain text.
_sourceimageImage attachments, downloaded and routed with MIME type (e.g. image/png).
_sourceaudioAudio attachments, downloaded with MIME type (e.g. audio/mpeg).
_sourcevideoVideo attachments, downloaded with MIME type (e.g. video/mp4).
_sourcetagsDocuments (PDF, Word, archive, and so on), downloaded as tagged stream data; connect a Parser node downstream. Also carries one JSON event object per reaction, no_reply, or outbound event while the matching emitReactions, emitNoReply, or emitOutbound setting is on, so a Parser on tags sees those events too.

Entry URLs are discord://<channel_id>/<message_id> for text, discord://<channel_id>/<message_id>/<attachment_id> for attachments, and discord://<channel_id>/<message_id>/<event_type> for events. Text objects are named with the message ID; attachment objects use <message_id>:<index> and events <message_id>:<event_type>. Metadata includes message/channel/thread/guild/author IDs, mentions, reply reference, attachment summaries, and correlationId, groupIndex, and groupSize fields shared by all objects from one message. Member display names and role IDs are opt-in.

Configuration​

See the Schema section below for the full field list, types, and defaults. Notes on the fields that shape behavior:

Reply Mode​

How the first answer is posted back: reply (a native reply to the message with no author ping — the default), thread (a single reused thread on the message), or channel (a plain channel message). threadName accepts {content}, threadNameMaxLength limits the resolved name (Discord accepts 1 to 100 characters, so a value outside that range is clamped to it), and threadAutoArchiveMinutes sets Discord's archive duration for threads the node creates — Discord accepts only 60, 1440, 4320, and 10080, so the field offers just those plus 0; 0 (the default) or any other value that still reaches the node is omitted and the channel's own default applies, with a debug line for an unsupported non-zero value. {content} resolves to the message text, or — for a message that carries only files — the first attachment's filename, before the length cap is applied; with neither, the name falls back to Pipeline Response. If the thread cannot be created (usually a missing Create Public Threads permission) the failure is logged and that chunk, plus the rest of the answer, is posted as a plain reply with destination reply, so a permission gap costs the thread rather than the whole answer.

If the question is deleted before the answer is posted, nothing is posted in reply mode or in thread mode (the thread cannot be created on a deleted message, and its fallback is a reply too), and with emitNoReply on the outcome is a no_reply with reason send_failed. channel mode does not reference the question, so it still posts.

Number Reply Chunks​

Answers longer than Discord's 2000-character limit are split on sentence and line boundaries; fenced code blocks are closed and reopened across message boundaries. With numberChunks enabled, a reply that needs more than one message ends each one with *(2/3)* so a reader sees the order; the label is paid for by the split, so every chunk still fits the 2000-character limit, and a reply that fits in one message is never labelled.

Require @Mention​

When true, the bot only processes messages in which it is directly @mentioned; @everyone and @here do not count. Useful in high-traffic channels. When false (default) it processes every message that passes the allowlists.

requireMentionChannelIds applies that gate only in selected channels, and recognizes a thread's parent channel ID.

Server IDs (Guild IDs) / Channel IDs​

Server and channel allowlists. When non-empty, only messages from the listed guild/channel IDs are processed; leave both empty to listen everywhere the bot has access. IDs are matched as strings, and every entry must be a numeric Discord ID (see Configuration errors under Notes). channelIds recognizes a thread's parent channel ID. Direct messages are answered only while both lists are empty; setting either one stops DMs. Setting guildIds is recommended in production, together with turning off Public Bot (see Authentication), because with both lists empty the bot answers in any server it is added to.

Ignore Bot Messages / Allowed Bot IDs​

ignoreBots (default true) drops messages from other bots to prevent loops; the bot never processes its own messages regardless. allowedBotIds permits selected bot accounts through while ignoreBots remains enabled.

Send Responses / Show Typing Indicator​

sendResponses (default true), when set to false, still ingests every message into the pipeline but posts nothing back. showTyping (default true) shows a typing indicator while the pipeline runs.

Max Attachment Size (bytes)​

Attachments larger than this (default 25 MB, at most 100 MB; a larger value is clamped to 100 MB, and 0 or a negative value means the default) are skipped without being downloaded; the reported size is checked before the file is fetched.

Max Concurrent Messages​

At most maxConcurrentMessages messages (default 4, 1 to 32) are processed at once, downloads included, so memory stays bounded; further messages wait their turn rather than being dropped. The limit is shared by every server and channel the bot serves, so slow pipelines delay all of them, and a message waiting for its turn shows no typing indicator yet. Values above the size of the default thread pool the pipeline calls run on (min(32, CPU count + 4)) do not make more pipelines run in parallel.

Text Attachment Extensions / Text Attachment Max Characters​

textAttachmentExtensions and textAttachmentMaxChars control which attachments are decoded as text and sent through the text lane; once any extension is listed, any text/* MIME is also treated as text, and with the list empty (the default) every attachment is routed as a binary object. An extension may be written with or without its leading dot (md and .md both match notes.md), in any case.

Merge attachments into the question​

A Discord message is one question even when it carries files, so with mergeAttachments enabled the node answers it once. A text-like attachment (one textAttachmentExtensions selects) is no longer asked about on its own: its content is folded into the message text as a Contents of attached file "<name>": block (fenced, capped by textAttachmentMaxChars, with a … (truncated) line when the cap bites; a file that turns out to hold binary content — a NUL byte — is routed as a binary attachment instead, below). Image, audio, video, and other attachments still run first, each as its own lane object with the same metadata and message SSE event as before, and every non-empty answer they produce is folded into the question as a What the pipeline found in the attached <image|audio|video|file> "<name>": block. The single text pass that follows sees the user's words, the file contents, and what the other lanes made of the media, and its answer is the reply. A message that carries only files is prefixed with The user shared the following file(s) with no message. Explain what each file is and what it does, and help them with it. so the pipeline is given a task rather than a bare document. If the text pass produces nothing, the first non-empty attachment answer is posted instead.

groupSize then counts the objects the message actually produces: the text pass is index 0, the remaining attachments follow in attachment order from index 1, and folded text files no longer count. (The count is decided before the attachment objects are pushed, so in the one case where nothing is left to ask — no text, no text file, and no attachment answer — the objects already pushed carry a groupSize one higher than the objects that existed.)

With mergeAttachments off (the default) the node keeps its original behavior: the text and every attachment are asked independently and the first non-empty answer overall — text first, then attachments in order — is posted.

Emit Reactions​

emitReactions adds a reaction event object for every reaction added to or removed from a message. The reactions Gateway intent is not privileged, and the node turns it on itself when this setting is on, so nothing needs enabling in the Developer Portal. Reaction capture covers human reactions only: a reaction whose user is the bot itself never produces a reaction event. Reactions are scoped exactly like messages: guildIds, channelIds (matching a thread's parent channel as well), and ignoreBots/allowedBotIds all apply, so a reaction from outside the node's scope is dropped rather than emitted. A reaction whose channel cannot be resolved is dropped while channelIds is set, because there is then no way to tell whether it is in scope; a reactor neither the payload nor the user cache knows is treated as a human. Reactions that arrive once shutdown has begun are dropped.

Emit No Reply Events / Emit Outbound Events​

emitNoReply and emitOutbound add event objects for unanswered/error outcomes and for sent replies. Both are off by default.

With emitOutbound on, every posted answer produces an outbound event carrying the posted message IDs, the destination, the answer text and complete, which is false when a later chunk failed after earlier ones were posted (Discord then shows only part of the answer). When sendResponses is false and emitOutbound is on, an answer produces an outbound event with no message IDs and destination: "suppressed", so shadow deployments can record what the bot would have said. An answer that was meant to be posted but could not be — every chunk failed, usually a missing Send Messages permission or a deleted question — produces no outbound event.

With emitNoReply on, a message that ends without a posted answer produces a no_reply event whose reason is one of:

  • no_answer: the pipeline produced no answer;
  • send_failed: there was an answer, but every chunk failed to post;
  • shutdown: shutdown began while the message waited for its turn, so it was never processed;
  • any other value: the error message of the first pipeline or download error, or of an unexpected failure, clipped to 200 characters (a reason built from an exception message would otherwise be unbounded).

Include Member Metadata​

includeMemberMetadata adds display names and member/mentioned role IDs to the metadata and requires the privileged Server Members Intent (see Authentication).

Allowed Mention User IDs / Allowed Mention Role IDs​

allowedMentionUserIds and allowedMentionRoleIds are the only outbound mention exceptions; leaving them empty preserves suppress-all behavior. Both lists keep plain ASCII-digit Discord IDs: any other entry is dropped with a debug line, because a single non-numeric entry would otherwise fail every outbound send and silence the bot entirely. An entry that is still an unresolved variable (${NAME} or <REDACTED>) is dropped too, with a warning in the task's warnings naming the field.

Authentication​

This node requires a Discord bot token. Create a bot in the Discord Developer Portal:

  1. Open Applications and click New Application.
  2. Name it and click Create.
  3. Under Bot, use Reset Token to reveal the bot token. It is shown once; keep it secret.
  4. Enable Message Content Intent under Privileged Gateway Intents. Also enable Server Members Intent when using member metadata.
  5. Still under Bot, turn off Public Bot unless anyone should be able to add the bot to their own server. A new application is public by default.
  6. Add the bot to your servers with the OAuth2 URL generator (bot scope plus the permissions listed under Notes).

Paste the token into the discord.botToken field. In production, also set guildIds to the servers the bot should serve: with it and channelIds both empty (the default) the bot answers in every server it is added to, and the node says so in the task's warnings at start. A missing token, an invalid token, or a missing Message Content Intent fails the source with an actionable status rather than idling silently.

The node tile shows whether a bot token is configured (Token: configured or Token: missing); it does not report whether the bot connected, which the task's status line does. The monitor panel shows only the last 6 characters of the token so you can confirm which bot is connected without exposing the secret.

Notes​

Prerequisites​

  • Message Content Intent must be enabled in the Developer Portal (Bot > Privileged Gateway Intents); without it message.content arrives empty. The node fails fast if the intent is missing.
  • The bot needs View Channels / Read Messages, Send Messages, and Read Message History in the channels it serves. Apply them via the OAuth2 URL generator or per role/channel.
  • For thread reply mode, the bot additionally needs Create Public Threads and Send Messages in Threads. In a DM or a channel that cannot host a thread — or when creating the thread is refused — the node falls back to a plain reply.

Message handling and replies​

  • Discord's own system notices (joins, pins, boosts, "started a thread") and messages with neither text nor attachments are dropped before any gate or pipeline work: there is nothing in them to answer.
  • With mergeAttachments on, one answer is posted per message: the text pass that has seen the folded files and the other lanes' answers. With it off, the first non-empty pipeline answer — text first, then attachments in order — is posted back. Optional no_reply and outbound event objects make those outcomes observable downstream.
  • A message may carry up to 10 attachments. Every attachment is downloaded and routed into the pipeline (each counted independently); a text-like attachment is folded into the text pass rather than routed on its own while merging is on.
  • Long answers are chunked at Discord's 2000-character limit on sentence and line boundaries.
  • Outbound content uses a restrictive allowed-mentions policy. Only configured user and role IDs can be pinged; @here and @everyone are never enabled.

What message text is broadcast and stored​

Every object the node opens (messages, attachments, and the reaction, no_reply, and outbound events) is also broadcast as an apaevt_sse event of type discord ({schemaVersion: 1, eventType, metadata, ...payload}), so a UI subscribed to SSE on the task can follow the conversation without reading pipeline traces. The broadcast is not live-only: the engine also writes every SSE body into the task's run log, so it is visible to every client monitoring the task and is kept in the run log afterwards.

The node puts Discord message text into the apaevt_sse bodies, and therefore into the task's run log, in exactly three places:

  • the question text, in the text field of each message event for the text lane (clipped at 2000 characters). With mergeAttachments on, a message that has no text of its own carries the merged question instead, which includes the folded text-file contents and what the pipeline found in the other attachments;
  • the decoded contents of each text-like attachment (one textAttachmentExtensions selects), framed with its filename, in the text field of its own message event when mergeAttachments is off (clipped at 2000 characters);
  • the answer text, in the text field of each outbound event (only when emitOutbound is on).

Binary attachments are never broadcast, only their MIME type and size. Every event's metadata also carries Discord IDs and attachment filenames, plus display names and role IDs when includeMemberMetadata is on, and a no_reply reason can quote an exception message. Operators need this list for their privacy notice: anyone who can monitor the task, and anyone who can read its run log, can read these messages.

Attachments and MIME detection​

  • Each attachment's reported size is checked against maxAttachmentBytes before download; oversized files are skipped with a debug log.
  • Files are routed by MIME type: the node uses Discord's reported content_type first (lowercased, with any ; charset=... parameters stripped) and falls back to the file extension: first the node's own table (e.g. .pdf maps to application/pdf), then Python's built-in mimetypes table (so .avi reaches the video lane, .bmp the image lane, and .csv is text/csv). The host's own MIME table (the Windows registry, /etc/mime.types) is never consulted, so a file routes the same way on every host; anything still unrecognized defaults to application/octet-stream and flows to the tags lane.
  • When textAttachmentExtensions lists any extension, those files and text/* MIME attachments are decoded, capped by textAttachmentMaxChars (0 means no limit), framed with their filename, and sent through the text lane — folded into the message's own text pass while mergeAttachments is on, or as their own object when it is off. Both paths decode the same way: a file that starts with a UTF-16 byte order mark (Windows Notepad "Unicode", PowerShell 5.1 redirects) is read as UTF-16, anything else as UTF-8 with a leading UTF-8 byte order mark dropped, and invalid bytes are ignored. A file whose decoded text still holds a NUL is binary content, not text, and is routed as a binary object on both paths.

Reliability and limits​

  • Rate limits: discord.py handles Discord 429 responses internally (honoring Retry-After with backoff); the node keeps a defensive extra retry for any RateLimited it surfaces.
  • Configuration errors: a list setting that starts with [ but is not valid JSON is reported in the task's warnings with the setting's name. In guildIds, channelIds or requireMentionChannelIds it fails the start instead, since the bot would otherwise answer nothing (or, for the mention list, answer without the mention). Only ROCKETRIDE_* server variables are resolved: an unset ${ROCKETRIDE_NAME} reaches the node as literal text, and any other ${NAME} as the literal <REDACTED>. Either one in botToken, guildIds, channelIds, or requireMentionChannelIds fails the start naming the problem (for example Discord Bot: <field> uses the variable <NAME>, which is not set on this server (only ROCKETRIDE_* server variables are resolved)), because it would match nothing (in requireMentionChannelIds, the mention gate would silently never apply). In allowedBotIds, allowedMentionRoleIds, or allowedMentionUserIds it produces a warning naming the field. A set variable whose value is empty arrives as an empty string, so guildIds, channelIds, or requireMentionChannelIds given items that resolve to no IDs (for example [""]) also fails the start, instead of being read as an empty list that means "everywhere"; a list that is genuinely empty still means all. Any other entry in those three lists that is not a Discord ID, plain ASCII digits with at most 20 of them (a channel or server name, a pasted mention such as <#123>, or two IDs run together), fails the start too, naming the field and the entry; for a pasted mention the message gives the ID inside it. In allowedBotIds such an entry produces a warning instead. An entry that is not a mention is shown by at most its first 12 characters (followed by … when longer), so a token or secret pasted into a list by mistake is never shown in full in the task status or the logs. JSON list items are trimmed and split on commas and whitespace, like a bare string.
  • Lifecycle: a terminal Gateway failure (invalid credentials, missing intent, or an unexpected disconnect) fails the source promptly; a successful start runs until the engine shuts the subprocess down.
  • No edit/delete handling: only MESSAGE_CREATE events are processed.
  • Byte accounting: processed message and file sizes are reported via monitorCompleted() / monitorFailed().

Schema​

FieldTypeDescriptionDefault
Pipe.source.parametersDiscord Bot Configuration
discord.allowedBotIdsarrayAllowed Bot IDs
Bot user IDs allowed through when Ignore Bot Messages is enabled.
[]
discord.allowedMentionRoleIdsarrayAllowed Mention Role IDs
Role IDs that outbound pipeline responses may mention.
[]
discord.allowedMentionUserIdsarrayAllowed Mention User IDs
User IDs that outbound pipeline responses may mention.
[]
discord.botTokenstringBot Token
Discord bot token from the Developer Portal (keep this secret - do not share)
discord.channelIdsarrayChannel IDs
List of channel IDs to listen to. Leave empty to listen to all channels.
[]
discord.emitNoReplybooleanEmit No Reply Events
Emit an event when processing produces no answer or raises an error. The reason is no_answer, send_failed or shutdown; any other value is an error message clipped to 200 characters.
false
discord.emitOutboundbooleanEmit Outbound Events
Emit an event after posting a pipeline response to Discord.
false
discord.emitReactionsbooleanEmit Reactions
Emit raw reaction add and remove events into the pipeline.
false
discord.guildIdsarrayServer IDs (Guild IDs)
List of Discord server IDs to listen to. Leave empty to listen to all servers the bot is in.
[]
discord.ignoreBotsbooleanIgnore Bot Messages
If true (default), messages from other bots are ignored to prevent loops.
true
discord.includeMemberMetadatabooleanInclude Member Metadata
Include display names and role IDs; requires the Discord members intent.
false
discord.maxAttachmentBytesnumberMax Attachment Size (bytes)
Maximum size of attachments to download. Larger files are skipped. Default 25 MB, at most 100 MB.
26214400
discord.maxConcurrentMessagesnumberMax Concurrent Messages
How many messages are processed at once. Further messages wait their turn; none are dropped.
4
discord.mergeAttachmentsbooleanMerge attachments into the question
When enabled, text-like files are folded into the message text and the answers the pipeline gives for image, audio, and video attachments are folded in as context before the text pass, so one reply covers everything. When disabled (the default), text and every attachment are asked separately and the first non-empty answer wins.
false
discord.numberChunksbooleanNumber Reply Chunks
When an answer is too long for one Discord message, end each message with its position, for example (2/3). A reply that fits in one message is never labelled.
false
discord.replyModestringReply Mode
How the bot sends answers: 'channel' (post as normal message), 'reply' (reply to the message), or 'thread' (post in a thread).
"reply"
discord.requireMentionbooleanRequire @Mention
If true, the bot only responds when explicitly @mentioned. If false, responds to all messages.
false
discord.requireMentionChannelIdsarrayRequire Mention Channel IDs
Channels (or thread parent channels) where a direct bot mention is always required.
[]
discord.sendResponsesbooleanSend Responses
If true, the bot sends pipeline answers back to Discord. If false, only processes messages.
true
discord.showTypingbooleanShow Typing Indicator
If true, show a typing indicator while processing the pipeline.
true
discord.textAttachmentExtensionsarrayText Attachment Extensions
Filename extensions decoded as text (UTF-8, or UTF-16 when the file starts with a UTF-16 byte order mark) and routed through the text lane, for example .pipe, .json, .log, .md, .txt, .csv, .yaml, .yml. Once any extension is listed, text/* files are decoded too. Empty (the default): every attachment is routed as a binary object.
[]
discord.textAttachmentMaxCharsnumberText Attachment Max Characters
Maximum decoded characters folded into the text lane per attachment. 0 means no limit.
12000
discord.threadAutoArchiveMinutesnumberThread Auto Archive Minutes
Discord auto-archive duration, in minutes, for response threads the node creates. Discord accepts only 60, 1440, 4320 or 10080; 0 (the default) uses the channel's own default.
0
discord.threadNamestringThread Name
Name template for response threads. {content} is replaced with the triggering message text.
"Pipeline Response"
discord.threadNameMaxLengthnumberThread Name Max Length
Maximum number of characters in a resolved response thread name. Discord accepts 1 to 100.
90

Dependencies​

  • discord.py