gotelegram

package module
v1.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 9, 2026 License: MIT Imports: 0 Imported by: 0

README

go-telegram

Build Telegram bots in Go that just work.
Type-safe. Batteries included. Always up to date with the latest Bot API.

CI Go Reference Go Version

Bot API v10.0 · 176 methods · 301 types · 1428 auto-generated tests

Website · API Reference · Examples · pkg.go.dev


Hello, Telegram 👋

bot := client.New(os.Getenv("TELEGRAM_BOT_TOKEN"))
router := dispatch.New(bot)

router.OnCommand("/start", func(c *dispatch.Context, m *api.Message) error {
    _, err := api.SendMessage(c.Ctx, c.Bot, &api.SendMessageParams{
        ChatID: api.ChatIDFromInt(m.Chat.ID),
        Text:   "Hi " + m.From.FirstName + "! 👋",
    })
    return err
})

router.Run(ctx, transport.NewLongPoller(bot))

That's a working bot. No magic strings, no any, no guessing what fields exist — your editor autocompletes everything.

Why you'll like it

  • 🎯 No any, anywhere. Telegram's "Integer or String" and "one of N types" unions are real Go types you can switch on.
  • 🔋 Batteries included. Long-poll, webhooks, retries on rate limits, conversation state machines, filters, handler groups — out of the box.
  • 🔄 Always current. The whole API is generated from Telegram's live docs. New Bot API release? make regen and you're done.
  • 🪶 Pluggable everything. Swap the HTTP client, JSON codec, or storage backend with a one-method interface. No forks.
  • 🧪 Already tested. 1428 generated tests cover every method × every failure mode (success, API errors, network failures, parse errors, timeouts, missing fields, forbidden, server errors).

Install

go get github.com/lukaszraczylo/go-telegram

A complete echo bot

Long-poll, graceful shutdown, retries on Telegram's 429 retry_after:

package main

import (
    "context"
    "fmt"
    "log"
    "os"
    "os/signal"
    "syscall"

    "github.com/lukaszraczylo/go-telegram/api"
    "github.com/lukaszraczylo/go-telegram/client"
    "github.com/lukaszraczylo/go-telegram/dispatch"
    "github.com/lukaszraczylo/go-telegram/transport"
)

func main() {
    token := os.Getenv("TELEGRAM_BOT_TOKEN")
    if token == "" {
        log.Fatal("TELEGRAM_BOT_TOKEN required")
    }

    ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
    defer stop()

    bot := client.New(token,
        client.WithHTTPClient(client.NewRetryDoer(client.NewDefaultHTTPDoer())),
    )

    router := dispatch.New(bot)
    router.OnCommand("/start", func(c *dispatch.Context, m *api.Message) error {
        _, err := api.SendMessage(c.Ctx, c.Bot, &api.SendMessageParams{
            ChatID: api.ChatIDFromInt(m.Chat.ID),
            Text:   fmt.Sprintf("Hello %s! Send me anything.", m.From.FirstName),
        })
        return err
    })
    router.OnText(`.+`, func(c *dispatch.Context, m *api.Message) error {
        _, err := api.SendMessage(c.Ctx, c.Bot, &api.SendMessageParams{
            ChatID:          api.ChatIDFromInt(m.Chat.ID),
            Text:            m.Text,
            ReplyParameters: &api.ReplyParameters{MessageID: m.MessageID},
        })
        return err
    })

    if err := router.Run(ctx, transport.NewLongPoller(bot)); err != nil && err != context.Canceled {
        log.Printf("router exited: %v", err)
    }
}

Examples

Run any example: TELEGRAM_BOT_TOKEN=xxx go run ./examples/<name>

Category Example What it shows
Basics echo Long-poll echo bot
webhook Webhook server with secret-token verification
files Upload and download cycle
inline Inline-mode results
Conversations & state conversation Multi-step state machine with /cancel exit
stateful Per-user state via closures
callback Inline keyboards and callback query handling
pagination Multi-page inline keyboard
Group management welcome Greet new chat members
moderation Kick/ban/mute/warn with permission checks
admin Auth middleware allowlist
Advanced middleware Use chains
polls sendPoll and answer tally
payments Invoice → pre-checkout → success

Optional fields

Telegram marks many fields as optional. For optional scalars (int, bool, float) we use pointers so you can explicitly send false or 0 when the wire format needs to override a chat default. The api.Ptr helper keeps that ergonomic:

api.SendMessage(ctx, bot, &api.SendMessageParams{
    ChatID:              api.ChatIDFromInt(chatID),
    Text:                "hi",
    DisableNotification: api.Ptr(true),     // type inferred
})

api.GetUserProfilePhotos(ctx, bot, &api.GetUserProfilePhotosParams{
    UserID: userID,
    Limit:  api.Ptr[int64](5),              // explicit type for untyped literals
})

Optional structs and slices are already nullable in Go — no helper needed.

Reference docs

Full API reference is auto-generated from source comments and lives in docs/reference/ — browse package by package on GitHub, or read it rendered at go-telegram.raczylo.com and pkg.go.dev.

How it works

Bot client and pluggable transport

client.New accepts functional options:

bot := client.New(token,
    client.WithHTTPClient(doer),       // any HTTPDoer (one-method interface)
    client.WithCodec(myCodec),         // any Codec (Marshal + Unmarshal)
    client.WithLogger(myLogger),
    client.WithBaseURL("https://..."), // proxy or local Bot API server
)

HTTPDoer is Do(*http.Request) (*http.Response, error) — a plain *http.Client satisfies it. Codec is Marshal(any) ([]byte, error) + Unmarshal([]byte, any) error — the default wraps goccy/go-json.

Every API call goes through client.Call[Req, Resp]; per-method generated functions are thin wrappers.

Typed unions — no any

Telegram's docs describe many fields as "Integer or String" or "one of N types". go-telegram turns every one of these into a concrete Go type.

// ChatID: construct from int64 or @username
chatID := api.ChatIDFromInt(123456789)
chatID := api.ChatIDFromString("@mychannel")

// Discriminated unions — 13 interfaces with auto-decode via generated UnmarshalJSON
for _, u := range updates {
    if u.MyChatMember == nil {
        continue
    }
    switch v := u.MyChatMember.OldChatMember.(type) {
    case *api.ChatMemberOwner:
        log.Println("was owner")
    case *api.ChatMemberAdministrator:
        log.Printf("was admin: can_post=%v", v.CanPostMessages)
    }
}

Full union list: ChatMember, MessageOrigin, ReactionType, PaidMedia, BackgroundType, BackgroundFill, ChatBoostSource, RevenueWithdrawalState, TransactionPartner, MenuButton, OwnedGift, StoryAreaType, MaybeInaccessibleMessage, plus ChatID, MessageOrBool, and InputFile.

Dispatcher, filters, and conversations

The router dispatches each update in its own goroutine (semaphore-bounded, default 50):

r := dispatch.New(bot, dispatch.WithMaxConcurrency(50))

r.OnCommand("/start", handler)
r.OnText(`^hi (\w+)`, handler)
r.OnCallback(`^like:\d+`, handler)
r.OnInlineQuery(handler)
r.OnMyChatMember(handler)
// + 20 more typed On* methods

Composable filters — each update type has its own filter package:

import "github.com/lukaszraczylo/go-telegram/dispatch/filters/message"

r.OnMessageFilter(
    message.Command("/admin").And(message.IsReply()),
    handler,
)

Filter packages: message, callback, inline, chatmember, chatjoinrequest, precheckoutquery. Combinators: And, Or, Not, All, Any.

Conversation state machines — multi-step flows with pluggable storage:

conv := &conversation.Conversation{
    EntryPoints: []conversation.Step{{
        Filter: dispatch.FilterFunc(func(c *dispatch.Context, u *api.Update) bool {
            return u.Message != nil && u.Message.Text == "/start"
        }),
        Handler: func(c *dispatch.Context, u *api.Update) error {
            // send prompt, advance state
            return conversation.Next("await_name")
        },
    }},
    States: map[conversation.State][]conversation.Step{
        "await_name": {{
            Handler: func(c *dispatch.Context, u *api.Update) error {
                return conversation.End()
            },
        }},
    },
}
router.Use(conv.Dispatch)

Key strategies: KeyByUser, KeyByChat, KeyByUserAndChat (default). Default storage: MemoryStorage (in-process, concurrency-safe). Implement the Storage interface for Redis or any other backend.

Errors and retry middleware

Wrap the default HTTP doer with RetryDoer for production:

bot := client.New(token,
    client.WithHTTPClient(
        client.NewRetryDoer(
            client.NewDefaultHTTPDoer(),
            client.WithMaxAttempts(5),
            client.WithBaseBackoff(500*time.Millisecond),
        ),
    ),
)

RetryDoer retries on 429, 5xx, and transient network errors. On a 429 it reads retry_after from Telegram's response body and waits exactly that long — overriding any backoff calculation. Request bodies are buffered and replayed across attempts.

Sentinel errors for errors.Is checks: client.ErrForbidden, client.ErrNotFound, client.ErrUnauthorized.

Handler groups and named handlers

Priority-ordered groups with flow control signals:

// Group 0 runs first — return EndGroups to stop, ContinueGroups to continue
r.Group(0).OnText(`.*`, authMiddleware)
r.Group(1).OnText(`.*`, businessHandler)

Named handlers — register and replace at runtime:

named := dispatch.NewNamedHandlers[*api.Message]()
named.Set("main", myHandler)
r.OnCommand("/cmd", named.Handler())
// later: named.Set("main", updatedHandler)

Keeping up with Telegram

When Telegram ships a new Bot API version, regenerating the whole library is one command:

make snapshot   # grab the latest HTML from core.telegram.org
make regen      # scrape → audit → emit Go → run tests → regenerate docs

The audit tool checks for any-typed escapes, surprise bool returns, and signature drift. CI runs it on every PR, and a weekly workflow opens an auto-PR with regenerated code so a new Bot API version never sits longer than a week.

If something in Telegram's docs trips up the scraper, add an override to internal/spec/overrides.json. The audit will tell you what to put there.

Testing

Mock the one-method HTTPDoer interface to test handlers in isolation — no test server needed:

type fakeDoer struct{ body string }
func (f fakeDoer) Do(*http.Request) (*http.Response, error) {
    return &http.Response{
        StatusCode: 200,
        Body:       io.NopCloser(strings.NewReader(f.body)),
    }, nil
}

bot := client.New("token", client.WithHTTPClient(fakeDoer{
    body: `{"ok":true,"result":{"message_id":1,"date":0,"chat":{"id":1,"type":"private"}}}`,
}))

The library's own generated test suite (api/methods_gen_test.go) covers 176 methods × 8 scenarios each: Success, APIError, NetworkError, ParseError, ContextCanceled, MissingRequiredFields, Forbidden, ServerError.

Contributing

See CONTRIBUTING.md.

License

MIT

Documentation

Overview

Package gotelegram is the module root.

The public API lives in the api, client, transport, and dispatch packages. See https://github.com/lukaszraczylo/go-telegram for documentation.

Directories

Path Synopsis
Package api contains the Telegram Bot API types and method wrappers.
Package api contains the Telegram Bot API types and method wrappers.
Package client provides HTTP client primitives for the Telegram Bot API.
Package client provides HTTP client primitives for the Telegram Bot API.
cmd
audit command
Command audit reports IR-level codegen fallbacks and signature drift.
Command audit reports IR-level codegen fallbacks and signature drift.
genapi command
Command genapi reads internal/spec/api.json and emits api/*.gen.go.
Command genapi reads internal/spec/api.json and emits api/*.gen.go.
scrape command
Command scrape parses the Telegram Bot API HTML page into the IR (internal/spec.API) and writes it to internal/spec/api.json.
Command scrape parses the Telegram Bot API HTML page into the IR (internal/spec.API) and writes it to internal/spec/api.json.
Package dispatch provides a typed router for Telegram updates.
Package dispatch provides a typed router for Telegram updates.
conversation
Package conversation implements a stateful conversation handler for the go-telegram dispatch router.
Package conversation implements a stateful conversation handler for the go-telegram dispatch router.
filters/callback
Package callback provides Filter helpers for *api.CallbackQuery payloads.
Package callback provides Filter helpers for *api.CallbackQuery payloads.
filters/chatjoinrequest
Package chatjoinrequest provides Filter helpers for *api.ChatJoinRequest payloads.
Package chatjoinrequest provides Filter helpers for *api.ChatJoinRequest payloads.
filters/chatmember
Package chatmember provides Filter helpers for *api.ChatMemberUpdated payloads.
Package chatmember provides Filter helpers for *api.ChatMemberUpdated payloads.
filters/inline
Package inline provides Filter helpers for *api.InlineQuery payloads.
Package inline provides Filter helpers for *api.InlineQuery payloads.
filters/message
Package message provides Filter helpers for *api.Message payloads.
Package message provides Filter helpers for *api.Message payloads.
filters/precheckoutquery
Package precheckoutquery provides Filter helpers for *api.PreCheckoutQuery payloads.
Package precheckoutquery provides Filter helpers for *api.PreCheckoutQuery payloads.
examples
admin command
Package main demonstrates auth middleware that restricts the bot to an allowlist of Telegram user IDs.
Package main demonstrates auth middleware that restricts the bot to an allowlist of Telegram user IDs.
callback command
Package main demonstrates inline keyboards and callback queries with go-telegram.
Package main demonstrates inline keyboards and callback queries with go-telegram.
conversation command
Package main demonstrates a /newbot-style conversation flow using dispatch/conversation.
Package main demonstrates a /newbot-style conversation flow using dispatch/conversation.
echo command
Package main is a long-poll echo bot.
Package main is a long-poll echo bot.
files command
Package main demonstrates file upload and download with go-telegram.
Package main demonstrates file upload and download with go-telegram.
inline command
Package main demonstrates inline-mode queries with go-telegram.
Package main demonstrates inline-mode queries with go-telegram.
middleware command
Package main demonstrates custom middleware for go-telegram.
Package main demonstrates custom middleware for go-telegram.
moderation command
Package main demonstrates group moderation commands: /kick, /ban, /mute, /warn.
Package main demonstrates group moderation commands: /kick, /ban, /mute, /warn.
pagination command
Package main demonstrates multi-page inline keyboard navigation.
Package main demonstrates multi-page inline keyboard navigation.
payments command
Package main demonstrates the Telegram Payments flow:
Package main demonstrates the Telegram Payments flow:
polls command
Package main demonstrates creating polls and tallying answers via OnPollAnswer.
Package main demonstrates creating polls and tallying answers via OnPollAnswer.
stateful command
Package main demonstrates per-user state without globals via closures.
Package main demonstrates per-user state without globals via closures.
webhook command
Package main is a webhook bot.
Package main is a webhook bot.
welcome command
Package main demonstrates greeting new chat members and detecting leaves.
Package main demonstrates greeting new chat members and detecting leaves.
internal
spec
Package spec defines the intermediate representation produced by the Telegram Bot API scraper (cmd/scrape) and consumed by the code generator (cmd/genapi).
Package spec defines the intermediate representation produced by the Telegram Bot API scraper (cmd/scrape) and consumed by the code generator (cmd/genapi).
Package transport provides update delivery mechanisms (long-poll and webhook) that feed updates into the dispatch package's Router.
Package transport provides update delivery mechanisms (long-poll and webhook) that feed updates into the dispatch package's Router.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL