docs: auto-generate markdown reference + soften README

- Add gomarkdoc-driven reference docs in docs/reference/, regenerated
  automatically by 'make regen' alongside the api/ codegen
- New 'make docs' target installs gomarkdoc on first run; 'make
  docs-check' is a CI gate
- Fold doc-clean assertion into existing codegen-clean job (single
  diff check covers spec + api + reference)
- Rewrite README header: logo via <picture>, friendlier tagline,
  emoji-led 'Why you'll like it' bullets instead of Why-table
- Drop duplicate echo snippet, soften 'Codegen pipeline' section into
  'Keeping up with Telegram'
- Link reference from README, Pages nav, and a new Markdown reference
  card on index.html (target = GitHub source view, renders .md natively)
This commit is contained in:
2026-05-09 14:11:28 +01:00
parent 35058dd70b
commit 1088b7f4d7
16 changed files with 16263 additions and 50 deletions
+1 -1
View File
@@ -134,7 +134,7 @@ jobs:
- name: Regenerate against pinned snapshot - name: Regenerate against pinned snapshot
run: make regen-from-fixture run: make regen-from-fixture
- name: Assert clean diff - name: Assert clean diff
run: git diff --exit-code internal/spec/api.json api/ run: git diff --exit-code internal/spec/api.json api/ docs/reference/
audit: audit:
runs-on: ubuntu-latest runs-on: ubuntu-latest
+27 -1
View File
@@ -1,4 +1,4 @@
.PHONY: test test-race lint vet integration regen snapshot regen-from-fixture test-update-golden clean clean-generated audit audit-drift help .PHONY: test test-race lint vet integration regen snapshot regen-from-fixture test-update-golden clean clean-generated audit audit-drift docs docs-check help
GO ?= go GO ?= go
@@ -14,6 +14,8 @@ help:
@echo " test-update-golden - refresh golden test fixtures (Plan 2)" @echo " test-update-golden - refresh golden test fixtures (Plan 2)"
@echo " audit - report any-typed/bool fallbacks in current IR" @echo " audit - report any-typed/bool fallbacks in current IR"
@echo " audit-drift - audit + compare against HEAD's IR for signature changes" @echo " audit-drift - audit + compare against HEAD's IR for signature changes"
@echo " docs - regenerate markdown reference docs into docs/reference/"
@echo " docs-check - assert docs/reference/ is up to date (CI gate)"
@echo " clean-generated - delete generated api/*.gen.go and internal/spec/api.json" @echo " clean-generated - delete generated api/*.gen.go and internal/spec/api.json"
@echo " clean - clean-generated + transient artefacts (binaries, coverage)" @echo " clean - clean-generated + transient artefacts (binaries, coverage)"
@@ -44,12 +46,14 @@ regen: clean-generated
$(GO) run ./cmd/audit -ir $(SCRAPE_OUTPUT) $(GO) run ./cmd/audit -ir $(SCRAPE_OUTPUT)
$(GO) run ./cmd/genapi -input $(SCRAPE_OUTPUT) -outdir api $(GO) run ./cmd/genapi -input $(SCRAPE_OUTPUT) -outdir api
$(GO) test ./api/... $(GO) test ./api/...
$(MAKE) docs
regen-from-fixture: clean-generated regen-from-fixture: clean-generated
$(GO) run ./cmd/scrape -input $(SCRAPE_INPUT) -output $(SCRAPE_OUTPUT) $(GO) run ./cmd/scrape -input $(SCRAPE_INPUT) -output $(SCRAPE_OUTPUT)
$(GO) run ./cmd/audit -ir $(SCRAPE_OUTPUT) $(GO) run ./cmd/audit -ir $(SCRAPE_OUTPUT)
$(GO) run ./cmd/genapi -input $(SCRAPE_OUTPUT) -outdir api $(GO) run ./cmd/genapi -input $(SCRAPE_OUTPUT) -outdir api
$(GO) test ./api/... $(GO) test ./api/...
$(MAKE) docs
audit: audit:
$(GO) run ./cmd/audit -ir $(SCRAPE_OUTPUT) $(GO) run ./cmd/audit -ir $(SCRAPE_OUTPUT)
@@ -61,6 +65,28 @@ test-update-golden:
$(GO) test -run TestEmit -update ./cmd/genapi/... $(GO) test -run TestEmit -update ./cmd/genapi/...
$(GO) test -run TestScrape -update ./cmd/scrape/... $(GO) test -run TestScrape -update ./cmd/scrape/...
# Regenerate godoc-style markdown reference docs into docs/reference/.
# Auto-installs gomarkdoc on first run.
DOC_PACKAGES := \
./client \
./transport \
./dispatch \
./dispatch/conversation \
./dispatch/filters/message \
./dispatch/filters/callback \
./dispatch/filters/inline \
./dispatch/filters/chatmember \
./dispatch/filters/chatjoinrequest \
./dispatch/filters/precheckoutquery \
./api
docs:
@which gomarkdoc > /dev/null || (echo "installing gomarkdoc..." && $(GO) install github.com/princjef/gomarkdoc/cmd/gomarkdoc@v1.1.0)
gomarkdoc -o 'docs/reference/{{.Dir}}.md' $(DOC_PACKAGES)
docs-check: docs
@git diff --exit-code docs/reference/ || (echo "docs/reference/ is stale — run 'make docs' and commit" && exit 1)
# clean-generated removes ONLY codegen output. Source code (cmd/scrape, # clean-generated removes ONLY codegen output. Source code (cmd/scrape,
# cmd/genapi, runtime helpers) is untouched. Run before regen to avoid # cmd/genapi, runtime helpers) is untouched. Run before regen to avoid
# orphan files lingering when the IR shrinks (renamed/removed methods). # orphan files lingering when the IR shrinks (renamed/removed methods).
+55 -48
View File
@@ -1,61 +1,71 @@
# go-telegram <p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="docs/logo-dark.svg">
<img alt="go-telegram" src="docs/logo-light.svg" width="320">
</picture>
</p>
> A fully-generated, strongly-typed Go client for the Telegram Bot API — no `any`, no guessing. <p align="center">
<strong>Build Telegram bots in Go that just work.</strong><br>
Type-safe. Batteries included. Always up to date with the latest Bot API.
</p>
[![CI](https://github.com/lukaszraczylo/go-telegram/actions/workflows/ci.yml/badge.svg)](https://github.com/lukaszraczylo/go-telegram/actions/workflows/ci.yml) <p align="center">
[![Go Reference](https://pkg.go.dev/badge/github.com/lukaszraczylo/go-telegram.svg)](https://pkg.go.dev/github.com/lukaszraczylo/go-telegram) <a href="https://github.com/lukaszraczylo/go-telegram/actions/workflows/ci.yml"><img src="https://github.com/lukaszraczylo/go-telegram/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
[![Go Version](https://img.shields.io/github/go-mod/go-version/lukaszraczylo/go-telegram)](go.mod) <a href="https://pkg.go.dev/github.com/lukaszraczylo/go-telegram"><img src="https://pkg.go.dev/badge/github.com/lukaszraczylo/go-telegram.svg" alt="Go Reference"></a>
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) <a href="go.mod"><img src="https://img.shields.io/github/go-mod/go-version/lukaszraczylo/go-telegram" alt="Go Version"></a>
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License: MIT"></a>
</p>
> Bot API **v10.0** · 176 methods · 301 types · 1428 auto-generated tests <p align="center">
Bot API <strong>v10.0</strong> · 176 methods · 301 types · 1428 auto-generated tests
</p>
Most Telegram bot libraries expose Telegram's "Integer or String" fields as `interface{}` or `any`. Every union type in go-telegram is a real Go type with compile-time safety and auto-decoding. The entire API surface is code-generated from a committed HTML snapshot of the live Telegram docs — regenerating picks up new Bot API versions in one command, with a self-verifying pipeline that catches regressions before they ship. <p align="center">
<a href="https://go-telegram.raczylo.com/">Website</a> ·
<a href="docs/reference/">API Reference</a> ·
<a href="examples/">Examples</a> ·
<a href="https://pkg.go.dev/github.com/lukaszraczylo/go-telegram">pkg.go.dev</a>
</p>
---
## Hello, Telegram 👋
```go ```go
bot := client.New(os.Getenv("TELEGRAM_BOT_TOKEN"), bot := client.New(os.Getenv("TELEGRAM_BOT_TOKEN"))
client.WithHTTPClient(client.NewRetryDoer(client.NewDefaultHTTPDoer())),
)
router := dispatch.New(bot) router := dispatch.New(bot)
router.OnCommand("/start", func(c *dispatch.Context, m *api.Message) error { router.OnCommand("/start", func(c *dispatch.Context, m *api.Message) error {
_, err := api.SendMessage(c.Ctx, c.Bot, &api.SendMessageParams{ _, err := api.SendMessage(c.Ctx, c.Bot, &api.SendMessageParams{
ChatID: api.ChatIDFromInt(m.Chat.ID), ChatID: api.ChatIDFromInt(m.Chat.ID),
Text: "Hello! Send me anything to echo.", Text: "Hi " + 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 return err
}) })
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
router.Run(ctx, transport.NewLongPoller(bot)) router.Run(ctx, transport.NewLongPoller(bot))
``` ```
## Why go-telegram That's a working bot. No magic strings, no `any`, no guessing what fields exist — your editor autocompletes everything.
| Feature | What it means for you | ## Why you'll like it
|---|---|
| **Typed unions** | `ChatID`, `MessageOrBool`, `InputFile`, and 13 discriminated-union interfaces give you `switch v.(type)` instead of runtime panics |
| **Full Bot API v10.0** | 176 methods and 301 types — all generated, none hand-written, nothing missing |
| **Self-verifying codegen** | `make snapshot && make regen` regenerates everything and runs 1428 tests; any regression fails the pipeline |
| **Pluggable transport + codec** | `HTTPDoer` and `Codec` are one-method interfaces — swap in fasthttp, sonic, or your test fake without forking |
| **Retry middleware** | `RetryDoer` honours Telegram's `retry_after`, backs off on 5xx, replays request bodies |
| **Composable dispatcher** | Per-update goroutine pool (default 50), filter combinators (`And`/`Or`/`Not`), conversation state machines, named handlers |
## Quickstart - 🎯 **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
```bash ```bash
go get github.com/lukaszraczylo/go-telegram go get github.com/lukaszraczylo/go-telegram
``` ```
Full echo bot — long-poll, graceful shutdown, retry on 429: ## A complete echo bot
Long-poll, graceful shutdown, retries on Telegram's `429 retry_after`:
```go ```go
package main package main
@@ -131,7 +141,11 @@ Run any example: `TELEGRAM_BOT_TOKEN=xxx go run ./examples/<name>`
| | [`polls`](examples/polls) | `sendPoll` and answer tally | | | [`polls`](examples/polls) | `sendPoll` and answer tally |
| | [`payments`](examples/payments) | Invoice → pre-checkout → success | | | [`payments`](examples/payments) | Invoice → pre-checkout → success |
## Concepts ## Reference docs
Full API reference is auto-generated from source comments and lives in [`docs/reference/`](docs/reference/README.md) — browse package by package on GitHub, or read it rendered at [go-telegram.raczylo.com](https://go-telegram.raczylo.com/) and [pkg.go.dev](https://pkg.go.dev/github.com/lukaszraczylo/go-telegram).
## How it works
<details> <details>
<summary>Bot client and pluggable transport</summary> <summary>Bot client and pluggable transport</summary>
@@ -284,25 +298,18 @@ r.OnCommand("/cmd", named.Handler())
</details> </details>
## Codegen pipeline ## Keeping up with Telegram
The full API surface in `api/*.gen.go` is generated from a committed HTML snapshot of `core.telegram.org/bots/api`: When Telegram ships a new Bot API version, regenerating the whole library is one command:
```bash ```bash
make snapshot # fetch and commit latest HTML from core.telegram.org make snapshot # grab the latest HTML from core.telegram.org
make regen # scrape → audit → emit Go code → run generated tests make regen # scrape → audit → emit Go → run tests → regenerate docs
go test -race ./...
``` ```
`make regen` is self-verifying. The audit tool (`cmd/audit`) checks: 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.
- `any`-typed fields or returns that escaped the union machinery 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.
- Methods returning `bool` not on the approved list (`internal/spec/overrides.json`)
- Signature drift vs HEAD's IR (added/removed/changed return types)
Exit codes: 0 clean · 1 fallback · 2 drift · 3 invalid. CI runs the audit on every PR. A weekly `regen.yml` workflow opens a PR with regenerated code and the audit summary in the body.
To track a new Bot API release: run `make snapshot && make regen`, review the audit output, update `internal/spec/overrides.json` for any newly unparseable methods, and submit a PR.
## Testing ## Testing
+14
View File
@@ -99,6 +99,7 @@
<a href="#usage" class="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 font-medium">Usage</a> <a href="#usage" class="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 font-medium">Usage</a>
<a href="#examples" class="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 font-medium">Examples</a> <a href="#examples" class="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 font-medium">Examples</a>
<a href="#advanced" class="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 font-medium">Advanced</a> <a href="#advanced" class="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 font-medium">Advanced</a>
<a href="https://github.com/lukaszraczylo/go-telegram/tree/main/docs/reference" target="_blank" class="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 font-medium">Reference</a>
</div> </div>
<div class="flex items-center space-x-4"> <div class="flex items-center space-x-4">
<button id="theme-toggle" class="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 p-2 min-w-[44px] min-h-[44px] flex items-center justify-center" aria-label="Toggle theme"> <button id="theme-toggle" class="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 p-2 min-w-[44px] min-h-[44px] flex items-center justify-center" aria-label="Toggle theme">
@@ -123,6 +124,7 @@
<a href="#usage" class="block px-3 py-3 text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 hover:bg-gray-50 dark:hover:bg-gray-700 rounded font-medium">Usage</a> <a href="#usage" class="block px-3 py-3 text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 hover:bg-gray-50 dark:hover:bg-gray-700 rounded font-medium">Usage</a>
<a href="#examples" class="block px-3 py-3 text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 hover:bg-gray-50 dark:hover:bg-gray-700 rounded font-medium">Examples</a> <a href="#examples" class="block px-3 py-3 text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 hover:bg-gray-50 dark:hover:bg-gray-700 rounded font-medium">Examples</a>
<a href="#advanced" class="block px-3 py-3 text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 hover:bg-gray-50 dark:hover:bg-gray-700 rounded font-medium">Advanced</a> <a href="#advanced" class="block px-3 py-3 text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 hover:bg-gray-50 dark:hover:bg-gray-700 rounded font-medium">Advanced</a>
<a href="https://github.com/lukaszraczylo/go-telegram/tree/main/docs/reference" target="_blank" class="block px-3 py-3 text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 hover:bg-gray-50 dark:hover:bg-gray-700 rounded font-medium">Reference</a>
</div> </div>
</div> </div>
</nav> </nav>
@@ -447,6 +449,18 @@
<i class="fas fa-external-link-alt mr-2"></i>View on pkg.go.dev <i class="fas fa-external-link-alt mr-2"></i>View on pkg.go.dev
</a> </a>
</div> </div>
<div class="glass p-6 sm:p-8 rounded-xl shadow-modern hover:shadow-xl transition-all duration-300">
<div class="flex items-center mb-4">
<i class="fas fa-book-open text-emerald-500 dark:text-emerald-400 text-2xl mr-3"></i>
<div>
<h3 class="text-xl font-semibold text-gray-900 dark:text-gray-100">Markdown reference</h3>
<p class="text-gray-600 dark:text-gray-400 text-sm">Auto-generated, browse on GitHub</p>
</div>
</div>
<a href="https://github.com/lukaszraczylo/go-telegram/tree/main/docs/reference" target="_blank" class="block text-center bg-gradient-to-r from-emerald-600 to-teal-700 hover:from-emerald-700 hover:to-teal-800 text-white px-4 py-3 rounded-lg text-sm font-medium shadow-lg hover:shadow-xl transition-all duration-300 hover:scale-105">
<i class="fas fa-external-link-alt mr-2"></i>Browse reference docs
</a>
</div>
<div class="glass p-6 sm:p-8 rounded-xl shadow-modern hover:shadow-xl transition-all duration-300"> <div class="glass p-6 sm:p-8 rounded-xl shadow-modern hover:shadow-xl transition-all duration-300">
<div class="flex items-center mb-4"> <div class="flex items-center mb-4">
<i class="fas fa-file-alt text-blue-500 dark:text-blue-400 text-2xl mr-3"></i> <i class="fas fa-file-alt text-blue-500 dark:text-blue-400 text-2xl mr-3"></i>
+25
View File
@@ -0,0 +1,25 @@
# API Reference
Auto-generated from Go source comments by [gomarkdoc](https://github.com/princjef/gomarkdoc). Do not edit by hand — run `make docs` to regenerate.
## Packages
| Package | Description |
|---|---|
| [`api`](api.md) | Telegram Bot API types and method wrappers — 176 methods, 301 types, fully generated |
| [`client`](client.md) | Bot client, codec, HTTP doer, retry middleware |
| [`transport`](transport.md) | Long-poll and webhook transports |
| [`dispatch`](dispatch.md) | Update router, filters, handler groups, named handlers |
| [`dispatch/conversation`](dispatch/conversation.md) | Multi-step conversation state machines |
| [`dispatch/filters/message`](dispatch/filters/message.md) | Message filters — `Command`, `Text`, `IsReply`, etc. |
| [`dispatch/filters/callback`](dispatch/filters/callback.md) | Callback query filters |
| [`dispatch/filters/inline`](dispatch/filters/inline.md) | Inline query filters |
| [`dispatch/filters/chatmember`](dispatch/filters/chatmember.md) | Chat member update filters |
| [`dispatch/filters/chatjoinrequest`](dispatch/filters/chatjoinrequest.md) | Join request filters |
| [`dispatch/filters/precheckoutquery`](dispatch/filters/precheckoutquery.md) | Pre-checkout filters for payments |
## Also see
- [Project home](../index.html) — landing page with examples and overview
- [GitHub repository](https://github.com/lukaszraczylo/go-telegram)
- [pkg.go.dev](https://pkg.go.dev/github.com/lukaszraczylo/go-telegram) — official Go package documentation
+14050
View File
File diff suppressed because it is too large Load Diff
+579
View File
@@ -0,0 +1,579 @@
<!-- Code generated by gomarkdoc. DO NOT EDIT -->
# client
```go
import "github.com/lukaszraczylo/go-telegram/client"
```
Package client provides HTTP client primitives for the Telegram Bot API.
## Index
- [Variables](<#variables>)
- [func Call\[Req any, Resp any\]\(ctx context.Context, b \*Bot, method string, req Req\) \(Resp, error\)](<#Call>)
- [func CallRaw\[Req any\]\(ctx context.Context, b \*Bot, method string, req Req\) \(json.RawMessage, error\)](<#CallRaw>)
- [func NewDefaultHTTPDoer\(\) \*http.Client](<#NewDefaultHTTPDoer>)
- [type APIError](<#APIError>)
- [func \(e \*APIError\) Error\(\) string](<#APIError.Error>)
- [func \(e \*APIError\) IsRetryable\(\) bool](<#APIError.IsRetryable>)
- [func \(e \*APIError\) RetryAfter\(\) time.Duration](<#APIError.RetryAfter>)
- [func \(e \*APIError\) Unwrap\(\) error](<#APIError.Unwrap>)
- [type Bot](<#Bot>)
- [func New\(token string, opts ...Option\) \*Bot](<#New>)
- [func \(b \*Bot\) BaseURL\(\) string](<#Bot.BaseURL>)
- [func \(b \*Bot\) Codec\(\) Codec](<#Bot.Codec>)
- [func \(b \*Bot\) HTTP\(\) HTTPDoer](<#Bot.HTTP>)
- [func \(b \*Bot\) Logger\(\) Logger](<#Bot.Logger>)
- [func \(b \*Bot\) Token\(\) string](<#Bot.Token>)
- [type Codec](<#Codec>)
- [type DefaultCodec](<#DefaultCodec>)
- [func \(DefaultCodec\) Marshal\(v any\) \(\[\]byte, error\)](<#DefaultCodec.Marshal>)
- [func \(DefaultCodec\) Unmarshal\(data \[\]byte, v any\) error](<#DefaultCodec.Unmarshal>)
- [type HTTPDoer](<#HTTPDoer>)
- [type Logger](<#Logger>)
- [type MultipartFile](<#MultipartFile>)
- [type NetworkError](<#NetworkError>)
- [func \(e \*NetworkError\) Error\(\) string](<#NetworkError.Error>)
- [func \(e \*NetworkError\) Unwrap\(\) error](<#NetworkError.Unwrap>)
- [type NoopLogger](<#NoopLogger>)
- [func \(NoopLogger\) Debug\(string, ...any\)](<#NoopLogger.Debug>)
- [func \(NoopLogger\) Error\(string, ...any\)](<#NoopLogger.Error>)
- [func \(NoopLogger\) Info\(string, ...any\)](<#NoopLogger.Info>)
- [func \(NoopLogger\) Warn\(string, ...any\)](<#NoopLogger.Warn>)
- [type Option](<#Option>)
- [func WithBaseURL\(url string\) Option](<#WithBaseURL>)
- [func WithCodec\(c Codec\) Option](<#WithCodec>)
- [func WithHTTPClient\(c HTTPDoer\) Option](<#WithHTTPClient>)
- [func WithLogger\(l Logger\) Option](<#WithLogger>)
- [type ParseError](<#ParseError>)
- [func \(e \*ParseError\) Error\(\) string](<#ParseError.Error>)
- [func \(e \*ParseError\) Unwrap\(\) error](<#ParseError.Unwrap>)
- [type ResponseParameters](<#ResponseParameters>)
- [type Result](<#Result>)
- [type RetryDoer](<#RetryDoer>)
- [func NewRetryDoer\(inner HTTPDoer, opts ...RetryOption\) \*RetryDoer](<#NewRetryDoer>)
- [func \(d \*RetryDoer\) Do\(req \*http.Request\) \(\*http.Response, error\)](<#RetryDoer.Do>)
- [type RetryOption](<#RetryOption>)
- [func WithBackoffFactor\(f float64\) RetryOption](<#WithBackoffFactor>)
- [func WithBaseBackoff\(d time.Duration\) RetryOption](<#WithBaseBackoff>)
- [func WithJitter\(j float64\) RetryOption](<#WithJitter>)
- [func WithMaxAttempts\(n int\) RetryOption](<#WithMaxAttempts>)
- [func WithMaxBackoff\(d time.Duration\) RetryOption](<#WithMaxBackoff>)
## Variables
<a name="ErrUnauthorized"></a>Sentinel errors returned via APIError.Unwrap when the description matches. Compare with errors.Is.
```go
var (
ErrUnauthorized = errors.New("telegram: unauthorized")
ErrChatNotFound = errors.New("telegram: chat not found")
ErrMessageNotModified = errors.New("telegram: message is not modified")
ErrTooManyRequests = errors.New("telegram: too many requests")
ErrBadRequest = errors.New("telegram: bad request")
ErrForbidden = errors.New("telegram: forbidden")
ErrUserNotFound = errors.New("telegram: user not found")
ErrMessageNotFound = errors.New("telegram: message not found")
)
```
<a name="Call"></a>
## func Call
```go
func Call[Req any, Resp any](ctx context.Context, b *Bot, method string, req Req) (Resp, error)
```
Call is the single point through which every Telegram Bot API method invocation flows. It marshals the request, signs the URL with the bot token, dispatches via HTTPDoer, decodes the Result envelope, and translates non\-OK responses into typed errors.
It is generic over both request and response types. Methods with no parameters may pass a nil Req; the helper sends "\{\}" in that case so Telegram receives a syntactically valid empty object.
Call is exported because the api package \(which lives outside this one\) invokes it from generated method wrappers. User code should not normally call it directly — use the typed wrappers in package api instead.
<a name="CallRaw"></a>
## func CallRaw
```go
func CallRaw[Req any](ctx context.Context, b *Bot, method string, req Req) (json.RawMessage, error)
```
CallRaw is like Call but returns the raw JSON of the result field instead of decoding it into a typed value. Generated method wrappers for sealed\-interface return types \(ChatMember, MenuButton, etc.\) use this helper, then dispatch through the union's UnmarshalXxx function.
CallRaw still translates non\-OK responses into \*APIError just like Call.
<a name="NewDefaultHTTPDoer"></a>
## func NewDefaultHTTPDoer
```go
func NewDefaultHTTPDoer() *http.Client
```
NewDefaultHTTPDoer returns an \*http.Client with sensible defaults for Telegram Bot API usage:
- 60s overall timeout \(longer than typical long\-poll Timeout=30s\).
- Connection pooling sized for a small number of long\-lived hosts.
- HTTP/2 enabled \(default in net/http\).
<a name="APIError"></a>
## type APIError
APIError represents a non\-OK Telegram Bot API response. It satisfies error and unwraps to a sentinel \(ErrUnauthorized, etc.\) where the description matches a known prefix, enabling errors.Is checks.
```go
type APIError struct {
Code int
Description string
Parameters *ResponseParameters
// contains filtered or unexported fields
}
```
<a name="APIError.Error"></a>
### func \(\*APIError\) Error
```go
func (e *APIError) Error() string
```
Error implements error.
<a name="APIError.IsRetryable"></a>
### func \(\*APIError\) IsRetryable
```go
func (e *APIError) IsRetryable() bool
```
IsRetryable returns true for transient HTTP statuses \(429, 5xx\).
<a name="APIError.RetryAfter"></a>
### func \(\*APIError\) RetryAfter
```go
func (e *APIError) RetryAfter() time.Duration
```
RetryAfter returns the recommended back\-off duration. It honours the Telegram\-supplied retry\_after parameter; if absent, returns 0.
<a name="APIError.Unwrap"></a>
### func \(\*APIError\) Unwrap
```go
func (e *APIError) Unwrap() error
```
Unwrap returns the matched sentinel error, if any.
<a name="Bot"></a>
## type Bot
Bot is the Telegram Bot API client. Construct via New. All API methods \(declared in package api\) hang off \*Bot via thin wrappers around call.
```go
type Bot struct {
// contains filtered or unexported fields
}
```
<a name="New"></a>
### func New
```go
func New(token string, opts ...Option) *Bot
```
New constructs a Bot with the given token and optional configuration. The default HTTP client is tuned for long\-poll workloads \(see NewDefaultHTTPDoer\); the default codec wraps encoding/json; the default logger discards records.
<a name="Bot.BaseURL"></a>
### func \(\*Bot\) BaseURL
```go
func (b *Bot) BaseURL() string
```
BaseURL returns the configured Telegram API base URL.
<a name="Bot.Codec"></a>
### func \(\*Bot\) Codec
```go
func (b *Bot) Codec() Codec
```
Codec returns the configured Codec.
<a name="Bot.HTTP"></a>
### func \(\*Bot\) HTTP
```go
func (b *Bot) HTTP() HTTPDoer
```
HTTP returns the underlying HTTPDoer. Exposed for adapters that need to share connection pools or for diagnostic checks.
<a name="Bot.Logger"></a>
### func \(\*Bot\) Logger
```go
func (b *Bot) Logger() Logger
```
Logger returns the configured Logger.
<a name="Bot.Token"></a>
### func \(\*Bot\) Token
```go
func (b *Bot) Token() string
```
Token returns the bot token. Exposed for advanced use cases \(custom transports, manual URL building\); ordinary code does not need it.
<a name="Codec"></a>
## type Codec
Codec encodes/decodes JSON payloads exchanged with the Telegram Bot API. The default implementation wraps goccy/go\-json. Users may plug in bytedance/sonic or any compatible encoder by passing WithCodec to New.
```go
type Codec interface {
Marshal(v any) ([]byte, error)
Unmarshal(data []byte, v any) error
}
```
<a name="DefaultCodec"></a>
## type DefaultCodec
DefaultCodec wraps goccy/go\-json. It is the zero\-value safe default.
```go
type DefaultCodec struct{}
```
<a name="DefaultCodec.Marshal"></a>
### func \(DefaultCodec\) Marshal
```go
func (DefaultCodec) Marshal(v any) ([]byte, error)
```
Marshal calls json.Marshal.
<a name="DefaultCodec.Unmarshal"></a>
### func \(DefaultCodec\) Unmarshal
```go
func (DefaultCodec) Unmarshal(data []byte, v any) error
```
Unmarshal calls json.Unmarshal.
<a name="HTTPDoer"></a>
## type HTTPDoer
HTTPDoer abstracts the HTTP transport. The default is a net/http client tuned for Telegram's long\-poll usage. Users may plug in valyala/fasthttp \(via an adapter\), or any custom retry/circuit\-breaker client by passing WithHTTPClient to New.
```go
type HTTPDoer interface {
Do(req *http.Request) (*http.Response, error)
}
```
<a name="Logger"></a>
## type Logger
Logger is a slog\-shaped logging interface. Users pass any compatible implementation via WithLogger. The default is NoopLogger, which discards everything.
```go
type Logger interface {
Debug(msg string, attrs ...any)
Info(msg string, attrs ...any)
Warn(msg string, attrs ...any)
Error(msg string, attrs ...any)
}
```
<a name="MultipartFile"></a>
## type MultipartFile
MultipartFile describes a single file part in a multipart upload.
```go
type MultipartFile struct {
FieldName string
Filename string
Reader io.Reader
}
```
<a name="NetworkError"></a>
## type NetworkError
NetworkError wraps a transport\-level failure \(DNS, TCP, TLS, timeout short of an HTTP response\).
```go
type NetworkError struct{ Err error }
```
<a name="NetworkError.Error"></a>
### func \(\*NetworkError\) Error
```go
func (e *NetworkError) Error() string
```
<a name="NetworkError.Unwrap"></a>
### func \(\*NetworkError\) Unwrap
```go
func (e *NetworkError) Unwrap() error
```
<a name="NoopLogger"></a>
## type NoopLogger
NoopLogger discards all log records. It is the zero\-value safe default.
```go
type NoopLogger struct{}
```
<a name="NoopLogger.Debug"></a>
### func \(NoopLogger\) Debug
```go
func (NoopLogger) Debug(string, ...any)
```
<a name="NoopLogger.Error"></a>
### func \(NoopLogger\) Error
```go
func (NoopLogger) Error(string, ...any)
```
<a name="NoopLogger.Info"></a>
### func \(NoopLogger\) Info
```go
func (NoopLogger) Info(string, ...any)
```
<a name="NoopLogger.Warn"></a>
### func \(NoopLogger\) Warn
```go
func (NoopLogger) Warn(string, ...any)
```
<a name="Option"></a>
## type Option
Option configures a Bot at construction time. Per\-call configuration is expressed via typed parameter structs \(e.g. SendMessageParams\), not options.
```go
type Option func(*Bot)
```
<a name="WithBaseURL"></a>
### func WithBaseURL
```go
func WithBaseURL(url string) Option
```
WithBaseURL overrides the API base URL. Useful for testing against a local httptest.Server, or for self\-hosted Bot API servers.
<a name="WithCodec"></a>
### func WithCodec
```go
func WithCodec(c Codec) Option
```
WithCodec overrides the JSON codec. Pass goccy/go\-json, sonic, or any type implementing Codec to swap out encoding/json.
<a name="WithHTTPClient"></a>
### func WithHTTPClient
```go
func WithHTTPClient(c HTTPDoer) Option
```
WithHTTPClient overrides the HTTP transport. Pass any HTTPDoer implementation \(e.g. an \*http.Client wrapping a custom RoundTripper, or a fasthttp adapter\).
<a name="WithLogger"></a>
### func WithLogger
```go
func WithLogger(l Logger) Option
```
WithLogger sets the logger used for diagnostic events. Passing nil silently disables logging.
<a name="ParseError"></a>
## type ParseError
ParseError wraps a JSON decode failure on a response body. Body is retained \(truncated to 4 KiB\); Error\(\) displays up to 256 bytes for diagnostics.
```go
type ParseError struct {
Err error
Body []byte
}
```
<a name="ParseError.Error"></a>
### func \(\*ParseError\) Error
```go
func (e *ParseError) Error() string
```
<a name="ParseError.Unwrap"></a>
### func \(\*ParseError\) Unwrap
```go
func (e *ParseError) Unwrap() error
```
<a name="ResponseParameters"></a>
## type ResponseParameters
ResponseParameters is the optional metadata Telegram includes on certain failures. The most common is RetryAfter \(seconds\) on 429 responses.
This type is duplicated in package api for users; keeping a copy here avoids an import cycle \(api imports client, not vice versa\).
```go
type ResponseParameters struct {
MigrateToChatID int64 `json:"migrate_to_chat_id,omitempty"`
RetryAfter int `json:"retry_after,omitempty"`
}
```
<a name="Result"></a>
## type Result
Result is the universal Telegram API response envelope. Every successful response is shaped \{"ok":true,"result":T,...\}; failure responses set ok to false and populate ErrorCode / Description / Parameters.
Result is generic over T so generated method wrappers can decode the strongly\-typed payload directly. Users do not normally construct or inspect Result values; method wrappers unwrap them and return either the typed payload or a \*APIError.
```go
type Result[T any] struct {
OK bool `json:"ok"`
Result T `json:"result,omitempty"`
ErrorCode int `json:"error_code,omitempty"`
Description string `json:"description,omitempty"`
Parameters *ResponseParameters `json:"parameters,omitempty"`
}
```
<a name="RetryDoer"></a>
## type RetryDoer
RetryDoer is an HTTPDoer that retries transient failures \(429, 5xx, and network errors\) with exponential backoff. It honours the retry\_after value Telegram supplies on rate\-limit responses.
Wrap any HTTPDoer to add retry behaviour:
```
bot := client.New(token, client.WithHTTPClient(
client.NewRetryDoer(client.NewDefaultHTTPDoer())))
```
```go
type RetryDoer struct {
// contains filtered or unexported fields
}
```
<a name="NewRetryDoer"></a>
### func NewRetryDoer
```go
func NewRetryDoer(inner HTTPDoer, opts ...RetryOption) *RetryDoer
```
NewRetryDoer wraps inner with retry behaviour.
<a name="RetryDoer.Do"></a>
### func \(\*RetryDoer\) Do
```go
func (d *RetryDoer) Do(req *http.Request) (*http.Response, error)
```
Do dispatches via the inner HTTPDoer and retries on transient failures. The request body is buffered on first attempt so it can be replayed.
<a name="RetryOption"></a>
## type RetryOption
RetryOption configures a RetryDoer.
```go
type RetryOption func(*RetryDoer)
```
<a name="WithBackoffFactor"></a>
### func WithBackoffFactor
```go
func WithBackoffFactor(f float64) RetryOption
```
WithBackoffFactor sets the exponential growth factor. Default 2.0.
<a name="WithBaseBackoff"></a>
### func WithBaseBackoff
```go
func WithBaseBackoff(d time.Duration) RetryOption
```
WithBaseBackoff sets the initial backoff duration. Default 500ms.
<a name="WithJitter"></a>
### func WithJitter
```go
func WithJitter(j float64) RetryOption
```
WithJitter sets the jitter fraction \(0..1\) applied to each backoff. Default 0.2.
<a name="WithMaxAttempts"></a>
### func WithMaxAttempts
```go
func WithMaxAttempts(n int) RetryOption
```
WithMaxAttempts sets the maximum number of attempts \(including the initial one\). Default 4 \(one initial \+ three retries\).
<a name="WithMaxBackoff"></a>
### func WithMaxBackoff
```go
func WithMaxBackoff(d time.Duration) RetryOption
```
WithMaxBackoff caps the backoff at max. Default 30s.
Generated by [gomarkdoc](<https://github.com/princjef/gomarkdoc>)
+668
View File
@@ -0,0 +1,668 @@
<!-- Code generated by gomarkdoc. DO NOT EDIT -->
# dispatch
```go
import "github.com/lukaszraczylo/go-telegram/dispatch"
```
Package dispatch provides a typed router for Telegram updates. It consumes any transport.Updater and dispatches updates to handlers registered by command, regex, or update\-payload kind.
## Index
- [Variables](<#variables>)
- [type Context](<#Context>)
- [func NewContext\(ctx context.Context, b \*client.Bot, u \*api.Update\) \*Context](<#NewContext>)
- [type Filter](<#Filter>)
- [func All\[T any\]\(filters ...Filter\[T\]\) Filter\[T\]](<#All>)
- [func Any\[T any\]\(filters ...Filter\[T\]\) Filter\[T\]](<#Any>)
- [func \(f Filter\[T\]\) And\(others ...Filter\[T\]\) Filter\[T\]](<#Filter[T].And>)
- [func \(f Filter\[T\]\) Not\(\) Filter\[T\]](<#Filter[T].Not>)
- [func \(f Filter\[T\]\) Or\(others ...Filter\[T\]\) Filter\[T\]](<#Filter[T].Or>)
- [type Handler](<#Handler>)
- [type Middleware](<#Middleware>)
- [func Chain\[T any\]\(mws ...Middleware\[T\]\) Middleware\[T\]](<#Chain>)
- [func Recovery\(\) Middleware\[\*api.Update\]](<#Recovery>)
- [type NamedHandlers](<#NamedHandlers>)
- [func NewNamedHandlers\[T any\]\(\) \*NamedHandlers\[T\]](<#NewNamedHandlers>)
- [func \(n \*NamedHandlers\[T\]\) Handler\(\) Handler\[T\]](<#NamedHandlers[T].Handler>)
- [func \(n \*NamedHandlers\[T\]\) Has\(name string\) bool](<#NamedHandlers[T].Has>)
- [func \(n \*NamedHandlers\[T\]\) Names\(\) \[\]string](<#NamedHandlers[T].Names>)
- [func \(n \*NamedHandlers\[T\]\) Remove\(name string\) bool](<#NamedHandlers[T].Remove>)
- [func \(n \*NamedHandlers\[T\]\) Set\(name string, h Handler\[T\]\)](<#NamedHandlers[T].Set>)
- [type Router](<#Router>)
- [func New\(b \*client.Bot, opts ...RouterOption\) \*Router](<#New>)
- [func \(r \*Router\) Group\(group int\) \*RouterScope](<#Router.Group>)
- [func \(r \*Router\) OnBusinessConnection\(h Handler\[\*api.BusinessConnection\]\)](<#Router.OnBusinessConnection>)
- [func \(r \*Router\) OnCallback\(pattern string, h Handler\[\*api.CallbackQuery\]\)](<#Router.OnCallback>)
- [func \(r \*Router\) OnCallbackFilter\(f Filter\[\*api.CallbackQuery\], h Handler\[\*api.CallbackQuery\]\)](<#Router.OnCallbackFilter>)
- [func \(r \*Router\) OnChannelPost\(h Handler\[\*api.Message\]\)](<#Router.OnChannelPost>)
- [func \(r \*Router\) OnChatBoost\(h Handler\[\*api.ChatBoostUpdated\]\)](<#Router.OnChatBoost>)
- [func \(r \*Router\) OnChatJoinRequest\(h Handler\[\*api.ChatJoinRequest\]\)](<#Router.OnChatJoinRequest>)
- [func \(r \*Router\) OnChatJoinRequestFilter\(f Filter\[\*api.ChatJoinRequest\], h Handler\[\*api.ChatJoinRequest\]\)](<#Router.OnChatJoinRequestFilter>)
- [func \(r \*Router\) OnChatMember\(h Handler\[\*api.ChatMemberUpdated\]\)](<#Router.OnChatMember>)
- [func \(r \*Router\) OnChatMemberFilter\(f Filter\[\*api.ChatMemberUpdated\], h Handler\[\*api.ChatMemberUpdated\]\)](<#Router.OnChatMemberFilter>)
- [func \(r \*Router\) OnChosenInlineResult\(h Handler\[\*api.ChosenInlineResult\]\)](<#Router.OnChosenInlineResult>)
- [func \(r \*Router\) OnCommand\(cmd string, h Handler\[\*api.Message\]\)](<#Router.OnCommand>)
- [func \(r \*Router\) OnEditedChannelPost\(h Handler\[\*api.Message\]\)](<#Router.OnEditedChannelPost>)
- [func \(r \*Router\) OnEditedMessage\(h Handler\[\*api.Message\]\)](<#Router.OnEditedMessage>)
- [func \(r \*Router\) OnInlineQuery\(h Handler\[\*api.InlineQuery\]\)](<#Router.OnInlineQuery>)
- [func \(r \*Router\) OnInlineQueryFilter\(f Filter\[\*api.InlineQuery\], h Handler\[\*api.InlineQuery\]\)](<#Router.OnInlineQueryFilter>)
- [func \(r \*Router\) OnMessageFilter\(f Filter\[\*api.Message\], h Handler\[\*api.Message\]\)](<#Router.OnMessageFilter>)
- [func \(r \*Router\) OnMessageReaction\(h Handler\[\*api.MessageReactionUpdated\]\)](<#Router.OnMessageReaction>)
- [func \(r \*Router\) OnMessageReactionCount\(h Handler\[\*api.MessageReactionCountUpdated\]\)](<#Router.OnMessageReactionCount>)
- [func \(r \*Router\) OnMyChatMember\(h Handler\[\*api.ChatMemberUpdated\]\)](<#Router.OnMyChatMember>)
- [func \(r \*Router\) OnMyChatMemberFilter\(f Filter\[\*api.ChatMemberUpdated\], h Handler\[\*api.ChatMemberUpdated\]\)](<#Router.OnMyChatMemberFilter>)
- [func \(r \*Router\) OnPoll\(h Handler\[\*api.Poll\]\)](<#Router.OnPoll>)
- [func \(r \*Router\) OnPollAnswer\(h Handler\[\*api.PollAnswer\]\)](<#Router.OnPollAnswer>)
- [func \(r \*Router\) OnPreCheckoutQuery\(h Handler\[\*api.PreCheckoutQuery\]\)](<#Router.OnPreCheckoutQuery>)
- [func \(r \*Router\) OnPreCheckoutQueryFilter\(f Filter\[\*api.PreCheckoutQuery\], h Handler\[\*api.PreCheckoutQuery\]\)](<#Router.OnPreCheckoutQueryFilter>)
- [func \(r \*Router\) OnPurchasedPaidMedia\(h Handler\[\*api.PaidMediaPurchased\]\)](<#Router.OnPurchasedPaidMedia>)
- [func \(r \*Router\) OnRemovedChatBoost\(h Handler\[\*api.ChatBoostRemoved\]\)](<#Router.OnRemovedChatBoost>)
- [func \(r \*Router\) OnShippingQuery\(h Handler\[\*api.ShippingQuery\]\)](<#Router.OnShippingQuery>)
- [func \(r \*Router\) OnText\(pattern string, h Handler\[\*api.Message\]\)](<#Router.OnText>)
- [func \(r \*Router\) Run\(ctx context.Context, u transport.Updater\) error](<#Router.Run>)
- [func \(r \*Router\) Use\(mw Middleware\[\*api.Update\]\)](<#Router.Use>)
- [type RouterOption](<#RouterOption>)
- [func WithMaxConcurrency\(n int\) RouterOption](<#WithMaxConcurrency>)
- [type RouterScope](<#RouterScope>)
- [func \(s \*RouterScope\) OnCommand\(cmd string, h Handler\[\*api.Message\]\)](<#RouterScope.OnCommand>)
- [func \(s \*RouterScope\) OnMessageFilter\(f Filter\[\*api.Message\], h Handler\[\*api.Message\]\)](<#RouterScope.OnMessageFilter>)
- [func \(s \*RouterScope\) OnText\(pattern string, h Handler\[\*api.Message\]\)](<#RouterScope.OnText>)
## Variables
<a name="ErrContinueGroups"></a>ErrContinueGroups signals that this group's handler should be treated as not\-matching when returned by a handler: dispatch moves on to the next handler in the same group, then to subsequent groups.
Without ErrContinueGroups, a non\-error return from a matched handler stops dispatch \(default first\-match\-wins semantics\).
```go
var ErrContinueGroups = errors.New("dispatch: continue groups")
```
<a name="ErrEndGroups"></a>ErrEndGroups stops dispatch from running any further handlers in any group for this update when returned by a handler. Use it to indicate the update has been definitively handled.
errors.Is\(err, ErrEndGroups\) is the canonical check, though dispatch itself recognises it by exact identity.
```go
var ErrEndGroups = errors.New("dispatch: end groups")
```
<a name="Context"></a>
## type Context
Context bundles the per\-update state every handler receives.
Ctx is the request context propagated from Router.Run; cancelling the run cancels every handler.
Bot is the API client. Handlers reply by calling api.SendMessage\(c.Ctx, c.Bot, ...\) etc.
Update is the raw update; payload\-typed handlers also receive a narrowed pointer to one of its sub\-fields.
Values is a per\-update bag matchers populate. Conventional keys:
```
"command": string, the matched bot command (e.g. "/start")
"command_args": string, everything after the command
"regex_match": []string, regex sub-matches when OnText matches
```
```go
type Context struct {
Ctx context.Context
Bot *client.Bot
Update *api.Update
Values map[string]any
}
```
<a name="NewContext"></a>
### func NewContext
```go
func NewContext(ctx context.Context, b *client.Bot, u *api.Update) *Context
```
NewContext constructs a Context. Used by Router internally; exposed for custom test harnesses.
<a name="Filter"></a>
## type Filter
Filter is a predicate over a typed payload \(e.g. \*api.Message\). Filters compose via And/Or/Not for multi\-condition matching.
Example:
```
f := message.HasPhoto().And(message.InChat(-100123456789))
```
```go
type Filter[T any] func(payload T) bool
```
<a name="All"></a>
### func All
```go
func All[T any](filters ...Filter[T]) Filter[T]
```
All combines filters with AND. Returns a Filter that matches when all match. Returns a filter that always matches when filters is empty.
<a name="Any"></a>
### func Any
```go
func Any[T any](filters ...Filter[T]) Filter[T]
```
Any combines filters with OR. Returns a Filter that matches when at least one matches. Returns a filter that never matches when filters is empty.
<a name="Filter[T].And"></a>
### func \(Filter\[T\]\) And
```go
func (f Filter[T]) And(others ...Filter[T]) Filter[T]
```
And returns a Filter that matches iff f and every one of others matches.
<a name="Filter[T].Not"></a>
### func \(Filter\[T\]\) Not
```go
func (f Filter[T]) Not() Filter[T]
```
Not returns a Filter that inverts f.
<a name="Filter[T].Or"></a>
### func \(Filter\[T\]\) Or
```go
func (f Filter[T]) Or(others ...Filter[T]) Filter[T]
```
Or returns a Filter that matches iff f matches OR any of others matches.
<a name="Handler"></a>
## type Handler
Handler is a generic handler over update payload type T. T is typically \*api.Message, \*api.CallbackQuery, \*api.InlineQuery, or \*api.Update for global middleware.
```go
type Handler[T any] func(ctx *Context, payload T) error
```
<a name="Middleware"></a>
## type Middleware
Middleware wraps a Handler\[T\] with cross\-cutting behaviour \(logging, recovery, auth\). Middleware composition is left\-to\-right: Use\(a,b,c\) runs as a\(b\(c\(handler\)\)\).
```go
type Middleware[T any] func(Handler[T]) Handler[T]
```
<a name="Chain"></a>
### func Chain
```go
func Chain[T any](mws ...Middleware[T]) Middleware[T]
```
Chain composes a slice of middleware into a single Middleware\[T\].
<a name="Recovery"></a>
### func Recovery
```go
func Recovery() Middleware[*api.Update]
```
Recovery returns middleware that recovers from panics in downstream handlers, converting them into a returned error and logging via the bot's configured logger. Registered automatically by NewRouter.
<a name="NamedHandlers"></a>
## type NamedHandlers
NamedHandlers manages handlers by string name, allowing runtime registration, replacement, and removal. This complements the Router's registration methods: each registration via Named\*\(\) also gets a name for later lookup.
Use case: a plugin system that loads/unloads command handlers without restarting the bot.
```go
type NamedHandlers[T any] struct {
// contains filtered or unexported fields
}
```
<a name="NewNamedHandlers"></a>
### func NewNamedHandlers
```go
func NewNamedHandlers[T any]() *NamedHandlers[T]
```
NewNamedHandlers returns a new, empty NamedHandlers\[T\].
<a name="NamedHandlers[T].Handler"></a>
### func \(\*NamedHandlers\[T\]\) Handler
```go
func (n *NamedHandlers[T]) Handler() Handler[T]
```
Handler returns a single Handler\[T\] that runs each registered handler in registration order, first non\-nil error stops the chain. Use this to wire NamedHandlers into a Router.OnXxx call:
```
names := dispatch.NewNamedHandlers[*api.Message]()
names.Set("logger", loggingHandler)
names.Set("audit", auditHandler)
router.OnCommand("/admin", names.Handler())
```
Subsequent Set/Remove calls take effect on the next dispatch.
<a name="NamedHandlers[T].Has"></a>
### func \(\*NamedHandlers\[T\]\) Has
```go
func (n *NamedHandlers[T]) Has(name string) bool
```
Has reports whether name is registered.
<a name="NamedHandlers[T].Names"></a>
### func \(\*NamedHandlers\[T\]\) Names
```go
func (n *NamedHandlers[T]) Names() []string
```
Names returns the registered names in registration order.
<a name="NamedHandlers[T].Remove"></a>
### func \(\*NamedHandlers\[T\]\) Remove
```go
func (n *NamedHandlers[T]) Remove(name string) bool
```
Remove unregisters the handler under name. Returns true if it existed.
<a name="NamedHandlers[T].Set"></a>
### func \(\*NamedHandlers\[T\]\) Set
```go
func (n *NamedHandlers[T]) Set(name string, h Handler[T])
```
Set registers or replaces the handler under name. If name is new, it is appended to the end of the registration order.
<a name="Router"></a>
## type Router
Router dispatches updates from any Updater to typed handlers.
Matchers run in registration order; first match wins. A panic\-recovery middleware is attached automatically and runs around every dispatch.
```go
type Router struct {
// contains filtered or unexported fields
}
```
<a name="New"></a>
### func New
```go
func New(b *client.Bot, opts ...RouterOption) *Router
```
New constructs a Router. Recovery middleware is added by default; users can disable it by passing WithoutRecovery \(not implemented here, but the hook is in place via Use\).
<a name="Router.Group"></a>
### func \(\*Router\) Group
```go
func (r *Router) Group(group int) *RouterScope
```
Group returns a RouterScope that registers handlers in the given group. Group 0 \(the default\) runs first, then group 1, etc. Within a group, handlers run in registration order; the first non\-skipped match terminates dispatch unless the handler returns ErrContinueGroups.
<a name="Router.OnBusinessConnection"></a>
### func \(\*Router\) OnBusinessConnection
```go
func (r *Router) OnBusinessConnection(h Handler[*api.BusinessConnection])
```
OnBusinessConnection registers a handler for business connection updates.
<a name="Router.OnCallback"></a>
### func \(\*Router\) OnCallback
```go
func (r *Router) OnCallback(pattern string, h Handler[*api.CallbackQuery])
```
OnCallback registers a handler for callback queries whose Data matches the regex.
Panics at registration time if pattern is not a valid regular expression.
<a name="Router.OnCallbackFilter"></a>
### func \(\*Router\) OnCallbackFilter
```go
func (r *Router) OnCallbackFilter(f Filter[*api.CallbackQuery], h Handler[*api.CallbackQuery])
```
OnCallbackFilter registers a typed callback\-query handler gated by filter f. Filter routes are checked after pattern\-based OnCallback routes; first match wins.
<a name="Router.OnChannelPost"></a>
### func \(\*Router\) OnChannelPost
```go
func (r *Router) OnChannelPost(h Handler[*api.Message])
```
OnChannelPost registers a handler for channel post updates.
<a name="Router.OnChatBoost"></a>
### func \(\*Router\) OnChatBoost
```go
func (r *Router) OnChatBoost(h Handler[*api.ChatBoostUpdated])
```
OnChatBoost registers a handler for chat boost updates.
<a name="Router.OnChatJoinRequest"></a>
### func \(\*Router\) OnChatJoinRequest
```go
func (r *Router) OnChatJoinRequest(h Handler[*api.ChatJoinRequest])
```
OnChatJoinRequest registers a handler for chat join requests.
<a name="Router.OnChatJoinRequestFilter"></a>
### func \(\*Router\) OnChatJoinRequestFilter
```go
func (r *Router) OnChatJoinRequestFilter(f Filter[*api.ChatJoinRequest], h Handler[*api.ChatJoinRequest])
```
OnChatJoinRequestFilter registers a filtered handler for chat join requests.
<a name="Router.OnChatMember"></a>
### func \(\*Router\) OnChatMember
```go
func (r *Router) OnChatMember(h Handler[*api.ChatMemberUpdated])
```
OnChatMember registers a handler for chat member status changes.
<a name="Router.OnChatMemberFilter"></a>
### func \(\*Router\) OnChatMemberFilter
```go
func (r *Router) OnChatMemberFilter(f Filter[*api.ChatMemberUpdated], h Handler[*api.ChatMemberUpdated])
```
OnChatMemberFilter registers a filtered handler for chat member status changes.
<a name="Router.OnChosenInlineResult"></a>
### func \(\*Router\) OnChosenInlineResult
```go
func (r *Router) OnChosenInlineResult(h Handler[*api.ChosenInlineResult])
```
OnChosenInlineResult registers a handler for chosen inline results.
<a name="Router.OnCommand"></a>
### func \(\*Router\) OnCommand
```go
func (r *Router) OnCommand(cmd string, h Handler[*api.Message])
```
OnCommand registers a handler for a slash command. The command string includes the leading slash \(e.g. "/start"\). Matching strips an optional "@BotName" suffix.
<a name="Router.OnEditedChannelPost"></a>
### func \(\*Router\) OnEditedChannelPost
```go
func (r *Router) OnEditedChannelPost(h Handler[*api.Message])
```
OnEditedChannelPost registers a handler for edited channel post updates.
<a name="Router.OnEditedMessage"></a>
### func \(\*Router\) OnEditedMessage
```go
func (r *Router) OnEditedMessage(h Handler[*api.Message])
```
OnEditedMessage registers a handler for edited message updates.
<a name="Router.OnInlineQuery"></a>
### func \(\*Router\) OnInlineQuery
```go
func (r *Router) OnInlineQuery(h Handler[*api.InlineQuery])
```
OnInlineQuery registers a handler for inline queries \(one matcher only; inline queries are not partitioned by content here\).
<a name="Router.OnInlineQueryFilter"></a>
### func \(\*Router\) OnInlineQueryFilter
```go
func (r *Router) OnInlineQueryFilter(f Filter[*api.InlineQuery], h Handler[*api.InlineQuery])
```
OnInlineQueryFilter registers an inline\-query handler gated by filter f. Filter routes are checked after bare OnInlineQuery handlers; first match wins.
<a name="Router.OnMessageFilter"></a>
### func \(\*Router\) OnMessageFilter
```go
func (r *Router) OnMessageFilter(f Filter[*api.Message], h Handler[*api.Message])
```
OnMessageFilter registers a typed message handler gated by filter f. Filter routes are checked after command and text routes; first match wins.
<a name="Router.OnMessageReaction"></a>
### func \(\*Router\) OnMessageReaction
```go
func (r *Router) OnMessageReaction(h Handler[*api.MessageReactionUpdated])
```
OnMessageReaction registers a handler for message reaction updates.
<a name="Router.OnMessageReactionCount"></a>
### func \(\*Router\) OnMessageReactionCount
```go
func (r *Router) OnMessageReactionCount(h Handler[*api.MessageReactionCountUpdated])
```
OnMessageReactionCount registers a handler for anonymous message reaction count updates.
<a name="Router.OnMyChatMember"></a>
### func \(\*Router\) OnMyChatMember
```go
func (r *Router) OnMyChatMember(h Handler[*api.ChatMemberUpdated])
```
OnMyChatMember registers a handler for bot's own chat member status changes.
<a name="Router.OnMyChatMemberFilter"></a>
### func \(\*Router\) OnMyChatMemberFilter
```go
func (r *Router) OnMyChatMemberFilter(f Filter[*api.ChatMemberUpdated], h Handler[*api.ChatMemberUpdated])
```
OnMyChatMemberFilter registers a filtered handler for bot's own chat member status changes.
<a name="Router.OnPoll"></a>
### func \(\*Router\) OnPoll
```go
func (r *Router) OnPoll(h Handler[*api.Poll])
```
OnPoll registers a handler for poll state updates.
<a name="Router.OnPollAnswer"></a>
### func \(\*Router\) OnPollAnswer
```go
func (r *Router) OnPollAnswer(h Handler[*api.PollAnswer])
```
OnPollAnswer registers a handler for poll answer updates.
<a name="Router.OnPreCheckoutQuery"></a>
### func \(\*Router\) OnPreCheckoutQuery
```go
func (r *Router) OnPreCheckoutQuery(h Handler[*api.PreCheckoutQuery])
```
OnPreCheckoutQuery registers a handler for pre\-checkout queries.
<a name="Router.OnPreCheckoutQueryFilter"></a>
### func \(\*Router\) OnPreCheckoutQueryFilter
```go
func (r *Router) OnPreCheckoutQueryFilter(f Filter[*api.PreCheckoutQuery], h Handler[*api.PreCheckoutQuery])
```
OnPreCheckoutQueryFilter registers a filtered handler for pre\-checkout queries.
<a name="Router.OnPurchasedPaidMedia"></a>
### func \(\*Router\) OnPurchasedPaidMedia
```go
func (r *Router) OnPurchasedPaidMedia(h Handler[*api.PaidMediaPurchased])
```
OnPurchasedPaidMedia registers a handler for purchased paid media updates.
<a name="Router.OnRemovedChatBoost"></a>
### func \(\*Router\) OnRemovedChatBoost
```go
func (r *Router) OnRemovedChatBoost(h Handler[*api.ChatBoostRemoved])
```
OnRemovedChatBoost registers a handler for removed chat boost updates.
<a name="Router.OnShippingQuery"></a>
### func \(\*Router\) OnShippingQuery
```go
func (r *Router) OnShippingQuery(h Handler[*api.ShippingQuery])
```
OnShippingQuery registers a handler for shipping queries.
<a name="Router.OnText"></a>
### func \(\*Router\) OnText
```go
func (r *Router) OnText(pattern string, h Handler[*api.Message])
```
OnText registers a handler for messages whose Text matches the regex.
Panics at registration time if pattern is not a valid regular expression.
<a name="Router.Run"></a>
### func \(\*Router\) Run
```go
func (r *Router) Run(ctx context.Context, u transport.Updater) error
```
Run consumes the Updater and dispatches each update. It blocks until the Updater's channel is closed or ctx is cancelled.
By default updates are processed concurrently \(up to WithMaxConcurrency\(50\) goroutines\). Handlers for different updates may therefore run simultaneously; shared state must be protected. Pass WithMaxConcurrency\(0\) to New to restore serial \(legacy\) behaviour.
Run waits for all in\-flight handlers to finish before returning.
<a name="Router.Use"></a>
### func \(\*Router\) Use
```go
func (r *Router) Use(mw Middleware[*api.Update])
```
Use registers a global middleware applied to every Update dispatch.
<a name="RouterOption"></a>
## type RouterOption
RouterOption configures a Router at construction time.
```go
type RouterOption func(*Router)
```
<a name="WithMaxConcurrency"></a>
### func WithMaxConcurrency
```go
func WithMaxConcurrency(n int) RouterOption
```
WithMaxConcurrency sets the maximum number of updates processed in parallel. Default is 50. Pass 0 to dispatch serially \(one update at a time, in the calling goroutine — the legacy behaviour before v1.1.0\).
Note: concurrent dispatch means handlers for different updates may run simultaneously. Handlers that mutate shared state must be safe for concurrent access.
<a name="RouterScope"></a>
## type RouterScope
RouterScope registers handlers into a specific priority group on its parent Router. Group 0 runs first, then group 1, etc. Within a group, handlers run in registration order; the first non\-skipped match terminates dispatch unless the handler returns ErrContinueGroups.
```go
type RouterScope struct {
// contains filtered or unexported fields
}
```
<a name="RouterScope.OnCommand"></a>
### func \(\*RouterScope\) OnCommand
```go
func (s *RouterScope) OnCommand(cmd string, h Handler[*api.Message])
```
OnCommand registers a command handler in this group.
<a name="RouterScope.OnMessageFilter"></a>
### func \(\*RouterScope\) OnMessageFilter
```go
func (s *RouterScope) OnMessageFilter(f Filter[*api.Message], h Handler[*api.Message])
```
OnMessageFilter registers a filter\-based message handler in this group.
<a name="RouterScope.OnText"></a>
### func \(\*RouterScope\) OnText
```go
func (s *RouterScope) OnText(pattern string, h Handler[*api.Message])
```
OnText registers a regex text handler in this group. Panics at registration time if pattern is not a valid regular expression.
Generated by [gomarkdoc](<https://github.com/princjef/gomarkdoc>)
+243
View File
@@ -0,0 +1,243 @@
<!-- Code generated by gomarkdoc. DO NOT EDIT -->
# conversation
```go
import "github.com/lukaszraczylo/go-telegram/dispatch/conversation"
```
Package conversation implements a stateful conversation handler for the go\-telegram dispatch router. It provides a state\-machine abstraction over multi\-step Telegram bot interactions, with pluggable storage and flexible key strategies.
## Index
- [Variables](<#variables>)
- [func End\(\) error](<#End>)
- [func Next\(s State\) error](<#Next>)
- [type Conversation](<#Conversation>)
- [func \(c \*Conversation\) Dispatch\(next dispatch.Handler\[\*api.Update\]\) dispatch.Handler\[\*api.Update\]](<#Conversation.Dispatch>)
- [type Handler](<#Handler>)
- [type KeyStrategy](<#KeyStrategy>)
- [type MemoryStorage](<#MemoryStorage>)
- [func NewMemoryStorage\(\) \*MemoryStorage](<#NewMemoryStorage>)
- [func \(s \*MemoryStorage\) Delete\(\_ context.Context, key string\) error](<#MemoryStorage.Delete>)
- [func \(s \*MemoryStorage\) Get\(\_ context.Context, key string\) \(State, error\)](<#MemoryStorage.Get>)
- [func \(s \*MemoryStorage\) Set\(\_ context.Context, key string, state State\) error](<#MemoryStorage.Set>)
- [type State](<#State>)
- [type Step](<#Step>)
- [type Storage](<#Storage>)
## Variables
<a name="ErrKeyNotFound"></a>ErrKeyNotFound is returned by Storage.Get when no conversation is active for the given key.
```go
var ErrKeyNotFound = errors.New("conversation: key not found")
```
<a name="End"></a>
## func End
```go
func End() error
```
End signals the conversation has finished and state should be cleared. Conversation handlers return End\(\) to terminate.
<a name="Next"></a>
## func Next
```go
func Next(s State) error
```
Next signals the conversation should advance to the given state. Conversation handlers return Next\("state\_name"\) to transition.
<a name="Conversation"></a>
## type Conversation
Conversation is a stateful handler with entry, per\-state, exit and fallback steps. A conversation is keyed by KeyStrategy \(default KeyByUserAndChat\) and persisted by Storage \(default in\-memory\).
```go
type Conversation struct {
// EntryPoints starts a new conversation when a matching filter fires
// and no conversation is already active for the key.
EntryPoints []Step
// States maps each state to the steps that handle it.
States map[State][]Step
// Exits, if any match, end the active conversation early. Useful for
// /cancel-style commands.
Exits []Step
// Fallbacks run when no state step matches the current update.
Fallbacks []Step
// Storage persists conversation state. Defaults to NewMemoryStorage.
Storage Storage
// KeyStrategy derives the persistence key. Defaults to KeyByUserAndChat.
KeyStrategy KeyStrategy
// AllowReEntry, when true, lets entry-point steps fire even while a
// conversation is already active for the key (effectively restarting it).
AllowReEntry bool
}
```
<a name="Conversation.Dispatch"></a>
### func \(\*Conversation\) Dispatch
```go
func (c *Conversation) Dispatch(next dispatch.Handler[*api.Update]) dispatch.Handler[*api.Update]
```
Dispatch is a global middleware\-shaped Handler that consumes updates and routes them through the conversation graph. Register via router.Use\(conv.Dispatch\).
If the conversation claims an update, downstream handlers are skipped. If the conversation does not claim it, downstream handlers run as normal.
<a name="Handler"></a>
## type Handler
Handler defines a step in the conversation. Receives the dispatch context and the raw update. Returns:
- nil to stay in the current state
- Next\("state"\) to transition to a different state
- End\(\) to end the conversation
- any other non\-nil error to surface to the dispatcher \(state unchanged\)
```go
type Handler func(ctx *dispatch.Context, u *api.Update) error
```
<a name="KeyStrategy"></a>
## type KeyStrategy
KeyStrategy derives a persistence key from an update. Strategies determine how conversation scope works — per\-user, per\-chat, or per\-user\-and\-chat. Implementations must return a stable string for the same logical scope across updates.
Returns the empty string if the update doesn't have enough context to derive a key \(in which case the conversation handler skips it\).
```go
type KeyStrategy func(u *api.Update) string
```
<a name="KeyByChat"></a>KeyByChat derives a key from the chat ID. Useful for group flows where any user in the chat can drive the conversation.
```go
var KeyByChat KeyStrategy = func(u *api.Update) string {
if cid := chatID(u); cid != 0 {
return fmt.Sprintf("c:%d", cid)
}
return ""
}
```
<a name="KeyByUser"></a>KeyByUser derives a key from the sending user's ID. Useful for DM conversations and any flow that should follow the user across chats.
```go
var KeyByUser KeyStrategy = func(u *api.Update) string {
if uid := userID(u); uid != 0 {
return fmt.Sprintf("u:%d", uid)
}
return ""
}
```
<a name="KeyByUserAndChat"></a>KeyByUserAndChat derives a key from both user and chat IDs. The most common strategy: each user has their own conversation per chat.
```go
var KeyByUserAndChat KeyStrategy = func(u *api.Update) string {
uid := userID(u)
cid := chatID(u)
if uid == 0 || cid == 0 {
return ""
}
return fmt.Sprintf("uc:%d:%d", cid, uid)
}
```
<a name="MemoryStorage"></a>
## type MemoryStorage
MemoryStorage is the default in\-process Storage. It is safe for concurrent use. Conversation state is lost on process restart; use a custom Storage backed by a database for persistent flows.
```go
type MemoryStorage struct {
// contains filtered or unexported fields
}
```
<a name="NewMemoryStorage"></a>
### func NewMemoryStorage
```go
func NewMemoryStorage() *MemoryStorage
```
NewMemoryStorage constructs an empty in\-memory storage.
<a name="MemoryStorage.Delete"></a>
### func \(\*MemoryStorage\) Delete
```go
func (s *MemoryStorage) Delete(_ context.Context, key string) error
```
<a name="MemoryStorage.Get"></a>
### func \(\*MemoryStorage\) Get
```go
func (s *MemoryStorage) Get(_ context.Context, key string) (State, error)
```
<a name="MemoryStorage.Set"></a>
### func \(\*MemoryStorage\) Set
```go
func (s *MemoryStorage) Set(_ context.Context, key string, state State) error
```
<a name="State"></a>
## type State
State is a label identifying a node in the conversation graph. The empty string is the implicit "no active conversation" state.
```go
type State string
```
<a name="Step"></a>
## type Step
Step pairs a filter with a handler for one conversation step.
```go
type Step struct {
Filter dispatch.Filter[*api.Update]
Handler Handler
}
```
<a name="Storage"></a>
## type Storage
Storage persists per\-user \(or per\-chat, per\-message — depending on the KeyStrategy in use\) conversation state across update deliveries.
Implementations must be safe for concurrent use.
```go
type Storage interface {
Get(ctx context.Context, key string) (State, error)
Set(ctx context.Context, key string, state State) error
Delete(ctx context.Context, key string) error
}
```
Generated by [gomarkdoc](<https://github.com/princjef/gomarkdoc>)
@@ -0,0 +1,55 @@
<!-- Code generated by gomarkdoc. DO NOT EDIT -->
# callback
```go
import "github.com/lukaszraczylo/go-telegram/dispatch/filters/callback"
```
Package callback provides Filter helpers for \*api.CallbackQuery payloads.
## Index
- [func Data\(pattern string\) dispatch.Filter\[\*api.CallbackQuery\]](<#Data>)
- [func DataEquals\(s string\) dispatch.Filter\[\*api.CallbackQuery\]](<#DataEquals>)
- [func DataPrefix\(prefix string\) dispatch.Filter\[\*api.CallbackQuery\]](<#DataPrefix>)
- [func FromUser\(userID int64\) dispatch.Filter\[\*api.CallbackQuery\]](<#FromUser>)
<a name="Data"></a>
## func Data
```go
func Data(pattern string) dispatch.Filter[*api.CallbackQuery]
```
Data returns a Filter that matches callback queries whose Data matches pattern \(regex\). Panics at registration time on an invalid pattern.
<a name="DataEquals"></a>
## func DataEquals
```go
func DataEquals(s string) dispatch.Filter[*api.CallbackQuery]
```
DataEquals returns a Filter that matches callback queries whose Data equals s exactly.
<a name="DataPrefix"></a>
## func DataPrefix
```go
func DataPrefix(prefix string) dispatch.Filter[*api.CallbackQuery]
```
DataPrefix returns a Filter that matches callback queries whose Data starts with prefix.
<a name="FromUser"></a>
## func FromUser
```go
func FromUser(userID int64) dispatch.Filter[*api.CallbackQuery]
```
FromUser returns a Filter that matches callback queries whose From.ID equals userID.
Generated by [gomarkdoc](<https://github.com/princjef/gomarkdoc>)
@@ -0,0 +1,35 @@
<!-- Code generated by gomarkdoc. DO NOT EDIT -->
# chatjoinrequest
```go
import "github.com/lukaszraczylo/go-telegram/dispatch/filters/chatjoinrequest"
```
Package chatjoinrequest provides Filter helpers for \*api.ChatJoinRequest payloads.
## Index
- [func FromUser\(uid int64\) dispatch.Filter\[\*api.ChatJoinRequest\]](<#FromUser>)
- [func InChat\(cid int64\) dispatch.Filter\[\*api.ChatJoinRequest\]](<#InChat>)
<a name="FromUser"></a>
## func FromUser
```go
func FromUser(uid int64) dispatch.Filter[*api.ChatJoinRequest]
```
FromUser returns a Filter that matches join requests where the requesting user's ID equals uid.
<a name="InChat"></a>
## func InChat
```go
func InChat(cid int64) dispatch.Filter[*api.ChatJoinRequest]
```
InChat returns a Filter that matches join requests directed at the chat with the given chat ID.
Generated by [gomarkdoc](<https://github.com/princjef/gomarkdoc>)
@@ -0,0 +1,35 @@
<!-- Code generated by gomarkdoc. DO NOT EDIT -->
# chatmember
```go
import "github.com/lukaszraczylo/go-telegram/dispatch/filters/chatmember"
```
Package chatmember provides Filter helpers for \*api.ChatMemberUpdated payloads.
## Index
- [func FromUser\(uid int64\) dispatch.Filter\[\*api.ChatMemberUpdated\]](<#FromUser>)
- [func NewStatus\(s string\) dispatch.Filter\[\*api.ChatMemberUpdated\]](<#NewStatus>)
<a name="FromUser"></a>
## func FromUser
```go
func FromUser(uid int64) dispatch.Filter[*api.ChatMemberUpdated]
```
FromUser returns a Filter that matches updates where the acting user \(From.ID\) equals uid.
<a name="NewStatus"></a>
## func NewStatus
```go
func NewStatus(s string) dispatch.Filter[*api.ChatMemberUpdated]
```
NewStatus returns a Filter that matches updates where the new chat member status equals s \(e.g. "member", "administrator", "kicked", "left"\).
Generated by [gomarkdoc](<https://github.com/princjef/gomarkdoc>)
+45
View File
@@ -0,0 +1,45 @@
<!-- Code generated by gomarkdoc. DO NOT EDIT -->
# inline
```go
import "github.com/lukaszraczylo/go-telegram/dispatch/filters/inline"
```
Package inline provides Filter helpers for \*api.InlineQuery payloads.
## Index
- [func Query\(pattern string\) dispatch.Filter\[\*api.InlineQuery\]](<#Query>)
- [func QueryEquals\(s string\) dispatch.Filter\[\*api.InlineQuery\]](<#QueryEquals>)
- [func QueryPrefix\(prefix string\) dispatch.Filter\[\*api.InlineQuery\]](<#QueryPrefix>)
<a name="Query"></a>
## func Query
```go
func Query(pattern string) dispatch.Filter[*api.InlineQuery]
```
Query returns a Filter that matches inline queries whose Query field matches pattern \(regex\). Panics at registration time on an invalid pattern.
<a name="QueryEquals"></a>
## func QueryEquals
```go
func QueryEquals(s string) dispatch.Filter[*api.InlineQuery]
```
QueryEquals returns a Filter that matches inline queries whose Query equals s exactly.
<a name="QueryPrefix"></a>
## func QueryPrefix
```go
func QueryPrefix(prefix string) dispatch.Filter[*api.InlineQuery]
```
QueryPrefix returns a Filter that matches inline queries whose Query starts with prefix.
Generated by [gomarkdoc](<https://github.com/princjef/gomarkdoc>)
+155
View File
@@ -0,0 +1,155 @@
<!-- Code generated by gomarkdoc. DO NOT EDIT -->
# message
```go
import "github.com/lukaszraczylo/go-telegram/dispatch/filters/message"
```
Package message provides Filter helpers for \*api.Message payloads.
## Index
- [func AnyCommand\(\) dispatch.Filter\[\*api.Message\]](<#AnyCommand>)
- [func ChatType\(t api.ChatType\) dispatch.Filter\[\*api.Message\]](<#ChatType>)
- [func Command\(name string\) dispatch.Filter\[\*api.Message\]](<#Command>)
- [func FromUser\(userID int64\) dispatch.Filter\[\*api.Message\]](<#FromUser>)
- [func HasDocument\(\) dispatch.Filter\[\*api.Message\]](<#HasDocument>)
- [func HasEntity\(t string\) dispatch.Filter\[\*api.Message\]](<#HasEntity>)
- [func HasPhoto\(\) dispatch.Filter\[\*api.Message\]](<#HasPhoto>)
- [func InChat\(chatID int64\) dispatch.Filter\[\*api.Message\]](<#InChat>)
- [func IsForward\(\) dispatch.Filter\[\*api.Message\]](<#IsForward>)
- [func IsReply\(\) dispatch.Filter\[\*api.Message\]](<#IsReply>)
- [func Text\(pattern string\) dispatch.Filter\[\*api.Message\]](<#Text>)
- [func TextContains\(sub string\) dispatch.Filter\[\*api.Message\]](<#TextContains>)
- [func TextEquals\(s string\) dispatch.Filter\[\*api.Message\]](<#TextEquals>)
- [func TextPrefix\(prefix string\) dispatch.Filter\[\*api.Message\]](<#TextPrefix>)
<a name="AnyCommand"></a>
## func AnyCommand
```go
func AnyCommand() dispatch.Filter[*api.Message]
```
AnyCommand returns a Filter that matches any message starting with a bot\_command entity at offset 0.
<a name="ChatType"></a>
## func ChatType
```go
func ChatType(t api.ChatType) dispatch.Filter[*api.Message]
```
ChatType returns a Filter that matches messages whose Chat.Type equals t.
<a name="Command"></a>
## func Command
```go
func Command(name string) dispatch.Filter[*api.Message]
```
Command returns a Filter that matches messages whose first entity is a bot\_command equal to "/\<name\>" \(with or without "@BotName" suffix\).
<a name="FromUser"></a>
## func FromUser
```go
func FromUser(userID int64) dispatch.Filter[*api.Message]
```
FromUser returns a Filter that matches messages whose From.ID equals userID.
<a name="HasDocument"></a>
## func HasDocument
```go
func HasDocument() dispatch.Filter[*api.Message]
```
HasDocument returns a Filter that matches messages with a Document attachment.
<a name="HasEntity"></a>
## func HasEntity
```go
func HasEntity(t string) dispatch.Filter[*api.Message]
```
HasEntity returns a Filter that matches messages whose Entities contain at least one entity of type t \(e.g. string\(api.EntityBotCommand\)\).
<a name="HasPhoto"></a>
## func HasPhoto
```go
func HasPhoto() dispatch.Filter[*api.Message]
```
HasPhoto returns a Filter that matches messages with a Photo attachment.
<a name="InChat"></a>
## func InChat
```go
func InChat(chatID int64) dispatch.Filter[*api.Message]
```
InChat returns a Filter that matches messages whose Chat.ID equals chatID.
<a name="IsForward"></a>
## func IsForward
```go
func IsForward() dispatch.Filter[*api.Message]
```
IsForward returns a Filter that matches messages that have ForwardOrigin set.
<a name="IsReply"></a>
## func IsReply
```go
func IsReply() dispatch.Filter[*api.Message]
```
IsReply returns a Filter that matches messages that have ReplyToMessage set.
<a name="Text"></a>
## func Text
```go
func Text(pattern string) dispatch.Filter[*api.Message]
```
Text returns a Filter that matches messages whose Text matches pattern \(regex\). Panics at registration time on an invalid pattern.
<a name="TextContains"></a>
## func TextContains
```go
func TextContains(sub string) dispatch.Filter[*api.Message]
```
TextContains returns a Filter that matches messages whose Text contains sub.
<a name="TextEquals"></a>
## func TextEquals
```go
func TextEquals(s string) dispatch.Filter[*api.Message]
```
TextEquals returns a Filter that matches messages whose Text equals s exactly.
<a name="TextPrefix"></a>
## func TextPrefix
```go
func TextPrefix(prefix string) dispatch.Filter[*api.Message]
```
TextPrefix returns a Filter that matches messages whose Text starts with prefix.
Generated by [gomarkdoc](<https://github.com/princjef/gomarkdoc>)
@@ -0,0 +1,35 @@
<!-- Code generated by gomarkdoc. DO NOT EDIT -->
# precheckoutquery
```go
import "github.com/lukaszraczylo/go-telegram/dispatch/filters/precheckoutquery"
```
Package precheckoutquery provides Filter helpers for \*api.PreCheckoutQuery payloads.
## Index
- [func Currency\(c string\) dispatch.Filter\[\*api.PreCheckoutQuery\]](<#Currency>)
- [func FromUser\(uid int64\) dispatch.Filter\[\*api.PreCheckoutQuery\]](<#FromUser>)
<a name="Currency"></a>
## func Currency
```go
func Currency(c string) dispatch.Filter[*api.PreCheckoutQuery]
```
Currency returns a Filter that matches pre\-checkout queries with the given ISO 4217 currency code \(e.g. "USD", "EUR", "XTR"\).
<a name="FromUser"></a>
## func FromUser
```go
func FromUser(uid int64) dispatch.Filter[*api.PreCheckoutQuery]
```
FromUser returns a Filter that matches pre\-checkout queries sent by the user with the given ID.
Generated by [gomarkdoc](<https://github.com/princjef/gomarkdoc>)
+241
View File
@@ -0,0 +1,241 @@
<!-- Code generated by gomarkdoc. DO NOT EDIT -->
# transport
```go
import "github.com/lukaszraczylo/go-telegram/transport"
```
Package transport provides update delivery mechanisms \(long\-poll and webhook\) that feed updates into the dispatch package's Router.
All implementations satisfy the Updater interface so user code can swap one for the other without touching handler logic.
## Index
- [type BackoffStrategy](<#BackoffStrategy>)
- [type ExponentialBackoff](<#ExponentialBackoff>)
- [func DefaultBackoff\(\) \*ExponentialBackoff](<#DefaultBackoff>)
- [func \(b \*ExponentialBackoff\) NextDelay\(attempt int\) time.Duration](<#ExponentialBackoff.NextDelay>)
- [type LongPoller](<#LongPoller>)
- [func NewLongPoller\(b \*client.Bot\) \*LongPoller](<#NewLongPoller>)
- [func \(p \*LongPoller\) Run\(ctx context.Context\) error](<#LongPoller.Run>)
- [func \(p \*LongPoller\) Stop\(ctx context.Context\) error](<#LongPoller.Stop>)
- [func \(p \*LongPoller\) Updates\(\) \<\-chan api.Update](<#LongPoller.Updates>)
- [type Updater](<#Updater>)
- [type WebhookOption](<#WebhookOption>)
- [func WithBufferSize\(n int\) WebhookOption](<#WithBufferSize>)
- [type WebhookServer](<#WebhookServer>)
- [func NewWebhookServer\(b \*client.Bot, opts ...WebhookOption\) \*WebhookServer](<#NewWebhookServer>)
- [func \(w \*WebhookServer\) ListenAndServe\(ctx context.Context, addr string\) error](<#WebhookServer.ListenAndServe>)
- [func \(w \*WebhookServer\) Run\(ctx context.Context\) error](<#WebhookServer.Run>)
- [func \(w \*WebhookServer\) ServeHTTP\(rw http.ResponseWriter, r \*http.Request\)](<#WebhookServer.ServeHTTP>)
- [func \(w \*WebhookServer\) Stop\(ctx context.Context\) error](<#WebhookServer.Stop>)
- [func \(w \*WebhookServer\) Updates\(\) \<\-chan api.Update](<#WebhookServer.Updates>)
<a name="BackoffStrategy"></a>
## type BackoffStrategy
BackoffStrategy returns the duration to wait before the next attempt after \`attempt\` consecutive failures \(1\-based\). Implementations must be safe to call from a single goroutine.
```go
type BackoffStrategy interface {
NextDelay(attempt int) time.Duration
}
```
<a name="ExponentialBackoff"></a>
## type ExponentialBackoff
ExponentialBackoff implements capped exponential back\-off with jitter. Defaults: Base=500ms, Max=30s, Factor=2.0, Jitter=0.2.
```go
type ExponentialBackoff struct {
Base time.Duration
Max time.Duration
Factor float64
Jitter float64 // 0..1; fraction of computed delay added/subtracted at random
}
```
<a name="DefaultBackoff"></a>
### func DefaultBackoff
```go
func DefaultBackoff() *ExponentialBackoff
```
DefaultBackoff returns an ExponentialBackoff with library defaults.
<a name="ExponentialBackoff.NextDelay"></a>
### func \(\*ExponentialBackoff\) NextDelay
```go
func (b *ExponentialBackoff) NextDelay(attempt int) time.Duration
```
NextDelay implements BackoffStrategy.
<a name="LongPoller"></a>
## type LongPoller
LongPoller pulls updates via Bot.GetUpdates in a loop, advancing the offset cursor after each batch. It applies BackoffStrategy on transient errors \(network failures, 5xx, 429\).
At\-least\-once semantics on shutdown: when ctx is cancelled or Stop is called mid\-batch, any updates already fetched but not yet dispatched are dropped without advancing the offset. On the next restart those updates will be re\-delivered by Telegram.
```go
type LongPoller struct {
Bot *client.Bot
Timeout int // seconds, default 30
Limit int // 1..100, default 100
AllowedTypes []api.UpdateType
Backoff BackoffStrategy
// contains filtered or unexported fields
}
```
<a name="NewLongPoller"></a>
### func NewLongPoller
```go
func NewLongPoller(b *client.Bot) *LongPoller
```
NewLongPoller constructs a LongPoller with sensible defaults.
<a name="LongPoller.Run"></a>
### func \(\*LongPoller\) Run
```go
func (p *LongPoller) Run(ctx context.Context) error
```
Run implements Updater. It blocks until ctx is cancelled, Stop is called, or a fatal error occurs \(e.g. unauthorized\). See LongPoller for at\-least\-once delivery semantics on shutdown.
<a name="LongPoller.Stop"></a>
### func \(\*LongPoller\) Stop
```go
func (p *LongPoller) Stop(ctx context.Context) error
```
Stop implements Updater.
<a name="LongPoller.Updates"></a>
### func \(\*LongPoller\) Updates
```go
func (p *LongPoller) Updates() <-chan api.Update
```
Updates implements Updater.
<a name="Updater"></a>
## type Updater
Updater is the abstraction over update sources. Implementations must:
- return a channel from Updates\(\) that receives every Update they read.
- close the channel after Run returns.
- honour ctx cancellation in Run.
```go
type Updater interface {
// Updates returns the channel updates flow into. Multiple readers
// is implementation-defined; users should treat it as single-reader.
Updates() <-chan api.Update
// Run blocks until ctx is cancelled or a fatal error occurs. It is
// the user's responsibility to call Run in a goroutine if needed.
Run(ctx context.Context) error
// Stop signals Run to exit and waits for the channel to drain.
// Implementations must be idempotent.
Stop(ctx context.Context) error
}
```
<a name="WebhookOption"></a>
## type WebhookOption
WebhookOption configures a WebhookServer at construction time.
```go
type WebhookOption func(*webhookOptions)
```
<a name="WithBufferSize"></a>
### func WithBufferSize
```go
func WithBufferSize(n int) WebhookOption
```
WithBufferSize sets the size of the updates channel buffer. Default is 64.
<a name="WebhookServer"></a>
## type WebhookServer
WebhookServer implements Updater by exposing an http.Handler that receives updates from Telegram. It can be mounted on the user's own HTTP server \(via ServeHTTP\) or run standalone \(via ListenAndServe\).
```go
type WebhookServer struct {
Bot *client.Bot
SecretToken string // verify X-Telegram-Bot-Api-Secret-Token; empty disables
// contains filtered or unexported fields
}
```
<a name="NewWebhookServer"></a>
### func NewWebhookServer
```go
func NewWebhookServer(b *client.Bot, opts ...WebhookOption) *WebhookServer
```
NewWebhookServer constructs a WebhookServer with default buffer size \(64\). Use WithBufferSize to override.
<a name="WebhookServer.ListenAndServe"></a>
### func \(\*WebhookServer\) ListenAndServe
```go
func (w *WebhookServer) ListenAndServe(ctx context.Context, addr string) error
```
ListenAndServe starts an HTTP server on addr and blocks until Stop is called \(which triggers Shutdown with the caller's context\) or the server returns an error other than http.ErrServerClosed. Callers must invoke Stop\(ctx\) to cleanly shut down the server; the ctx passed here is only used as the server's base context for incoming requests.
<a name="WebhookServer.Run"></a>
### func \(\*WebhookServer\) Run
```go
func (w *WebhookServer) Run(ctx context.Context) error
```
Run implements Updater. It blocks until Stop is called or ctx is cancelled. If the server has not been started via ListenAndServe, Run only watches for shutdown — the user is expected to mount ServeHTTP on their own router.
<a name="WebhookServer.ServeHTTP"></a>
### func \(\*WebhookServer\) ServeHTTP
```go
func (w *WebhookServer) ServeHTTP(rw http.ResponseWriter, r *http.Request)
```
ServeHTTP implements http.Handler. Telegram POSTs each update as JSON to this endpoint. Non\-POST requests get 405; bad bodies get 400; secret token mismatches get 401.
<a name="WebhookServer.Stop"></a>
### func \(\*WebhookServer\) Stop
```go
func (w *WebhookServer) Stop(ctx context.Context) error
```
Stop implements Updater.
<a name="WebhookServer.Updates"></a>
### func \(\*WebhookServer\) Updates
```go
func (w *WebhookServer) Updates() <-chan api.Update
```
Updates implements Updater.
Generated by [gomarkdoc](<https://github.com/princjef/gomarkdoc>)