sync: auto-sync from HOWARD-HOME at 2026-07-07 20:31:16

Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-07-07 20:31:16
This commit is contained in:
2026-07-07 20:31:47 -07:00
parent 9340cdec17
commit a2a277ab67
8 changed files with 200 additions and 13 deletions

View File

@@ -57,6 +57,8 @@ if [ "${1:-}" = "--read" ]; then
THREAD="${2:-}"
LIMIT="${3:-25}"
[ -z "$THREAD" ] && { echo "[ERROR] usage: ask-forum.sh --read <thread_id> [limit]" >&2; exit 1; }
printf '%s' "$LIMIT" | grep -qE '^[0-9]+$' || LIMIT=25 # Discord caps at 100
[ "$LIMIT" -lt 1 ] && LIMIT=1; [ "$LIMIT" -gt 100 ] && LIMIT=100
TOKEN="$(resolve_token)"
[ -z "$TOKEN" ] || [ "$TOKEN" = "null" ] && { echo "[ERROR] no Discord bot token" >&2; exit 2; }
auth=(-H "Authorization: Bot ${TOKEN}" -H "User-Agent: ${UA}")
@@ -86,15 +88,20 @@ if [ "${1:-}" = "--wait" ]; then
TOKEN="$(resolve_token)"
[ -z "$TOKEN" ] || [ "$TOKEN" = "null" ] && { echo "[ERROR] no Discord bot token" >&2; exit 2; }
auth=(-H "Authorization: Bot ${TOKEN}" -H "User-Agent: ${UA}")
# baseline = id of newest message present right now
AFTER="$(curl -s -m 20 "${auth[@]}" "$API/channels/${THREAD}/messages?limit=1" | jq -r '.[0].id // "0"' 2>/dev/null)"
[ -z "$AFTER" ] && AFTER="0"
# baseline: for a forum post the starter message id == the thread id, so
# after=<thread> returns every human reply after the starter -- this catches a
# reply that already landed BEFORE this --wait began (the old "newest message
# now" baseline missed it and hung to timeout).
AFTER="${THREAD}"
echo "[INFO] waiting on thread ${THREAD} for a human reply (up to ${TIMEOUT}s)..." >&2
DEADLINE=$(( $(date +%s) + TIMEOUT ))
while [ "$(date +%s)" -lt "$DEADLINE" ]; do
sleep "$POLL"
MSGS="$(curl -s -m 20 "${auth[@]}" "$API/channels/${THREAD}/messages?after=${AFTER}")"
printf '%s' "$MSGS" | jq -e 'type=="array"' >/dev/null 2>&1 || continue
if ! printf '%s' "$MSGS" | jq -e 'type=="array"' >/dev/null 2>&1; then
# thread gone -> bail fast; otherwise (transient/429) keep waiting
printf '%s' "$MSGS" | jq -e '.code==10003' >/dev/null 2>&1 && { echo "[ERROR] thread ${THREAD} no longer exists" >&2; exit 3; }
sleep "$POLL"; continue
fi
HUMAN="$(printf '%s' "$MSGS" | jq -c 'reverse[] | select(.author.bot|not) | {u:.author.username, c:.content}')"
if [ -n "$HUMAN" ]; then
echo "[OK] answer received in thread ${THREAD}:"
@@ -102,6 +109,7 @@ if [ "${1:-}" = "--wait" ]; then
echo "THREAD_ID=${THREAD}"
exit 0
fi
sleep "$POLL"
done
echo "[TIMEOUT] no human reply on thread ${THREAD} within ${TIMEOUT}s" >&2
echo "THREAD_ID=${THREAD}" >&2
@@ -149,7 +157,18 @@ fi
auth=(-H "Authorization: Bot ${TOKEN}" -H "Content-Type: application/json" -H "User-Agent: ${UA}")
# --- create the forum post (thread) with the question ---
BODY="$(jq -nc --arg name "$TITLE" --arg c "$CONTENT" '{name:$name, message:{content:$c}}')"
# Discord caps message content at 2000 chars. Keep the starter <=1900 and send any
# overflow as follow-up messages in the created thread (rare, but don't hard-fail).
LIMIT_CHARS=1900
STARTER_CONTENT="$CONTENT"
OVERFLOW=""
if [ "${#CONTENT}" -gt "$LIMIT_CHARS" ]; then
STARTER_CONTENT="${CONTENT:0:$LIMIT_CHARS}"
nl="${STARTER_CONTENT%$'\n'*}"
[ "${#nl}" -lt "${#STARTER_CONTENT}" ] && [ "${#nl}" -gt 0 ] && STARTER_CONTENT="$nl"
OVERFLOW="${CONTENT:${#STARTER_CONTENT}}"; OVERFLOW="${OVERFLOW#$'\n'}"
fi
BODY="$(jq -nc --arg name "$TITLE" --arg c "$STARTER_CONTENT" '{name:$name, message:{content:$c}}')"
RESP="$(printf '%s' "$BODY" | curl -s -m 20 -w $'\n%{http_code}' "${auth[@]}" \
-X POST "$API/channels/${FORUM_ID}/threads" --data-binary @-)"
HTTP="$(printf '%s' "$RESP" | tail -n1)"
@@ -165,17 +184,29 @@ if [ -z "$THREAD" ]; then
echo "[ERROR] posted but no thread id in response: $(printf '%s' "$JSON" | head -c 200)" >&2
exit 3
fi
# --- post any overflow chunks as follow-up messages in the new thread ---
rest="$OVERFLOW"
while [ -n "$rest" ]; do
piece="${rest:0:$LIMIT_CHARS}"
nl="${piece%$'\n'*}"; [ "${#nl}" -lt "${#piece}" ] && [ "${#nl}" -gt 0 ] && piece="$nl"
printf '%s' "$(jq -nc --arg c "$piece" '{content:$c}')" | \
curl -s -m 15 "${auth[@]}" -X POST "$API/channels/${THREAD}/messages" --data-binary @- >/dev/null 2>&1
rest="${rest:${#piece}}"; rest="${rest#$'\n'}"
done
echo "[INFO] asked in #ct-forum (thread=${THREAD}) — waiting up to ${TIMEOUT}s for a human reply..." >&2
# --- block-poll the thread for the first NON-BOT reply ---
AFTER="${STARTER:-0}"
# starter message id == thread id for a forum post, so after=<starter> = all replies.
AFTER="${STARTER:-$THREAD}"
DEADLINE=$(( $(date +%s) + TIMEOUT ))
while [ "$(date +%s)" -lt "$DEADLINE" ]; do
sleep "$POLL"
Q="after=${AFTER}"
[ "$AFTER" = "0" ] && Q="limit=50"
MSGS="$(curl -s -m 20 "${auth[@]}" "$API/channels/${THREAD}/messages?${Q}")"
printf '%s' "$MSGS" | jq -e 'type=="array"' >/dev/null 2>&1 || continue
MSGS="$(curl -s -m 20 "${auth[@]}" "$API/channels/${THREAD}/messages?after=${AFTER}")"
if ! printf '%s' "$MSGS" | jq -e 'type=="array"' >/dev/null 2>&1; then
printf '%s' "$MSGS" | jq -e '.code==10003' >/dev/null 2>&1 && { echo "[ERROR] thread ${THREAD} disappeared" >&2; exit 3; }
continue
fi
# oldest-first; keep only human (non-bot) messages
HUMAN="$(printf '%s' "$MSGS" | jq -c 'reverse[] | select(.author.bot|not) | {u:.author.username, c:.content, id:.id}')"
if [ -n "$HUMAN" ]; then