tgen

tgen is a command-line tool that generates ready-to-use API bindings from
the Telegram Bot API HTML documentation.
Instead of relying on manually updated boilerplate, tgen parses the specification to generate
strongly-typed client code.
[!WARNING]
Work in Progress: This project is in early development. The generated code is currently
incomplete and may not be fully functional. The API is subject to breaking changes.
Features
-
Up-to-Date by Default: New API methods and standard types are picked up automatically — just
run tgen go after a new Telegram Bot API release.
-
Spec-Faithful Types: Ambiguous spec types become real Go types. The Telegram API describes
chat_id as Integer or String. Instead of collapsing this into any, tgen generates a proper
union type with explicit variants:
// address a public channel by username
ChatID: api.ChatID{Username: new("@news")},
// or a group by its numeric ID — same field, different variant
ChatID: api.ChatID{ID: new(int64(-1001122334455))},
-
Deterministic Builds: Supports local HTML files for reproducibility or offline work.
Installation
If you use mise, install the latest release globally:
mise use -g github:andreychh/tgen
Using Go
With Go available, you can fetch the latest version directly:
go install github.com/andreychh/tgen@latest
Pre-built Binaries
You can download pre-compiled binaries for your operating system (Linux, macOS, Windows) from
the Releases page.
Usage
tgen uses subcommands to target specific languages — currently only go is available.
Fetch from the web
Downloads and parses the specification directly from the Telegram website, writing the generated
files to ./api:
tgen go --out ./api
Use a local file
If you have downloaded the HTML specification locally, pass the file path using the --spec or -s
flag. This is recommended to ensure build reproducibility and avoid network issues:
# Download the specification
curl -o api.html https://core.telegram.org/bots/api
# Generate the code
tgen go -s ./api.html -o ./api
Generated API
Go
The generated API follows a consistent pattern: a constructor takes a Connection and returns an
endpoint, which you call with a typed request struct to get a typed response. You can replace
HTTPConnection with any implementation to add retries, proxy requests, or inject FakeConnection
in tests:
package main
import (
"context"
"fmt"
"net/http"
"os"
"awesome-bot/api"
)
func main() {
conn := api.NewHTTPConnection(http.DefaultClient, os.Getenv("BOT_TOKEN"))
ctx := context.Background()
msg, err := api.NewSendMessageEndpoint(conn).Call(ctx, api.SendMessageRequest{
ChatID: api.ChatID{Username: new("@news")},
Text: "Hello from tgen!",
})
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Printf("Sent message %d\n", msg.MessageID)
}
Testing
FakeConnection provides canned responses for unit tests: use api.Ok(v) for a successful result
and api.Err(err) to simulate a failure.
func TestSendMessage(t *testing.T) {
conn := api.NewFakeConnection(api.Responses{
api.MethodSendMessage: api.Ok(api.Message{MessageID: 42}),
})
msg, err := api.NewSendMessageEndpoint(conn).Call(
context.Background(),
api.SendMessageRequest{
ChatID: api.ChatID{Username: new("@news")},
Text: "Hello from tgen!",
},
)
assert.NoError(t, err)
assert.Equal(t, int64(42), msg.MessageID, "SendMessage must return the sent message")
}
func TestSendMessage_Failure(t *testing.T) {
conn := api.NewFakeConnection(api.Responses{
api.MethodSendMessage: api.Err(errors.New("unauthorized")),
})
_, err := api.NewSendMessageEndpoint(conn).Call(
context.Background(),
api.SendMessageRequest{
ChatID: api.ChatID{Username: new("@news")},
Text: "Hello from tgen!",
},
)
assert.ErrorContains(t, err, "unauthorized")
}
Contributing
Contributions are welcome! As the project evolves, help with refining the HTML parser and generation
templates is highly appreciated. Whether you're fixing a bug, improving documentation, or adding a
new feature — feel free to open a pull request.
Getting started
-
Install mise-en-place — the toolchain manager we use.
-
Set up the toolchain — installs Go, Task, and all other tools declared in .config/mise.toml:
mise install
-
Explore available tasks:
task --list
-
Before submitting a PR, make sure linters and tests pass:
task lint test
If this fails in your environment for reasons unrelated to your changes,
please open an issue.
[!TIP]
Renovate automatically keeps all dependencies up to date. Once
its PRs are merged, run the following to update your local toolchain:
mise upgrade