diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..13775d9 --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,33 @@ +name: pages + +on: + push: + branches: [main] + paths: + - 'docs/**' + - '.github/workflows/pages.yml' + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: false + +jobs: + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/configure-pages@v5 + - uses: actions/upload-pages-artifact@v3 + with: + path: docs/ + - id: deployment + uses: actions/deploy-pages@v4 diff --git a/docs/CNAME b/docs/CNAME new file mode 100644 index 0000000..1c11351 --- /dev/null +++ b/docs/CNAME @@ -0,0 +1 @@ +go-telegram.raczylo.com diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..fa13a7b --- /dev/null +++ b/docs/index.html @@ -0,0 +1,948 @@ + + +
+ + +
+ 176 methods. 1408 generated tests. Zero any in the public surface.
+
package main
+
+import (
+ "context"
+ "log"
+ "os"
+
+ "github.com/lukaszraczylo/go-telegram/api"
+ "github.com/lukaszraczylo/go-telegram/client"
+ "github.com/lukaszraczylo/go-telegram/dispatch"
+)
+
+func main() {
+ bot, _ := client.NewRetryDoer(os.Getenv("BOT_TOKEN"), nil)
+ d := dispatch.New(bot)
+
+ d.OnMessage(func(ctx context.Context, msg *api.Message) {
+ bot.SendMessage(ctx, &api.SendMessageParams{
+ ChatID: api.ChatIDFromInt(msg.Chat.ID),
+ Text: msg.Text,
+ })
+ })
+
+ if err := d.Run(context.Background()); err != nil {
+ log.Fatal(err)
+ }
+}
+ Built for correctness, composability, and production reliability
+IR + emitter pipeline runs weekly; a PR opens automatically for any Telegram-side change.
+any in the public APITyped unions — ChatID, MessageOrBool — sealed interfaces with marker methods and auto-decode.
Drop in fasthttp, sonic, or goccy/go-json with a one-line swap.
+Retry middleware honouring retry_after, panic recovery, structured errors with sentinels.
Generic Handler[T], composable filters, conversation handler with pluggable storage, per-update goroutine pool.
Every regen runs scrape → audit → emit → 1408 generated tests. Nothing ships without passing.
+How go-telegram compares to other Go Telegram bot libraries
+HTML scraper · this library
+JSON spec · popular library
+Hand-coded
+| Feature | +go-telegram | +gotgbot/v2 | +telegram-bot-api/v5 | +
|---|---|---|---|
| Generated from spec | +✓ HTML scraper | +✓ JSON spec | +✗ hand-coded | +
Typed unions (no any) |
+ ✓ ChatID, MessageOrBool, sealed interfaces | +△ partial | +✗ | +
| Auto-generated tests | +✓ 1,408 (8 scenarios/method) | +✗ | +✗ | +
| Conversation handler | +✓ pluggable storage | +✓ | +✗ | +
| Retry middleware | +✓ honours retry_after | +△ user-implemented | +✗ | +
| Pluggable JSON codec | +✓ | +✗ | +✗ | +
One command. No CGO, no system deps.
+Requires Go 1.21+
+go get github.com/lukaszraczylo/go-telegram
+ Full API reference
+Upstream spec this library tracks
+Echo bot — the minimal working example
+package main
+
+import (
+ "context"
+ "log"
+ "os"
+
+ "github.com/lukaszraczylo/go-telegram/api"
+ "github.com/lukaszraczylo/go-telegram/client"
+ "github.com/lukaszraczylo/go-telegram/dispatch"
+)
+
+func main() {
+ // NewRetryDoer wraps the default transport with retry middleware
+ // that honours Telegram's retry_after field automatically.
+ bot, err := client.NewRetryDoer(os.Getenv("BOT_TOKEN"), nil)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ d := dispatch.New(bot)
+
+ // Handler[T] is generic — the type parameter is the concrete update type.
+ d.OnMessage(func(ctx context.Context, msg *api.Message) {
+ _, err := bot.SendMessage(ctx, &api.SendMessageParams{
+ // ChatIDFromInt returns a typed ChatID — no interface{} here.
+ ChatID: api.ChatIDFromInt(msg.Chat.ID),
+ Text: msg.Text,
+ })
+ if err != nil {
+ log.Printf("send error: %v", err)
+ }
+ })
+
+ if err := d.Run(context.Background()); err != nil {
+ log.Fatal(err)
+ }
+}
+ The emitter scrapes the live Telegram Bot API docs (HTML), builds an intermediate representation (api.json), then generates Go code and tests. An audit step validates every method signature against the IR before emission.
make snapshot && make regen
+ 14 runnable bots covering the most common patterns
+Minimal echo bot — get up and running in 30 lines.
+Inline keyboard buttons and callback query handling.
+Inline mode queries and result sets.
+Multi-step conversation flows with pluggable state storage.
+Per-user state machine pattern.
+Admin commands — ban, kick, restrict members.
+Composable middleware chain — logging, auth, rate limiting.
+Upload and download photos, documents, audio.
+Webhook server instead of long-polling.
+Send polls and handle poll-answer updates.
+Telegram Payments — invoices, pre-checkout, successful payment.
+Paginated inline keyboards for long result sets.
+Greet new members joining a group or channel.
+Automated content moderation with filter chains.
+Power-user patterns — expand to read
+
+ The conversation handler chains multiple message steps and stores state between them. Any storage backend implementing the
+ StateStorage interface works — in-memory, Redis, Postgres.
+
conv := conversation.New(storage)
+
+conv.AddState("ask_name", func(ctx context.Context, msg *api.Message) (string, error) {
+ bot.SendMessage(ctx, &api.SendMessageParams{
+ ChatID: api.ChatIDFromInt(msg.Chat.ID),
+ Text: "What's your name?",
+ })
+ return "ask_age", nil
+})
+
+conv.AddState("ask_age", func(ctx context.Context, msg *api.Message) (string, error) {
+ name := conv.GetData(ctx, "name")
+ // ... handle age input
+ return conversation.Done, nil
+})
+
+d.OnMessage(conv.Handler("start"))
+
+ Filters compose with And, Or, Not.
+ A filter is just a function func(*api.Update) bool.
+
// Only handle private messages from admins
+adminOnly := filters.And(
+ filters.IsPrivate,
+ filters.UserIDIn(adminIDs...),
+)
+
+d.OnMessage(func(ctx context.Context, msg *api.Message) {
+ // handler body
+}, adminOnly)
+
+// Custom filter — any function works
+isLong := func(u *api.Update) bool {
+ return u.Message != nil && len(u.Message.Text) > 200
+}
+d.OnMessage(handleLongMsg, isLong)
+
+ Pass a transport.Options to swap the HTTP client or JSON codec.
+ Useful for squeezing throughput on high-volume bots.
+
import (
+ "github.com/lukaszraczylo/go-telegram/client"
+ "github.com/lukaszraczylo/go-telegram/transport"
+ jsoniter "github.com/json-iterator/go"
+)
+
+opts := &client.Options{
+ Transport: transport.NewFasthttpTransport(nil),
+ Codec: transport.JSONCodec(jsoniter.ConfigCompatibleWithStandardLibrary),
+}
+
+bot, err := client.NewRetryDoer(token, opts)
+