Skip to content

Discord action #34

Description

@nicosammito

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 the Rest flow 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 via CodeZeroEvent.error like 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) on DiscordMessageCreated / 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 flag
  • DiscordGuildMember — user, nickname, role ids, joinedAt, guild id
  • DiscordMessage — id, channelId, guildId, author (DiscordUser), content,
    embeds, attachments, mentions, referenced message id, createdAt
  • DiscordChannel — id, guildId, name, type (DiscordChannelType), topic,
    parent id
  • DiscordChannelType — enum registered as its own data type (text, voice,
    category, announcement, thread, forum, dm)
  • DiscordGuild — id, name, icon URL, ownerId, memberCount
  • DiscordRole — id, guildId, name, color, position
  • DiscordEmbed, DiscordEmbedField, DiscordEmbedFooter,
    DiscordEmbedAuthor
  • DiscordAttachment — id, filename, contentType, size, url
  • DiscordReaction — 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:

  • @Signature values are capped at 500 characters — register enums/unions as
    data types instead of inlining them.
  • Optional parameters need optional: true on @Parameter; ?: in the
    signature alone is not enough.
  • DiscordMessage references 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 (interactionCreate component
    events + a discordSendMessageWithComponents function)
  • 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

Labels

No labels
No labels

Fields

System

None yet

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions