diff --git a/internal/adaptercfg/adaptercfg.go b/internal/adaptercfg/adaptercfg.go index c8f0b9c..c8d680a 100644 --- a/internal/adaptercfg/adaptercfg.go +++ b/internal/adaptercfg/adaptercfg.go @@ -52,11 +52,22 @@ func FromResolved(rs *state.Resolved, channel, tokenRef string) (Config, error) return Config{ Campaign: rs.Campaign, Channel: channel, - ChatID: ch.ChatID, + ChatID: BotAPIChatID(ch.ChatID), TokenRef: tokenRef, }, nil } +// BotAPIChatID converts a channel id as the provisioner records it (the +// MTProto channel id, positive) into the form the Bot API addresses it by +// (-100 followed by the id). An id that is already negative is in Bot API +// form and is returned unchanged. +func BotAPIChatID(id int64) int64 { + if id > 0 { + return -1000000000000 - id + } + return id +} + // EnvFile is the non-secret configuration an adapter process is started with. // The token is named, not emitted: the runtime injects it from OpenBao. func (c Config) EnvFile() string { diff --git a/internal/adaptercfg/adaptercfg_test.go b/internal/adaptercfg/adaptercfg_test.go index a967787..1d2d136 100644 --- a/internal/adaptercfg/adaptercfg_test.go +++ b/internal/adaptercfg/adaptercfg_test.go @@ -112,3 +112,18 @@ func TestLoadTokenReadsOnlyTheBotToken(t *testing.T) { } } } + +func TestBotAPIChatIDPrefixesChannelIDs(t *testing.T) { + if got := BotAPIChatID(4427430639); got != -1004427430639 { + t.Fatalf("BotAPIChatID(4427430639) = %d", got) + } + if got := BotAPIChatID(-1001); got != -1001 { + t.Fatalf("an id already in Bot API form changed: %d", got) + } + rs := provisioned() + rs.Channels[spec.Test] = state.Channel{ChatID: 4427430639, AdminRights: []string{spec.PostMessages}} + cfg, err := FromResolved(rs, spec.Test, "ref") + if err != nil || cfg.ChatID != -1004427430639 { + t.Fatalf("FromResolved chat id = %d, err %v", cfg.ChatID, err) + } +} diff --git a/scripts/send-post.sh b/scripts/send-post.sh new file mode 100755 index 0000000..9d965da --- /dev/null +++ b/scripts/send-post.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# Send one prepared post (pr-hall-of-helix/posts/.yaml) through a +# short-lived local adapter, then stop it. +# +# Run it yourself, logged in to OpenBao (BAO_ADDR/BAO_TOKEN set). The bot token +# goes from OpenBao straight into the adapter's environment and is never +# printed. The adapter's store is in memory, so idempotency on post_id holds +# only within one run: do not re-run for a post that already went out. +# +# scripts/send-post.sh ~/pr-hall-of-helix/posts/2026-10-08-test-outlet-parametrization.yaml test +set -euo pipefail + +post=$(realpath "$1") +channel=${2:-test} +spec=${SPEC:-$HOME/pr-hall-of-helix/presence/telegram.yaml} +port=${PORT:-18089} +cd "$(dirname "$0")/.." + +read -r want reviewed < <(python3 -c ' +import sys, yaml +d = yaml.safe_load(open(sys.argv[1])) +print(d["channel"], d["request"].get("reviewed_by") or "-")' "$post") +[ "$want" = "$channel" ] || { echo "post is for channel '$want', not '$channel'" >&2; exit 2; } +[ "$reviewed" != "-" ] || { echo "request.reviewed_by is empty; a person reviews before sending" >&2; exit 2; } + +TELEGRAM_CHANNEL_ID=$(go run ./cmd/provision adapter-config --spec "$spec" --channel "$channel" \ + | sed -n 's/^TELEGRAM_CHANNEL_ID=//p') +[ -n "$TELEGRAM_CHANNEL_ID" ] || { echo "no chat id for channel '$channel'" >&2; exit 2; } + +tmp=$(mktemp -d) +go build -o "$tmp/adapter" ./cmd/adapter + +token=$(bao kv get -mount=platform -field=token \ + workloads/infotech/fluid-telegram/hall-of-helix/bot-token) \ + || { rm -rf "$tmp"; echo "could not read the bot token from OpenBao (logged in?)" >&2; exit 2; } + +TELEGRAM_BOT_TOKEN=$token TELEGRAM_CHANNEL_ID=$TELEGRAM_CHANNEL_ID \ + ADAPTER_LISTEN_ADDR=127.0.0.1:$port "$tmp/adapter" & +pid=$! +unset token +trap 'kill $pid 2>/dev/null; rm -rf "$tmp"' EXIT + +for _ in $(seq 50); do + curl -s -o /dev/null "http://127.0.0.1:$port/" && break + 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"