Architecture
┌─────────────────────────────────────────────────────────────────┐
│ HTTP Request │
│ POST /bot<TOKEN>/<method> │
└──────────┬──────────────────────────────────────────────────────┘
│
▼
┌──────────────────────┐ parses multipart/form-data + query params
│ internal/server │──────────────────────────────┐
│ (net/http, no fwk) │ │
└──────────┬───────────┘ ▼
│ routes by bot token ┌─────────────────────┐
▼ │ internal/response │
┌──────────────────────┐ │ JSON envelope: │
│ internal/manager │◄─────────────────┤ {ok,result,error} │
│ ClientManager │ └─────────────────────┘
│ • token routing │
│ • per-bot lifecycle │
│ • flood control │
└──────────┬───────────┘
│ creates/reuses one Client per bot
▼
┌──────────────────────┐ raw tg.RPCClient ────► Telegram MTProto
│ internal/client │ ▲
│ Per-bot Client │ ┌─────────────┴──────────────┐
│ • 180 handlers │ │ tg types ↔ Bot API JSON │
│ • dispatch table │ └────────────────────────────┘
└──┬───────┬───────┬───┘
│ │ │
│ │ └──────────────────────────────────────────┐
│ ▼ ▼
│ ┌────────────────┐ ┌──────────────────┐
│ │ internal/tqueue│ │ internal/webhook │
│ │ Update queue │ │ Outgoing webhooks│
│ │ (monotonic IDs)│ │ • SSRF protection│
└───────┬────────┘ │ • flood gates │
│ │ • retry/Retry-After│
│ persists └──────────────────┘
▼
┌──────────────────────┐
│ internal/storage │
│ SQLite (pure Go) │
│ • update log │
│ • webhook config │
│ • peer cache │
└──────────────────────┘Request flow
internal/server— a rawnet/httpserver parsesmultipart/form-dataand query params (mirrors the officialHttpServer/Query).internal/manager—ClientManagerroutes by bot token, manages the per-bot lifecycle, and applies flood control.internal/client— the per-botClientruns all 180 Bot API method handlers via a dispatch table.internal/convert— translates between mtgotgtypes and Bot API JSON types in both directions.internal/response— wraps every result in the{ok, result, error_code, description, parameters}envelope.internal/tqueue+internal/storage— updates pass through the monotonic queue and are persisted to SQLite.internal/webhook— delivers updates to registered webhooks with SSRF protection and retry/backoff.
Key design principles
Raw TL layer, not high-level wrappers. The per-bot
Clientconstructstg.*Requeststructs and invokes them viatg.RPCClient— the exact TL schema (layer 225). No opinionated convenience methods. This mirrors how the official server callstd_apimethods directly rather than through wrappers.Per-bot isolation. Each bot gets its own
Client, connection, session, and SQLite database (.mtgo-bot-api/<bot_id>/bot.db). No shared state between bots.Persistent update queue. Updates survive restarts. The TQueue stores events with monotonic IDs and replays them on
getUpdates. The storage callback persists every push to SQLite.Exact error mapping. MTProto RPC errors are translated to Bot API error messages and HTTP codes exactly as the official
Client.cppdoes.
The raw tg layer convention
mtgo-bot-api talks to MTProto via the generated tg package (github.com/mtgo-labs/mtgo/tg), not mtgo's high-level telegram.Client convenience methods or its Bound* methods.
// telegram.Client owns only the connection/session/bot auth.
cl, _ := telegram.NewClient(apiID, apiHash, &telegram.Config{BotToken: tok})
// Take the raw RPC client and invoke generated TL request structs directly:
rpc := cl.RPC() // *tg.RPCClient
result, err := rpc.MessagesSendMessage(ctx, &tg.MessagesSendMessageRequest{
Peer: inputPeer,
Message: text,
RandomID: randID,
})Why raw TL?
The high-level telegram.Client methods add abstractions/opinions that don't always map cleanly onto Bot API semantics. Generated request structs are the exact TL schema, giving precise 1:1 control over every RPC call — the same way the official server invokes td_api methods directly.
Build a tg.RPCClient once per bot, construct TL request structs in internal/client/, and decode the returned tg.TLObject in internal/convert/. Do not import high-level telegram helpers that re-wrap these calls.
Project layout
mtgo-bot-api/
├── cmd/
│ └── mtgo-bot-api/ # Entry point: CLI flags, bootstrap, graceful shutdown
│
├── internal/
│ ├── server/ # Raw net/http server, multipart/query parsing
│ ├── manager/ # ClientManager: multi-bot lifecycle, token routing
│ ├── client/ # Per-bot Client: all 180 Bot API method handlers
│ ├── tqueue/ # TQueue: monotonic update queue + storage callback
│ ├── storage/ # SQLite persistence (pure Go, no CGO)
│ ├── webhook/ # Outgoing webhook delivery + SSRF protection
│ ├── convert/ # MTProto tg types ↔ Bot API JSON types
│ ├── types/ # Bot API type structs (User, Chat, Message, Update, …)
│ ├── fileid/ # file_id / file_unique_id encode/decode (parity with TDLib)
│ ├── log/ # TDLib-compatible stderr logger (verbosity 0–4)
│ ├── response/ # JSON response envelope
│ ├── stats/ # Per-bot and global request/update statistics
│ └── version/ # Static build version + Bot API spec version
│
├── examples/ # Multi-library example bots (Go, TypeScript, Python)
├── schema/ # Scraped Bot API schema + parity certification
└── go.mod