Skip to content

Commit 7ac50b0

Browse files
committed
feat(trogon-source-discord): add Discord interaction webhook receiver
Adds Gateway WebSocket (Serenity) and HTTP Interactions Endpoint as a single inbound dumb pipe for all Discord events into NATS JetStream. Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
1 parent 430b2db commit 7ac50b0

14 files changed

Lines changed: 2626 additions & 0 deletions

File tree

devops/docker/compose/.env.example

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,17 @@
1919
# LINEAR_WEBHOOK_TIMESTAMP_TOLERANCE_SECS=60
2020
# LINEAR_NATS_ACK_TIMEOUT_MS=10000
2121

22+
# --- Discord Source ---
23+
# DISCORD_BOT_TOKEN=
24+
# DISCORD_GATEWAY_INTENTS=guilds,guild_members,guild_messages,guild_message_reactions,direct_messages,message_content,guild_voice_states
25+
# DISCORD_PUBLIC_KEY=
26+
# DISCORD_WEBHOOK_PORT=8080
27+
# DISCORD_SUBJECT_PREFIX=discord
28+
# DISCORD_STREAM_NAME=DISCORD
29+
# DISCORD_STREAM_MAX_AGE_SECS=604800
30+
# DISCORD_NATS_ACK_TIMEOUT_SECS=10
31+
# DISCORD_MAX_BODY_SIZE=4194304
32+
2233
# --- Slack Source ---
2334
# SLACK_SIGNING_SECRET=
2435
# SLACK_WEBHOOK_PORT=3000

devops/docker/compose/compose.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,33 @@ services:
6767
start_period: 10s
6868
retries: 3
6969

70+
trogon-source-discord:
71+
build:
72+
context: ../../../rsworkspace
73+
dockerfile: ../devops/docker/compose/services/trogon-source-discord/Dockerfile
74+
environment:
75+
NATS_URL: "nats:4222"
76+
DISCORD_BOT_TOKEN: "${DISCORD_BOT_TOKEN:-}"
77+
DISCORD_GATEWAY_INTENTS: "${DISCORD_GATEWAY_INTENTS:-}"
78+
DISCORD_PUBLIC_KEY: "${DISCORD_PUBLIC_KEY:-}"
79+
DISCORD_WEBHOOK_PORT: "${DISCORD_WEBHOOK_PORT:-8080}"
80+
DISCORD_SUBJECT_PREFIX: "${DISCORD_SUBJECT_PREFIX:-discord}"
81+
DISCORD_STREAM_NAME: "${DISCORD_STREAM_NAME:-DISCORD}"
82+
DISCORD_STREAM_MAX_AGE_SECS: "${DISCORD_STREAM_MAX_AGE_SECS:-604800}"
83+
DISCORD_NATS_ACK_TIMEOUT_SECS: "${DISCORD_NATS_ACK_TIMEOUT_SECS:-10}"
84+
DISCORD_MAX_BODY_SIZE: "${DISCORD_MAX_BODY_SIZE:-4194304}"
85+
RUST_LOG: "${RUST_LOG:-info}"
86+
depends_on:
87+
nats:
88+
condition: service_healthy
89+
restart: unless-stopped
90+
healthcheck:
91+
test: ["CMD", "curl", "-sf", "http://localhost:${DISCORD_WEBHOOK_PORT:-8080}/health"]
92+
interval: 10s
93+
timeout: 3s
94+
start_period: 10s
95+
retries: 3
96+
7097
trogon-source-slack:
7198
build:
7299
context: ../../../rsworkspace
@@ -97,6 +124,7 @@ services:
97124
image: ngrok/ngrok:alpine
98125
environment:
99126
NGROK_AUTHTOKEN: "${NGROK_AUTHTOKEN:-}"
127+
DISCORD_ADDR: "trogon-source-discord:${DISCORD_WEBHOOK_PORT:-8080}"
100128
GITHUB_ADDR: "trogon-source-github:${GITHUB_WEBHOOK_PORT:-8080}"
101129
LINEAR_ADDR: "trogon-source-linear:${LINEAR_WEBHOOK_PORT:-8080}"
102130
SLACK_ADDR: "trogon-source-slack:${SLACK_WEBHOOK_PORT:-3000}"
@@ -107,6 +135,9 @@ services:
107135
cat > /tmp/ngrok.yml <<EOF
108136
version: 3
109137
tunnels:
138+
discord:
139+
addr: $${DISCORD_ADDR}
140+
proto: http
110141
github:
111142
addr: $${GITHUB_ADDR}
112143
proto: http
@@ -119,6 +150,8 @@ services:
119150
EOF
120151
exec ngrok start --all --config /tmp/ngrok.yml
121152
depends_on:
153+
trogon-source-discord:
154+
condition: service_healthy
122155
trogon-source-github:
123156
condition: service_healthy
124157
trogon-source-linear:
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# ── Stage 1: chef — generate dependency recipe ──────────────────────────────
2+
FROM rust:1.93-slim AS chef
3+
4+
RUN cargo install cargo-chef --locked
5+
6+
WORKDIR /build
7+
8+
# ── Stage 2: planner — capture dependency graph ─────────────────────────────
9+
FROM chef AS planner
10+
11+
COPY Cargo.toml Cargo.lock ./
12+
COPY crates/ crates/
13+
14+
RUN cargo chef prepare --recipe-path recipe.json
15+
16+
# ── Stage 3: builder — cached dependency build + final compile ──────────────
17+
FROM chef AS builder
18+
19+
COPY --from=planner /build/recipe.json recipe.json
20+
RUN cargo chef cook --release --recipe-path recipe.json -p trogon-source-discord
21+
22+
COPY Cargo.toml Cargo.lock ./
23+
COPY crates/ crates/
24+
25+
RUN cargo build --release -p trogon-source-discord && \
26+
strip target/release/trogon-source-discord
27+
28+
# ── Stage 4: runtime ────────────────────────────────────────────────────────
29+
FROM debian:bookworm-20250317-slim AS runtime
30+
31+
RUN apt-get update && apt-get install -y --no-install-recommends \
32+
ca-certificates curl \
33+
&& rm -rf /var/lib/apt/lists/*
34+
35+
RUN useradd --no-create-home --shell /usr/sbin/nologin trogon
36+
37+
COPY --from=builder /build/target/release/trogon-source-discord /usr/local/bin/trogon-source-discord
38+
39+
USER trogon
40+
41+
EXPOSE 8080
42+
43+
STOPSIGNAL SIGTERM
44+
45+
ENTRYPOINT ["/usr/local/bin/trogon-source-discord"]
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# Receiving Discord Events Locally
2+
3+
`trogon-source-discord` is the single inbound pipe for ALL Discord events into
4+
NATS JetStream. It supports two input paths that can run independently or
5+
together:
6+
7+
- **Gateway** — WebSocket connection via the Discord Bot API (messages, reactions,
8+
members, voice, etc.)
9+
- **HTTP Interactions Endpoint** — receives slash commands, buttons, modals, and
10+
autocomplete via Discord's Interactions Endpoint URL
11+
12+
## Prerequisites
13+
14+
- Docker Compose
15+
- A [Discord Application](https://discord.com/developers/applications)
16+
- An [ngrok](https://ngrok.com) account (free tier works, only needed for HTTP
17+
Interactions)
18+
19+
## Gateway mode (bot token)
20+
21+
### 1. Get your bot token
22+
23+
1. Go to **Discord Developer Portal → Applications → your app → Bot**
24+
2. Copy the **Token**
25+
26+
### 2. Start the stack
27+
28+
```bash
29+
DISCORD_BOT_TOKEN=<your-bot-token> docker compose up
30+
```
31+
32+
This connects to the Discord Gateway and publishes every event to NATS on
33+
`discord.{event_name}` subjects (e.g. `discord.message_create`,
34+
`discord.guild_member_add`).
35+
36+
## HTTP Interactions mode (public key)
37+
38+
### 1. Get your public key
39+
40+
1. Go to **Discord Developer Portal → Applications → your app → General Information**
41+
2. Copy the **Public Key** (hex string)
42+
43+
### 2. Start the stack
44+
45+
```bash
46+
DISCORD_PUBLIC_KEY=<your-hex-public-key> docker compose --profile dev up
47+
```
48+
49+
This starts NATS, the webhook receiver, and ngrok. Find the public tunnel URL
50+
in the ngrok container logs:
51+
52+
```bash
53+
docker compose logs ngrok
54+
```
55+
56+
### 3. Configure your Discord Application
57+
58+
1. Go to **Discord Developer Portal → Applications → your app → General Information**
59+
2. Set **Interactions Endpoint URL** to `https://<ngrok-url>/webhook`
60+
3. Discord will send a PING to verify the endpoint — the receiver handles this
61+
automatically
62+
63+
## Both modes together
64+
65+
```bash
66+
DISCORD_BOT_TOKEN=<token> DISCORD_PUBLIC_KEY=<key> docker compose --profile dev up
67+
```
68+
69+
## Verify
70+
71+
Subscribe to NATS to see events flowing:
72+
73+
```bash
74+
nats sub -s nats://nats.trogonai.orb.local:4222 "discord.>"
75+
```
76+
77+
Without `--profile dev`, ngrok is excluded and only the core services start.
78+
79+
## Environment variables
80+
81+
| Variable | Required | Default | Description |
82+
|---|---|---|---|
83+
| `DISCORD_BOT_TOKEN` | one of token/key || Bot token for Gateway WebSocket |
84+
| `DISCORD_PUBLIC_KEY` | one of token/key || Ed25519 public key (hex) for Interactions |
85+
| `DISCORD_GATEWAY_INTENTS` | no | see code | Comma-separated gateway intents |
86+
| `NGROK_AUTHTOKEN` | yes (dev profile) || ngrok auth token |
87+
| `DISCORD_WEBHOOK_PORT` | no | `8080` | HTTP port for the webhook receiver |
88+
| `DISCORD_SUBJECT_PREFIX` | no | `discord` | NATS subject prefix |
89+
| `DISCORD_STREAM_NAME` | no | `DISCORD` | JetStream stream name |
90+
| `RUST_LOG` | no | `info` | Log level |

0 commit comments

Comments
 (0)