Attach images to posts and return the Telegram message id

The adapter response now carries telegram_message_id and a t.me/c link.
compose visual resolves a post's image: own visual_url, else the entry's
rendered portrait, else hold an entry with only a visual prompt, else the
HelixForge main image. send-post.sh resolves before touching credentials,
requires an image on public posts and runs the adapter with ATTACH_VISUAL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5457@bnt-lap001
Assistant-Session: 792bf84a-3783-4f5d-a811-cded61dd2012
This commit is contained in:
tegwick 2026-10-08 10:20:09 +02:00
parent b61261d042
commit 4861dd994b
7 changed files with 342 additions and 12 deletions

View file

@ -42,6 +42,38 @@ TELEGRAM_CHANNEL_ID=$(go run ./cmd/provision adapter-config --spec "$spec" --cha
tmp=$(mktemp -d)
go build -o "$tmp/adapter" ./cmd/adapter
go build -o "$tmp/compose" ./cmd/compose
# The image, resolved before any credential is touched. A post's own
# request.visual_url wins; otherwise its source entry's rendered portrait;
# an entry with only a visual prompt is held until the portrait is rendered;
# a post with no prompt at all (an outlet post) carries the campaign image.
# Public posts always carry an image.
hall=${HALL_DIR:-$(realpath ../hall-of-helix)}
forge=${FORGE_RAW:-https://forgejo.coulomb.social/coulomb}
python3 - "$post" "$channel" "$tmp" "$hall" "$forge" <<'PY' || { rm -rf "$tmp"; exit 2; }
import json, os, subprocess, sys, yaml
post, channel, tmp, hall, forge = sys.argv[1:]
d = yaml.safe_load(open(post))
req = d["request"]
if not req.get("visual_url"):
cmd = [f"{tmp}/compose", "visual", "--hall-dir", hall,
"--hall-raw-base", f"{forge}/hall-of-helix/raw/branch/main",
"--fallback", f"{forge}/pr-hall-of-helix/raw/branch/main/assets/helix-forge.png"]
if d.get("source_entry_path"):
cmd += ["--entry", os.path.join(hall, d["source_entry_path"])]
v = json.loads(subprocess.run(cmd, check=True, capture_output=True, text=True).stdout)
if v["kind"] == "needs-render":
sys.exit(f"held: the source entry has a visual prompt but no rendered portrait "
f"(expected at {v.get('target') or 'an unnamed visuals/ file'}); render it into the hall first")
req["visual_url"] = v["visual_url"]
print(f"image: {v['kind']} {v['visual_url']}")
else:
print(f"image: post {req['visual_url']}")
if channel == "public" and not req.get("visual_url"):
sys.exit("public posts carry an image")
json.dump(req, open(f"{tmp}/request.json", "w"))
PY
# Read the KV v2 data path directly: `bao kv get` first queries
# sys/internal/ui/mounts, which a workload-scoped token may not be allowed to.
@ -50,7 +82,7 @@ token=$(bao read -format=json \
| python3 -c 'import sys, json; print(json.load(sys.stdin)["data"]["data"]["token"])') \
|| { rm -rf "$tmp"; echo "could not read the bot token from OpenBao (logged in? read on platform/data/workloads/infotech/fluid-telegram/hall-of-helix/*?)" >&2; exit 2; }
TELEGRAM_BOT_TOKEN=$token TELEGRAM_CHANNEL_ID=$TELEGRAM_CHANNEL_ID \
TELEGRAM_BOT_TOKEN=$token TELEGRAM_CHANNEL_ID=$TELEGRAM_CHANNEL_ID ATTACH_VISUAL=true \
ADAPTER_LISTEN_ADDR=127.0.0.1:$port "$tmp/adapter" &
pid=$!
unset token
@ -61,6 +93,5 @@ for _ in $(seq 50); do
sleep 0.1
done
python3 -c 'import sys, yaml, json; print(json.dumps(yaml.safe_load(open(sys.argv[1]))["request"]))' "$post" \
| curl -sS -w '\nHTTP %{http_code}\n' -X POST -H 'Content-Type: application/json' \
--data-binary @- "http://127.0.0.1:$port/v1/channel-posts"
curl -sS -w '\nHTTP %{http_code}\n' -X POST -H 'Content-Type: application/json' \
--data-binary @"$tmp/request.json" "http://127.0.0.1:$port/v1/channel-posts"