Files
scrabble-game/platform/telegram
Ilia Denisov 52f898ca6f
Tests · Go / test (push) Successful in 7s
Tests · Integration / integration (push) Successful in 11s
Tests · UI / test (push) Successful in 20s
Tests · Go / test (pull_request) Successful in 6s
Tests · Integration / integration (pull_request) Successful in 11s
Tests · UI / test (pull_request) Successful in 19s
Stage 11: account linking & merge (email + Telegram Login Widget)
Link an email (confirm-code) or Telegram (web Login Widget) to the current
account; if the identity already has its own account, merge the two into the
one in use (the current account is primary, except a guest initiator whose
durable counterpart wins). The merge runs in one transaction
(internal/accountmerge): stats + hint wallet summed, paid_account ORed,
identities/games/chat/complaints transferred, friends/blocks de-duplicated,
the secondary kept as a merged_into tombstone so a shared finished game's
no-cascade FKs hold; a shared active game blocks the merge.

- migration 00009: accounts.paid_account, merged_into, merged_at (+ jetgen)
- internal/link orchestrator; session.RevokeAllForAccount on merge
- connector ValidateLoginWidget RPC + loginwidget HMAC validator
- edge ops link.email.request/confirm/merge, link.telegram.confirm/merge;
  supersedes the Stage 8 email.bind.* surface (request never reveals 'taken'
  before the code is verified, so a probe cannot enumerate addresses)
- UI Profile link section + irreversible-merge dialog; Telegram web sign-in
- focused regression tests (merge core, guest inversion, active-game refusal,
  finished-shared-game kept), gateway transcode + connector + UI codec/e2e
- docs: PLAN, ARCHITECTURE 3/4/9, FUNCTIONAL(+ru), module READMEs
2026-06-04 11:15:14 +02:00
..

scrabble/platform/telegram — Telegram connector

The Telegram platform side-service. It is the only component that holds the bot token: it runs the Bot API long-poll loop (Mini App launch + deep-links) and serves the connector gRPC API that the gateway and backend call over the trusted internal network. See docs/ARCHITECTURE.md §1/§3/§10/§12.

Responsibilities

  • Mini App auth. ValidateInitData verifies Telegram Web App initData (HMAC under the bot token) and returns the user identity. The gateway calls it during the auth.telegram edge operation, then provisions the session through the backend internal API — so the bot token never leaves this process.
  • Out-of-app push. Notify renders a backend push event (your_turn, nudge, match_found, and the invitation / friend_request notify sub-kinds) into a localized message with a Mini App launch button and sends it. The gateway calls it only for a recipient with no live in-app stream and the notifications_in_app_only flag off, so the platform push never duplicates in-app delivery.
  • Bot chat. /start <payload> (and the chat menu button) reply with a Mini App launch button; a deep-link payload routes the launch to a game / invitation / friend code.
  • Admin messaging (wired in Stage 10). SendToUser and SendToGameChannel send arbitrary text to one user or the configured game channel.

The generic methods (Notify, SendToUser, SendToGameChannel) address a recipient by the identity external_id (as in the backend identities table), so a future VK / MAX connector can implement the same service; only ValidateInitData is Telegram-specific.

gRPC API

pkg/proto/telegram/v1, service Telegram: ValidateInitData, ValidateLoginWidget, Notify, SendToUser, SendToGameChannel. Generated Go is committed under pkg. ValidateLoginWidget (Stage 11) verifies Telegram Login Widget web sign-in data — HMAC under SHA-256(bot_token), distinct from initData (internal/loginwidget) — for attaching a Telegram identity to an account from a browser.

Shared verbatim with the UI (ui/src/lib/deeplink.ts). A Mini App start parameter is a one-character kind prefix plus a value:

Parameter Destination
g<game uuid> open that game
i<invitation uuid> open that invitation
f<6-digit code> redeem that friend code
empty / unknown the lobby

The bot turns a /start <payload> or a notification target into a launch-button URL <MiniAppURL>?startapp=<payload>.

Configuration

Env var Default Meaning
TELEGRAM_BOT_TOKEN — (required) Bot API token + the initData HMAC secret
TELEGRAM_MINIAPP_URL — (required) Mini App HTTPS origin (BotFather-registered)
TELEGRAM_GRPC_ADDR :9091 connector gRPC listen address
TELEGRAM_API_BASE_URL https://api.telegram.org Bot API host override (mock / self-hosted)
TELEGRAM_TEST_ENV false route to the Bot API test environment (/bot<token>/test/METHOD)
TELEGRAM_GAME_CHANNEL_ID game channel chat id for SendToGameChannel
TELEGRAM_LOG_LEVEL info zap log level

The test environment is selected by TELEGRAM_TEST_ENV=true, which suffixes the Bot API path with /test (the connector appends it to the token, since the client builds <host>/bot<token>/<method>).

Build, test, run

go build ./platform/telegram/...
go test ./platform/telegram/...        # unit tests use an httptest fake Bot API
go run ./platform/telegram/cmd/telegram # needs a real TELEGRAM_BOT_TOKEN

Deploy

The connector runs in its own container with the bot token held only there and all egress through a VPN sidecar (deploy/docker-compose.yml, mirroring ../../15-puzzle). It needs no public ingress — it long-polls Telegram and answers internal gRPC at telegram:9091 on the shared edge network. The host reverse proxy routes public traffic to the gateway port only, which serves the Mini App under /telegram/. The full multi-service deploy lands with Stage 12.

A real end-to-end Telegram smoke needs a BotFather bot, its token, a public HTTPS Mini App origin, and the connector container; the unit tests cover the wire format, templates, deep-links and the gRPC handlers without a live bot.