107 lines
5.0 KiB
Bash
107 lines
5.0 KiB
Bash
#!/usr/bin/env bash
|
|
# discord-dm.sh — send a Discord message to an org member (DM) or a team channel,
|
|
# using the ClaudeTools bot. Built for copy-paste-friendly delivery of wrapped
|
|
# command lines (consent links, long one-liners) straight to a person's DMs.
|
|
#
|
|
# Usage:
|
|
# discord-dm.sh <recipient> "message text"
|
|
# echo "message text" | discord-dm.sh <recipient>
|
|
# discord-dm.sh list # show known users + channels
|
|
#
|
|
# <recipient> is one of:
|
|
# - a user name: mike | howard | rob | winter -> opens a DM
|
|
# - a channel: #bot-alerts | #dev-alerts | bot | dev -> posts to the channel
|
|
# - a raw snowflake: 17-19 digit ID (treated as a USER DM; prefix chan:<id> for a channel)
|
|
#
|
|
# Token resolution (mirrors post-bot-alert.sh, 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
|
|
#
|
|
# Exit codes: 0 success; 1 usage/arg error; 2 token missing; 3 Discord API error.
|
|
# Unlike post-bot-alert.sh this does NOT soft-fail — a DM is usually load-bearing
|
|
# (you asked for it on purpose), so a failure is surfaced with a non-zero exit.
|
|
|
|
set -u
|
|
|
|
ROOT="${CLAUDETOOLS_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
|
|
API="https://discord.com/api/v10"
|
|
UA="ClaudeToolsBot (claudetools, 1.0)"
|
|
|
|
# --- prepopulated org directory (from .claude/users.json + post-bot-alert.sh) ---
|
|
declare -A USERS=(
|
|
[mike]="264814939619721216"
|
|
[howard]="624667664501178379"
|
|
[rob]="261978810713505792"
|
|
[winter]="624666486362996755"
|
|
)
|
|
declare -A CHANNELS=(
|
|
[bot-alerts]="624710699771232265" # whole team (Syncro + general)
|
|
[dev-alerts]="1509998508198068484" # private RMM/Dev (Howard + Mike)
|
|
)
|
|
|
|
print_dir() {
|
|
echo "Users (DM):"
|
|
for u in "${!USERS[@]}"; do printf " %-8s %s\n" "$u" "${USERS[$u]}"; done
|
|
echo "Channels:"
|
|
for c in "${!CHANNELS[@]}"; do printf " #%-12s %s\n" "$c" "${CHANNELS[$c]}"; done
|
|
}
|
|
|
|
RECIPIENT="${1:-}"
|
|
if [ -z "$RECIPIENT" ] || [ "$RECIPIENT" = "-h" ] || [ "$RECIPIENT" = "--help" ]; then
|
|
sed -n '2,30p' "$0"; exit 1
|
|
fi
|
|
if [ "$RECIPIENT" = "list" ]; then print_dir; exit 0; fi
|
|
shift
|
|
|
|
# --- message (remaining args, else stdin) ---
|
|
if [ "$#" -gt 0 ]; then MSG="$*"; elif [ ! -t 0 ]; then MSG="$(cat)"; else MSG=""; fi
|
|
if [ -z "$MSG" ]; then echo "[ERROR] empty message — nothing sent" >&2; exit 1; fi
|
|
|
|
# --- resolve recipient -> mode (dm|channel) + target id ---
|
|
MODE=""; TARGET=""; LABEL=""
|
|
key="$(printf '%s' "$RECIPIENT" | tr '[:upper:]' '[:lower:]')"
|
|
case "$key" in
|
|
bot|bot-alerts|"#bot-alerts") MODE=channel; TARGET="${CHANNELS[bot-alerts]}"; LABEL="#bot-alerts" ;;
|
|
dev|dev-alerts|"#dev-alerts") MODE=channel; TARGET="${CHANNELS[dev-alerts]}"; LABEL="#dev-alerts" ;;
|
|
chan:*) MODE=channel; TARGET="${key#chan:}"; LABEL="channel ${TARGET}" ;;
|
|
\#*) c="${key#\#}"; if [ -n "${CHANNELS[$c]:-}" ]; then MODE=channel; TARGET="${CHANNELS[$c]}"; LABEL="#$c"; else echo "[ERROR] unknown channel: $RECIPIENT (try: discord-dm.sh list)" >&2; exit 1; fi ;;
|
|
*)
|
|
if [ -n "${USERS[$key]:-}" ]; then MODE=dm; TARGET="${USERS[$key]}"; LABEL="$key (DM)"
|
|
elif printf '%s' "$key" | grep -qE '^[0-9]{17,19}$'; then MODE=dm; TARGET="$key"; LABEL="user ${key} (DM)"
|
|
else echo "[ERROR] unknown recipient: $RECIPIENT (try: discord-dm.sh list)" >&2; exit 1; fi ;;
|
|
esac
|
|
|
|
# --- bot 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"
|
|
[ -f "$ENV_FILE" ] && TOKEN="$(grep -iE '^[[:space:]]*DISCORD_TOKEN[[:space:]]*=' "$ENV_FILE" | head -1 | sed -E 's/^[^=]*=[[:space:]]*//; s/^["'"'"']//; s/["'"'"'][[:space:]]*$//')"
|
|
fi
|
|
if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then echo "[ERROR] no bot token (vault + .env both empty)" >&2; exit 2; fi
|
|
|
|
auth=(-H "Authorization: Bot ${TOKEN}" -H "Content-Type: application/json" -H "User-Agent: ${UA}")
|
|
|
|
# --- for a DM, open (or fetch) the user's DM channel ---
|
|
if [ "$MODE" = "dm" ]; then
|
|
DM="$(printf '%s' "$(jq -nc --arg r "$TARGET" '{recipient_id:$r}')" | \
|
|
curl -s -m 15 "${auth[@]}" -X POST "$API/users/@me/channels" --data-binary @-)"
|
|
CHID="$(printf '%s' "$DM" | jq -r '.id // empty' 2>/dev/null)"
|
|
if [ -z "$CHID" ]; then echo "[ERROR] could not open DM channel for $LABEL: $DM" >&2; exit 3; fi
|
|
TARGET="$CHID"
|
|
fi
|
|
|
|
# --- post the message (printf | --data-binary @- — direct -d mangles multiline JSON) ---
|
|
RESP="$(printf '%s' "$(jq -nc --arg c "$MSG" '{content:$c}')" | \
|
|
curl -s -m 15 -w $'\n%{http_code}' "${auth[@]}" \
|
|
-X POST "$API/channels/${TARGET}/messages" --data-binary @-)"
|
|
HTTP="$(printf '%s' "$RESP" | tail -n1)"
|
|
BODY="$(printf '%s' "$RESP" | sed '$d')"
|
|
|
|
if [ "$HTTP" = "200" ]; then
|
|
echo "[OK] discord-dm: sent to ${LABEL} (message_id=$(printf '%s' "$BODY" | jq -r '.id // empty'))"
|
|
exit 0
|
|
fi
|
|
echo "[ERROR] discord-dm: Discord returned ${HTTP:-no-response} — ${BODY}" >&2
|
|
exit 3
|