Nik Afiq 39b7d6d4b4
All checks were successful
CI / changes (push) Successful in 19s
CI / test (push) Successful in 31s
CI / build-ai-gateway (push) Successful in 1m8s
CI / build-ha-gateway (push) Successful in 1m19s
CI / build-discord-bot (push) Successful in 59s
CI / build-alexa-bridge (push) Successful in 1m8s
CI / build-alert-bridge (push) Successful in 1m17s
CI / build-tts-gateway (push) Successful in 1m10s
CI / build-tts-sidecar (push) Has been skipped
CI / build-tts-model (push) Has been skipped
feat: add alert-bridge service for external cronjob -> Discord alerts
New standalone service that receives authenticated HTTP POSTs (e.g. from a
cronjob elsewhere on the home network) and posts them to a Discord channel
via an Incoming Webhook. Fire-and-forget, no dependency on discord-bot's bot
session at all - mirrors alexa-bridge's precedent of a small bridge service
with its own HTTP listener and auth rather than adding an inbound edge to an
existing internal service.

Wires alert-bridge into go.work, CI (changes filter, test job, new
build-alert-bridge job), and the other five Dockerfiles' COPY lines.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 22:45:18 +09:00
..

discord-bot

discord-bot is a Discord slash-command service for home control. It calls ha-gateway for direct Home Assistant actions and ai-gateway for free-form AI-assisted commands.

Runtime Flow

  1. The process loads .env, opens a Discord session, and registers slash commands.
  2. A user runs a command such as /light list, /light on, or /ai query.
  3. The Discord adapter validates and routes the interaction to the app layer.
  4. The app layer calls ha-gateway or ai-gateway through secondary gRPC adapters.
  5. The bot sends a Discord response. Long-running AI work is tracked so shutdown can wait briefly for in-flight requests.

Commands

Lights

/light list
/light on light:<entity> brightness:80 color_temp:3000
/light off light:<entity> transition:3
/light toggle light:<entity>
  • light is required for action commands and uses autocomplete.
  • brightness is optional and accepts 1-100.
  • color_temp is optional and accepts 2000-6535 kelvin.
  • transition is optional and accepts 0-30 seconds.

Switches

/switch list
/switch on switch:<entity>
/switch off switch:<entity>
/switch toggle switch:<entity>
  • switch is required for action commands and uses autocomplete.

Air Conditioner

/ac on ac:<entity>
/ac off ac:<entity>
/ac mode ac:<entity> mode:<Cool|Heat|Dry|Auto|Fan>
/ac temp up ac:<entity>
/ac temp down ac:<entity>
  • ac is required for every subcommand and uses autocomplete.
  • mode is a fixed choice list mapping to Home Assistant HVAC modes (Cool→cool, Heat→heat, Dry→dry, Auto→heat_cool, Fan→fan_only).
  • temp up/temp down step the target temperature by one target_temp_step increment, clamped to the entity's min_temp/max_temp.

AI

/ai query text:<prompt>
/ai model list
/ai model get
/ai model set name:<model>

AI queries are sent to ai-gateway. The active model is stored in process memory, so it resets when the bot restarts.

Speak

/speak speaker:<name> text:<text>

Synthesizes text via tts-gateway (92-speaker autocomplete) and plays the result in the invoking user's current voice channel. tts-gateway returns AAC; discord-bot transcodes it to Opus locally (ffmpeg + github.com/jonas747/dca) before streaming, since Discord's voice transport requires Opus. Currently non-functional in practice - see "Limitations" below; the synthesis half works, voice playback doesn't yet.

Configuration

Environment variables:

Variable Default Description
DISCORD_TOKEN required Discord bot token
GUILD_ID empty Guild-scoped command registration target; empty registers global commands
HA_GATEWAY_ADDR required gRPC address for ha-gateway
AI_GATEWAY_ADDR ai-gateway.home-services.svc.cluster.local:50052 gRPC address for ai-gateway
TTS_GATEWAY_ADDR tts-gateway.home-services.svc.cluster.local:50053 gRPC address for tts-gateway
TLS_DIR empty Enables mTLS for ha-gateway/ai-gateway clients when set - not currently used for the tts-gateway client, since tts-gateway's own mTLS is still disabled (see its README); that's hardcoded to plaintext until both sides are ready together
OTEL_ENDPOINT empty OTLP gRPC collector endpoint; empty disables telemetry
LOG_LEVEL info debug, info, warn, or error
LOG_FORMAT json json or text

Example env file: .env.example

When TLS_DIR is set, the directory must contain tls.crt, tls.key, and ca.crt.

Local Run

cp .env.example .env
go run ./cmd/bot

Run from discord-bot/ so godotenv loads discord-bot/.env.

For local plaintext gateway connections, leave TLS_DIR empty and use:

HA_GATEWAY_ADDR=localhost:50051
AI_GATEWAY_ADDR=localhost:50052

Test And Build

go test ./...
go build ./...

Build the container image from the workspace root:

docker build -f discord-bot/Dockerfile -t discord-bot:dev .

Optionally pass a build version:

docker build -f discord-bot/Dockerfile --build-arg VERSION=$(git rev-parse --short HEAD) -t discord-bot:dev .

Package Map

cmd/bot/                                 # process entrypoint and wiring
internal/adapters/primary/discord/       # slash command registration, handlers, voice playback (voice.go)
internal/adapters/secondary/gateway/     # ha-gateway gRPC client
internal/adapters/secondary/aigateway/   # ai-gateway gRPC client
internal/adapters/secondary/ttsgateway/  # tts-gateway gRPC client
internal/app/                            # command orchestration and formatting
internal/config/                         # environment loading
internal/core/ports/driven/              # app-facing gateway interfaces
internal/modelstore/                     # in-memory active AI model store
internal/modelvalidator/                 # model availability checks
internal/logger/                         # slog setup
internal/telemetry/                      # OpenTelemetry setup

Limitations

  • The active AI model is not persisted.
  • The bot relies on Discord auth plus internal gateway/network controls; it does not implement per-user authorization.
  • List output is optimized for monospace Discord messages, not rich embeds.
  • /speak cannot currently join a voice channel at all. Discord requires its DAVE end-to-end-encryption protocol for every voice connection as of March 1, 2026 (non-DAVE clients get disconnected with close code 4017, "E2EE/DAVE protocol required"). github.com/bwmarrin/discordgo v0.29.0 - the latest tagged release, and what this module depends on - has no DAVE support (confirmed by inspecting its source; an upstream PR adding it was still open, unmerged, as of March 2026). This blocks any discordgo-based voice bot right now, not just this one. The synthesis half of /speak (calling tts-gateway, getting audio back) works correctly; only the final "join the channel and stream" step fails. Revisit once discordgo ships DAVE support - deliberately not worked around with an unofficial fork (e.g. cartridge-gg/discordgo's cgo/libdave binding) for now.