#!/usr/bin/env bash # post-bot-alert.sh — post a one-line message to a Discord alert channel. # # Routing: #bot-alerts (default; Syncro + general, whole team) vs #dev-alerts # (private; RMM/Dev alerts, Howard + Mike only). Auto-routes by message prefix: # [RMM] [DEPLOY] [DEV] [BUILD] [GURURMM] [SMARTBADGE-WATCH] -> #dev-alerts; # everything else (incl. [SYNCRO]) -> #bot-alerts. Override with a 2nd arg. # # Usage: # bash post-bot-alert.sh "message text" # auto-route by prefix # bash post-bot-alert.sh "message text" dev # force #dev-alerts # bash post-bot-alert.sh "message text" bot # force #bot-alerts # echo "message text" | bash post-bot-alert.sh # # Token resolution (first hit wins): # 1. SOPS vault: projects/discord-bot/bot-token.sops.yaml field credentials.bot_token # 2. projects/discord-bot/.env key DISCORD_TOKEN # Reading from the vault means this works from any machine, not just BEAST. # # Soft-fail by design: if the message is empty, the token is missing, or Discord is # unreachable, it prints a [WARNING] and exits 0 so it NEVER breaks the caller # (e.g. the /syncro billing workflow). The alert is best-effort, not load-bearing. set -u BOT_CHANNEL_ID="624710699771232265" # #bot-alerts — default (Syncro + general; whole team) DEV_CHANNEL_ID="1509998508198068484" # #dev-alerts — private RMM/Dev alerts (Howard + Mike only) ROOT="${CLAUDETOOLS_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}" # --- message (arg or stdin) --- MSG="${1:-}" if [ -z "$MSG" ] && [ ! -t 0 ]; then MSG="$(cat)"; fi if [ -z "$MSG" ]; then echo "[WARNING] post-bot-alert: empty message — nothing sent" >&2 exit 0 fi # --- channel routing --- # Optional 2nd arg: "dev"/"bot" keyword, a raw channel id, or omit for auto. # Auto: RMM/Dev-category prefixes -> #dev-alerts (private); everything else # (Syncro, general) -> #bot-alerts. Keeps existing call sites correct unchanged. TARGET="${2:-auto}" case "$TARGET" in dev|dev-alerts) CHANNEL_ID="$DEV_CHANNEL_ID"; CHANNEL_NAME="#dev-alerts" ;; bot|bot-alerts) CHANNEL_ID="$BOT_CHANNEL_ID"; CHANNEL_NAME="#bot-alerts" ;; auto|"") if printf '%s' "$MSG" | grep -qiE '^\[(RMM|DEPLOY|DEV|BUILD|GURURMM|SMARTBADGE-WATCH)\b'; then CHANNEL_ID="$DEV_CHANNEL_ID"; CHANNEL_NAME="#dev-alerts" else CHANNEL_ID="$BOT_CHANNEL_ID"; CHANNEL_NAME="#bot-alerts" fi ;; *) CHANNEL_ID="$TARGET"; CHANNEL_NAME="channel ${TARGET}" ;; esac # --- token: vault first, then .env --- TOKEN="$(bash "$ROOT/.claude/scripts/vault.sh" get-field \ projects/discord-bot/bot-token.sops.yaml credentials.bot_token 2>/dev/null)" if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then ENV_FILE="$ROOT/projects/discord-bot/.env" if [ -f "$ENV_FILE" ]; then TOKEN="$(grep -iE '^[[:space:]]*DISCORD_TOKEN[[:space:]]*=' "$ENV_FILE" | head -1 \ | sed -E 's/^[^=]*=[[:space:]]*//; s/^["'"'"']//; s/["'"'"'][[:space:]]*$//')" fi fi if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then echo "[WARNING] post-bot-alert: no bot token (vault + .env both empty) — alert skipped" >&2 exit 0 fi # --- post (jq builds JSON so the message is safely escaped) --- PAYLOAD="$(jq -nc --arg c "$MSG" '{content: $c}')" RESP="$(curl -s -m 15 -w $'\n%{http_code}' \ -X POST "https://discord.com/api/v10/channels/${CHANNEL_ID}/messages" \ -H "Authorization: Bot ${TOKEN}" \ -H "Content-Type: application/json" \ -H "User-Agent: ClaudeToolsBot (claudetools, 1.0)" \ --data-binary "$PAYLOAD" 2>/dev/null)" HTTP="$(printf '%s' "$RESP" | tail -n1)" BODY="$(printf '%s' "$RESP" | sed '$d')" if [ "$HTTP" = "200" ]; then MID="$(printf '%s' "$BODY" | jq -r '.id // empty' 2>/dev/null)" echo "[OK] post-bot-alert: posted to ${CHANNEL_NAME} (message_id=${MID})" exit 0 fi echo "[WARNING] post-bot-alert: Discord returned ${HTTP:-no-response} — ${BODY}" >&2 exit 0