Repository navigation
Discord action #34
Description
Discord Action — v1 Concept
Integration of Discord into the Hercules automation platform, built on
discord.js. This document defines the scope for the
first version: which triggers/events and which functions should be implemented.
Architecture
Unlike the Shopware/Shopify actions, Discord does not push events via plain
HTTP webhooks. Events arrive over the Discord Gateway (a persistent
WebSocket connection) that discord.js manages through its Client.
This maps onto Hercules as follows:
- The action process runs a single discord.js
Client(one bot) next to the
Aquila connection. - Event classes are registered as runtime events (like the existing actions),
but instead of extending theRestflow type, the action listens to
discord.js client events and pushes them into flows via
action.fire(eventClass, projectId, payload). - Functions call the Discord REST API through the same discord.js client
(client.channels,guild.members, etc.), so no separate HTTP layer is
needed.
Discord Gateway ──ws──> discord.js Client ──action.fire()──> Aquila ──> Flows
Flows ──function call──> discord.js Client ──REST──> Discord API
Client lifecycle
- Log in on action start (
client.login(token)), reuse the connected client
for all function calls. - discord.js handles gateway reconnects itself; on fatal auth errors, surface
them viaCodeZeroEvent.errorlike the reconnect loop in the existing
actions.
Gateway intents
Discord requires intents to be declared for the events a bot receives. v1
needs:
| Intent | Required for | Privileged |
|---|---|---|
Guilds |
base guild/channel cache | no |
GuildMessages |
message events | no |
MessageContent |
reading message content | yes — must be enabled in the Discord developer portal |
GuildMembers |
member join/leave events | yes |
GuildMessageReactions |
reaction events | no |
The README of the action must document how to enable the two privileged
intents in the developer portal.
Configuration
Defined as ConfigurationDefinitions on the Action (like client_id /
client_secret in the GLS action):
| Identifier | Type | Description |
|---|---|---|
bot_token |
TEXT | Bot token from the Discord developer portal. Used for client.login(). |
application_id |
TEXT | Application ID; needed for registering slash commands. |
guild_id |
TEXT (optional) | If set, slash commands are registered guild-scoped (instant updates, ideal for development). Otherwise global. |
Events (Triggers) — v1
| Identifier | discord.js event | Payload data type | Notes |
|---|---|---|---|
DiscordMessageCreated |
messageCreate |
DiscordMessage |
Ignore messages from bots (incl. self) by default to avoid feedback loops. |
DiscordSlashCommandInvoked |
interactionCreate (ChatInputCommand) |
DiscordCommandInteraction |
Event setting: commandName (unique per flow) — the action registers the slash command at Discord on flow deployment. |
DiscordMemberJoined |
guildMemberAdd |
DiscordGuildMember |
Classic onboarding trigger (welcome message, auto-role). |
DiscordMemberLeft |
guildMemberRemove |
DiscordGuildMember |
|
DiscordReactionAdded |
messageReactionAdd |
DiscordReaction |
Enables reaction-role and approval flows. |
Deliberately not in v1: messageUpdate/messageDelete (partials handling),
buttons/select menus (interactionCreate component types), voice events,
scheduled events, bans. See Out of scope.
Event settings
Where useful, events get optional settings to filter at the source instead of
in the flow:
channelId(optional) onDiscordMessageCreated/DiscordReactionAdded—
only fire for a specific channel.commandName(+commandDescription, parameter definitions later) on
DiscordSlashCommandInvoked.
Functions — v1
Grouped like the GLS action (functions/, functions/utils/).
Messaging
| Identifier | Signature (sketch) | Description |
|---|---|---|
discordSendMessage |
(channelId: string, content: string, embed?: DISCORD_EMBED): DISCORD_MESSAGE |
Send a message to a channel. |
discordSendDirectMessage |
(userId: string, content: string, embed?: DISCORD_EMBED): DISCORD_MESSAGE |
Open a DM channel and send a message. |
discordReplyToMessage |
(channelId: string, messageId: string, content: string, embed?: DISCORD_EMBED): DISCORD_MESSAGE |
Reply with message reference. |
discordEditMessage |
(channelId: string, messageId: string, content: string): DISCORD_MESSAGE |
Edit a message previously sent by the bot. |
discordDeleteMessage |
(channelId: string, messageId: string): BOOLEAN |
Delete a message. |
discordAddReaction |
(channelId: string, messageId: string, emoji: string): BOOLEAN |
React with a unicode or custom emoji. |
Members & roles (moderation basics)
| Identifier | Signature (sketch) | Description |
|---|---|---|
discordAddRoleToMember |
(guildId: string, userId: string, roleId: string): DISCORD_GUILD_MEMBER |
Assign a role. |
discordRemoveRoleFromMember |
(guildId: string, userId: string, roleId: string): DISCORD_GUILD_MEMBER |
Remove a role. |
discordKickMember |
(guildId: string, userId: string, reason?: string): BOOLEAN |
Kick a member. |
discordBanMember |
(guildId: string, userId: string, reason?: string, deleteMessageSeconds?: number): BOOLEAN |
Ban a member. |
discordSetMemberNickname |
(guildId: string, userId: string, nickname: string): DISCORD_GUILD_MEMBER |
Set/clear a nickname. |
Lookups
| Identifier | Signature (sketch) | Description |
|---|---|---|
discordGetUser |
(userId: string): DISCORD_USER |
Fetch a user. |
discordGetMember |
(guildId: string, userId: string): DISCORD_GUILD_MEMBER |
Fetch a guild member (roles, join date). |
discordGetChannel |
(channelId: string): DISCORD_CHANNEL |
Fetch channel metadata. |
discordGetGuild |
(guildId: string): DISCORD_GUILD |
Fetch guild metadata (member count, name). |
Utils (data type builders)
Like the GLS create* util functions — pure builders so flows can assemble
nested payloads:
| Identifier | Signature (sketch) | Description |
|---|---|---|
discordCreateEmbed |
(title?: string, description?: string, color?: number, url?: string, fields?: Array<DISCORD_EMBED_FIELD>, footerText?: string, imageUrl?: string, thumbnailUrl?: string): DISCORD_EMBED |
Build an embed. |
discordCreateEmbedField |
(name: string, value: string, inline?: boolean): DISCORD_EMBED_FIELD |
Build an embed field. |
All functions wrap discord.js errors in RuntimeError with stable error codes
(e.g. DISCORD_UNKNOWN_CHANNEL, DISCORD_MISSING_PERMISSIONS,
DISCORD_MISSING_ACCESS), mapped from DiscordAPIError.code.
Data types — v1
One class per file in src/data_types/, zod schema + @Schema decorator,
following the existing actions:
DiscordUser— id, username, globalName, avatar URL, bot flagDiscordGuildMember— user, nickname, role ids, joinedAt, guild idDiscordMessage— id, channelId, guildId, author (DiscordUser), content,
embeds, attachments, mentions, referenced message id, createdAtDiscordChannel— id, guildId, name, type (DiscordChannelType), topic,
parent idDiscordChannelType— enum registered as its own data type (text, voice,
category, announcement, thread, forum, dm)DiscordGuild— id, name, icon URL, ownerId, memberCountDiscordRole— id, guildId, name, color, positionDiscordEmbed,DiscordEmbedField,DiscordEmbedFooter,
DiscordEmbedAuthorDiscordAttachment— id, filename, contentType, size, urlDiscordReaction— emoji (name/id), messageId, channelId, user
(DiscordUser)DiscordCommandInteraction— id, commandName, options (name/value pairs),
user, channelId, guildId
Known pitfalls from the other actions apply here:
@Signaturevalues are capped at 500 characters — register enums/unions as
data types instead of inlining them.- Optional parameters need
optional: trueon@Parameter;?:in the
signature alone is not enough. DiscordMessagereferences itself (replied-to message) — keep cyclic
references as plain id fields in v1 to avoid recursive schema handling, or
register each cyclic schema as its own data type with a deferred getter if
the full object is needed.
Out of scope — v2 candidates
- Components: buttons, select menus, modals (
interactionCreatecomponent
events + adiscordSendMessageWithComponentsfunction) - Message events:
messageUpdate,messageDelete(require partials
handling and cache tuning) - Channel management: create/delete/rename channels, threads
- Moderation events:
guildBanAdd,guildAuditLogEntryCreate, timeouts
(discordTimeoutMember) - Voice: voice state events
- Scheduled events, webhooks management, polls
- Multi-bot support (one client per config scope) — v1 assumes a single
bot token per action instance
Suggested project layout
actions/discord-action/
├── src/
│ ├── index.ts # Action setup, discord.js client, event wiring
│ ├── client.ts # discord.js client factory + login/reconnect
│ ├── helpers.ts # error mapping (DiscordAPIError -> RuntimeError)
│ ├── data_types/ # one data type class per file
│ ├── events/ # one event class per file
│ └── functions/
│ ├── messaging/
│ ├── members/
│ ├── lookups/
│ └── utils/
├── module.json
├── package.json # deps: @code0-tech/hercules, discord.js, zod
└── README.md # bot setup, privileged intents, invite URL
Metadata
Metadata
Assignees
Labels
Type
Fields
System
Activity