mirror of
https://github.com/lukaszraczylo/go-telegram.git
synced 2026-06-06 22:49:32 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 70aaacabc7 | |||
| 9072e9eafb |
@@ -134,7 +134,7 @@ jobs:
|
||||
- name: Regenerate against pinned snapshot
|
||||
run: make regen-from-fixture
|
||||
- name: Assert clean diff
|
||||
run: git diff --exit-code internal/spec/api.json api/ docs/reference/
|
||||
run: git diff --exit-code internal/spec/api.json api/
|
||||
|
||||
audit:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
name: pages
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'docs/**'
|
||||
- '.github/workflows/pages.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
concurrency:
|
||||
group: pages
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/configure-pages@v5
|
||||
- uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: docs/
|
||||
- id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
+6
-10
@@ -19,12 +19,11 @@ blacklist:
|
||||
- "Merge remote-tracking branch"
|
||||
- "go mod tidy"
|
||||
|
||||
# NOTE: do not configure tag_prefixes here. The action's behaviour with
|
||||
# that option present is to FILTER tags down to those matching the
|
||||
# listed prefixes — meaning v0.x.y / v1.x.y tags get ignored and version
|
||||
# numbering restarts from zero. The bot-api-vX.Y tag is created by a
|
||||
# separate workflow step and does not need to participate in version
|
||||
# selection.
|
||||
# Strip the auto-generated bot-api-vX.Y tag prefix when scanning existing
|
||||
# tags — those are markers that point at library releases, not version
|
||||
# sources themselves.
|
||||
tag_prefixes:
|
||||
- "bot-api-"
|
||||
|
||||
wording:
|
||||
patch:
|
||||
@@ -40,8 +39,5 @@ wording:
|
||||
minor:
|
||||
- "feat"
|
||||
major:
|
||||
# Match only the canonical Conventional Commits trailer. The bare
|
||||
# word "breaking" is too greedy under semver-generator's fuzzy match —
|
||||
# it triggers on substrings like "breaking-value drift" inside a
|
||||
# commit body and wrongly produces a major bump.
|
||||
- "breaking"
|
||||
- "BREAKING CHANGE"
|
||||
|
||||
@@ -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 docs docs-check help
|
||||
.PHONY: test test-race lint vet integration regen snapshot regen-from-fixture test-update-golden clean clean-generated audit audit-drift help
|
||||
|
||||
GO ?= go
|
||||
|
||||
@@ -14,8 +14,6 @@ help:
|
||||
@echo " test-update-golden - refresh golden test fixtures (Plan 2)"
|
||||
@echo " audit - report any-typed/bool fallbacks in current IR"
|
||||
@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 - clean-generated + transient artefacts (binaries, coverage)"
|
||||
|
||||
@@ -46,14 +44,12 @@ regen: clean-generated
|
||||
$(GO) run ./cmd/audit -ir $(SCRAPE_OUTPUT)
|
||||
$(GO) run ./cmd/genapi -input $(SCRAPE_OUTPUT) -outdir api
|
||||
$(GO) test ./api/...
|
||||
$(MAKE) docs
|
||||
|
||||
regen-from-fixture: clean-generated
|
||||
$(GO) run ./cmd/scrape -input $(SCRAPE_INPUT) -output $(SCRAPE_OUTPUT)
|
||||
$(GO) run ./cmd/audit -ir $(SCRAPE_OUTPUT)
|
||||
$(GO) run ./cmd/genapi -input $(SCRAPE_OUTPUT) -outdir api
|
||||
$(GO) test ./api/...
|
||||
$(MAKE) docs
|
||||
|
||||
audit:
|
||||
$(GO) run ./cmd/audit -ir $(SCRAPE_OUTPUT)
|
||||
@@ -65,28 +61,6 @@ test-update-golden:
|
||||
$(GO) test -run TestEmit -update ./cmd/genapi/...
|
||||
$(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,
|
||||
# cmd/genapi, runtime helpers) is untouched. Run before regen to avoid
|
||||
# orphan files lingering when the IR shrinks (renamed/removed methods).
|
||||
|
||||
@@ -1,71 +1,61 @@
|
||||
<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>
|
||||
# go-telegram
|
||||
|
||||
<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>
|
||||
> A fully-generated, strongly-typed Go client for the Telegram Bot API — no `any`, no guessing.
|
||||
|
||||
<p align="center">
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
[](https://github.com/lukaszraczylo/go-telegram/actions/workflows/ci.yml)
|
||||
[](https://pkg.go.dev/github.com/lukaszraczylo/go-telegram)
|
||||
[](go.mod)
|
||||
[](LICENSE)
|
||||
|
||||
<p align="center">
|
||||
Bot API <strong>v10.0</strong> · 176 methods · 301 types · 1428 auto-generated tests
|
||||
</p>
|
||||
> Bot API **v10.0** · 176 methods · 301 types · 1428 auto-generated tests
|
||||
|
||||
<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 👋
|
||||
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.
|
||||
|
||||
```go
|
||||
bot := client.New(os.Getenv("TELEGRAM_BOT_TOKEN"))
|
||||
router := dispatch.New(bot)
|
||||
bot := client.New(os.Getenv("TELEGRAM_BOT_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: "Hi " + m.From.FirstName + "! 👋",
|
||||
Text: "Hello! Send me anything to echo.",
|
||||
})
|
||||
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
|
||||
})
|
||||
|
||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||
defer stop()
|
||||
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 go-telegram
|
||||
|
||||
## Why you'll like it
|
||||
| Feature | What it means for you |
|
||||
|---|---|
|
||||
| **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 |
|
||||
|
||||
- 🎯 **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
|
||||
## Quickstart
|
||||
|
||||
```bash
|
||||
go get github.com/lukaszraczylo/go-telegram
|
||||
```
|
||||
|
||||
## A complete echo bot
|
||||
|
||||
Long-poll, graceful shutdown, retries on Telegram's `429 retry_after`:
|
||||
Full echo bot — long-poll, graceful shutdown, retry on 429:
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -141,30 +131,7 @@ Run any example: `TELEGRAM_BOT_TOKEN=xxx go run ./examples/<name>`
|
||||
| | [`polls`](examples/polls) | `sendPoll` and answer tally |
|
||||
| | [`payments`](examples/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:
|
||||
|
||||
```go
|
||||
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/`](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
|
||||
## Concepts
|
||||
|
||||
<details>
|
||||
<summary>Bot client and pluggable transport</summary>
|
||||
@@ -317,18 +284,25 @@ r.OnCommand("/cmd", named.Handler())
|
||||
|
||||
</details>
|
||||
|
||||
## Keeping up with Telegram
|
||||
## Codegen pipeline
|
||||
|
||||
When Telegram ships a new Bot API version, regenerating the whole library is one command:
|
||||
The full API surface in `api/*.gen.go` is generated from a committed HTML snapshot of `core.telegram.org/bots/api`:
|
||||
|
||||
```bash
|
||||
make snapshot # grab the latest HTML from core.telegram.org
|
||||
make regen # scrape → audit → emit Go → run tests → regenerate docs
|
||||
make snapshot # fetch and commit latest HTML from core.telegram.org
|
||||
make regen # scrape → audit → emit Go code → run generated tests
|
||||
go test -race ./...
|
||||
```
|
||||
|
||||
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.
|
||||
`make regen` is self-verifying. The audit tool (`cmd/audit`) checks:
|
||||
|
||||
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.
|
||||
- `any`-typed fields or returns that escaped the union machinery
|
||||
- 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
|
||||
|
||||
|
||||
+33
-463
@@ -4,102 +4,16 @@
|
||||
|
||||
package api
|
||||
|
||||
type BackgroundFillFreeformGradientType string
|
||||
// ParseMode controls how Telegram interprets formatting in message text.
|
||||
type ParseMode string
|
||||
|
||||
const (
|
||||
BackgroundFillFreeformGradientTypeFreeformGradient BackgroundFillFreeformGradientType = "freeform_gradient"
|
||||
)
|
||||
|
||||
type BackgroundFillGradientType string
|
||||
|
||||
const (
|
||||
BackgroundFillGradientTypeGradient BackgroundFillGradientType = "gradient"
|
||||
)
|
||||
|
||||
type BackgroundFillSolidType string
|
||||
|
||||
const (
|
||||
BackgroundFillSolidTypeSolid BackgroundFillSolidType = "solid"
|
||||
)
|
||||
|
||||
type BackgroundTypeChatThemeType string
|
||||
|
||||
const (
|
||||
BackgroundTypeChatThemeTypeChatTheme BackgroundTypeChatThemeType = "chat_theme"
|
||||
)
|
||||
|
||||
type BackgroundTypeFillType string
|
||||
|
||||
const (
|
||||
BackgroundTypeFillTypeFill BackgroundTypeFillType = "fill"
|
||||
)
|
||||
|
||||
type BackgroundTypePatternType string
|
||||
|
||||
const (
|
||||
BackgroundTypePatternTypePattern BackgroundTypePatternType = "pattern"
|
||||
)
|
||||
|
||||
type BackgroundTypeWallpaperType string
|
||||
|
||||
const (
|
||||
BackgroundTypeWallpaperTypeWallpaper BackgroundTypeWallpaperType = "wallpaper"
|
||||
)
|
||||
|
||||
type ChatBoostSourceGiftCodeSource string
|
||||
|
||||
const (
|
||||
ChatBoostSourceGiftCodeSourceGiftCode ChatBoostSourceGiftCodeSource = "gift_code"
|
||||
)
|
||||
|
||||
type ChatBoostSourceGiveawaySource string
|
||||
|
||||
const (
|
||||
ChatBoostSourceGiveawaySourceGiveaway ChatBoostSourceGiveawaySource = "giveaway"
|
||||
)
|
||||
|
||||
type ChatBoostSourcePremiumSource string
|
||||
|
||||
const (
|
||||
ChatBoostSourcePremiumSourcePremium ChatBoostSourcePremiumSource = "premium"
|
||||
)
|
||||
|
||||
type ChatMemberAdministratorStatus string
|
||||
|
||||
const (
|
||||
ChatMemberAdministratorStatusAdministrator ChatMemberAdministratorStatus = "administrator"
|
||||
)
|
||||
|
||||
type ChatMemberBannedStatus string
|
||||
|
||||
const (
|
||||
ChatMemberBannedStatusKicked ChatMemberBannedStatus = "kicked"
|
||||
)
|
||||
|
||||
type ChatMemberLeftStatus string
|
||||
|
||||
const (
|
||||
ChatMemberLeftStatusLeft ChatMemberLeftStatus = "left"
|
||||
)
|
||||
|
||||
type ChatMemberMemberStatus string
|
||||
|
||||
const (
|
||||
ChatMemberMemberStatusMember ChatMemberMemberStatus = "member"
|
||||
)
|
||||
|
||||
type ChatMemberOwnerStatus string
|
||||
|
||||
const (
|
||||
ChatMemberOwnerStatusCreator ChatMemberOwnerStatus = "creator"
|
||||
)
|
||||
|
||||
type ChatMemberRestrictedStatus string
|
||||
|
||||
const (
|
||||
ChatMemberRestrictedStatusRestricted ChatMemberRestrictedStatus = "restricted"
|
||||
ParseModeMarkdown ParseMode = "Markdown" // legacy
|
||||
ParseModeMarkdownV2 ParseMode = "MarkdownV2"
|
||||
ParseModeHTML ParseMode = "HTML"
|
||||
)
|
||||
|
||||
// ChatType is the type of a Telegram chat.
|
||||
type ChatType string
|
||||
|
||||
const (
|
||||
@@ -109,382 +23,38 @@ const (
|
||||
ChatTypeChannel ChatType = "channel"
|
||||
)
|
||||
|
||||
type EncryptedPassportElementType string
|
||||
// UpdateType identifies an Update payload variant. Used by allowed_updates
|
||||
// in getUpdates / setWebhook.
|
||||
type UpdateType string
|
||||
|
||||
const (
|
||||
EncryptedPassportElementTypePersonalDetails EncryptedPassportElementType = "personal_details"
|
||||
EncryptedPassportElementTypePassport EncryptedPassportElementType = "passport"
|
||||
EncryptedPassportElementTypeDriverLicense EncryptedPassportElementType = "driver_license"
|
||||
EncryptedPassportElementTypeIdentityCard EncryptedPassportElementType = "identity_card"
|
||||
EncryptedPassportElementTypeInternalPassport EncryptedPassportElementType = "internal_passport"
|
||||
EncryptedPassportElementTypeAddress EncryptedPassportElementType = "address"
|
||||
EncryptedPassportElementTypeUtilityBill EncryptedPassportElementType = "utility_bill"
|
||||
EncryptedPassportElementTypeBankStatement EncryptedPassportElementType = "bank_statement"
|
||||
EncryptedPassportElementTypeRentalAgreement EncryptedPassportElementType = "rental_agreement"
|
||||
EncryptedPassportElementTypePassportRegistration EncryptedPassportElementType = "passport_registration"
|
||||
EncryptedPassportElementTypeTemporaryRegistration EncryptedPassportElementType = "temporary_registration"
|
||||
EncryptedPassportElementTypePhoneNumber EncryptedPassportElementType = "phone_number"
|
||||
EncryptedPassportElementTypeEmail EncryptedPassportElementType = "email"
|
||||
)
|
||||
|
||||
type InlineQueryChatType string
|
||||
|
||||
const (
|
||||
InlineQueryChatTypeSender InlineQueryChatType = "sender"
|
||||
InlineQueryChatTypePrivate InlineQueryChatType = "private"
|
||||
InlineQueryChatTypeGroup InlineQueryChatType = "group"
|
||||
InlineQueryChatTypeSupergroup InlineQueryChatType = "supergroup"
|
||||
InlineQueryChatTypeChannel InlineQueryChatType = "channel"
|
||||
)
|
||||
|
||||
type InlineQueryResultDocumentMimeType string
|
||||
|
||||
const (
|
||||
InlineQueryResultDocumentMimeTypeApplicationOfPdf InlineQueryResultDocumentMimeType = "application/pdf"
|
||||
InlineQueryResultDocumentMimeTypeApplicationOfZip InlineQueryResultDocumentMimeType = "application/zip"
|
||||
)
|
||||
|
||||
type InlineQueryResultGifThumbnailMimeType string
|
||||
|
||||
const (
|
||||
InlineQueryResultGifThumbnailMimeTypeImageOfJpeg InlineQueryResultGifThumbnailMimeType = "image/jpeg"
|
||||
InlineQueryResultGifThumbnailMimeTypeImageOfGif InlineQueryResultGifThumbnailMimeType = "image/gif"
|
||||
InlineQueryResultGifThumbnailMimeTypeVideoOfMp4 InlineQueryResultGifThumbnailMimeType = "video/mp4"
|
||||
)
|
||||
|
||||
type InputStickerFormat string
|
||||
|
||||
const (
|
||||
InputStickerFormatStatic InputStickerFormat = "static"
|
||||
InputStickerFormatAnimated InputStickerFormat = "animated"
|
||||
InputStickerFormatVideo InputStickerFormat = "video"
|
||||
)
|
||||
|
||||
type KeyboardButtonStyle string
|
||||
|
||||
const (
|
||||
KeyboardButtonStyleDanger KeyboardButtonStyle = "danger"
|
||||
KeyboardButtonStyleSuccess KeyboardButtonStyle = "success"
|
||||
KeyboardButtonStylePrimary KeyboardButtonStyle = "primary"
|
||||
)
|
||||
|
||||
type MaskPositionPoint string
|
||||
|
||||
const (
|
||||
MaskPositionPointForehead MaskPositionPoint = "forehead"
|
||||
MaskPositionPointEyes MaskPositionPoint = "eyes"
|
||||
MaskPositionPointMouth MaskPositionPoint = "mouth"
|
||||
MaskPositionPointChin MaskPositionPoint = "chin"
|
||||
UpdateMessage UpdateType = "message"
|
||||
UpdateEditedMessage UpdateType = "edited_message"
|
||||
UpdateChannelPost UpdateType = "channel_post"
|
||||
UpdateEditedChannelPost UpdateType = "edited_channel_post"
|
||||
UpdateCallbackQuery UpdateType = "callback_query"
|
||||
UpdateInlineQuery UpdateType = "inline_query"
|
||||
)
|
||||
|
||||
// MessageEntityType is the kind of an entity (mention, hashtag, command, ...).
|
||||
type MessageEntityType string
|
||||
|
||||
const (
|
||||
MessageEntityTypeMention MessageEntityType = "mention"
|
||||
MessageEntityTypeHashtag MessageEntityType = "hashtag"
|
||||
MessageEntityTypeCashtag MessageEntityType = "cashtag"
|
||||
MessageEntityTypeBotCommand MessageEntityType = "bot_command"
|
||||
MessageEntityTypeURL MessageEntityType = "url"
|
||||
MessageEntityTypeEmail MessageEntityType = "email"
|
||||
MessageEntityTypePhoneNumber MessageEntityType = "phone_number"
|
||||
MessageEntityTypeBold MessageEntityType = "bold"
|
||||
MessageEntityTypeItalic MessageEntityType = "italic"
|
||||
MessageEntityTypeUnderline MessageEntityType = "underline"
|
||||
MessageEntityTypeStrikethrough MessageEntityType = "strikethrough"
|
||||
MessageEntityTypeSpoiler MessageEntityType = "spoiler"
|
||||
MessageEntityTypeBlockquote MessageEntityType = "blockquote"
|
||||
MessageEntityTypeExpandableBlockquote MessageEntityType = "expandable_blockquote"
|
||||
MessageEntityTypeCode MessageEntityType = "code"
|
||||
MessageEntityTypePre MessageEntityType = "pre"
|
||||
MessageEntityTypeTextLink MessageEntityType = "text_link"
|
||||
MessageEntityTypeTextMention MessageEntityType = "text_mention"
|
||||
MessageEntityTypeCustomEmoji MessageEntityType = "custom_emoji"
|
||||
MessageEntityTypeDateTime MessageEntityType = "date_time"
|
||||
)
|
||||
|
||||
type MessageOriginChannelType string
|
||||
|
||||
const (
|
||||
MessageOriginChannelTypeChannel MessageOriginChannelType = "channel"
|
||||
)
|
||||
|
||||
type MessageOriginChatType string
|
||||
|
||||
const (
|
||||
MessageOriginChatTypeChat MessageOriginChatType = "chat"
|
||||
)
|
||||
|
||||
type MessageOriginHiddenUserType string
|
||||
|
||||
const (
|
||||
MessageOriginHiddenUserTypeHiddenUser MessageOriginHiddenUserType = "hidden_user"
|
||||
)
|
||||
|
||||
type MessageOriginUserType string
|
||||
|
||||
const (
|
||||
MessageOriginUserTypeUser MessageOriginUserType = "user"
|
||||
)
|
||||
|
||||
type OwnedGiftRegularType string
|
||||
|
||||
const (
|
||||
OwnedGiftRegularTypeRegular OwnedGiftRegularType = "regular"
|
||||
)
|
||||
|
||||
type OwnedGiftUniqueType string
|
||||
|
||||
const (
|
||||
OwnedGiftUniqueTypeUnique OwnedGiftUniqueType = "unique"
|
||||
)
|
||||
|
||||
type PaidMediaLivePhotoType string
|
||||
|
||||
const (
|
||||
PaidMediaLivePhotoTypeLivePhoto PaidMediaLivePhotoType = "live_photo"
|
||||
)
|
||||
|
||||
type PaidMediaPhotoType string
|
||||
|
||||
const (
|
||||
PaidMediaPhotoTypePhoto PaidMediaPhotoType = "photo"
|
||||
)
|
||||
|
||||
type PaidMediaPreviewType string
|
||||
|
||||
const (
|
||||
PaidMediaPreviewTypePreview PaidMediaPreviewType = "preview"
|
||||
)
|
||||
|
||||
type PaidMediaVideoType string
|
||||
|
||||
const (
|
||||
PaidMediaVideoTypeVideo PaidMediaVideoType = "video"
|
||||
)
|
||||
|
||||
type ParseMode string
|
||||
|
||||
const (
|
||||
ParseModeMarkdown ParseMode = "Markdown"
|
||||
ParseModeMarkdownV2 ParseMode = "MarkdownV2"
|
||||
ParseModeHTML ParseMode = "HTML"
|
||||
)
|
||||
|
||||
type PassportElementErrorDataFieldType string
|
||||
|
||||
const (
|
||||
PassportElementErrorDataFieldTypePersonalDetails PassportElementErrorDataFieldType = "personal_details"
|
||||
PassportElementErrorDataFieldTypePassport PassportElementErrorDataFieldType = "passport"
|
||||
PassportElementErrorDataFieldTypeDriverLicense PassportElementErrorDataFieldType = "driver_license"
|
||||
PassportElementErrorDataFieldTypeIdentityCard PassportElementErrorDataFieldType = "identity_card"
|
||||
PassportElementErrorDataFieldTypeInternalPassport PassportElementErrorDataFieldType = "internal_passport"
|
||||
PassportElementErrorDataFieldTypeAddress PassportElementErrorDataFieldType = "address"
|
||||
)
|
||||
|
||||
type PassportElementErrorFileType string
|
||||
|
||||
const (
|
||||
PassportElementErrorFileTypeUtilityBill PassportElementErrorFileType = "utility_bill"
|
||||
PassportElementErrorFileTypeBankStatement PassportElementErrorFileType = "bank_statement"
|
||||
PassportElementErrorFileTypeRentalAgreement PassportElementErrorFileType = "rental_agreement"
|
||||
PassportElementErrorFileTypePassportRegistration PassportElementErrorFileType = "passport_registration"
|
||||
PassportElementErrorFileTypeTemporaryRegistration PassportElementErrorFileType = "temporary_registration"
|
||||
)
|
||||
|
||||
type PassportElementErrorReverseSideType string
|
||||
|
||||
const (
|
||||
PassportElementErrorReverseSideTypeDriverLicense PassportElementErrorReverseSideType = "driver_license"
|
||||
PassportElementErrorReverseSideTypeIdentityCard PassportElementErrorReverseSideType = "identity_card"
|
||||
)
|
||||
|
||||
type PassportElementErrorSelfieType string
|
||||
|
||||
const (
|
||||
PassportElementErrorSelfieTypePassport PassportElementErrorSelfieType = "passport"
|
||||
PassportElementErrorSelfieTypeDriverLicense PassportElementErrorSelfieType = "driver_license"
|
||||
PassportElementErrorSelfieTypeIdentityCard PassportElementErrorSelfieType = "identity_card"
|
||||
PassportElementErrorSelfieTypeInternalPassport PassportElementErrorSelfieType = "internal_passport"
|
||||
)
|
||||
|
||||
type PassportElementErrorTranslationFileType string
|
||||
|
||||
const (
|
||||
PassportElementErrorTranslationFileTypePassport PassportElementErrorTranslationFileType = "passport"
|
||||
PassportElementErrorTranslationFileTypeDriverLicense PassportElementErrorTranslationFileType = "driver_license"
|
||||
PassportElementErrorTranslationFileTypeIdentityCard PassportElementErrorTranslationFileType = "identity_card"
|
||||
PassportElementErrorTranslationFileTypeInternalPassport PassportElementErrorTranslationFileType = "internal_passport"
|
||||
PassportElementErrorTranslationFileTypeUtilityBill PassportElementErrorTranslationFileType = "utility_bill"
|
||||
PassportElementErrorTranslationFileTypeBankStatement PassportElementErrorTranslationFileType = "bank_statement"
|
||||
PassportElementErrorTranslationFileTypeRentalAgreement PassportElementErrorTranslationFileType = "rental_agreement"
|
||||
PassportElementErrorTranslationFileTypePassportRegistration PassportElementErrorTranslationFileType = "passport_registration"
|
||||
PassportElementErrorTranslationFileTypeTemporaryRegistration PassportElementErrorTranslationFileType = "temporary_registration"
|
||||
)
|
||||
|
||||
type PollType string
|
||||
|
||||
const (
|
||||
PollTypeRegular PollType = "regular"
|
||||
PollTypeQuiz PollType = "quiz"
|
||||
)
|
||||
|
||||
type ReactionTypeCustomEmojiType string
|
||||
|
||||
const (
|
||||
ReactionTypeCustomEmojiTypeCustomEmoji ReactionTypeCustomEmojiType = "custom_emoji"
|
||||
)
|
||||
|
||||
type ReactionTypeEmojiType string
|
||||
|
||||
const (
|
||||
ReactionTypeEmojiTypeEmoji ReactionTypeEmojiType = "emoji"
|
||||
)
|
||||
|
||||
type ReactionTypePaidType string
|
||||
|
||||
const (
|
||||
ReactionTypePaidTypePaid ReactionTypePaidType = "paid"
|
||||
)
|
||||
|
||||
type RefundedPaymentCurrency string
|
||||
|
||||
const (
|
||||
RefundedPaymentCurrencyXTR RefundedPaymentCurrency = "XTR"
|
||||
)
|
||||
|
||||
type RevenueWithdrawalStateFailedType string
|
||||
|
||||
const (
|
||||
RevenueWithdrawalStateFailedTypeFailed RevenueWithdrawalStateFailedType = "failed"
|
||||
)
|
||||
|
||||
type RevenueWithdrawalStatePendingType string
|
||||
|
||||
const (
|
||||
RevenueWithdrawalStatePendingTypePending RevenueWithdrawalStatePendingType = "pending"
|
||||
)
|
||||
|
||||
type RevenueWithdrawalStateSucceededType string
|
||||
|
||||
const (
|
||||
RevenueWithdrawalStateSucceededTypeSucceeded RevenueWithdrawalStateSucceededType = "succeeded"
|
||||
)
|
||||
|
||||
type StickerType string
|
||||
|
||||
const (
|
||||
StickerTypeRegular StickerType = "regular"
|
||||
StickerTypeMask StickerType = "mask"
|
||||
StickerTypeCustomEmoji StickerType = "custom_emoji"
|
||||
)
|
||||
|
||||
type StoryAreaTypeLinkType string
|
||||
|
||||
const (
|
||||
StoryAreaTypeLinkTypeLink StoryAreaTypeLinkType = "link"
|
||||
)
|
||||
|
||||
type StoryAreaTypeLocationType string
|
||||
|
||||
const (
|
||||
StoryAreaTypeLocationTypeLocation StoryAreaTypeLocationType = "location"
|
||||
)
|
||||
|
||||
type StoryAreaTypeSuggestedReactionType string
|
||||
|
||||
const (
|
||||
StoryAreaTypeSuggestedReactionTypeSuggestedReaction StoryAreaTypeSuggestedReactionType = "suggested_reaction"
|
||||
)
|
||||
|
||||
type StoryAreaTypeUniqueGiftType string
|
||||
|
||||
const (
|
||||
StoryAreaTypeUniqueGiftTypeUniqueGift StoryAreaTypeUniqueGiftType = "unique_gift"
|
||||
)
|
||||
|
||||
type StoryAreaTypeWeatherType string
|
||||
|
||||
const (
|
||||
StoryAreaTypeWeatherTypeWeather StoryAreaTypeWeatherType = "weather"
|
||||
)
|
||||
|
||||
type SuggestedPostInfoState string
|
||||
|
||||
const (
|
||||
SuggestedPostInfoStatePending SuggestedPostInfoState = "pending"
|
||||
SuggestedPostInfoStateApproved SuggestedPostInfoState = "approved"
|
||||
SuggestedPostInfoStateDeclined SuggestedPostInfoState = "declined"
|
||||
)
|
||||
|
||||
type SuggestedPostPaidCurrency string
|
||||
|
||||
const (
|
||||
SuggestedPostPaidCurrencyXTR SuggestedPostPaidCurrency = "XTR"
|
||||
SuggestedPostPaidCurrencyTON SuggestedPostPaidCurrency = "TON"
|
||||
)
|
||||
|
||||
type SuggestedPostRefundedReason string
|
||||
|
||||
const (
|
||||
SuggestedPostRefundedReasonPostDeleted SuggestedPostRefundedReason = "post_deleted"
|
||||
SuggestedPostRefundedReasonPaymentRefunded SuggestedPostRefundedReason = "payment_refunded"
|
||||
)
|
||||
|
||||
type TransactionPartnerAffiliateProgramType string
|
||||
|
||||
const (
|
||||
TransactionPartnerAffiliateProgramTypeAffiliateProgram TransactionPartnerAffiliateProgramType = "affiliate_program"
|
||||
)
|
||||
|
||||
type TransactionPartnerFragmentType string
|
||||
|
||||
const (
|
||||
TransactionPartnerFragmentTypeFragment TransactionPartnerFragmentType = "fragment"
|
||||
)
|
||||
|
||||
type TransactionPartnerOtherType string
|
||||
|
||||
const (
|
||||
TransactionPartnerOtherTypeOther TransactionPartnerOtherType = "other"
|
||||
)
|
||||
|
||||
type TransactionPartnerTelegramAdsType string
|
||||
|
||||
const (
|
||||
TransactionPartnerTelegramAdsTypeTelegramAds TransactionPartnerTelegramAdsType = "telegram_ads"
|
||||
)
|
||||
|
||||
type TransactionPartnerTelegramApiType string
|
||||
|
||||
const (
|
||||
TransactionPartnerTelegramApiTypeTelegramApi TransactionPartnerTelegramApiType = "telegram_api"
|
||||
)
|
||||
|
||||
type TransactionPartnerUserTransactionType string
|
||||
|
||||
const (
|
||||
TransactionPartnerUserTransactionTypeInvoicePayment TransactionPartnerUserTransactionType = "invoice_payment"
|
||||
TransactionPartnerUserTransactionTypePaidMediaPayment TransactionPartnerUserTransactionType = "paid_media_payment"
|
||||
TransactionPartnerUserTransactionTypeGiftPurchase TransactionPartnerUserTransactionType = "gift_purchase"
|
||||
TransactionPartnerUserTransactionTypePremiumPurchase TransactionPartnerUserTransactionType = "premium_purchase"
|
||||
TransactionPartnerUserTransactionTypeBusinessAccountTransfer TransactionPartnerUserTransactionType = "business_account_transfer"
|
||||
)
|
||||
|
||||
type UniqueGiftInfoOrigin string
|
||||
|
||||
const (
|
||||
UniqueGiftInfoOriginUpgrade UniqueGiftInfoOrigin = "upgrade"
|
||||
UniqueGiftInfoOriginTransfer UniqueGiftInfoOrigin = "transfer"
|
||||
UniqueGiftInfoOriginResale UniqueGiftInfoOrigin = "resale"
|
||||
UniqueGiftInfoOriginGiftedUpgrade UniqueGiftInfoOrigin = "gifted_upgrade"
|
||||
UniqueGiftInfoOriginOffer UniqueGiftInfoOrigin = "offer"
|
||||
)
|
||||
|
||||
type UniqueGiftModelRarity string
|
||||
|
||||
const (
|
||||
UniqueGiftModelRarityUncommon UniqueGiftModelRarity = "uncommon"
|
||||
UniqueGiftModelRarityRare UniqueGiftModelRarity = "rare"
|
||||
UniqueGiftModelRarityEpic UniqueGiftModelRarity = "epic"
|
||||
UniqueGiftModelRarityLegendary UniqueGiftModelRarity = "legendary"
|
||||
EntityMention MessageEntityType = "mention"
|
||||
EntityHashtag MessageEntityType = "hashtag"
|
||||
EntityCashtag MessageEntityType = "cashtag"
|
||||
EntityBotCommand MessageEntityType = "bot_command"
|
||||
EntityURL MessageEntityType = "url"
|
||||
EntityEmail MessageEntityType = "email"
|
||||
EntityPhoneNumber MessageEntityType = "phone_number"
|
||||
EntityBold MessageEntityType = "bold"
|
||||
EntityItalic MessageEntityType = "italic"
|
||||
EntityUnderline MessageEntityType = "underline"
|
||||
EntityStrike MessageEntityType = "strikethrough"
|
||||
EntitySpoiler MessageEntityType = "spoiler"
|
||||
EntityCode MessageEntityType = "code"
|
||||
EntityPre MessageEntityType = "pre"
|
||||
EntityTextLink MessageEntityType = "text_link"
|
||||
EntityTextMention MessageEntityType = "text_mention"
|
||||
EntityCustomEmoji MessageEntityType = "custom_emoji"
|
||||
)
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
package api
|
||||
|
||||
// UpdateType identifies an Update payload variant. Used by allowed_updates
|
||||
// in getUpdates / setWebhook. The Telegram docs do not enumerate these
|
||||
// values inline (they are derived from the optional fields of Update),
|
||||
// so the codegen pipeline cannot synthesise this enum and it lives here
|
||||
// as a hand-curated companion to the generated enums.gen.go.
|
||||
type UpdateType string
|
||||
|
||||
const (
|
||||
UpdateMessage UpdateType = "message"
|
||||
UpdateEditedMessage UpdateType = "edited_message"
|
||||
UpdateChannelPost UpdateType = "channel_post"
|
||||
UpdateEditedChannelPost UpdateType = "edited_channel_post"
|
||||
UpdateBusinessConnection UpdateType = "business_connection"
|
||||
UpdateBusinessMessage UpdateType = "business_message"
|
||||
UpdateEditedBusinessMessage UpdateType = "edited_business_message"
|
||||
UpdateDeletedBusinessMessages UpdateType = "deleted_business_messages"
|
||||
UpdateMessageReaction UpdateType = "message_reaction"
|
||||
UpdateMessageReactionCount UpdateType = "message_reaction_count"
|
||||
UpdateInlineQuery UpdateType = "inline_query"
|
||||
UpdateChosenInlineResult UpdateType = "chosen_inline_result"
|
||||
UpdateCallbackQuery UpdateType = "callback_query"
|
||||
UpdateShippingQuery UpdateType = "shipping_query"
|
||||
UpdatePreCheckoutQuery UpdateType = "pre_checkout_query"
|
||||
UpdatePurchasedPaidMedia UpdateType = "purchased_paid_media"
|
||||
UpdatePoll UpdateType = "poll"
|
||||
UpdatePollAnswer UpdateType = "poll_answer"
|
||||
UpdateMyChatMember UpdateType = "my_chat_member"
|
||||
UpdateChatMember UpdateType = "chat_member"
|
||||
UpdateChatJoinRequest UpdateType = "chat_join_request"
|
||||
UpdateChatBoost UpdateType = "chat_boost"
|
||||
UpdateRemovedChatBoost UpdateType = "removed_chat_boost"
|
||||
)
|
||||
+32
-32
@@ -198,7 +198,7 @@ type SendMessageParams struct {
|
||||
// Text of the message to be sent, 1-4096 characters after entities parsing
|
||||
Text string `json:"text"`
|
||||
// Mode for parsing entities in the message text. See formatting options for more details.
|
||||
ParseMode ParseMode `json:"parse_mode,omitempty"`
|
||||
ParseMode string `json:"parse_mode,omitempty"`
|
||||
// A JSON-serialized list of special entities that appear in message text, which can be specified instead of parse_mode
|
||||
Entities []MessageEntity `json:"entities,omitempty"`
|
||||
// Link preview generation options for the message
|
||||
@@ -305,7 +305,7 @@ type CopyMessageParams struct {
|
||||
// New caption for media, 0-1024 characters after entities parsing. If not specified, the original caption is kept
|
||||
Caption string `json:"caption,omitempty"`
|
||||
// Mode for parsing entities in the new caption. See formatting options for more details.
|
||||
ParseMode ParseMode `json:"parse_mode,omitempty"`
|
||||
ParseMode string `json:"parse_mode,omitempty"`
|
||||
// A JSON-serialized list of special entities that appear in the new caption, which can be specified instead of parse_mode
|
||||
CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
|
||||
// Pass True, if the caption must be shown above the message media. Ignored if a new caption isn't specified.
|
||||
@@ -379,7 +379,7 @@ type SendPhotoParams struct {
|
||||
// Photo caption (may also be used when resending photos by file_id), 0-1024 characters after entities parsing
|
||||
Caption string `json:"caption,omitempty"`
|
||||
// Mode for parsing entities in the photo caption. See formatting options for more details.
|
||||
ParseMode ParseMode `json:"parse_mode,omitempty"`
|
||||
ParseMode string `json:"parse_mode,omitempty"`
|
||||
// A JSON-serialized list of special entities that appear in the caption, which can be specified instead of parse_mode
|
||||
CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
|
||||
// Pass True, if the caption must be shown above the message media
|
||||
@@ -427,7 +427,7 @@ func (p *SendPhotoParams) MultipartFields() map[string]string {
|
||||
out["caption"] = p.Caption
|
||||
}
|
||||
if p.ParseMode != "" {
|
||||
out["parse_mode"] = string(p.ParseMode)
|
||||
out["parse_mode"] = p.ParseMode
|
||||
}
|
||||
if p.CaptionEntities != nil {
|
||||
if b, _ := json.Marshal(p.CaptionEntities); len(b) > 0 {
|
||||
@@ -509,7 +509,7 @@ type SendLivePhotoParams struct {
|
||||
// Video caption (may also be used when resending videos by file_id), 0-1024 characters after entities parsing
|
||||
Caption string `json:"caption,omitempty"`
|
||||
// Mode for parsing entities in the video caption. See formatting options for more details.
|
||||
ParseMode ParseMode `json:"parse_mode,omitempty"`
|
||||
ParseMode string `json:"parse_mode,omitempty"`
|
||||
// A JSON-serialized list of special entities that appear in the caption, which can be specified instead of parse_mode
|
||||
CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
|
||||
// Pass True, if the caption must be shown above the message media
|
||||
@@ -560,7 +560,7 @@ func (p *SendLivePhotoParams) MultipartFields() map[string]string {
|
||||
out["caption"] = p.Caption
|
||||
}
|
||||
if p.ParseMode != "" {
|
||||
out["parse_mode"] = string(p.ParseMode)
|
||||
out["parse_mode"] = p.ParseMode
|
||||
}
|
||||
if p.CaptionEntities != nil {
|
||||
if b, _ := json.Marshal(p.CaptionEntities); len(b) > 0 {
|
||||
@@ -648,7 +648,7 @@ type SendAudioParams struct {
|
||||
// Audio caption, 0-1024 characters after entities parsing
|
||||
Caption string `json:"caption,omitempty"`
|
||||
// Mode for parsing entities in the audio caption. See formatting options for more details.
|
||||
ParseMode ParseMode `json:"parse_mode,omitempty"`
|
||||
ParseMode string `json:"parse_mode,omitempty"`
|
||||
// A JSON-serialized list of special entities that appear in the caption, which can be specified instead of parse_mode
|
||||
CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
|
||||
// Duration of the audio in seconds
|
||||
@@ -703,7 +703,7 @@ func (p *SendAudioParams) MultipartFields() map[string]string {
|
||||
out["caption"] = p.Caption
|
||||
}
|
||||
if p.ParseMode != "" {
|
||||
out["parse_mode"] = string(p.ParseMode)
|
||||
out["parse_mode"] = p.ParseMode
|
||||
}
|
||||
if p.CaptionEntities != nil {
|
||||
if b, _ := json.Marshal(p.CaptionEntities); len(b) > 0 {
|
||||
@@ -796,7 +796,7 @@ type SendDocumentParams struct {
|
||||
// Document caption (may also be used when resending documents by file_id), 0-1024 characters after entities parsing
|
||||
Caption string `json:"caption,omitempty"`
|
||||
// Mode for parsing entities in the document caption. See formatting options for more details.
|
||||
ParseMode ParseMode `json:"parse_mode,omitempty"`
|
||||
ParseMode string `json:"parse_mode,omitempty"`
|
||||
// A JSON-serialized list of special entities that appear in the caption, which can be specified instead of parse_mode
|
||||
CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
|
||||
// Disables automatic server-side content type detection for files uploaded using multipart/form-data
|
||||
@@ -845,7 +845,7 @@ func (p *SendDocumentParams) MultipartFields() map[string]string {
|
||||
out["caption"] = p.Caption
|
||||
}
|
||||
if p.ParseMode != "" {
|
||||
out["parse_mode"] = string(p.ParseMode)
|
||||
out["parse_mode"] = p.ParseMode
|
||||
}
|
||||
if p.CaptionEntities != nil {
|
||||
if b, _ := json.Marshal(p.CaptionEntities); len(b) > 0 {
|
||||
@@ -941,7 +941,7 @@ type SendVideoParams struct {
|
||||
// Video caption (may also be used when resending videos by file_id), 0-1024 characters after entities parsing
|
||||
Caption string `json:"caption,omitempty"`
|
||||
// Mode for parsing entities in the video caption. See formatting options for more details.
|
||||
ParseMode ParseMode `json:"parse_mode,omitempty"`
|
||||
ParseMode string `json:"parse_mode,omitempty"`
|
||||
// A JSON-serialized list of special entities that appear in the caption, which can be specified instead of parse_mode
|
||||
CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
|
||||
// Pass True, if the caption must be shown above the message media
|
||||
@@ -1009,7 +1009,7 @@ func (p *SendVideoParams) MultipartFields() map[string]string {
|
||||
out["caption"] = p.Caption
|
||||
}
|
||||
if p.ParseMode != "" {
|
||||
out["parse_mode"] = string(p.ParseMode)
|
||||
out["parse_mode"] = p.ParseMode
|
||||
}
|
||||
if p.CaptionEntities != nil {
|
||||
if b, _ := json.Marshal(p.CaptionEntities); len(b) > 0 {
|
||||
@@ -1114,7 +1114,7 @@ type SendAnimationParams struct {
|
||||
// Animation caption (may also be used when resending animation by file_id), 0-1024 characters after entities parsing
|
||||
Caption string `json:"caption,omitempty"`
|
||||
// Mode for parsing entities in the animation caption. See formatting options for more details.
|
||||
ParseMode ParseMode `json:"parse_mode,omitempty"`
|
||||
ParseMode string `json:"parse_mode,omitempty"`
|
||||
// A JSON-serialized list of special entities that appear in the caption, which can be specified instead of parse_mode
|
||||
CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
|
||||
// Pass True, if the caption must be shown above the message media
|
||||
@@ -1174,7 +1174,7 @@ func (p *SendAnimationParams) MultipartFields() map[string]string {
|
||||
out["caption"] = p.Caption
|
||||
}
|
||||
if p.ParseMode != "" {
|
||||
out["parse_mode"] = string(p.ParseMode)
|
||||
out["parse_mode"] = p.ParseMode
|
||||
}
|
||||
if p.CaptionEntities != nil {
|
||||
if b, _ := json.Marshal(p.CaptionEntities); len(b) > 0 {
|
||||
@@ -1261,7 +1261,7 @@ type SendVoiceParams struct {
|
||||
// Voice message caption, 0-1024 characters after entities parsing
|
||||
Caption string `json:"caption,omitempty"`
|
||||
// Mode for parsing entities in the voice message caption. See formatting options for more details.
|
||||
ParseMode ParseMode `json:"parse_mode,omitempty"`
|
||||
ParseMode string `json:"parse_mode,omitempty"`
|
||||
// A JSON-serialized list of special entities that appear in the caption, which can be specified instead of parse_mode
|
||||
CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
|
||||
// Duration of the voice message in seconds
|
||||
@@ -1307,7 +1307,7 @@ func (p *SendVoiceParams) MultipartFields() map[string]string {
|
||||
out["caption"] = p.Caption
|
||||
}
|
||||
if p.ParseMode != "" {
|
||||
out["parse_mode"] = string(p.ParseMode)
|
||||
out["parse_mode"] = p.ParseMode
|
||||
}
|
||||
if p.CaptionEntities != nil {
|
||||
if b, _ := json.Marshal(p.CaptionEntities); len(b) > 0 {
|
||||
@@ -1511,7 +1511,7 @@ type SendPaidMediaParams struct {
|
||||
// Media caption, 0-1024 characters after entities parsing
|
||||
Caption string `json:"caption,omitempty"`
|
||||
// Mode for parsing entities in the media caption. See formatting options for more details.
|
||||
ParseMode ParseMode `json:"parse_mode,omitempty"`
|
||||
ParseMode string `json:"parse_mode,omitempty"`
|
||||
// A JSON-serialized list of special entities that appear in the caption, which can be specified instead of parse_mode
|
||||
CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
|
||||
// Pass True, if the caption must be shown above the message media
|
||||
@@ -1559,7 +1559,7 @@ func (p *SendPaidMediaParams) MultipartFields() map[string]string {
|
||||
out["caption"] = p.Caption
|
||||
}
|
||||
if p.ParseMode != "" {
|
||||
out["parse_mode"] = string(p.ParseMode)
|
||||
out["parse_mode"] = p.ParseMode
|
||||
}
|
||||
if p.CaptionEntities != nil {
|
||||
if b, _ := json.Marshal(p.CaptionEntities); len(b) > 0 {
|
||||
@@ -1843,7 +1843,7 @@ type SendPollParams struct {
|
||||
// Poll question, 1-300 characters
|
||||
Question string `json:"question"`
|
||||
// Mode for parsing entities in the question. See formatting options for more details. Currently, only custom emoji entities are allowed
|
||||
QuestionParseMode ParseMode `json:"question_parse_mode,omitempty"`
|
||||
QuestionParseMode string `json:"question_parse_mode,omitempty"`
|
||||
// A JSON-serialized list of special entities that appear in the poll question. It can be specified instead of question_parse_mode
|
||||
QuestionEntities []MessageEntity `json:"question_entities,omitempty"`
|
||||
// A JSON-serialized list of 1-12 answer options
|
||||
@@ -1871,7 +1871,7 @@ type SendPollParams struct {
|
||||
// Text that is shown when a user chooses an incorrect answer or taps on the lamp icon in a quiz-style poll, 0-200 characters with at most 2 line feeds after entities parsing
|
||||
Explanation string `json:"explanation,omitempty"`
|
||||
// Mode for parsing entities in the explanation. See formatting options for more details.
|
||||
ExplanationParseMode ParseMode `json:"explanation_parse_mode,omitempty"`
|
||||
ExplanationParseMode string `json:"explanation_parse_mode,omitempty"`
|
||||
// A JSON-serialized list of special entities that appear in the poll explanation. It can be specified instead of explanation_parse_mode
|
||||
ExplanationEntities []MessageEntity `json:"explanation_entities,omitempty"`
|
||||
// Media added to the quiz explanation
|
||||
@@ -1885,7 +1885,7 @@ type SendPollParams struct {
|
||||
// Description of the poll to be sent, 0-1024 characters after entities parsing
|
||||
Description string `json:"description,omitempty"`
|
||||
// Mode for parsing entities in the poll description. See formatting options for more details.
|
||||
DescriptionParseMode ParseMode `json:"description_parse_mode,omitempty"`
|
||||
DescriptionParseMode string `json:"description_parse_mode,omitempty"`
|
||||
// A JSON-serialized list of special entities that appear in the poll description, which can be specified instead of description_parse_mode
|
||||
DescriptionEntities []MessageEntity `json:"description_entities,omitempty"`
|
||||
// Media added to the poll description
|
||||
@@ -1990,7 +1990,7 @@ type SendMessageDraftParams struct {
|
||||
// Text of the message to be sent, 0-4096 characters after entities parsing. Pass an empty text to show a “Thinking…” placeholder.
|
||||
Text string `json:"text,omitempty"`
|
||||
// Mode for parsing entities in the message text. See formatting options for more details.
|
||||
ParseMode ParseMode `json:"parse_mode,omitempty"`
|
||||
ParseMode string `json:"parse_mode,omitempty"`
|
||||
// A JSON-serialized list of special entities that appear in message text, which can be specified instead of parse_mode
|
||||
Entities []MessageEntity `json:"entities,omitempty"`
|
||||
}
|
||||
@@ -3390,7 +3390,7 @@ type SendGiftParams struct {
|
||||
// Text that will be shown along with the gift; 0-128 characters
|
||||
Text string `json:"text,omitempty"`
|
||||
// Mode for parsing entities in the text. See formatting options for more details. Entities other than “bold”, “italic”, “underline”, “strikethrough”, “spoiler”, “custom_emoji”, and “date_time” are ignored.
|
||||
TextParseMode ParseMode `json:"text_parse_mode,omitempty"`
|
||||
TextParseMode string `json:"text_parse_mode,omitempty"`
|
||||
// A JSON-serialized list of special entities that appear in the gift text. It can be specified instead of text_parse_mode. Entities other than “bold”, “italic”, “underline”, “strikethrough”, “spoiler”, “custom_emoji”, and “date_time” are ignored.
|
||||
TextEntities []MessageEntity `json:"text_entities,omitempty"`
|
||||
}
|
||||
@@ -3415,7 +3415,7 @@ type GiftPremiumSubscriptionParams struct {
|
||||
// Text that will be shown along with the service message about the subscription; 0-128 characters
|
||||
Text string `json:"text,omitempty"`
|
||||
// Mode for parsing entities in the text. See formatting options for more details. Entities other than “bold”, “italic”, “underline”, “strikethrough”, “spoiler”, “custom_emoji”, and “date_time” are ignored.
|
||||
TextParseMode ParseMode `json:"text_parse_mode,omitempty"`
|
||||
TextParseMode string `json:"text_parse_mode,omitempty"`
|
||||
// A JSON-serialized list of special entities that appear in the gift text. It can be specified instead of text_parse_mode. Entities other than “bold”, “italic”, “underline”, “strikethrough”, “spoiler”, “custom_emoji”, and “date_time” are ignored.
|
||||
TextEntities []MessageEntity `json:"text_entities,omitempty"`
|
||||
}
|
||||
@@ -3840,7 +3840,7 @@ type PostStoryParams struct {
|
||||
// Caption of the story, 0-2048 characters after entities parsing
|
||||
Caption string `json:"caption,omitempty"`
|
||||
// Mode for parsing entities in the story caption. See formatting options for more details.
|
||||
ParseMode ParseMode `json:"parse_mode,omitempty"`
|
||||
ParseMode string `json:"parse_mode,omitempty"`
|
||||
// A JSON-serialized list of special entities that appear in the caption, which can be specified instead of parse_mode
|
||||
CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
|
||||
// A JSON-serialized list of clickable areas to be shown on the story
|
||||
@@ -3896,7 +3896,7 @@ type EditStoryParams struct {
|
||||
// Caption of the story, 0-2048 characters after entities parsing
|
||||
Caption string `json:"caption,omitempty"`
|
||||
// Mode for parsing entities in the story caption. See formatting options for more details.
|
||||
ParseMode ParseMode `json:"parse_mode,omitempty"`
|
||||
ParseMode string `json:"parse_mode,omitempty"`
|
||||
// A JSON-serialized list of special entities that appear in the caption, which can be specified instead of parse_mode
|
||||
CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
|
||||
// A JSON-serialized list of clickable areas to be shown on the story
|
||||
@@ -4001,7 +4001,7 @@ type EditMessageTextParams struct {
|
||||
// New text of the message, 1-4096 characters after entities parsing
|
||||
Text string `json:"text"`
|
||||
// Mode for parsing entities in the message text. See formatting options for more details.
|
||||
ParseMode ParseMode `json:"parse_mode,omitempty"`
|
||||
ParseMode string `json:"parse_mode,omitempty"`
|
||||
// A JSON-serialized list of special entities that appear in message text, which can be specified instead of parse_mode
|
||||
Entities []MessageEntity `json:"entities,omitempty"`
|
||||
// Link preview generation options for the message
|
||||
@@ -4032,7 +4032,7 @@ type EditMessageCaptionParams struct {
|
||||
// New caption of the message, 0-1024 characters after entities parsing
|
||||
Caption string `json:"caption,omitempty"`
|
||||
// Mode for parsing entities in the message caption. See formatting options for more details.
|
||||
ParseMode ParseMode `json:"parse_mode,omitempty"`
|
||||
ParseMode string `json:"parse_mode,omitempty"`
|
||||
// A JSON-serialized list of special entities that appear in the caption, which can be specified instead of parse_mode
|
||||
CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
|
||||
// Pass True, if the caption must be shown above the message media. Supported only for animation, photo and video messages.
|
||||
@@ -4492,7 +4492,7 @@ type UploadStickerFileParams struct {
|
||||
// A file with the sticker in .WEBP, .PNG, .TGS, or .WEBM format. See https://core.telegram.org/stickers for technical requirements. More information on Sending Files »
|
||||
Sticker *InputFile `json:"sticker"`
|
||||
// Format of the sticker, must be one of “static”, “animated”, “video”
|
||||
StickerFormat InputStickerFormat `json:"sticker_format"`
|
||||
StickerFormat string `json:"sticker_format"`
|
||||
}
|
||||
|
||||
// HasFile reports whether a multipart upload is required.
|
||||
@@ -4507,7 +4507,7 @@ func (p *UploadStickerFileParams) HasFile() bool {
|
||||
func (p *UploadStickerFileParams) MultipartFields() map[string]string {
|
||||
out := map[string]string{}
|
||||
out["user_id"] = strconv.FormatInt(p.UserID, 10)
|
||||
out["sticker_format"] = string(p.StickerFormat)
|
||||
out["sticker_format"] = p.StickerFormat
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -4707,7 +4707,7 @@ type SetStickerSetThumbnailParams struct {
|
||||
// A .WEBP or .PNG image with the thumbnail, must be up to 128 kilobytes in size and have a width and height of exactly 100px, or a .TGS animation with a thumbnail up to 32 kilobytes in size (see https://core.telegram.org/stickers#animation-requirements for animated sticker technical requirements), or a .WEBM video with the thumbnail up to 32 kilobytes in size; see https://core.telegram.org/stickers#video-requirements for video sticker technical requirements. Pass a file_id as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. More information on Sending Files ». Animated and video sticker set thumbnails can't be uploaded via HTTP URL. If omitted, then the thumbnail is dropped and the first sticker is used as the thumbnail.
|
||||
Thumbnail *InputFile `json:"thumbnail,omitempty"`
|
||||
// Format of the thumbnail, must be one of “static” for a .WEBP or .PNG image, “animated” for a .TGS animation, or “video” for a .WEBM video
|
||||
Format InputStickerFormat `json:"format"`
|
||||
Format string `json:"format"`
|
||||
}
|
||||
|
||||
// HasFile reports whether a multipart upload is required.
|
||||
@@ -4723,7 +4723,7 @@ func (p *SetStickerSetThumbnailParams) MultipartFields() map[string]string {
|
||||
out := map[string]string{}
|
||||
out["name"] = p.Name
|
||||
out["user_id"] = strconv.FormatInt(p.UserID, 10)
|
||||
out["format"] = string(p.Format)
|
||||
out["format"] = p.Format
|
||||
return out
|
||||
}
|
||||
|
||||
|
||||
+14
-14
@@ -20954,7 +20954,7 @@ func Test_UploadStickerFile_Success(t *testing.T) {
|
||||
params := &UploadStickerFileParams{
|
||||
UserID: 42,
|
||||
Sticker: &InputFile{PathOrID: "file_id_test"},
|
||||
StickerFormat: InputStickerFormatStatic,
|
||||
StickerFormat: "test_value",
|
||||
}
|
||||
_, err := UploadStickerFile(context.Background(), bot, params)
|
||||
require.NoError(t, err)
|
||||
@@ -20969,7 +20969,7 @@ func Test_UploadStickerFile_APIError(t *testing.T) {
|
||||
params := &UploadStickerFileParams{
|
||||
UserID: 42,
|
||||
Sticker: &InputFile{PathOrID: "file_id_test"},
|
||||
StickerFormat: InputStickerFormatStatic,
|
||||
StickerFormat: "test_value",
|
||||
}
|
||||
_, err := UploadStickerFile(context.Background(), bot, params)
|
||||
require.Error(t, err)
|
||||
@@ -20987,7 +20987,7 @@ func Test_UploadStickerFile_NetworkError(t *testing.T) {
|
||||
params := &UploadStickerFileParams{
|
||||
UserID: 42,
|
||||
Sticker: &InputFile{PathOrID: "file_id_test"},
|
||||
StickerFormat: InputStickerFormatStatic,
|
||||
StickerFormat: "test_value",
|
||||
}
|
||||
_, err := UploadStickerFile(context.Background(), bot, params)
|
||||
require.Error(t, err)
|
||||
@@ -21003,7 +21003,7 @@ func Test_UploadStickerFile_ParseError(t *testing.T) {
|
||||
params := &UploadStickerFileParams{
|
||||
UserID: 42,
|
||||
Sticker: &InputFile{PathOrID: "file_id_test"},
|
||||
StickerFormat: InputStickerFormatStatic,
|
||||
StickerFormat: "test_value",
|
||||
}
|
||||
_, err := UploadStickerFile(context.Background(), bot, params)
|
||||
require.Error(t, err)
|
||||
@@ -21022,7 +21022,7 @@ func Test_UploadStickerFile_ContextCanceled(t *testing.T) {
|
||||
params := &UploadStickerFileParams{
|
||||
UserID: 42,
|
||||
Sticker: &InputFile{PathOrID: "file_id_test"},
|
||||
StickerFormat: InputStickerFormatStatic,
|
||||
StickerFormat: "test_value",
|
||||
}
|
||||
_, err := UploadStickerFile(ctx, bot, params)
|
||||
require.Error(t, err)
|
||||
@@ -21063,7 +21063,7 @@ func Test_UploadStickerFile_Forbidden(t *testing.T) {
|
||||
params := &UploadStickerFileParams{
|
||||
UserID: 42,
|
||||
Sticker: &InputFile{PathOrID: "file_id_test"},
|
||||
StickerFormat: InputStickerFormatStatic,
|
||||
StickerFormat: "test_value",
|
||||
}
|
||||
_, err := UploadStickerFile(context.Background(), bot, params)
|
||||
require.Error(t, err)
|
||||
@@ -21085,7 +21085,7 @@ func Test_UploadStickerFile_ServerError(t *testing.T) {
|
||||
params := &UploadStickerFileParams{
|
||||
UserID: 42,
|
||||
Sticker: &InputFile{PathOrID: "file_id_test"},
|
||||
StickerFormat: InputStickerFormatStatic,
|
||||
StickerFormat: "test_value",
|
||||
}
|
||||
_, err := UploadStickerFile(context.Background(), bot, params)
|
||||
require.Error(t, err)
|
||||
@@ -22415,7 +22415,7 @@ func Test_SetStickerSetThumbnail_Success(t *testing.T) {
|
||||
params := &SetStickerSetThumbnailParams{
|
||||
Name: "test_value",
|
||||
UserID: 42,
|
||||
Format: InputStickerFormatStatic,
|
||||
Format: "test_value",
|
||||
}
|
||||
_, err := SetStickerSetThumbnail(context.Background(), bot, params)
|
||||
require.NoError(t, err)
|
||||
@@ -22430,7 +22430,7 @@ func Test_SetStickerSetThumbnail_APIError(t *testing.T) {
|
||||
params := &SetStickerSetThumbnailParams{
|
||||
Name: "test_value",
|
||||
UserID: 42,
|
||||
Format: InputStickerFormatStatic,
|
||||
Format: "test_value",
|
||||
}
|
||||
_, err := SetStickerSetThumbnail(context.Background(), bot, params)
|
||||
require.Error(t, err)
|
||||
@@ -22448,7 +22448,7 @@ func Test_SetStickerSetThumbnail_NetworkError(t *testing.T) {
|
||||
params := &SetStickerSetThumbnailParams{
|
||||
Name: "test_value",
|
||||
UserID: 42,
|
||||
Format: InputStickerFormatStatic,
|
||||
Format: "test_value",
|
||||
}
|
||||
_, err := SetStickerSetThumbnail(context.Background(), bot, params)
|
||||
require.Error(t, err)
|
||||
@@ -22464,7 +22464,7 @@ func Test_SetStickerSetThumbnail_ParseError(t *testing.T) {
|
||||
params := &SetStickerSetThumbnailParams{
|
||||
Name: "test_value",
|
||||
UserID: 42,
|
||||
Format: InputStickerFormatStatic,
|
||||
Format: "test_value",
|
||||
}
|
||||
_, err := SetStickerSetThumbnail(context.Background(), bot, params)
|
||||
require.Error(t, err)
|
||||
@@ -22483,7 +22483,7 @@ func Test_SetStickerSetThumbnail_ContextCanceled(t *testing.T) {
|
||||
params := &SetStickerSetThumbnailParams{
|
||||
Name: "test_value",
|
||||
UserID: 42,
|
||||
Format: InputStickerFormatStatic,
|
||||
Format: "test_value",
|
||||
}
|
||||
_, err := SetStickerSetThumbnail(ctx, bot, params)
|
||||
require.Error(t, err)
|
||||
@@ -22524,7 +22524,7 @@ func Test_SetStickerSetThumbnail_Forbidden(t *testing.T) {
|
||||
params := &SetStickerSetThumbnailParams{
|
||||
Name: "test_value",
|
||||
UserID: 42,
|
||||
Format: InputStickerFormatStatic,
|
||||
Format: "test_value",
|
||||
}
|
||||
_, err := SetStickerSetThumbnail(context.Background(), bot, params)
|
||||
require.Error(t, err)
|
||||
@@ -22546,7 +22546,7 @@ func Test_SetStickerSetThumbnail_ServerError(t *testing.T) {
|
||||
params := &SetStickerSetThumbnailParams{
|
||||
Name: "test_value",
|
||||
UserID: 42,
|
||||
Format: InputStickerFormatStatic,
|
||||
Format: "test_value",
|
||||
}
|
||||
_, err := SetStickerSetThumbnail(context.Background(), bot, params)
|
||||
require.Error(t, err)
|
||||
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
package api
|
||||
|
||||
// Ptr returns a pointer to v. Useful for optional scalar fields where
|
||||
// the wire format must distinguish absent (nil) from an explicit zero
|
||||
// value (e.g. DisableNotification: api.Ptr(false) to override the
|
||||
// chat default).
|
||||
//
|
||||
// For untyped literals, supply the type parameter explicitly:
|
||||
//
|
||||
// Limit: api.Ptr[int64](5)
|
||||
//
|
||||
// For already-typed values, type inference handles it:
|
||||
//
|
||||
// var n int64 = 5
|
||||
// Limit: api.Ptr(n)
|
||||
func Ptr[T any](v T) *T { return &v }
|
||||
@@ -1,28 +0,0 @@
|
||||
package api_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/lukaszraczylo/go-telegram/api"
|
||||
)
|
||||
|
||||
func TestPtr(t *testing.T) {
|
||||
if got := api.Ptr[int64](5); got == nil || *got != 5 {
|
||||
t.Fatalf("Ptr[int64](5) = %v, want *5", got)
|
||||
}
|
||||
if got := api.Ptr(false); got == nil || *got != false {
|
||||
t.Fatalf("Ptr(false) = %v, want *false", got)
|
||||
}
|
||||
if got := api.Ptr("hello"); got == nil || *got != "hello" {
|
||||
t.Fatalf("Ptr(\"hello\") = %v, want *\"hello\"", got)
|
||||
}
|
||||
|
||||
n := int64(42)
|
||||
got := api.Ptr(n)
|
||||
if got == nil || *got != 42 {
|
||||
t.Fatalf("Ptr(n) = %v, want *42", got)
|
||||
}
|
||||
if got == &n {
|
||||
t.Fatalf("Ptr should copy, not alias caller's variable")
|
||||
}
|
||||
}
|
||||
+101
-101
@@ -137,7 +137,7 @@ type Chat struct {
|
||||
// Unique identifier for this chat. This number may have more than 32 significant bits and some programming languages may have difficulty/silent defects in interpreting it. But it has at most 52 significant bits, so a signed 64-bit integer or double-precision float type are safe for storing this identifier.
|
||||
ID int64 `json:"id"`
|
||||
// Type of the chat, can be either “private”, “group”, “supergroup” or “channel”
|
||||
Type ChatType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
// Optional. Title, for supergroups, channels and group chats
|
||||
Title string `json:"title,omitempty"`
|
||||
// Optional. Username, for private chats, supergroups and channels if available
|
||||
@@ -157,7 +157,7 @@ type ChatFullInfo struct {
|
||||
// Unique identifier for this chat. This number may have more than 32 significant bits and some programming languages may have difficulty/silent defects in interpreting it. But it has at most 52 significant bits, so a signed 64-bit integer or double-precision float type are safe for storing this identifier.
|
||||
ID int64 `json:"id"`
|
||||
// Type of the chat, can be either “private”, “group”, “supergroup” or “channel”
|
||||
Type ChatType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
// Optional. Title, for supergroups, channels and group chats
|
||||
Title string `json:"title,omitempty"`
|
||||
// Optional. Username, for private chats, supergroups and channels if available
|
||||
@@ -614,7 +614,7 @@ func UnmarshalMaybeInaccessibleMessage(data []byte) (MaybeInaccessibleMessage, e
|
||||
// This object represents one special entity in a text message. For example, hashtags, usernames, URLs, etc.
|
||||
type MessageEntity struct {
|
||||
// Type of the entity. Currently, can be “mention” (@username), “hashtag” (#hashtag or #hashtag@chatusername), “cashtag” ($USD or $USD@chatusername), “bot_command” (/start@jobs_bot), “url” (https://telegram.org), “email” (do-not-reply@telegram.org), “phone_number” (+1-212-555-0123), “bold” (bold text), “italic” (italic text), “underline” (underlined text), “strikethrough” (strikethrough text), “spoiler” (spoiler message), “blockquote” (block quotation), “expandable_blockquote” (collapsed-by-default block quotation), “code” (monowidth string), “pre” (monowidth block), “text_link” (for clickable text URLs), “text_mention” (for users without usernames), “custom_emoji” (for inline custom emoji stickers), or “date_time” (for formatted date and time)
|
||||
Type MessageEntityType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
// Offset in UTF-16 code units to the start of the entity
|
||||
Offset int64 `json:"offset"`
|
||||
// Length of the entity in UTF-16 code units
|
||||
@@ -736,7 +736,7 @@ type ReplyParameters struct {
|
||||
// Optional. Quoted part of the message to be replied to; 0-1024 characters after entities parsing. The quote must be an exact substring of the message to be replied to, including bold, italic, underline, strikethrough, spoiler, custom_emoji, and date_time entities. The message will fail to send if the quote isn't found in the original message.
|
||||
Quote string `json:"quote,omitempty"`
|
||||
// Optional. Mode for parsing entities in the quote. See formatting options for more details.
|
||||
QuoteParseMode ParseMode `json:"quote_parse_mode,omitempty"`
|
||||
QuoteParseMode string `json:"quote_parse_mode,omitempty"`
|
||||
// Optional. A JSON-serialized list of special entities that appear in the quote. It can be specified instead of quote_parse_mode.
|
||||
QuoteEntities []MessageEntity `json:"quote_entities,omitempty"`
|
||||
// Optional. Position of the quote in the original message in UTF-16 code units
|
||||
@@ -800,7 +800,7 @@ func UnmarshalMessageOrigin(data []byte) (MessageOrigin, error) {
|
||||
// The message was originally sent by a known user.
|
||||
type MessageOriginUser struct {
|
||||
// Type of the message origin, always “user”
|
||||
Type MessageOriginUserType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
// Date the message was sent originally in Unix time
|
||||
Date int64 `json:"date"`
|
||||
// User that sent the message originally
|
||||
@@ -810,7 +810,7 @@ type MessageOriginUser struct {
|
||||
// The message was originally sent by an unknown user.
|
||||
type MessageOriginHiddenUser struct {
|
||||
// Type of the message origin, always “hidden_user”
|
||||
Type MessageOriginHiddenUserType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
// Date the message was sent originally in Unix time
|
||||
Date int64 `json:"date"`
|
||||
// Name of the user that sent the message originally
|
||||
@@ -820,7 +820,7 @@ type MessageOriginHiddenUser struct {
|
||||
// The message was originally sent on behalf of a chat to a group chat.
|
||||
type MessageOriginChat struct {
|
||||
// Type of the message origin, always “chat”
|
||||
Type MessageOriginChatType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
// Date the message was sent originally in Unix time
|
||||
Date int64 `json:"date"`
|
||||
// Chat that sent the message originally
|
||||
@@ -832,7 +832,7 @@ type MessageOriginChat struct {
|
||||
// The message was originally sent to a channel chat.
|
||||
type MessageOriginChannel struct {
|
||||
// Type of the message origin, always “channel”
|
||||
Type MessageOriginChannelType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
// Date the message was sent originally in Unix time
|
||||
Date int64 `json:"date"`
|
||||
// Channel chat to which the message was originally sent
|
||||
@@ -1110,7 +1110,7 @@ func UnmarshalPaidMedia(data []byte) (PaidMedia, error) {
|
||||
// The paid media is a live photo.
|
||||
type PaidMediaLivePhoto struct {
|
||||
// Type of the paid media, always “live_photo”
|
||||
Type PaidMediaLivePhotoType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
// The photo
|
||||
LivePhoto LivePhoto `json:"live_photo"`
|
||||
}
|
||||
@@ -1118,7 +1118,7 @@ type PaidMediaLivePhoto struct {
|
||||
// The paid media is a photo.
|
||||
type PaidMediaPhoto struct {
|
||||
// Type of the paid media, always “photo”
|
||||
Type PaidMediaPhotoType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
// The photo
|
||||
Photo []PhotoSize `json:"photo"`
|
||||
}
|
||||
@@ -1126,7 +1126,7 @@ type PaidMediaPhoto struct {
|
||||
// The paid media isn't available before the payment.
|
||||
type PaidMediaPreview struct {
|
||||
// Type of the paid media, always “preview”
|
||||
Type PaidMediaPreviewType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
// Optional. Media width as defined by the sender
|
||||
Width *int64 `json:"width,omitempty"`
|
||||
// Optional. Media height as defined by the sender
|
||||
@@ -1138,7 +1138,7 @@ type PaidMediaPreview struct {
|
||||
// The paid media is a video.
|
||||
type PaidMediaVideo struct {
|
||||
// Type of the paid media, always “video”
|
||||
Type PaidMediaVideoType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
// The video
|
||||
Video Video `json:"video"`
|
||||
}
|
||||
@@ -1284,7 +1284,7 @@ type InputPollOption struct {
|
||||
// Option text, 1-100 characters
|
||||
Text string `json:"text"`
|
||||
// Optional. Mode for parsing entities in the text. See formatting options for more details. Currently, only custom emoji entities are allowed
|
||||
TextParseMode ParseMode `json:"text_parse_mode,omitempty"`
|
||||
TextParseMode string `json:"text_parse_mode,omitempty"`
|
||||
// Optional. A JSON-serialized list of special entities that appear in the poll option text. It can be specified instead of text_parse_mode
|
||||
TextEntities []MessageEntity `json:"text_entities,omitempty"`
|
||||
// Optional. Media added to the poll option
|
||||
@@ -1322,7 +1322,7 @@ type Poll struct {
|
||||
// True, if the poll is anonymous
|
||||
IsAnonymous bool `json:"is_anonymous"`
|
||||
// Poll type, currently can be “regular” or “quiz”
|
||||
Type PollType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
// True, if the poll allows multiple answers
|
||||
AllowsMultipleAnswers bool `json:"allows_multiple_answers"`
|
||||
// True, if the poll allows to change the chosen answer options
|
||||
@@ -1388,7 +1388,7 @@ type InputChecklistTask struct {
|
||||
// Text of the task; 1-100 characters after entities parsing
|
||||
Text string `json:"text"`
|
||||
// Optional. Mode for parsing entities in the text. See formatting options for more details.
|
||||
ParseMode ParseMode `json:"parse_mode,omitempty"`
|
||||
ParseMode string `json:"parse_mode,omitempty"`
|
||||
// Optional. List of special entities that appear in the text, which can be specified instead of parse_mode. Currently, only bold, italic, underline, strikethrough, spoiler, custom_emoji, and date_time entities are allowed.
|
||||
TextEntities []MessageEntity `json:"text_entities,omitempty"`
|
||||
}
|
||||
@@ -1398,7 +1398,7 @@ type InputChecklist struct {
|
||||
// Title of the checklist; 1-255 characters after entities parsing
|
||||
Title string `json:"title"`
|
||||
// Optional. Mode for parsing entities in the title. See formatting options for more details.
|
||||
ParseMode ParseMode `json:"parse_mode,omitempty"`
|
||||
ParseMode string `json:"parse_mode,omitempty"`
|
||||
// Optional. List of special entities that appear in the title, which can be specified instead of parse_mode. Currently, only bold, italic, underline, strikethrough, spoiler, custom_emoji, and date_time entities are allowed.
|
||||
TitleEntities []MessageEntity `json:"title_entities,omitempty"`
|
||||
// List of 1-30 tasks in the checklist
|
||||
@@ -1624,7 +1624,7 @@ func UnmarshalBackgroundFill(data []byte) (BackgroundFill, error) {
|
||||
// The background is filled using the selected color.
|
||||
type BackgroundFillSolid struct {
|
||||
// Type of the background fill, always “solid”
|
||||
Type BackgroundFillSolidType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
// The color of the background fill in the RGB24 format
|
||||
Color int64 `json:"color"`
|
||||
}
|
||||
@@ -1632,7 +1632,7 @@ type BackgroundFillSolid struct {
|
||||
// The background is a gradient fill.
|
||||
type BackgroundFillGradient struct {
|
||||
// Type of the background fill, always “gradient”
|
||||
Type BackgroundFillGradientType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
// Top color of the gradient in the RGB24 format
|
||||
TopColor int64 `json:"top_color"`
|
||||
// Bottom color of the gradient in the RGB24 format
|
||||
@@ -1644,7 +1644,7 @@ type BackgroundFillGradient struct {
|
||||
// The background is a freeform gradient that rotates after every message in the chat.
|
||||
type BackgroundFillFreeformGradient struct {
|
||||
// Type of the background fill, always “freeform_gradient”
|
||||
Type BackgroundFillFreeformGradientType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
// A list of the 3 or 4 base colors that are used to generate the freeform gradient in the RGB24 format
|
||||
Colors []int64 `json:"colors"`
|
||||
}
|
||||
@@ -1702,7 +1702,7 @@ func UnmarshalBackgroundType(data []byte) (BackgroundType, error) {
|
||||
// The background is automatically filled based on the selected colors.
|
||||
type BackgroundTypeFill struct {
|
||||
// Type of the background, always “fill”
|
||||
Type BackgroundTypeFillType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
// The background fill
|
||||
Fill BackgroundFill `json:"fill"`
|
||||
// Dimming of the background in dark themes, as a percentage; 0-100
|
||||
@@ -1736,7 +1736,7 @@ func (m *BackgroundTypeFill) UnmarshalJSON(data []byte) error {
|
||||
// The background is a wallpaper in the JPEG format.
|
||||
type BackgroundTypeWallpaper struct {
|
||||
// Type of the background, always “wallpaper”
|
||||
Type BackgroundTypeWallpaperType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
// Document with the wallpaper
|
||||
Document Document `json:"document"`
|
||||
// Dimming of the background in dark themes, as a percentage; 0-100
|
||||
@@ -1750,7 +1750,7 @@ type BackgroundTypeWallpaper struct {
|
||||
// The background is a .PNG or .TGV (gzipped subset of SVG with MIME type “application/x-tgwallpattern”) pattern to be combined with the background fill chosen by the user.
|
||||
type BackgroundTypePattern struct {
|
||||
// Type of the background, always “pattern”
|
||||
Type BackgroundTypePatternType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
// Document with the pattern
|
||||
Document Document `json:"document"`
|
||||
// The background fill that is combined with the pattern
|
||||
@@ -1790,7 +1790,7 @@ func (m *BackgroundTypePattern) UnmarshalJSON(data []byte) error {
|
||||
// The background is taken directly from a built-in chat theme.
|
||||
type BackgroundTypeChatTheme struct {
|
||||
// Type of the background, always “chat_theme”
|
||||
Type BackgroundTypeChatThemeType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
// Name of the chat theme, which is usually an emoji
|
||||
ThemeName string `json:"theme_name"`
|
||||
}
|
||||
@@ -1974,7 +1974,7 @@ type SuggestedPostPaid struct {
|
||||
// Optional. Message containing the suggested post. Note that the Message object in this field will not contain the reply_to_message field even if it itself is a reply.
|
||||
SuggestedPostMessage *Message `json:"suggested_post_message,omitempty"`
|
||||
// Currency in which the payment was made. Currently, one of “XTR” for Telegram Stars or “TON” for toncoins
|
||||
Currency SuggestedPostPaidCurrency `json:"currency"`
|
||||
Currency string `json:"currency"`
|
||||
// Optional. The amount of the currency that was received by the channel in nanotoncoins; for payments in toncoins only
|
||||
Amount *int64 `json:"amount,omitempty"`
|
||||
// Optional. The amount of Telegram Stars that was received by the channel; for payments in Telegram Stars only
|
||||
@@ -1986,7 +1986,7 @@ type SuggestedPostRefunded struct {
|
||||
// Optional. Message containing the suggested post. Note that the Message object in this field will not contain the reply_to_message field even if it itself is a reply.
|
||||
SuggestedPostMessage *Message `json:"suggested_post_message,omitempty"`
|
||||
// Reason for the refund. Currently, one of “post_deleted” if the post was deleted within 24 hours of being posted or removed from scheduled messages without being posted, or “payment_refunded” if the payer refunded their payment.
|
||||
Reason SuggestedPostRefundedReason `json:"reason"`
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
|
||||
// This object represents a service message about the creation of a scheduled giveaway.
|
||||
@@ -2074,7 +2074,7 @@ type LinkPreviewOptions struct {
|
||||
// Describes the price of a suggested post.
|
||||
type SuggestedPostPrice struct {
|
||||
// Currency in which the post will be paid. Currently, must be one of “XTR” for Telegram Stars or “TON” for toncoins
|
||||
Currency SuggestedPostPaidCurrency `json:"currency"`
|
||||
Currency string `json:"currency"`
|
||||
// The amount of the currency that will be paid for the post in the smallest units of the currency, i.e. Telegram Stars or nanotoncoins. Currently, price in Telegram Stars must be between 5 and 100000, and price in nanotoncoins must be between 10000000 and 10000000000000.
|
||||
Amount int64 `json:"amount"`
|
||||
}
|
||||
@@ -2082,7 +2082,7 @@ type SuggestedPostPrice struct {
|
||||
// Contains information about a suggested post.
|
||||
type SuggestedPostInfo struct {
|
||||
// State of the suggested post. Currently, it can be one of “pending”, “approved”, “declined”.
|
||||
State SuggestedPostInfoState `json:"state"`
|
||||
State string `json:"state"`
|
||||
// Optional. Proposed price of the post. If the field is omitted, then the post is unpaid.
|
||||
Price *SuggestedPostPrice `json:"price,omitempty"`
|
||||
// Optional. Proposed send date of the post. If the field is omitted, then the post can be published at any time within 30 days at the sole discretion of the user or administrator who approves it.
|
||||
@@ -2163,7 +2163,7 @@ type KeyboardButton struct {
|
||||
// Optional. Unique identifier of the custom emoji shown before the text of the button. Can only be used by bots that purchased additional usernames on Fragment or in the messages directly sent by the bot to private, group and supergroup chats if the owner of the bot has a Telegram Premium subscription.
|
||||
IconCustomEmojiID string `json:"icon_custom_emoji_id,omitempty"`
|
||||
// Optional. Style of the button. Must be one of “danger” (red), “success” (green) or “primary” (blue). If omitted, then an app-specific style is used.
|
||||
Style KeyboardButtonStyle `json:"style,omitempty"`
|
||||
Style string `json:"style,omitempty"`
|
||||
// Optional. If specified, pressing the button will open a list of suitable users. Identifiers of selected users will be sent to the bot in a “users_shared” service message. Available in private chats only.
|
||||
RequestUsers *KeyboardButtonRequestUsers `json:"request_users,omitempty"`
|
||||
// Optional. If specified, pressing the button will open a list of suitable chats. Tapping on a chat will send its identifier to the bot in a “chat_shared” service message. Available in private chats only.
|
||||
@@ -2261,7 +2261,7 @@ type InlineKeyboardButton struct {
|
||||
// Optional. Unique identifier of the custom emoji shown before the text of the button. Can only be used by bots that purchased additional usernames on Fragment or in the messages directly sent by the bot to private, group and supergroup chats if the owner of the bot has a Telegram Premium subscription.
|
||||
IconCustomEmojiID string `json:"icon_custom_emoji_id,omitempty"`
|
||||
// Optional. Style of the button. Must be one of “danger” (red), “success” (green) or “primary” (blue). If omitted, then an app-specific style is used.
|
||||
Style KeyboardButtonStyle `json:"style,omitempty"`
|
||||
Style string `json:"style,omitempty"`
|
||||
// Optional. HTTP or tg:// URL to be opened when the button is pressed. Links tg://user?id=<user_id> can be used to mention a user by their identifier without using a username, if this is allowed by their privacy settings.
|
||||
URL string `json:"url,omitempty"`
|
||||
// Optional. Data to be sent in a callback query to the bot when the button is pressed, 1-64 bytes
|
||||
@@ -2568,7 +2568,7 @@ func UnmarshalChatMember(data []byte) (ChatMember, error) {
|
||||
// Represents a chat member that owns the chat and has all administrator privileges.
|
||||
type ChatMemberOwner struct {
|
||||
// The member's status in the chat, always “creator”
|
||||
Status ChatMemberOwnerStatus `json:"status"`
|
||||
Status string `json:"status"`
|
||||
// Information about the user
|
||||
User User `json:"user"`
|
||||
// True, if the user's presence in the chat is hidden
|
||||
@@ -2580,7 +2580,7 @@ type ChatMemberOwner struct {
|
||||
// Represents a chat member that has some additional privileges.
|
||||
type ChatMemberAdministrator struct {
|
||||
// The member's status in the chat, always “administrator”
|
||||
Status ChatMemberAdministratorStatus `json:"status"`
|
||||
Status string `json:"status"`
|
||||
// Information about the user
|
||||
User User `json:"user"`
|
||||
// True, if the bot is allowed to edit administrator privileges of that user
|
||||
@@ -2626,7 +2626,7 @@ type ChatMemberAdministrator struct {
|
||||
// Represents a chat member that has no additional privileges or restrictions.
|
||||
type ChatMemberMember struct {
|
||||
// The member's status in the chat, always “member”
|
||||
Status ChatMemberMemberStatus `json:"status"`
|
||||
Status string `json:"status"`
|
||||
// Optional. Tag of the member
|
||||
Tag string `json:"tag,omitempty"`
|
||||
// Information about the user
|
||||
@@ -2638,7 +2638,7 @@ type ChatMemberMember struct {
|
||||
// Represents a chat member that is under certain restrictions in the chat. Supergroups only.
|
||||
type ChatMemberRestricted struct {
|
||||
// The member's status in the chat, always “restricted”
|
||||
Status ChatMemberRestrictedStatus `json:"status"`
|
||||
Status string `json:"status"`
|
||||
// Optional. Tag of the member
|
||||
Tag string `json:"tag,omitempty"`
|
||||
// Information about the user
|
||||
@@ -2684,7 +2684,7 @@ type ChatMemberRestricted struct {
|
||||
// Represents a chat member that isn't currently a member of the chat, but may join it themselves.
|
||||
type ChatMemberLeft struct {
|
||||
// The member's status in the chat, always “left”
|
||||
Status ChatMemberLeftStatus `json:"status"`
|
||||
Status string `json:"status"`
|
||||
// Information about the user
|
||||
User User `json:"user"`
|
||||
}
|
||||
@@ -2692,7 +2692,7 @@ type ChatMemberLeft struct {
|
||||
// Represents a chat member that was banned in the chat and can't return to the chat or view chat messages.
|
||||
type ChatMemberBanned struct {
|
||||
// The member's status in the chat, always “kicked”
|
||||
Status ChatMemberBannedStatus `json:"status"`
|
||||
Status string `json:"status"`
|
||||
// Information about the user
|
||||
User User `json:"user"`
|
||||
// Date when restrictions will be lifted for this user; Unix time. If 0, then the user is banned forever
|
||||
@@ -2894,7 +2894,7 @@ func UnmarshalStoryAreaType(data []byte) (StoryAreaType, error) {
|
||||
// Describes a story area pointing to a location. Currently, a story can have up to 10 location areas.
|
||||
type StoryAreaTypeLocation struct {
|
||||
// Type of the area, always “location”
|
||||
Type StoryAreaTypeLocationType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
// Location latitude in degrees
|
||||
Latitude float64 `json:"latitude"`
|
||||
// Location longitude in degrees
|
||||
@@ -2906,7 +2906,7 @@ type StoryAreaTypeLocation struct {
|
||||
// Describes a story area pointing to a suggested reaction. Currently, a story can have up to 5 suggested reaction areas.
|
||||
type StoryAreaTypeSuggestedReaction struct {
|
||||
// Type of the area, always “suggested_reaction”
|
||||
Type StoryAreaTypeSuggestedReactionType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
// Type of the reaction
|
||||
ReactionType ReactionType `json:"reaction_type"`
|
||||
// Optional. Pass True if the reaction area has a dark background
|
||||
@@ -2942,7 +2942,7 @@ func (m *StoryAreaTypeSuggestedReaction) UnmarshalJSON(data []byte) error {
|
||||
// Describes a story area pointing to an HTTP or tg:// link. Currently, a story can have up to 3 link areas.
|
||||
type StoryAreaTypeLink struct {
|
||||
// Type of the area, always “link”
|
||||
Type StoryAreaTypeLinkType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
// HTTP or tg:// URL to be opened when the area is clicked
|
||||
URL string `json:"url"`
|
||||
}
|
||||
@@ -2950,7 +2950,7 @@ type StoryAreaTypeLink struct {
|
||||
// Describes a story area containing weather information. Currently, a story can have up to 3 weather areas.
|
||||
type StoryAreaTypeWeather struct {
|
||||
// Type of the area, always “weather”
|
||||
Type StoryAreaTypeWeatherType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
// Temperature, in degree Celsius
|
||||
Temperature float64 `json:"temperature"`
|
||||
// Emoji representing the weather
|
||||
@@ -2962,7 +2962,7 @@ type StoryAreaTypeWeather struct {
|
||||
// Describes a story area pointing to a unique gift. Currently, a story can have at most 1 unique gift area.
|
||||
type StoryAreaTypeUniqueGift struct {
|
||||
// Type of the area, always “unique_gift”
|
||||
Type StoryAreaTypeUniqueGiftType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
// Unique name of the gift
|
||||
Name string `json:"name"`
|
||||
}
|
||||
@@ -3054,7 +3054,7 @@ func UnmarshalReactionType(data []byte) (ReactionType, error) {
|
||||
// The reaction is based on an emoji.
|
||||
type ReactionTypeEmoji struct {
|
||||
// Type of the reaction, always “emoji”
|
||||
Type ReactionTypeEmojiType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
// Reaction emoji. Currently, it can be one of "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""
|
||||
Emoji string `json:"emoji"`
|
||||
}
|
||||
@@ -3062,7 +3062,7 @@ type ReactionTypeEmoji struct {
|
||||
// The reaction is based on a custom emoji.
|
||||
type ReactionTypeCustomEmoji struct {
|
||||
// Type of the reaction, always “custom_emoji”
|
||||
Type ReactionTypeCustomEmojiType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
// Custom emoji identifier
|
||||
CustomEmojiID string `json:"custom_emoji_id"`
|
||||
}
|
||||
@@ -3070,7 +3070,7 @@ type ReactionTypeCustomEmoji struct {
|
||||
// The reaction is paid.
|
||||
type ReactionTypePaid struct {
|
||||
// Type of the reaction, always “paid”
|
||||
Type ReactionTypePaidType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// Represents a reaction added to a message along with the number of times it was added.
|
||||
@@ -3254,7 +3254,7 @@ type UniqueGiftModel struct {
|
||||
// The number of unique gifts that receive this model for every 1000 gift upgrades. Always 0 for crafted gifts.
|
||||
RarityPerMille int64 `json:"rarity_per_mille"`
|
||||
// Optional. Rarity of the model if it is a crafted model. Currently, can be “uncommon”, “rare”, “epic”, or “legendary”.
|
||||
Rarity UniqueGiftModelRarity `json:"rarity,omitempty"`
|
||||
Rarity string `json:"rarity,omitempty"`
|
||||
}
|
||||
|
||||
// This object describes the symbol shown on the pattern of a unique gift.
|
||||
@@ -3362,9 +3362,9 @@ type UniqueGiftInfo struct {
|
||||
// Information about the gift
|
||||
Gift UniqueGift `json:"gift"`
|
||||
// Origin of the gift. Currently, either “upgrade” for gifts upgraded from regular gifts, “transfer” for gifts transferred from other users or channels, “resale” for gifts bought from other users, “gifted_upgrade” for upgrades purchased after the gift was sent, or “offer” for gifts bought or sold through gift purchase offers
|
||||
Origin UniqueGiftInfoOrigin `json:"origin"`
|
||||
Origin string `json:"origin"`
|
||||
// Optional. For gifts bought from other users, the currency in which the payment for the gift was done. Currently, one of “XTR” for Telegram Stars or “TON” for toncoins.
|
||||
LastResaleCurrency SuggestedPostPaidCurrency `json:"last_resale_currency,omitempty"`
|
||||
LastResaleCurrency string `json:"last_resale_currency,omitempty"`
|
||||
// Optional. For gifts bought from other users, the price paid for the gift in either Telegram Stars or nanotoncoins
|
||||
LastResaleAmount *int64 `json:"last_resale_amount,omitempty"`
|
||||
// Optional. Unique identifier of the received gift for the bot; only present for gifts received on behalf of business accounts
|
||||
@@ -3416,7 +3416,7 @@ func UnmarshalOwnedGift(data []byte) (OwnedGift, error) {
|
||||
// Describes a regular gift owned by a user or a chat.
|
||||
type OwnedGiftRegular struct {
|
||||
// Type of the gift, always “regular”
|
||||
Type OwnedGiftRegularType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
// Information about the regular gift
|
||||
Gift Gift `json:"gift"`
|
||||
// Optional. Unique identifier of the gift for the bot; for gifts received on behalf of business accounts only
|
||||
@@ -3450,7 +3450,7 @@ type OwnedGiftRegular struct {
|
||||
// Describes a unique gift received and owned by a user or a chat.
|
||||
type OwnedGiftUnique struct {
|
||||
// Type of the gift, always “unique”
|
||||
Type OwnedGiftUniqueType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
// Information about the unique gift
|
||||
Gift UniqueGift `json:"gift"`
|
||||
// Optional. Unique identifier of the received gift for the bot; for gifts received on behalf of business accounts only
|
||||
@@ -3765,7 +3765,7 @@ func UnmarshalChatBoostSource(data []byte) (ChatBoostSource, error) {
|
||||
// The boost was obtained by subscribing to Telegram Premium or by gifting a Telegram Premium subscription to another user.
|
||||
type ChatBoostSourcePremium struct {
|
||||
// Source of the boost, always “premium”
|
||||
Source ChatBoostSourcePremiumSource `json:"source"`
|
||||
Source string `json:"source"`
|
||||
// User that boosted the chat
|
||||
User User `json:"user"`
|
||||
}
|
||||
@@ -3773,7 +3773,7 @@ type ChatBoostSourcePremium struct {
|
||||
// The boost was obtained by the creation of Telegram Premium gift codes to boost a chat. Each such code boosts the chat 4 times for the duration of the corresponding Telegram Premium subscription.
|
||||
type ChatBoostSourceGiftCode struct {
|
||||
// Source of the boost, always “gift_code”
|
||||
Source ChatBoostSourceGiftCodeSource `json:"source"`
|
||||
Source string `json:"source"`
|
||||
// User for which the gift code was created
|
||||
User User `json:"user"`
|
||||
}
|
||||
@@ -3781,7 +3781,7 @@ type ChatBoostSourceGiftCode struct {
|
||||
// The boost was obtained by the creation of a Telegram Premium or a Telegram Star giveaway. This boosts the chat 4 times for the duration of the corresponding Telegram Premium subscription for Telegram Premium giveaways and prize_star_count / 500 times for one year for Telegram Star giveaways.
|
||||
type ChatBoostSourceGiveaway struct {
|
||||
// Source of the boost, always “giveaway”
|
||||
Source ChatBoostSourceGiveawaySource `json:"source"`
|
||||
Source string `json:"source"`
|
||||
// Identifier of a message in the chat with the giveaway; the message could have been deleted already. May be 0 if the message isn't sent yet.
|
||||
GiveawayMessageID int64 `json:"giveaway_message_id"`
|
||||
// Optional. User that won the prize in the giveaway if any; for Telegram Premium giveaways only
|
||||
@@ -4015,7 +4015,7 @@ type InputMediaAnimation struct {
|
||||
// Optional. Caption of the animation to be sent, 0-1024 characters after entities parsing
|
||||
Caption string `json:"caption,omitempty"`
|
||||
// Optional. Mode for parsing entities in the animation caption. See formatting options for more details.
|
||||
ParseMode ParseMode `json:"parse_mode,omitempty"`
|
||||
ParseMode string `json:"parse_mode,omitempty"`
|
||||
// Optional. List of special entities that appear in the caption, which can be specified instead of parse_mode
|
||||
CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
|
||||
// Optional. Pass True, if the caption must be shown above the message media
|
||||
@@ -4041,7 +4041,7 @@ type InputMediaAudio struct {
|
||||
// Optional. Caption of the audio to be sent, 0-1024 characters after entities parsing
|
||||
Caption string `json:"caption,omitempty"`
|
||||
// Optional. Mode for parsing entities in the audio caption. See formatting options for more details.
|
||||
ParseMode ParseMode `json:"parse_mode,omitempty"`
|
||||
ParseMode string `json:"parse_mode,omitempty"`
|
||||
// Optional. List of special entities that appear in the caption, which can be specified instead of parse_mode
|
||||
CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
|
||||
// Optional. Duration of the audio in seconds
|
||||
@@ -4063,7 +4063,7 @@ type InputMediaDocument struct {
|
||||
// Optional. Caption of the document to be sent, 0-1024 characters after entities parsing
|
||||
Caption string `json:"caption,omitempty"`
|
||||
// Optional. Mode for parsing entities in the document caption. See formatting options for more details.
|
||||
ParseMode ParseMode `json:"parse_mode,omitempty"`
|
||||
ParseMode string `json:"parse_mode,omitempty"`
|
||||
// Optional. List of special entities that appear in the caption, which can be specified instead of parse_mode
|
||||
CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
|
||||
// Optional. Disables automatic server-side content type detection for files uploaded using multipart/form-data. Always True, if the document is sent as part of an album.
|
||||
@@ -4081,7 +4081,7 @@ type InputMediaLivePhoto struct {
|
||||
// Optional. Caption of the live photo to be sent, 0-1024 characters after entities parsing
|
||||
Caption string `json:"caption,omitempty"`
|
||||
// Optional. Mode for parsing entities in the live photo caption. See formatting options for more details.
|
||||
ParseMode ParseMode `json:"parse_mode,omitempty"`
|
||||
ParseMode string `json:"parse_mode,omitempty"`
|
||||
// Optional. List of special entities that appear in the caption, which can be specified instead of parse_mode
|
||||
CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
|
||||
// Optional. Pass True, if the caption must be shown above the message media
|
||||
@@ -4111,7 +4111,7 @@ type InputMediaPhoto struct {
|
||||
// Optional. Caption of the photo to be sent, 0-1024 characters after entities parsing
|
||||
Caption string `json:"caption,omitempty"`
|
||||
// Optional. Mode for parsing entities in the photo caption. See formatting options for more details.
|
||||
ParseMode ParseMode `json:"parse_mode,omitempty"`
|
||||
ParseMode string `json:"parse_mode,omitempty"`
|
||||
// Optional. List of special entities that appear in the caption, which can be specified instead of parse_mode
|
||||
CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
|
||||
// Optional. Pass True, if the caption must be shown above the message media
|
||||
@@ -4167,7 +4167,7 @@ type InputMediaVideo struct {
|
||||
// Optional. Caption of the video to be sent, 0-1024 characters after entities parsing
|
||||
Caption string `json:"caption,omitempty"`
|
||||
// Optional. Mode for parsing entities in the video caption. See formatting options for more details.
|
||||
ParseMode ParseMode `json:"parse_mode,omitempty"`
|
||||
ParseMode string `json:"parse_mode,omitempty"`
|
||||
// Optional. List of special entities that appear in the caption, which can be specified instead of parse_mode
|
||||
CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
|
||||
// Optional. Pass True, if the caption must be shown above the message media
|
||||
@@ -4317,7 +4317,7 @@ type Sticker struct {
|
||||
// Unique identifier for this file, which is supposed to be the same over time and for different bots. Can't be used to download or reuse the file.
|
||||
FileUniqueID string `json:"file_unique_id"`
|
||||
// Type of the sticker, currently one of “regular”, “mask”, “custom_emoji”. The type of the sticker is independent from its format, which is determined by the fields is_animated and is_video.
|
||||
Type StickerType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
// Sticker width
|
||||
Width int64 `json:"width"`
|
||||
// Sticker height
|
||||
@@ -4351,7 +4351,7 @@ type StickerSet struct {
|
||||
// Sticker set title
|
||||
Title string `json:"title"`
|
||||
// Type of stickers in the set, currently one of “regular”, “mask”, “custom_emoji”
|
||||
StickerType StickerType `json:"sticker_type"`
|
||||
StickerType string `json:"sticker_type"`
|
||||
// List of all set stickers
|
||||
Stickers []Sticker `json:"stickers"`
|
||||
// Optional. Sticker set thumbnail in the .WEBP, .TGS, or .WEBM format
|
||||
@@ -4361,7 +4361,7 @@ type StickerSet struct {
|
||||
// This object describes the position on faces where a mask should be placed by default.
|
||||
type MaskPosition struct {
|
||||
// The part of the face relative to which the mask should be placed. One of “forehead”, “eyes”, “mouth”, or “chin”.
|
||||
Point MaskPositionPoint `json:"point"`
|
||||
Point string `json:"point"`
|
||||
// Shift by X-axis measured in widths of the mask scaled to the face size, from left to right. For example, choosing -1.0 will place mask just to the left of the default mask position.
|
||||
XShift float64 `json:"x_shift"`
|
||||
// Shift by Y-axis measured in heights of the mask scaled to the face size, from top to bottom. For example, 1.0 will place the mask just below the default mask position.
|
||||
@@ -4375,7 +4375,7 @@ type InputSticker struct {
|
||||
// The added sticker. Pass a file_id as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or pass “attach://<file_attach_name>” to upload a new file using multipart/form-data under <file_attach_name> name. Animated and video stickers can't be uploaded via HTTP URL. More information on Sending Files »
|
||||
Sticker string `json:"sticker"`
|
||||
// Format of the added sticker, must be one of “static” for a .WEBP or .PNG image, “animated” for a .TGS animation, “video” for a .WEBM video
|
||||
Format InputStickerFormat `json:"format"`
|
||||
Format string `json:"format"`
|
||||
// List of 1-20 emoji associated with the sticker
|
||||
EmojiList []string `json:"emoji_list"`
|
||||
// Optional. Position where the mask should be placed on faces. For “mask” stickers only.
|
||||
@@ -4395,7 +4395,7 @@ type InlineQuery struct {
|
||||
// Offset of the results to be returned, can be controlled by the bot
|
||||
Offset string `json:"offset"`
|
||||
// Optional. Type of the chat from which the inline query was sent. Can be either “sender” for a private chat with the inline query sender, “private”, “group”, “supergroup”, or “channel”. The chat type should be always known for requests sent from official clients and most third-party clients, unless the request was sent from a secret chat
|
||||
ChatType InlineQueryChatType `json:"chat_type,omitempty"`
|
||||
ChatType string `json:"chat_type,omitempty"`
|
||||
// Optional. Sender location, only for bots that request user location
|
||||
Location *Location `json:"location,omitempty"`
|
||||
}
|
||||
@@ -4542,7 +4542,7 @@ type InlineQueryResultPhoto struct {
|
||||
// Optional. Caption of the photo to be sent, 0-1024 characters after entities parsing
|
||||
Caption string `json:"caption,omitempty"`
|
||||
// Optional. Mode for parsing entities in the photo caption. See formatting options for more details.
|
||||
ParseMode ParseMode `json:"parse_mode,omitempty"`
|
||||
ParseMode string `json:"parse_mode,omitempty"`
|
||||
// Optional. List of special entities that appear in the caption, which can be specified instead of parse_mode
|
||||
CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
|
||||
// Optional. Pass True, if the caption must be shown above the message media
|
||||
@@ -4570,13 +4570,13 @@ type InlineQueryResultGif struct {
|
||||
// URL of the static (JPEG or GIF) or animated (MPEG4) thumbnail for the result
|
||||
ThumbnailURL string `json:"thumbnail_url"`
|
||||
// Optional. MIME type of the thumbnail, must be one of “image/jpeg”, “image/gif”, or “video/mp4”. Defaults to “image/jpeg”
|
||||
ThumbnailMimeType InlineQueryResultGifThumbnailMimeType `json:"thumbnail_mime_type,omitempty"`
|
||||
ThumbnailMimeType string `json:"thumbnail_mime_type,omitempty"`
|
||||
// Optional. Title for the result
|
||||
Title string `json:"title,omitempty"`
|
||||
// Optional. Caption of the GIF file to be sent, 0-1024 characters after entities parsing
|
||||
Caption string `json:"caption,omitempty"`
|
||||
// Optional. Mode for parsing entities in the caption. See formatting options for more details.
|
||||
ParseMode ParseMode `json:"parse_mode,omitempty"`
|
||||
ParseMode string `json:"parse_mode,omitempty"`
|
||||
// Optional. List of special entities that appear in the caption, which can be specified instead of parse_mode
|
||||
CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
|
||||
// Optional. Pass True, if the caption must be shown above the message media
|
||||
@@ -4604,13 +4604,13 @@ type InlineQueryResultMpeg4Gif struct {
|
||||
// URL of the static (JPEG or GIF) or animated (MPEG4) thumbnail for the result
|
||||
ThumbnailURL string `json:"thumbnail_url"`
|
||||
// Optional. MIME type of the thumbnail, must be one of “image/jpeg”, “image/gif”, or “video/mp4”. Defaults to “image/jpeg”
|
||||
ThumbnailMimeType InlineQueryResultGifThumbnailMimeType `json:"thumbnail_mime_type,omitempty"`
|
||||
ThumbnailMimeType string `json:"thumbnail_mime_type,omitempty"`
|
||||
// Optional. Title for the result
|
||||
Title string `json:"title,omitempty"`
|
||||
// Optional. Caption of the MPEG-4 file to be sent, 0-1024 characters after entities parsing
|
||||
Caption string `json:"caption,omitempty"`
|
||||
// Optional. Mode for parsing entities in the caption. See formatting options for more details.
|
||||
ParseMode ParseMode `json:"parse_mode,omitempty"`
|
||||
ParseMode string `json:"parse_mode,omitempty"`
|
||||
// Optional. List of special entities that appear in the caption, which can be specified instead of parse_mode
|
||||
CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
|
||||
// Optional. Pass True, if the caption must be shown above the message media
|
||||
@@ -4639,7 +4639,7 @@ type InlineQueryResultVideo struct {
|
||||
// Optional. Caption of the video to be sent, 0-1024 characters after entities parsing
|
||||
Caption string `json:"caption,omitempty"`
|
||||
// Optional. Mode for parsing entities in the video caption. See formatting options for more details.
|
||||
ParseMode ParseMode `json:"parse_mode,omitempty"`
|
||||
ParseMode string `json:"parse_mode,omitempty"`
|
||||
// Optional. List of special entities that appear in the caption, which can be specified instead of parse_mode
|
||||
CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
|
||||
// Optional. Pass True, if the caption must be shown above the message media
|
||||
@@ -4671,7 +4671,7 @@ type InlineQueryResultAudio struct {
|
||||
// Optional. Caption, 0-1024 characters after entities parsing
|
||||
Caption string `json:"caption,omitempty"`
|
||||
// Optional. Mode for parsing entities in the audio caption. See formatting options for more details.
|
||||
ParseMode ParseMode `json:"parse_mode,omitempty"`
|
||||
ParseMode string `json:"parse_mode,omitempty"`
|
||||
// Optional. List of special entities that appear in the caption, which can be specified instead of parse_mode
|
||||
CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
|
||||
// Optional. Performer
|
||||
@@ -4697,7 +4697,7 @@ type InlineQueryResultVoice struct {
|
||||
// Optional. Caption, 0-1024 characters after entities parsing
|
||||
Caption string `json:"caption,omitempty"`
|
||||
// Optional. Mode for parsing entities in the voice message caption. See formatting options for more details.
|
||||
ParseMode ParseMode `json:"parse_mode,omitempty"`
|
||||
ParseMode string `json:"parse_mode,omitempty"`
|
||||
// Optional. List of special entities that appear in the caption, which can be specified instead of parse_mode
|
||||
CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
|
||||
// Optional. Recording duration in seconds
|
||||
@@ -4719,13 +4719,13 @@ type InlineQueryResultDocument struct {
|
||||
// Optional. Caption of the document to be sent, 0-1024 characters after entities parsing
|
||||
Caption string `json:"caption,omitempty"`
|
||||
// Optional. Mode for parsing entities in the document caption. See formatting options for more details.
|
||||
ParseMode ParseMode `json:"parse_mode,omitempty"`
|
||||
ParseMode string `json:"parse_mode,omitempty"`
|
||||
// Optional. List of special entities that appear in the caption, which can be specified instead of parse_mode
|
||||
CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
|
||||
// A valid URL for the file
|
||||
DocumentURL string `json:"document_url"`
|
||||
// MIME type of the content of the file, either “application/pdf” or “application/zip”
|
||||
MimeType InlineQueryResultDocumentMimeType `json:"mime_type"`
|
||||
MimeType string `json:"mime_type"`
|
||||
// Optional. Short description of the result
|
||||
Description string `json:"description,omitempty"`
|
||||
// Optional. Inline keyboard attached to the message
|
||||
@@ -4859,7 +4859,7 @@ type InlineQueryResultCachedPhoto struct {
|
||||
// Optional. Caption of the photo to be sent, 0-1024 characters after entities parsing
|
||||
Caption string `json:"caption,omitempty"`
|
||||
// Optional. Mode for parsing entities in the photo caption. See formatting options for more details.
|
||||
ParseMode ParseMode `json:"parse_mode,omitempty"`
|
||||
ParseMode string `json:"parse_mode,omitempty"`
|
||||
// Optional. List of special entities that appear in the caption, which can be specified instead of parse_mode
|
||||
CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
|
||||
// Optional. Pass True, if the caption must be shown above the message media
|
||||
@@ -4883,7 +4883,7 @@ type InlineQueryResultCachedGif struct {
|
||||
// Optional. Caption of the GIF file to be sent, 0-1024 characters after entities parsing
|
||||
Caption string `json:"caption,omitempty"`
|
||||
// Optional. Mode for parsing entities in the caption. See formatting options for more details.
|
||||
ParseMode ParseMode `json:"parse_mode,omitempty"`
|
||||
ParseMode string `json:"parse_mode,omitempty"`
|
||||
// Optional. List of special entities that appear in the caption, which can be specified instead of parse_mode
|
||||
CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
|
||||
// Optional. Pass True, if the caption must be shown above the message media
|
||||
@@ -4907,7 +4907,7 @@ type InlineQueryResultCachedMpeg4Gif struct {
|
||||
// Optional. Caption of the MPEG-4 file to be sent, 0-1024 characters after entities parsing
|
||||
Caption string `json:"caption,omitempty"`
|
||||
// Optional. Mode for parsing entities in the caption. See formatting options for more details.
|
||||
ParseMode ParseMode `json:"parse_mode,omitempty"`
|
||||
ParseMode string `json:"parse_mode,omitempty"`
|
||||
// Optional. List of special entities that appear in the caption, which can be specified instead of parse_mode
|
||||
CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
|
||||
// Optional. Pass True, if the caption must be shown above the message media
|
||||
@@ -4947,7 +4947,7 @@ type InlineQueryResultCachedDocument struct {
|
||||
// Optional. Caption of the document to be sent, 0-1024 characters after entities parsing
|
||||
Caption string `json:"caption,omitempty"`
|
||||
// Optional. Mode for parsing entities in the document caption. See formatting options for more details.
|
||||
ParseMode ParseMode `json:"parse_mode,omitempty"`
|
||||
ParseMode string `json:"parse_mode,omitempty"`
|
||||
// Optional. List of special entities that appear in the caption, which can be specified instead of parse_mode
|
||||
CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
|
||||
// Optional. Inline keyboard attached to the message
|
||||
@@ -4971,7 +4971,7 @@ type InlineQueryResultCachedVideo struct {
|
||||
// Optional. Caption of the video to be sent, 0-1024 characters after entities parsing
|
||||
Caption string `json:"caption,omitempty"`
|
||||
// Optional. Mode for parsing entities in the video caption. See formatting options for more details.
|
||||
ParseMode ParseMode `json:"parse_mode,omitempty"`
|
||||
ParseMode string `json:"parse_mode,omitempty"`
|
||||
// Optional. List of special entities that appear in the caption, which can be specified instead of parse_mode
|
||||
CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
|
||||
// Optional. Pass True, if the caption must be shown above the message media
|
||||
@@ -4995,7 +4995,7 @@ type InlineQueryResultCachedVoice struct {
|
||||
// Optional. Caption, 0-1024 characters after entities parsing
|
||||
Caption string `json:"caption,omitempty"`
|
||||
// Optional. Mode for parsing entities in the voice message caption. See formatting options for more details.
|
||||
ParseMode ParseMode `json:"parse_mode,omitempty"`
|
||||
ParseMode string `json:"parse_mode,omitempty"`
|
||||
// Optional. List of special entities that appear in the caption, which can be specified instead of parse_mode
|
||||
CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
|
||||
// Optional. Inline keyboard attached to the message
|
||||
@@ -5015,7 +5015,7 @@ type InlineQueryResultCachedAudio struct {
|
||||
// Optional. Caption, 0-1024 characters after entities parsing
|
||||
Caption string `json:"caption,omitempty"`
|
||||
// Optional. Mode for parsing entities in the audio caption. See formatting options for more details.
|
||||
ParseMode ParseMode `json:"parse_mode,omitempty"`
|
||||
ParseMode string `json:"parse_mode,omitempty"`
|
||||
// Optional. List of special entities that appear in the caption, which can be specified instead of parse_mode
|
||||
CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
|
||||
// Optional. Inline keyboard attached to the message
|
||||
@@ -5055,7 +5055,7 @@ type InputTextMessageContent struct {
|
||||
// Text of the message to be sent, 1-4096 characters
|
||||
MessageText string `json:"message_text"`
|
||||
// Optional. Mode for parsing entities in the message text. See formatting options for more details.
|
||||
ParseMode ParseMode `json:"parse_mode,omitempty"`
|
||||
ParseMode string `json:"parse_mode,omitempty"`
|
||||
// Optional. List of special entities that appear in message text, which can be specified instead of parse_mode
|
||||
Entities []MessageEntity `json:"entities,omitempty"`
|
||||
// Optional. Link preview generation options for the message
|
||||
@@ -5256,7 +5256,7 @@ type SuccessfulPayment struct {
|
||||
// This object contains basic information about a refunded payment.
|
||||
type RefundedPayment struct {
|
||||
// Three-letter ISO 4217 currency code, or “XTR” for payments in Telegram Stars. Currently, always “XTR”
|
||||
Currency RefundedPaymentCurrency `json:"currency"`
|
||||
Currency string `json:"currency"`
|
||||
// Total refunded price in the smallest units of the currency (integer, not float/double). For example, for a price of US$ 1.45, total_amount = 145. See the exp parameter in currencies.json, it shows the number of digits past the decimal point for each currency (2 for the majority of currencies).
|
||||
TotalAmount int64 `json:"total_amount"`
|
||||
// Bot-specified invoice payload
|
||||
@@ -5352,13 +5352,13 @@ func UnmarshalRevenueWithdrawalState(data []byte) (RevenueWithdrawalState, error
|
||||
// The withdrawal is in progress.
|
||||
type RevenueWithdrawalStatePending struct {
|
||||
// Type of the state, always “pending”
|
||||
Type RevenueWithdrawalStatePendingType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// The withdrawal succeeded.
|
||||
type RevenueWithdrawalStateSucceeded struct {
|
||||
// Type of the state, always “succeeded”
|
||||
Type RevenueWithdrawalStateSucceededType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
// Date the withdrawal was completed in Unix time
|
||||
Date int64 `json:"date"`
|
||||
// An HTTPS URL that can be used to see transaction details
|
||||
@@ -5368,7 +5368,7 @@ type RevenueWithdrawalStateSucceeded struct {
|
||||
// The withdrawal failed and the transaction was refunded.
|
||||
type RevenueWithdrawalStateFailed struct {
|
||||
// Type of the state, always “failed”
|
||||
Type RevenueWithdrawalStateFailedType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// Contains information about the affiliate that received a commission via this transaction.
|
||||
@@ -5452,9 +5452,9 @@ func UnmarshalTransactionPartner(data []byte) (TransactionPartner, error) {
|
||||
// Describes a transaction with a user.
|
||||
type TransactionPartnerUser struct {
|
||||
// Type of the transaction partner, always “user”
|
||||
Type MessageOriginUserType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
// Type of the transaction, currently one of “invoice_payment” for payments via invoices, “paid_media_payment” for payments for paid media, “gift_purchase” for gifts sent by the bot, “premium_purchase” for Telegram Premium subscriptions gifted by the bot, “business_account_transfer” for direct transfers from managed business accounts
|
||||
TransactionType TransactionPartnerUserTransactionType `json:"transaction_type"`
|
||||
TransactionType string `json:"transaction_type"`
|
||||
// Information about the user
|
||||
User User `json:"user"`
|
||||
// Optional. Information about the affiliate that received a commission via this transaction. Can be available only for “invoice_payment” and “paid_media_payment” transactions.
|
||||
@@ -5508,7 +5508,7 @@ func (m *TransactionPartnerUser) UnmarshalJSON(data []byte) error {
|
||||
// Describes a transaction with a chat.
|
||||
type TransactionPartnerChat struct {
|
||||
// Type of the transaction partner, always “chat”
|
||||
Type MessageOriginChatType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
// Information about the chat
|
||||
Chat Chat `json:"chat"`
|
||||
// Optional. The gift sent to the chat by the bot
|
||||
@@ -5518,7 +5518,7 @@ type TransactionPartnerChat struct {
|
||||
// Describes the affiliate program that issued the affiliate commission received via this transaction.
|
||||
type TransactionPartnerAffiliateProgram struct {
|
||||
// Type of the transaction partner, always “affiliate_program”
|
||||
Type TransactionPartnerAffiliateProgramType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
// Optional. Information about the bot that sponsored the affiliate program
|
||||
SponsorUser *User `json:"sponsor_user,omitempty"`
|
||||
// The number of Telegram Stars received by the bot for each 1000 Telegram Stars received by the affiliate program sponsor from referred users
|
||||
@@ -5528,7 +5528,7 @@ type TransactionPartnerAffiliateProgram struct {
|
||||
// Describes a withdrawal transaction with Fragment.
|
||||
type TransactionPartnerFragment struct {
|
||||
// Type of the transaction partner, always “fragment”
|
||||
Type TransactionPartnerFragmentType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
// Optional. State of the transaction if the transaction is outgoing
|
||||
WithdrawalState RevenueWithdrawalState `json:"withdrawal_state,omitempty"`
|
||||
}
|
||||
@@ -5560,13 +5560,13 @@ func (m *TransactionPartnerFragment) UnmarshalJSON(data []byte) error {
|
||||
// Describes a withdrawal transaction to the Telegram Ads platform.
|
||||
type TransactionPartnerTelegramAds struct {
|
||||
// Type of the transaction partner, always “telegram_ads”
|
||||
Type TransactionPartnerTelegramAdsType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// Describes a transaction with payment for paid broadcasting.
|
||||
type TransactionPartnerTelegramApi struct {
|
||||
// Type of the transaction partner, always “telegram_api”
|
||||
Type TransactionPartnerTelegramApiType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
// The number of successful requests that exceeded regular limits and were therefore billed
|
||||
RequestCount int64 `json:"request_count"`
|
||||
}
|
||||
@@ -5574,7 +5574,7 @@ type TransactionPartnerTelegramApi struct {
|
||||
// Describes a transaction with an unknown source or recipient.
|
||||
type TransactionPartnerOther struct {
|
||||
// Type of the transaction partner, always “other”
|
||||
Type TransactionPartnerOtherType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// Describes a Telegram Star transaction. Note that if the buyer initiates a chargeback with the payment provider from whom they acquired Stars (e.g., Apple, Google) following this transaction, the refunded Stars will be deducted from the bot's balance. This is outside of Telegram's control.
|
||||
@@ -5656,7 +5656,7 @@ type PassportFile struct {
|
||||
// Describes documents or other Telegram Passport elements shared with the bot by the user.
|
||||
type EncryptedPassportElement struct {
|
||||
// Element type. One of “personal_details”, “passport”, “driver_license”, “identity_card”, “internal_passport”, “address”, “utility_bill”, “bank_statement”, “rental_agreement”, “passport_registration”, “temporary_registration”, “phone_number”, “email”.
|
||||
Type EncryptedPassportElementType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
// Optional. Base64-encoded encrypted Telegram Passport element data provided by the user; available only for “personal_details”, “passport”, “driver_license”, “identity_card”, “internal_passport” and “address” types. Can be decrypted and verified using the accompanying EncryptedCredentials.
|
||||
Data string `json:"data,omitempty"`
|
||||
// Optional. User's verified phone number; available only for “phone_number” type
|
||||
@@ -5734,7 +5734,7 @@ type PassportElementErrorDataField struct {
|
||||
// Error source, must be data
|
||||
Source string `json:"source"`
|
||||
// The section of the user's Telegram Passport which has the error, one of “personal_details”, “passport”, “driver_license”, “identity_card”, “internal_passport”, “address”
|
||||
Type PassportElementErrorDataFieldType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
// Name of the data field which has the error
|
||||
FieldName string `json:"field_name"`
|
||||
// Base64-encoded data hash
|
||||
@@ -5748,7 +5748,7 @@ type PassportElementErrorFrontSide struct {
|
||||
// Error source, must be front_side
|
||||
Source string `json:"source"`
|
||||
// The section of the user's Telegram Passport which has the issue, one of “passport”, “driver_license”, “identity_card”, “internal_passport”
|
||||
Type PassportElementErrorSelfieType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
// Base64-encoded hash of the file with the front side of the document
|
||||
FileHash string `json:"file_hash"`
|
||||
// Error message
|
||||
@@ -5760,7 +5760,7 @@ type PassportElementErrorReverseSide struct {
|
||||
// Error source, must be reverse_side
|
||||
Source string `json:"source"`
|
||||
// The section of the user's Telegram Passport which has the issue, one of “driver_license”, “identity_card”
|
||||
Type PassportElementErrorReverseSideType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
// Base64-encoded hash of the file with the reverse side of the document
|
||||
FileHash string `json:"file_hash"`
|
||||
// Error message
|
||||
@@ -5772,7 +5772,7 @@ type PassportElementErrorSelfie struct {
|
||||
// Error source, must be selfie
|
||||
Source string `json:"source"`
|
||||
// The section of the user's Telegram Passport which has the issue, one of “passport”, “driver_license”, “identity_card”, “internal_passport”
|
||||
Type PassportElementErrorSelfieType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
// Base64-encoded hash of the file with the selfie
|
||||
FileHash string `json:"file_hash"`
|
||||
// Error message
|
||||
@@ -5784,7 +5784,7 @@ type PassportElementErrorFile struct {
|
||||
// Error source, must be file
|
||||
Source string `json:"source"`
|
||||
// The section of the user's Telegram Passport which has the issue, one of “utility_bill”, “bank_statement”, “rental_agreement”, “passport_registration”, “temporary_registration”
|
||||
Type PassportElementErrorFileType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
// Base64-encoded file hash
|
||||
FileHash string `json:"file_hash"`
|
||||
// Error message
|
||||
@@ -5796,7 +5796,7 @@ type PassportElementErrorFiles struct {
|
||||
// Error source, must be files
|
||||
Source string `json:"source"`
|
||||
// The section of the user's Telegram Passport which has the issue, one of “utility_bill”, “bank_statement”, “rental_agreement”, “passport_registration”, “temporary_registration”
|
||||
Type PassportElementErrorFileType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
// List of base64-encoded file hashes
|
||||
FileHashes []string `json:"file_hashes"`
|
||||
// Error message
|
||||
@@ -5808,7 +5808,7 @@ type PassportElementErrorTranslationFile struct {
|
||||
// Error source, must be translation_file
|
||||
Source string `json:"source"`
|
||||
// Type of element of the user's Telegram Passport which has the issue, one of “passport”, “driver_license”, “identity_card”, “internal_passport”, “utility_bill”, “bank_statement”, “rental_agreement”, “passport_registration”, “temporary_registration”
|
||||
Type PassportElementErrorTranslationFileType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
// Base64-encoded file hash
|
||||
FileHash string `json:"file_hash"`
|
||||
// Error message
|
||||
@@ -5820,7 +5820,7 @@ type PassportElementErrorTranslationFiles struct {
|
||||
// Error source, must be translation_files
|
||||
Source string `json:"source"`
|
||||
// Type of element of the user's Telegram Passport which has the issue, one of “passport”, “driver_license”, “identity_card”, “internal_passport”, “utility_bill”, “bank_statement”, “rental_agreement”, “passport_registration”, “temporary_registration”
|
||||
Type PassportElementErrorTranslationFileType `json:"type"`
|
||||
Type string `json:"type"`
|
||||
// List of base64-encoded file hashes
|
||||
FileHashes []string `json:"file_hashes"`
|
||||
// Error message
|
||||
|
||||
+26
-61
@@ -164,16 +164,15 @@ var knownDiscriminators = map[string]discriminatorSpec{
|
||||
type emitter struct {
|
||||
api *spec.API
|
||||
outDir string
|
||||
enums *enumPlan
|
||||
}
|
||||
|
||||
func newEmitter(api *spec.API, outDir string) *emitter {
|
||||
return &emitter{api: api, outDir: outDir, enums: planEnums(api)}
|
||||
return &emitter{api: api, outDir: outDir}
|
||||
}
|
||||
|
||||
// emitTypes renders types.gen.go.
|
||||
func (e *emitter) emitTypes() error {
|
||||
t, err := template.New("types").Funcs(funcs(e.enums)).Parse(typesTmpl)
|
||||
t, err := template.New("types").Funcs(funcs()).Parse(typesTmpl)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parse types.tmpl: %w", err)
|
||||
}
|
||||
@@ -209,33 +208,20 @@ func loadAPI(path string) (*spec.API, error) {
|
||||
return &api, nil
|
||||
}
|
||||
|
||||
// funcs is the FuncMap shared across templates. plan is the resolved
|
||||
// enum plan; pass nil only in unit tests that don't exercise enums.
|
||||
func funcs(plan *enumPlan) template.FuncMap {
|
||||
// funcs is the FuncMap shared across templates.
|
||||
func funcs() template.FuncMap {
|
||||
return template.FuncMap{
|
||||
"goType": goType,
|
||||
"goField": func(parent string, f spec.Field) string {
|
||||
return goField(plan, parent, f)
|
||||
},
|
||||
"docComment": docComment,
|
||||
"isOptional": func(f spec.Field) bool { return !f.Required },
|
||||
"not": func(b bool) bool { return !b },
|
||||
"title": title,
|
||||
"isFileField": isFileField,
|
||||
"fileCheck": fileCheck,
|
||||
"multipartFieldEntry": func(parent string, f spec.Field) string {
|
||||
return multipartFieldEntry(plan, parent, f)
|
||||
},
|
||||
"multipartFileEntry": multipartFileEntry,
|
||||
"returnGoType": returnGoType,
|
||||
// enum helpers
|
||||
"enums": func() []enumDecl {
|
||||
if plan == nil {
|
||||
return nil
|
||||
}
|
||||
return plan.All()
|
||||
},
|
||||
"enumConstName": constName,
|
||||
"goType": goType,
|
||||
"goField": goField,
|
||||
"docComment": docComment,
|
||||
"isOptional": func(f spec.Field) bool { return !f.Required },
|
||||
"not": func(b bool) bool { return !b },
|
||||
"title": title,
|
||||
"isFileField": isFileField,
|
||||
"fileCheck": fileCheck,
|
||||
"multipartFieldEntry": multipartFieldEntry,
|
||||
"multipartFileEntry": multipartFileEntry,
|
||||
"returnGoType": returnGoType,
|
||||
// discriminator helpers for types.tmpl
|
||||
"hasDiscriminator": func(name string) bool { s, ok := knownDiscriminators[name]; return ok && len(s.Variants) > 0 },
|
||||
"isSealedUnionReturn": func(tr spec.TypeRef) bool {
|
||||
@@ -309,10 +295,8 @@ func multipartFileEntry(f spec.Field) string {
|
||||
|
||||
// multipartFieldEntry generates the line that adds f to the multipart map.
|
||||
// Required scalar fields go in unconditionally; optional ones go in only
|
||||
// when non-zero/non-empty. Typed-string enum fields are cast to string
|
||||
// before assignment because the multipart map is map[string]string.
|
||||
func multipartFieldEntry(plan *enumPlan, parent string, f spec.Field) string {
|
||||
enumName := plan.FieldEnum(parent, f.Name)
|
||||
// when non-zero/non-empty.
|
||||
func multipartFieldEntry(f spec.Field) string {
|
||||
switch f.Type.Kind {
|
||||
case spec.KindPrimitive:
|
||||
switch f.Type.Name {
|
||||
@@ -322,12 +306,6 @@ func multipartFieldEntry(plan *enumPlan, parent string, f spec.Field) string {
|
||||
}
|
||||
return fmt.Sprintf("\tif p.%s != nil { out[%q] = strconv.FormatInt(*p.%s, 10) }\n", f.Name, f.JSONName, f.Name)
|
||||
case "string":
|
||||
if enumName != "" {
|
||||
if f.Required {
|
||||
return fmt.Sprintf("\tout[%q] = string(p.%s)\n", f.JSONName, f.Name)
|
||||
}
|
||||
return fmt.Sprintf("\tif p.%s != \"\" { out[%q] = string(p.%s) }\n", f.Name, f.JSONName, f.Name)
|
||||
}
|
||||
if f.Required {
|
||||
return fmt.Sprintf("\tout[%q] = p.%s\n", f.JSONName, f.Name)
|
||||
}
|
||||
@@ -414,7 +392,7 @@ func returnGoElem(tr spec.TypeRef) string {
|
||||
|
||||
// emitMethods renders methods.gen.go.
|
||||
func (e *emitter) emitMethods() error {
|
||||
t, err := template.New("methods").Funcs(funcs(e.enums)).Parse(methodsTmpl)
|
||||
t, err := template.New("methods").Funcs(funcs()).Parse(methodsTmpl)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parse methods.tmpl: %w", err)
|
||||
}
|
||||
@@ -431,7 +409,7 @@ func (e *emitter) emitMethods() error {
|
||||
|
||||
// emitEnums renders enums.gen.go.
|
||||
func (e *emitter) emitEnums() error {
|
||||
t, err := template.New("enums").Funcs(funcs(e.enums)).Parse(enumsTmpl)
|
||||
t, err := template.New("enums").Funcs(funcs()).Parse(enumsTmpl)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parse enums.tmpl: %w", err)
|
||||
}
|
||||
@@ -580,16 +558,8 @@ func matchesVariants(got []string, want ...string) bool {
|
||||
}
|
||||
|
||||
// goField returns the Go struct-field declaration for a Field.
|
||||
// When the field carries scraper-detected enum values and the emitter
|
||||
// has a planned enum name for (parent, field), the field's Go type is
|
||||
// the enum identifier. Typed-string enums use the zero string ""
|
||||
// behaviour for omitempty, so we do not pointer-wrap optional enum
|
||||
// fields. Parent is "" for method parameters.
|
||||
func goField(plan *enumPlan, parent string, f spec.Field) string {
|
||||
func goField(f spec.Field) string {
|
||||
tag := fmt.Sprintf("`json:%q`", f.JSONName+omitempty(f))
|
||||
if name := plan.FieldEnum(parent, f.Name); name != "" {
|
||||
return fmt.Sprintf("%s %s %s", f.Name, name, tag)
|
||||
}
|
||||
return fmt.Sprintf("%s %s %s", f.Name, goType(f.Type, !f.Required), tag)
|
||||
}
|
||||
|
||||
@@ -653,19 +623,14 @@ func buildUnionTypeSet(api *spec.API) map[string]bool {
|
||||
|
||||
// makeSentinelValue returns a sentinelValue func that uses the given union type set.
|
||||
// It returns a minimal valid Go expression for a spec.Field's type,
|
||||
// used in generated test param literals. plan supplies typed-enum names
|
||||
// so a method-param sentinel for a ParseMode field becomes a typed
|
||||
// constant rather than a magic string.
|
||||
func makeSentinelValue(unionTypes map[string]bool, plan *enumPlan) func(spec.Field) string {
|
||||
// used in generated test param literals.
|
||||
func makeSentinelValue(unionTypes map[string]bool) func(spec.Field) string {
|
||||
return func(f spec.Field) string {
|
||||
return sentinelForField(f, unionTypes, plan)
|
||||
return sentinelForField(f, unionTypes)
|
||||
}
|
||||
}
|
||||
|
||||
func sentinelForField(f spec.Field, unionTypes map[string]bool, plan *enumPlan) string {
|
||||
if name := plan.FieldEnum("", f.Name); name != "" && len(f.EnumValues) > 0 {
|
||||
return constName(name, f.EnumValues[0])
|
||||
}
|
||||
func sentinelForField(f spec.Field, unionTypes map[string]bool) string {
|
||||
tr := f.Type
|
||||
switch tr.Kind {
|
||||
case spec.KindPrimitive:
|
||||
@@ -764,8 +729,8 @@ func (e *emitter) emitTests() error {
|
||||
unionTypes := buildUnionTypeSet(e.api)
|
||||
|
||||
// Add test-specific helpers to the shared func map.
|
||||
fm := funcs(e.enums)
|
||||
fm["sentinelValue"] = makeSentinelValue(unionTypes, e.enums)
|
||||
fm := funcs()
|
||||
fm["sentinelValue"] = makeSentinelValue(unionTypes)
|
||||
fm["successResp"] = successResp
|
||||
|
||||
t, err := template.New("tests").Funcs(fm).Parse(testsTmpl)
|
||||
|
||||
@@ -1,307 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/lukaszraczylo/go-telegram/internal/spec"
|
||||
)
|
||||
|
||||
// enumDecl is one generated enum: a Go type alias of string plus a set
|
||||
// of named constants. Values keep doc order; constant identifiers are
|
||||
// derived from values via constName.
|
||||
type enumDecl struct {
|
||||
Name string
|
||||
Values []string
|
||||
}
|
||||
|
||||
// enumPlan is the deduplicated, name-resolved set of enums emitted from
|
||||
// an API IR. Lookup returns the enum name for a given field reference;
|
||||
// All returns the deterministic-ordered list of declarations to emit.
|
||||
type enumPlan struct {
|
||||
// fieldKey -> enum name. The fieldKey is a string built by enumKey.
|
||||
byField map[string]string
|
||||
// enum name -> declaration.
|
||||
decls map[string]enumDecl
|
||||
}
|
||||
|
||||
// enumKey identifies a single Field occurrence so the emitter can look
|
||||
// up the enum name later. Parent is "" for method params (the method
|
||||
// doesn't share a Go type with the field).
|
||||
func enumKey(parent, fieldName string) string { return parent + "::" + fieldName }
|
||||
|
||||
// planEnums walks the IR, decides on enum names, deduplicates, and
|
||||
// returns an enumPlan. All scraper-marked enum fields are covered.
|
||||
func planEnums(api *spec.API) *enumPlan {
|
||||
type ref struct {
|
||||
parent string
|
||||
fieldName string
|
||||
jsonName string
|
||||
values []string
|
||||
valueKey string // canonical key for value-set dedup
|
||||
}
|
||||
|
||||
var refs []ref
|
||||
collect := func(parent string, fields []spec.Field) {
|
||||
for _, f := range fields {
|
||||
if len(f.EnumValues) == 0 {
|
||||
continue
|
||||
}
|
||||
refs = append(refs, ref{
|
||||
parent: parent,
|
||||
fieldName: f.Name,
|
||||
jsonName: f.JSONName,
|
||||
values: f.EnumValues,
|
||||
valueKey: valueKey(f.EnumValues),
|
||||
})
|
||||
}
|
||||
}
|
||||
for _, t := range api.Types {
|
||||
collect(t.Name, t.Fields)
|
||||
}
|
||||
for _, m := range api.Methods {
|
||||
// Method params have no shared Go parent type, so we pass "" as
|
||||
// the parent. The default-name heuristic still produces the
|
||||
// right answer for ParseMode-style enums.
|
||||
collect("", m.Params)
|
||||
}
|
||||
|
||||
// candidate name per ref (before collision resolution)
|
||||
candidate := make([]string, len(refs))
|
||||
for i, r := range refs {
|
||||
candidate[i] = defaultEnumName(r.parent, r.jsonName, r.fieldName)
|
||||
}
|
||||
|
||||
// Group by valueKey to coalesce identical value-sets across fields.
|
||||
// Choose canonical name: prefer the most common candidate; tie-break
|
||||
// by shortest name; final tie-break alphabetical.
|
||||
type groupInfo struct {
|
||||
values []string
|
||||
name string
|
||||
first int
|
||||
}
|
||||
groups := map[string]*groupInfo{}
|
||||
for i, r := range refs {
|
||||
g, ok := groups[r.valueKey]
|
||||
if !ok {
|
||||
groups[r.valueKey] = &groupInfo{values: r.values, first: i}
|
||||
g = groups[r.valueKey]
|
||||
}
|
||||
_ = g
|
||||
}
|
||||
// Rank candidate names per group.
|
||||
for vk := range groups {
|
||||
counts := map[string]int{}
|
||||
hasParent := map[string]bool{}
|
||||
var names []string
|
||||
for i, r := range refs {
|
||||
if r.valueKey != vk {
|
||||
continue
|
||||
}
|
||||
n := candidate[i]
|
||||
if _, ok := counts[n]; !ok {
|
||||
names = append(names, n)
|
||||
}
|
||||
counts[n]++
|
||||
if r.parent != "" {
|
||||
hasParent[n] = true
|
||||
}
|
||||
}
|
||||
// Pick the canonical name for this group:
|
||||
// 1. highest occurrence count wins;
|
||||
// 2. names that originated from a parent type win over plain
|
||||
// method-param candidates (avoids "Format"-style
|
||||
// monosyllables);
|
||||
// 3. shortest name wins;
|
||||
// 4. alphabetical for full determinism.
|
||||
sort.SliceStable(names, func(a, b int) bool {
|
||||
if counts[names[a]] != counts[names[b]] {
|
||||
return counts[names[a]] > counts[names[b]]
|
||||
}
|
||||
if hasParent[names[a]] != hasParent[names[b]] {
|
||||
return hasParent[names[a]]
|
||||
}
|
||||
if len(names[a]) != len(names[b]) {
|
||||
return len(names[a]) < len(names[b])
|
||||
}
|
||||
return names[a] < names[b]
|
||||
})
|
||||
groups[vk].name = names[0]
|
||||
}
|
||||
|
||||
// Collision pass: two groups must not share the same enum name.
|
||||
// When that happens, suffix the loser(s) with their parent type
|
||||
// name so the result is unique. Iterate in deterministic order
|
||||
// (groups sorted by valueKey).
|
||||
used := map[string]string{} // name -> valueKey owner
|
||||
var keys []string
|
||||
for vk := range groups {
|
||||
keys = append(keys, vk)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, vk := range keys {
|
||||
g := groups[vk]
|
||||
if _, taken := used[g.name]; !taken {
|
||||
used[g.name] = vk
|
||||
continue
|
||||
}
|
||||
// Find a unique name by prepending a parent prefix from one of
|
||||
// the contributing refs (the lowest-index ref in this group).
|
||||
for i, r := range refs {
|
||||
if r.valueKey != vk {
|
||||
continue
|
||||
}
|
||||
if r.parent == "" {
|
||||
continue
|
||||
}
|
||||
cand := r.parent + goNamePart(r.jsonName)
|
||||
if _, taken := used[cand]; !taken {
|
||||
g.name = cand
|
||||
used[cand] = vk
|
||||
goto next
|
||||
}
|
||||
_ = i
|
||||
}
|
||||
// Fallback: append a numeric disambiguator. Should not happen
|
||||
// in practice for the Telegram docs but keeps the algorithm
|
||||
// total.
|
||||
for n := 2; ; n++ {
|
||||
cand := groups[vk].name + itoa(n)
|
||||
if _, taken := used[cand]; !taken {
|
||||
g.name = cand
|
||||
used[cand] = vk
|
||||
break
|
||||
}
|
||||
}
|
||||
next:
|
||||
}
|
||||
|
||||
// Build the plan.
|
||||
plan := &enumPlan{
|
||||
byField: map[string]string{},
|
||||
decls: map[string]enumDecl{},
|
||||
}
|
||||
for i, r := range refs {
|
||||
name := groups[r.valueKey].name
|
||||
plan.byField[enumKey(r.parent, r.fieldName)] = name
|
||||
_ = i
|
||||
}
|
||||
for vk, g := range groups {
|
||||
plan.decls[g.name] = enumDecl{Name: g.name, Values: g.values}
|
||||
_ = vk
|
||||
}
|
||||
return plan
|
||||
}
|
||||
|
||||
// All returns the enum declarations sorted by name for deterministic emit.
|
||||
func (p *enumPlan) All() []enumDecl {
|
||||
out := make([]enumDecl, 0, len(p.decls))
|
||||
for _, d := range p.decls {
|
||||
out = append(out, d)
|
||||
}
|
||||
sort.Slice(out, func(i, j int) bool { return out[i].Name < out[j].Name })
|
||||
return out
|
||||
}
|
||||
|
||||
// FieldEnum returns the enum name for a field on a given parent type
|
||||
// (use parent="" for method parameters), or "" if the field is not an
|
||||
// enum.
|
||||
func (p *enumPlan) FieldEnum(parent, fieldName string) string {
|
||||
if p == nil {
|
||||
return ""
|
||||
}
|
||||
return p.byField[enumKey(parent, fieldName)]
|
||||
}
|
||||
|
||||
// defaultEnumName picks an initial Go enum name for a field. parse_mode
|
||||
// fields collapse to the canonical "ParseMode"; otherwise the name is
|
||||
// parent + PascalCase(jsonName).
|
||||
func defaultEnumName(parent, jsonName, fieldName string) string {
|
||||
if strings.HasSuffix(jsonName, "parse_mode") {
|
||||
return "ParseMode"
|
||||
}
|
||||
return parent + goNamePart(jsonName)
|
||||
}
|
||||
|
||||
// constName builds a Go constant identifier "<EnumName><PascalValue>"
|
||||
// from a wire value. Slashes (mime types) become "Of" so
|
||||
// "image/jpeg" → "ImageOfJpeg".
|
||||
func constName(enumName, value string) string {
|
||||
return enumName + valuePascal(value)
|
||||
}
|
||||
|
||||
func valuePascal(v string) string {
|
||||
// "image/jpeg" → "ImageOfJpeg"
|
||||
parts := strings.Split(v, "/")
|
||||
for i, p := range parts {
|
||||
parts[i] = goNamePart(p)
|
||||
}
|
||||
return strings.Join(parts, "Of")
|
||||
}
|
||||
|
||||
// goNamePart converts a snake_case (or already-PascalCase) token to
|
||||
// PascalCase, mirroring scrape.goName behaviour without the acronym
|
||||
// special-cases (which apply to wire identifiers, not enum values).
|
||||
func goNamePart(s string) string {
|
||||
if s == "" {
|
||||
return ""
|
||||
}
|
||||
parts := strings.Split(s, "_")
|
||||
var b strings.Builder
|
||||
for _, p := range parts {
|
||||
if p == "" {
|
||||
continue
|
||||
}
|
||||
// Acronyms used in Telegram wire names. Keeping in sync with
|
||||
// scrape/table.go avoids divergent capitalisation between
|
||||
// fieldName and constName.
|
||||
switch p {
|
||||
case "id":
|
||||
b.WriteString("ID")
|
||||
continue
|
||||
case "url":
|
||||
b.WriteString("URL")
|
||||
continue
|
||||
case "ip":
|
||||
b.WriteString("IP")
|
||||
continue
|
||||
case "https":
|
||||
b.WriteString("HTTPS")
|
||||
continue
|
||||
case "json":
|
||||
b.WriteString("JSON")
|
||||
continue
|
||||
case "html":
|
||||
b.WriteString("HTML")
|
||||
continue
|
||||
}
|
||||
if c := p[0]; c >= 'a' && c <= 'z' {
|
||||
b.WriteByte(c - 'a' + 'A')
|
||||
b.WriteString(p[1:])
|
||||
} else {
|
||||
b.WriteString(p)
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func valueKey(values []string) string {
|
||||
cp := make([]string, len(values))
|
||||
copy(cp, values)
|
||||
sort.Strings(cp)
|
||||
return strings.Join(cp, "\x00")
|
||||
}
|
||||
|
||||
func itoa(n int) string {
|
||||
if n == 0 {
|
||||
return "0"
|
||||
}
|
||||
var buf [20]byte
|
||||
i := len(buf)
|
||||
for n > 0 {
|
||||
i--
|
||||
buf[i] = byte('0' + n%10)
|
||||
n /= 10
|
||||
}
|
||||
return string(buf[i:])
|
||||
}
|
||||
+52
-5
@@ -4,10 +4,57 @@
|
||||
|
||||
package api
|
||||
|
||||
{{range $e := enums}}
|
||||
type {{$e.Name}} string
|
||||
// ParseMode controls how Telegram interprets formatting in message text.
|
||||
type ParseMode string
|
||||
|
||||
const (
|
||||
{{range $v := $e.Values}} {{enumConstName $e.Name $v}} {{$e.Name}} = {{printf "%q" $v}}
|
||||
{{end}})
|
||||
{{end}}
|
||||
ParseModeMarkdown ParseMode = "Markdown" // legacy
|
||||
ParseModeMarkdownV2 ParseMode = "MarkdownV2"
|
||||
ParseModeHTML ParseMode = "HTML"
|
||||
)
|
||||
|
||||
// ChatType is the type of a Telegram chat.
|
||||
type ChatType string
|
||||
|
||||
const (
|
||||
ChatTypePrivate ChatType = "private"
|
||||
ChatTypeGroup ChatType = "group"
|
||||
ChatTypeSupergroup ChatType = "supergroup"
|
||||
ChatTypeChannel ChatType = "channel"
|
||||
)
|
||||
|
||||
// UpdateType identifies an Update payload variant. Used by allowed_updates
|
||||
// in getUpdates / setWebhook.
|
||||
type UpdateType string
|
||||
|
||||
const (
|
||||
UpdateMessage UpdateType = "message"
|
||||
UpdateEditedMessage UpdateType = "edited_message"
|
||||
UpdateChannelPost UpdateType = "channel_post"
|
||||
UpdateEditedChannelPost UpdateType = "edited_channel_post"
|
||||
UpdateCallbackQuery UpdateType = "callback_query"
|
||||
UpdateInlineQuery UpdateType = "inline_query"
|
||||
)
|
||||
|
||||
// MessageEntityType is the kind of an entity (mention, hashtag, command, ...).
|
||||
type MessageEntityType string
|
||||
|
||||
const (
|
||||
EntityMention MessageEntityType = "mention"
|
||||
EntityHashtag MessageEntityType = "hashtag"
|
||||
EntityCashtag MessageEntityType = "cashtag"
|
||||
EntityBotCommand MessageEntityType = "bot_command"
|
||||
EntityURL MessageEntityType = "url"
|
||||
EntityEmail MessageEntityType = "email"
|
||||
EntityPhoneNumber MessageEntityType = "phone_number"
|
||||
EntityBold MessageEntityType = "bold"
|
||||
EntityItalic MessageEntityType = "italic"
|
||||
EntityUnderline MessageEntityType = "underline"
|
||||
EntityStrike MessageEntityType = "strikethrough"
|
||||
EntitySpoiler MessageEntityType = "spoiler"
|
||||
EntityCode MessageEntityType = "code"
|
||||
EntityPre MessageEntityType = "pre"
|
||||
EntityTextLink MessageEntityType = "text_link"
|
||||
EntityTextMention MessageEntityType = "text_mention"
|
||||
EntityCustomEmoji MessageEntityType = "custom_emoji"
|
||||
)
|
||||
|
||||
+17
-17
@@ -175,101 +175,101 @@ func makeFieldVariants(name, jname string, kind spec.Kind, variants []string, re
|
||||
|
||||
func TestMultipartFieldEntry_Int64Required(t *testing.T) {
|
||||
f := makeField("ChatID", "chat_id", "int64", spec.KindPrimitive, true)
|
||||
got := multipartFieldEntry(nil, "", f)
|
||||
got := multipartFieldEntry(f)
|
||||
require.Contains(t, got, `FormatInt`)
|
||||
require.NotContains(t, got, "if p.")
|
||||
}
|
||||
|
||||
func TestMultipartFieldEntry_Int64Optional(t *testing.T) {
|
||||
f := makeField("MessageThreadID", "message_thread_id", "int64", spec.KindPrimitive, false)
|
||||
got := multipartFieldEntry(nil, "", f)
|
||||
got := multipartFieldEntry(f)
|
||||
require.Contains(t, got, `FormatInt`)
|
||||
require.Contains(t, got, "if p.")
|
||||
}
|
||||
|
||||
func TestMultipartFieldEntry_StringRequired(t *testing.T) {
|
||||
f := makeField("Text", "text", "string", spec.KindPrimitive, true)
|
||||
got := multipartFieldEntry(nil, "", f)
|
||||
got := multipartFieldEntry(f)
|
||||
require.Contains(t, got, `out["text"]`)
|
||||
require.NotContains(t, got, "if p.Text")
|
||||
}
|
||||
|
||||
func TestMultipartFieldEntry_StringOptional(t *testing.T) {
|
||||
f := makeField("ParseMode", "parse_mode", "string", spec.KindPrimitive, false)
|
||||
got := multipartFieldEntry(nil, "", f)
|
||||
got := multipartFieldEntry(f)
|
||||
require.Contains(t, got, `if p.ParseMode`)
|
||||
}
|
||||
|
||||
func TestMultipartFieldEntry_BoolRequired(t *testing.T) {
|
||||
f := makeField("DisableNotification", "disable_notification", "bool", spec.KindPrimitive, true)
|
||||
got := multipartFieldEntry(nil, "", f)
|
||||
got := multipartFieldEntry(f)
|
||||
require.Contains(t, got, `FormatBool`)
|
||||
require.NotContains(t, got, "if p.")
|
||||
}
|
||||
|
||||
func TestMultipartFieldEntry_BoolOptional(t *testing.T) {
|
||||
f := makeField("Protected", "protect_content", "bool", spec.KindPrimitive, false)
|
||||
got := multipartFieldEntry(nil, "", f)
|
||||
got := multipartFieldEntry(f)
|
||||
require.Contains(t, got, `FormatBool`)
|
||||
require.Contains(t, got, "if p.")
|
||||
}
|
||||
|
||||
func TestMultipartFieldEntry_Float64Required(t *testing.T) {
|
||||
f := makeField("Latitude", "latitude", "float64", spec.KindPrimitive, true)
|
||||
got := multipartFieldEntry(nil, "", f)
|
||||
got := multipartFieldEntry(f)
|
||||
require.Contains(t, got, `FormatFloat`)
|
||||
require.NotContains(t, got, "if p.")
|
||||
}
|
||||
|
||||
func TestMultipartFieldEntry_Float64Optional(t *testing.T) {
|
||||
f := makeField("Longitude", "longitude", "float64", spec.KindPrimitive, false)
|
||||
got := multipartFieldEntry(nil, "", f)
|
||||
got := multipartFieldEntry(f)
|
||||
require.Contains(t, got, `FormatFloat`)
|
||||
require.Contains(t, got, "if p.")
|
||||
}
|
||||
|
||||
func TestMultipartFieldEntry_OneOf_ChatIDRequired(t *testing.T) {
|
||||
f := makeFieldVariants("ChatID", "chat_id", spec.KindOneOf, []string{"int64", "string"}, true)
|
||||
got := multipartFieldEntry(nil, "", f)
|
||||
got := multipartFieldEntry(f)
|
||||
require.Contains(t, got, `.String()`)
|
||||
require.NotContains(t, got, "IsZero")
|
||||
}
|
||||
|
||||
func TestMultipartFieldEntry_OneOf_ChatIDOptional(t *testing.T) {
|
||||
f := makeFieldVariants("ChatID", "chat_id", spec.KindOneOf, []string{"int64", "string"}, false)
|
||||
got := multipartFieldEntry(nil, "", f)
|
||||
got := multipartFieldEntry(f)
|
||||
require.Contains(t, got, `IsZero`)
|
||||
}
|
||||
|
||||
func TestMultipartFieldEntry_OneOf_InputFileOrString(t *testing.T) {
|
||||
f := makeFieldVariants("Photo", "photo", spec.KindOneOf, []string{"InputFile", "string"}, false)
|
||||
got := multipartFieldEntry(nil, "", f)
|
||||
got := multipartFieldEntry(f)
|
||||
require.Contains(t, got, `PathOrID`)
|
||||
}
|
||||
|
||||
func TestMultipartFieldEntry_OneOf_SealedRequired(t *testing.T) {
|
||||
f := makeFieldVariants("Markup", "reply_markup", spec.KindOneOf, []string{"A", "B"}, true)
|
||||
got := multipartFieldEntry(nil, "", f)
|
||||
got := multipartFieldEntry(f)
|
||||
require.Contains(t, got, `json.Marshal`)
|
||||
}
|
||||
|
||||
func TestMultipartFieldEntry_OneOf_SealedOptional(t *testing.T) {
|
||||
f := makeFieldVariants("Markup", "reply_markup", spec.KindOneOf, []string{"A", "B"}, false)
|
||||
got := multipartFieldEntry(nil, "", f)
|
||||
got := multipartFieldEntry(f)
|
||||
require.Contains(t, got, `json.Marshal`)
|
||||
require.Contains(t, got, "if p.Markup")
|
||||
}
|
||||
|
||||
func TestMultipartFieldEntry_Named_Required(t *testing.T) {
|
||||
f := makeField("Entities", "entities", "MessageEntity", spec.KindNamed, true)
|
||||
got := multipartFieldEntry(nil, "", f)
|
||||
got := multipartFieldEntry(f)
|
||||
require.Contains(t, got, `json.Marshal`)
|
||||
require.NotContains(t, got, "if p.")
|
||||
}
|
||||
|
||||
func TestMultipartFieldEntry_Named_Optional(t *testing.T) {
|
||||
f := makeField("Entities", "entities", "MessageEntity", spec.KindNamed, false)
|
||||
got := multipartFieldEntry(nil, "", f)
|
||||
got := multipartFieldEntry(f)
|
||||
require.Contains(t, got, `json.Marshal`)
|
||||
require.Contains(t, got, "if p.")
|
||||
}
|
||||
@@ -574,7 +574,7 @@ func TestSentinelForField(t *testing.T) {
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
got := sentinelForField(c.field, unionTypes, nil)
|
||||
got := sentinelForField(c.field, unionTypes)
|
||||
require.Contains(t, got, c.contains, "sentinelForField for %q", c.name)
|
||||
})
|
||||
}
|
||||
@@ -637,7 +637,7 @@ func TestUnionTypeFor_OneOfNoMatch(t *testing.T) {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestFuncs_HasExpectedKeys(t *testing.T) {
|
||||
fm := funcs(nil)
|
||||
fm := funcs()
|
||||
require.NotNil(t, fm)
|
||||
for _, key := range []string{"goType", "docComment", "returnGoType", "unionFields"} {
|
||||
require.NotNil(t, fm[key], "funcs() missing key %q", key)
|
||||
|
||||
@@ -20,7 +20,7 @@ var _ = json.Marshal // keep import for complex multipart fields
|
||||
//
|
||||
{{docComment .Doc -}}
|
||||
type {{title .Name}}Params struct {
|
||||
{{range .Params}}{{docComment .Doc}} {{goField "" .}}
|
||||
{{range .Params}}{{docComment .Doc}} {{goField .}}
|
||||
{{end}}}
|
||||
{{if .HasFiles}}
|
||||
// HasFile reports whether a multipart upload is required.
|
||||
@@ -31,7 +31,7 @@ func (p *{{title .Name}}Params) HasFile() bool {
|
||||
// MultipartFields returns the non-file fields used in the multipart body.
|
||||
func (p *{{title .Name}}Params) MultipartFields() map[string]string {
|
||||
out := map[string]string{}
|
||||
{{range .Params}}{{if not (isFileField .)}}{{multipartFieldEntry "" .}}{{end}}{{end}} return out
|
||||
{{range .Params}}{{if not (isFileField .)}}{{multipartFieldEntry .}}{{end}}{{end}} return out
|
||||
}
|
||||
|
||||
// MultipartFiles returns the file parts.
|
||||
|
||||
@@ -82,7 +82,7 @@ func UnmarshalMaybeInaccessibleMessage(data []byte) (MaybeInaccessibleMessage, e
|
||||
{{else}}
|
||||
{{docComment .Doc -}}
|
||||
type {{.Name}} struct {
|
||||
{{range .Fields}}{{docComment .Doc}}{{goField $td.Name .}}
|
||||
{{range .Fields}}{{docComment .Doc}}{{goField .}}
|
||||
{{end}}}
|
||||
{{$unionFields := unionFields .}}{{if $unionFields}}
|
||||
// UnmarshalJSON decodes {{.Name}} by dispatching union-typed fields
|
||||
|
||||
@@ -1,205 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// extractEnumValues inspects a field-description string and returns the
|
||||
// list of wire-level string values when the description matches one of
|
||||
// the enum-like patterns Telegram uses in its docs. Order follows doc
|
||||
// order; duplicates are removed but order of first occurrence is kept.
|
||||
//
|
||||
// Handled patterns (curly quotes “…” are required to avoid false
|
||||
// positives on free-text quoting):
|
||||
//
|
||||
// - "Type of the chat, can be either “private”, “group”, … or “channel”"
|
||||
// - "Currently, can be “mention”, “hashtag”, …"
|
||||
// - "Currently, one of “XTR” … or “TON” …"
|
||||
// - "Currently, must be one of “XTR” …"
|
||||
// - "Currently, it can be one of “pending”, “approved”, “declined”."
|
||||
// - "Must be one of “danger” …, “success” …"
|
||||
// - "Must be one of “image/jpeg”, “image/gif”, or “video/mp4”"
|
||||
// - "Format … must be one of “static” …, “animated” …, “video” …"
|
||||
// - "Currently, either “upgrade” …, “transfer” …, “resale” …"
|
||||
// - "..., always “creator”"
|
||||
// - parse_mode parameter special case ("Mode for parsing entities …")
|
||||
// emits the canonical Markdown / MarkdownV2 / HTML triple.
|
||||
//
|
||||
// Returns nil when the description does not look like an enum.
|
||||
func extractEnumValues(jsonName, desc string) []string {
|
||||
if values := parseModeEnumValues(jsonName, desc); values != nil {
|
||||
return values
|
||||
}
|
||||
|
||||
trigger, triggerEnd, isAlways := findEnumTrigger(desc)
|
||||
if trigger < 0 {
|
||||
return nil
|
||||
}
|
||||
tail := desc[trigger:]
|
||||
|
||||
values := collectQuotedValues(tail)
|
||||
if len(values) == 0 {
|
||||
return nil
|
||||
}
|
||||
// First quoted value must sit close to the trigger phrase (e.g.
|
||||
// "can be “private”…"). Phrasings like "can be available only for
|
||||
// “invoice_payment”…" introduce a referenced value, not an enum,
|
||||
// and the gap between trigger end and first quote rules them out.
|
||||
firstQuote := strings.Index(desc[triggerEnd:], "“")
|
||||
if firstQuote < 0 {
|
||||
return nil
|
||||
}
|
||||
gap := desc[triggerEnd : triggerEnd+firstQuote]
|
||||
// Allow "always " as a permitted bridge (e.g. "Currently, always
|
||||
// “XTR”") and promote the match to single-value form.
|
||||
if strings.Contains(strings.ToLower(gap), "always ") {
|
||||
isAlways = true
|
||||
} else if firstQuote > 8 {
|
||||
return nil
|
||||
}
|
||||
// Single-value matches are only credible after "always". Multi-
|
||||
// value matches are credible after any trigger; the trigger phrase
|
||||
// already constrained the context.
|
||||
if !isAlways && len(values) < 2 {
|
||||
return nil
|
||||
}
|
||||
for _, v := range values {
|
||||
if !looksLikeEnumValue(v) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return dedupeStrings(values)
|
||||
}
|
||||
|
||||
// parseMode parameters do not list values inline — Telegram links to a
|
||||
// separate "formatting options" section. We hardcode the canonical set
|
||||
// here so callers get a typed ParseMode without writing magic strings.
|
||||
func parseModeEnumValues(jsonName, desc string) []string {
|
||||
if !strings.HasSuffix(jsonName, "parse_mode") {
|
||||
return nil
|
||||
}
|
||||
if !strings.Contains(desc, "Mode for parsing entities") {
|
||||
return nil
|
||||
}
|
||||
return []string{"Markdown", "MarkdownV2", "HTML"}
|
||||
}
|
||||
|
||||
// enumTriggers are anchor phrases that introduce a list of valid wire
|
||||
// values. Order matches longest-prefix priority; the matcher uses the
|
||||
// earliest match in the description.
|
||||
var enumTriggers = []string{
|
||||
"can be either ",
|
||||
"can be one of ",
|
||||
"can be ",
|
||||
"must be one of ",
|
||||
"must be ",
|
||||
"currently one of ",
|
||||
"currently, one of ",
|
||||
"currently, either ",
|
||||
"currently, must be one of ",
|
||||
"currently, can be ",
|
||||
"currently, it can be one of ",
|
||||
"currently, ",
|
||||
"one of ",
|
||||
"either ",
|
||||
"always ",
|
||||
}
|
||||
|
||||
// findEnumTrigger returns the byte offset where the first enum trigger
|
||||
// phrase begins, the offset just past the phrase, and whether the
|
||||
// trigger is the single-value "always" form. Returns (-1, -1, false)
|
||||
// when no trigger matches. Matching is case-insensitive so "Currently"
|
||||
// and "currently" both fire.
|
||||
func findEnumTrigger(desc string) (int, int, bool) {
|
||||
lower := strings.ToLower(desc)
|
||||
bestStart := -1
|
||||
bestEnd := -1
|
||||
bestAlways := false
|
||||
for _, t := range enumTriggers {
|
||||
i := strings.Index(lower, t)
|
||||
if i < 0 {
|
||||
continue
|
||||
}
|
||||
if bestStart != -1 && i >= bestStart {
|
||||
// Earlier-trigger wins outright; on a tie, the longer trigger
|
||||
// (which we visit first) already populated bestEnd.
|
||||
continue
|
||||
}
|
||||
bestStart = i
|
||||
bestEnd = i + len(t)
|
||||
bestAlways = t == "always "
|
||||
}
|
||||
return bestStart, bestEnd, bestAlways
|
||||
}
|
||||
|
||||
// quotedRE matches a curly-quoted token: “value”.
|
||||
var quotedRE = regexp.MustCompile(`“([^”]*)”`)
|
||||
|
||||
// collectQuotedValues returns the contents of every “…” pair in s in
|
||||
// order. Multi-line is fine; the docs use single-paragraph cells.
|
||||
func collectQuotedValues(s string) []string {
|
||||
matches := quotedRE.FindAllStringSubmatch(s, -1)
|
||||
out := make([]string, 0, len(matches))
|
||||
for _, m := range matches {
|
||||
out = append(out, m[1])
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// looksLikeEnumValue returns true for short identifiers that fit the
|
||||
// shape of a Telegram wire enum. This rules out values like
|
||||
// "attach://…", "h264", arbitrary URLs, and stylised punctuation.
|
||||
//
|
||||
// Permitted shapes:
|
||||
//
|
||||
// a-z0-9_ (e.g. "private", "bot_command")
|
||||
// A-Z0-9_ (e.g. "XTR", "TON", "MarkdownV2")
|
||||
// mixed case incl. "/" once (e.g. "image/jpeg", "video/mp4")
|
||||
func looksLikeEnumValue(v string) bool {
|
||||
if v == "" || len(v) > 64 {
|
||||
return false
|
||||
}
|
||||
if strings.Contains(v, "://") || strings.Contains(v, " ") {
|
||||
return false
|
||||
}
|
||||
// "image/jpeg"-style mime types: at most one slash, both halves alnum.
|
||||
if i := strings.Index(v, "/"); i >= 0 {
|
||||
if strings.Count(v, "/") > 1 {
|
||||
return false
|
||||
}
|
||||
left, right := v[:i], v[i+1:]
|
||||
return isIdent(left) && isIdent(right)
|
||||
}
|
||||
return isIdent(v)
|
||||
}
|
||||
|
||||
func isIdent(s string) bool {
|
||||
if s == "" {
|
||||
return false
|
||||
}
|
||||
for _, r := range s {
|
||||
switch {
|
||||
case r >= 'a' && r <= 'z':
|
||||
case r >= 'A' && r <= 'Z':
|
||||
case r >= '0' && r <= '9':
|
||||
case r == '_' || r == '-' || r == '.':
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func dedupeStrings(in []string) []string {
|
||||
seen := make(map[string]struct{}, len(in))
|
||||
out := make([]string, 0, len(in))
|
||||
for _, s := range in {
|
||||
if _, ok := seen[s]; ok {
|
||||
continue
|
||||
}
|
||||
seen[s] = struct{}{}
|
||||
out = append(out, s)
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestExtractEnumValues_CanBeEither(t *testing.T) {
|
||||
desc := "Type of the chat, can be either “private”, “group”, “supergroup” or “channel”"
|
||||
got := extractEnumValues("type", desc)
|
||||
require.Equal(t, []string{"private", "group", "supergroup", "channel"}, got)
|
||||
}
|
||||
|
||||
func TestExtractEnumValues_CurrentlyCanBe(t *testing.T) {
|
||||
desc := "Type of the entity. Currently, can be “mention” (@username), “hashtag” (#hashtag), or “code” (monowidth string)"
|
||||
got := extractEnumValues("type", desc)
|
||||
require.Equal(t, []string{"mention", "hashtag", "code"}, got)
|
||||
}
|
||||
|
||||
func TestExtractEnumValues_AlwaysSingle(t *testing.T) {
|
||||
desc := "Type of the message origin, always “user”"
|
||||
got := extractEnumValues("type", desc)
|
||||
require.Equal(t, []string{"user"}, got)
|
||||
}
|
||||
|
||||
func TestExtractEnumValues_MustBeOneOfMime(t *testing.T) {
|
||||
desc := "Optional. MIME type of the thumbnail, must be one of “image/jpeg”, “image/gif”, or “video/mp4”. Defaults to “image/jpeg”"
|
||||
got := extractEnumValues("thumbnail_mime_type", desc)
|
||||
require.Equal(t, []string{"image/jpeg", "image/gif", "video/mp4"}, got)
|
||||
}
|
||||
|
||||
func TestExtractEnumValues_ParseModeSpecial(t *testing.T) {
|
||||
desc := "Optional. Mode for parsing entities in the message text. See formatting options for more details."
|
||||
got := extractEnumValues("parse_mode", desc)
|
||||
require.Equal(t, []string{"Markdown", "MarkdownV2", "HTML"}, got)
|
||||
}
|
||||
|
||||
func TestExtractEnumValues_QuestionParseMode(t *testing.T) {
|
||||
desc := "Mode for parsing entities in the question. See formatting options for more details."
|
||||
got := extractEnumValues("question_parse_mode", desc)
|
||||
require.Equal(t, []string{"Markdown", "MarkdownV2", "HTML"}, got)
|
||||
}
|
||||
|
||||
func TestExtractEnumValues_FalsePositiveReferencedValue(t *testing.T) {
|
||||
// "Can be available only for "X"" is NOT an enum: the quote is a
|
||||
// reference to a transaction-type value, not an introduced list.
|
||||
desc := "Optional. Bot-specified invoice payload. Can be available only for “invoice_payment” transactions."
|
||||
got := extractEnumValues("invoice_payload", desc)
|
||||
require.Nil(t, got)
|
||||
}
|
||||
|
||||
func TestExtractEnumValues_FalsePositiveSingleQuotedNonEnum(t *testing.T) {
|
||||
// "can be ignored" with a quoted reference value later — not an enum.
|
||||
desc := "Optional. Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side."
|
||||
got := extractEnumValues("thumbnail", desc)
|
||||
require.Nil(t, got)
|
||||
}
|
||||
|
||||
func TestExtractEnumValues_RefundedPaymentCurrentlyAlways(t *testing.T) {
|
||||
desc := "Three-letter ISO 4217 currency code, or “XTR” for payments in Telegram Stars. Currently, always “XTR”"
|
||||
got := extractEnumValues("currency", desc)
|
||||
require.Equal(t, []string{"XTR"}, got)
|
||||
}
|
||||
|
||||
func TestExtractEnumValues_RejectURLValues(t *testing.T) {
|
||||
// "attach://<file_attach_name>" must never be promoted to an enum value.
|
||||
desc := "Pass “attach://<file_attach_name>” to upload a new one"
|
||||
got := extractEnumValues("media", desc)
|
||||
require.Nil(t, got)
|
||||
}
|
||||
|
||||
func TestExtractEnumValues_StringTypeOnly(t *testing.T) {
|
||||
// (Sanity — table.go gates on string type, but the function itself
|
||||
// should still respond consistently.)
|
||||
desc := "ABC, can be “a”, “b”"
|
||||
got := extractEnumValues("x", desc)
|
||||
require.Equal(t, []string{"a", "b"}, got)
|
||||
}
|
||||
|
||||
func TestExtractEnumValues_DedupeRepeatedValues(t *testing.T) {
|
||||
desc := "Currently, one of “XTR” for Telegram Stars or “XTR” again"
|
||||
got := extractEnumValues("currency", desc)
|
||||
require.Equal(t, []string{"XTR"}, got)
|
||||
}
|
||||
+10
-22
@@ -30,18 +30,12 @@ func parseFieldsTable(t *html.Node) []spec.Field {
|
||||
desc := strings.TrimSpace(textOf(cells[2]))
|
||||
|
||||
required := !strings.HasPrefix(desc, "Optional.")
|
||||
tref := parseTypeRef(typeText)
|
||||
var enumVals []string
|
||||
if tref.Kind == spec.KindPrimitive && tref.Name == "string" {
|
||||
enumVals = extractEnumValues(jname, desc)
|
||||
}
|
||||
fields = append(fields, spec.Field{
|
||||
Name: goName(jname),
|
||||
JSONName: jname,
|
||||
Type: tref,
|
||||
Required: required,
|
||||
Doc: desc,
|
||||
EnumValues: enumVals,
|
||||
Name: goName(jname),
|
||||
JSONName: jname,
|
||||
Type: parseTypeRef(typeText),
|
||||
Required: required,
|
||||
Doc: desc,
|
||||
})
|
||||
}
|
||||
return fields
|
||||
@@ -65,18 +59,12 @@ func parseParamsTable(t *html.Node) []spec.Field {
|
||||
req := strings.EqualFold(strings.TrimSpace(textOf(cells[2])), "Yes")
|
||||
desc := strings.TrimSpace(textOf(cells[3]))
|
||||
|
||||
tref := parseTypeRef(typeText)
|
||||
var enumVals []string
|
||||
if tref.Kind == spec.KindPrimitive && tref.Name == "string" {
|
||||
enumVals = extractEnumValues(jname, desc)
|
||||
}
|
||||
params = append(params, spec.Field{
|
||||
Name: goName(jname),
|
||||
JSONName: jname,
|
||||
Type: tref,
|
||||
Required: req,
|
||||
Doc: desc,
|
||||
EnumValues: enumVals,
|
||||
Name: goName(jname),
|
||||
JSONName: jname,
|
||||
Type: parseTypeRef(typeText),
|
||||
Required: req,
|
||||
Doc: desc,
|
||||
})
|
||||
}
|
||||
return params
|
||||
|
||||
@@ -15,17 +15,17 @@ func NewStatus(s string) dispatch.Filter[*api.ChatMemberUpdated] {
|
||||
}
|
||||
switch m := u.NewChatMember.(type) {
|
||||
case *api.ChatMemberOwner:
|
||||
return string(m.Status) == s
|
||||
return m.Status == s
|
||||
case *api.ChatMemberAdministrator:
|
||||
return string(m.Status) == s
|
||||
return m.Status == s
|
||||
case *api.ChatMemberMember:
|
||||
return string(m.Status) == s
|
||||
return m.Status == s
|
||||
case *api.ChatMemberRestricted:
|
||||
return string(m.Status) == s
|
||||
return m.Status == s
|
||||
case *api.ChatMemberLeft:
|
||||
return string(m.Status) == s
|
||||
return m.Status == s
|
||||
case *api.ChatMemberBanned:
|
||||
return string(m.Status) == s
|
||||
return m.Status == s
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -12,15 +12,15 @@ func memberUpdate(status string, fromID int64) *api.ChatMemberUpdated {
|
||||
var newMember api.ChatMember
|
||||
switch status {
|
||||
case "member":
|
||||
newMember = &api.ChatMemberMember{Status: api.ChatMemberMemberStatusMember}
|
||||
newMember = &api.ChatMemberMember{Status: status}
|
||||
case "administrator":
|
||||
newMember = &api.ChatMemberAdministrator{Status: api.ChatMemberAdministratorStatusAdministrator}
|
||||
newMember = &api.ChatMemberAdministrator{Status: status}
|
||||
case "kicked":
|
||||
newMember = &api.ChatMemberBanned{Status: api.ChatMemberBannedStatusKicked}
|
||||
newMember = &api.ChatMemberBanned{Status: status}
|
||||
case "left":
|
||||
newMember = &api.ChatMemberLeft{Status: api.ChatMemberLeftStatusLeft}
|
||||
newMember = &api.ChatMemberLeft{Status: status}
|
||||
default:
|
||||
newMember = &api.ChatMemberMember{Status: api.ChatMemberMemberStatusMember}
|
||||
newMember = &api.ChatMemberMember{Status: status}
|
||||
}
|
||||
return &api.ChatMemberUpdated{
|
||||
From: api.User{ID: fromID},
|
||||
@@ -70,7 +70,7 @@ func TestComposedFilters(t *testing.T) {
|
||||
func TestNewStatus_Owner(t *testing.T) {
|
||||
u := &api.ChatMemberUpdated{
|
||||
From: api.User{ID: 1},
|
||||
NewChatMember: &api.ChatMemberOwner{Status: api.ChatMemberOwnerStatusCreator},
|
||||
NewChatMember: &api.ChatMemberOwner{Status: "creator"},
|
||||
}
|
||||
require.True(t, cmfilter.NewStatus("creator")(u))
|
||||
require.False(t, cmfilter.NewStatus("member")(u))
|
||||
@@ -79,7 +79,7 @@ func TestNewStatus_Owner(t *testing.T) {
|
||||
func TestNewStatus_Restricted(t *testing.T) {
|
||||
u := &api.ChatMemberUpdated{
|
||||
From: api.User{ID: 1},
|
||||
NewChatMember: &api.ChatMemberRestricted{Status: api.ChatMemberRestrictedStatusRestricted},
|
||||
NewChatMember: &api.ChatMemberRestricted{Status: "restricted"},
|
||||
}
|
||||
require.True(t, cmfilter.NewStatus("restricted")(u))
|
||||
require.False(t, cmfilter.NewStatus("member")(u))
|
||||
|
||||
@@ -48,7 +48,7 @@ func Command(name string) dispatch.Filter[*api.Message] {
|
||||
return false
|
||||
}
|
||||
first := m.Entities[0]
|
||||
if first.Type != api.MessageEntityTypeBotCommand || first.Offset != 0 {
|
||||
if first.Type != string(api.EntityBotCommand) || first.Offset != 0 {
|
||||
return false
|
||||
}
|
||||
end := int(first.Length)
|
||||
@@ -72,7 +72,7 @@ func AnyCommand() dispatch.Filter[*api.Message] {
|
||||
return false
|
||||
}
|
||||
first := m.Entities[0]
|
||||
return first.Type == api.MessageEntityTypeBotCommand && first.Offset == 0
|
||||
return first.Type == string(api.EntityBotCommand) && first.Offset == 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,8 +105,8 @@ func HasDocument() dispatch.Filter[*api.Message] {
|
||||
}
|
||||
|
||||
// HasEntity returns a Filter that matches messages whose Entities contain at
|
||||
// least one entity of type t (e.g. api.MessageEntityTypeBotCommand).
|
||||
func HasEntity(t api.MessageEntityType) dispatch.Filter[*api.Message] {
|
||||
// least one entity of type t (e.g. string(api.EntityBotCommand)).
|
||||
func HasEntity(t string) dispatch.Filter[*api.Message] {
|
||||
return func(m *api.Message) bool {
|
||||
if m == nil {
|
||||
return false
|
||||
@@ -123,7 +123,7 @@ func HasEntity(t api.MessageEntityType) dispatch.Filter[*api.Message] {
|
||||
// ChatType returns a Filter that matches messages whose Chat.Type equals t.
|
||||
func ChatType(t api.ChatType) dispatch.Filter[*api.Message] {
|
||||
return func(m *api.Message) bool {
|
||||
return m != nil && m.Chat.Type == t
|
||||
return m != nil && m.Chat.Type == string(t)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
func msg(text string) *api.Message {
|
||||
return &api.Message{
|
||||
MessageID: 1,
|
||||
Chat: api.Chat{ID: 1, Type: api.ChatTypePrivate},
|
||||
Chat: api.Chat{ID: 1, Type: string(api.ChatTypePrivate)},
|
||||
Text: text,
|
||||
}
|
||||
}
|
||||
@@ -20,10 +20,10 @@ func cmdMsg(cmd string) *api.Message {
|
||||
text := cmd
|
||||
return &api.Message{
|
||||
MessageID: 1,
|
||||
Chat: api.Chat{ID: 1, Type: api.ChatTypePrivate},
|
||||
Chat: api.Chat{ID: 1, Type: string(api.ChatTypePrivate)},
|
||||
Text: text,
|
||||
Entities: []api.MessageEntity{
|
||||
{Type: api.MessageEntityTypeBotCommand, Offset: 0, Length: int64(len([]rune(text)))},
|
||||
{Type: string(api.EntityBotCommand), Offset: 0, Length: int64(len([]rune(text)))},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -72,7 +72,7 @@ func TestCommand(t *testing.T) {
|
||||
t.Run("strips BotName suffix", func(t *testing.T) {
|
||||
m := &api.Message{
|
||||
Text: "/start@MyBot",
|
||||
Entities: []api.MessageEntity{{Type: api.MessageEntityTypeBotCommand, Offset: 0, Length: 12}},
|
||||
Entities: []api.MessageEntity{{Type: string(api.EntityBotCommand), Offset: 0, Length: 12}},
|
||||
}
|
||||
f := msgfilter.Command("/start")
|
||||
require.True(t, f(m))
|
||||
@@ -134,9 +134,9 @@ func TestHasDocument(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestHasEntity(t *testing.T) {
|
||||
f := msgfilter.HasEntity(api.MessageEntityTypeURL)
|
||||
f := msgfilter.HasEntity(string(api.EntityURL))
|
||||
m := msg("check https://example.com")
|
||||
m.Entities = []api.MessageEntity{{Type: api.MessageEntityTypeURL, Offset: 6, Length: 19}}
|
||||
m.Entities = []api.MessageEntity{{Type: string(api.EntityURL), Offset: 6, Length: 19}}
|
||||
require.True(t, f(m))
|
||||
require.False(t, f(msg("plain")))
|
||||
require.False(t, f(nil))
|
||||
@@ -148,7 +148,7 @@ func TestChatType(t *testing.T) {
|
||||
require.True(t, f(private))
|
||||
|
||||
group := msg("hi")
|
||||
group.Chat.Type = api.ChatTypeGroup
|
||||
group.Chat.Type = string(api.ChatTypeGroup)
|
||||
require.False(t, f(group))
|
||||
require.False(t, f(nil))
|
||||
}
|
||||
@@ -183,6 +183,6 @@ func TestComposedMessageFilters(t *testing.T) {
|
||||
require.True(t, f(m))
|
||||
|
||||
m2 := msg("say hello")
|
||||
m2.Chat.Type = api.ChatTypeGroup
|
||||
m2.Chat.Type = string(api.ChatTypeGroup)
|
||||
require.False(t, f(m2))
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ func msgUpdate(id int64, text string) api.Update {
|
||||
UpdateID: id,
|
||||
Message: &api.Message{
|
||||
MessageID: id,
|
||||
Chat: api.Chat{ID: 1, Type: api.ChatTypePrivate},
|
||||
Chat: api.Chat{ID: 1, Type: string(api.ChatTypePrivate)},
|
||||
Text: text,
|
||||
},
|
||||
}
|
||||
@@ -29,10 +29,10 @@ func cmdUpdate(id int64, cmd string) api.Update {
|
||||
UpdateID: id,
|
||||
Message: &api.Message{
|
||||
MessageID: id,
|
||||
Chat: api.Chat{ID: 1, Type: api.ChatTypePrivate},
|
||||
Chat: api.Chat{ID: 1, Type: string(api.ChatTypePrivate)},
|
||||
Text: cmd,
|
||||
Entities: []api.MessageEntity{
|
||||
{Type: api.MessageEntityTypeBotCommand, Offset: 0, Length: int64(len(cmd))},
|
||||
{Type: string(api.EntityBotCommand), Offset: 0, Length: int64(len(cmd))},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
+1
-1
@@ -516,7 +516,7 @@ func extractCommand(m *api.Message) (cmd, args string, ok bool) {
|
||||
return "", "", false
|
||||
}
|
||||
first := m.Entities[0]
|
||||
if first.Type != api.MessageEntityTypeBotCommand || first.Offset != 0 {
|
||||
if first.Type != string(api.EntityBotCommand) || first.Offset != 0 {
|
||||
return "", "", false
|
||||
}
|
||||
cmd, sliceOk := utf16Slice(m.Text, int(first.Offset), int(first.Length))
|
||||
|
||||
+15
-15
@@ -31,9 +31,9 @@ func cmdMessage(text string) api.Update {
|
||||
return api.Update{
|
||||
UpdateID: 1,
|
||||
Message: &api.Message{
|
||||
MessageID: 1, Date: 0, Chat: api.Chat{ID: 1, Type: api.ChatTypePrivate},
|
||||
MessageID: 1, Date: 0, Chat: api.Chat{ID: 1, Type: string(api.ChatTypePrivate)},
|
||||
Text: text,
|
||||
Entities: []api.MessageEntity{{Type: api.MessageEntityTypeBotCommand, Offset: 0, Length: int64(indexEnd(text))}},
|
||||
Entities: []api.MessageEntity{{Type: string(api.EntityBotCommand), Offset: 0, Length: int64(indexEnd(text))}},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -153,10 +153,10 @@ func TestRouter_NonASCIICommand(t *testing.T) {
|
||||
UpdateID: 1,
|
||||
Message: &api.Message{
|
||||
MessageID: 1,
|
||||
Chat: api.Chat{ID: 1, Type: api.ChatTypePrivate},
|
||||
Chat: api.Chat{ID: 1, Type: string(api.ChatTypePrivate)},
|
||||
Text: text,
|
||||
Entities: []api.MessageEntity{
|
||||
{Type: api.MessageEntityTypeBotCommand, Offset: 0, Length: cmdU16Len},
|
||||
{Type: string(api.EntityBotCommand), Offset: 0, Length: cmdU16Len},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -196,7 +196,7 @@ func TestRouter_CommandValuesNotLeakedOnNoMatch(t *testing.T) {
|
||||
u := api.Update{UpdateID: 1, Message: &api.Message{
|
||||
MessageID: 1, Chat: api.Chat{ID: 1, Type: "private"},
|
||||
Text: "/unknown",
|
||||
Entities: []api.MessageEntity{{Type: api.MessageEntityTypeBotCommand, Offset: 0, Length: 8}},
|
||||
Entities: []api.MessageEntity{{Type: string(api.EntityBotCommand), Offset: 0, Length: 8}},
|
||||
}}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
|
||||
@@ -247,7 +247,7 @@ func TestRouter_OnChannelPost(t *testing.T) {
|
||||
})
|
||||
|
||||
u := api.Update{UpdateID: 1, ChannelPost: &api.Message{
|
||||
MessageID: 99, Chat: api.Chat{ID: -100, Type: api.ChatTypeChannel},
|
||||
MessageID: 99, Chat: api.Chat{ID: -100, Type: string(api.ChatTypeChannel)},
|
||||
}}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
|
||||
defer cancel()
|
||||
@@ -393,7 +393,7 @@ func TestRouter_OnInlineQueryFilter_Matches(t *testing.T) {
|
||||
func TestRouter_FilterChain_Composition(t *testing.T) {
|
||||
// Filter: private chat AND text contains "hello"
|
||||
privateChat := Filter[*api.Message](func(m *api.Message) bool {
|
||||
return m != nil && m.Chat.Type == api.ChatTypePrivate
|
||||
return m != nil && m.Chat.Type == string(api.ChatTypePrivate)
|
||||
})
|
||||
hasHello := Filter[*api.Message](func(m *api.Message) bool {
|
||||
return m != nil && len(m.Text) > 0 && containsStr(m.Text, "hello")
|
||||
@@ -405,10 +405,10 @@ func TestRouter_FilterChain_Composition(t *testing.T) {
|
||||
r.OnMessageFilter(combined, func(c *Context, m *api.Message) error { hit <- m.Text; return nil })
|
||||
|
||||
match := api.Update{UpdateID: 1, Message: &api.Message{
|
||||
MessageID: 1, Chat: api.Chat{ID: 1, Type: api.ChatTypePrivate}, Text: "say hello",
|
||||
MessageID: 1, Chat: api.Chat{ID: 1, Type: string(api.ChatTypePrivate)}, Text: "say hello",
|
||||
}}
|
||||
noMatch := api.Update{UpdateID: 2, Message: &api.Message{
|
||||
MessageID: 2, Chat: api.Chat{ID: 2, Type: api.ChatTypeGroup}, Text: "say hello",
|
||||
MessageID: 2, Chat: api.Chat{ID: 2, Type: string(api.ChatTypeGroup)}, Text: "say hello",
|
||||
}}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
|
||||
@@ -462,7 +462,7 @@ func TestRouter_ConcurrentDispatch_AllHandlersFire(t *testing.T) {
|
||||
for i := range ups {
|
||||
ups[i] = api.Update{UpdateID: int64(i + 1), Message: &api.Message{
|
||||
MessageID: int64(i + 1),
|
||||
Chat: api.Chat{ID: 1, Type: api.ChatTypePrivate},
|
||||
Chat: api.Chat{ID: 1, Type: string(api.ChatTypePrivate)},
|
||||
Text: "hi",
|
||||
}}
|
||||
}
|
||||
@@ -493,7 +493,7 @@ func TestRouter_ConcurrentDispatch_SemaphoreBoundsConcurrency(t *testing.T) {
|
||||
for i := range ups {
|
||||
ups[i] = api.Update{UpdateID: int64(i + 1), Message: &api.Message{
|
||||
MessageID: int64(i + 1),
|
||||
Chat: api.Chat{ID: 1, Type: api.ChatTypePrivate},
|
||||
Chat: api.Chat{ID: 1, Type: string(api.ChatTypePrivate)},
|
||||
Text: "hi",
|
||||
}}
|
||||
}
|
||||
@@ -555,7 +555,7 @@ func TestRouter_ConcurrentDispatch_WaitsForInFlight(t *testing.T) {
|
||||
)
|
||||
|
||||
u := api.Update{UpdateID: 1, Message: &api.Message{
|
||||
MessageID: 1, Chat: api.Chat{ID: 1, Type: api.ChatTypePrivate}, Text: "hi",
|
||||
MessageID: 1, Chat: api.Chat{ID: 1, Type: string(api.ChatTypePrivate)}, Text: "hi",
|
||||
}}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
@@ -594,7 +594,7 @@ func TestRouter_SerialMode_NoRace(t *testing.T) {
|
||||
for i := range ups {
|
||||
ups[i] = api.Update{UpdateID: int64(i + 1), Message: &api.Message{
|
||||
MessageID: int64(i + 1),
|
||||
Chat: api.Chat{ID: 1, Type: api.ChatTypePrivate},
|
||||
Chat: api.Chat{ID: 1, Type: string(api.ChatTypePrivate)},
|
||||
Text: "hi",
|
||||
}}
|
||||
}
|
||||
@@ -908,7 +908,7 @@ func TestRouter_ContextCancel_UnblocksWaitingAcquire(t *testing.T) {
|
||||
for i := range limit {
|
||||
lu.Send(api.Update{UpdateID: int64(i + 1), Message: &api.Message{
|
||||
MessageID: int64(i + 1),
|
||||
Chat: api.Chat{ID: 1, Type: api.ChatTypePrivate},
|
||||
Chat: api.Chat{ID: 1, Type: string(api.ChatTypePrivate)},
|
||||
Text: "hi",
|
||||
}})
|
||||
}
|
||||
@@ -919,7 +919,7 @@ func TestRouter_ContextCancel_UnblocksWaitingAcquire(t *testing.T) {
|
||||
// Send one more update — Run will block trying to acquire the full semaphore.
|
||||
lu.Send(api.Update{UpdateID: int64(limit + 1), Message: &api.Message{
|
||||
MessageID: int64(limit + 1),
|
||||
Chat: api.Chat{ID: 1, Type: api.ChatTypePrivate},
|
||||
Chat: api.Chat{ID: 1, Type: string(api.ChatTypePrivate)},
|
||||
Text: "extra",
|
||||
}})
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
go-telegram.raczylo.com
|
||||
-962
@@ -1,962 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en" class="scroll-smooth">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>go-telegram — Strongly-typed Go client for the Telegram Bot API</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="A fully-generated, strongly-typed Go client for the Telegram Bot API. 176 methods, 1408 generated tests, zero any in the public surface."
|
||||
/>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
darkMode: 'class'
|
||||
}
|
||||
</script>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"
|
||||
/>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<style>
|
||||
body { font-family: "Inter", sans-serif; }
|
||||
code, pre { font-family: "JetBrains Mono", monospace; }
|
||||
.theme-transition {
|
||||
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
|
||||
}
|
||||
@keyframes fadeInUp {
|
||||
from { opacity: 0; transform: translateY(20px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
@keyframes float {
|
||||
0%, 100% { transform: translateY(0px); }
|
||||
50% { transform: translateY(-10px); }
|
||||
}
|
||||
.animate-fade-in-up { animation: fadeInUp 0.6s ease-out; }
|
||||
.animate-float { animation: float 3s ease-in-out infinite; }
|
||||
.glass {
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
.dark .glass {
|
||||
background: rgba(17, 24, 39, 0.7);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
.gradient-text {
|
||||
background: linear-gradient(135deg, #29B6F6 0%, #0288D1 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
.dark .gradient-text {
|
||||
background: linear-gradient(135deg, #4FC3F7 0%, #81D4FA 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
.shadow-modern { box-shadow: 0 10px 40px -10px rgba(0, 0, 0, 0.1); }
|
||||
.dark .shadow-modern { box-shadow: 0 10px 40px -10px rgba(0, 0, 0, 0.4); }
|
||||
html { scroll-behavior: smooth; }
|
||||
/* Syntax highlight tokens */
|
||||
.tok-kw { color: #c792ea; }
|
||||
.tok-fn { color: #82aaff; }
|
||||
.tok-str { color: #c3e88d; }
|
||||
.tok-cmt { color: #546e7a; font-style: italic; }
|
||||
.tok-pkg { color: #ffcb6b; }
|
||||
.tok-num { color: #f78c6c; }
|
||||
.tok-type { color: #4FC3F7; }
|
||||
</style>
|
||||
<script>
|
||||
if (localStorage.theme === "dark" || (!("theme" in localStorage) && window.matchMedia("(prefers-color-scheme: dark)").matches)) {
|
||||
document.documentElement.classList.add("dark");
|
||||
} else {
|
||||
document.documentElement.classList.remove("dark");
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body class="bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 theme-transition">
|
||||
|
||||
<!-- Navigation -->
|
||||
<nav class="fixed w-full glass shadow-modern z-50 theme-transition">
|
||||
<div class="max-w-6xl mx-auto px-4 sm:px-6">
|
||||
<div class="flex justify-between h-16 items-center">
|
||||
<a href="#" class="flex items-center hover:opacity-80 transition-opacity duration-300">
|
||||
<img src="logo-light.svg" alt="go-telegram logo" class="h-10 w-auto dark:hidden" />
|
||||
<img src="logo-dark.svg" alt="go-telegram logo" class="h-10 w-auto hidden dark:block" />
|
||||
</a>
|
||||
<div class="hidden md:flex space-x-6">
|
||||
<a href="#features" class="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 font-medium">Features</a>
|
||||
<a href="#comparison" class="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 font-medium">Comparison</a>
|
||||
<a href="#install" class="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 font-medium">Install</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="#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 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">
|
||||
<i class="fas fa-moon dark:hidden text-xl"></i>
|
||||
<i class="fas fa-sun hidden dark:inline text-xl"></i>
|
||||
</button>
|
||||
<a href="https://github.com/lukaszraczylo/go-telegram" target="_blank" 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="View on GitHub">
|
||||
<i class="fab fa-github text-xl"></i>
|
||||
</a>
|
||||
<button id="mobile-menu-toggle" class="md:hidden 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 menu">
|
||||
<i class="fas fa-bars text-xl" id="menu-open-icon"></i>
|
||||
<i class="fas fa-times text-xl hidden" id="menu-close-icon"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="mobile-menu" class="hidden md:hidden border-t border-gray-200 dark:border-gray-700">
|
||||
<div class="px-4 py-3 space-y-1 bg-white dark:bg-gray-800">
|
||||
<a href="#features" 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">Features</a>
|
||||
<a href="#comparison" 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">Comparison</a>
|
||||
<a href="#install" 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">Install</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="#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>
|
||||
</nav>
|
||||
|
||||
<!-- Hero Section -->
|
||||
<section class="relative pt-24 sm:pt-32 pb-12 sm:pb-20 overflow-hidden">
|
||||
<div class="absolute inset-0 bg-gradient-to-br from-sky-50 via-cyan-50 to-blue-50 dark:from-gray-900 dark:via-sky-900/20 dark:to-cyan-900/20 theme-transition"></div>
|
||||
<div class="absolute top-0 -left-4 w-72 h-72 bg-cyan-300 dark:bg-cyan-500 rounded-full mix-blend-multiply dark:mix-blend-soft-light filter blur-xl opacity-20 animate-float"></div>
|
||||
<div class="absolute top-0 -right-4 w-72 h-72 bg-sky-300 dark:bg-sky-500 rounded-full mix-blend-multiply dark:mix-blend-soft-light filter blur-xl opacity-20 animate-float" style="animation-delay: 1s;"></div>
|
||||
<div class="absolute -bottom-8 left-20 w-72 h-72 bg-blue-300 dark:bg-blue-500 rounded-full mix-blend-multiply dark:mix-blend-soft-light filter blur-xl opacity-20 animate-float" style="animation-delay: 2s;"></div>
|
||||
|
||||
<div class="relative max-w-6xl mx-auto px-4 sm:px-6">
|
||||
<div class="text-center">
|
||||
<div class="mb-6 sm:mb-8 flex justify-center animate-fade-in-up">
|
||||
<img src="logo-light.svg" alt="go-telegram logo" class="h-20 sm:h-24 md:h-32 w-auto dark:hidden" />
|
||||
<img src="logo-dark.svg" alt="go-telegram logo" class="h-20 sm:h-24 md:h-32 w-auto hidden dark:block" />
|
||||
</div>
|
||||
<h1 class="text-3xl sm:text-4xl md:text-5xl lg:text-6xl font-bold text-gray-900 dark:text-gray-100 mb-4 sm:mb-6 leading-tight animate-fade-in-up" style="animation-delay: 0.1s;">
|
||||
A fully-generated,<br /><span class="gradient-text">strongly-typed</span> Go client<br />for the Telegram Bot API
|
||||
</h1>
|
||||
<p class="text-base sm:text-lg md:text-xl text-gray-600 dark:text-gray-300 mb-8 sm:mb-10 max-w-2xl mx-auto leading-relaxed px-4 animate-fade-in-up" style="animation-delay: 0.2s;">
|
||||
176 methods. 1408 generated tests. Zero <code class="bg-gray-100 dark:bg-gray-800 px-1.5 py-0.5 rounded text-sm">any</code> in the public surface.
|
||||
</p>
|
||||
<div class="flex flex-col sm:flex-row gap-3 sm:gap-4 justify-center mb-8 sm:mb-10 px-4 animate-fade-in-up" style="animation-delay: 0.3s;">
|
||||
<a href="#install" class="group relative bg-gradient-to-r from-sky-500 to-cyan-600 hover:from-sky-600 hover:to-cyan-700 text-white px-8 py-3 rounded-lg font-medium transition-all duration-300 min-h-[48px] flex items-center justify-center shadow-lg hover:shadow-xl hover:scale-105">
|
||||
<i class="fas fa-rocket mr-2"></i><span class="relative z-10">Get started</span>
|
||||
</a>
|
||||
<a href="https://github.com/lukaszraczylo/go-telegram" target="_blank" class="group glass hover:shadow-lg text-gray-900 dark:text-gray-100 px-8 py-3 rounded-lg font-medium transition-all duration-300 min-h-[48px] flex items-center justify-center hover:scale-105">
|
||||
<i class="fab fa-github mr-2"></i>View on GitHub
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Stats row -->
|
||||
<div class="flex flex-wrap justify-center gap-x-3 gap-y-1 text-sm font-mono text-gray-500 dark:text-gray-400 mb-12 sm:mb-16 px-4 animate-fade-in-up" style="animation-delay: 0.35s;">
|
||||
<span>176 methods</span>
|
||||
<span class="text-gray-300 dark:text-gray-600">·</span>
|
||||
<span>301 types</span>
|
||||
<span class="text-gray-300 dark:text-gray-600">·</span>
|
||||
<span>1,408 tests</span>
|
||||
<span class="text-gray-300 dark:text-gray-600">·</span>
|
||||
<span>MIT licensed</span>
|
||||
</div>
|
||||
|
||||
<!-- Code preview card -->
|
||||
<div class="mt-4 max-w-2xl mx-auto px-4 animate-fade-in-up animate-float" style="animation-delay: 0.4s; animation-duration: 4s;">
|
||||
<div class="relative group">
|
||||
<div class="absolute -inset-1 bg-gradient-to-r from-sky-500 to-cyan-600 rounded-xl blur opacity-25 group-hover:opacity-50 transition duration-500"></div>
|
||||
<div class="relative bg-gradient-to-br from-gray-900 to-gray-800 rounded-xl p-5 text-left border border-gray-700">
|
||||
<div class="flex items-center gap-2 mb-4">
|
||||
<span class="w-3 h-3 rounded-full bg-red-500"></span>
|
||||
<span class="w-3 h-3 rounded-full bg-yellow-500"></span>
|
||||
<span class="w-3 h-3 rounded-full bg-green-500"></span>
|
||||
<span class="ml-2 text-xs text-gray-500 font-mono">echo_bot.go</span>
|
||||
</div>
|
||||
<pre class="text-sm text-gray-100 overflow-x-auto"><code class="font-mono"><span class="tok-kw">package</span> <span class="tok-pkg">main</span>
|
||||
|
||||
<span class="tok-kw">import</span> (
|
||||
<span class="tok-str">"context"</span>
|
||||
<span class="tok-str">"log"</span>
|
||||
<span class="tok-str">"os"</span>
|
||||
|
||||
<span class="tok-str">"github.com/lukaszraczylo/go-telegram/api"</span>
|
||||
<span class="tok-str">"github.com/lukaszraczylo/go-telegram/client"</span>
|
||||
<span class="tok-str">"github.com/lukaszraczylo/go-telegram/dispatch"</span>
|
||||
)
|
||||
|
||||
<span class="tok-kw">func</span> <span class="tok-fn">main</span>() {
|
||||
bot, _ := <span class="tok-pkg">client</span>.<span class="tok-fn">NewRetryDoer</span>(os.<span class="tok-fn">Getenv</span>(<span class="tok-str">"BOT_TOKEN"</span>), <span class="tok-kw">nil</span>)
|
||||
d := <span class="tok-pkg">dispatch</span>.<span class="tok-fn">New</span>(bot)
|
||||
|
||||
d.<span class="tok-fn">OnMessage</span>(<span class="tok-kw">func</span>(ctx <span class="tok-type">context.Context</span>, msg *<span class="tok-type">api.Message</span>) {
|
||||
bot.<span class="tok-fn">SendMessage</span>(ctx, &<span class="tok-type">api.SendMessageParams</span>{
|
||||
ChatID: <span class="tok-pkg">api</span>.<span class="tok-fn">ChatIDFromInt</span>(msg.Chat.ID),
|
||||
Text: msg.Text,
|
||||
})
|
||||
})
|
||||
|
||||
<span class="tok-kw">if</span> err := d.<span class="tok-fn">Run</span>(<span class="tok-pkg">context</span>.<span class="tok-fn">Background</span>()); err != <span class="tok-kw">nil</span> {
|
||||
<span class="tok-pkg">log</span>.<span class="tok-fn">Fatal</span>(err)
|
||||
}
|
||||
}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Features Section -->
|
||||
<section id="features" class="py-12 sm:py-16 md:py-20 bg-white dark:bg-gray-900 theme-transition">
|
||||
<div class="max-w-6xl mx-auto px-4 sm:px-6">
|
||||
<div class="text-center mb-8 sm:mb-12">
|
||||
<h2 class="text-2xl sm:text-3xl md:text-4xl font-bold text-gray-900 dark:text-gray-100 mb-3 sm:mb-4">Features</h2>
|
||||
<p class="text-base sm:text-lg text-gray-600 dark:text-gray-300 px-4">Built for correctness, composability, and production reliability</p>
|
||||
</div>
|
||||
<div class="grid sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<div class="glass p-5 rounded-xl group hover:shadow-lg transition-all duration-300">
|
||||
<div class="flex items-start gap-4">
|
||||
<div class="w-12 h-12 rounded-xl bg-gradient-to-br from-sky-500 to-sky-600 flex items-center justify-center flex-shrink-0 group-hover:scale-110 transition-transform duration-300">
|
||||
<i class="fas fa-code-branch text-white"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-1">Generated from the live docs</h3>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">IR + emitter pipeline runs weekly; a PR opens automatically for any Telegram-side change.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="glass p-5 rounded-xl group hover:shadow-lg transition-all duration-300">
|
||||
<div class="flex items-start gap-4">
|
||||
<div class="w-12 h-12 rounded-xl bg-gradient-to-br from-emerald-500 to-emerald-600 flex items-center justify-center flex-shrink-0 group-hover:scale-110 transition-transform duration-300">
|
||||
<i class="fas fa-shield-halved text-white"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-1">No <code class="text-xs bg-emerald-100 dark:bg-emerald-900 px-1 rounded">any</code> in the public API</h3>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">Typed unions — <code class="text-xs">ChatID</code>, <code class="text-xs">MessageOrBool</code> — sealed interfaces with marker methods and auto-decode.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="glass p-5 rounded-xl group hover:shadow-lg transition-all duration-300">
|
||||
<div class="flex items-start gap-4">
|
||||
<div class="w-12 h-12 rounded-xl bg-gradient-to-br from-purple-500 to-purple-600 flex items-center justify-center flex-shrink-0 group-hover:scale-110 transition-transform duration-300">
|
||||
<i class="fas fa-plug text-white"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-1">Pluggable transport + codec</h3>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">Drop in fasthttp, sonic, or goccy/go-json with a one-line swap.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="glass p-5 rounded-xl group hover:shadow-lg transition-all duration-300">
|
||||
<div class="flex items-start gap-4">
|
||||
<div class="w-12 h-12 rounded-xl bg-gradient-to-br from-amber-500 to-amber-600 flex items-center justify-center flex-shrink-0 group-hover:scale-110 transition-transform duration-300">
|
||||
<i class="fas fa-bolt text-white"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-1">Production-ready out of the box</h3>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">Retry middleware honouring <code class="text-xs">retry_after</code>, panic recovery, structured errors with sentinels.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="glass p-5 rounded-xl group hover:shadow-lg transition-all duration-300">
|
||||
<div class="flex items-start gap-4">
|
||||
<div class="w-12 h-12 rounded-xl bg-gradient-to-br from-rose-500 to-rose-600 flex items-center justify-center flex-shrink-0 group-hover:scale-110 transition-transform duration-300">
|
||||
<i class="fas fa-route text-white"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-1">Typed dispatcher</h3>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">Generic <code class="text-xs">Handler[T]</code>, composable filters, conversation handler with pluggable storage, per-update goroutine pool.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="glass p-5 rounded-xl group hover:shadow-lg transition-all duration-300">
|
||||
<div class="flex items-start gap-4">
|
||||
<div class="w-12 h-12 rounded-xl bg-gradient-to-br from-teal-500 to-teal-600 flex items-center justify-center flex-shrink-0 group-hover:scale-110 transition-transform duration-300">
|
||||
<i class="fas fa-circle-check text-white"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-1">Self-verifying codegen</h3>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">Every regen runs scrape → audit → emit → 1408 generated tests. Nothing ships without passing.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Comparison Section -->
|
||||
<section id="comparison" class="py-12 sm:py-16 md:py-20 bg-gray-50 dark:bg-gray-800 theme-transition">
|
||||
<div class="max-w-6xl mx-auto px-4 sm:px-6">
|
||||
<div class="text-center mb-8 sm:mb-12">
|
||||
<h2 class="text-2xl sm:text-3xl md:text-4xl font-bold text-gray-900 dark:text-gray-100 mb-3 sm:mb-4">Comparison</h2>
|
||||
<p class="text-base sm:text-lg text-gray-600 dark:text-gray-300 px-4">How go-telegram compares to other Go Telegram bot libraries</p>
|
||||
</div>
|
||||
|
||||
<!-- Mobile cards -->
|
||||
<div class="block md:hidden space-y-4 mb-8">
|
||||
<div class="glass rounded-xl p-5 shadow-modern">
|
||||
<div class="flex items-center gap-3 mb-4 pb-3 border-b border-gray-200 dark:border-gray-700">
|
||||
<div class="w-10 h-10 rounded-lg bg-gradient-to-br from-sky-500 to-cyan-600 flex items-center justify-center">
|
||||
<i class="fas fa-paper-plane text-white text-sm"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-semibold text-gray-900 dark:text-gray-100">go-telegram</h4>
|
||||
<p class="text-xs text-gray-500">HTML scraper · this library</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 gap-2 text-sm">
|
||||
<div class="flex items-center gap-2"><span class="text-green-500 font-bold">✓</span><span class="text-gray-600 dark:text-gray-400">Generated from spec (HTML)</span></div>
|
||||
<div class="flex items-center gap-2"><span class="text-green-500 font-bold">✓</span><span class="text-gray-600 dark:text-gray-400">Typed unions, no any</span></div>
|
||||
<div class="flex items-center gap-2"><span class="text-green-500 font-bold">✓</span><span class="text-gray-600 dark:text-gray-400">1,408 auto-generated tests</span></div>
|
||||
<div class="flex items-center gap-2"><span class="text-green-500 font-bold">✓</span><span class="text-gray-600 dark:text-gray-400">Conversation handler</span></div>
|
||||
<div class="flex items-center gap-2"><span class="text-green-500 font-bold">✓</span><span class="text-gray-600 dark:text-gray-400">Retry middleware (retry_after)</span></div>
|
||||
<div class="flex items-center gap-2"><span class="text-green-500 font-bold">✓</span><span class="text-gray-600 dark:text-gray-400">Pluggable JSON codec</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="glass rounded-xl p-5 shadow-modern">
|
||||
<div class="flex items-center gap-3 mb-4 pb-3 border-b border-gray-200 dark:border-gray-700">
|
||||
<div class="w-10 h-10 rounded-lg bg-gradient-to-br from-indigo-600 to-indigo-700 flex items-center justify-center">
|
||||
<span class="text-white font-bold text-xs">gtb</span>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-semibold text-gray-900 dark:text-gray-100">gotgbot/v2</h4>
|
||||
<p class="text-xs text-gray-500">JSON spec · popular library</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 gap-2 text-sm">
|
||||
<div class="flex items-center gap-2"><span class="text-green-500 font-bold">✓</span><span class="text-gray-600 dark:text-gray-400">Generated from spec (JSON)</span></div>
|
||||
<div class="flex items-center gap-2"><span class="text-yellow-500 font-bold">△</span><span class="text-gray-600 dark:text-gray-400">Partial typed unions</span></div>
|
||||
<div class="flex items-center gap-2"><span class="text-red-500 font-bold">✗</span><span class="text-gray-400">Auto-generated tests</span></div>
|
||||
<div class="flex items-center gap-2"><span class="text-green-500 font-bold">✓</span><span class="text-gray-600 dark:text-gray-400">Conversation handler</span></div>
|
||||
<div class="flex items-center gap-2"><span class="text-yellow-500 font-bold">△</span><span class="text-gray-600 dark:text-gray-400">User-implemented retry</span></div>
|
||||
<div class="flex items-center gap-2"><span class="text-red-500 font-bold">✗</span><span class="text-gray-400">Pluggable JSON codec</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="glass rounded-xl p-5 shadow-modern">
|
||||
<div class="flex items-center gap-3 mb-4 pb-3 border-b border-gray-200 dark:border-gray-700">
|
||||
<div class="w-10 h-10 rounded-lg bg-gradient-to-br from-gray-600 to-gray-700 flex items-center justify-center">
|
||||
<span class="text-white font-bold text-xs">tba</span>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-semibold text-gray-900 dark:text-gray-100">telegram-bot-api/v5</h4>
|
||||
<p class="text-xs text-gray-500">Hand-coded</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 gap-2 text-sm">
|
||||
<div class="flex items-center gap-2"><span class="text-red-500 font-bold">✗</span><span class="text-gray-400">Generated from spec</span></div>
|
||||
<div class="flex items-center gap-2"><span class="text-red-500 font-bold">✗</span><span class="text-gray-400">Typed unions</span></div>
|
||||
<div class="flex items-center gap-2"><span class="text-red-500 font-bold">✗</span><span class="text-gray-400">Auto-generated tests</span></div>
|
||||
<div class="flex items-center gap-2"><span class="text-red-500 font-bold">✗</span><span class="text-gray-400">Conversation handler</span></div>
|
||||
<div class="flex items-center gap-2"><span class="text-red-500 font-bold">✗</span><span class="text-gray-400">Retry middleware</span></div>
|
||||
<div class="flex items-center gap-2"><span class="text-red-500 font-bold">✗</span><span class="text-gray-400">Pluggable JSON codec</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Desktop table -->
|
||||
<div class="hidden md:block glass rounded-xl overflow-hidden shadow-modern">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead class="bg-gradient-to-r from-sky-500 to-cyan-600 text-white">
|
||||
<tr>
|
||||
<th class="px-4 py-3 text-left font-semibold">Feature</th>
|
||||
<th class="px-4 py-3 text-center font-semibold">go-telegram</th>
|
||||
<th class="px-4 py-3 text-center font-semibold"><a href="https://github.com/PaulSonOfLars/gotgbot" class="hover:underline" target="_blank">gotgbot/v2</a></th>
|
||||
<th class="px-4 py-3 text-center font-semibold"><a href="https://github.com/go-telegram-bot-api/telegram-bot-api" class="hover:underline" target="_blank">telegram-bot-api/v5</a></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-800/50">
|
||||
<td class="px-4 py-3 text-gray-700 dark:text-gray-300 font-medium">Generated from spec</td>
|
||||
<td class="px-4 py-3 text-center"><span class="text-green-500 font-bold">✓</span> <span class="text-xs text-gray-500">HTML scraper</span></td>
|
||||
<td class="px-4 py-3 text-center"><span class="text-green-500 font-bold">✓</span> <span class="text-xs text-gray-500">JSON spec</span></td>
|
||||
<td class="px-4 py-3 text-center"><span class="text-red-500 font-bold">✗</span> <span class="text-xs text-gray-500">hand-coded</span></td>
|
||||
</tr>
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-800/50">
|
||||
<td class="px-4 py-3 text-gray-700 dark:text-gray-300 font-medium">Typed unions (no <code class="text-xs">any</code>)</td>
|
||||
<td class="px-4 py-3 text-center"><span class="text-green-500 font-bold">✓</span> <span class="text-xs text-gray-500">ChatID, MessageOrBool, sealed interfaces</span></td>
|
||||
<td class="px-4 py-3 text-center"><span class="text-yellow-500 font-bold">△</span> <span class="text-xs text-gray-500">partial</span></td>
|
||||
<td class="px-4 py-3 text-center"><span class="text-red-500 font-bold">✗</span></td>
|
||||
</tr>
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-800/50">
|
||||
<td class="px-4 py-3 text-gray-700 dark:text-gray-300 font-medium">Auto-generated tests</td>
|
||||
<td class="px-4 py-3 text-center"><span class="text-green-500 font-bold">✓</span> <span class="text-xs text-gray-500">1,408 (8 scenarios/method)</span></td>
|
||||
<td class="px-4 py-3 text-center"><span class="text-red-500 font-bold">✗</span></td>
|
||||
<td class="px-4 py-3 text-center"><span class="text-red-500 font-bold">✗</span></td>
|
||||
</tr>
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-800/50">
|
||||
<td class="px-4 py-3 text-gray-700 dark:text-gray-300 font-medium">Conversation handler</td>
|
||||
<td class="px-4 py-3 text-center"><span class="text-green-500 font-bold">✓</span> <span class="text-xs text-gray-500">pluggable storage</span></td>
|
||||
<td class="px-4 py-3 text-center"><span class="text-green-500 font-bold">✓</span></td>
|
||||
<td class="px-4 py-3 text-center"><span class="text-red-500 font-bold">✗</span></td>
|
||||
</tr>
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-800/50">
|
||||
<td class="px-4 py-3 text-gray-700 dark:text-gray-300 font-medium">Retry middleware</td>
|
||||
<td class="px-4 py-3 text-center"><span class="text-green-500 font-bold">✓</span> <span class="text-xs text-gray-500">honours retry_after</span></td>
|
||||
<td class="px-4 py-3 text-center"><span class="text-yellow-500 font-bold">△</span> <span class="text-xs text-gray-500">user-implemented</span></td>
|
||||
<td class="px-4 py-3 text-center"><span class="text-red-500 font-bold">✗</span></td>
|
||||
</tr>
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-800/50">
|
||||
<td class="px-4 py-3 text-gray-700 dark:text-gray-300 font-medium">Pluggable JSON codec</td>
|
||||
<td class="px-4 py-3 text-center"><span class="text-green-500 font-bold">✓</span></td>
|
||||
<td class="px-4 py-3 text-center"><span class="text-red-500 font-bold">✗</span></td>
|
||||
<td class="px-4 py-3 text-center"><span class="text-red-500 font-bold">✗</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Installation Section -->
|
||||
<section id="install" class="py-12 sm:py-16 md:py-20 bg-white dark:bg-gray-900 theme-transition">
|
||||
<div class="max-w-4xl mx-auto px-4 sm:px-6">
|
||||
<div class="text-center mb-10 sm:mb-12">
|
||||
<h2 class="text-2xl sm:text-3xl md:text-4xl font-bold text-gray-900 dark:text-gray-100 mb-3 sm:mb-4">Installation</h2>
|
||||
<p class="text-base sm:text-lg text-gray-600 dark:text-gray-300 px-4">One command. No CGO, no system deps.</p>
|
||||
</div>
|
||||
<div class="space-y-4 sm:space-y-6">
|
||||
<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-cubes text-sky-500 dark:text-sky-400 text-2xl mr-3"></i>
|
||||
<div>
|
||||
<h3 class="text-xl font-semibold text-gray-900 dark:text-gray-100">go get</h3>
|
||||
<p class="text-gray-600 dark:text-gray-400 text-sm">Requires Go 1.21+</p>
|
||||
</div>
|
||||
</div>
|
||||
<div onclick="copyToClipboard('go get github.com/lukaszraczylo/go-telegram', this)" class="relative bg-gradient-to-br from-gray-900 to-gray-800 dark:from-gray-950 dark:to-black text-gray-100 p-4 rounded-lg text-sm cursor-pointer group overflow-x-auto border border-gray-700 hover:border-sky-500 transition-all duration-300">
|
||||
<code class="block whitespace-nowrap font-mono">go get github.com/lukaszraczylo/go-telegram</code>
|
||||
<div class="absolute top-3 right-3"><i class="fas fa-copy text-gray-500 group-hover:text-sky-400 transition-colors duration-300"></i></div>
|
||||
</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 text-cyan-500 dark:text-cyan-400 text-2xl mr-3"></i>
|
||||
<div>
|
||||
<h3 class="text-xl font-semibold text-gray-900 dark:text-gray-100">pkg.go.dev</h3>
|
||||
<p class="text-gray-600 dark:text-gray-400 text-sm">Full API reference</p>
|
||||
</div>
|
||||
</div>
|
||||
<a href="https://pkg.go.dev/github.com/lukaszraczylo/go-telegram" target="_blank" class="block text-center bg-gradient-to-r from-cyan-600 to-sky-700 hover:from-cyan-700 hover:to-sky-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>View on pkg.go.dev
|
||||
</a>
|
||||
</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="flex items-center mb-4">
|
||||
<i class="fas fa-file-alt text-blue-500 dark:text-blue-400 text-2xl mr-3"></i>
|
||||
<div>
|
||||
<h3 class="text-xl font-semibold text-gray-900 dark:text-gray-100">Telegram Bot API reference</h3>
|
||||
<p class="text-gray-600 dark:text-gray-400 text-sm">Upstream spec this library tracks</p>
|
||||
</div>
|
||||
</div>
|
||||
<a href="https://core.telegram.org/bots/api" target="_blank" class="block text-center bg-gradient-to-r from-blue-500 to-blue-700 hover:from-blue-600 hover:to-blue-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>core.telegram.org/bots/api
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Usage Section -->
|
||||
<section id="usage" class="py-12 sm:py-16 md:py-20 bg-gray-50 dark:bg-gray-800 theme-transition">
|
||||
<div class="max-w-6xl mx-auto px-4 sm:px-6">
|
||||
<div class="text-center mb-10 sm:mb-12">
|
||||
<h2 class="text-2xl sm:text-3xl md:text-4xl font-bold text-gray-900 dark:text-gray-100 mb-3 sm:mb-4">Usage</h2>
|
||||
<p class="text-base sm:text-lg text-gray-600 dark:text-gray-300 px-4">Echo bot — the minimal working example</p>
|
||||
</div>
|
||||
|
||||
<div class="relative group mb-10">
|
||||
<div class="absolute -inset-1 bg-gradient-to-r from-sky-500 to-cyan-600 rounded-xl blur opacity-25 group-hover:opacity-50 transition duration-300"></div>
|
||||
<div class="relative bg-gradient-to-br from-gray-900 to-gray-800 rounded-xl p-4 sm:p-6 overflow-x-auto border border-gray-700">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="flex items-center">
|
||||
<i class="fas fa-file-code text-sky-400 mr-2"></i>
|
||||
<span class="text-sky-400 text-sm font-mono font-semibold">echo_bot.go</span>
|
||||
</div>
|
||||
<button onclick="copyToClipboard(document.getElementById('echo-bot-code').textContent, this)" class="text-gray-400 hover:text-sky-400 transition-colors duration-300" aria-label="Copy code">
|
||||
<i class="fas fa-copy"></i>
|
||||
</button>
|
||||
</div>
|
||||
<pre class="text-xs sm:text-sm text-gray-100 overflow-x-auto"><code id="echo-bot-code" class="font-mono">package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/lukaszraczylo/go-telegram/api"
|
||||
"github.com/lukaszraczylo/go-telegram/client"
|
||||
"github.com/lukaszraczylo/go-telegram/dispatch"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// NewRetryDoer wraps the default transport with retry middleware
|
||||
// that honours Telegram's retry_after field automatically.
|
||||
bot, err := client.NewRetryDoer(os.Getenv("BOT_TOKEN"), nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
d := dispatch.New(bot)
|
||||
|
||||
// Handler[T] is generic — the type parameter is the concrete update type.
|
||||
d.OnMessage(func(ctx context.Context, msg *api.Message) {
|
||||
_, err := bot.SendMessage(ctx, &api.SendMessageParams{
|
||||
// ChatIDFromInt returns a typed ChatID — no interface{} here.
|
||||
ChatID: api.ChatIDFromInt(msg.Chat.ID),
|
||||
Text: msg.Text,
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("send error: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
if err := d.Run(context.Background()); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Codegen pipeline -->
|
||||
<div id="codegen" class="glass p-6 sm:p-8 rounded-xl shadow-modern">
|
||||
<h3 class="text-lg sm:text-xl font-semibold text-gray-900 dark:text-gray-100 mb-2"><i class="fas fa-cogs text-sky-500 mr-2"></i>Codegen pipeline</h3>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-6">The emitter scrapes the live Telegram Bot API docs (HTML), builds an intermediate representation (<code class="text-xs">api.json</code>), then generates Go code and tests. An audit step validates every method signature against the IR before emission.</p>
|
||||
|
||||
<!-- Flow diagram -->
|
||||
<div class="flex flex-wrap items-center justify-center gap-2 sm:gap-3 mb-6 text-sm font-mono">
|
||||
<div class="px-3 py-2 bg-sky-100 dark:bg-sky-900/40 text-sky-800 dark:text-sky-300 rounded-lg border border-sky-200 dark:border-sky-700 flex items-center gap-2">
|
||||
<i class="fas fa-globe text-sky-500"></i> HTML docs
|
||||
</div>
|
||||
<span class="text-gray-400 font-sans">→</span>
|
||||
<div class="px-3 py-2 bg-purple-100 dark:bg-purple-900/40 text-purple-800 dark:text-purple-300 rounded-lg border border-purple-200 dark:border-purple-700 flex items-center gap-2">
|
||||
<i class="fas fa-database text-purple-500"></i> IR (api.json)
|
||||
</div>
|
||||
<span class="text-gray-400 font-sans">→</span>
|
||||
<div class="px-3 py-2 bg-amber-100 dark:bg-amber-900/40 text-amber-800 dark:text-amber-300 rounded-lg border border-amber-200 dark:border-amber-700 flex items-center gap-2">
|
||||
<i class="fas fa-search text-amber-500"></i> audit
|
||||
</div>
|
||||
<span class="text-gray-400 font-sans">→</span>
|
||||
<div class="px-3 py-2 bg-emerald-100 dark:bg-emerald-900/40 text-emerald-800 dark:text-emerald-300 rounded-lg border border-emerald-200 dark:border-emerald-700 flex items-center gap-2">
|
||||
<i class="fas fa-code text-emerald-500"></i> Go code + tests
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div onclick="copyToClipboard('make snapshot && make regen', this)" class="relative bg-gradient-to-br from-gray-900 to-gray-800 text-gray-100 p-4 rounded-lg text-sm cursor-pointer group overflow-x-auto border border-gray-700 hover:border-sky-500 transition-all duration-300">
|
||||
<code class="block whitespace-nowrap font-mono">make snapshot && make regen</code>
|
||||
<div class="absolute top-3 right-3"><i class="fas fa-copy text-gray-500 group-hover:text-sky-400 transition-colors duration-300"></i></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Examples Section -->
|
||||
<section id="examples" class="py-12 sm:py-16 md:py-20 bg-white dark:bg-gray-900 theme-transition">
|
||||
<div class="max-w-6xl mx-auto px-4 sm:px-6">
|
||||
<div class="text-center mb-8 sm:mb-12">
|
||||
<h2 class="text-2xl sm:text-3xl md:text-4xl font-bold text-gray-900 dark:text-gray-100 mb-3 sm:mb-4">Examples</h2>
|
||||
<p class="text-base sm:text-lg text-gray-600 dark:text-gray-300 px-4">14 runnable bots covering the most common patterns</p>
|
||||
</div>
|
||||
<div class="grid sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<!-- Echo -->
|
||||
<a href="https://github.com/lukaszraczylo/go-telegram/tree/main/examples/echo" target="_blank" class="glass p-4 rounded-xl group hover:shadow-lg transition-all duration-300 flex items-start gap-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-gradient-to-br from-sky-500 to-sky-600 flex items-center justify-center flex-shrink-0 group-hover:scale-110 transition-transform duration-300">
|
||||
<i class="fas fa-reply text-white text-sm"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-1 group-hover:text-sky-600 dark:group-hover:text-sky-400">echo</h3>
|
||||
<p class="text-xs text-gray-600 dark:text-gray-400">Minimal echo bot — get up and running in 30 lines.</p>
|
||||
</div>
|
||||
</a>
|
||||
<!-- Callback -->
|
||||
<a href="https://github.com/lukaszraczylo/go-telegram/tree/main/examples/callback" target="_blank" class="glass p-4 rounded-xl group hover:shadow-lg transition-all duration-300 flex items-start gap-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-gradient-to-br from-purple-500 to-purple-600 flex items-center justify-center flex-shrink-0 group-hover:scale-110 transition-transform duration-300">
|
||||
<i class="fas fa-hand-pointer text-white text-sm"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-1 group-hover:text-sky-600 dark:group-hover:text-sky-400">callback</h3>
|
||||
<p class="text-xs text-gray-600 dark:text-gray-400">Inline keyboard buttons and callback query handling.</p>
|
||||
</div>
|
||||
</a>
|
||||
<!-- Inline -->
|
||||
<a href="https://github.com/lukaszraczylo/go-telegram/tree/main/examples/inline" target="_blank" class="glass p-4 rounded-xl group hover:shadow-lg transition-all duration-300 flex items-start gap-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-gradient-to-br from-cyan-500 to-cyan-600 flex items-center justify-center flex-shrink-0 group-hover:scale-110 transition-transform duration-300">
|
||||
<i class="fas fa-search text-white text-sm"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-1 group-hover:text-sky-600 dark:group-hover:text-sky-400">inline</h3>
|
||||
<p class="text-xs text-gray-600 dark:text-gray-400">Inline mode queries and result sets.</p>
|
||||
</div>
|
||||
</a>
|
||||
<!-- Conversation -->
|
||||
<a href="https://github.com/lukaszraczylo/go-telegram/tree/main/examples/conversation" target="_blank" class="glass p-4 rounded-xl group hover:shadow-lg transition-all duration-300 flex items-start gap-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-gradient-to-br from-emerald-500 to-emerald-600 flex items-center justify-center flex-shrink-0 group-hover:scale-110 transition-transform duration-300">
|
||||
<i class="fas fa-comments text-white text-sm"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-1 group-hover:text-sky-600 dark:group-hover:text-sky-400">conversation</h3>
|
||||
<p class="text-xs text-gray-600 dark:text-gray-400">Multi-step conversation flows with pluggable state storage.</p>
|
||||
</div>
|
||||
</a>
|
||||
<!-- Stateful -->
|
||||
<a href="https://github.com/lukaszraczylo/go-telegram/tree/main/examples/stateful" target="_blank" class="glass p-4 rounded-xl group hover:shadow-lg transition-all duration-300 flex items-start gap-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-gradient-to-br from-amber-500 to-amber-600 flex items-center justify-center flex-shrink-0 group-hover:scale-110 transition-transform duration-300">
|
||||
<i class="fas fa-memory text-white text-sm"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-1 group-hover:text-sky-600 dark:group-hover:text-sky-400">stateful</h3>
|
||||
<p class="text-xs text-gray-600 dark:text-gray-400">Per-user state machine pattern.</p>
|
||||
</div>
|
||||
</a>
|
||||
<!-- Admin -->
|
||||
<a href="https://github.com/lukaszraczylo/go-telegram/tree/main/examples/admin" target="_blank" class="glass p-4 rounded-xl group hover:shadow-lg transition-all duration-300 flex items-start gap-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-gradient-to-br from-rose-500 to-rose-600 flex items-center justify-center flex-shrink-0 group-hover:scale-110 transition-transform duration-300">
|
||||
<i class="fas fa-user-shield text-white text-sm"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-1 group-hover:text-sky-600 dark:group-hover:text-sky-400">admin</h3>
|
||||
<p class="text-xs text-gray-600 dark:text-gray-400">Admin commands — ban, kick, restrict members.</p>
|
||||
</div>
|
||||
</a>
|
||||
<!-- Middleware -->
|
||||
<a href="https://github.com/lukaszraczylo/go-telegram/tree/main/examples/middleware" target="_blank" class="glass p-4 rounded-xl group hover:shadow-lg transition-all duration-300 flex items-start gap-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-gradient-to-br from-indigo-500 to-indigo-600 flex items-center justify-center flex-shrink-0 group-hover:scale-110 transition-transform duration-300">
|
||||
<i class="fas fa-layer-group text-white text-sm"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-1 group-hover:text-sky-600 dark:group-hover:text-sky-400">middleware</h3>
|
||||
<p class="text-xs text-gray-600 dark:text-gray-400">Composable middleware chain — logging, auth, rate limiting.</p>
|
||||
</div>
|
||||
</a>
|
||||
<!-- Files -->
|
||||
<a href="https://github.com/lukaszraczylo/go-telegram/tree/main/examples/files" target="_blank" class="glass p-4 rounded-xl group hover:shadow-lg transition-all duration-300 flex items-start gap-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-gradient-to-br from-teal-500 to-teal-600 flex items-center justify-center flex-shrink-0 group-hover:scale-110 transition-transform duration-300">
|
||||
<i class="fas fa-paperclip text-white text-sm"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-1 group-hover:text-sky-600 dark:group-hover:text-sky-400">files</h3>
|
||||
<p class="text-xs text-gray-600 dark:text-gray-400">Upload and download photos, documents, audio.</p>
|
||||
</div>
|
||||
</a>
|
||||
<!-- Webhook -->
|
||||
<a href="https://github.com/lukaszraczylo/go-telegram/tree/main/examples/webhook" target="_blank" class="glass p-4 rounded-xl group hover:shadow-lg transition-all duration-300 flex items-start gap-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-gradient-to-br from-lime-500 to-lime-600 flex items-center justify-center flex-shrink-0 group-hover:scale-110 transition-transform duration-300">
|
||||
<i class="fas fa-network-wired text-white text-sm"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-1 group-hover:text-sky-600 dark:group-hover:text-sky-400">webhook</h3>
|
||||
<p class="text-xs text-gray-600 dark:text-gray-400">Webhook server instead of long-polling.</p>
|
||||
</div>
|
||||
</a>
|
||||
<!-- Polls -->
|
||||
<a href="https://github.com/lukaszraczylo/go-telegram/tree/main/examples/polls" target="_blank" class="glass p-4 rounded-xl group hover:shadow-lg transition-all duration-300 flex items-start gap-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-gradient-to-br from-fuchsia-500 to-fuchsia-600 flex items-center justify-center flex-shrink-0 group-hover:scale-110 transition-transform duration-300">
|
||||
<i class="fas fa-poll text-white text-sm"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-1 group-hover:text-sky-600 dark:group-hover:text-sky-400">polls</h3>
|
||||
<p class="text-xs text-gray-600 dark:text-gray-400">Send polls and handle poll-answer updates.</p>
|
||||
</div>
|
||||
</a>
|
||||
<!-- Payments -->
|
||||
<a href="https://github.com/lukaszraczylo/go-telegram/tree/main/examples/payments" target="_blank" class="glass p-4 rounded-xl group hover:shadow-lg transition-all duration-300 flex items-start gap-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-gradient-to-br from-yellow-500 to-yellow-600 flex items-center justify-center flex-shrink-0 group-hover:scale-110 transition-transform duration-300">
|
||||
<i class="fas fa-credit-card text-white text-sm"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-1 group-hover:text-sky-600 dark:group-hover:text-sky-400">payments</h3>
|
||||
<p class="text-xs text-gray-600 dark:text-gray-400">Telegram Payments — invoices, pre-checkout, successful payment.</p>
|
||||
</div>
|
||||
</a>
|
||||
<!-- Pagination -->
|
||||
<a href="https://github.com/lukaszraczylo/go-telegram/tree/main/examples/pagination" target="_blank" class="glass p-4 rounded-xl group hover:shadow-lg transition-all duration-300 flex items-start gap-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-gradient-to-br from-slate-500 to-slate-600 flex items-center justify-center flex-shrink-0 group-hover:scale-110 transition-transform duration-300">
|
||||
<i class="fas fa-chevron-right text-white text-sm"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-1 group-hover:text-sky-600 dark:group-hover:text-sky-400">pagination</h3>
|
||||
<p class="text-xs text-gray-600 dark:text-gray-400">Paginated inline keyboards for long result sets.</p>
|
||||
</div>
|
||||
</a>
|
||||
<!-- Welcome -->
|
||||
<a href="https://github.com/lukaszraczylo/go-telegram/tree/main/examples/welcome" target="_blank" class="glass p-4 rounded-xl group hover:shadow-lg transition-all duration-300 flex items-start gap-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-gradient-to-br from-green-500 to-green-600 flex items-center justify-center flex-shrink-0 group-hover:scale-110 transition-transform duration-300">
|
||||
<i class="fas fa-door-open text-white text-sm"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-1 group-hover:text-sky-600 dark:group-hover:text-sky-400">welcome</h3>
|
||||
<p class="text-xs text-gray-600 dark:text-gray-400">Greet new members joining a group or channel.</p>
|
||||
</div>
|
||||
</a>
|
||||
<!-- Moderation -->
|
||||
<a href="https://github.com/lukaszraczylo/go-telegram/tree/main/examples/moderation" target="_blank" class="glass p-4 rounded-xl group hover:shadow-lg transition-all duration-300 flex items-start gap-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-gradient-to-br from-red-500 to-red-600 flex items-center justify-center flex-shrink-0 group-hover:scale-110 transition-transform duration-300">
|
||||
<i class="fas fa-gavel text-white text-sm"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-1 group-hover:text-sky-600 dark:group-hover:text-sky-400">moderation</h3>
|
||||
<p class="text-xs text-gray-600 dark:text-gray-400">Automated content moderation with filter chains.</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Advanced Section -->
|
||||
<section id="advanced" class="py-12 sm:py-16 md:py-20 bg-gray-50 dark:bg-gray-800 theme-transition">
|
||||
<div class="max-w-4xl mx-auto px-4 sm:px-6">
|
||||
<div class="text-center mb-10 sm:mb-12">
|
||||
<h2 class="text-2xl sm:text-3xl md:text-4xl font-bold text-gray-900 dark:text-gray-100 mb-3 sm:mb-4">Advanced</h2>
|
||||
<p class="text-base sm:text-lg text-gray-600 dark:text-gray-300 px-4">Power-user patterns — expand to read</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-3">
|
||||
<!-- Conversation flows -->
|
||||
<details class="glass rounded-xl overflow-hidden group">
|
||||
<summary class="flex items-center justify-between p-5 cursor-pointer list-none select-none">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-9 h-9 rounded-lg bg-gradient-to-br from-emerald-500 to-emerald-600 flex items-center justify-center flex-shrink-0">
|
||||
<i class="fas fa-comments text-white text-sm"></i>
|
||||
</div>
|
||||
<h3 class="font-semibold text-gray-900 dark:text-gray-100">Conversation flows</h3>
|
||||
</div>
|
||||
<i class="fas fa-chevron-down text-gray-400 transition-transform duration-300 group-open:rotate-180"></i>
|
||||
</summary>
|
||||
<div class="px-5 pb-5 border-t border-gray-200 dark:border-gray-700">
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mt-4 mb-4">
|
||||
The conversation handler chains multiple message steps and stores state between them. Any storage backend implementing the
|
||||
<code class="text-xs bg-gray-100 dark:bg-gray-800 px-1 rounded">StateStorage</code> interface works — in-memory, Redis, Postgres.
|
||||
</p>
|
||||
<div class="bg-gradient-to-br from-gray-900 to-gray-800 rounded-xl p-4 text-sm text-gray-100 overflow-x-auto border border-gray-700">
|
||||
<pre class="font-mono"><code>conv := conversation.New(storage)
|
||||
|
||||
conv.AddState("ask_name", func(ctx context.Context, msg *api.Message) (string, error) {
|
||||
bot.SendMessage(ctx, &api.SendMessageParams{
|
||||
ChatID: api.ChatIDFromInt(msg.Chat.ID),
|
||||
Text: "What's your name?",
|
||||
})
|
||||
return "ask_age", nil
|
||||
})
|
||||
|
||||
conv.AddState("ask_age", func(ctx context.Context, msg *api.Message) (string, error) {
|
||||
name := conv.GetData(ctx, "name")
|
||||
// ... handle age input
|
||||
return conversation.Done, nil
|
||||
})
|
||||
|
||||
d.OnMessage(conv.Handler("start"))</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<!-- Custom filters -->
|
||||
<details class="glass rounded-xl overflow-hidden group">
|
||||
<summary class="flex items-center justify-between p-5 cursor-pointer list-none select-none">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-9 h-9 rounded-lg bg-gradient-to-br from-purple-500 to-purple-600 flex items-center justify-center flex-shrink-0">
|
||||
<i class="fas fa-filter text-white text-sm"></i>
|
||||
</div>
|
||||
<h3 class="font-semibold text-gray-900 dark:text-gray-100">Custom filters</h3>
|
||||
</div>
|
||||
<i class="fas fa-chevron-down text-gray-400 transition-transform duration-300 group-open:rotate-180"></i>
|
||||
</summary>
|
||||
<div class="px-5 pb-5 border-t border-gray-200 dark:border-gray-700">
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mt-4 mb-4">
|
||||
Filters compose with <code class="text-xs bg-gray-100 dark:bg-gray-800 px-1 rounded">And</code>, <code class="text-xs bg-gray-100 dark:bg-gray-800 px-1 rounded">Or</code>, <code class="text-xs bg-gray-100 dark:bg-gray-800 px-1 rounded">Not</code>.
|
||||
A filter is just a function <code class="text-xs bg-gray-100 dark:bg-gray-800 px-1 rounded">func(*api.Update) bool</code>.
|
||||
</p>
|
||||
<div class="bg-gradient-to-br from-gray-900 to-gray-800 rounded-xl p-4 text-sm text-gray-100 overflow-x-auto border border-gray-700">
|
||||
<pre class="font-mono"><code>// Only handle private messages from admins
|
||||
adminOnly := filters.And(
|
||||
filters.IsPrivate,
|
||||
filters.UserIDIn(adminIDs...),
|
||||
)
|
||||
|
||||
d.OnMessage(func(ctx context.Context, msg *api.Message) {
|
||||
// handler body
|
||||
}, adminOnly)
|
||||
|
||||
// Custom filter — any function works
|
||||
isLong := func(u *api.Update) bool {
|
||||
return u.Message != nil && len(u.Message.Text) > 200
|
||||
}
|
||||
d.OnMessage(handleLongMsg, isLong)</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<!-- Custom HTTP/JSON -->
|
||||
<details class="glass rounded-xl overflow-hidden group">
|
||||
<summary class="flex items-center justify-between p-5 cursor-pointer list-none select-none">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-9 h-9 rounded-lg bg-gradient-to-br from-amber-500 to-amber-600 flex items-center justify-center flex-shrink-0">
|
||||
<i class="fas fa-plug text-white text-sm"></i>
|
||||
</div>
|
||||
<h3 class="font-semibold text-gray-900 dark:text-gray-100">Custom HTTP / JSON codec</h3>
|
||||
</div>
|
||||
<i class="fas fa-chevron-down text-gray-400 transition-transform duration-300 group-open:rotate-180"></i>
|
||||
</summary>
|
||||
<div class="px-5 pb-5 border-t border-gray-200 dark:border-gray-700">
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mt-4 mb-4">
|
||||
Pass a <code class="text-xs bg-gray-100 dark:bg-gray-800 px-1 rounded">transport.Options</code> to swap the HTTP client or JSON codec.
|
||||
Useful for squeezing throughput on high-volume bots.
|
||||
</p>
|
||||
<div class="bg-gradient-to-br from-gray-900 to-gray-800 rounded-xl p-4 text-sm text-gray-100 overflow-x-auto border border-gray-700">
|
||||
<pre class="font-mono"><code>import (
|
||||
"github.com/lukaszraczylo/go-telegram/client"
|
||||
"github.com/lukaszraczylo/go-telegram/transport"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
)
|
||||
|
||||
opts := &client.Options{
|
||||
Transport: transport.NewFasthttpTransport(nil),
|
||||
Codec: transport.JSONCodec(jsoniter.ConfigCompatibleWithStandardLibrary),
|
||||
}
|
||||
|
||||
bot, err := client.NewRetryDoer(token, opts)</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="bg-gray-900 dark:bg-black text-gray-400 py-8 sm:py-10 theme-transition">
|
||||
<div class="max-w-6xl mx-auto px-4 sm:px-6">
|
||||
<div class="grid grid-cols-2 sm:grid-cols-2 md:grid-cols-4 gap-6 sm:gap-8">
|
||||
<div class="col-span-2 sm:col-span-1">
|
||||
<img src="logo-dark.svg" alt="go-telegram logo" class="h-10 sm:h-12 w-auto mb-3 sm:mb-4" />
|
||||
<p class="text-xs sm:text-sm">Fully-generated, strongly-typed Go client for the Telegram Bot API.</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-white font-semibold mb-3 sm:mb-4 text-sm sm:text-base">Links</h3>
|
||||
<ul class="space-y-1.5 sm:space-y-2 text-xs sm:text-sm">
|
||||
<li><a href="https://github.com/lukaszraczylo/go-telegram" target="_blank" class="hover:text-white transition"><i class="fab fa-github mr-2"></i>GitHub</a></li>
|
||||
<li><a href="https://github.com/lukaszraczylo/go-telegram/issues" target="_blank" class="hover:text-white transition"><i class="fas fa-bug mr-2"></i>Issues</a></li>
|
||||
<li><a href="https://github.com/lukaszraczylo/go-telegram/releases" target="_blank" class="hover:text-white transition"><i class="fas fa-tag mr-2"></i>Releases</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-white font-semibold mb-3 sm:mb-4 text-sm sm:text-base">Docs</h3>
|
||||
<ul class="space-y-1.5 sm:space-y-2 text-xs sm:text-sm">
|
||||
<li><a href="https://pkg.go.dev/github.com/lukaszraczylo/go-telegram" target="_blank" class="hover:text-white transition"><i class="fas fa-book mr-1"></i> pkg.go.dev</a></li>
|
||||
<li><a href="https://core.telegram.org/bots/api" target="_blank" class="hover:text-white transition">Telegram Bot API</a></li>
|
||||
<li><a href="https://github.com/lukaszraczylo/go-telegram#readme" target="_blank" class="hover:text-white transition">README</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-span-2 sm:col-span-1">
|
||||
<h3 class="text-white font-semibold mb-3 sm:mb-4 text-sm sm:text-base">Sibling projects</h3>
|
||||
<ul class="space-y-1.5 sm:space-y-2 text-xs sm:text-sm">
|
||||
<li><a href="https://kportal.raczylo.com" target="_blank" class="hover:text-white transition"><i class="fas fa-dharmachakra mr-2"></i>kportal</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-6 sm:mt-8 pt-6 sm:pt-8 border-t border-gray-800 text-center text-xs sm:text-sm">
|
||||
<p>Made by <a href="https://github.com/lukaszraczylo" class="text-sky-400 hover:text-sky-300 transition">Lukasz Raczylo</a></p>
|
||||
<p class="mt-1.5 sm:mt-2">MIT License</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
// Mobile menu toggle
|
||||
const mobileMenuToggle = document.getElementById("mobile-menu-toggle");
|
||||
const mobileMenu = document.getElementById("mobile-menu");
|
||||
const menuOpenIcon = document.getElementById("menu-open-icon");
|
||||
const menuCloseIcon = document.getElementById("menu-close-icon");
|
||||
|
||||
mobileMenuToggle.addEventListener("click", () => {
|
||||
mobileMenu.classList.toggle("hidden");
|
||||
menuOpenIcon.classList.toggle("hidden");
|
||||
menuCloseIcon.classList.toggle("hidden");
|
||||
});
|
||||
|
||||
const mobileMenuLinks = mobileMenu.querySelectorAll("a");
|
||||
mobileMenuLinks.forEach(link => {
|
||||
link.addEventListener("click", () => {
|
||||
mobileMenu.classList.add("hidden");
|
||||
menuOpenIcon.classList.remove("hidden");
|
||||
menuCloseIcon.classList.add("hidden");
|
||||
});
|
||||
});
|
||||
|
||||
// Theme toggle
|
||||
const themeToggle = document.getElementById("theme-toggle");
|
||||
themeToggle.addEventListener("click", () => {
|
||||
if (document.documentElement.classList.contains("dark")) {
|
||||
document.documentElement.classList.remove("dark");
|
||||
localStorage.theme = "light";
|
||||
} else {
|
||||
document.documentElement.classList.add("dark");
|
||||
localStorage.theme = "dark";
|
||||
}
|
||||
});
|
||||
|
||||
// Copy to clipboard
|
||||
function copyToClipboard(text, button) {
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(text).then(() => showCopySuccess(button)).catch(() => fallbackCopy(text, button));
|
||||
} else {
|
||||
fallbackCopy(text, button);
|
||||
}
|
||||
}
|
||||
|
||||
function fallbackCopy(text, button) {
|
||||
const textarea = document.createElement("textarea");
|
||||
textarea.value = text;
|
||||
textarea.style.position = "fixed";
|
||||
textarea.style.opacity = "0";
|
||||
document.body.appendChild(textarea);
|
||||
textarea.select();
|
||||
try {
|
||||
document.execCommand("copy") ? showCopySuccess(button) : showCopyError(button);
|
||||
} catch (err) {
|
||||
showCopyError(button);
|
||||
}
|
||||
document.body.removeChild(textarea);
|
||||
}
|
||||
|
||||
function showCopySuccess(button) {
|
||||
const original = button.innerHTML;
|
||||
button.innerHTML = '<i class="fas fa-check text-green-500"></i>';
|
||||
setTimeout(() => button.innerHTML = original, 2000);
|
||||
}
|
||||
|
||||
function showCopyError(button) {
|
||||
const original = button.innerHTML;
|
||||
button.innerHTML = '<i class="fas fa-times text-red-500"></i>';
|
||||
setTimeout(() => button.innerHTML = original, 2000);
|
||||
}
|
||||
|
||||
// Smooth scrolling
|
||||
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
||||
anchor.addEventListener("click", function(e) {
|
||||
e.preventDefault();
|
||||
const target = document.querySelector(this.getAttribute("href"));
|
||||
if (target) target.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,19 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 56" fill="none">
|
||||
<!-- Paper plane glyph in Telegram blue gradient -->
|
||||
<defs>
|
||||
<linearGradient id="planeGradD" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0%" stop-color="#29B6F6"/>
|
||||
<stop offset="100%" stop-color="#4FC3F7"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<!-- Plane body -->
|
||||
<polygon points="4,28 44,12 36,44" fill="url(#planeGradD)" opacity="0.15"/>
|
||||
<polygon points="4,28 44,12 36,44" stroke="url(#planeGradD)" stroke-width="1.5" fill="none"/>
|
||||
<!-- Plane wings / send arrow -->
|
||||
<path d="M4 28 L44 12 L30 30 Z" fill="url(#planeGradD)"/>
|
||||
<path d="M30 30 L36 44 L24 34 Z" fill="url(#planeGradD)" opacity="0.7"/>
|
||||
<!-- Fold line -->
|
||||
<line x1="30" y1="30" x2="24" y2="34" stroke="white" stroke-width="1" opacity="0.8"/>
|
||||
<!-- Wordmark -->
|
||||
<text x="56" y="35" font-family="Inter, sans-serif" font-weight="700" font-size="20" fill="#e5e7eb" letter-spacing="-0.5">go-telegram</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 977 B |
@@ -1,19 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 56" fill="none">
|
||||
<!-- Paper plane glyph in Telegram blue gradient -->
|
||||
<defs>
|
||||
<linearGradient id="planeGradL" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0%" stop-color="#29B6F6"/>
|
||||
<stop offset="100%" stop-color="#0288D1"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<!-- Plane body -->
|
||||
<polygon points="4,28 44,12 36,44" fill="url(#planeGradL)" opacity="0.15"/>
|
||||
<polygon points="4,28 44,12 36,44" stroke="url(#planeGradL)" stroke-width="1.5" fill="none"/>
|
||||
<!-- Plane wings / send arrow -->
|
||||
<path d="M4 28 L44 12 L30 30 Z" fill="url(#planeGradL)"/>
|
||||
<path d="M30 30 L36 44 L24 34 Z" fill="url(#planeGradL)" opacity="0.7"/>
|
||||
<!-- Fold line -->
|
||||
<line x1="30" y1="30" x2="24" y2="34" stroke="white" stroke-width="1" opacity="0.8"/>
|
||||
<!-- Wordmark -->
|
||||
<text x="56" y="35" font-family="Inter, sans-serif" font-weight="700" font-size="20" fill="#1f2937" letter-spacing="-0.5">go-telegram</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 977 B |
@@ -1,25 +0,0 @@
|
||||
# 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
|
||||
-15292
File diff suppressed because it is too large
Load Diff
@@ -1,579 +0,0 @@
|
||||
<!-- 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>)
|
||||
@@ -1,668 +0,0 @@
|
||||
<!-- 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>)
|
||||
@@ -1,243 +0,0 @@
|
||||
<!-- 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>)
|
||||
@@ -1,55 +0,0 @@
|
||||
<!-- 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>)
|
||||
@@ -1,35 +0,0 @@
|
||||
<!-- 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>)
|
||||
@@ -1,35 +0,0 @@
|
||||
<!-- 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>)
|
||||
@@ -1,45 +0,0 @@
|
||||
<!-- 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>)
|
||||
@@ -1,155 +0,0 @@
|
||||
<!-- 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 api.MessageEntityType\) 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 api.MessageEntityType) dispatch.Filter[*api.Message]
|
||||
```
|
||||
|
||||
HasEntity returns a Filter that matches messages whose Entities contain at least one entity of type t \(e.g. api.MessageEntityTypeBotCommand\).
|
||||
|
||||
<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>)
|
||||
@@ -1,35 +0,0 @@
|
||||
<!-- 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>)
|
||||
@@ -1,241 +0,0 @@
|
||||
<!-- 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>)
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,499 @@
|
||||
# go-telegram — Design Spec
|
||||
|
||||
- **Date:** 2026-05-08
|
||||
- **Module:** `github.com/lukaszraczylo/go-telegram`
|
||||
- **License:** MIT
|
||||
- **Status:** Approved for planning
|
||||
- **Author:** Lukasz Raczylo (with Claude assistance)
|
||||
|
||||
## 1. Purpose & scope
|
||||
|
||||
A Go library for the Telegram Bot API, primarily a portfolio piece showcasing:
|
||||
|
||||
- Codegen-driven full API coverage (parsed from `https://core.telegram.org/bots/api`)
|
||||
- Pragmatic Go generics
|
||||
- Pluggable HTTP transport and JSON codec for resource-conscious deployments
|
||||
- Long-poll and webhook update delivery behind a unified interface
|
||||
- A typed dispatcher/router for handlers, commands, and callbacks
|
||||
- Comprehensive testify-based unit tests with golden fixtures for codegen
|
||||
|
||||
Out of scope for v1: the daily auto-regen GitHub Action (deferred — see §11).
|
||||
|
||||
## 2. Non-goals
|
||||
|
||||
- Competing on completeness/maturity with `mymmrac/telego` or `go-telegram-bot-api/telegram-bot-api`. We optimise for clarity and design.
|
||||
- Bot-framework features beyond the dispatcher (no plugin marketplace, no FSM, no scenes).
|
||||
- A documentation website. `pkg.go.dev` + README is sufficient.
|
||||
|
||||
## 3. Requirements
|
||||
|
||||
### Functional
|
||||
|
||||
1. Cover all Telegram Bot API methods and types via codegen.
|
||||
2. Support long-poll and webhook update delivery, both implementing one `Updater` interface.
|
||||
3. Allow the user to swap the HTTP client (e.g. `valyala/fasthttp`) and JSON codec (e.g. `goccy/go-json`).
|
||||
4. Provide a typed dispatcher with command, text-regex, callback, and inline-query matching plus generic middleware.
|
||||
5. Provide unit tests using `stretchr/testify` covering happy paths and explicit edge cases.
|
||||
6. Provide an optional integration test suite gated by build tag and env vars.
|
||||
|
||||
### Non-functional
|
||||
|
||||
- Lean dependency footprint (stdlib + `golang.org/x/net/html` + testify).
|
||||
- Deterministic, reproducible codegen (`go test ./... -run TestGen` is hermetic).
|
||||
- Generated files committed to the repo so consumers do not need to run codegen.
|
||||
- Doc comments on every exported symbol; generated types carry Telegram's verbatim prose.
|
||||
|
||||
## 4. Architecture
|
||||
|
||||
Two-stage codegen with a JSON intermediate representation:
|
||||
|
||||
```
|
||||
HTML page cmd/scrape api.json (IR) cmd/genapi api/*.gen.go
|
||||
```
|
||||
|
||||
The IR is committed; PRs from a future regen workflow show the diff against the previous IR, providing a readable changelog of Telegram-side changes.
|
||||
|
||||
### 4.1 Repository layout
|
||||
|
||||
```
|
||||
go-telegram/
|
||||
├── api/ GENERATED types + method wrappers (typed param structs)
|
||||
│ ├── types.gen.go
|
||||
│ ├── methods.gen.go
|
||||
│ └── enums.gen.go
|
||||
├── client/ HAND Bot client, request building, error handling
|
||||
│ ├── client.go
|
||||
│ ├── codec.go Codec interface + encoding/json default
|
||||
│ ├── httpclient.go HTTPDoer interface + net/http default
|
||||
│ ├── errors.go Typed APIError, NetworkError, ParseError
|
||||
│ └── result.go generic Result[T] decode
|
||||
├── transport/ HAND Updater abstraction
|
||||
│ ├── updater.go Updater interface
|
||||
│ ├── longpoll.go LongPoller
|
||||
│ └── webhook.go WebhookServer
|
||||
├── dispatch/ HAND Handler router
|
||||
│ ├── router.go Router, OnCommand/OnCallback/OnText
|
||||
│ ├── middleware.go Generic Middleware[T]
|
||||
│ └── context.go Per-update context
|
||||
├── internal/
|
||||
│ └── spec/ Shared IR types
|
||||
│ ├── ir.go Types describing parsed Telegram API
|
||||
│ └── api.json Committed golden IR (regenerated by scraper)
|
||||
├── cmd/
|
||||
│ ├── scrape/ HTML → api.json
|
||||
│ └── genapi/ api.json → api/*.gen.go
|
||||
├── examples/
|
||||
│ ├── echo/ Long-poll echo bot
|
||||
│ └── webhook/ Webhook bot with command router
|
||||
├── testdata/
|
||||
│ ├── html/ Golden HTML snapshots for scraper
|
||||
│ ├── golden/ Expected api.json + emitted Go for codegen tests
|
||||
│ └── responses/ Canned Telegram JSON responses
|
||||
├── .github/workflows/
|
||||
│ └── ci.yml lint + test + codegen-clean check
|
||||
├── Makefile regen, test, lint targets
|
||||
├── go.mod module github.com/lukaszraczylo/go-telegram
|
||||
├── LICENSE MIT
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## 5. Core types and client (`client/`)
|
||||
|
||||
### 5.1 Pluggability interfaces
|
||||
|
||||
```go
|
||||
// Codec is the JSON encoder/decoder. Default impl wraps encoding/json.
|
||||
type Codec interface {
|
||||
Marshal(v any) ([]byte, error)
|
||||
Unmarshal(data []byte, v any) error
|
||||
}
|
||||
|
||||
// HTTPDoer is the HTTP transport. Default is *http.Client.
|
||||
type HTTPDoer interface {
|
||||
Do(req *http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
// Logger is a slog-shaped interface; nil-safe default writes nowhere.
|
||||
type Logger interface {
|
||||
Debug(msg string, attrs ...any)
|
||||
Info(msg string, attrs ...any)
|
||||
Warn(msg string, attrs ...any)
|
||||
Error(msg string, attrs ...any)
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 Bot client
|
||||
|
||||
```go
|
||||
type Bot struct {
|
||||
token string
|
||||
base string // https://api.telegram.org
|
||||
http HTTPDoer
|
||||
codec Codec
|
||||
logger Logger
|
||||
}
|
||||
|
||||
type Option func(*Bot)
|
||||
func WithHTTPClient(c HTTPDoer) Option
|
||||
func WithCodec(c Codec) Option
|
||||
func WithBaseURL(url string) Option
|
||||
func WithLogger(l Logger) Option
|
||||
|
||||
func New(token string, opts ...Option) *Bot
|
||||
```
|
||||
|
||||
Constructor-level functional options only; per-call params are typed structs (codegen-friendly).
|
||||
|
||||
### 5.3 Result envelope and call helper
|
||||
|
||||
```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"`
|
||||
}
|
||||
|
||||
// Single point for marshalling, URL signing, decoding, error mapping.
|
||||
// Used by every generated method wrapper.
|
||||
func call[Req, Resp any](ctx context.Context, b *Bot, method string, req Req) (Resp, error)
|
||||
```
|
||||
|
||||
Generated wrappers stay thin:
|
||||
|
||||
```go
|
||||
func (b *Bot) SendMessage(ctx context.Context, p *SendMessageParams) (*Message, error) {
|
||||
return call[*SendMessageParams, *Message](ctx, b, "sendMessage", p)
|
||||
}
|
||||
```
|
||||
|
||||
### 5.4 Errors
|
||||
|
||||
```go
|
||||
type APIError struct {
|
||||
Code int
|
||||
Description string
|
||||
Parameters *ResponseParameters // retry_after, migrate_to_chat_id
|
||||
}
|
||||
func (e *APIError) Error() string
|
||||
func (e *APIError) IsRetryable() bool // 429, 5xx
|
||||
func (e *APIError) RetryAfter() time.Duration
|
||||
|
||||
type NetworkError struct{ Err error }
|
||||
type ParseError struct{ Err error; Body []byte }
|
||||
```
|
||||
|
||||
Sentinel errors via `errors.Is`: `ErrUnauthorized`, `ErrChatNotFound`, `ErrMessageNotModified`, `ErrTooManyRequests`. Mapped from `error_code` plus description-prefix matching.
|
||||
|
||||
## 6. Codegen pipeline
|
||||
|
||||
### 6.1 Stage 1 — `cmd/scrape/`
|
||||
|
||||
- Input: live URL `https://core.telegram.org/bots/api` (default) or a local HTML fixture (`-input` flag).
|
||||
- Parser: `golang.org/x/net/html` (no goquery).
|
||||
- Walk strategy: traverse `<h4>` headings sequentially. Lowercase first letter → method. Uppercase → type.
|
||||
- Following `<p>` until next heading → description.
|
||||
- Following `<table>` → fields/params (columns: Field|Type|Required|Description for types; Parameter|Type|Required|Description for methods).
|
||||
- Return type extracted by regex on description: `Returns *X* on success`, `Returns an Array of X`, `Returns True on success`.
|
||||
- Italic markers in the type column denote optional, array depth, and union-member candidates.
|
||||
- "Recent changes" section parsed for current API version.
|
||||
|
||||
### 6.2 Intermediate representation (`internal/spec/ir.go`)
|
||||
|
||||
```go
|
||||
type API struct {
|
||||
Version string
|
||||
Types []TypeDecl
|
||||
Methods []MethodDecl
|
||||
}
|
||||
type TypeDecl struct {
|
||||
Name string
|
||||
Doc string
|
||||
Fields []Field
|
||||
OneOf []string // unions (InputMedia, ChatMember, …)
|
||||
}
|
||||
type MethodDecl struct {
|
||||
Name string // sendMessage
|
||||
Doc string
|
||||
Params []Field
|
||||
Returns TypeRef
|
||||
HasFiles bool // forces multipart
|
||||
}
|
||||
type Field struct {
|
||||
Name string
|
||||
JSONName string
|
||||
Type TypeRef
|
||||
Required bool
|
||||
Doc string
|
||||
}
|
||||
type Kind int
|
||||
const (
|
||||
KindPrimitive Kind = iota
|
||||
KindNamed
|
||||
KindArray
|
||||
KindOneOf
|
||||
)
|
||||
type TypeRef struct {
|
||||
Kind Kind
|
||||
Name string
|
||||
ElemType *TypeRef
|
||||
Variants []string
|
||||
}
|
||||
```
|
||||
|
||||
`internal/spec/api.json` is committed. Marshalling is stable (sorted fields, deterministic JSON output) so diffs read as a Telegram changelog.
|
||||
|
||||
### 6.3 Stage 2 — `cmd/genapi/`
|
||||
|
||||
- Reads `api.json`.
|
||||
- Emits Go via `text/template`, finalised with `go/format`.
|
||||
- Templates:
|
||||
- `types.tmpl` → struct per `TypeDecl`. Optional fields are pointers (or `omitempty` for slices/maps). Doc comments verbatim from API.
|
||||
- `enums.tmpl` → string consts for known enumerations (parse modes, chat types, etc., extracted from doc prose).
|
||||
- `oneof.tmpl` → union types as `interface { isFooBar() }` plus concrete impls and a `UnmarshalJSON` that switches on a discriminator field (typically `type` or `source`).
|
||||
- `methods.tmpl` → param struct + thin `Bot.<MethodName>` wrapper using `call[…]`.
|
||||
- `multipart.tmpl` → for methods with `HasFiles`, custom request builder using `mime/multipart`.
|
||||
- Header on every emitted file: `// Code generated by cmd/genapi. DO NOT EDIT.` + `//go:build !ignore_autogenerated`.
|
||||
|
||||
### 6.4 Makefile contract
|
||||
|
||||
The Makefile owns the codegen entry points; tools and CI call `make`, never raw `go run`:
|
||||
|
||||
| Target | What it does |
|
||||
|---|---|
|
||||
| `make snapshot` | `curl -fsSL https://core.telegram.org/bots/api > testdata/html/snapshot_<date>.html` and update a `latest.html` symlink. |
|
||||
| `make regen` | Run scraper against `testdata/html/latest.html`, then run emitter. Writes `internal/spec/api.json` and `api/*.gen.go`. |
|
||||
| `make regen-from-fixture` | Same as `make regen` but pinned to `testdata/html/snapshot_2026-05-08.html` for deterministic CI checks. |
|
||||
| `make test` | `go test -race ./...` |
|
||||
| `make test-update-golden` | `go test -run TestGen -update ./...` to refresh golden fixtures. |
|
||||
| `make lint` | `go vet` + `staticcheck`. |
|
||||
| `make integration` | `go test -tags=integration ./test/integration/...` (requires env). |
|
||||
|
||||
## 7. Transport (`transport/`)
|
||||
|
||||
```go
|
||||
type Updater interface {
|
||||
Updates() <-chan api.Update
|
||||
Run(ctx context.Context) error
|
||||
Stop(ctx context.Context) error
|
||||
}
|
||||
```
|
||||
|
||||
### 7.1 LongPoller
|
||||
|
||||
```go
|
||||
type LongPoller struct {
|
||||
Bot *client.Bot
|
||||
Timeout int // seconds, default 30
|
||||
Limit int // 1..100, default 100
|
||||
AllowedTypes []api.UpdateType
|
||||
Backoff BackoffStrategy
|
||||
}
|
||||
```
|
||||
|
||||
Calls `getUpdates` in a loop, tracks `offset`, applies exponential backoff on transient errors via `BackoffStrategy`.
|
||||
|
||||
### 7.2 WebhookServer
|
||||
|
||||
```go
|
||||
type WebhookServer struct {
|
||||
Bot *client.Bot
|
||||
SecretToken string // verify X-Telegram-Bot-Api-Secret-Token
|
||||
BufferSize int
|
||||
}
|
||||
func (w *WebhookServer) ServeHTTP(rw http.ResponseWriter, r *http.Request)
|
||||
func (w *WebhookServer) ListenAndServe(ctx context.Context, addr string) error
|
||||
```
|
||||
|
||||
`ServeHTTP` lets users mount on their own router. `ListenAndServe` is a convenience for standalone use.
|
||||
|
||||
## 8. Dispatcher (`dispatch/`)
|
||||
|
||||
```go
|
||||
type Context struct {
|
||||
Ctx context.Context
|
||||
Bot *client.Bot
|
||||
Update *api.Update
|
||||
Values map[string]any // matched groups, command args
|
||||
}
|
||||
|
||||
type Handler[T any] func(ctx *Context, payload T) error
|
||||
type Middleware[T any] func(Handler[T]) Handler[T]
|
||||
|
||||
type Router struct{ /* … */ }
|
||||
|
||||
func New(bot *client.Bot) *Router
|
||||
|
||||
func (r *Router) OnCommand(cmd string, h Handler[*api.Message])
|
||||
func (r *Router) OnText(pattern string, h Handler[*api.Message])
|
||||
func (r *Router) OnCallback(pattern string, h Handler[*api.CallbackQuery])
|
||||
func (r *Router) OnInlineQuery(h Handler[*api.InlineQuery])
|
||||
func (r *Router) OnEditedMessage(h Handler[*api.Message])
|
||||
func (r *Router) Use(mw Middleware[*api.Update])
|
||||
|
||||
func (r *Router) Run(ctx context.Context, u transport.Updater) error
|
||||
```
|
||||
|
||||
Matchers run in registration order; first match wins. A panic-recovery middleware is registered automatically. Generic `Handler[T]` keeps payloads precisely typed in user code.
|
||||
|
||||
## 9. Testing strategy
|
||||
|
||||
### 9.1 Unit tests (every package, fast, no network)
|
||||
|
||||
- `testify/require` for assertions, `testify/mock` on `client.HTTPDoer`.
|
||||
- Edge cases explicitly covered:
|
||||
- API error decode for `429` with `retry_after` → `*APIError.IsRetryable()=true`, `RetryAfter()=N`.
|
||||
- Network error from transport → wrapped `*NetworkError`.
|
||||
- Malformed JSON → `*ParseError`.
|
||||
- Void-result methods (`setWebhook` returning `bool`).
|
||||
- Optional pointer fields nil round-trip.
|
||||
- OneOf union types unmarshal via discriminator.
|
||||
- Multipart upload path for methods with `InputFile`.
|
||||
- Context cancellation mid-call returns `ctx.Err()`.
|
||||
- Long-poller backoff after transient error.
|
||||
- Webhook secret-token mismatch → 401.
|
||||
- Webhook handles oversized body, malformed JSON, wrong content-type.
|
||||
- Router: command match with/without bot mention (`/start@MyBot`), regex matchers, panic recovery, middleware ordering, no-match update.
|
||||
|
||||
### 9.2 Codegen golden tests
|
||||
|
||||
```
|
||||
testdata/html/
|
||||
├── snapshot_2026-05-08.html full Bots API page snapshot
|
||||
└── small_fixture.html hand-crafted minimal page (1 type, 1 method)
|
||||
testdata/golden/
|
||||
├── api.json expected IR from snapshot
|
||||
└── *.gen.go expected emitted Go
|
||||
```
|
||||
|
||||
- `cmd/scrape` test: parse fixture → compare to golden `api.json`.
|
||||
- `cmd/genapi` test: read golden `api.json` → compare emitted Go to golden `*.gen.go`.
|
||||
- `-update` flag (custom in `internal/testutil`) regenerates goldens deliberately.
|
||||
|
||||
### 9.3 Optional integration suite
|
||||
|
||||
- Build tag `//go:build integration`.
|
||||
- Skipped by default `go test ./...`.
|
||||
- Activated by `go test -tags=integration ./test/integration/...`.
|
||||
- Requires `TELEGRAM_BOT_TOKEN` (and `TELEGRAM_TEST_CHAT_ID` where applicable).
|
||||
- Covers `getMe`, `sendMessage`, `setWebhook`/`deleteWebhook`, `getUpdates` loop with short timeout.
|
||||
- Not part of default CI to avoid flakes.
|
||||
|
||||
## 10. CI
|
||||
|
||||
`.github/workflows/ci.yml` (every push and PR):
|
||||
|
||||
- `actions/setup-go` matrix: 1.23, 1.24
|
||||
- `go vet ./...`
|
||||
- `staticcheck ./...`
|
||||
- `go test -race -coverprofile=coverage.out ./...`
|
||||
- Codegen-clean check: `make regen-from-fixture` + `git diff --exit-code` to assert generated files match the committed IR for the snapshot fixture (deterministic).
|
||||
- Upload coverage artifact.
|
||||
|
||||
## 11. Handling API changes & test maintenance
|
||||
|
||||
Telegram ships changes to the Bot API roughly every 1–3 months: new methods, new types, added optional fields, occasional removals or renames, occasional union-variant additions. The design must absorb these with minimum manual work.
|
||||
|
||||
### 11.1 Test-suite invariants under change
|
||||
|
||||
Tests are layered so that the cost of an API change is bounded.
|
||||
|
||||
| Test layer | Affected by API change? | Cost |
|
||||
|---|---|---|
|
||||
| `client/` unit tests (call helper, error mapping, codec, multipart builder) | No — they target `call[Req,Resp]`, not specific methods. | Zero. |
|
||||
| `transport/` unit tests (long-poll loop, webhook server) | No — they target update plumbing, not payload fields. | Zero. |
|
||||
| `dispatch/` unit tests (matchers, middleware, router) | No — generic over `*api.Update`. | Zero. |
|
||||
| Codegen golden tests (`cmd/scrape`, `cmd/genapi`) | Yes — golden `api.json` and `*.gen.go` will diff. | Refresh goldens deliberately (`go test -run TestGen -update`). |
|
||||
| Codegen "shape" smoke tests | Only if a code-generation pattern changes. | One test per pattern, not per method. |
|
||||
| Examples (`examples/echo`, `examples/webhook`) | Only if they reference a removed/renamed symbol. | Hand-fix; rare. |
|
||||
| Integration suite (build tag `integration`) | Only for the ~5 methods it touches. | Hand-fix on removal/rename; rare. |
|
||||
|
||||
The deliberate invariant: **we do not write a test per generated method.** All ~100+ generated wrappers funnel through `call[Req,Resp]` and the multipart builder; coverage of those two paths covers them all. Each generated method is then sanity-checked by being type-checked at compile time against the IR.
|
||||
|
||||
### 11.2 Shape smoke tests
|
||||
|
||||
In `api/api_test.go`, one test per code-generation pattern, hitting a representative method through a mocked `HTTPDoer`:
|
||||
|
||||
- **Simple** — `getMe` (no params, scalar response).
|
||||
- **Typed-struct param** — `sendMessage` (struct in, object out).
|
||||
- **Optional fields** — `sendMessage` with only required fields set; verify omitted fields do not appear in the request body.
|
||||
- **Array result** — `getUpdates` (array of `Update`).
|
||||
- **Bool result** — `setWebhook`.
|
||||
- **Multipart upload** — `sendDocument` with an `InputFile` (verify content-type, boundary, field names).
|
||||
- **OneOf union response** — `getChatMember` (returns `ChatMember` union).
|
||||
- **OneOf union request** — `sendMediaGroup` (accepts `[]InputMedia` union).
|
||||
|
||||
If new code-generation patterns appear (Telegram introducing a new shape we have not seen), one new shape test is added — not one per affected method.
|
||||
|
||||
### 11.3 Categories of change and how each is absorbed
|
||||
|
||||
| Change | Pipeline effect | Test effect |
|
||||
|---|---|---|
|
||||
| New type | Appears in `api.json`, emitted into `types.gen.go`. | Golden diff only. Refresh. |
|
||||
| New optional field | Same. | Golden diff only. |
|
||||
| New required field | Same. Breaking for users who construct that struct literally. | Golden diff only; example code may need update. |
|
||||
| Removed type/method/field | Disappears from emitted Go. Breaking for users referring to it. | Golden diff. Integration test or example may break — fix or skip. |
|
||||
| Renamed field | Old name disappears, new appears. | Same as above; no automatic rename (we treat as remove + add). |
|
||||
| New method | Wrapper generated; no new test required (shape tests cover the call path). | Golden diff. |
|
||||
| Return type changed for an existing method | Wrapper signature changes. Breaking. | Golden diff; integration test for that method may break. |
|
||||
| New OneOf variant | `UnmarshalJSON` switch grows a case. | Golden diff. If a brand-new variant style appears, may require scraper work and a new shape test. |
|
||||
| Telegram doc layout change | Scraper may misparse. | Scraper unit test against the new HTML fixture should be added before regenerating. |
|
||||
|
||||
### 11.4 The change procedure
|
||||
|
||||
When the scraper output changes:
|
||||
|
||||
1. Run `make regen-from-fixture` against the current `testdata/html/snapshot_*.html` (deterministic check) — confirm zero unrelated diffs.
|
||||
2. Capture a fresh HTML snapshot: `make snapshot` (writes `testdata/html/snapshot_<date>.html`).
|
||||
3. Run `make regen` against the new snapshot → IR diff appears in `internal/spec/api.json`.
|
||||
4. Review the IR diff as a Telegram changelog. This is the human read-through; it is the entire point of having an IR.
|
||||
5. Run `go test ./...` → expect golden codegen diffs.
|
||||
6. Refresh goldens: `go test -run TestGen -update`.
|
||||
7. Re-run `go test ./...` → green.
|
||||
8. If shape tests reveal a new code-generation pattern (e.g. a never-before-seen union shape), extend templates and add a shape test before refreshing goldens.
|
||||
9. If `examples/` or integration tests reference a removed symbol, fix them.
|
||||
10. Commit with a clear message: `chore(api): regenerate from Telegram Bot API vX.Y` plus a bullet list extracted from the IR diff (added/changed/removed types and methods).
|
||||
|
||||
This is the same procedure the future auto-regen workflow will run; doing it by hand first ensures the workflow has nothing surprising to do.
|
||||
|
||||
### 11.5 Versioning policy
|
||||
|
||||
- Library SemVer is decoupled from Telegram's API version.
|
||||
- Telegram-side additions → minor bump.
|
||||
- Telegram-side removals or signature changes → major bump (we do not preserve removed symbols as deprecated stubs; the breaking change ships).
|
||||
- Bug fixes in hand-written code → patch bump.
|
||||
- Each release records the Telegram API version it was generated against in the release notes and in a `// Generated from Bot API vX.Y` constant in `api/version.gen.go`.
|
||||
|
||||
## 12. Future work (deferred)
|
||||
|
||||
- **Auto-regen workflow** — daily cron + `workflow_dispatch` that runs `cmd/scrape` against the live URL, regenerates code, opens a PR with a diff summary, and auto-merges on green CI. Implementation sketch retained in design discussion; not part of v1 acceptance.
|
||||
- **Release workflow** — tag-triggered `goreleaser` pipeline producing GH Releases. SemVer for the library; Telegram's API version recorded in release notes.
|
||||
- **Additional codecs/HTTP adapters** as separate sub-packages or contrib modules so users can opt in without bringing transitive deps into the core.
|
||||
|
||||
## 13. Dependency policy
|
||||
|
||||
Production:
|
||||
- Go standard library
|
||||
- `golang.org/x/net/html` (scraper only)
|
||||
|
||||
Test-only:
|
||||
- `github.com/stretchr/testify`
|
||||
|
||||
Explicit non-deps: `goquery`, `cobra`, third-party logging, third-party HTTP clients, third-party JSON codecs (these are user-supplied via `HTTPDoer` and `Codec`).
|
||||
|
||||
## 14. Acceptance criteria
|
||||
|
||||
v1 is done when:
|
||||
|
||||
1. `go test ./...` passes on a clean checkout with no env vars.
|
||||
2. `make regen` produces zero diff against the committed `api.json` and `api/*.gen.go` when run against the committed HTML fixture.
|
||||
3. `examples/echo` and `examples/webhook` build and the echo example runs end-to-end against a real bot when `TELEGRAM_BOT_TOKEN` is set.
|
||||
4. `go vet`, `staticcheck`, and `go test -race` are clean.
|
||||
5. Every exported symbol in hand-written packages has a doc comment.
|
||||
6. README covers Why, Install, Quick Start, Custom HTTP/JSON, Webhooks, Dispatcher, Updating, Contributing.
|
||||
7. The integration test suite (`-tags=integration`) runs cleanly when env is provided.
|
||||
|
||||
## 15. Open questions
|
||||
|
||||
None at sign-off. (Auto-regen behaviour intentionally deferred.)
|
||||
@@ -68,7 +68,7 @@ func TestHandleStart_SendsInitialKeyboard(t *testing.T) {
|
||||
bot := client.New("test:token", client.WithHTTPClient(m))
|
||||
msg := &api.Message{
|
||||
MessageID: 1,
|
||||
Chat: api.Chat{ID: 42, Type: api.ChatTypePrivate},
|
||||
Chat: api.Chat{ID: 42, Type: string(api.ChatTypePrivate)},
|
||||
From: &api.User{ID: 7, FirstName: "Alice"},
|
||||
Text: "/start",
|
||||
}
|
||||
@@ -88,7 +88,7 @@ func callbackCtx(bot *client.Bot, q *api.CallbackQuery, groups []string) *dispat
|
||||
func callbackQuery(data string, msgID int64, chatID int64) *api.CallbackQuery {
|
||||
msg := &api.Message{
|
||||
MessageID: msgID,
|
||||
Chat: api.Chat{ID: chatID, Type: api.ChatTypePrivate},
|
||||
Chat: api.Chat{ID: chatID, Type: string(api.ChatTypePrivate)},
|
||||
}
|
||||
return &api.CallbackQuery{
|
||||
ID: "cb1",
|
||||
|
||||
@@ -46,7 +46,7 @@ func msgUpd(userID, chatID int64, text string) api.Update {
|
||||
}
|
||||
}
|
||||
entities = append(entities, api.MessageEntity{
|
||||
Type: api.MessageEntityTypeBotCommand,
|
||||
Type: string(api.EntityBotCommand),
|
||||
Offset: 0,
|
||||
Length: int64(end),
|
||||
})
|
||||
@@ -56,7 +56,7 @@ func msgUpd(userID, chatID int64, text string) api.Update {
|
||||
Message: &api.Message{
|
||||
MessageID: 1,
|
||||
From: &api.User{ID: userID},
|
||||
Chat: api.Chat{ID: chatID, Type: api.ChatTypePrivate},
|
||||
Chat: api.Chat{ID: chatID, Type: string(api.ChatTypePrivate)},
|
||||
Text: text,
|
||||
Entities: entities,
|
||||
},
|
||||
|
||||
@@ -55,7 +55,7 @@ func TestHandleStart_GreetsUser(t *testing.T) {
|
||||
bot := client.New("test:token", client.WithHTTPClient(m))
|
||||
msg := &api.Message{
|
||||
MessageID: 1,
|
||||
Chat: api.Chat{ID: 42, Type: api.ChatTypePrivate},
|
||||
Chat: api.Chat{ID: 42, Type: string(api.ChatTypePrivate)},
|
||||
From: &api.User{ID: 7, FirstName: "Alice"},
|
||||
Text: "/start",
|
||||
}
|
||||
@@ -82,7 +82,7 @@ func TestHandleEcho_RepliesWithSameText(t *testing.T) {
|
||||
bot := client.New("test:token", client.WithHTTPClient(m))
|
||||
msg := &api.Message{
|
||||
MessageID: 5,
|
||||
Chat: api.Chat{ID: 42, Type: api.ChatTypePrivate},
|
||||
Chat: api.Chat{ID: 42, Type: string(api.ChatTypePrivate)},
|
||||
From: &api.User{ID: 7, FirstName: "Alice"},
|
||||
Text: "hello echo",
|
||||
}
|
||||
|
||||
+14
-11
@@ -96,10 +96,11 @@ func kickHandler(c *dispatch.Context, m *api.Message) error {
|
||||
}); err != nil {
|
||||
return handleAdminErr(c, m.Chat.ID, err)
|
||||
}
|
||||
truVal := true
|
||||
if _, err := api.UnbanChatMember(c.Ctx, c.Bot, &api.UnbanChatMemberParams{
|
||||
ChatID: api.ChatIDFromInt(m.Chat.ID),
|
||||
UserID: target,
|
||||
OnlyIfBanned: api.Ptr(true),
|
||||
OnlyIfBanned: &truVal,
|
||||
}); err != nil {
|
||||
log.Printf("unban after kick: %v", err)
|
||||
}
|
||||
@@ -129,21 +130,23 @@ func muteHandler(c *dispatch.Context, m *api.Message) error {
|
||||
reply(c, m.Chat.ID, "Reply to a user's message with /mute to silence them for 1 hour.")
|
||||
return nil
|
||||
}
|
||||
until := time.Now().Add(time.Hour).Unix()
|
||||
falseVal := false
|
||||
if _, err := api.RestrictChatMember(c.Ctx, c.Bot, &api.RestrictChatMemberParams{
|
||||
ChatID: api.ChatIDFromInt(m.Chat.ID),
|
||||
UserID: target,
|
||||
Permissions: api.ChatPermissions{
|
||||
CanSendMessages: api.Ptr(false),
|
||||
CanSendAudios: api.Ptr(false),
|
||||
CanSendDocuments: api.Ptr(false),
|
||||
CanSendPhotos: api.Ptr(false),
|
||||
CanSendVideos: api.Ptr(false),
|
||||
CanSendVideoNotes: api.Ptr(false),
|
||||
CanSendVoiceNotes: api.Ptr(false),
|
||||
CanSendPolls: api.Ptr(false),
|
||||
CanSendOtherMessages: api.Ptr(false),
|
||||
CanSendMessages: &falseVal,
|
||||
CanSendAudios: &falseVal,
|
||||
CanSendDocuments: &falseVal,
|
||||
CanSendPhotos: &falseVal,
|
||||
CanSendVideos: &falseVal,
|
||||
CanSendVideoNotes: &falseVal,
|
||||
CanSendVoiceNotes: &falseVal,
|
||||
CanSendPolls: &falseVal,
|
||||
CanSendOtherMessages: &falseVal,
|
||||
},
|
||||
UntilDate: api.Ptr(time.Now().Add(time.Hour).Unix()),
|
||||
UntilDate: &until,
|
||||
}); err != nil {
|
||||
return handleAdminErr(c, m.Chat.ID, err)
|
||||
}
|
||||
|
||||
@@ -94,11 +94,12 @@ func main() {
|
||||
})
|
||||
return nil
|
||||
}
|
||||
isAnon := false
|
||||
msg, err := api.SendPoll(c.Ctx, c.Bot, &api.SendPollParams{
|
||||
ChatID: api.ChatIDFromInt(m.Chat.ID),
|
||||
Question: question,
|
||||
Options: pollOptions,
|
||||
IsAnonymous: api.Ptr(false),
|
||||
IsAnonymous: &isAnon,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -49,7 +49,7 @@ func TestHandlePing_RepliesWithPong(t *testing.T) {
|
||||
bot := client.New("test:token", client.WithHTTPClient(m))
|
||||
msg := &api.Message{
|
||||
MessageID: 1,
|
||||
Chat: api.Chat{ID: 42, Type: api.ChatTypePrivate},
|
||||
Chat: api.Chat{ID: 42, Type: string(api.ChatTypePrivate)},
|
||||
From: &api.User{ID: 7, FirstName: "Alice"},
|
||||
Text: "/ping",
|
||||
}
|
||||
|
||||
+123
-699
File diff suppressed because it is too large
Load Diff
@@ -45,11 +45,6 @@ type Field struct {
|
||||
Type TypeRef `json:"type"`
|
||||
Required bool `json:"required,omitempty"`
|
||||
Doc string `json:"doc,omitempty"`
|
||||
// EnumValues, when non-empty, lists the wire-level string values the
|
||||
// scraper detected for an enum-like description ("can be A, B or C",
|
||||
// "always X", parse_mode special-case). Order is doc order, deduped.
|
||||
// Emitted as a typed Go enum that replaces the field's string type.
|
||||
EnumValues []string `json:"enum_values,omitempty"`
|
||||
}
|
||||
|
||||
// Kind enumerates TypeRef shapes.
|
||||
|
||||
+1
-6
@@ -98,12 +98,7 @@
|
||||
"kind": "primitive",
|
||||
"name": "string"
|
||||
},
|
||||
"doc": "Mode for parsing entities in the message text.",
|
||||
"enum_values": [
|
||||
"Markdown",
|
||||
"MarkdownV2",
|
||||
"HTML"
|
||||
]
|
||||
"doc": "Mode for parsing entities in the message text."
|
||||
}
|
||||
],
|
||||
"returns": {
|
||||
|
||||
Vendored
+48
-1
@@ -4,10 +4,57 @@
|
||||
|
||||
package api
|
||||
|
||||
// ParseMode controls how Telegram interprets formatting in message text.
|
||||
type ParseMode string
|
||||
|
||||
const (
|
||||
ParseModeMarkdown ParseMode = "Markdown"
|
||||
ParseModeMarkdown ParseMode = "Markdown" // legacy
|
||||
ParseModeMarkdownV2 ParseMode = "MarkdownV2"
|
||||
ParseModeHTML ParseMode = "HTML"
|
||||
)
|
||||
|
||||
// ChatType is the type of a Telegram chat.
|
||||
type ChatType string
|
||||
|
||||
const (
|
||||
ChatTypePrivate ChatType = "private"
|
||||
ChatTypeGroup ChatType = "group"
|
||||
ChatTypeSupergroup ChatType = "supergroup"
|
||||
ChatTypeChannel ChatType = "channel"
|
||||
)
|
||||
|
||||
// UpdateType identifies an Update payload variant. Used by allowed_updates
|
||||
// in getUpdates / setWebhook.
|
||||
type UpdateType string
|
||||
|
||||
const (
|
||||
UpdateMessage UpdateType = "message"
|
||||
UpdateEditedMessage UpdateType = "edited_message"
|
||||
UpdateChannelPost UpdateType = "channel_post"
|
||||
UpdateEditedChannelPost UpdateType = "edited_channel_post"
|
||||
UpdateCallbackQuery UpdateType = "callback_query"
|
||||
UpdateInlineQuery UpdateType = "inline_query"
|
||||
)
|
||||
|
||||
// MessageEntityType is the kind of an entity (mention, hashtag, command, ...).
|
||||
type MessageEntityType string
|
||||
|
||||
const (
|
||||
EntityMention MessageEntityType = "mention"
|
||||
EntityHashtag MessageEntityType = "hashtag"
|
||||
EntityCashtag MessageEntityType = "cashtag"
|
||||
EntityBotCommand MessageEntityType = "bot_command"
|
||||
EntityURL MessageEntityType = "url"
|
||||
EntityEmail MessageEntityType = "email"
|
||||
EntityPhoneNumber MessageEntityType = "phone_number"
|
||||
EntityBold MessageEntityType = "bold"
|
||||
EntityItalic MessageEntityType = "italic"
|
||||
EntityUnderline MessageEntityType = "underline"
|
||||
EntityStrike MessageEntityType = "strikethrough"
|
||||
EntitySpoiler MessageEntityType = "spoiler"
|
||||
EntityCode MessageEntityType = "code"
|
||||
EntityPre MessageEntityType = "pre"
|
||||
EntityTextLink MessageEntityType = "text_link"
|
||||
EntityTextMention MessageEntityType = "text_mention"
|
||||
EntityCustomEmoji MessageEntityType = "custom_emoji"
|
||||
)
|
||||
|
||||
Vendored
+1
-1
@@ -37,7 +37,7 @@ type SendMessageParams struct {
|
||||
// Text of the message to be sent.
|
||||
Text string `json:"text"`
|
||||
// Mode for parsing entities in the message text.
|
||||
ParseMode ParseMode `json:"parse_mode,omitempty"`
|
||||
ParseMode string `json:"parse_mode,omitempty"`
|
||||
}
|
||||
|
||||
// SendMessage calls the sendMessage Telegram Bot API method.
|
||||
|
||||
Reference in New Issue
Block a user