diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e29149..7f6ed49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,46 @@ # Changelog +## v2.0.0-rc.1 (Unreleased) + +### Added +- Added Bot API 10.3 rich buttons: `RichMessageButton`, `RichTextButton`, `RichBlockButtons`, and `InputRichBlockButtons`, including inline-query chat selection, login, clipboard, and disabled actions. Empty inline queries are preserved through pointer fields; `RichMessageButtonStyleLink` supports borderless callback buttons. +- Added incoming and outgoing expandable block quotations and document blocks, with `tgrich.Button`, `Buttons`, `ButtonsWithAlign`, `ExpandableBlockquote`, `ExpandableBlockquoteWithCredit`, `Document`, and `DocumentWithCaption` helpers. +- Added compact rich tables through `IsCompact` and `RichTable.SetCompact`, including JSON decoding and HTML rendering. +- Added rich-button validation for a single action, permitted label entities and styles, callback byte limits, copy-text length, row size, and alignment. Invalid values return `ErrRichInvalidButton` from `tgrich` builders. +- Added HTML rendering for rich buttons, expandable quotations, and documents. Document media is collected into `InputRichMessage.Media` with `tg://document?id=` references and supports multipart attachments through the existing rich-message uploader. +- Added `EphemeralMessageParameters`, including `ReplaceCallbackQueryMessage`, to JSON and multipart send parameters for text, rich messages, animation, audio, documents, live photos, photos, stickers, video, video notes, voice, contacts, locations, and venues. Ephemeral media editing now supports multipart attachments through `Uploader.EditEphemeralMessageMedia`. +- Added `RichMessage` to `EditEphemeralMessageText` and `ShowCaptionAboveMedia` to `EditEphemeralMessageCaption`. +- Added `CanStop` and `KeepOnStop` to message and rich-message draft parameters. Stopped-generation updates are decoded as `UpdateTypeMessageGenerationStopped` and expose their chat through normalized message context. +- Added disabled inline buttons and `SetDisabled`, plus `ForceReply` support on specialized inline and reply keyboard markup. +- Added `CommunityChatJoined` service messages; `UniqueGiftInfo.Text`, `Entities`, and `IsPrivate`; and `CanSendWelcomeMessages` to administrator members, rights, and promotion parameters. +- Added `RichTextUnknown` and `RichBlockUnknown` to preserve unrecognized Telegram rich objects without losing fields. +- Added explicit `InlineKeyboard.SetUnlimitedRows` configuration. + +### Breaking changes +- Changed the Go module and import path to `git.scuroneko.dev/scuroneko/laniakea/v2`, raised the minimum Go version to 1.27, and removed the v1-only retraction directive. +- `PollAnswer.User` and `VoterChat` are now pointers; removed `VoterUser` and `VoterChatInfo`. Update-context normalization and tests use the pointer fields. +- `RunnerFn` and `NewRunner` now require callbacks with `context.Context`; removed `ContextRunnerFn` and `NewContextRunner`. +- `InlineKeyboard.Get` now validates and returns `(*tgapi.ReplyMarkup, error)`; removed `GetValidated` and keyboard-level `Validate`. Message-context helpers reject invalid keyboards before sending or editing. Previously built rows exceeding a subsequently lowered row limit now fail final validation. +- `CallbackData.Encode` now returns `(string, error)` and validates encoding and payload length; removed `EncodeValidated`. +- Moved custom request execution from `TelegramRequest.Do` / `DoWithContext` to `API.Do` / `DoWithContext`, and from `UploaderRequest` methods to the corresponding `Uploader` methods. +- Renamed `ChatFullInfo.AvailableReaction` to `AvailableReactions`, `ReplyParameters.QuoteParsingMode` to `QuoteParseMode`, and `InputChecklist.OtherCanAddTasks` / `OtherCanMarkTasksAsDone` to `OthersCanAddTasks` / `OthersCanMarkTasksAsDone`; JSON keys remain unchanged. +- Removed `DeleteAllMessageReactionWithContext`; use `DeleteAllMessageReactionsWithContext`. +- The JSON and multipart send parameter structs listed above replace top-level `ReceiverUserID` and `CallbackQueryID` with `EphemeralMessageParameters`. +- `UnmarshalRichMessage` now requires a non-null `blocks` array. Unknown rich objects use lossless raw wrappers instead of lossy text wrappers or errors. +- Removed the unbounded `API.GetFileByLink` and `GetFileByLinkWithContext`; use `GetFileByLinkLimit*` with an explicit byte limit or `OpenFileByLink*` for streaming. +- Non-positive inline-keyboard row limits are invalid by default; call `SetUnlimitedRows` to disable automatic wrapping explicitly. + +### Changed +- Prepared the declared version and user-agent metadata for `v2.0.0-rc.1`. +- Updated English and Russian README runner and keyboard examples for the final v2 contracts, and generalized the prerelease policy for the v2 RC cycle. + +### Fixed +- Fixed nested rich-text validation skipping children of formatting and link entities, which could bypass text-size and nesting limits. + +### Tests +- Migrated runner, poll-voter, keyboard, message-context, and wire-format tests to the new contracts. +- Added rich-button action round trips, malformed-input and nesting cases, row and callback boundaries, HTML escaping, compact tables, expandable quotations, document media, multipart attachments, draft-upload coverage, Bot API 10.3 wire fields, stopped-generation routing, strict root parsing, lossless unknown objects, bounded downloads, explicit unlimited rows, and compile-focused v1-to-v2 migration coverage. + ## v1.2.0 ### Added diff --git a/README.md b/README.md index f16552c..db0c818 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ![Laniakea](assets/logo.jpg) -[![Go Version](https://img.shields.io/badge/Go-1.26+-00ADD8?logo=go&style=flat-square)](https://go.dev/) +[![Go Version](https://img.shields.io/badge/Go-1.27+-00ADD8?logo=go&style=flat-square)](https://go.dev/) [![License: GPL-3.0](https://img.shields.io/badge/License-GPL%203.0-blue.svg?style=flat-square)](LICENSE) ![Gitea Release](https://img.shields.io/gitea/v/release/ScuroNeko/Laniakea?gitea_url=https%3A%2F%2Fgit.scuroneko.dev&sort=semver&display_name=release&style=flat-square&color=purple&link=https%3A%2F%2Fgit.scuroneko.dev%2FScuroNeko%2FLaniakea%2Freleases) @@ -30,13 +30,7 @@ A lightweight, easy-to-use, and performant Telegram Bot API wrapper for Go. It s ## 📦 Installation ```bash -go get git.scuroneko.dev/scuroneko/laniakea -``` - -or - -```bash -go get github.com/scuroneko/laniakea +go get git.scuroneko.dev/scuroneko/laniakea/v2@v2.0.0-rc.1 ``` ## 🚀 Quick Start (with step-by-step explanation) @@ -48,7 +42,7 @@ package main import ( "log" - "git.scuroneko.dev/scuroneko/laniakea" // Import the Laniakea library + "git.scuroneko.dev/scuroneko/laniakea/v2" // Import the Laniakea library ) // echo is a command handler function. @@ -288,32 +282,32 @@ import ( // One-shot runner — fires once in a goroutine when the bot starts (default). bot.AddRunner( - laniakea.NewRunner("seed-cache", func(b *laniakea.Bot[*MyDB]) error { - return b.GetAppData().SeedCache() - }), + laniakea.NewRunner("seed-cache", func(ctx context.Context, b *laniakea.Bot[*MyDB]) error { + return b.GetAppData().SeedCache(ctx) + }), ) // Periodic runner — fires every 10 minutes in a goroutine. bot.AddRunner( - laniakea.NewContextRunner("refresh-stats", func(ctx context.Context, b *laniakea.Bot[*MyDB]) error { - return b.GetAppData().RefreshStats(ctx) - }).Every(10 * time.Minute), + laniakea.NewRunner("refresh-stats", func(ctx context.Context, b *laniakea.Bot[*MyDB]) error { + return b.GetAppData().RefreshStats(ctx) + }).Every(10 * time.Minute), ) // Synchronous one-shot — blocks runtime startup until it completes. bot.AddRunner( - laniakea.NewRunner("migrate", func(b *laniakea.Bot[*MyDB]) error { - return b.GetAppData().Migrate() - }).Async(false), + laniakea.NewRunner("migrate", func(ctx context.Context, b *laniakea.Bot[*MyDB]) error { + return b.GetAppData().Migrate(ctx) + }).Async(false), ) ``` Builder methods: -- `Async(bool) *Runner[T]` — if `true` (default), runs in a goroutine; if `false`, blocks runtime startup. -- `Every(time.Duration) *Runner[T]` — sets the repeat interval. Zero (default) means run once; positive value repeats. Periodic runners require `Async(true)`. +- `Async(bool) Runner[T]` — if `true` (default), runs in a goroutine; if `false`, blocks runtime startup. +- `Every(time.Duration) Runner[T]` — sets the repeat interval. Zero (default) means run once; positive value repeats. Periodic runners require `Async(true)`. -Prefer `NewContextRunner` for I/O and blocking work. Its context is canceled -when polling or webhook execution stops, allowing shutdown to complete. +Every runner receives a context that is canceled when polling or webhook +execution stops. I/O and blocking work should pass it to downstream calls. ## 🧩 Middleware Middleware are functions that run before a command handler. They are perfect for cross-cutting concerns like logging, access control, rate limiting, or modifying the context. @@ -364,7 +358,7 @@ func adminOnlyMiddleware(ctx *laniakea.MessageContext, db *MyDB) bool { ## ⚙️ Advanced Configuration - **Inline Keyboards**: Build keyboards using `laniakea.NewInlineKeyboardJSON`, `laniakea.NewInlineKeyboardBase64`, or `laniakea.NewInlineKeyboard`. `Bot.SetPayloadType(...)` defines the default payload format, and `InlineKeyboard.SetPayloadType(...)` overrides it for one keyboard. -- **Keyboard Validation**: Call `InlineKeyboard.GetValidated()` before sending untrusted or dynamically generated callback payloads; Telegram limits `callback_data` to 1–64 bytes. +- **Keyboard Validation**: `InlineKeyboard.Get()` validates every button and row before returning markup; Telegram limits `callback_data` to 1–64 bytes. Use `SetUnlimitedRows()` when automatic wrapping must be disabled explicitly. - **Rate Limiting**: Pass a configured utils.RateLimiter via BotOpts to handle Telegram's rate limits gracefully. - **Observers**: Runtime observer callbacks are dispatched asynchronously in order through a bounded queue. Slow observers do not block handlers; overload drops events with sampled warnings, and shutdown drains queued events. - **Localization**: `L10n` is safe for concurrent use once attached to the bot. @@ -382,7 +376,7 @@ func adminOnlyMiddleware(ctx *laniakea.MessageContext, db *MyDB) bool { This project is licensed under the GNU General Public License v3.0 — see the [LICENSE](LICENSE) file for details. ## 📚 Learn More -[GoDoc](https://pkg.go.dev/git.scuroneko.dev/scuroneko/laniakea) +[GoDoc](https://pkg.go.dev/git.scuroneko.dev/scuroneko/laniakea/v2) [Wiki](https://git.scuroneko.dev/ScuroNeko/Laniakea/wiki) diff --git a/README_RU.md b/README_RU.md index e102e47..b2afe92 100644 --- a/README_RU.md +++ b/README_RU.md @@ -31,13 +31,7 @@ ## 📦 Установка ```bash -go get git.scuroneko.dev/scuroneko/laniakea -``` - -или - -```bash -go get github.com/scuroneko/laniakea +go get git.scuroneko.dev/scuroneko/laniakea/v2@v2.0.0-rc.1 ``` ## 🚀 Быстрый старт (с пошаговыми комментариями) @@ -49,7 +43,7 @@ package main import ( "log" - "git.scuroneko.dev/scuroneko/laniakea" // Импортируем библиотеку Laniakea + "git.scuroneko.dev/scuroneko/laniakea/v2" // Импортируем библиотеку Laniakea ) // echo — это функция-обработчик команды. @@ -270,32 +264,32 @@ import ( // Одноразовый раннер — запускается один раз в горутине при старте (по умолчанию). bot.AddRunner( - laniakea.NewRunner("seed-cache", func(b *laniakea.Bot[*MyDB]) error { - return b.GetAppData().SeedCache() - }), + laniakea.NewRunner("seed-cache", func(ctx context.Context, b *laniakea.Bot[*MyDB]) error { + return b.GetAppData().SeedCache(ctx) + }), ) // Периодический раннер — запускается каждые 10 минут в горутине. bot.AddRunner( - laniakea.NewContextRunner("refresh-stats", func(ctx context.Context, b *laniakea.Bot[*MyDB]) error { - return b.GetAppData().RefreshStats(ctx) - }).Every(10 * time.Minute), + laniakea.NewRunner("refresh-stats", func(ctx context.Context, b *laniakea.Bot[*MyDB]) error { + return b.GetAppData().RefreshStats(ctx) + }).Every(10 * time.Minute), ) // Синхронный одноразовый — блокирует запуск runtime до завершения. bot.AddRunner( - laniakea.NewRunner("migrate", func(b *laniakea.Bot[*MyDB]) error { - return b.GetAppData().Migrate() - }).Async(false), + laniakea.NewRunner("migrate", func(ctx context.Context, b *laniakea.Bot[*MyDB]) error { + return b.GetAppData().Migrate(ctx) + }).Async(false), ) ``` Методы builder: -- `Async(bool) *Runner[T]` — если `true` (по умолчанию), запускается в горутине; если `false`, блокирует запуск runtime. -- `Every(time.Duration) *Runner[T]` — задаёт интервал повторного запуска. Ноль (по умолчанию) означает одноразовый запуск; положительное значение — периодический. Периодические раннеры требуют `Async(true)`. +- `Async(bool) Runner[T]` — если `true` (по умолчанию), запускается в горутине; если `false`, блокирует запуск runtime. +- `Every(time.Duration) Runner[T]` — задаёт интервал повторного запуска. Ноль (по умолчанию) означает одноразовый запуск; положительное значение — периодический. Периодические раннеры требуют `Async(true)`. -Для I/O и блокирующей работы предпочитайте `NewContextRunner`. Его context -отменяется при остановке polling или webhook runtime, поэтому shutdown может завершиться. +Каждый runner получает context, который отменяется при остановке polling или +webhook runtime. I/O и блокирующие операции должны передавать его во вложенные вызовы. ### tgapi: API и Uploader @@ -362,7 +356,7 @@ func adminOnlyMiddleware(ctx *laniakea.MessageContext, db *MyDB) bool { ## ⚙️ Расширенная настройка - **Инлайн-клавиатуры**: Создавайте клавиатуры с помощью `laniakea.NewInlineKeyboardJSON`, `laniakea.NewInlineKeyboardBase64` или `laniakea.NewInlineKeyboard`. `Bot.SetPayloadType(...)` задаёт payload format по умолчанию, а `InlineKeyboard.SetPayloadType(...)` переопределяет его для конкретной клавиатуры. -- **Валидация клавиатур**: Вызывайте `InlineKeyboard.GetValidated()` перед отправкой недоверенных или динамически собранных callback payload; Telegram ограничивает `callback_data` диапазоном 1–64 байта. +- **Валидация клавиатур**: `InlineKeyboard.Get()` проверяет все кнопки и строки перед возвратом markup; Telegram ограничивает `callback_data` диапазоном 1–64 байта. Для явного отключения автоматического переноса используйте `SetUnlimitedRows()`. - **Ограничение запросов**: Передайте настроенный `utils.RateLimiter` через `BotOpts` для корректной обработки лимитов Telegram. - **Observer**: Во время runtime callbacks observer выполняются асинхронно и по порядку через ограниченную очередь. Медленный observer не блокирует handlers; при переполнении события отбрасываются с редкими предупреждениями, а shutdown обрабатывает уже поставленные в очередь события. - **Локализация**: `L10n` безопасен для конкурентного использования после подключения к боту. @@ -379,7 +373,7 @@ func adminOnlyMiddleware(ctx *laniakea.MessageContext, db *MyDB) bool { Этот проект лицензирован под GNU General Public License v3.0 - подробности см. в файле [LICENSE](LICENSE). ## 📚 Дополнительная информация -[GoDoc Laniakea](https://pkg.go.dev/git.scuroneko.dev/scuroneko/laniakea) +[GoDoc Laniakea](https://pkg.go.dev/git.scuroneko.dev/scuroneko/laniakea/v2) [Wiki](https://git.scuroneko.dev/ScuroNeko/Laniakea/wiki) diff --git a/SEMVER.md b/SEMVER.md index 4422ed4..f1c61b2 100644 --- a/SEMVER.md +++ b/SEMVER.md @@ -41,5 +41,5 @@ A release uses a patch version bump for backward-compatible fixes: ## Pre-Releases -`-rc.N` builds may still adjust API details before `v1.0.0`. -Once `v1.0.0` is released, breaking changes require a new major version. +`-rc.N` builds may still correct release-blocking API defects before the corresponding stable release. +After a stable release, breaking changes require a new major version. diff --git a/bot.go b/bot.go index 4e40828..05780f9 100644 --- a/bot.go +++ b/bot.go @@ -9,8 +9,8 @@ import ( "time" "git.scuroneko.dev/scuroneko/extypes" - "git.scuroneko.dev/scuroneko/laniakea/tgapi" - "git.scuroneko.dev/scuroneko/laniakea/utils" + "git.scuroneko.dev/scuroneko/laniakea/v2/tgapi" + "git.scuroneko.dev/scuroneko/laniakea/v2/utils" "git.scuroneko.dev/scuroneko/sneklog/v2" ) diff --git a/bot_config.go b/bot_config.go index a87fcbc..b84acf6 100644 --- a/bot_config.go +++ b/bot_config.go @@ -4,7 +4,7 @@ import ( "fmt" "slices" - "git.scuroneko.dev/scuroneko/laniakea/tgapi" + "git.scuroneko.dev/scuroneko/laniakea/v2/tgapi" "git.scuroneko.dev/scuroneko/sneklog/v2" ) diff --git a/bot_opts.go b/bot_opts.go index 49478ea..a2941c5 100644 --- a/bot_opts.go +++ b/bot_opts.go @@ -5,8 +5,8 @@ import ( "strconv" "strings" - "git.scuroneko.dev/scuroneko/laniakea/tgapi" - "git.scuroneko.dev/scuroneko/laniakea/utils" + "git.scuroneko.dev/scuroneko/laniakea/v2/tgapi" + "git.scuroneko.dev/scuroneko/laniakea/v2/utils" "git.scuroneko.dev/scuroneko/sneklog/v2" ) diff --git a/bot_opts_loader.go b/bot_opts_loader.go index a4f162a..941fb8b 100644 --- a/bot_opts_loader.go +++ b/bot_opts_loader.go @@ -7,8 +7,8 @@ import ( "os" "regexp" - "git.scuroneko.dev/scuroneko/laniakea/tgapi" - "git.scuroneko.dev/scuroneko/laniakea/utils" + "git.scuroneko.dev/scuroneko/laniakea/v2/tgapi" + "git.scuroneko.dev/scuroneko/laniakea/v2/utils" ) // ConfigVersion is the current version of the built-in JSON BotOpts file format. diff --git a/bot_opts_loader_test.go b/bot_opts_loader_test.go index 44439e8..59ffb7e 100644 --- a/bot_opts_loader_test.go +++ b/bot_opts_loader_test.go @@ -7,7 +7,7 @@ import ( "reflect" "testing" - "git.scuroneko.dev/scuroneko/laniakea/tgapi" + "git.scuroneko.dev/scuroneko/laniakea/v2/tgapi" ) func TestBotOptsFileJSONCodecRoundTrip(t *testing.T) { diff --git a/bot_opts_test.go b/bot_opts_test.go index 7dc3340..a8df36b 100644 --- a/bot_opts_test.go +++ b/bot_opts_test.go @@ -4,7 +4,7 @@ import ( "reflect" "testing" - "git.scuroneko.dev/scuroneko/laniakea/tgapi" + "git.scuroneko.dev/scuroneko/laniakea/v2/tgapi" ) func TestLoadOptsFromEnvIgnoresEmptyUpdateTypes(t *testing.T) { diff --git a/bot_register.go b/bot_register.go index 559e1c5..a328d1a 100644 --- a/bot_register.go +++ b/bot_register.go @@ -4,7 +4,7 @@ import ( "fmt" "sort" - "git.scuroneko.dev/scuroneko/laniakea/utils" + "git.scuroneko.dev/scuroneko/laniakea/v2/utils" ) // AddPlugins registers one or more plugins. diff --git a/bot_test.go b/bot_test.go index 8087237..ab1f662 100644 --- a/bot_test.go +++ b/bot_test.go @@ -12,7 +12,7 @@ import ( "testing" "time" - "git.scuroneko.dev/scuroneko/laniakea/tgapi" + "git.scuroneko.dev/scuroneko/laniakea/v2/tgapi" "git.scuroneko.dev/scuroneko/sneklog/v2" ) @@ -966,8 +966,8 @@ func TestAddPluginsAndRuntimeRegistrationsNoOpAfterRunStarts(t *testing.T) { bot := &Bot[NoData]{ logger: sneklog.NewLogger(), prefixes: []string{"/"}, - middlewares: []Middleware[NoData]{NewMiddleware("base", func(ctx *MessageContext, db NoData) bool { return true })}, - runners: []Runner[NoData]{NewRunner("base", func(bot *Bot[NoData]) error { return nil })}, + middlewares: []Middleware[NoData]{NewMiddleware("base", func(*MessageContext, NoData) bool { return true })}, + runners: []Runner[NoData]{NewRunner("base", func(context.Context, *Bot[NoData]) error { return nil })}, } plugin := NewPlugin[NoData]("late") @@ -977,8 +977,8 @@ func TestAddPluginsAndRuntimeRegistrationsNoOpAfterRunStarts(t *testing.T) { defer bot.finishRun() bot.AddPlugins(plugin) - bot.AddMiddleware(NewMiddleware("late", func(ctx *MessageContext, db NoData) bool { return true })) - bot.AddRunner(NewRunner("late", func(bot *Bot[NoData]) error { return nil })) + bot.AddMiddleware(NewMiddleware("late", func(*MessageContext, NoData) bool { return true })) + bot.AddRunner(NewRunner("late", func(context.Context, *Bot[NoData]) error { return nil })) if len(bot.plugins) != 0 { t.Fatalf("expected AddPlugins to be ignored after configuration freeze, got %d plugins", len(bot.plugins)) diff --git a/bot_utils.go b/bot_utils.go index 2e14406..e2722d3 100644 --- a/bot_utils.go +++ b/bot_utils.go @@ -11,8 +11,8 @@ import ( "time" "git.scuroneko.dev/scuroneko/extypes" - "git.scuroneko.dev/scuroneko/laniakea/tgapi" - "git.scuroneko.dev/scuroneko/laniakea/utils" + "git.scuroneko.dev/scuroneko/laniakea/v2/tgapi" + "git.scuroneko.dev/scuroneko/laniakea/v2/utils" "git.scuroneko.dev/scuroneko/sneklog/v2" "github.com/alitto/pond/v2" diff --git a/bot_webhook.go b/bot_webhook.go index e6be725..5a2918e 100644 --- a/bot_webhook.go +++ b/bot_webhook.go @@ -12,8 +12,8 @@ import ( "strings" "time" - "git.scuroneko.dev/scuroneko/laniakea/tgapi" - "git.scuroneko.dev/scuroneko/laniakea/utils" + "git.scuroneko.dev/scuroneko/laniakea/v2/tgapi" + "git.scuroneko.dev/scuroneko/laniakea/v2/utils" ) // BotWebhookOpts configures Telegram webhook registration and the local HTTP server. diff --git a/bot_webhook_test.go b/bot_webhook_test.go index 9a5e1de..f0f79ab 100644 --- a/bot_webhook_test.go +++ b/bot_webhook_test.go @@ -11,7 +11,7 @@ import ( "testing" "time" - "git.scuroneko.dev/scuroneko/laniakea/tgapi" + "git.scuroneko.dev/scuroneko/laniakea/v2/tgapi" "git.scuroneko.dev/scuroneko/sneklog/v2" ) @@ -91,7 +91,7 @@ func TestRunWebhookRuntimeExecutesRunners(t *testing.T) { updateQueue: make(chan *tgapi.Update, 1), maxWorkers: 1, runners: []Runner[NoData]{ - NewRunner("runner", func(bot *Bot[NoData]) error { + NewRunner("runner", func(context.Context, *Bot[NoData]) error { calls.Add(1) return nil }).Async(false), diff --git a/cmd_generator.go b/cmd_generator.go index 00899a2..bd2adeb 100644 --- a/cmd_generator.go +++ b/cmd_generator.go @@ -9,7 +9,7 @@ import ( "strings" "unicode/utf8" - "git.scuroneko.dev/scuroneko/laniakea/tgapi" + "git.scuroneko.dev/scuroneko/laniakea/v2/tgapi" ) var cmdRegexp = regexp.MustCompile("^[_a-z0-9]{1,32}$") diff --git a/cmd_generator_test.go b/cmd_generator_test.go index 8c9de1a..001e13b 100644 --- a/cmd_generator_test.go +++ b/cmd_generator_test.go @@ -10,7 +10,7 @@ import ( "sync/atomic" "testing" - "git.scuroneko.dev/scuroneko/laniakea/tgapi" + "git.scuroneko.dev/scuroneko/laniakea/v2/tgapi" "git.scuroneko.dev/scuroneko/sneklog/v2" ) diff --git a/drafts.go b/drafts.go index e6ab005..9d1e530 100644 --- a/drafts.go +++ b/drafts.go @@ -5,7 +5,7 @@ import ( "sync" "sync/atomic" - "git.scuroneko.dev/scuroneko/laniakea/tgapi" + "git.scuroneko.dev/scuroneko/laniakea/v2/tgapi" ) type draftIDGenerator interface { diff --git a/drafts_test.go b/drafts_test.go index 411515c..767b5c4 100644 --- a/drafts_test.go +++ b/drafts_test.go @@ -5,7 +5,7 @@ import ( "strings" "testing" - "git.scuroneko.dev/scuroneko/laniakea/tgapi" + "git.scuroneko.dev/scuroneko/laniakea/v2/tgapi" "git.scuroneko.dev/scuroneko/sneklog/v2" ) diff --git a/errors.go b/errors.go index 85d6feb..be21692 100644 --- a/errors.go +++ b/errors.go @@ -76,6 +76,8 @@ var ( ErrCallbackDataLength = errors.New("callback data must be between 1 and 64 bytes") // ErrInlineKeyboardRowTooLong reports a row exceeding the configured maximum. ErrInlineKeyboardRowTooLong = errors.New("inline keyboard row exceeds maximum size") + // ErrInlineKeyboardMaxRow reports a non-positive row limit without explicit unlimited mode. + ErrInlineKeyboardMaxRow = errors.New("inline keyboard maximum row size must be positive") ) var ( diff --git a/go.mod b/go.mod index b1952c8..645f424 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,6 @@ -module git.scuroneko.dev/scuroneko/laniakea +module git.scuroneko.dev/scuroneko/laniakea/v2 -go 1.26 - -retract v1.0.0-rc.5 +go 1.27 require ( git.scuroneko.dev/scuroneko/extypes v1.2.3 diff --git a/handler.go b/handler.go index b27ee99..97c26de 100644 --- a/handler.go +++ b/handler.go @@ -9,7 +9,7 @@ import ( "strings" "time" - "git.scuroneko.dev/scuroneko/laniakea/tgapi" + "git.scuroneko.dev/scuroneko/laniakea/v2/tgapi" ) // ErrInvalidPayloadType is returned when callback payload encoding type is unknown. diff --git a/handler_test.go b/handler_test.go index e4fc34a..f1ce15d 100644 --- a/handler_test.go +++ b/handler_test.go @@ -9,7 +9,7 @@ import ( "testing" "time" - "git.scuroneko.dev/scuroneko/laniakea/tgapi" + "git.scuroneko.dev/scuroneko/laniakea/v2/tgapi" "git.scuroneko.dev/scuroneko/sneklog/v2" ) @@ -317,7 +317,7 @@ func TestPrepareUpdateCtxContract(t *testing.T) { name: "poll answer", update: &tgapi.Update{ Type: tgapi.UpdateTypePollAnswer, - PollAnswer: &tgapi.PollAnswer{User: tgapi.User{ID: 115}}, + PollAnswer: &tgapi.PollAnswer{User: &tgapi.User{ID: 115}}, }, wantFrom: true, wantFromID: 115, @@ -326,7 +326,7 @@ func TestPrepareUpdateCtxContract(t *testing.T) { name: "anonymous poll answer", update: &tgapi.Update{ Type: tgapi.UpdateTypePollAnswer, - PollAnswer: &tgapi.PollAnswer{VoterChat: tgapi.Chat{ID: -2007}}, + PollAnswer: &tgapi.PollAnswer{VoterChat: &tgapi.Chat{ID: -2007}}, }, wantChat: true, wantChatID: -2007, @@ -428,6 +428,18 @@ func TestPrepareUpdateCtxContract(t *testing.T) { wantFrom: true, wantFromID: 121, }, + { + name: "stopped message generation", + update: &tgapi.Update{ + Type: tgapi.UpdateTypeMessageGenerationStopped, + StoppedMessageGeneration: &tgapi.MessageGenerationStopped{ + Chat: tgapi.Chat{ID: -2012}, + DraftID: 7, + }, + }, + wantChat: true, + wantChatID: -2012, + }, { name: "giveaway chat boost has no user", update: &tgapi.Update{ diff --git a/keyboard.go b/keyboard.go index 593bef7..1653572 100644 --- a/keyboard.go +++ b/keyboard.go @@ -4,7 +4,7 @@ import ( "fmt" "git.scuroneko.dev/scuroneko/extypes" - "git.scuroneko.dev/scuroneko/laniakea/tgapi" + "git.scuroneko.dev/scuroneko/laniakea/v2/tgapi" ) const ( @@ -31,8 +31,9 @@ type InlineKeyboardButtonBuilder struct { emojiID string style tgapi.KeyboardButtonStyle - url string - data string + url string + data string + disabled bool payloadType BotPayloadType } @@ -64,10 +65,21 @@ func (b InlineKeyboardButtonBuilder) SetURL(url string) InlineKeyboardButtonBuil b.url = url if url != "" { b.data = "" + b.disabled = false } return b } +// SetDisabled makes the button inert and clears URL and callback actions. +// +// Since: Bot API 10.3 +func (b InlineKeyboardButtonBuilder) SetDisabled() InlineKeyboardButtonBuilder { + b.url = "" + b.data = "" + b.disabled = true + return b +} + // SetPayloadType sets the encoding used by SetCallbackData. func (b InlineKeyboardButtonBuilder) SetPayloadType(t BotPayloadType) InlineKeyboardButtonBuilder { b.payloadType = t @@ -83,6 +95,7 @@ func (b InlineKeyboardButtonBuilder) SetPayloadType(t BotPayloadType) InlineKeyb // Example: SetCallbackDataJSON("delete_user", 123, "confirm") → {"cmd":"delete_user","args":["123","confirm"]}. func (b InlineKeyboardButtonBuilder) SetCallbackDataJSON(cmd string, args ...any) InlineKeyboardButtonBuilder { b.url = "" + b.disabled = false b.data = NewCallbackData(cmd, args...).ToJSON() return b } @@ -91,6 +104,7 @@ func (b InlineKeyboardButtonBuilder) SetCallbackDataJSON(cmd string, args ...any // Base64 does not bypass Telegram's 64-byte callback-data limit. func (b InlineKeyboardButtonBuilder) SetCallbackDataBase64(cmd string, args ...any) InlineKeyboardButtonBuilder { b.url = "" + b.disabled = false b.data = NewCallbackData(cmd, args...).ToBase64() return b } @@ -98,6 +112,7 @@ func (b InlineKeyboardButtonBuilder) SetCallbackDataBase64(cmd string, args ...a // SetCallbackDataCompact sets a structured callback payload encoded as compact text. func (b InlineKeyboardButtonBuilder) SetCallbackDataCompact(cmd string, args ...any) InlineKeyboardButtonBuilder { b.url = "" + b.disabled = false b.data = NewCallbackData(cmd, args...).ToCompact() return b } @@ -105,6 +120,7 @@ func (b InlineKeyboardButtonBuilder) SetCallbackDataCompact(cmd string, args ... // SetCallbackDataCompactBase64 sets a compact callback payload encoded as Base64. func (b InlineKeyboardButtonBuilder) SetCallbackDataCompactBase64(cmd string, args ...any) InlineKeyboardButtonBuilder { b.url = "" + b.disabled = false b.data = NewCallbackData(cmd, args...).ToCompactBase64() return b } @@ -113,6 +129,7 @@ func (b InlineKeyboardButtonBuilder) SetCallbackDataCompactBase64(cmd string, ar // The default payload type is JSON. func (b InlineKeyboardButtonBuilder) SetCallbackData(cmd string, args ...any) InlineKeyboardButtonBuilder { b.url = "" + b.disabled = false switch b.payloadType { case BotPayloadJSON: b.data = NewCallbackData(cmd, args...).ToJSON() @@ -128,27 +145,32 @@ func (b InlineKeyboardButtonBuilder) SetCallbackData(cmd string, args ...any) In return b } +func (b InlineKeyboardButtonBuilder) validate() error { return validateButton(b.build()) } + func (b InlineKeyboardButtonBuilder) build() tgapi.InlineKeyboardButton { + var disabled *tgapi.DisabledButton + if b.disabled { + disabled = &tgapi.DisabledButton{} + } return tgapi.InlineKeyboardButton{ Text: b.text, URL: b.url, Style: b.style, IconCustomEmojiID: b.emojiID, CallbackData: b.data, + Disabled: disabled, } } // Validate checks that the button has exactly one action and valid callback data. -func (b InlineKeyboardButtonBuilder) Validate() error { - return validateInlineKeyboardButton(b.build()) -} +func (b InlineKeyboardButtonBuilder) Validate() error { return b.validate() } // Build validates and returns the configured inline keyboard button. func (b InlineKeyboardButtonBuilder) Build() (tgapi.InlineKeyboardButton, error) { - button := b.build() - if err := validateInlineKeyboardButton(button); err != nil { + if err := b.validate(); err != nil { return tgapi.InlineKeyboardButton{}, err } + button := b.build() return button, nil } @@ -162,13 +184,14 @@ type InlineKeyboard struct { // CurrentLine is the row currently being built. CurrentLine extypes.Slice[tgapi.InlineKeyboardButton] // Lines contains completed keyboard rows. - Lines [][]tgapi.InlineKeyboardButton - maxRow int // Max buttons per row (e.g., 3 or 4) + Lines [][]tgapi.InlineKeyboardButton + maxRow int // Max buttons per row (e.g., 3 or 4) + unlimitedRows bool payloadType BotPayloadType // Serialization format for callback data (JSON or Base64) } -// NewInlineKeyboardJSON creates a new keyboard builder with the specified maximum +// NewInlineKeyboardJSON creates a keyboard builder with a positive maximum // number of buttons per row. // // Example: NewInlineKeyboardJSON(3) creates a keyboard with at most 3 buttons per line. @@ -176,7 +199,7 @@ func NewInlineKeyboardJSON(maxRow int) *InlineKeyboard { return NewInlineKeyboard(BotPayloadJSON, maxRow) } -// NewInlineKeyboardBase64 creates a new keyboard builder with the specified maximum +// NewInlineKeyboardBase64 creates a keyboard builder with a positive maximum // number of buttons per row, using Base64 encoding for button payloads. // // Example: NewInlineKeyboardBase64(3) creates a keyboard with at most 3 buttons per line. @@ -194,8 +217,8 @@ func NewInlineKeyboardCompactBase64(maxRow int) *InlineKeyboard { return NewInlineKeyboard(BotPayloadCompactBase64, maxRow) } -// NewInlineKeyboard creates a new keyboard builder with the specified payload encoding -// type and maximum number of buttons per row. +// NewInlineKeyboard creates a keyboard builder with the specified payload encoding +// and a positive maximum number of buttons per row. // // Use NewInlineKeyboardJSON or NewInlineKeyboardBase64 for the common cases. func NewInlineKeyboard(payloadType BotPayloadType, maxRow int) *InlineKeyboard { @@ -218,19 +241,27 @@ func (in *InlineKeyboard) SetPayloadType(t BotPayloadType) *InlineKeyboard { // GetPayloadType returns the keyboard-local callback payload encoding type. func (in *InlineKeyboard) GetPayloadType() BotPayloadType { return in.payloadType } -// SetMaxRow sets the maximum number of buttons appended to a row before the -// keyboard automatically starts a new line. Values <= 0 retain the legacy -// unlimited-row behavior; this convention is subject to change in v2. +// SetMaxRow sets the positive number of buttons appended to a row before the +// keyboard automatically starts a new line. Get returns ErrInlineKeyboardMaxRow +// when maxRow is not positive. func (in *InlineKeyboard) SetMaxRow(maxRow int) *InlineKeyboard { in.maxRow = maxRow + in.unlimitedRows = false return in } -// GetMaxRow returns the maximum number of buttons per row. +// SetUnlimitedRows disables automatic row wrapping explicitly. +func (in *InlineKeyboard) SetUnlimitedRows() *InlineKeyboard { + in.maxRow = 0 + in.unlimitedRows = true + return in +} + +// GetMaxRow returns the maximum number of buttons per row, or zero in explicit unlimited mode. func (in *InlineKeyboard) GetMaxRow() int { return in.maxRow } func (in *InlineKeyboard) append(button tgapi.InlineKeyboardButton) *InlineKeyboard { - if in.maxRow > 0 && in.CurrentLine.Len() >= in.maxRow { + if !in.unlimitedRows && in.maxRow > 0 && in.CurrentLine.Len() >= in.maxRow { in.AddLine() } in.CurrentLine = in.CurrentLine.Push(button) @@ -254,7 +285,7 @@ func (in *InlineKeyboard) AddURLButtonStyle(text string, style tgapi.KeyboardBut func (in *InlineKeyboard) AddCallbackButton(text, cmd string, args ...any) *InlineKeyboard { return in.append(tgapi.InlineKeyboardButton{ Text: text, - CallbackData: NewCallbackData(cmd, args...).Encode(in.payloadType), + CallbackData: NewCallbackData(cmd, args...).encode(in.payloadType), }) } @@ -264,7 +295,7 @@ func (in *InlineKeyboard) AddCallbackButtonStyle(text string, style tgapi.Keyboa return in.append(tgapi.InlineKeyboardButton{ Text: text, Style: style, - CallbackData: NewCallbackData(cmd, args...).Encode(in.payloadType), + CallbackData: NewCallbackData(cmd, args...).encode(in.payloadType), }) } @@ -289,7 +320,7 @@ func (in *InlineKeyboard) AddLine() *InlineKeyboard { // Automatically flushes the current line if not empty. // // Returns a pointer to a ReplyMarkup suitable for use with tgapi.SendMessage. -func (in *InlineKeyboard) Get() *tgapi.ReplyMarkup { +func (in *InlineKeyboard) Get() (*tgapi.ReplyMarkup, error) { if in.CurrentLine.Len() > 0 { in.AddLine() } @@ -297,38 +328,26 @@ func (in *InlineKeyboard) Get() *tgapi.ReplyMarkup { for i := range in.Lines { lines[i] = append([]tgapi.InlineKeyboardButton(nil), in.Lines[i]...) } - return &tgapi.ReplyMarkup{InlineKeyboard: lines} -} - -// GetValidated finalizes and validates the keyboard before returning it. -// -// Existing fluent Add* methods remain error-free for v1 compatibility. Their -// signatures are subject to change in v2; new code should use GetValidated. -func (in *InlineKeyboard) GetValidated() (*tgapi.ReplyMarkup, error) { - markup := in.Get() - if err := in.validateMarkup(markup); err != nil { + markup := &tgapi.ReplyMarkup{InlineKeyboard: lines} + if err := in.validate(); err != nil { return nil, err } return markup, nil } -// Validate checks completed and pending rows without finalizing the keyboard. -func (in *InlineKeyboard) Validate() error { - lines := make([][]tgapi.InlineKeyboardButton, 0, len(in.Lines)+1) - lines = append(lines, in.Lines...) - if len(in.CurrentLine) > 0 { - lines = append(lines, in.CurrentLine) +func (in *InlineKeyboard) validate() error { + if !in.unlimitedRows && in.maxRow <= 0 { + return fmt.Errorf("%w: got %d", ErrInlineKeyboardMaxRow, in.maxRow) } - return in.validateMarkup(&tgapi.ReplyMarkup{InlineKeyboard: lines}) -} - -func (in *InlineKeyboard) validateMarkup(markup *tgapi.ReplyMarkup) error { - for rowIndex, row := range markup.InlineKeyboard { - if in.maxRow > 0 && len(row) > in.maxRow { - return fmt.Errorf("%w: row %d has %d buttons, limit %d", ErrInlineKeyboardRowTooLong, rowIndex, len(row), in.maxRow) + for rowIndex, row := range in.Lines { + if !in.unlimitedRows && len(row) > in.maxRow { + return fmt.Errorf( + "%w: row %d has %d buttons, limit %d", + ErrInlineKeyboardRowTooLong, rowIndex, len(row), in.maxRow, + ) } for columnIndex, button := range row { - if err := validateInlineKeyboardButton(button); err != nil { + if err := validateButton(button); err != nil { return fmt.Errorf("row %d button %d: %w", rowIndex, columnIndex, err) } } @@ -336,19 +355,22 @@ func (in *InlineKeyboard) validateMarkup(markup *tgapi.ReplyMarkup) error { return nil } -func validateInlineKeyboardButton(button tgapi.InlineKeyboardButton) error { +func validateButton(b tgapi.InlineKeyboardButton) error { actions := 0 - if button.URL != "" { + if b.URL != "" { actions++ } - if button.CallbackData != "" { + if b.CallbackData != "" { + actions++ + } + if b.Disabled != nil { actions++ } if actions != 1 { return ErrInlineKeyboardButtonAction } - if button.CallbackData != "" { - length := len([]byte(button.CallbackData)) + if b.CallbackData != "" { + length := len([]byte(b.CallbackData)) if length < 1 || length > 64 { return fmt.Errorf("%w: got %d", ErrCallbackDataLength, length) } @@ -436,10 +458,7 @@ func (d CallbackData) ToCompactBase64() string { return data } -// Encode serializes the CallbackData according to the specified payload type. -// Supported types: BotPayloadJSON, BotPayloadBase64, BotPayloadCompact, and BotPayloadCompactBase64. -// For unknown types, returns an empty string. -func (d CallbackData) Encode(t BotPayloadType) string { +func (d CallbackData) encode(t BotPayloadType) string { switch t { case BotPayloadBase64: return d.ToBase64() @@ -453,13 +472,15 @@ func (d CallbackData) Encode(t BotPayloadType) string { return "" } -// EncodeValidated serializes callback data and enforces Telegram's 1-64 byte limit. -func (d CallbackData) EncodeValidated(t BotPayloadType) (string, error) { - encoded := d.Encode(t) +// Encode serializes the CallbackData according to the specified payload type. +// Supported types: BotPayloadJSON, BotPayloadBase64, BotPayloadCompact, and BotPayloadCompactBase64. +// For unknown types, returns an empty string. +func (d CallbackData) Encode(t BotPayloadType) (string, error) { + if t != BotPayloadBase64 && t != BotPayloadJSON && t != BotPayloadCompact && t != BotPayloadCompactBase64 { + return "", ErrInvalidPayloadType + } + encoded := d.encode(t) if encoded == "" { - if t != BotPayloadBase64 && t != BotPayloadJSON && t != BotPayloadCompact && t != BotPayloadCompactBase64 { - return "", ErrInvalidPayloadType - } return "", ErrCallbackDataLength } if length := len([]byte(encoded)); length > 64 { diff --git a/keyboard_test.go b/keyboard_test.go index a9edd19..18045bd 100644 --- a/keyboard_test.go +++ b/keyboard_test.go @@ -13,7 +13,10 @@ func TestInlineKeyboardWrapsRowsAndEncodesJSONPayloads(t *testing.T) { AddCallbackButton("B", "cmd", 2). AddCallbackButton("C", "cmd", 3) - markup := kb.Get() + markup, err := kb.Get() + if err != nil { + t.Fatalf("keyboard encoding error: %s", err) + } if got := len(markup.InlineKeyboard); got != 2 { t.Fatalf("unexpected row count: %d", got) } @@ -36,7 +39,11 @@ func TestInlineKeyboardBuilderPreservesConfiguredButtonFields(t *testing.T) { SetURL("https://example.test"), ) - button := kb.Get().InlineKeyboard[0][0] + markup, err := kb.Get() + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + button := markup.InlineKeyboard[0][0] if button.Style != ButtonStylePrimary { t.Fatalf("unexpected style: %q", button.Style) } @@ -49,7 +56,11 @@ func TestInlineKeyboardButtonBuilderSetCallbackDataDefaultsToJSON(t *testing.T) kb := NewInlineKeyboardBase64(1). AddButton(NewInlineKeyboardButton("A").SetCallbackData("cmd", 1, "two")) - button := kb.Get().InlineKeyboard[0][0] + markup, err := kb.Get() + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + button := markup.InlineKeyboard[0][0] if !strings.Contains(button.CallbackData, `"cmd":"cmd"`) { t.Fatalf("expected JSON callback payload, got %q", button.CallbackData) } @@ -62,7 +73,12 @@ func TestInlineKeyboardButtonBuilderSetCallbackDataUsesConfiguredPayloadType(t * SetCallbackData("cmd", 1, "two"), ) - got, _, err := decodePayload(BotPayloadJSON, kb.Get().InlineKeyboard[0][0].CallbackData, false) + markup, err := kb.Get() + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + button := markup.InlineKeyboard[0][0] + got, _, err := decodePayload(BotPayloadJSON, button.CallbackData, false) if err != nil { t.Fatalf("decodePayload returned error: %v", err) } @@ -93,10 +109,16 @@ func TestInlineKeyboardButtonBuilderKeepsExactlyOneAction(t *testing.T) { func TestInlineKeyboardGetReturnsIndependentMarkup(t *testing.T) { keyboard := NewInlineKeyboardJSON(1).AddURLButton("Docs", "https://example.test") - first := keyboard.Get() + first, err := keyboard.Get() + if err != nil { + t.Fatalf("unexpected err: %v", err) + } first.InlineKeyboard[0][0].Text = "mutated" - second := keyboard.Get() + second, err := keyboard.Get() + if err != nil { + t.Fatalf("unexpected err: %v", err) + } if got := second.InlineKeyboard[0][0].Text; got != "Docs" { t.Fatalf("Get exposed builder state for mutation: got %q", got) } @@ -117,7 +139,11 @@ func TestDecodePayloadAcceptsBase64KeyboardPayloadWhenBotPrefersJSON(t *testing. kb := NewInlineKeyboardBase64(1). AddCallbackButton("A", "cmd", 1, "two") - got, _, err := decodePayload(BotPayloadJSON, kb.Get().InlineKeyboard[0][0].CallbackData, false) + markup, err := kb.Get() + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + got, _, err := decodePayload(BotPayloadJSON, markup.InlineKeyboard[0][0].CallbackData, false) if err != nil { t.Fatalf("decodePayload returned error: %v", err) } @@ -132,7 +158,11 @@ func TestDecodePayloadAcceptsJSONKeyboardPayloadWhenBotPrefersBase64(t *testing. kb := NewInlineKeyboardJSON(1). AddCallbackButton("A", "cmd", 1, "two") - got, _, err := decodePayload(BotPayloadBase64, kb.Get().InlineKeyboard[0][0].CallbackData, false) + markup, err := kb.Get() + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + got, _, err := decodePayload(BotPayloadBase64, markup.InlineKeyboard[0][0].CallbackData, false) if err != nil { t.Fatalf("decodePayload returned error: %v", err) } @@ -147,7 +177,11 @@ func TestDecodePayloadAcceptsCompactKeyboardPayloadWhenBotPrefersJSON(t *testing kb := NewInlineKeyboardCompact(1). AddCallbackButton("A", "cmd", 1, "two") - got, decodedType, err := decodePayload(BotPayloadJSON, kb.Get().InlineKeyboard[0][0].CallbackData, false) + markup, err := kb.Get() + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + got, decodedType, err := decodePayload(BotPayloadJSON, markup.InlineKeyboard[0][0].CallbackData, false) if err != nil { t.Fatalf("decodePayload returned error: %v", err) } @@ -165,7 +199,11 @@ func TestDecodePayloadAcceptsCompactBase64KeyboardPayloadWhenBotPrefersJSON(t *t kb := NewInlineKeyboardCompactBase64(1). AddCallbackButton("A", "cmd", 1, "two") - got, decodedType, err := decodePayload(BotPayloadJSON, kb.Get().InlineKeyboard[0][0].CallbackData, false) + markup, err := kb.Get() + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + got, decodedType, err := decodePayload(BotPayloadJSON, markup.InlineKeyboard[0][0].CallbackData, false) if err != nil { t.Fatalf("decodePayload returned error: %v", err) } @@ -237,7 +275,11 @@ func TestDecodePayloadStrictRejectsCompactMismatchedType(t *testing.T) { kb := NewInlineKeyboardCompact(1). AddCallbackButton("A", "cmd", 1) - _, _, err := decodePayload(BotPayloadJSON, kb.Get().InlineKeyboard[0][0].CallbackData, true) + markup, err := kb.Get() + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + _, _, err = decodePayload(BotPayloadJSON, markup.InlineKeyboard[0][0].CallbackData, true) if !errors.Is(err, ErrPayloadTypeMismatch) { t.Fatalf("expected ErrPayloadTypeMismatch, got %v", err) } @@ -247,7 +289,11 @@ func TestDecodePayloadStrictRejectsMismatchedType(t *testing.T) { kb := NewInlineKeyboardBase64(1). AddCallbackButton("A", "cmd", 1) - _, _, err := decodePayload(BotPayloadJSON, kb.Get().InlineKeyboard[0][0].CallbackData, true) + markup, err := kb.Get() + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + _, _, err = decodePayload(BotPayloadJSON, markup.InlineKeyboard[0][0].CallbackData, true) if !errors.Is(err, ErrPayloadTypeMismatch) { t.Fatalf("expected ErrPayloadTypeMismatch, got %v", err) } @@ -265,12 +311,12 @@ func TestInlineKeyboardValidation(t *testing.T) { } keyboard := NewInlineKeyboardCompact(1).AddButton(button) - if _, err := keyboard.GetValidated(); !errors.Is(err, ErrCallbackDataLength) { + if _, err := keyboard.Get(); !errors.Is(err, ErrCallbackDataLength) { t.Fatalf("expected validated keyboard to reject callback data, got %v", err) } } -func TestCallbackDataEncodeValidatedBoundaries(t *testing.T) { +func TestCallbackDataEncodeBoundaries(t *testing.T) { tests := []struct { name string command string @@ -283,7 +329,7 @@ func TestCallbackDataEncodeValidatedBoundaries(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - encoded, err := (CallbackData{Command: tt.command}).EncodeValidated(BotPayloadCompact) + encoded, err := (CallbackData{Command: tt.command}).Encode(BotPayloadCompact) if !errors.Is(err, tt.wantErr) { t.Fatalf("expected %v, got %v", tt.wantErr, err) } @@ -292,7 +338,7 @@ func TestCallbackDataEncodeValidatedBoundaries(t *testing.T) { } }) } - if _, err := (CallbackData{Command: "ok"}).EncodeValidated(BotPayloadType("unknown")); !errors.Is(err, ErrInvalidPayloadType) { + if _, err := (CallbackData{Command: "ok"}).Encode(BotPayloadType("unknown")); !errors.Is(err, ErrInvalidPayloadType) { t.Fatalf("expected ErrInvalidPayloadType, got %v", err) } } @@ -304,8 +350,50 @@ func TestInlineKeyboardLoweredMaxRowStillWraps(t *testing.T) { SetMaxRow(1). AddURLButton("C", "https://example.test/c") - markup := keyboard.Get() - if len(markup.InlineKeyboard) != 2 || len(markup.InlineKeyboard[0]) != 2 || len(markup.InlineKeyboard[1]) != 1 { - t.Fatalf("lowered maxRow stopped automatic wrapping: %#v", markup.InlineKeyboard) + markup, err := keyboard.Get() + if !errors.Is(err, ErrInlineKeyboardRowTooLong) || markup != nil { + t.Fatalf("expected strict validation failure for the existing oversized row: %v", err) + } + if len(keyboard.Lines) != 2 || len(keyboard.Lines[0]) != 2 || len(keyboard.Lines[1]) != 1 { + t.Fatalf("lowered maxRow stopped automatic wrapping: %#v", keyboard.Lines) + } +} + +func TestInlineKeyboardUnlimitedRowsMustBeExplicit(t *testing.T) { + for _, maxRow := range []int{0, -1} { + keyboard := NewInlineKeyboardJSON(maxRow).AddURLButton("A", "https://example.test/a") + if markup, err := keyboard.Get(); !errors.Is(err, ErrInlineKeyboardMaxRow) || markup != nil { + t.Fatalf("expected invalid row limit %d, markup=%#v err=%v", maxRow, markup, err) + } + } + + keyboard := NewInlineKeyboardJSON(1). + SetUnlimitedRows(). + AddURLButton("A", "https://example.test/a"). + AddURLButton("B", "https://example.test/b") + markup, err := keyboard.Get() + if err != nil { + t.Fatalf("explicit unlimited rows failed: %v", err) + } + if len(markup.InlineKeyboard) != 1 || len(markup.InlineKeyboard[0]) != 2 { + t.Fatalf("unexpected unlimited keyboard: %#v", markup.InlineKeyboard) + } +} + +func TestInlineKeyboardDisabledButton(t *testing.T) { + button, err := NewInlineKeyboardButton("Unavailable").SetDisabled().Build() + if err != nil { + t.Fatalf("Build returned error: %v", err) + } + if button.Disabled == nil || button.URL != "" || button.CallbackData != "" { + t.Fatalf("unexpected disabled button: %#v", button) + } + + button, err = NewInlineKeyboardButton("Open").SetDisabled().SetURL("https://example.test").Build() + if err != nil { + t.Fatalf("SetURL didn't replace disabled action: %v", err) + } + if button.Disabled != nil || button.URL == "" { + t.Fatalf("unexpected URL button: %#v", button) } } diff --git a/methods.go b/methods.go index fd20dcb..e44d118 100644 --- a/methods.go +++ b/methods.go @@ -5,7 +5,7 @@ import ( "encoding/json" "iter" - "git.scuroneko.dev/scuroneko/laniakea/tgapi" + "git.scuroneko.dev/scuroneko/laniakea/v2/tgapi" ) // Updates fetches new updates from Telegram API using long polling. diff --git a/methods_test.go b/methods_test.go index 1e95a79..a391edc 100644 --- a/methods_test.go +++ b/methods_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - "git.scuroneko.dev/scuroneko/laniakea/tgapi" + "git.scuroneko.dev/scuroneko/laniakea/v2/tgapi" ) func TestUpdatesIterYieldsFetchError(t *testing.T) { diff --git a/migration_v2_test.go b/migration_v2_test.go new file mode 100644 index 0000000..41d8196 --- /dev/null +++ b/migration_v2_test.go @@ -0,0 +1,32 @@ +package laniakea + +import ( + "context" + "testing" +) + +func TestV2MigrationSurfaceCompiles(t *testing.T) { + runner := NewRunner[NoData]("sync", func(ctx context.Context, bot *Bot[NoData]) error { + return ctx.Err() + }) + if runner.name != "sync" { + t.Fatalf("unexpected runner: %+v", runner) + } + + if _, err := NewCallbackData("open", 42).Encode(BotPayloadCompact); err != nil { + t.Fatalf("Encode returned error: %v", err) + } + + keyboard := NewInlineKeyboardJSON(2).AddCallbackButton("Open", "open", 42) + if _, err := keyboard.Get(); err != nil { + t.Fatalf("Get returned error: %v", err) + } + + unlimited := NewInlineKeyboardJSON(1). + SetUnlimitedRows(). + AddURLButton("A", "https://example.test/a"). + AddURLButton("B", "https://example.test/b") + if _, err := unlimited.Get(); err != nil { + t.Fatalf("explicit unlimited keyboard failed: %v", err) + } +} diff --git a/msg_context.go b/msg_context.go index e1e93e5..a387f0d 100644 --- a/msg_context.go +++ b/msg_context.go @@ -9,8 +9,8 @@ import ( "strconv" "strings" - "git.scuroneko.dev/scuroneko/laniakea/tgapi" - "git.scuroneko.dev/scuroneko/laniakea/tgrich" + "git.scuroneko.dev/scuroneko/laniakea/v2/tgapi" + "git.scuroneko.dev/scuroneko/laniakea/v2/tgrich" "git.scuroneko.dev/scuroneko/sneklog/v2" ) @@ -106,7 +106,11 @@ func (ctx *MessageContext) buildEditMessageTextParams(messageID int, keyboard *I params.ParseMode = parseMode } if keyboard != nil { - params.ReplyMarkup = keyboard.Get() + markup, err := keyboard.Get() + if err != nil { + return params, fmt.Errorf("keyboard get error: %w", err) + } + params.ReplyMarkup = markup } return params, nil } @@ -218,7 +222,12 @@ func (ctx *MessageContext) editPhotoText(messageID int, text string, kb *InlineK return nil } if kb != nil { - params.ReplyMarkup = kb.Get() + markup, err := kb.Get() + if err != nil { + ctx.Logger.Errorln(err) + return nil + } + params.ReplyMarkup = markup } msg, _, err := ctx.API.EditMessageCaptionWithContext(ctx.Context(), params) @@ -275,7 +284,12 @@ func (ctx *MessageContext) answer(text string, keyboard *InlineKeyboard, parseMo ParseMode: parseMode, } if keyboard != nil { - params.ReplyMarkup = keyboard.Get() + markup, err := keyboard.Get() + if err != nil { + ctx.Logger.Errorln(err) + return nil + } + params.ReplyMarkup = markup } if ctx.Msg.MessageThreadID > 0 { params.MessageThreadID = ctx.Msg.MessageThreadID @@ -406,7 +420,12 @@ func (ctx *MessageContext) answerPhoto(photoID, text string, kb *InlineKeyboard, Photo: photoID, } if kb != nil { - params.ReplyMarkup = kb.Get() + markup, err := kb.Get() + if err != nil { + ctx.Logger.Errorln(err) + return nil + } + params.ReplyMarkup = markup } if ctx.Msg.MessageThreadID > 0 { params.MessageThreadID = ctx.Msg.MessageThreadID @@ -855,7 +874,12 @@ func (ctx *MessageContext) richAnswer(rich tgapi.InputRichMessage, keyboard *Inl RichMessage: rich, } if keyboard != nil { - params.ReplyMarkup = keyboard.Get() + markup, err := keyboard.Get() + if err != nil { + ctx.Logger.Errorln(err) + return nil + } + params.ReplyMarkup = markup } if ctx.Msg.MessageThreadID > 0 { params.MessageThreadID = int64(ctx.Msg.MessageThreadID) diff --git a/msg_context_test.go b/msg_context_test.go index a862531..8fd4c0c 100644 --- a/msg_context_test.go +++ b/msg_context_test.go @@ -9,8 +9,8 @@ import ( "strings" "testing" - "git.scuroneko.dev/scuroneko/laniakea/tgapi" - "git.scuroneko.dev/scuroneko/laniakea/tgrich" + "git.scuroneko.dev/scuroneko/laniakea/v2/tgapi" + "git.scuroneko.dev/scuroneko/laniakea/v2/tgrich" "git.scuroneko.dev/scuroneko/sneklog/v2" ) @@ -442,7 +442,11 @@ func TestNewInlineKeyboardButtonUsesContextPayloadType(t *testing.T) { kb := NewInlineKeyboardJSON(1). AddButton(ctx.NewInlineKeyboardButton("A").SetCallbackData("cmd", 1, "two")) - got, _, err := decodePayload(BotPayloadJSON, kb.Get().InlineKeyboard[0][0].CallbackData, false) + markup, err := kb.Get() + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + got, _, err := decodePayload(BotPayloadJSON, markup.InlineKeyboard[0][0].CallbackData, false) if err != nil { t.Fatalf("decodePayload returned error: %v", err) } diff --git a/msg_handler.go b/msg_handler.go index d723a59..964574f 100644 --- a/msg_handler.go +++ b/msg_handler.go @@ -6,7 +6,7 @@ import ( "time" "unicode" - "git.scuroneko.dev/scuroneko/laniakea/tgapi" + "git.scuroneko.dev/scuroneko/laniakea/v2/tgapi" ) func (bot *Bot[T]) handleMessage(update *tgapi.Update, ctx *MessageContext) bool { diff --git a/observer.go b/observer.go index 34bf4ab..a89daa1 100644 --- a/observer.go +++ b/observer.go @@ -5,7 +5,7 @@ import ( "fmt" "time" - "git.scuroneko.dev/scuroneko/laniakea/tgapi" + "git.scuroneko.dev/scuroneko/laniakea/v2/tgapi" ) // HandlerEventKind identifies the kind of handler observed by runtime events. diff --git a/plugins.go b/plugins.go index 76aca58..16eeacc 100644 --- a/plugins.go +++ b/plugins.go @@ -5,8 +5,8 @@ import ( "fmt" "git.scuroneko.dev/scuroneko/extypes" - "git.scuroneko.dev/scuroneko/laniakea/tgapi" - "git.scuroneko.dev/scuroneko/laniakea/utils" + "git.scuroneko.dev/scuroneko/laniakea/v2/tgapi" + "git.scuroneko.dev/scuroneko/laniakea/v2/utils" "git.scuroneko.dev/scuroneko/sneklog/v2" ) diff --git a/plugins_test.go b/plugins_test.go index b0e24d4..1831477 100644 --- a/plugins_test.go +++ b/plugins_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "git.scuroneko.dev/scuroneko/laniakea/tgapi" + "git.scuroneko.dev/scuroneko/laniakea/v2/tgapi" ) type middlewareErrorObserver struct { diff --git a/policy.go b/policy.go index 995af08..a7cb238 100644 --- a/policy.go +++ b/policy.go @@ -4,7 +4,7 @@ import ( "errors" "fmt" - "git.scuroneko.dev/scuroneko/laniakea/tgapi" + "git.scuroneko.dev/scuroneko/laniakea/v2/tgapi" ) // Policy defines a reusable authorization rule for the current update context. diff --git a/policy_test.go b/policy_test.go index 49dc750..34ed272 100644 --- a/policy_test.go +++ b/policy_test.go @@ -9,7 +9,7 @@ import ( "strings" "testing" - "git.scuroneko.dev/scuroneko/laniakea/tgapi" + "git.scuroneko.dev/scuroneko/laniakea/v2/tgapi" "git.scuroneko.dev/scuroneko/sneklog/v2" ) diff --git a/runners.go b/runners.go index 96d8e43..aa69b16 100644 --- a/runners.go +++ b/runners.go @@ -6,16 +6,8 @@ import ( "time" ) -// RunnerFn is the legacy function type for a runner. It receives a pointer to -// the Bot and returns an error if execution fails. New blocking or I/O work -// should use ContextRunnerFn so runtime cancellation can stop the callback. -// -// Subject to change in v2: runner callbacks may require context.Context. -type RunnerFn[T AppData] func(*Bot[T]) error - -// ContextRunnerFn is a cancelable runner function. -// The runtime context is canceled when polling or webhook execution stops. -type ContextRunnerFn[T AppData] func(context.Context, *Bot[T]) error +// RunnerFn is a cancelable task executed by a Bot runtime. +type RunnerFn[T AppData] func(context.Context, *Bot[T]) error // Runner represents a configurable background or one-time task to be // executed by a Bot. @@ -33,22 +25,6 @@ type Runner[T AppData] struct { async bool // If true, runs in a goroutine; else, runs synchronously every time.Duration // Interval between periodic executions; zero means one-shot fn RunnerFn[T] // The function to execute - ctxFn ContextRunnerFn[T] -} - -func executeRunnerWithContext[T AppData](ctx context.Context, runner Runner[T], bot *Bot[T]) (err error) { - defer func() { - if recovered := recover(); recovered != nil { - err = fmt.Errorf("runner %q panicked: %v", runner.name, recovered) - } - }() - if runner.ctxFn != nil { - return runner.ctxFn(ctx, bot) - } - if runner.fn == nil { - return fmt.Errorf("runner %q has no function", runner.name) - } - return runner.fn(bot) } // NewRunner creates a new Runner with the given name and function. @@ -57,18 +33,10 @@ func executeRunnerWithContext[T AppData](ctx context.Context, runner Runner[T], // goroutine that fires once when the bot runtime starts. Use Async and Every // to customize this. Do not call builder methods concurrently or after the // bot runtime has begun executing runners. -// -// Subject to change in v2: NewContextRunner may become the primary constructor. func NewRunner[T AppData](name string, fn RunnerFn[T]) Runner[T] { return Runner[T]{name: name, fn: fn, async: true, every: 0} } -// NewContextRunner creates a runner whose callback observes runtime cancellation. -// It is the preferred constructor for I/O, blocking, and periodic work. -func NewContextRunner[T AppData](name string, fn ContextRunnerFn[T]) Runner[T] { - return Runner[T]{name: name, ctxFn: fn, async: true, every: 0} -} - // Async sets whether the runner executes synchronously or asynchronously. // If true, the runner runs in a goroutine (non-blocking). // If false, the runner blocks the caller during execution. @@ -198,3 +166,15 @@ func (bot *Bot[T]) ExecRunners(ctx context.Context) { } } } + +func executeRunnerWithContext[T AppData](ctx context.Context, runner Runner[T], bot *Bot[T]) (err error) { + defer func() { + if recovered := recover(); recovered != nil { + err = fmt.Errorf("runner %q panicked: %v", runner.name, recovered) + } + }() + if runner.fn == nil { + return fmt.Errorf("runner %q has no function", runner.name) + } + return runner.fn(ctx, bot) +} diff --git a/runners_test.go b/runners_test.go index 641f357..f1b2454 100644 --- a/runners_test.go +++ b/runners_test.go @@ -19,7 +19,7 @@ func TestExecRunnersRunsOnceSyncRunner(t *testing.T) { bot := &Bot[NoData]{ logger: sneklog.NewLogger(), runners: []Runner[NoData]{ - NewRunner("sync-once", func(*Bot[NoData]) error { + NewRunner("sync-once", func(context.Context, *Bot[NoData]) error { calls.Add(1) return nil }).Async(false), @@ -41,7 +41,7 @@ func TestExecRunnersStopsBackgroundRunnerOnCancel(t *testing.T) { bot := &Bot[NoData]{ logger: sneklog.NewLogger(), runners: []Runner[NoData]{ - NewRunner("background", func(*Bot[NoData]) error { + NewRunner("background", func(context.Context, *Bot[NoData]) error { if calls.Add(1) == 1 { triggered <- struct{}{} } @@ -74,7 +74,7 @@ func TestExecRunnersEmitObserverEvents(t *testing.T) { logger: sneklog.NewLogger(), observer: observer, runners: []Runner[NoData]{ - NewRunner("sync-once", func(*Bot[NoData]) error { + NewRunner("sync-once", func(context.Context, *Bot[NoData]) error { return wantErr }).Async(false), }, @@ -102,7 +102,7 @@ func TestExecRunnersRecoversRunnerPanic(t *testing.T) { logger: sneklog.NewLogger(), observer: observer, runners: []Runner[NoData]{ - NewRunner("panic", func(*Bot[NoData]) error { + NewRunner("panic", func(context.Context, *Bot[NoData]) error { panic("boom") }).Async(false), }, @@ -130,7 +130,7 @@ func TestContextRunnerStopsActiveCallbackOnCancel(t *testing.T) { bot := &Bot[NoData]{ logger: sneklog.NewLogger(), runners: []Runner[NoData]{ - NewContextRunner("cancelable", func(ctx context.Context, _ *Bot[NoData]) error { + NewRunner("cancelable", func(ctx context.Context, _ *Bot[NoData]) error { close(started) <-ctx.Done() close(stopped) diff --git a/scene_test.go b/scene_test.go index 2cb1def..dbbe3f1 100644 --- a/scene_test.go +++ b/scene_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "git.scuroneko.dev/scuroneko/laniakea/tgapi" + "git.scuroneko.dev/scuroneko/laniakea/v2/tgapi" "git.scuroneko.dev/scuroneko/sneklog/v2" ) diff --git a/tgapi/api.go b/tgapi/api.go index 97f3a83..1403f4b 100644 --- a/tgapi/api.go +++ b/tgapi/api.go @@ -9,7 +9,7 @@ import ( "net/http" "time" - "git.scuroneko.dev/scuroneko/laniakea/utils" + "git.scuroneko.dev/scuroneko/laniakea/v2/utils" "git.scuroneko.dev/scuroneko/sneklog/v2" ) @@ -236,7 +236,7 @@ func NewRequestWithChatID[R, P any](method string, params P, chatID int64) Teleg return TelegramRequest[R, P]{method, params, chatID} } -func (r TelegramRequest[R, P]) doRequest(ctx context.Context, api *API) (R, error) { +func (api *API) doRequest[R, P any](ctx context.Context, r TelegramRequest[R, P]) (R, error) { var zero R reqData, err := json.Marshal(r.params) if err != nil { @@ -336,11 +336,11 @@ func (r TelegramRequest[R, P]) doRequest(ctx context.Context, api *API) (R, erro // DoWithContext executes the request asynchronously via the worker pool. // Returns result or error via channel. Respects context cancellation. -func (r TelegramRequest[R, P]) DoWithContext(ctx context.Context, api *API) (R, error) { +func (api *API) DoWithContext[R, P any](ctx context.Context, r TelegramRequest[R, P]) (R, error) { var zero R resultChan, err := api.pool.submit(ctx, func(ctx context.Context) (any, error) { - return r.doRequest(ctx, api) + return api.doRequest(ctx, r) }) if err != nil { return zero, err @@ -362,8 +362,8 @@ func (r TelegramRequest[R, P]) DoWithContext(ctx context.Context, api *API) (R, // Do executes the request synchronously with a background context. // Use only for simple, non-critical calls. -func (r TelegramRequest[R, P]) Do(api *API) (R, error) { - return r.DoWithContext(context.Background(), api) +func (api *API) Do[R, P any](r TelegramRequest[R, P]) (R, error) { + return api.DoWithContext(context.Background(), r) } func readBody(body io.ReadCloser) ([]byte, error) { diff --git a/tgapi/api101_test.go b/tgapi/api101_test.go index 05b4ca9..2be5a1d 100644 --- a/tgapi/api101_test.go +++ b/tgapi/api101_test.go @@ -38,13 +38,15 @@ func TestSendRichMessageDraftMarshal(t *testing.T) { ChatID: 1, DraftID: 7, RichMessage: InputRichMessage{Markdown: "*hi*"}, + CanStop: true, + KeepOnStop: true, } data, err := json.Marshal(params) if err != nil { t.Fatalf("Marshal returned error: %v", err) } got := string(data) - for _, want := range []string{`"chat_id":1`, `"draft_id":7`, `"rich_message":{"markdown":"*hi*"}`} { + for _, want := range []string{`"chat_id":1`, `"draft_id":7`, `"rich_message":{"markdown":"*hi*"}`, `"can_stop":true`, `"keep_on_stop":true`} { if !strings.Contains(got, want) { t.Fatalf("missing %s in sendRichMessageDraft JSON: %s", want, got) } @@ -131,18 +133,18 @@ func TestEphemeralSendParametersMarshal(t *testing.T) { name string params any }{ - {"message", SendMessage{ChatID: 1, Text: "text", ReceiverUserID: 2, CallbackQueryID: "callback"}}, - {"animation", SendAnimation{ChatID: 1, Animation: "animation", ReceiverUserID: 2, CallbackQueryID: "callback"}}, - {"audio", SendAudio{ChatID: 1, Audio: "audio", ReceiverUserID: 2, CallbackQueryID: "callback"}}, - {"document", SendDocument{ChatID: 1, Document: "document", ReceiverUserID: 2, CallbackQueryID: "callback"}}, - {"photo", SendPhoto{ChatID: 1, Photo: "photo", ReceiverUserID: 2, CallbackQueryID: "callback"}}, - {"sticker", SendSticker{ChatID: 1, Sticker: "sticker", ReceiverUserID: 2, CallbackQueryID: "callback"}}, - {"video", SendVideo{ChatID: 1, Video: "video", ReceiverUserID: 2, CallbackQueryID: "callback"}}, - {"video note", SendVideoNote{ChatID: 1, VideoNote: "video-note", ReceiverUserID: 2, CallbackQueryID: "callback"}}, - {"voice", SendVoice{ChatID: 1, Voice: "voice", ReceiverUserID: 2, CallbackQueryID: "callback"}}, - {"contact", SendContact{ChatID: 1, PhoneNumber: "+10000000000", FirstName: "A", ReceiverUserID: 2, CallbackQueryID: "callback"}}, - {"location", SendLocation{ChatID: 1, Latitude: 1, Longitude: 2, ReceiverUserID: 2, CallbackQueryID: "callback"}}, - {"venue", SendVenue{ChatID: 1, Latitude: 1, Longitude: 2, Title: "Venue", Address: "Address", ReceiverUserID: 2, CallbackQueryID: "callback"}}, + {"message", SendMessage{ChatID: 1, Text: "text", EphemeralMessageParameters: &EphemeralMessageParameters{ReceiverUserID: 2, CallbackQueryID: "callback"}}}, + {"animation", SendAnimation{ChatID: 1, Animation: "animation", EphemeralMessageParameters: &EphemeralMessageParameters{ReceiverUserID: 2, CallbackQueryID: "callback"}}}, + {"audio", SendAudio{ChatID: 1, Audio: "audio", EphemeralMessageParameters: &EphemeralMessageParameters{ReceiverUserID: 2, CallbackQueryID: "callback"}}}, + {"document", SendDocument{ChatID: 1, Document: "document", EphemeralMessageParameters: &EphemeralMessageParameters{ReceiverUserID: 2, CallbackQueryID: "callback"}}}, + {"photo", SendPhoto{ChatID: 1, Photo: "photo", EphemeralMessageParameters: &EphemeralMessageParameters{ReceiverUserID: 2, CallbackQueryID: "callback"}}}, + {"sticker", SendSticker{ChatID: 1, Sticker: "sticker", EphemeralMessageParameters: &EphemeralMessageParameters{ReceiverUserID: 2, CallbackQueryID: "callback"}}}, + {"video", SendVideo{ChatID: 1, Video: "video", EphemeralMessageParameters: &EphemeralMessageParameters{ReceiverUserID: 2, CallbackQueryID: "callback"}}}, + {"video note", SendVideoNote{ChatID: 1, VideoNote: "video-note", EphemeralMessageParameters: &EphemeralMessageParameters{ReceiverUserID: 2, CallbackQueryID: "callback"}}}, + {"voice", SendVoice{ChatID: 1, Voice: "voice", EphemeralMessageParameters: &EphemeralMessageParameters{ReceiverUserID: 2, CallbackQueryID: "callback"}}}, + {"contact", SendContact{ChatID: 1, PhoneNumber: "+10000000000", FirstName: "A", EphemeralMessageParameters: &EphemeralMessageParameters{ReceiverUserID: 2, CallbackQueryID: "callback"}}}, + {"location", SendLocation{ChatID: 1, Latitude: 1, Longitude: 2, EphemeralMessageParameters: &EphemeralMessageParameters{ReceiverUserID: 2, CallbackQueryID: "callback"}}}, + {"venue", SendVenue{ChatID: 1, Latitude: 1, Longitude: 2, Title: "Venue", Address: "Address", EphemeralMessageParameters: &EphemeralMessageParameters{ReceiverUserID: 2, CallbackQueryID: "callback"}}}, } for _, tt := range cases { @@ -151,6 +153,19 @@ func TestEphemeralSendParametersMarshal(t *testing.T) { if err != nil { t.Fatalf("Marshal returned error: %v", err) } + var fields map[string]json.RawMessage + if err := json.Unmarshal(data, &fields); err != nil { + t.Fatal(err) + } + if _, old := fields["receiver_user_id"]; old { + t.Fatalf("legacy receiver field remains: %s", data) + } + if _, old := fields["callback_query_id"]; old { + t.Fatalf("legacy callback field remains: %s", data) + } + if len(fields["ephemeral_message_parameters"]) == 0 { + t.Fatalf("missing nested parameters: %s", data) + } if !strings.Contains(string(data), `"receiver_user_id":2`) || !strings.Contains(string(data), `"callback_query_id":"callback"`) { t.Fatalf("missing ephemeral parameters in %s", data) } @@ -158,6 +173,140 @@ func TestEphemeralSendParametersMarshal(t *testing.T) { } } +func TestEphemeralUploadParametersMarshal(t *testing.T) { + ephemeral := &EphemeralMessageParameters{ReceiverUserID: 2, CallbackQueryID: "callback"} + cases := []struct { + name string + params any + }{ + {"photo", UploadPhoto{ChatID: 1, EphemeralMessageParameters: ephemeral}}, + {"animation", UploadAnimation{ChatID: 1, EphemeralMessageParameters: ephemeral}}, + {"audio", UploadAudio{ChatID: 1, EphemeralMessageParameters: ephemeral}}, + {"document", UploadDocument{ChatID: 1, EphemeralMessageParameters: ephemeral}}, + {"live photo", UploadLivePhoto{ChatID: 1, EphemeralMessageParameters: ephemeral}}, + {"video", UploadVideo{ChatID: 1, EphemeralMessageParameters: ephemeral}}, + {"video note", UploadVideoNote{ChatID: 1, EphemeralMessageParameters: ephemeral}}, + {"voice", UploadVoice{ChatID: 1, EphemeralMessageParameters: ephemeral}}, + } + + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + data, err := json.Marshal(tt.params) + if err != nil { + t.Fatalf("Marshal returned error: %v", err) + } + var fields map[string]json.RawMessage + if err := json.Unmarshal(data, &fields); err != nil { + t.Fatal(err) + } + if _, old := fields["receiver_user_id"]; old { + t.Fatalf("legacy receiver field remains: %s", data) + } + if _, old := fields["callback_query_id"]; old { + t.Fatalf("legacy callback field remains: %s", data) + } + if !strings.Contains(string(fields["ephemeral_message_parameters"]), `"receiver_user_id":2`) { + t.Fatalf("missing nested parameters: %s", data) + } + }) + } +} + +func TestBotAPI103KeyboardAndAdministratorFieldsMarshal(t *testing.T) { + trueValue := true + cases := []struct { + name string + value any + fields []string + }{ + {"inline keyboard force reply", InlineKeyboardMarkup{ForceReply: true}, []string{`"force_reply":true`}}, + {"reply keyboard force reply", ReplyKeyboardMarkup{Keyboard: [][]KeyboardButton{}, ForceReply: true}, []string{`"force_reply":true`}}, + {"disabled inline button", InlineKeyboardButton{Text: "Wait", Disabled: &DisabledButton{}}, []string{`"disabled":{}`}}, + {"administrator member", ChatMember{CanSendWelcomeMessages: &trueValue}, []string{`"can_send_welcome_messages":true`}}, + {"administrator rights", ChatAdministratorRights{CanSendWelcomeMessages: &trueValue}, []string{`"can_send_welcome_messages":true`}}, + {"promote administrator", PromoteChatMember{ChatID: 1, UserID: 2, CanSendWelcomeMessages: true}, []string{`"can_send_welcome_messages":true`}}, + } + + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + data, err := json.Marshal(tt.value) + if err != nil { + t.Fatalf("Marshal returned error: %v", err) + } + for _, field := range tt.fields { + if !strings.Contains(string(data), field) { + t.Fatalf("missing %s in %s", field, data) + } + } + if strings.Contains(string(data), "melcome") { + t.Fatalf("misspelled welcome field in %s", data) + } + }) + } +} + +func TestMessageGenerationStoppedUpdateUnmarshal(t *testing.T) { + var update Update + err := json.Unmarshal([]byte(`{"update_id":1,"stopped_message_generation":{"chat":{"id":42,"type":"private"},"message_thread_id":3,"draft_id":7}}`), &update) + if err != nil { + t.Fatalf("Unmarshal returned error: %v", err) + } + if update.Type != UpdateTypeMessageGenerationStopped || update.StoppedMessageGeneration == nil { + t.Fatalf("unexpected update routing: type=%q payload=%+v", update.Type, update.StoppedMessageGeneration) + } + if update.StoppedMessageGeneration.Chat.ID != 42 || update.StoppedMessageGeneration.DraftID != 7 { + t.Fatalf("unexpected stopped-generation payload: %+v", update.StoppedMessageGeneration) + } +} + +func TestBotAPI103AdditionalFieldsMarshal(t *testing.T) { + params := []struct { + name string + value any + want []string + }{ + { + "ephemeral replacement", + EphemeralMessageParameters{ReceiverUserID: 2, ReplaceCallbackQueryMessage: true}, + []string{`"receiver_user_id":2`, `"replace_callback_query_message":true`}, + }, + { + "message draft controls", + SendMessageDraft{ChatID: 1, DraftID: 7, CanStop: true, KeepOnStop: true}, + []string{`"can_stop":true`, `"keep_on_stop":true`}, + }, + { + "ephemeral rich text", + EditEphemeralMessageText{ChatID: 1, ReceiverUserID: 2, EphemeralMessageID: 3, RichMessage: &InputRichMessage{HTML: "x"}}, + []string{`"rich_message":{`, `"html":`}, + }, + { + "ephemeral caption position", + EditEphemeralMessageCaption{ChatID: 1, ReceiverUserID: 2, EphemeralMessageID: 3, ShowCaptionAboveMedia: true}, + []string{`"show_caption_above_media":true`}, + }, + { + "unique gift privacy", + UniqueGiftInfo{Text: "gift", Entities: []MessageEntity{{Type: MessageEntityBold, Length: 4}}, IsPrivate: true}, + []string{`"text":"gift"`, `"entities":[`, `"is_private":true`}, + }, + } + + for _, tt := range params { + t.Run(tt.name, func(t *testing.T) { + data, err := json.Marshal(tt.value) + if err != nil { + t.Fatalf("Marshal returned error: %v", err) + } + for _, want := range tt.want { + if !strings.Contains(string(data), want) { + t.Fatalf("missing %s in %s", want, data) + } + } + }) + } +} + func TestInputPollOptionMediaLinkMarshal(t *testing.T) { media := InputPollOptionMedia{Type: "link", URL: "https://example.com"} data, err := json.Marshal(media) diff --git a/tgapi/attachments_methods.go b/tgapi/attachments_methods.go index 084de7f..1acb73f 100644 --- a/tgapi/attachments_methods.go +++ b/tgapi/attachments_methods.go @@ -18,10 +18,8 @@ type SendPhoto struct { // DirectMessagesTopicID Optional. Identifier of the direct messages topic to which the message will be // sent; required if the message is sent to a direct messages chat DirectMessagesTopicID int `json:"direct_messages_topic_id,omitempty"` - // ReceiverUserID identifies the user who can see the ephemeral message. - ReceiverUserID int64 `json:"receiver_user_id,omitempty"` // Since: Bot API 10.2 - // CallbackQueryID identifies the callback query that triggered an ephemeral response. - CallbackQueryID string `json:"callback_query_id,omitempty"` // Since: Bot API 10.2 + + EphemeralMessageParameters *EphemeralMessageParameters `json:"ephemeral_message_parameters,omitempty"` // Since: Bot API 10.3 // Photo Required. Photo to send. Pass a file_id as String to send a photo that exists on the Telegram // servers (recommended), pass an HTTP URL as a String for Telegram to get a photo from the Internet, or @@ -72,7 +70,7 @@ type SendPhoto struct { // See https://core.telegram.org/bots/api#sendphoto func (api *API) SendPhoto(params SendPhoto) (Message, error) { req := NewRequestWithChatID[Message]("sendPhoto", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // SendPhotoWithContext is the context-aware variant of SendPhoto. @@ -81,7 +79,7 @@ func (api *API) SendPhoto(params SendPhoto) (Message, error) { // See https://core.telegram.org/bots/api#sendphoto func (api *API) SendPhotoWithContext(ctx context.Context, params SendPhoto) (Message, error) { req := NewRequestWithChatID[Message]("sendPhoto", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // SendAudio holds parameters for the sendAudio method. @@ -100,10 +98,8 @@ type SendAudio struct { // DirectMessagesTopicID Optional. Identifier of the direct messages topic to which the message will be // sent; required if the message is sent to a direct messages chat DirectMessagesTopicID int `json:"direct_messages_topic_id,omitempty"` - // ReceiverUserID identifies the user who can see the ephemeral message. - ReceiverUserID int64 `json:"receiver_user_id,omitempty"` // Since: Bot API 10.2 - // CallbackQueryID identifies the callback query that triggered an ephemeral response. - CallbackQueryID string `json:"callback_query_id,omitempty"` // Since: Bot API 10.2 + + EphemeralMessageParameters *EphemeralMessageParameters `json:"ephemeral_message_parameters,omitempty"` // Since: Bot API 10.3 // Audio Required. Audio file to send. Pass a file_id as String to send an audio file that exists on the // Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get an audio file from the @@ -160,7 +156,7 @@ type SendAudio struct { // See https://core.telegram.org/bots/api#sendaudio func (api *API) SendAudio(params SendAudio) (Message, error) { req := NewRequestWithChatID[Message]("sendAudio", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // SendAudioWithContext is the context-aware variant of SendAudio. @@ -169,7 +165,7 @@ func (api *API) SendAudio(params SendAudio) (Message, error) { // See https://core.telegram.org/bots/api#sendaudio func (api *API) SendAudioWithContext(ctx context.Context, params SendAudio) (Message, error) { req := NewRequestWithChatID[Message]("sendAudio", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // SendDocument holds parameters for the sendDocument method. @@ -188,10 +184,8 @@ type SendDocument struct { // DirectMessagesTopicID Optional. Identifier of the direct messages topic to which the message will be // sent; required if the message is sent to a direct messages chat DirectMessagesTopicID int `json:"direct_messages_topic_id,omitempty"` - // ReceiverUserID identifies the user who can see the ephemeral message. - ReceiverUserID int64 `json:"receiver_user_id,omitempty"` // Since: Bot API 10.2 - // CallbackQueryID identifies the callback query that triggered an ephemeral response. - CallbackQueryID string `json:"callback_query_id,omitempty"` // Since: Bot API 10.2 + + EphemeralMessageParameters *EphemeralMessageParameters `json:"ephemeral_message_parameters,omitempty"` // Since: Bot API 10.3 // Document Required. File to send. Pass a file_id as String to send a file that exists on the Telegram // servers (recommended), pass an HTTP URL as a String for Telegram to get a file from the Internet, or @@ -246,7 +240,7 @@ type SendDocument struct { // See https://core.telegram.org/bots/api#senddocument func (api *API) SendDocument(params SendDocument) (Message, error) { req := NewRequestWithChatID[Message]("sendDocument", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // SendDocumentWithContext is the context-aware variant of SendDocument. @@ -255,7 +249,7 @@ func (api *API) SendDocument(params SendDocument) (Message, error) { // See https://core.telegram.org/bots/api#senddocument func (api *API) SendDocumentWithContext(ctx context.Context, params SendDocument) (Message, error) { req := NewRequestWithChatID[Message]("sendDocument", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // SendVideo holds parameters for the sendVideo method. @@ -274,10 +268,8 @@ type SendVideo struct { // DirectMessagesTopicID Optional. Identifier of the direct messages topic to which the message will be // sent; required if the message is sent to a direct messages chat DirectMessagesTopicID int `json:"direct_messages_topic_id,omitempty"` - // ReceiverUserID identifies the user who can see the ephemeral message. - ReceiverUserID int64 `json:"receiver_user_id,omitempty"` // Since: Bot API 10.2 - // CallbackQueryID identifies the callback query that triggered an ephemeral response. - CallbackQueryID string `json:"callback_query_id,omitempty"` // Since: Bot API 10.2 + + EphemeralMessageParameters *EphemeralMessageParameters `json:"ephemeral_message_parameters,omitempty"` // Since: Bot API 10.3 // Video Required. Video to send. Pass a file_id as String to send a video that exists on the Telegram // servers (recommended), pass an HTTP URL as a String for Telegram to get a video from the Internet, or @@ -349,7 +341,7 @@ type SendVideo struct { // See https://core.telegram.org/bots/api#sendvideo func (api *API) SendVideo(params SendVideo) (Message, error) { req := NewRequestWithChatID[Message]("sendVideo", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // SendVideoWithContext is the context-aware variant of SendVideo. @@ -358,7 +350,7 @@ func (api *API) SendVideo(params SendVideo) (Message, error) { // See https://core.telegram.org/bots/api#sendvideo func (api *API) SendVideoWithContext(ctx context.Context, params SendVideo) (Message, error) { req := NewRequestWithChatID[Message]("sendVideo", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // SendAnimation holds parameters for the sendAnimation method. @@ -377,10 +369,8 @@ type SendAnimation struct { // DirectMessagesTopicID Optional. Identifier of the direct messages topic to which the message will be // sent; required if the message is sent to a direct messages chat DirectMessagesTopicID int `json:"direct_messages_topic_id,omitempty"` - // ReceiverUserID identifies the user who can see the ephemeral message. - ReceiverUserID int64 `json:"receiver_user_id,omitempty"` // Since: Bot API 10.2 - // CallbackQueryID identifies the callback query that triggered an ephemeral response. - CallbackQueryID string `json:"callback_query_id,omitempty"` // Since: Bot API 10.2 + + EphemeralMessageParameters *EphemeralMessageParameters `json:"ephemeral_message_parameters,omitempty"` // Since: Bot API 10.3 // Animation Required. Animation to send. Pass a file_id as String to send an animation that exists on the // Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get an animation from the @@ -442,7 +432,7 @@ type SendAnimation struct { // See https://core.telegram.org/bots/api#sendanimation func (api *API) SendAnimation(params SendAnimation) (Message, error) { req := NewRequestWithChatID[Message]("sendAnimation", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // SendAnimationWithContext is the context-aware variant of SendAnimation. @@ -451,7 +441,7 @@ func (api *API) SendAnimation(params SendAnimation) (Message, error) { // See https://core.telegram.org/bots/api#sendanimation func (api *API) SendAnimationWithContext(ctx context.Context, params SendAnimation) (Message, error) { req := NewRequestWithChatID[Message]("sendAnimation", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // SendVoice holds parameters for the sendVoice method. @@ -470,10 +460,8 @@ type SendVoice struct { // DirectMessagesTopicID Optional. Identifier of the direct messages topic to which the message will be // sent; required if the message is sent to a direct messages chat DirectMessagesTopicID int `json:"direct_messages_topic_id,omitempty"` - // ReceiverUserID identifies the user who can see the ephemeral message. - ReceiverUserID int64 `json:"receiver_user_id,omitempty"` // Since: Bot API 10.2 - // CallbackQueryID identifies the callback query that triggered an ephemeral response. - CallbackQueryID string `json:"callback_query_id,omitempty"` // Since: Bot API 10.2 + + EphemeralMessageParameters *EphemeralMessageParameters `json:"ephemeral_message_parameters,omitempty"` // Since: Bot API 10.3 // Voice Required. Audio file to send. Pass a file_id as String to send a file that exists on the Telegram // servers (recommended), pass an HTTP URL as a String for Telegram to get a file from the Internet, or @@ -518,7 +506,7 @@ type SendVoice struct { // See https://core.telegram.org/bots/api#sendvoice func (api *API) SendVoice(params SendVoice) (Message, error) { req := NewRequestWithChatID[Message]("sendVoice", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // SendVoiceWithContext is the context-aware variant of SendVoice. @@ -527,7 +515,7 @@ func (api *API) SendVoice(params SendVoice) (Message, error) { // See https://core.telegram.org/bots/api#sendvoice func (api *API) SendVoiceWithContext(ctx context.Context, params SendVoice) (Message, error) { req := NewRequestWithChatID[Message]("sendVoice", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // SendVideoNote holds parameters for the sendVideoNote method. @@ -546,10 +534,8 @@ type SendVideoNote struct { // DirectMessagesTopicID Optional. Identifier of the direct messages topic to which the message will be // sent; required if the message is sent to a direct messages chat DirectMessagesTopicID int `json:"direct_messages_topic_id,omitempty"` - // ReceiverUserID identifies the user who can see the ephemeral message. - ReceiverUserID int64 `json:"receiver_user_id,omitempty"` // Since: Bot API 10.2 - // CallbackQueryID identifies the callback query that triggered an ephemeral response. - CallbackQueryID string `json:"callback_query_id,omitempty"` // Since: Bot API 10.2 + + EphemeralMessageParameters *EphemeralMessageParameters `json:"ephemeral_message_parameters,omitempty"` // Since: Bot API 10.3 // VideoNote Required. Video note to send. Pass a file_id as String to send a video note that exists on the // Telegram servers (recommended) or upload a new video using multipart/form-data. More information on @@ -595,7 +581,7 @@ type SendVideoNote struct { // See https://core.telegram.org/bots/api#sendvideonote func (api *API) SendVideoNote(params SendVideoNote) (Message, error) { req := NewRequestWithChatID[Message]("sendVideoNote", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // SendVideoNoteWithContext is the context-aware variant of SendVideoNote. @@ -604,7 +590,7 @@ func (api *API) SendVideoNote(params SendVideoNote) (Message, error) { // See https://core.telegram.org/bots/api#sendvideonote func (api *API) SendVideoNoteWithContext(ctx context.Context, params SendVideoNote) (Message, error) { req := NewRequestWithChatID[Message]("sendVideoNote", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // SendPaidMedia holds parameters for the sendPaidMedia method. @@ -668,7 +654,7 @@ type SendPaidMedia struct { // See https://core.telegram.org/bots/api#sendpaidmedia func (api *API) SendPaidMedia(params SendPaidMedia) (Message, error) { req := NewRequestWithChatID[Message]("sendPaidMedia", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // SendPaidMediaWithContext is the context-aware variant of SendPaidMedia. @@ -677,7 +663,7 @@ func (api *API) SendPaidMedia(params SendPaidMedia) (Message, error) { // See https://core.telegram.org/bots/api#sendpaidmedia func (api *API) SendPaidMediaWithContext(ctx context.Context, params SendPaidMedia) (Message, error) { req := NewRequestWithChatID[Message]("sendPaidMedia", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // SendMediaGroup holds parameters for the sendMediaGroup method. @@ -719,7 +705,7 @@ type SendMediaGroup struct { // See https://core.telegram.org/bots/api#sendmediagroup func (api *API) SendMediaGroup(params SendMediaGroup) ([]Message, error) { req := NewRequestWithChatID[[]Message]("sendMediaGroup", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // SendMediaGroupWithContext is the context-aware variant of SendMediaGroup. @@ -728,7 +714,7 @@ func (api *API) SendMediaGroup(params SendMediaGroup) ([]Message, error) { // See https://core.telegram.org/bots/api#sendmediagroup func (api *API) SendMediaGroupWithContext(ctx context.Context, params SendMediaGroup) ([]Message, error) { req := NewRequestWithChatID[[]Message]("sendMediaGroup", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // SendLivePhoto holds parameters for the sendLivePhoto method. @@ -748,10 +734,8 @@ type SendLivePhoto struct { // sent; required if the message is sent to a direct messages chat DirectMessagesTopicID int `json:"direct_messages_topic_id,omitempty"` - // ReceiverUserID identifies the user who can see the ephemeral message. - ReceiverUserID int64 `json:"receiver_user_id,omitempty"` // Since: Bot API 10.2 - // CallbackQueryID identifies the callback query that triggered an ephemeral response. - CallbackQueryID string `json:"callback_query_id,omitempty"` // Since: Bot API 10.2 + EphemeralMessageParameters *EphemeralMessageParameters `json:"ephemeral_message_parameters,omitempty"` // Since: Bot API 10.3 + // LivePhoto Required. Live photo video to send. The video must be no longer than 10 seconds and must not // exceed 10 MB in size. Pass a file_id as String to send a video that exists on the Telegram servers // (recommended) or upload a new video using multipart/form-data. More information on Sending Files ». @@ -802,7 +786,7 @@ type SendLivePhoto struct { // See https://core.telegram.org/bots/api#sendlivephoto func (api *API) SendLivePhoto(params SendLivePhoto) (Message, error) { req := NewRequestWithChatID[Message]("sendLivePhoto", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // SendLivePhotoWithContext is the context-aware variant of SendLivePhoto. @@ -811,5 +795,5 @@ func (api *API) SendLivePhoto(params SendLivePhoto) (Message, error) { // See https://core.telegram.org/bots/api#sendlivephoto func (api *API) SendLivePhotoWithContext(ctx context.Context, params SendLivePhoto) (Message, error) { req := NewRequestWithChatID[Message]("sendLivePhoto", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } diff --git a/tgapi/attachments_types.go b/tgapi/attachments_types.go index 37ebd49..d75f587 100644 --- a/tgapi/attachments_types.go +++ b/tgapi/attachments_types.go @@ -355,44 +355,22 @@ const ( // PollAnswer represents an answer submitted by a poll voter. // -// User and VoterChat remain value fields for v1 compatibility. Their pointer -// representation is subject to change in v2; use VoterUser and VoterChatInfo -// when presence matters. +// Exactly one of User and VoterChat is present. // Since: Bot API 4.6 // See https://core.telegram.org/bots/api#pollanswer type PollAnswer struct { // PollID identifies the poll. PollID string `json:"poll_id"` // VoterChat is the chat that changed the answer, when the voter is anonymous. - VoterChat Chat `json:"voter_chat,omitempty"` // Since: Bot API 6.8 + VoterChat *Chat `json:"voter_chat,omitempty"` // Since: Bot API 6.8 // User is the user that changed the answer, when the voter is not anonymous. - User User `json:"user,omitempty"` + User *User `json:"user,omitempty"` // OptionIDs contains the chosen option indices and is empty for a retracted vote. OptionIDs []int `json:"option_ids"` // OptionPersistentIDs contains the persistent identifiers of the chosen options. OptionPersistentIDs []string `json:"option_persistent_ids"` // Since: Bot API 9.6 } -// VoterUser returns the non-anonymous voter when it is present. -// -// Since: Bot API 4.6 -func (a PollAnswer) VoterUser() (*User, bool) { - if a.User.ID == 0 { - return nil, false - } - return &a.User, true -} - -// VoterChatInfo returns the anonymous voter chat when it is present. -// -// Since: Bot API 6.8 -func (a PollAnswer) VoterChatInfo() (*Chat, bool) { - if a.VoterChat.ID == 0 { - return nil, false - } - return &a.VoterChat, true -} - // Poll contains information about a poll. // Since: Bot API 4.2 // See https://core.telegram.org/bots/api#poll @@ -549,13 +527,11 @@ type InputChecklist struct { TitleEntities []MessageEntity `json:"title_entities,omitempty"` // Tasks List of 1-30 tasks in the checklist Tasks []InputChecklistTask `json:"tasks"` - // OtherCanAddTasks Optional. Pass True if other users can add tasks to the checklist - // Subject to change in v2: the Go field name may be corrected to OthersCanAddTasks. - OtherCanAddTasks bool `json:"others_can_add_tasks,omitempty"` + // OthersCanAddTasks Optional. Pass True if other users can add tasks to the checklist + OthersCanAddTasks bool `json:"others_can_add_tasks,omitempty"` // OtherCanMarkTasksAsDone Optional. Pass True if other users can mark tasks as done or not done in the // checklist - // Subject to change in v2: the Go field name may be corrected to OthersCanMarkTasksAsDone. - OtherCanMarkTasksAsDone bool `json:"others_can_mark_tasks_as_done,omitempty"` + OthersCanMarkTasksAsDone bool `json:"others_can_mark_tasks_as_done,omitempty"` } // ChecklistTaskDone describes a service message about checklist tasks being marked as done. diff --git a/tgapi/attachments_types_test.go b/tgapi/attachments_types_test.go index af5fc12..4d77766 100644 --- a/tgapi/attachments_types_test.go +++ b/tgapi/attachments_types_test.go @@ -3,18 +3,18 @@ package tgapi import "testing" func TestPollAnswerVoterHelpers(t *testing.T) { - userAnswer := PollAnswer{User: User{ID: 42}} - user, ok := userAnswer.VoterUser() - if !ok || user.ID != 42 { - t.Fatalf("unexpected voter user: %#v, %v", user, ok) + userAnswer := PollAnswer{User: &User{ID: 42}} + user := userAnswer.User + if user.ID != 42 { + t.Fatalf("unexpected voter user: %#v", user) } - if chat, ok := userAnswer.VoterChatInfo(); ok || chat != nil { - t.Fatalf("unexpected voter chat: %#v, %v", chat, ok) + if chat := userAnswer.VoterChat; chat != nil { + t.Fatalf("unexpected voter chat: %#v", chat) } - chatAnswer := PollAnswer{VoterChat: Chat{ID: -100}} - chat, ok := chatAnswer.VoterChatInfo() - if !ok || chat.ID != -100 { - t.Fatalf("unexpected voter chat: %#v, %v", chat, ok) + chatAnswer := PollAnswer{VoterChat: &Chat{ID: -100}} + chat := chatAnswer.VoterChat + if chat.ID != -100 { + t.Fatalf("unexpected voter chat: %#v", chat) } } diff --git a/tgapi/bot_methods.go b/tgapi/bot_methods.go index e6db0be..c79a41e 100644 --- a/tgapi/bot_methods.go +++ b/tgapi/bot_methods.go @@ -23,7 +23,7 @@ type SetMyCommands struct { // See https://core.telegram.org/bots/api#setmycommands func (api *API) SetMyCommands(params SetMyCommands) (bool, error) { req := NewRequest[bool]("setMyCommands", params) - return req.Do(api) + return api.Do(req) } // SetMyCommandsWithContext is the context-aware variant of SetMyCommands. @@ -32,7 +32,7 @@ func (api *API) SetMyCommands(params SetMyCommands) (bool, error) { // See https://core.telegram.org/bots/api#setmycommands func (api *API) SetMyCommandsWithContext(ctx context.Context, params SetMyCommands) (bool, error) { req := NewRequest[bool]("setMyCommands", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // DeleteMyCommands holds parameters for the deleteMyCommands method. @@ -53,7 +53,7 @@ type DeleteMyCommands struct { // See https://core.telegram.org/bots/api#deletemycommands func (api *API) DeleteMyCommands(params DeleteMyCommands) (bool, error) { req := NewRequest[bool]("deleteMyCommands", params) - return req.Do(api) + return api.Do(req) } // DeleteMyCommandsWithContext is the context-aware variant of DeleteMyCommands. @@ -62,7 +62,7 @@ func (api *API) DeleteMyCommands(params DeleteMyCommands) (bool, error) { // See https://core.telegram.org/bots/api#deletemycommands func (api *API) DeleteMyCommandsWithContext(ctx context.Context, params DeleteMyCommands) (bool, error) { req := NewRequest[bool]("deleteMyCommands", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // GetMyCommands holds parameters for the getMyCommands method. @@ -80,7 +80,7 @@ type GetMyCommands struct { // See https://core.telegram.org/bots/api#getmycommands func (api *API) GetMyCommands(params GetMyCommands) ([]BotCommand, error) { req := NewRequest[[]BotCommand]("getMyCommands", params) - return req.Do(api) + return api.Do(req) } // GetMyCommandsWithContext is the context-aware variant of GetMyCommands. @@ -89,7 +89,7 @@ func (api *API) GetMyCommands(params GetMyCommands) ([]BotCommand, error) { // See https://core.telegram.org/bots/api#getmycommands func (api *API) GetMyCommandsWithContext(ctx context.Context, params GetMyCommands) ([]BotCommand, error) { req := NewRequest[[]BotCommand]("getMyCommands", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // SetMyName holds parameters for the setMyName method. @@ -110,7 +110,7 @@ type SetMyName struct { // See https://core.telegram.org/bots/api#setmyname func (api *API) SetMyName(params SetMyName) (bool, error) { req := NewRequest[bool]("setMyName", params) - return req.Do(api) + return api.Do(req) } // SetMyNameWithContext is the context-aware variant of SetMyName. @@ -119,7 +119,7 @@ func (api *API) SetMyName(params SetMyName) (bool, error) { // See https://core.telegram.org/bots/api#setmyname func (api *API) SetMyNameWithContext(ctx context.Context, params SetMyName) (bool, error) { req := NewRequest[bool]("setMyName", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // GetMyName holds parameters for the getMyName method. @@ -135,7 +135,7 @@ type GetMyName struct { // See https://core.telegram.org/bots/api#getmyname func (api *API) GetMyName(params GetMyName) (BotName, error) { req := NewRequest[BotName]("getMyName", params) - return req.Do(api) + return api.Do(req) } // GetMyNameWithContext is the context-aware variant of GetMyName. @@ -144,7 +144,7 @@ func (api *API) GetMyName(params GetMyName) (BotName, error) { // See https://core.telegram.org/bots/api#getmyname func (api *API) GetMyNameWithContext(ctx context.Context, params GetMyName) (BotName, error) { req := NewRequest[BotName]("getMyName", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // SetMyDescription holds parameters for the setMyDescription method. @@ -165,7 +165,7 @@ type SetMyDescription struct { // See https://core.telegram.org/bots/api#setmydescription func (api *API) SetMyDescription(params SetMyDescription) (bool, error) { req := NewRequest[bool]("setMyDescription", params) - return req.Do(api) + return api.Do(req) } // SetMyDescriptionWithContext is the context-aware variant of SetMyDescription. @@ -174,7 +174,7 @@ func (api *API) SetMyDescription(params SetMyDescription) (bool, error) { // See https://core.telegram.org/bots/api#setmydescription func (api *API) SetMyDescriptionWithContext(ctx context.Context, params SetMyDescription) (bool, error) { req := NewRequest[bool]("setMyDescription", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // GetMyDescription holds parameters for the getMyDescription method. @@ -190,7 +190,7 @@ type GetMyDescription struct { // See https://core.telegram.org/bots/api#getmydescription func (api *API) GetMyDescription(params GetMyDescription) (BotDescription, error) { req := NewRequest[BotDescription]("getMyDescription", params) - return req.Do(api) + return api.Do(req) } // GetMyDescriptionWithContext is the context-aware variant of GetMyDescription. @@ -199,7 +199,7 @@ func (api *API) GetMyDescription(params GetMyDescription) (BotDescription, error // See https://core.telegram.org/bots/api#getmydescription func (api *API) GetMyDescriptionWithContext(ctx context.Context, params GetMyDescription) (BotDescription, error) { req := NewRequest[BotDescription]("getMyDescription", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // SetMyShortDescription holds parameters for the setMyShortDescription method. @@ -220,7 +220,7 @@ type SetMyShortDescription struct { // See https://core.telegram.org/bots/api#setmyshortdescription func (api *API) SetMyShortDescription(params SetMyShortDescription) (bool, error) { req := NewRequest[bool]("setMyShortDescription", params) - return req.Do(api) + return api.Do(req) } // SetMyShortDescriptionWithContext is the context-aware variant of SetMyShortDescription. @@ -229,7 +229,7 @@ func (api *API) SetMyShortDescription(params SetMyShortDescription) (bool, error // See https://core.telegram.org/bots/api#setmyshortdescription func (api *API) SetMyShortDescriptionWithContext(ctx context.Context, params SetMyShortDescription) (bool, error) { req := NewRequest[bool]("setMyShortDescription", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // GetMyShortDescription holds parameters for the getMyShortDescription method. @@ -245,7 +245,7 @@ type GetMyShortDescription struct { // See https://core.telegram.org/bots/api#getmyshortdescription func (api *API) GetMyShortDescription(params GetMyShortDescription) (BotShortDescription, error) { req := NewRequest[BotShortDescription]("getMyShortDescription", params) - return req.Do(api) + return api.Do(req) } // GetMyShortDescriptionWithContext is the context-aware variant of GetMyShortDescription. @@ -254,7 +254,7 @@ func (api *API) GetMyShortDescription(params GetMyShortDescription) (BotShortDes // See https://core.telegram.org/bots/api#getmyshortdescription func (api *API) GetMyShortDescriptionWithContext(ctx context.Context, params GetMyShortDescription) (BotShortDescription, error) { req := NewRequest[BotShortDescription]("getMyShortDescription", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // SetMyProfilePhoto holds parameters for the setMyProfilePhoto method. @@ -271,7 +271,7 @@ type SetMyProfilePhoto struct { // See https://core.telegram.org/bots/api#setmyprofilephoto func (api *API) SetMyProfilePhoto(params SetMyProfilePhoto) (bool, error) { req := NewRequest[bool]("setMyProfilePhoto", params) - return req.Do(api) + return api.Do(req) } // SetMyProfilePhotoWithContext is the context-aware variant of SetMyProfilePhoto. @@ -280,7 +280,7 @@ func (api *API) SetMyProfilePhoto(params SetMyProfilePhoto) (bool, error) { // See https://core.telegram.org/bots/api#setmyprofilephoto func (api *API) SetMyProfilePhotoWithContext(ctx context.Context, params SetMyProfilePhoto) (bool, error) { req := NewRequest[bool]("setMyProfilePhoto", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // RemoveMyProfilePhoto removes the bot's profile photo. @@ -289,7 +289,7 @@ func (api *API) SetMyProfilePhotoWithContext(ctx context.Context, params SetMyPr // See https://core.telegram.org/bots/api#removemyprofilephoto func (api *API) RemoveMyProfilePhoto() (bool, error) { req := NewRequest[bool]("removeMyProfilePhoto", NoParams) - return req.Do(api) + return api.Do(req) } // RemoveMyProfilePhotoWithContext is the context-aware variant of RemoveMyProfilePhoto. @@ -298,7 +298,7 @@ func (api *API) RemoveMyProfilePhoto() (bool, error) { // See https://core.telegram.org/bots/api#removemyprofilephoto func (api *API) RemoveMyProfilePhotoWithContext(ctx context.Context) (bool, error) { req := NewRequest[bool]("removeMyProfilePhoto", NoParams) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // SetChatMenuButton holds parameters for the setChatMenuButton method. @@ -319,7 +319,7 @@ type SetChatMenuButton struct { // See https://core.telegram.org/bots/api#setchatmenubutton func (api *API) SetChatMenuButton(params SetChatMenuButton) (bool, error) { req := NewRequest[bool]("setChatMenuButton", params) - return req.Do(api) + return api.Do(req) } // SetChatMenuButtonWithContext is the context-aware variant of SetChatMenuButton. @@ -328,7 +328,7 @@ func (api *API) SetChatMenuButton(params SetChatMenuButton) (bool, error) { // See https://core.telegram.org/bots/api#setchatmenubutton func (api *API) SetChatMenuButtonWithContext(ctx context.Context, params SetChatMenuButton) (bool, error) { req := NewRequest[bool]("setChatMenuButton", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // GetChatMenuButton holds parameters for the getChatMenuButton method. @@ -345,7 +345,7 @@ type GetChatMenuButton struct { // See https://core.telegram.org/bots/api#getchatmenubutton func (api *API) GetChatMenuButton(params GetChatMenuButton) (MenuButton, error) { req := NewRequest[MenuButton]("getChatMenuButton", params) - return req.Do(api) + return api.Do(req) } // GetChatMenuButtonWithContext is the context-aware variant of GetChatMenuButton. @@ -354,7 +354,7 @@ func (api *API) GetChatMenuButton(params GetChatMenuButton) (MenuButton, error) // See https://core.telegram.org/bots/api#getchatmenubutton func (api *API) GetChatMenuButtonWithContext(ctx context.Context, params GetChatMenuButton) (MenuButton, error) { req := NewRequest[MenuButton]("getChatMenuButton", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // SetMyDefaultAdministratorRights holds parameters for the setMyDefaultAdministratorRights method. @@ -375,7 +375,7 @@ type SetMyDefaultAdministratorRights struct { // See https://core.telegram.org/bots/api#setmydefaultadministratorrights func (api *API) SetMyDefaultAdministratorRights(params SetMyDefaultAdministratorRights) (bool, error) { req := NewRequest[bool]("setMyDefaultAdministratorRights", params) - return req.Do(api) + return api.Do(req) } // SetMyDefaultAdministratorRightsWithContext is the context-aware variant of SetMyDefaultAdministratorRights. @@ -384,7 +384,7 @@ func (api *API) SetMyDefaultAdministratorRights(params SetMyDefaultAdministrator // See https://core.telegram.org/bots/api#setmydefaultadministratorrights func (api *API) SetMyDefaultAdministratorRightsWithContext(ctx context.Context, params SetMyDefaultAdministratorRights) (bool, error) { req := NewRequest[bool]("setMyDefaultAdministratorRights", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // GetMyDefaultAdministratorRights holds parameters for the getMyDefaultAdministratorRights method. @@ -401,7 +401,7 @@ type GetMyDefaultAdministratorRights struct { // See https://core.telegram.org/bots/api#getmydefaultadministratorrights func (api *API) GetMyDefaultAdministratorRights(params GetMyDefaultAdministratorRights) (ChatAdministratorRights, error) { req := NewRequest[ChatAdministratorRights]("getMyDefaultAdministratorRights", params) - return req.Do(api) + return api.Do(req) } // GetMyDefaultAdministratorRightsWithContext is the context-aware variant of GetMyDefaultAdministratorRights. @@ -410,7 +410,7 @@ func (api *API) GetMyDefaultAdministratorRights(params GetMyDefaultAdministrator // See https://core.telegram.org/bots/api#getmydefaultadministratorrights func (api *API) GetMyDefaultAdministratorRightsWithContext(ctx context.Context, params GetMyDefaultAdministratorRights) (ChatAdministratorRights, error) { req := NewRequest[ChatAdministratorRights]("getMyDefaultAdministratorRights", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // GetAvailableGifts returns the list of gifts that can be sent by the bot. @@ -418,7 +418,7 @@ func (api *API) GetMyDefaultAdministratorRightsWithContext(ctx context.Context, // See https://core.telegram.org/bots/api#getavailablegifts func (api *API) GetAvailableGifts() (Gifts, error) { req := NewRequest[Gifts]("getAvailableGifts", NoParams) - return req.Do(api) + return api.Do(req) } // GetAvailableGiftsWithContext is the context-aware variant of GetAvailableGifts. @@ -427,7 +427,7 @@ func (api *API) GetAvailableGifts() (Gifts, error) { // See https://core.telegram.org/bots/api#getavailablegifts func (api *API) GetAvailableGiftsWithContext(ctx context.Context) (Gifts, error) { req := NewRequest[Gifts]("getAvailableGifts", NoParams) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // SendGift holds parameters for the sendGift method. @@ -463,7 +463,7 @@ type SendGift struct { // See https://core.telegram.org/bots/api#sendgift func (api *API) SendGift(params SendGift) (bool, error) { req := NewRequest[bool]("sendGift", params) - return req.Do(api) + return api.Do(req) } // SendGiftWithContext is the context-aware variant of SendGift. @@ -472,7 +472,7 @@ func (api *API) SendGift(params SendGift) (bool, error) { // See https://core.telegram.org/bots/api#sendgift func (api *API) SendGiftWithContext(ctx context.Context, params SendGift) (bool, error) { req := NewRequest[bool]("sendGift", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // GiftPremiumSubscription holds parameters for the giftPremiumSubscription method. @@ -506,7 +506,7 @@ type GiftPremiumSubscription struct { // See https://core.telegram.org/bots/api#giftpremiumsubscription func (api *API) GiftPremiumSubscription(params GiftPremiumSubscription) (bool, error) { req := NewRequest[bool]("giftPremiumSubscription", params) - return req.Do(api) + return api.Do(req) } // GiftPremiumSubscriptionWithContext is the context-aware variant of GiftPremiumSubscription. @@ -515,7 +515,7 @@ func (api *API) GiftPremiumSubscription(params GiftPremiumSubscription) (bool, e // See https://core.telegram.org/bots/api#giftpremiumsubscription func (api *API) GiftPremiumSubscriptionWithContext(ctx context.Context, params GiftPremiumSubscription) (bool, error) { req := NewRequest[bool]("giftPremiumSubscription", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // GetManagedBotAccessSettings holds parameters for the getManagedBotAccessSettings method. @@ -531,7 +531,7 @@ type GetManagedBotAccessSettings struct { // See https://core.telegram.org/bots/api#getmanagedbotaccesssettings func (api *API) GetManagedBotAccessSettings(params GetManagedBotAccessSettings) (BotAccessSettings, error) { req := NewRequest[BotAccessSettings]("getManagedBotAccessSettings", params) - return req.Do(api) + return api.Do(req) } // GetManagedBotAccessSettingsWithContext is the context-aware variant of GetManagedBotAccessSettings. @@ -540,7 +540,7 @@ func (api *API) GetManagedBotAccessSettings(params GetManagedBotAccessSettings) // See https://core.telegram.org/bots/api#getmanagedbotaccesssettings func (api *API) GetManagedBotAccessSettingsWithContext(ctx context.Context, params GetManagedBotAccessSettings) (BotAccessSettings, error) { req := NewRequest[BotAccessSettings]("getManagedBotAccessSettings", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // SetManagedBotAccessSettings holds parameters for the setManagedBotAccessSettings method. @@ -559,7 +559,7 @@ type SetManagedBotAccessSettings struct { // See https://core.telegram.org/bots/api#setmanagedbotaccesssettings func (api *API) SetManagedBotAccessSettings(params SetManagedBotAccessSettings) (bool, error) { req := NewRequest[bool]("setManagedBotAccessSettings", params) - return req.Do(api) + return api.Do(req) } // SetManagedBotAccessSettingsWithContext is the context-aware variant of SetManagedBotAccessSettings. @@ -568,5 +568,5 @@ func (api *API) SetManagedBotAccessSettings(params SetManagedBotAccessSettings) // See https://core.telegram.org/bots/api#setmanagedbotaccesssettings func (api *API) SetManagedBotAccessSettingsWithContext(ctx context.Context, params SetManagedBotAccessSettings) (bool, error) { req := NewRequest[bool]("setManagedBotAccessSettings", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } diff --git a/tgapi/business_methods.go b/tgapi/business_methods.go index f6039aa..9eafd08 100644 --- a/tgapi/business_methods.go +++ b/tgapi/business_methods.go @@ -19,7 +19,7 @@ type VerifyUser struct { // See https://core.telegram.org/bots/api#verifyuser func (api *API) VerifyUser(params VerifyUser) (bool, error) { req := NewRequest[bool]("verifyUser", params) - return req.Do(api) + return api.Do(req) } // VerifyUserWithContext is the context-aware variant of VerifyUser. @@ -28,7 +28,7 @@ func (api *API) VerifyUser(params VerifyUser) (bool, error) { // See https://core.telegram.org/bots/api#verifyuser func (api *API) VerifyUserWithContext(ctx context.Context, params VerifyUser) (bool, error) { req := NewRequest[bool]("verifyUser", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // VerifyChat holds parameters for the verifyChat method. @@ -49,7 +49,7 @@ type VerifyChat struct { // See https://core.telegram.org/bots/api#verifychat func (api *API) VerifyChat(params VerifyChat) (bool, error) { req := NewRequest[bool]("verifyChat", params) - return req.Do(api) + return api.Do(req) } // VerifyChatWithContext is the context-aware variant of VerifyChat. @@ -58,7 +58,7 @@ func (api *API) VerifyChat(params VerifyChat) (bool, error) { // See https://core.telegram.org/bots/api#verifychat func (api *API) VerifyChatWithContext(ctx context.Context, params VerifyChat) (bool, error) { req := NewRequest[bool]("verifyChat", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // RemoveUserVerification holds parameters for the removeUserVerification method. @@ -75,7 +75,7 @@ type RemoveUserVerification struct { // See https://core.telegram.org/bots/api#removeuserverification func (api *API) RemoveUserVerification(params RemoveUserVerification) (bool, error) { req := NewRequest[bool]("removeUserVerification", params) - return req.Do(api) + return api.Do(req) } // RemoveUserVerificationWithContext is the context-aware variant of RemoveUserVerification. @@ -84,7 +84,7 @@ func (api *API) RemoveUserVerification(params RemoveUserVerification) (bool, err // See https://core.telegram.org/bots/api#removeuserverification func (api *API) RemoveUserVerificationWithContext(ctx context.Context, params RemoveUserVerification) (bool, error) { req := NewRequest[bool]("removeUserVerification", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // RemoveChatVerification holds parameters for the removeChatVerification method. @@ -102,7 +102,7 @@ type RemoveChatVerification struct { // See https://core.telegram.org/bots/api#removechatverification func (api *API) RemoveChatVerification(params RemoveChatVerification) (bool, error) { req := NewRequest[bool]("removeChatVerification", params) - return req.Do(api) + return api.Do(req) } // RemoveChatVerificationWithContext is the context-aware variant of RemoveChatVerification. @@ -111,7 +111,7 @@ func (api *API) RemoveChatVerification(params RemoveChatVerification) (bool, err // See https://core.telegram.org/bots/api#removechatverification func (api *API) RemoveChatVerificationWithContext(ctx context.Context, params RemoveChatVerification) (bool, error) { req := NewRequest[bool]("removeChatVerification", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // ReadBusinessMessage holds parameters for the readBusinessMessage method. @@ -134,7 +134,7 @@ type ReadBusinessMessage struct { // See https://core.telegram.org/bots/api#readbusinessmessage func (api *API) ReadBusinessMessage(params ReadBusinessMessage) (bool, error) { req := NewRequest[bool]("readBusinessMessage", params) - return req.Do(api) + return api.Do(req) } // ReadBusinessMessageWithContext is the context-aware variant of ReadBusinessMessage. @@ -143,7 +143,7 @@ func (api *API) ReadBusinessMessage(params ReadBusinessMessage) (bool, error) { // See https://core.telegram.org/bots/api#readbusinessmessage func (api *API) ReadBusinessMessageWithContext(ctx context.Context, params ReadBusinessMessage) (bool, error) { req := NewRequest[bool]("readBusinessMessage", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // GetBusinessConnection holds parameters for the getBusinessConnection method. @@ -159,7 +159,7 @@ type GetBusinessConnection struct { // See https://core.telegram.org/bots/api#getbusinessconnection func (api *API) GetBusinessConnection(params GetBusinessConnection) (BusinessConnection, error) { req := NewRequest[BusinessConnection]("getBusinessConnection", params) - return req.Do(api) + return api.Do(req) } // GetBusinessConnectionWithContext is the context-aware variant of GetBusinessConnection. @@ -168,7 +168,7 @@ func (api *API) GetBusinessConnection(params GetBusinessConnection) (BusinessCon // See https://core.telegram.org/bots/api#getbusinessconnection func (api *API) GetBusinessConnectionWithContext(ctx context.Context, params GetBusinessConnection) (BusinessConnection, error) { req := NewRequest[BusinessConnection]("getBusinessConnection", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // DeleteBusinessMessages holds parameters for the deleteBusinessMessages method. @@ -189,7 +189,7 @@ type DeleteBusinessMessages struct { // See https://core.telegram.org/bots/api#deletebusinessmessages func (api *API) DeleteBusinessMessages(params DeleteBusinessMessages) (bool, error) { req := NewRequest[bool]("deleteBusinessMessages", params) - return req.Do(api) + return api.Do(req) } // DeleteBusinessMessagesWithContext is the context-aware variant of DeleteBusinessMessages. @@ -198,7 +198,7 @@ func (api *API) DeleteBusinessMessages(params DeleteBusinessMessages) (bool, err // See https://core.telegram.org/bots/api#deletebusinessmessages func (api *API) DeleteBusinessMessagesWithContext(ctx context.Context, params DeleteBusinessMessages) (bool, error) { req := NewRequest[bool]("deleteBusinessMessages", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // SetBusinessAccountName holds parameters for the setBusinessAccountName method. @@ -219,7 +219,7 @@ type SetBusinessAccountName struct { // See https://core.telegram.org/bots/api#setbusinessaccountname func (api *API) SetBusinessAccountName(params SetBusinessAccountName) (bool, error) { req := NewRequest[bool]("setBusinessAccountName", params) - return req.Do(api) + return api.Do(req) } // SetBusinessAccountNameWithContext is the context-aware variant of SetBusinessAccountName. @@ -228,7 +228,7 @@ func (api *API) SetBusinessAccountName(params SetBusinessAccountName) (bool, err // See https://core.telegram.org/bots/api#setbusinessaccountname func (api *API) SetBusinessAccountNameWithContext(ctx context.Context, params SetBusinessAccountName) (bool, error) { req := NewRequest[bool]("setBusinessAccountName", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // SetBusinessAccountUsername holds parameters for the setBusinessAccountUsername method. @@ -247,7 +247,7 @@ type SetBusinessAccountUsername struct { // See https://core.telegram.org/bots/api#setbusinessaccountusername func (api *API) SetBusinessAccountUsername(params SetBusinessAccountUsername) (bool, error) { req := NewRequest[bool]("setBusinessAccountUsername", params) - return req.Do(api) + return api.Do(req) } // SetBusinessAccountUsernameWithContext is the context-aware variant of SetBusinessAccountUsername. @@ -256,7 +256,7 @@ func (api *API) SetBusinessAccountUsername(params SetBusinessAccountUsername) (b // See https://core.telegram.org/bots/api#setbusinessaccountusername func (api *API) SetBusinessAccountUsernameWithContext(ctx context.Context, params SetBusinessAccountUsername) (bool, error) { req := NewRequest[bool]("setBusinessAccountUsername", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // SetBusinessAccountBio holds parameters for the setBusinessAccountBio method. @@ -275,7 +275,7 @@ type SetBusinessAccountBio struct { // See https://core.telegram.org/bots/api#setbusinessaccountbio func (api *API) SetBusinessAccountBio(params SetBusinessAccountBio) (bool, error) { req := NewRequest[bool]("setBusinessAccountBio", params) - return req.Do(api) + return api.Do(req) } // SetBusinessAccountBioWithContext is the context-aware variant of SetBusinessAccountBio. @@ -284,7 +284,7 @@ func (api *API) SetBusinessAccountBio(params SetBusinessAccountBio) (bool, error // See https://core.telegram.org/bots/api#setbusinessaccountbio func (api *API) SetBusinessAccountBioWithContext(ctx context.Context, params SetBusinessAccountBio) (bool, error) { req := NewRequest[bool]("setBusinessAccountBio", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // SetBusinessAccountProfilePhoto holds parameters for the setBusinessAccountProfilePhoto method. @@ -306,7 +306,7 @@ type SetBusinessAccountProfilePhoto struct { // See https://core.telegram.org/bots/api#setbusinessaccountprofilephoto func (api *API) SetBusinessAccountProfilePhoto(params SetBusinessAccountProfilePhoto) (bool, error) { req := NewRequest[bool]("setBusinessAccountProfilePhoto", params) - return req.Do(api) + return api.Do(req) } // SetBusinessAccountProfilePhotoWithContext is the context-aware variant of SetBusinessAccountProfilePhoto. @@ -315,7 +315,7 @@ func (api *API) SetBusinessAccountProfilePhoto(params SetBusinessAccountProfileP // See https://core.telegram.org/bots/api#setbusinessaccountprofilephoto func (api *API) SetBusinessAccountProfilePhotoWithContext(ctx context.Context, params SetBusinessAccountProfilePhoto) (bool, error) { req := NewRequest[bool]("setBusinessAccountProfilePhoto", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // RemoveBusinessAccountProfilePhoto holds parameters for the removeBusinessAccountProfilePhoto method. @@ -336,7 +336,7 @@ type RemoveBusinessAccountProfilePhoto struct { // See https://core.telegram.org/bots/api#removebusinessaccountprofilephoto func (api *API) RemoveBusinessAccountProfilePhoto(params RemoveBusinessAccountProfilePhoto) (bool, error) { req := NewRequest[bool]("removeBusinessAccountProfilePhoto", params) - return req.Do(api) + return api.Do(req) } // RemoveBusinessAccountProfilePhotoWithContext is the context-aware variant of RemoveBusinessAccountProfilePhoto. @@ -345,7 +345,7 @@ func (api *API) RemoveBusinessAccountProfilePhoto(params RemoveBusinessAccountPr // See https://core.telegram.org/bots/api#removebusinessaccountprofilephoto func (api *API) RemoveBusinessAccountProfilePhotoWithContext(ctx context.Context, params RemoveBusinessAccountProfilePhoto) (bool, error) { req := NewRequest[bool]("removeBusinessAccountProfilePhoto", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // SetBusinessAccountGiftSettings holds parameters for the setBusinessAccountGiftSettings method. @@ -367,7 +367,7 @@ type SetBusinessAccountGiftSettings struct { // See https://core.telegram.org/bots/api#setbusinessaccountgiftsettings func (api *API) SetBusinessAccountGiftSettings(params SetBusinessAccountGiftSettings) (bool, error) { req := NewRequest[bool]("setBusinessAccountGiftSettings", params) - return req.Do(api) + return api.Do(req) } // SetBusinessAccountGiftSettingsWithContext is the context-aware variant of SetBusinessAccountGiftSettings. @@ -376,7 +376,7 @@ func (api *API) SetBusinessAccountGiftSettings(params SetBusinessAccountGiftSett // See https://core.telegram.org/bots/api#setbusinessaccountgiftsettings func (api *API) SetBusinessAccountGiftSettingsWithContext(ctx context.Context, params SetBusinessAccountGiftSettings) (bool, error) { req := NewRequest[bool]("setBusinessAccountGiftSettings", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // GetBusinessAccountStarBalance holds parameters for the getBusinessAccountStarBalance method. @@ -392,7 +392,7 @@ type GetBusinessAccountStarBalance struct { // See https://core.telegram.org/bots/api#getbusinessaccountstarbalance func (api *API) GetBusinessAccountStarBalance(params GetBusinessAccountStarBalance) (StarAmount, error) { req := NewRequest[StarAmount]("getBusinessAccountStarBalance", params) - return req.Do(api) + return api.Do(req) } // GetBusinessAccountStarBalanceWithContext is the context-aware variant of GetBusinessAccountStarBalance. @@ -401,7 +401,7 @@ func (api *API) GetBusinessAccountStarBalance(params GetBusinessAccountStarBalan // See https://core.telegram.org/bots/api#getbusinessaccountstarbalance func (api *API) GetBusinessAccountStarBalanceWithContext(ctx context.Context, params GetBusinessAccountStarBalance) (StarAmount, error) { req := NewRequest[StarAmount]("getBusinessAccountStarBalance", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // TransferBusinessAccountStars holds parameters for the transferBusinessAccountStars method. @@ -420,7 +420,7 @@ type TransferBusinessAccountStars struct { // See https://core.telegram.org/bots/api#transferbusinessaccountstars func (api *API) TransferBusinessAccountStars(params TransferBusinessAccountStars) (bool, error) { req := NewRequest[bool]("transferBusinessAccountStars", params) - return req.Do(api) + return api.Do(req) } // TransferBusinessAccountStarsWithContext is the context-aware variant of TransferBusinessAccountStars. @@ -429,7 +429,7 @@ func (api *API) TransferBusinessAccountStars(params TransferBusinessAccountStars // See https://core.telegram.org/bots/api#transferbusinessaccountstars func (api *API) TransferBusinessAccountStarsWithContext(ctx context.Context, params TransferBusinessAccountStars) (bool, error) { req := NewRequest[bool]("transferBusinessAccountStars", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // GetBusinessAccountGifts holds parameters for the getBusinessAccountGifts method. @@ -470,7 +470,7 @@ type GetBusinessAccountGifts struct { // See https://core.telegram.org/bots/api#getbusinessaccountgifts func (api *API) GetBusinessAccountGifts(params GetBusinessAccountGifts) (OwnedGifts, error) { req := NewRequest[OwnedGifts]("getBusinessAccountGifts", params) - return req.Do(api) + return api.Do(req) } // GetBusinessAccountGiftsWithContext is the context-aware variant of GetBusinessAccountGifts. @@ -479,7 +479,7 @@ func (api *API) GetBusinessAccountGifts(params GetBusinessAccountGifts) (OwnedGi // See https://core.telegram.org/bots/api#getbusinessaccountgifts func (api *API) GetBusinessAccountGiftsWithContext(ctx context.Context, params GetBusinessAccountGifts) (OwnedGifts, error) { req := NewRequest[OwnedGifts]("getBusinessAccountGifts", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // ConvertGiftToStars holds parameters for the convertGiftToStars method. @@ -498,7 +498,7 @@ type ConvertGiftToStars struct { // See https://core.telegram.org/bots/api#convertgifttostars func (api *API) ConvertGiftToStars(params ConvertGiftToStars) (bool, error) { req := NewRequest[bool]("convertGiftToStars", params) - return req.Do(api) + return api.Do(req) } // ConvertGiftToStarsWithContext is the context-aware variant of ConvertGiftToStars. @@ -507,7 +507,7 @@ func (api *API) ConvertGiftToStars(params ConvertGiftToStars) (bool, error) { // See https://core.telegram.org/bots/api#convertgifttostars func (api *API) ConvertGiftToStarsWithContext(ctx context.Context, params ConvertGiftToStars) (bool, error) { req := NewRequest[bool]("convertGiftToStars", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // UpgradeGift holds parameters for the upgradeGift method. @@ -533,7 +533,7 @@ type UpgradeGift struct { // See https://core.telegram.org/bots/api#upgradegift func (api *API) UpgradeGift(params UpgradeGift) (bool, error) { req := NewRequest[bool]("upgradeGift", params) - return req.Do(api) + return api.Do(req) } // UpgradeGiftWithContext is the context-aware variant of UpgradeGift. @@ -542,7 +542,7 @@ func (api *API) UpgradeGift(params UpgradeGift) (bool, error) { // See https://core.telegram.org/bots/api#upgradegift func (api *API) UpgradeGiftWithContext(ctx context.Context, params UpgradeGift) (bool, error) { req := NewRequest[bool]("upgradeGift", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // TransferGift holds parameters for the transferGift method. @@ -567,7 +567,7 @@ type TransferGift struct { // See https://core.telegram.org/bots/api#transfergift func (api *API) TransferGift(params TransferGift) (bool, error) { req := NewRequest[bool]("transferGift", params) - return req.Do(api) + return api.Do(req) } // TransferGiftWithContext is the context-aware variant of TransferGift. @@ -576,7 +576,7 @@ func (api *API) TransferGift(params TransferGift) (bool, error) { // See https://core.telegram.org/bots/api#transfergift func (api *API) TransferGiftWithContext(ctx context.Context, params TransferGift) (bool, error) { req := NewRequest[bool]("transferGift", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // PostStory holds parameters for the postStory method. @@ -614,7 +614,7 @@ type PostStory struct { // See https://core.telegram.org/bots/api#poststory func (api *API) PostStory(params PostStory) (Story, error) { req := NewRequest[Story]("postStory", params) - return req.Do(api) + return api.Do(req) } // PostStoryWithContext is the context-aware variant of PostStory. @@ -623,7 +623,7 @@ func (api *API) PostStory(params PostStory) (Story, error) { // See https://core.telegram.org/bots/api#poststory func (api *API) PostStoryWithContext(ctx context.Context, params PostStory) (Story, error) { req := NewRequest[Story]("postStory", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // RepostStory holds parameters for the repostStory method. @@ -652,7 +652,7 @@ type RepostStory struct { // See https://core.telegram.org/bots/api#repoststory func (api *API) RepostStory(params RepostStory) (Story, error) { req := NewRequest[Story]("repostStory", params) - return req.Do(api) + return api.Do(req) } // RepostStoryWithContext is the context-aware variant of RepostStory. @@ -661,7 +661,7 @@ func (api *API) RepostStory(params RepostStory) (Story, error) { // See https://core.telegram.org/bots/api#repoststory func (api *API) RepostStoryWithContext(ctx context.Context, params RepostStory) (Story, error) { req := NewRequest[Story]("repostStory", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // EditStory holds parameters for the editStory method. @@ -693,7 +693,7 @@ type EditStory struct { // See https://core.telegram.org/bots/api#editstory func (api *API) EditStory(params EditStory) (Story, error) { req := NewRequest[Story]("editStory", params) - return req.Do(api) + return api.Do(req) } // EditStoryWithContext is the context-aware variant of EditStory. @@ -702,7 +702,7 @@ func (api *API) EditStory(params EditStory) (Story, error) { // See https://core.telegram.org/bots/api#editstory func (api *API) EditStoryWithContext(ctx context.Context, params EditStory) (Story, error) { req := NewRequest[Story]("editStory", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // DeleteStory holds parameters for the deleteStory method. @@ -721,7 +721,7 @@ type DeleteStory struct { // See https://core.telegram.org/bots/api#deletestory func (api *API) DeleteStory(params DeleteStory) (bool, error) { req := NewRequest[bool]("deleteStory", params) - return req.Do(api) + return api.Do(req) } // DeleteStoryWithContext is the context-aware variant of DeleteStory. @@ -730,5 +730,5 @@ func (api *API) DeleteStory(params DeleteStory) (bool, error) { // See https://core.telegram.org/bots/api#deletestory func (api *API) DeleteStoryWithContext(ctx context.Context, params DeleteStory) (bool, error) { req := NewRequest[bool]("deleteStory", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } diff --git a/tgapi/chat_methods.go b/tgapi/chat_methods.go index 3ddad51..611de48 100644 --- a/tgapi/chat_methods.go +++ b/tgapi/chat_methods.go @@ -27,7 +27,7 @@ type BanChatMember struct { // See https://core.telegram.org/bots/api#banchatmember func (api *API) BanChatMember(params BanChatMember) (bool, error) { req := NewRequestWithChatID[bool]("banChatMember", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // BanChatMemberWithContext is the context-aware variant of BanChatMember. @@ -36,7 +36,7 @@ func (api *API) BanChatMember(params BanChatMember) (bool, error) { // See https://core.telegram.org/bots/api#banchatmember func (api *API) BanChatMemberWithContext(ctx context.Context, params BanChatMember) (bool, error) { req := NewRequestWithChatID[bool]("banChatMember", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // UnbanChatMember holds parameters for the unbanChatMember method. @@ -58,7 +58,7 @@ type UnbanChatMember struct { // See https://core.telegram.org/bots/api#unbanchatmember func (api *API) UnbanChatMember(params UnbanChatMember) (bool, error) { req := NewRequestWithChatID[bool]("unbanChatMember", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // UnbanChatMemberWithContext is the context-aware variant of UnbanChatMember. @@ -67,7 +67,7 @@ func (api *API) UnbanChatMember(params UnbanChatMember) (bool, error) { // See https://core.telegram.org/bots/api#unbanchatmember func (api *API) UnbanChatMemberWithContext(ctx context.Context, params UnbanChatMember) (bool, error) { req := NewRequestWithChatID[bool]("unbanChatMember", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // RestrictChatMember holds parameters for the restrictChatMember method. @@ -99,7 +99,7 @@ type RestrictChatMember struct { // See https://core.telegram.org/bots/api#restrictchatmember func (api *API) RestrictChatMember(params RestrictChatMember) (bool, error) { req := NewRequestWithChatID[bool]("restrictChatMember", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // RestrictChatMemberWithContext is the context-aware variant of RestrictChatMember. @@ -108,7 +108,7 @@ func (api *API) RestrictChatMember(params RestrictChatMember) (bool, error) { // See https://core.telegram.org/bots/api#restrictchatmember func (api *API) RestrictChatMemberWithContext(ctx context.Context, params RestrictChatMember) (bool, error) { req := NewRequestWithChatID[bool]("restrictChatMember", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // PromoteChatMember holds parameters for the promoteChatMember method. @@ -167,6 +167,8 @@ type PromoteChatMember struct { // CanManageTags Optional. Pass True if the administrator can edit the tags of regular members; for groups // and supergroups only CanManageTags bool `json:"can_manage_tags,omitempty"` // Since: Bot API 9.5 + // CanSendWelcomeMessages allows the administrator to manage chat welcome messages or send them as a bot. + CanSendWelcomeMessages bool `json:"can_send_welcome_messages,omitempty"` // Since: Bot API 10.3 } // PromoteChatMember promotes or demotes a user in a chat. @@ -175,7 +177,7 @@ type PromoteChatMember struct { // See https://core.telegram.org/bots/api#promotechatmember func (api *API) PromoteChatMember(params PromoteChatMember) (bool, error) { req := NewRequestWithChatID[bool]("promoteChatMember", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // PromoteChatMemberWithContext is the context-aware variant of PromoteChatMember. @@ -184,7 +186,7 @@ func (api *API) PromoteChatMember(params PromoteChatMember) (bool, error) { // See https://core.telegram.org/bots/api#promotechatmember func (api *API) PromoteChatMemberWithContext(ctx context.Context, params PromoteChatMember) (bool, error) { req := NewRequestWithChatID[bool]("promoteChatMember", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // SetChatAdministratorCustomTitle holds parameters for the setChatAdministratorCustomTitle method. @@ -206,7 +208,7 @@ type SetChatAdministratorCustomTitle struct { // See https://core.telegram.org/bots/api#setchatadministratorcustomtitle func (api *API) SetChatAdministratorCustomTitle(params SetChatAdministratorCustomTitle) (bool, error) { req := NewRequestWithChatID[bool]("setChatAdministratorCustomTitle", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // SetChatAdministratorCustomTitleWithContext is the context-aware variant of SetChatAdministratorCustomTitle. @@ -215,7 +217,7 @@ func (api *API) SetChatAdministratorCustomTitle(params SetChatAdministratorCusto // See https://core.telegram.org/bots/api#setchatadministratorcustomtitle func (api *API) SetChatAdministratorCustomTitleWithContext(ctx context.Context, params SetChatAdministratorCustomTitle) (bool, error) { req := NewRequestWithChatID[bool]("setChatAdministratorCustomTitle", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // SetChatMemberTag holds parameters for the setChatMemberTag method. @@ -237,7 +239,7 @@ type SetChatMemberTag struct { // See https://core.telegram.org/bots/api#setchatmembertag func (api *API) SetChatMemberTag(params SetChatMemberTag) (bool, error) { req := NewRequestWithChatID[bool]("setChatMemberTag", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // SetChatMemberTagWithContext is the context-aware variant of SetChatMemberTag. @@ -246,7 +248,7 @@ func (api *API) SetChatMemberTag(params SetChatMemberTag) (bool, error) { // See https://core.telegram.org/bots/api#setchatmembertag func (api *API) SetChatMemberTagWithContext(ctx context.Context, params SetChatMemberTag) (bool, error) { req := NewRequestWithChatID[bool]("setChatMemberTag", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // BanChatSenderChat holds parameters for the banChatSenderChat method. @@ -266,7 +268,7 @@ type BanChatSenderChat struct { // See https://core.telegram.org/bots/api#banchatsenderchat func (api *API) BanChatSenderChat(params BanChatSenderChat) (bool, error) { req := NewRequestWithChatID[bool]("banChatSenderChat", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // BanChatSenderChatWithContext is the context-aware variant of BanChatSenderChat. @@ -275,7 +277,7 @@ func (api *API) BanChatSenderChat(params BanChatSenderChat) (bool, error) { // See https://core.telegram.org/bots/api#banchatsenderchat func (api *API) BanChatSenderChatWithContext(ctx context.Context, params BanChatSenderChat) (bool, error) { req := NewRequestWithChatID[bool]("banChatSenderChat", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // UnbanChatSenderChat holds parameters for the unbanChatSenderChat method. @@ -295,7 +297,7 @@ type UnbanChatSenderChat struct { // See https://core.telegram.org/bots/api#unbanchatsenderchat func (api *API) UnbanChatSenderChat(params UnbanChatSenderChat) (bool, error) { req := NewRequestWithChatID[bool]("unbanChatSenderChat", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // UnbanChatSenderChatWithContext is the context-aware variant of UnbanChatSenderChat. @@ -304,7 +306,7 @@ func (api *API) UnbanChatSenderChat(params UnbanChatSenderChat) (bool, error) { // See https://core.telegram.org/bots/api#unbanchatsenderchat func (api *API) UnbanChatSenderChatWithContext(ctx context.Context, params UnbanChatSenderChat) (bool, error) { req := NewRequestWithChatID[bool]("unbanChatSenderChat", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // SetChatPermissions holds parameters for the setChatPermissions method. @@ -330,7 +332,7 @@ type SetChatPermissions struct { // See https://core.telegram.org/bots/api#setchatpermissions func (api *API) SetChatPermissions(params SetChatPermissions) (bool, error) { req := NewRequestWithChatID[bool]("setChatPermissions", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // SetChatPermissionsWithContext is the context-aware variant of SetChatPermissions. @@ -339,7 +341,7 @@ func (api *API) SetChatPermissions(params SetChatPermissions) (bool, error) { // See https://core.telegram.org/bots/api#setchatpermissions func (api *API) SetChatPermissionsWithContext(ctx context.Context, params SetChatPermissions) (bool, error) { req := NewRequestWithChatID[bool]("setChatPermissions", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // ExportChatInviteLink holds parameters for the exportChatInviteLink method. @@ -357,7 +359,7 @@ type ExportChatInviteLink struct { // See https://core.telegram.org/bots/api#exportchatinvitelink func (api *API) ExportChatInviteLink(params ExportChatInviteLink) (string, error) { req := NewRequestWithChatID[string]("exportChatInviteLink", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // ExportChatInviteLinkWithContext is the context-aware variant of ExportChatInviteLink. @@ -366,7 +368,7 @@ func (api *API) ExportChatInviteLink(params ExportChatInviteLink) (string, error // See https://core.telegram.org/bots/api#exportchatinvitelink func (api *API) ExportChatInviteLinkWithContext(ctx context.Context, params ExportChatInviteLink) (string, error) { req := NewRequestWithChatID[string]("exportChatInviteLink", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // CreateChatInviteLink holds parameters for the createChatInviteLink method. @@ -394,7 +396,7 @@ type CreateChatInviteLink struct { // See https://core.telegram.org/bots/api#createchatinvitelink func (api *API) CreateChatInviteLink(params CreateChatInviteLink) (ChatInviteLink, error) { req := NewRequestWithChatID[ChatInviteLink]("createChatInviteLink", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // CreateChatInviteLinkWithContext is the context-aware variant of CreateChatInviteLink. @@ -403,7 +405,7 @@ func (api *API) CreateChatInviteLink(params CreateChatInviteLink) (ChatInviteLin // See https://core.telegram.org/bots/api#createchatinvitelink func (api *API) CreateChatInviteLinkWithContext(ctx context.Context, params CreateChatInviteLink) (ChatInviteLink, error) { req := NewRequestWithChatID[ChatInviteLink]("createChatInviteLink", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // EditChatInviteLink holds parameters for the editChatInviteLink method. @@ -434,7 +436,7 @@ type EditChatInviteLink struct { // See https://core.telegram.org/bots/api#editchatinvitelink func (api *API) EditChatInviteLink(params EditChatInviteLink) (ChatInviteLink, error) { req := NewRequestWithChatID[ChatInviteLink]("editChatInviteLink", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // EditChatInviteLinkWithContext is the context-aware variant of EditChatInviteLink. @@ -443,7 +445,7 @@ func (api *API) EditChatInviteLink(params EditChatInviteLink) (ChatInviteLink, e // See https://core.telegram.org/bots/api#editchatinvitelink func (api *API) EditChatInviteLinkWithContext(ctx context.Context, params EditChatInviteLink) (ChatInviteLink, error) { req := NewRequestWithChatID[ChatInviteLink]("editChatInviteLink", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // CreateChatSubscriptionInviteLink holds parameters for the createChatSubscriptionInviteLink method. @@ -469,7 +471,7 @@ type CreateChatSubscriptionInviteLink struct { // See https://core.telegram.org/bots/api#createchatsubscriptioninvitelink func (api *API) CreateChatSubscriptionInviteLink(params CreateChatSubscriptionInviteLink) (ChatInviteLink, error) { req := NewRequestWithChatID[ChatInviteLink]("createChatSubscriptionInviteLink", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // CreateChatSubscriptionInviteLinkWithContext is the context-aware variant of CreateChatSubscriptionInviteLink. @@ -478,7 +480,7 @@ func (api *API) CreateChatSubscriptionInviteLink(params CreateChatSubscriptionIn // See https://core.telegram.org/bots/api#createchatsubscriptioninvitelink func (api *API) CreateChatSubscriptionInviteLinkWithContext(ctx context.Context, params CreateChatSubscriptionInviteLink) (ChatInviteLink, error) { req := NewRequestWithChatID[ChatInviteLink]("createChatSubscriptionInviteLink", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // EditChatSubscriptionInviteLink holds parameters for the editChatSubscriptionInviteLink method. @@ -500,7 +502,7 @@ type EditChatSubscriptionInviteLink struct { // See https://core.telegram.org/bots/api#editchatsubscriptioninvitelink func (api *API) EditChatSubscriptionInviteLink(params EditChatSubscriptionInviteLink) (ChatInviteLink, error) { req := NewRequestWithChatID[ChatInviteLink]("editChatSubscriptionInviteLink", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // EditChatSubscriptionInviteLinkWithContext is the context-aware variant of EditChatSubscriptionInviteLink. @@ -509,7 +511,7 @@ func (api *API) EditChatSubscriptionInviteLink(params EditChatSubscriptionInvite // See https://core.telegram.org/bots/api#editchatsubscriptioninvitelink func (api *API) EditChatSubscriptionInviteLinkWithContext(ctx context.Context, params EditChatSubscriptionInviteLink) (ChatInviteLink, error) { req := NewRequestWithChatID[ChatInviteLink]("editChatSubscriptionInviteLink", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // RevokeChatInviteLink holds parameters for the revokeChatInviteLink method. @@ -529,7 +531,7 @@ type RevokeChatInviteLink struct { // See https://core.telegram.org/bots/api#revokechatinvitelink func (api *API) RevokeChatInviteLink(params RevokeChatInviteLink) (ChatInviteLink, error) { req := NewRequestWithChatID[ChatInviteLink]("revokeChatInviteLink", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // RevokeChatInviteLinkWithContext is the context-aware variant of RevokeChatInviteLink. @@ -538,7 +540,7 @@ func (api *API) RevokeChatInviteLink(params RevokeChatInviteLink) (ChatInviteLin // See https://core.telegram.org/bots/api#revokechatinvitelink func (api *API) RevokeChatInviteLinkWithContext(ctx context.Context, params RevokeChatInviteLink) (ChatInviteLink, error) { req := NewRequestWithChatID[ChatInviteLink]("revokeChatInviteLink", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // ApproveChatJoinRequest holds parameters for the approveChatJoinRequest method. @@ -558,7 +560,7 @@ type ApproveChatJoinRequest struct { // See https://core.telegram.org/bots/api#approvechatjoinrequest func (api *API) ApproveChatJoinRequest(params ApproveChatJoinRequest) (bool, error) { req := NewRequestWithChatID[bool]("approveChatJoinRequest", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // ApproveChatJoinRequestWithContext is the context-aware variant of ApproveChatJoinRequest. @@ -567,7 +569,7 @@ func (api *API) ApproveChatJoinRequest(params ApproveChatJoinRequest) (bool, err // See https://core.telegram.org/bots/api#approvechatjoinrequest func (api *API) ApproveChatJoinRequestWithContext(ctx context.Context, params ApproveChatJoinRequest) (bool, error) { req := NewRequestWithChatID[bool]("approveChatJoinRequest", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // DeclineChatJoinRequest holds parameters for the declineChatJoinRequest method. @@ -587,7 +589,7 @@ type DeclineChatJoinRequest struct { // See https://core.telegram.org/bots/api#declinechatjoinrequest func (api *API) DeclineChatJoinRequest(params DeclineChatJoinRequest) (bool, error) { req := NewRequestWithChatID[bool]("declineChatJoinRequest", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // DeclineChatJoinRequestWithContext is the context-aware variant of DeclineChatJoinRequest. @@ -596,7 +598,7 @@ func (api *API) DeclineChatJoinRequest(params DeclineChatJoinRequest) (bool, err // See https://core.telegram.org/bots/api#declinechatjoinrequest func (api *API) DeclineChatJoinRequestWithContext(ctx context.Context, params DeclineChatJoinRequest) (bool, error) { req := NewRequestWithChatID[bool]("declineChatJoinRequest", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // ChatJoinRequestQueryResult is the verdict passed to answerChatJoinRequestQuery. @@ -628,7 +630,7 @@ type AnswerChatJoinRequestQuery struct { // See https://core.telegram.org/bots/api#answerchatjoinrequestquery func (api *API) AnswerChatJoinRequestQuery(params AnswerChatJoinRequestQuery) (bool, error) { req := NewRequest[bool]("answerChatJoinRequestQuery", params) - return req.Do(api) + return api.Do(req) } // AnswerChatJoinRequestQueryWithContext is the context-aware variant of AnswerChatJoinRequestQuery. @@ -637,7 +639,7 @@ func (api *API) AnswerChatJoinRequestQuery(params AnswerChatJoinRequestQuery) (b // See https://core.telegram.org/bots/api#answerchatjoinrequestquery func (api *API) AnswerChatJoinRequestQueryWithContext(ctx context.Context, params AnswerChatJoinRequestQuery) (bool, error) { req := NewRequest[bool]("answerChatJoinRequestQuery", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // SendChatJoinRequestWebApp holds parameters for the sendChatJoinRequestWebApp method. @@ -658,7 +660,7 @@ type SendChatJoinRequestWebApp struct { // See https://core.telegram.org/bots/api#sendchatjoinrequestwebapp func (api *API) SendChatJoinRequestWebApp(params SendChatJoinRequestWebApp) (bool, error) { req := NewRequest[bool]("sendChatJoinRequestWebApp", params) - return req.Do(api) + return api.Do(req) } // SendChatJoinRequestWebAppWithContext is the context-aware variant of SendChatJoinRequestWebApp. @@ -667,7 +669,7 @@ func (api *API) SendChatJoinRequestWebApp(params SendChatJoinRequestWebApp) (boo // See https://core.telegram.org/bots/api#sendchatjoinrequestwebapp func (api *API) SendChatJoinRequestWebAppWithContext(ctx context.Context, params SendChatJoinRequestWebApp) (bool, error) { req := NewRequest[bool]("sendChatJoinRequestWebApp", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // SetChatPhoto holds parameters for the setChatPhoto method. @@ -699,7 +701,7 @@ func (api *API) SetChatPhotoWithContext(ctx context.Context, params SetChatPhoto _ = uploader.Close() }() req := NewUploaderRequestWithChatID[bool]("setChatPhoto", params, params.ChatID, photo.SetType(UploaderPhotoType)) - return req.DoWithContext(ctx, uploader) + return uploader.DoWithContext(ctx, req) } // DeleteChatPhoto holds parameters for the deleteChatPhoto method. @@ -717,7 +719,7 @@ type DeleteChatPhoto struct { // See https://core.telegram.org/bots/api#deletechatphoto func (api *API) DeleteChatPhoto(params DeleteChatPhoto) (bool, error) { req := NewRequestWithChatID[bool]("deleteChatPhoto", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // DeleteChatPhotoWithContext is the context-aware variant of DeleteChatPhoto. @@ -726,7 +728,7 @@ func (api *API) DeleteChatPhoto(params DeleteChatPhoto) (bool, error) { // See https://core.telegram.org/bots/api#deletechatphoto func (api *API) DeleteChatPhotoWithContext(ctx context.Context, params DeleteChatPhoto) (bool, error) { req := NewRequestWithChatID[bool]("deleteChatPhoto", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // SetChatTitle holds parameters for the setChatTitle method. @@ -746,7 +748,7 @@ type SetChatTitle struct { // See https://core.telegram.org/bots/api#setchattitle func (api *API) SetChatTitle(params SetChatTitle) (bool, error) { req := NewRequestWithChatID[bool]("setChatTitle", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // SetChatTitleWithContext is the context-aware variant of SetChatTitle. @@ -755,7 +757,7 @@ func (api *API) SetChatTitle(params SetChatTitle) (bool, error) { // See https://core.telegram.org/bots/api#setchattitle func (api *API) SetChatTitleWithContext(ctx context.Context, params SetChatTitle) (bool, error) { req := NewRequestWithChatID[bool]("setChatTitle", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // SetChatDescription holds parameters for the setChatDescription method. @@ -775,7 +777,7 @@ type SetChatDescription struct { // See https://core.telegram.org/bots/api#setchatdescription func (api *API) SetChatDescription(params SetChatDescription) (bool, error) { req := NewRequestWithChatID[bool]("setChatDescription", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // SetChatDescriptionWithContext is the context-aware variant of SetChatDescription. @@ -784,7 +786,7 @@ func (api *API) SetChatDescription(params SetChatDescription) (bool, error) { // See https://core.telegram.org/bots/api#setchatdescription func (api *API) SetChatDescriptionWithContext(ctx context.Context, params SetChatDescription) (bool, error) { req := NewRequestWithChatID[bool]("setChatDescription", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // PinChatMessage holds parameters for the pinChatMessage method. @@ -810,7 +812,7 @@ type PinChatMessage struct { // See https://core.telegram.org/bots/api#pinchatmessage func (api *API) PinChatMessage(params PinChatMessage) (bool, error) { req := NewRequestWithChatID[bool]("pinChatMessage", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // PinChatMessageWithContext is the context-aware variant of PinChatMessage. @@ -819,7 +821,7 @@ func (api *API) PinChatMessage(params PinChatMessage) (bool, error) { // See https://core.telegram.org/bots/api#pinchatmessage func (api *API) PinChatMessageWithContext(ctx context.Context, params PinChatMessage) (bool, error) { req := NewRequestWithChatID[bool]("pinChatMessage", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // UnpinChatMessage holds parameters for the unpinChatMessage method. @@ -843,7 +845,7 @@ type UnpinChatMessage struct { // See https://core.telegram.org/bots/api#unpinchatmessage func (api *API) UnpinChatMessage(params UnpinChatMessage) (bool, error) { req := NewRequestWithChatID[bool]("unpinChatMessage", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // UnpinChatMessageWithContext is the context-aware variant of UnpinChatMessage. @@ -852,7 +854,7 @@ func (api *API) UnpinChatMessage(params UnpinChatMessage) (bool, error) { // See https://core.telegram.org/bots/api#unpinchatmessage func (api *API) UnpinChatMessageWithContext(ctx context.Context, params UnpinChatMessage) (bool, error) { req := NewRequestWithChatID[bool]("unpinChatMessage", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // UnpinAllChatMessages holds parameters for the unpinAllChatMessages method. @@ -870,7 +872,7 @@ type UnpinAllChatMessages struct { // See https://core.telegram.org/bots/api#unpinallchatmessages func (api *API) UnpinAllChatMessages(params UnpinAllChatMessages) (bool, error) { req := NewRequestWithChatID[bool]("unpinAllChatMessages", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // UnpinAllChatMessagesWithContext is the context-aware variant of UnpinAllChatMessages. @@ -879,7 +881,7 @@ func (api *API) UnpinAllChatMessages(params UnpinAllChatMessages) (bool, error) // See https://core.telegram.org/bots/api#unpinallchatmessages func (api *API) UnpinAllChatMessagesWithContext(ctx context.Context, params UnpinAllChatMessages) (bool, error) { req := NewRequestWithChatID[bool]("unpinAllChatMessages", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // LeaveChat holds parameters for the leaveChat method. @@ -898,7 +900,7 @@ type LeaveChat struct { // See https://core.telegram.org/bots/api#leavechat func (api *API) LeaveChat(params LeaveChat) (bool, error) { req := NewRequestWithChatID[bool]("leaveChat", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // LeaveChatWithContext is the context-aware variant of LeaveChat. @@ -907,7 +909,7 @@ func (api *API) LeaveChat(params LeaveChat) (bool, error) { // See https://core.telegram.org/bots/api#leavechat func (api *API) LeaveChatWithContext(ctx context.Context, params LeaveChat) (bool, error) { req := NewRequestWithChatID[bool]("leaveChat", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // GetChat holds parameters for the getChat method. @@ -924,7 +926,7 @@ type GetChat struct { // See https://core.telegram.org/bots/api#getchat func (api *API) GetChat(params GetChat) (ChatFullInfo, error) { req := NewRequestWithChatID[ChatFullInfo]("getChat", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // GetChatWithContext is the context-aware variant of GetChat. @@ -933,7 +935,7 @@ func (api *API) GetChat(params GetChat) (ChatFullInfo, error) { // See https://core.telegram.org/bots/api#getchat func (api *API) GetChatWithContext(ctx context.Context, params GetChat) (ChatFullInfo, error) { req := NewRequestWithChatID[ChatFullInfo]("getChat", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // GetChatAdministrators holds parameters for the getChatAdministrators method. @@ -953,7 +955,7 @@ type GetChatAdministrators struct { // See https://core.telegram.org/bots/api#getchatadministrators func (api *API) GetChatAdministrators(params GetChatAdministrators) ([]ChatMember, error) { req := NewRequestWithChatID[[]ChatMember]("getChatAdministrators", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // GetChatAdministratorsWithContext is the context-aware variant of GetChatAdministrators. @@ -962,7 +964,7 @@ func (api *API) GetChatAdministrators(params GetChatAdministrators) ([]ChatMembe // See https://core.telegram.org/bots/api#getchatadministrators func (api *API) GetChatAdministratorsWithContext(ctx context.Context, params GetChatAdministrators) ([]ChatMember, error) { req := NewRequestWithChatID[[]ChatMember]("getChatAdministrators", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // GetChatMemberCount holds parameters for the getChatMemberCount method. @@ -979,7 +981,7 @@ type GetChatMemberCount struct { // See https://core.telegram.org/bots/api#getchatmembercount func (api *API) GetChatMemberCount(params GetChatMemberCount) (int, error) { req := NewRequestWithChatID[int]("getChatMemberCount", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // GetChatMemberCountWithContext is the context-aware variant of GetChatMemberCount. @@ -988,7 +990,7 @@ func (api *API) GetChatMemberCount(params GetChatMemberCount) (int, error) { // See https://core.telegram.org/bots/api#getchatmembercount func (api *API) GetChatMemberCountWithContext(ctx context.Context, params GetChatMemberCount) (int, error) { req := NewRequestWithChatID[int]("getChatMemberCount", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // GetChatMember holds parameters for the getChatMember method. @@ -1007,7 +1009,7 @@ type GetChatMember struct { // See https://core.telegram.org/bots/api#getchatmember func (api *API) GetChatMember(params GetChatMember) (ChatMember, error) { req := NewRequestWithChatID[ChatMember]("getChatMember", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // GetChatMemberWithContext is the context-aware variant of GetChatMember. @@ -1016,7 +1018,7 @@ func (api *API) GetChatMember(params GetChatMember) (ChatMember, error) { // See https://core.telegram.org/bots/api#getchatmember func (api *API) GetChatMemberWithContext(ctx context.Context, params GetChatMember) (ChatMember, error) { req := NewRequestWithChatID[ChatMember]("getChatMember", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // SetChatStickerSet holds parameters for the setChatStickerSet method. @@ -1036,7 +1038,7 @@ type SetChatStickerSet struct { // See https://core.telegram.org/bots/api#setchatstickerset func (api *API) SetChatStickerSet(params SetChatStickerSet) (bool, error) { req := NewRequestWithChatID[bool]("setChatStickerSet", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // SetChatStickerSetWithContext is the context-aware variant of SetChatStickerSet. @@ -1045,7 +1047,7 @@ func (api *API) SetChatStickerSet(params SetChatStickerSet) (bool, error) { // See https://core.telegram.org/bots/api#setchatstickerset func (api *API) SetChatStickerSetWithContext(ctx context.Context, params SetChatStickerSet) (bool, error) { req := NewRequestWithChatID[bool]("setChatStickerSet", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // DeleteChatStickerSet holds parameters for the deleteChatStickerSet method. @@ -1063,7 +1065,7 @@ type DeleteChatStickerSet struct { // See https://core.telegram.org/bots/api#deletechatstickerset func (api *API) DeleteChatStickerSet(params DeleteChatStickerSet) (bool, error) { req := NewRequestWithChatID[bool]("deleteChatStickerSet", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // DeleteChatStickerSetWithContext is the context-aware variant of DeleteChatStickerSet. @@ -1072,7 +1074,7 @@ func (api *API) DeleteChatStickerSet(params DeleteChatStickerSet) (bool, error) // See https://core.telegram.org/bots/api#deletechatstickerset func (api *API) DeleteChatStickerSetWithContext(ctx context.Context, params DeleteChatStickerSet) (bool, error) { req := NewRequestWithChatID[bool]("deleteChatStickerSet", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // GetUserChatBoosts holds parameters for the getUserChatBoosts method. @@ -1090,7 +1092,7 @@ type GetUserChatBoosts struct { // See https://core.telegram.org/bots/api#getuserchatboosts func (api *API) GetUserChatBoosts(params GetUserChatBoosts) (UserChatBoosts, error) { req := NewRequestWithChatID[UserChatBoosts]("getUserChatBoosts", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // GetUserChatBoostsWithContext is the context-aware variant of GetUserChatBoosts. @@ -1099,7 +1101,7 @@ func (api *API) GetUserChatBoosts(params GetUserChatBoosts) (UserChatBoosts, err // See https://core.telegram.org/bots/api#getuserchatboosts func (api *API) GetUserChatBoostsWithContext(ctx context.Context, params GetUserChatBoosts) (UserChatBoosts, error) { req := NewRequestWithChatID[UserChatBoosts]("getUserChatBoosts", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // GetChatGifts holds parameters for the getChatGifts method. @@ -1143,7 +1145,7 @@ type GetChatGifts struct { // See https://core.telegram.org/bots/api#getchatgifts func (api *API) GetChatGifts(params GetChatGifts) (OwnedGifts, error) { req := NewRequestWithChatID[OwnedGifts]("getChatGifts", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // GetChatGiftsWithContext is the context-aware variant of GetChatGifts. @@ -1152,5 +1154,5 @@ func (api *API) GetChatGifts(params GetChatGifts) (OwnedGifts, error) { // See https://core.telegram.org/bots/api#getchatgifts func (api *API) GetChatGiftsWithContext(ctx context.Context, params GetChatGifts) (OwnedGifts, error) { req := NewRequestWithChatID[OwnedGifts]("getChatGifts", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } diff --git a/tgapi/chat_types.go b/tgapi/chat_types.go index d32b1bc..35f41e8 100644 --- a/tgapi/chat_types.go +++ b/tgapi/chat_types.go @@ -90,8 +90,7 @@ type ChatFullInfo struct { // AvailableReaction Optional. List of available reactions allowed in the chat. If omitted, then all emoji // reactions are allowed. - // Subject to change in v2: the Go field name may be pluralized to AvailableReactions. - AvailableReaction []ReactionType `json:"available_reactions,omitempty"` + AvailableReactions []ReactionType `json:"available_reactions,omitempty"` // BackgroundCustomEmojiID Optional. Custom emoji identifier of the emoji chosen by the chat for the reply // header and link preview background @@ -358,6 +357,8 @@ type ChatMember struct { CanEditStories *bool `json:"can_edit_stories,omitempty"` // Since: Bot API 6.9 // CanDeleteStories True, if the administrator can delete stories posted by other users CanDeleteStories *bool `json:"can_delete_stories,omitempty"` // Since: Bot API 6.9 + // CanSendWelcomeMessages reports whether the administrator can manage chat welcome messages or send them as a bot. + CanSendWelcomeMessages *bool `json:"can_send_welcome_messages,omitempty"` // Since: Bot API 10.3 // CanPostMessages Optional. True, if the administrator can post messages in the channel, approve suggested // posts, or access channel statistics; for channels only @@ -534,6 +535,8 @@ type ChatAdministratorRights struct { // CanManageTags Optional. True, if the administrator can edit the tags of regular members; for groups and // supergroups only. If omitted, defaults to the value of can_pin_messages. CanManageTags *bool `json:"can_manage_tags,omitempty"` + // CanSendWelcomeMessages reports whether the administrator can manage chat welcome messages or send them as a bot. + CanSendWelcomeMessages *bool `json:"can_send_welcome_messages,omitempty"` // Since: Bot API 10.3 } // ChatBoostUpdated represents a boost added to a chat or changed. @@ -578,6 +581,14 @@ type CommunityChatAdded struct { Community Community `json:"community"` } +// CommunityChatJoined describes a service message about a chat being joined by a user from a community. +// +// Since: Bot API 10.3 +type CommunityChatJoined struct { + // Community contains information about the community from which the user joined the chat. + Community Community `json:"community"` +} + // CommunityChatRemoved describes a service message about a chat leaving a community. // // Since: Bot API 10.2 diff --git a/tgapi/forum_methods.go b/tgapi/forum_methods.go index 5348ad3..d9a6958 100644 --- a/tgapi/forum_methods.go +++ b/tgapi/forum_methods.go @@ -16,7 +16,7 @@ type BaseForumTopic struct { // See https://core.telegram.org/bots/api#getforumtopiciconstickers func (api *API) GetForumTopicIconStickers() ([]Sticker, error) { req := NewRequest[[]Sticker]("getForumTopicIconStickers", NoParams) - return req.Do(api) + return api.Do(req) } // GetForumTopicIconStickersWithContext is the context-aware variant of GetForumTopicIconStickers. @@ -25,7 +25,7 @@ func (api *API) GetForumTopicIconStickers() ([]Sticker, error) { // See https://core.telegram.org/bots/api#getforumtopiciconstickers func (api *API) GetForumTopicIconStickersWithContext(ctx context.Context) ([]Sticker, error) { req := NewRequest[[]Sticker]("getForumTopicIconStickers", NoParams) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // CreateForumTopic holds parameters for the createForumTopic method. @@ -52,7 +52,7 @@ type CreateForumTopic struct { // See https://core.telegram.org/bots/api#createforumtopic func (api *API) CreateForumTopic(params CreateForumTopic) (ForumTopic, error) { req := NewRequestWithChatID[ForumTopic]("createForumTopic", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // CreateForumTopicWithContext is the context-aware variant of CreateForumTopic. @@ -61,7 +61,7 @@ func (api *API) CreateForumTopic(params CreateForumTopic) (ForumTopic, error) { // See https://core.telegram.org/bots/api#createforumtopic func (api *API) CreateForumTopicWithContext(ctx context.Context, params CreateForumTopic) (ForumTopic, error) { req := NewRequestWithChatID[ForumTopic]("createForumTopic", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // EditForumTopic holds parameters for the editForumTopic method. @@ -84,7 +84,7 @@ type EditForumTopic struct { // See https://core.telegram.org/bots/api#editforumtopic func (api *API) EditForumTopic(params EditForumTopic) (bool, error) { req := NewRequestWithChatID[bool]("editForumTopic", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // EditForumTopicWithContext is the context-aware variant of EditForumTopic. @@ -93,7 +93,7 @@ func (api *API) EditForumTopic(params EditForumTopic) (bool, error) { // See https://core.telegram.org/bots/api#editforumtopic func (api *API) EditForumTopicWithContext(ctx context.Context, params EditForumTopic) (bool, error) { req := NewRequestWithChatID[bool]("editForumTopic", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // CloseForumTopic closes an open forum topic. @@ -102,7 +102,7 @@ func (api *API) EditForumTopicWithContext(ctx context.Context, params EditForumT // See https://core.telegram.org/bots/api#closeforumtopic func (api *API) CloseForumTopic(params BaseForumTopic) (bool, error) { req := NewRequestWithChatID[bool]("closeForumTopic", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // CloseForumTopicWithContext is the context-aware variant of CloseForumTopic. @@ -111,7 +111,7 @@ func (api *API) CloseForumTopic(params BaseForumTopic) (bool, error) { // See https://core.telegram.org/bots/api#closeforumtopic func (api *API) CloseForumTopicWithContext(ctx context.Context, params BaseForumTopic) (bool, error) { req := NewRequestWithChatID[bool]("closeForumTopic", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // ReopenForumTopic reopens a closed forum topic. @@ -120,7 +120,7 @@ func (api *API) CloseForumTopicWithContext(ctx context.Context, params BaseForum // See https://core.telegram.org/bots/api#reopenforumtopic func (api *API) ReopenForumTopic(params BaseForumTopic) (bool, error) { req := NewRequestWithChatID[bool]("reopenForumTopic", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // ReopenForumTopicWithContext is the context-aware variant of ReopenForumTopic. @@ -129,7 +129,7 @@ func (api *API) ReopenForumTopic(params BaseForumTopic) (bool, error) { // See https://core.telegram.org/bots/api#reopenforumtopic func (api *API) ReopenForumTopicWithContext(ctx context.Context, params BaseForumTopic) (bool, error) { req := NewRequestWithChatID[bool]("reopenForumTopic", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // DeleteForumTopic deletes a forum topic. @@ -138,7 +138,7 @@ func (api *API) ReopenForumTopicWithContext(ctx context.Context, params BaseForu // See https://core.telegram.org/bots/api#deleteforumtopic func (api *API) DeleteForumTopic(params BaseForumTopic) (bool, error) { req := NewRequestWithChatID[bool]("deleteForumTopic", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // DeleteForumTopicWithContext is the context-aware variant of DeleteForumTopic. @@ -147,7 +147,7 @@ func (api *API) DeleteForumTopic(params BaseForumTopic) (bool, error) { // See https://core.telegram.org/bots/api#deleteforumtopic func (api *API) DeleteForumTopicWithContext(ctx context.Context, params BaseForumTopic) (bool, error) { req := NewRequestWithChatID[bool]("deleteForumTopic", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // UnpinAllForumTopicMessages clears the list of pinned messages in a forum topic. @@ -156,7 +156,7 @@ func (api *API) DeleteForumTopicWithContext(ctx context.Context, params BaseForu // See https://core.telegram.org/bots/api#unpinallforumtopicmessages func (api *API) UnpinAllForumTopicMessages(params BaseForumTopic) (bool, error) { req := NewRequestWithChatID[bool]("unpinAllForumTopicMessages", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // UnpinAllForumTopicMessagesWithContext is the context-aware variant of UnpinAllForumTopicMessages. @@ -165,7 +165,7 @@ func (api *API) UnpinAllForumTopicMessages(params BaseForumTopic) (bool, error) // See https://core.telegram.org/bots/api#unpinallforumtopicmessages func (api *API) UnpinAllForumTopicMessagesWithContext(ctx context.Context, params BaseForumTopic) (bool, error) { req := NewRequestWithChatID[bool]("unpinAllForumTopicMessages", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // BaseGeneralForumTopic contains common fields for general forum topic operations that require a chat ID. @@ -192,7 +192,7 @@ type EditGeneralForumTopic struct { // See https://core.telegram.org/bots/api#editgeneralforumtopic func (api *API) EditGeneralForumTopic(params EditGeneralForumTopic) (bool, error) { req := NewRequestWithChatID[bool]("editGeneralForumTopic", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // EditGeneralForumTopicWithContext is the context-aware variant of EditGeneralForumTopic. @@ -201,7 +201,7 @@ func (api *API) EditGeneralForumTopic(params EditGeneralForumTopic) (bool, error // See https://core.telegram.org/bots/api#editgeneralforumtopic func (api *API) EditGeneralForumTopicWithContext(ctx context.Context, params EditGeneralForumTopic) (bool, error) { req := NewRequestWithChatID[bool]("editGeneralForumTopic", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // CloseGeneralForumTopic closes the 'General' topic in a forum supergroup. @@ -210,7 +210,7 @@ func (api *API) EditGeneralForumTopicWithContext(ctx context.Context, params Edi // See https://core.telegram.org/bots/api#closegeneralforumtopic func (api *API) CloseGeneralForumTopic(params BaseGeneralForumTopic) (bool, error) { req := NewRequestWithChatID[bool]("closeGeneralForumTopic", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // CloseGeneralForumTopicWithContext is the context-aware variant of CloseGeneralForumTopic. @@ -219,7 +219,7 @@ func (api *API) CloseGeneralForumTopic(params BaseGeneralForumTopic) (bool, erro // See https://core.telegram.org/bots/api#closegeneralforumtopic func (api *API) CloseGeneralForumTopicWithContext(ctx context.Context, params BaseGeneralForumTopic) (bool, error) { req := NewRequestWithChatID[bool]("closeGeneralForumTopic", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // ReopenGeneralForumTopic reopens the 'General' topic in a forum supergroup. @@ -228,7 +228,7 @@ func (api *API) CloseGeneralForumTopicWithContext(ctx context.Context, params Ba // See https://core.telegram.org/bots/api#reopengeneralforumtopic func (api *API) ReopenGeneralForumTopic(params BaseGeneralForumTopic) (bool, error) { req := NewRequestWithChatID[bool]("reopenGeneralForumTopic", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // ReopenGeneralForumTopicWithContext is the context-aware variant of ReopenGeneralForumTopic. @@ -237,7 +237,7 @@ func (api *API) ReopenGeneralForumTopic(params BaseGeneralForumTopic) (bool, err // See https://core.telegram.org/bots/api#reopengeneralforumtopic func (api *API) ReopenGeneralForumTopicWithContext(ctx context.Context, params BaseGeneralForumTopic) (bool, error) { req := NewRequestWithChatID[bool]("reopenGeneralForumTopic", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // HideGeneralForumTopic hides the 'General' topic in a forum supergroup. @@ -246,7 +246,7 @@ func (api *API) ReopenGeneralForumTopicWithContext(ctx context.Context, params B // See https://core.telegram.org/bots/api#hidegeneralforumtopic func (api *API) HideGeneralForumTopic(params BaseGeneralForumTopic) (bool, error) { req := NewRequestWithChatID[bool]("hideGeneralForumTopic", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // HideGeneralForumTopicWithContext is the context-aware variant of HideGeneralForumTopic. @@ -255,7 +255,7 @@ func (api *API) HideGeneralForumTopic(params BaseGeneralForumTopic) (bool, error // See https://core.telegram.org/bots/api#hidegeneralforumtopic func (api *API) HideGeneralForumTopicWithContext(ctx context.Context, params BaseGeneralForumTopic) (bool, error) { req := NewRequestWithChatID[bool]("hideGeneralForumTopic", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // UnhideGeneralForumTopic unhides the 'General' topic in a forum supergroup. @@ -264,7 +264,7 @@ func (api *API) HideGeneralForumTopicWithContext(ctx context.Context, params Bas // See https://core.telegram.org/bots/api#unhidegeneralforumtopic func (api *API) UnhideGeneralForumTopic(params BaseGeneralForumTopic) (bool, error) { req := NewRequestWithChatID[bool]("unhideGeneralForumTopic", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // UnhideGeneralForumTopicWithContext is the context-aware variant of UnhideGeneralForumTopic. @@ -273,7 +273,7 @@ func (api *API) UnhideGeneralForumTopic(params BaseGeneralForumTopic) (bool, err // See https://core.telegram.org/bots/api#unhidegeneralforumtopic func (api *API) UnhideGeneralForumTopicWithContext(ctx context.Context, params BaseGeneralForumTopic) (bool, error) { req := NewRequestWithChatID[bool]("unhideGeneralForumTopic", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // UnpinAllGeneralForumTopicMessages clears the list of pinned messages in the 'General' topic. @@ -282,7 +282,7 @@ func (api *API) UnhideGeneralForumTopicWithContext(ctx context.Context, params B // See https://core.telegram.org/bots/api#unpinallgeneralforumtopicmessages func (api *API) UnpinAllGeneralForumTopicMessages(params BaseGeneralForumTopic) (bool, error) { req := NewRequestWithChatID[bool]("unpinAllGeneralForumTopicMessages", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // UnpinAllGeneralForumTopicMessagesWithContext is the context-aware variant of UnpinAllGeneralForumTopicMessages. @@ -291,5 +291,5 @@ func (api *API) UnpinAllGeneralForumTopicMessages(params BaseGeneralForumTopic) // See https://core.telegram.org/bots/api#unpinallgeneralforumtopicmessages func (api *API) UnpinAllGeneralForumTopicMessagesWithContext(ctx context.Context, params BaseGeneralForumTopic) (bool, error) { req := NewRequestWithChatID[bool]("unpinAllGeneralForumTopicMessages", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } diff --git a/tgapi/games_methods.go b/tgapi/games_methods.go index 8dbbcda..8964ab2 100644 --- a/tgapi/games_methods.go +++ b/tgapi/games_methods.go @@ -44,7 +44,7 @@ type SendGame struct { // See https://core.telegram.org/bots/api#sendgame func (api *API) SendGame(params SendGame) (Message, error) { req := NewRequestWithChatID[Message]("sendGame", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // SendGameWithContext is the context-aware variant of SendGame. @@ -53,7 +53,7 @@ func (api *API) SendGame(params SendGame) (Message, error) { // See https://core.telegram.org/bots/api#sendgame func (api *API) SendGameWithContext(ctx context.Context, params SendGame) (Message, error) { req := NewRequestWithChatID[Message]("sendGame", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // SetGameScore holds parameters for the setGameScore method. @@ -88,11 +88,11 @@ func (api *API) SetGameScore(params SetGameScore) (Message, bool, error) { var zero Message if params.InlineMessageID != "" { req := NewRequestWithChatID[bool]("setGameScore", params, params.ChatID) - res, err := req.Do(api) + res, err := api.Do(req) return zero, res, err } req := NewRequestWithChatID[Message]("setGameScore", params, params.ChatID) - res, err := req.Do(api) + res, err := api.Do(req) return res, false, err } @@ -104,11 +104,11 @@ func (api *API) SetGameScoreWithContext(ctx context.Context, params SetGameScore var zero Message if params.InlineMessageID != "" { req := NewRequestWithChatID[bool]("setGameScore", params, params.ChatID) - res, err := req.DoWithContext(ctx, api) + res, err := api.DoWithContext(ctx, req) return zero, res, err } req := NewRequestWithChatID[Message]("setGameScore", params, params.ChatID) - res, err := req.DoWithContext(ctx, api) + res, err := api.DoWithContext(ctx, req) return res, false, err } @@ -132,7 +132,7 @@ type GetGameHighScores struct { // See https://core.telegram.org/bots/api#getgamehighscores func (api *API) GetGameHighScores(params GetGameHighScores) ([]GameHighScore, error) { req := NewRequestWithChatID[[]GameHighScore]("getGameHighScores", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // GetGameHighScoresWithContext is the context-aware variant of GetGameHighScores. @@ -141,5 +141,5 @@ func (api *API) GetGameHighScores(params GetGameHighScores) ([]GameHighScore, er // See https://core.telegram.org/bots/api#getgamehighscores func (api *API) GetGameHighScoresWithContext(ctx context.Context, params GetGameHighScores) ([]GameHighScore, error) { req := NewRequestWithChatID[[]GameHighScore]("getGameHighScores", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } diff --git a/tgapi/inline_methods.go b/tgapi/inline_methods.go index fa44c36..a6c8177 100644 --- a/tgapi/inline_methods.go +++ b/tgapi/inline_methods.go @@ -30,7 +30,7 @@ type AnswerInlineQuery struct { // See https://core.telegram.org/bots/api#answerinlinequery func (api *API) AnswerInlineQuery(params AnswerInlineQuery) (bool, error) { req := NewRequest[bool]("answerInlineQuery", params) - return req.Do(api) + return api.Do(req) } // AnswerInlineQueryWithContext is the context-aware variant of AnswerInlineQuery. @@ -39,7 +39,7 @@ func (api *API) AnswerInlineQuery(params AnswerInlineQuery) (bool, error) { // See https://core.telegram.org/bots/api#answerinlinequery func (api *API) AnswerInlineQueryWithContext(ctx context.Context, params AnswerInlineQuery) (bool, error) { req := NewRequest[bool]("answerInlineQuery", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // AnswerWebAppQuery holds parameters for the answerWebAppQuery method. @@ -57,7 +57,7 @@ type AnswerWebAppQuery struct { // See https://core.telegram.org/bots/api#answerwebappquery func (api *API) AnswerWebAppQuery(params AnswerWebAppQuery) (SentWebAppMessage, error) { req := NewRequest[SentWebAppMessage]("answerWebAppQuery", params) - return req.Do(api) + return api.Do(req) } // AnswerWebAppQueryWithContext is the context-aware variant of AnswerWebAppQuery. @@ -66,7 +66,7 @@ func (api *API) AnswerWebAppQuery(params AnswerWebAppQuery) (SentWebAppMessage, // See https://core.telegram.org/bots/api#answerwebappquery func (api *API) AnswerWebAppQueryWithContext(ctx context.Context, params AnswerWebAppQuery) (SentWebAppMessage, error) { req := NewRequest[SentWebAppMessage]("answerWebAppQuery", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // SavePreparedInlineMessage holds parameters for the savePreparedInlineMessage method. @@ -92,7 +92,7 @@ type SavePreparedInlineMessage struct { // See https://core.telegram.org/bots/api#savepreparedinlinemessage func (api *API) SavePreparedInlineMessage(params SavePreparedInlineMessage) (PreparedInlineMessage, error) { req := NewRequest[PreparedInlineMessage]("savePreparedInlineMessage", params) - return req.Do(api) + return api.Do(req) } // SavePreparedInlineMessageWithContext is the context-aware variant of SavePreparedInlineMessage. @@ -101,7 +101,7 @@ func (api *API) SavePreparedInlineMessage(params SavePreparedInlineMessage) (Pre // See https://core.telegram.org/bots/api#savepreparedinlinemessage func (api *API) SavePreparedInlineMessageWithContext(ctx context.Context, params SavePreparedInlineMessage) (PreparedInlineMessage, error) { req := NewRequest[PreparedInlineMessage]("savePreparedInlineMessage", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // SavePreparedKeyboardButton holds parameters for the savePreparedKeyboardButton method. @@ -120,7 +120,7 @@ type SavePreparedKeyboardButton struct { // See https://core.telegram.org/bots/api#savepreparedkeyboardbutton func (api *API) SavePreparedKeyboardButton(params SavePreparedKeyboardButton) (PreparedKeyboardButton, error) { req := NewRequest[PreparedKeyboardButton]("savePreparedKeyboardButton", params) - return req.Do(api) + return api.Do(req) } // SavePreparedKeyboardButtonWithContext is the context-aware variant of SavePreparedKeyboardButton. @@ -129,5 +129,5 @@ func (api *API) SavePreparedKeyboardButton(params SavePreparedKeyboardButton) (P // See https://core.telegram.org/bots/api#savepreparedkeyboardbutton func (api *API) SavePreparedKeyboardButtonWithContext(ctx context.Context, params SavePreparedKeyboardButton) (PreparedKeyboardButton, error) { req := NewRequest[PreparedKeyboardButton]("savePreparedKeyboardButton", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } diff --git a/tgapi/messages_methods.go b/tgapi/messages_methods.go index cae5996..6140e8e 100644 --- a/tgapi/messages_methods.go +++ b/tgapi/messages_methods.go @@ -18,10 +18,8 @@ type SendMessage struct { // DirectMessagesTopicID Optional. Identifier of the direct messages topic to which the message will be // sent; required if the message is sent to a direct messages chat DirectMessagesTopicID int64 `json:"direct_messages_topic_id,omitempty"` - // ReceiverUserID identifies the user who can see the ephemeral message. - ReceiverUserID int64 `json:"receiver_user_id,omitempty"` // Since: Bot API 10.2 - // CallbackQueryID identifies the callback query that triggered an ephemeral response. - CallbackQueryID string `json:"callback_query_id,omitempty"` // Since: Bot API 10.2 + + EphemeralMessageParameters *EphemeralMessageParameters `json:"ephemeral_message_parameters,omitempty"` // Since: Bot API 10.3 // Text Required. Text of the message to be sent, 1-4096 characters after entities parsing Text string `json:"text"` @@ -62,7 +60,7 @@ type SendMessage struct { // See https://core.telegram.org/bots/api#sendmessage func (api *API) SendMessage(params SendMessage) (Message, error) { req := NewRequestWithChatID[Message]("sendMessage", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // SendMessageWithContext is the context-aware variant of SendMessage. @@ -71,7 +69,7 @@ func (api *API) SendMessage(params SendMessage) (Message, error) { // See https://core.telegram.org/bots/api#sendmessage func (api *API) SendMessageWithContext(ctx context.Context, params SendMessage) (Message, error) { req := NewRequestWithChatID[Message]("sendMessage", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // ForwardMessage holds parameters for the forwardMessage method. @@ -114,7 +112,7 @@ type ForwardMessage struct { // See https://core.telegram.org/bots/api#forwardmessage func (api *API) ForwardMessage(params ForwardMessage) (Message, error) { req := NewRequestWithChatID[Message]("forwardMessage", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // ForwardMessageWithContext is the context-aware variant of ForwardMessage. @@ -123,7 +121,7 @@ func (api *API) ForwardMessage(params ForwardMessage) (Message, error) { // See https://core.telegram.org/bots/api#forwardmessage func (api *API) ForwardMessageWithContext(ctx context.Context, params ForwardMessage) (Message, error) { req := NewRequestWithChatID[Message]("forwardMessage", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // ForwardMessages holds parameters for the forwardMessages method. @@ -159,7 +157,7 @@ type ForwardMessages struct { // See https://core.telegram.org/bots/api#forwardmessages func (api *API) ForwardMessages(params ForwardMessages) ([]MessageID, error) { req := NewRequestWithChatID[[]MessageID]("forwardMessages", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // ForwardMessagesWithContext is the context-aware variant of ForwardMessages. @@ -168,7 +166,7 @@ func (api *API) ForwardMessages(params ForwardMessages) ([]MessageID, error) { // See https://core.telegram.org/bots/api#forwardmessages func (api *API) ForwardMessagesWithContext(ctx context.Context, params ForwardMessages) ([]MessageID, error) { req := NewRequestWithChatID[[]MessageID]("forwardMessages", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // CopyMessage holds parameters for the copyMessage method. @@ -234,7 +232,7 @@ type CopyMessage struct { // Returns the MessageID of the sent copy. // See https://core.telegram.org/bots/api#copymessage func (api *API) CopyMessage(params CopyMessage) (int, error) { - msgID, err := NewRequestWithChatID[MessageID]("copyMessage", params, params.ChatID).Do(api) + msgID, err := api.Do(NewRequestWithChatID[MessageID]("copyMessage", params, params.ChatID)) if err != nil { return 0, err } @@ -246,7 +244,7 @@ func (api *API) CopyMessage(params CopyMessage) (int, error) { // It executes the same request but uses ctx for cancellation and deadlines. // See https://core.telegram.org/bots/api#copymessage func (api *API) CopyMessageWithContext(ctx context.Context, params CopyMessage) (int, error) { - msgID, err := NewRequestWithChatID[MessageID]("copyMessage", params, params.ChatID).DoWithContext(ctx, api) + msgID, err := api.DoWithContext(ctx, NewRequestWithChatID[MessageID]("copyMessage", params, params.ChatID)) if err != nil { return 0, err } @@ -288,7 +286,7 @@ type CopyMessages struct { // See https://core.telegram.org/bots/api#copymessages func (api *API) CopyMessages(params CopyMessages) ([]MessageID, error) { req := NewRequestWithChatID[[]MessageID]("copyMessages", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // CopyMessagesWithContext is the context-aware variant of CopyMessages. @@ -297,7 +295,7 @@ func (api *API) CopyMessages(params CopyMessages) ([]MessageID, error) { // See https://core.telegram.org/bots/api#copymessages func (api *API) CopyMessagesWithContext(ctx context.Context, params CopyMessages) ([]MessageID, error) { req := NewRequestWithChatID[[]MessageID]("copyMessages", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // SendLocation holds parameters for the sendLocation method. @@ -316,10 +314,8 @@ type SendLocation struct { // DirectMessagesTopicID Optional. Identifier of the direct messages topic to which the message will be // sent; required if the message is sent to a direct messages chat DirectMessagesTopicID int `json:"direct_messages_topic_id,omitempty"` - // ReceiverUserID identifies the user who can see the ephemeral message. - ReceiverUserID int64 `json:"receiver_user_id,omitempty"` // Since: Bot API 10.2 - // CallbackQueryID identifies the callback query that triggered an ephemeral response. - CallbackQueryID string `json:"callback_query_id,omitempty"` // Since: Bot API 10.2 + + EphemeralMessageParameters *EphemeralMessageParameters `json:"ephemeral_message_parameters,omitempty"` // Since: Bot API 10.3 // Latitude Required. Latitude of the location Latitude float64 `json:"latitude"` @@ -367,7 +363,7 @@ type SendLocation struct { // See https://core.telegram.org/bots/api#sendlocation func (api *API) SendLocation(params SendLocation) (Message, error) { req := NewRequestWithChatID[Message]("sendLocation", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // SendLocationWithContext is the context-aware variant of SendLocation. @@ -376,7 +372,7 @@ func (api *API) SendLocation(params SendLocation) (Message, error) { // See https://core.telegram.org/bots/api#sendlocation func (api *API) SendLocationWithContext(ctx context.Context, params SendLocation) (Message, error) { req := NewRequestWithChatID[Message]("sendLocation", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // SendVenue holds parameters for the sendVenue method. @@ -395,10 +391,8 @@ type SendVenue struct { // DirectMessagesTopicID Optional. Identifier of the direct messages topic to which the message will be // sent; required if the message is sent to a direct messages chat DirectMessagesTopicID int `json:"direct_messages_topic_id,omitempty"` - // ReceiverUserID identifies the user who can see the ephemeral message. - ReceiverUserID int64 `json:"receiver_user_id,omitempty"` // Since: Bot API 10.2 - // CallbackQueryID identifies the callback query that triggered an ephemeral response. - CallbackQueryID string `json:"callback_query_id,omitempty"` // Since: Bot API 10.2 + + EphemeralMessageParameters *EphemeralMessageParameters `json:"ephemeral_message_parameters,omitempty"` // Since: Bot API 10.3 // Latitude Required. Latitude of the venue Latitude float64 `json:"latitude"` @@ -447,7 +441,7 @@ type SendVenue struct { // See https://core.telegram.org/bots/api#sendvenue func (api *API) SendVenue(params SendVenue) (Message, error) { req := NewRequestWithChatID[Message]("sendVenue", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // SendVenueWithContext is the context-aware variant of SendVenue. @@ -456,7 +450,7 @@ func (api *API) SendVenue(params SendVenue) (Message, error) { // See https://core.telegram.org/bots/api#sendvenue func (api *API) SendVenueWithContext(ctx context.Context, params SendVenue) (Message, error) { req := NewRequestWithChatID[Message]("sendVenue", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // SendContact holds parameters for the sendContact method. @@ -475,10 +469,8 @@ type SendContact struct { // DirectMessagesTopicID Optional. Identifier of the direct messages topic to which the message will be // sent; required if the message is sent to a direct messages chat DirectMessagesTopicID int `json:"direct_messages_topic_id,omitempty"` - // ReceiverUserID identifies the user who can see the ephemeral message. - ReceiverUserID int64 `json:"receiver_user_id,omitempty"` // Since: Bot API 10.2 - // CallbackQueryID identifies the callback query that triggered an ephemeral response. - CallbackQueryID string `json:"callback_query_id,omitempty"` // Since: Bot API 10.2 + + EphemeralMessageParameters *EphemeralMessageParameters `json:"ephemeral_message_parameters,omitempty"` // Since: Bot API 10.3 // PhoneNumber Required. Contact's phone number PhoneNumber string `json:"phone_number"` @@ -518,7 +510,7 @@ type SendContact struct { // See https://core.telegram.org/bots/api#sendcontact func (api *API) SendContact(params SendContact) (Message, error) { req := NewRequestWithChatID[Message]("sendContact", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // SendContactWithContext is the context-aware variant of SendContact. @@ -527,7 +519,7 @@ func (api *API) SendContact(params SendContact) (Message, error) { // See https://core.telegram.org/bots/api#sendcontact func (api *API) SendContactWithContext(ctx context.Context, params SendContact) (Message, error) { req := NewRequestWithChatID[Message]("sendContact", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // SendPoll holds parameters for the sendPoll method. @@ -638,7 +630,7 @@ type SendPoll struct { // See https://core.telegram.org/bots/api#sendpoll func (api *API) SendPoll(params SendPoll) (Message, error) { req := NewRequestWithChatID[Message]("sendPoll", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // SendPollWithContext is the context-aware variant of SendPoll. @@ -647,7 +639,7 @@ func (api *API) SendPoll(params SendPoll) (Message, error) { // See https://core.telegram.org/bots/api#sendpoll func (api *API) SendPollWithContext(ctx context.Context, params SendPoll) (Message, error) { req := NewRequestWithChatID[Message]("sendPoll", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // SendChecklist holds parameters for the sendChecklist method. @@ -682,7 +674,7 @@ type SendChecklist struct { // See https://core.telegram.org/bots/api#sendchecklist func (api *API) SendChecklist(params SendChecklist) (Message, error) { req := NewRequestWithChatID[Message]("sendChecklist", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // SendChecklistWithContext is the context-aware variant of SendChecklist. @@ -691,7 +683,7 @@ func (api *API) SendChecklist(params SendChecklist) (Message, error) { // See https://core.telegram.org/bots/api#sendchecklist func (api *API) SendChecklistWithContext(ctx context.Context, params SendChecklist) (Message, error) { req := NewRequestWithChatID[Message]("sendChecklist", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // SendDice holds parameters for the sendDice method. @@ -745,7 +737,7 @@ type SendDice struct { // See https://core.telegram.org/bots/api#senddice func (api *API) SendDice(params SendDice) (Message, error) { req := NewRequestWithChatID[Message]("sendDice", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // SendDiceWithContext is the context-aware variant of SendDice. @@ -754,7 +746,7 @@ func (api *API) SendDice(params SendDice) (Message, error) { // See https://core.telegram.org/bots/api#senddice func (api *API) SendDiceWithContext(ctx context.Context, params SendDice) (Message, error) { req := NewRequestWithChatID[Message]("sendDice", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // SendMessageDraft holds parameters for the sendMessageDraft method. @@ -777,6 +769,11 @@ type SendMessageDraft struct { // Entities Optional. 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"` + + // CanStop allows the user to stop message generation while the draft is shown. + CanStop bool `json:"can_stop,omitempty"` // Since: Bot API 10.3 + // KeepOnStop preserves the draft as an ephemeral message when generation is stopped. + KeepOnStop bool `json:"keep_on_stop,omitempty"` // Since: Bot API 10.3 } // SendMessageDraft sends or updates a draft message in the target chat. @@ -785,7 +782,7 @@ type SendMessageDraft struct { // See https://core.telegram.org/bots/api#sendmessagedraft func (api *API) SendMessageDraft(params SendMessageDraft) (bool, error) { req := NewRequestWithChatID[bool]("sendMessageDraft", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // SendMessageDraftWithContext is the context-aware variant of SendMessageDraft. @@ -794,7 +791,7 @@ func (api *API) SendMessageDraft(params SendMessageDraft) (bool, error) { // See https://core.telegram.org/bots/api#sendmessagedraft func (api *API) SendMessageDraftWithContext(ctx context.Context, params SendMessageDraft) (bool, error) { req := NewRequestWithChatID[bool]("sendMessageDraft", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // SendChatAction holds parameters for the sendChatAction method. @@ -823,7 +820,7 @@ type SendChatAction struct { // See https://core.telegram.org/bots/api#sendchataction func (api *API) SendChatAction(params SendChatAction) (bool, error) { req := NewRequestWithChatID[bool]("sendChatAction", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // SendChatActionWithContext is the context-aware variant of SendChatAction. @@ -832,7 +829,7 @@ func (api *API) SendChatAction(params SendChatAction) (bool, error) { // See https://core.telegram.org/bots/api#sendchataction func (api *API) SendChatActionWithContext(ctx context.Context, params SendChatAction) (bool, error) { req := NewRequestWithChatID[bool]("sendChatAction", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // SetMessageReaction holds parameters for the setMessageReaction method. @@ -860,7 +857,7 @@ type SetMessageReaction struct { // See https://core.telegram.org/bots/api#setmessagereaction func (api *API) SetMessageReaction(params SetMessageReaction) (bool, error) { req := NewRequestWithChatID[bool]("setMessageReaction", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // SetMessageReactionWithContext is the context-aware variant of SetMessageReaction. @@ -869,7 +866,7 @@ func (api *API) SetMessageReaction(params SetMessageReaction) (bool, error) { // See https://core.telegram.org/bots/api#setmessagereaction func (api *API) SetMessageReactionWithContext(ctx context.Context, params SetMessageReaction) (bool, error) { req := NewRequestWithChatID[bool]("setMessageReaction", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // EditMessageText holds parameters for the editMessageText method. @@ -913,11 +910,11 @@ func (api *API) EditMessageText(params EditMessageText) (Message, bool, error) { var zero Message if params.InlineMessageID != "" { req := NewRequestWithChatID[bool]("editMessageText", params, params.ChatID) - res, err := req.Do(api) + res, err := api.Do(req) return zero, res, err } req := NewRequestWithChatID[Message]("editMessageText", params, params.ChatID) - res, err := req.Do(api) + res, err := api.Do(req) return res, false, err } @@ -929,11 +926,11 @@ func (api *API) EditMessageTextWithContext(ctx context.Context, params EditMessa var zero Message if params.InlineMessageID != "" { req := NewRequestWithChatID[bool]("editMessageText", params, params.ChatID) - res, err := req.DoWithContext(ctx, api) + res, err := api.DoWithContext(ctx, req) return zero, res, err } req := NewRequestWithChatID[Message]("editMessageText", params, params.ChatID) - res, err := req.DoWithContext(ctx, api) + res, err := api.DoWithContext(ctx, req) return res, false, err } @@ -976,11 +973,11 @@ func (api *API) EditMessageCaption(params EditMessageCaption) (Message, bool, er var zero Message if params.InlineMessageID != "" { req := NewRequestWithChatID[bool]("editMessageCaption", params, params.ChatID) - res, err := req.Do(api) + res, err := api.Do(req) return zero, res, err } req := NewRequestWithChatID[Message]("editMessageCaption", params, params.ChatID) - res, err := req.Do(api) + res, err := api.Do(req) return res, false, err } @@ -992,11 +989,11 @@ func (api *API) EditMessageCaptionWithContext(ctx context.Context, params EditMe var zero Message if params.InlineMessageID != "" { req := NewRequestWithChatID[bool]("editMessageCaption", params, params.ChatID) - res, err := req.DoWithContext(ctx, api) + res, err := api.DoWithContext(ctx, req) return zero, res, err } req := NewRequestWithChatID[Message]("editMessageCaption", params, params.ChatID) - res, err := req.DoWithContext(ctx, api) + res, err := api.DoWithContext(ctx, req) return res, false, err } @@ -1030,11 +1027,11 @@ func (api *API) EditMessageMedia(params EditMessageMedia) (Message, bool, error) var zero Message if params.InlineMessageID != "" { req := NewRequestWithChatID[bool]("editMessageMedia", params, params.ChatID) - res, err := req.Do(api) + res, err := api.Do(req) return zero, res, err } req := NewRequestWithChatID[Message]("editMessageMedia", params, params.ChatID) - res, err := req.Do(api) + res, err := api.Do(req) return res, false, err } @@ -1046,11 +1043,11 @@ func (api *API) EditMessageMediaWithContext(ctx context.Context, params EditMess var zero Message if params.InlineMessageID != "" { req := NewRequestWithChatID[bool]("editMessageMedia", params, params.ChatID) - res, err := req.DoWithContext(ctx, api) + res, err := api.DoWithContext(ctx, req) return zero, res, err } req := NewRequestWithChatID[Message]("editMessageMedia", params, params.ChatID) - res, err := req.DoWithContext(ctx, api) + res, err := api.DoWithContext(ctx, req) return res, false, err } @@ -1100,11 +1097,11 @@ func (api *API) EditMessageLiveLocation(params EditMessageLiveLocation) (Message var zero Message if params.InlineMessageID != "" { req := NewRequestWithChatID[bool]("editMessageLiveLocation", params, params.ChatID) - res, err := req.Do(api) + res, err := api.Do(req) return zero, res, err } req := NewRequestWithChatID[Message]("editMessageLiveLocation", params, params.ChatID) - res, err := req.Do(api) + res, err := api.Do(req) return res, false, err } @@ -1116,11 +1113,11 @@ func (api *API) EditMessageLiveLocationWithContext(ctx context.Context, params E var zero Message if params.InlineMessageID != "" { req := NewRequestWithChatID[bool]("editMessageLiveLocation", params, params.ChatID) - res, err := req.DoWithContext(ctx, api) + res, err := api.DoWithContext(ctx, req) return zero, res, err } req := NewRequestWithChatID[Message]("editMessageLiveLocation", params, params.ChatID) - res, err := req.DoWithContext(ctx, api) + res, err := api.DoWithContext(ctx, req) return res, false, err } @@ -1153,11 +1150,11 @@ func (api *API) StopMessageLiveLocation(params StopMessageLiveLocation) (Message var zero Message if params.InlineMessageID != "" { req := NewRequestWithChatID[bool]("stopMessageLiveLocation", params, params.ChatID) - res, err := req.Do(api) + res, err := api.Do(req) return zero, res, err } req := NewRequestWithChatID[Message]("stopMessageLiveLocation", params, params.ChatID) - res, err := req.Do(api) + res, err := api.Do(req) return res, false, err } @@ -1169,11 +1166,11 @@ func (api *API) StopMessageLiveLocationWithContext(ctx context.Context, params S var zero Message if params.InlineMessageID != "" { req := NewRequestWithChatID[bool]("stopMessageLiveLocation", params, params.ChatID) - res, err := req.DoWithContext(ctx, api) + res, err := api.DoWithContext(ctx, req) return zero, res, err } req := NewRequestWithChatID[Message]("stopMessageLiveLocation", params, params.ChatID) - res, err := req.DoWithContext(ctx, api) + res, err := api.DoWithContext(ctx, req) return res, false, err } @@ -1200,7 +1197,7 @@ type EditMessageChecklist struct { // See https://core.telegram.org/bots/api#editmessagechecklist func (api *API) EditMessageChecklist(params EditMessageChecklist) (Message, error) { req := NewRequestWithChatID[Message]("editMessageChecklist", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // EditMessageChecklistWithContext is the context-aware variant of EditMessageChecklist. @@ -1209,7 +1206,7 @@ func (api *API) EditMessageChecklist(params EditMessageChecklist) (Message, erro // See https://core.telegram.org/bots/api#editmessagechecklist func (api *API) EditMessageChecklistWithContext(ctx context.Context, params EditMessageChecklist) (Message, error) { req := NewRequestWithChatID[Message]("editMessageChecklist", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // EditMessageReplyMarkup holds parameters for the editMessageReplyMarkup method. @@ -1240,11 +1237,11 @@ func (api *API) EditMessageReplyMarkup(params EditMessageReplyMarkup) (Message, var zero Message if params.InlineMessageID != "" { req := NewRequestWithChatID[bool]("editMessageReplyMarkup", params, params.ChatID) - res, err := req.Do(api) + res, err := api.Do(req) return zero, res, err } req := NewRequestWithChatID[Message]("editMessageReplyMarkup", params, params.ChatID) - res, err := req.Do(api) + res, err := api.Do(req) return res, false, err } @@ -1256,11 +1253,11 @@ func (api *API) EditMessageReplyMarkupWithContext(ctx context.Context, params Ed var zero Message if params.InlineMessageID != "" { req := NewRequestWithChatID[bool]("editMessageReplyMarkup", params, params.ChatID) - res, err := req.DoWithContext(ctx, api) + res, err := api.DoWithContext(ctx, req) return zero, res, err } req := NewRequestWithChatID[Message]("editMessageReplyMarkup", params, params.ChatID) - res, err := req.DoWithContext(ctx, api) + res, err := api.DoWithContext(ctx, req) return res, false, err } @@ -1286,7 +1283,7 @@ type StopPoll struct { // See https://core.telegram.org/bots/api#stoppoll func (api *API) StopPoll(params StopPoll) (Poll, error) { req := NewRequestWithChatID[Poll]("stopPoll", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // StopPollWithContext is the context-aware variant of StopPoll. @@ -1295,7 +1292,7 @@ func (api *API) StopPoll(params StopPoll) (Poll, error) { // See https://core.telegram.org/bots/api#stoppoll func (api *API) StopPollWithContext(ctx context.Context, params StopPoll) (Poll, error) { req := NewRequestWithChatID[Poll]("stopPoll", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // ApproveSuggestedPost holds parameters for the approveSuggestedPost method. @@ -1318,7 +1315,7 @@ type ApproveSuggestedPost struct { // See https://core.telegram.org/bots/api#approvesuggestedpost func (api *API) ApproveSuggestedPost(params ApproveSuggestedPost) (bool, error) { req := NewRequestWithChatID[bool]("approveSuggestedPost", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // ApproveSuggestedPostWithContext is the context-aware variant of ApproveSuggestedPost. @@ -1327,7 +1324,7 @@ func (api *API) ApproveSuggestedPost(params ApproveSuggestedPost) (bool, error) // See https://core.telegram.org/bots/api#approvesuggestedpost func (api *API) ApproveSuggestedPostWithContext(ctx context.Context, params ApproveSuggestedPost) (bool, error) { req := NewRequestWithChatID[bool]("approveSuggestedPost", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // DeclineSuggestedPost holds parameters for the declineSuggestedPost method. @@ -1348,7 +1345,7 @@ type DeclineSuggestedPost struct { // See https://core.telegram.org/bots/api#declinesuggestedpost func (api *API) DeclineSuggestedPost(params DeclineSuggestedPost) (bool, error) { req := NewRequestWithChatID[bool]("declineSuggestedPost", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // DeclineSuggestedPostWithContext is the context-aware variant of DeclineSuggestedPost. @@ -1357,7 +1354,7 @@ func (api *API) DeclineSuggestedPost(params DeclineSuggestedPost) (bool, error) // See https://core.telegram.org/bots/api#declinesuggestedpost func (api *API) DeclineSuggestedPostWithContext(ctx context.Context, params DeclineSuggestedPost) (bool, error) { req := NewRequestWithChatID[bool]("declineSuggestedPost", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // DeleteMessage holds parameters for the deleteMessage method. @@ -1377,7 +1374,7 @@ type DeleteMessage struct { // See https://core.telegram.org/bots/api#deletemessage func (api *API) DeleteMessage(params DeleteMessage) (bool, error) { req := NewRequestWithChatID[bool]("deleteMessage", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // DeleteMessageWithContext is the context-aware variant of DeleteMessage. @@ -1386,7 +1383,7 @@ func (api *API) DeleteMessage(params DeleteMessage) (bool, error) { // See https://core.telegram.org/bots/api#deletemessage func (api *API) DeleteMessageWithContext(ctx context.Context, params DeleteMessage) (bool, error) { req := NewRequestWithChatID[bool]("deleteMessage", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // DeleteMessages holds parameters for the deleteMessages method. @@ -1407,7 +1404,7 @@ type DeleteMessages struct { // See https://core.telegram.org/bots/api#deletemessages func (api *API) DeleteMessages(params DeleteMessages) (bool, error) { req := NewRequestWithChatID[bool]("deleteMessages", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // DeleteMessagesWithContext is the context-aware variant of DeleteMessages. @@ -1416,7 +1413,7 @@ func (api *API) DeleteMessages(params DeleteMessages) (bool, error) { // See https://core.telegram.org/bots/api#deletemessages func (api *API) DeleteMessagesWithContext(ctx context.Context, params DeleteMessages) (bool, error) { req := NewRequestWithChatID[bool]("deleteMessages", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // AnswerCallbackQuery holds parameters for the answerCallbackQuery method. @@ -1447,7 +1444,7 @@ type AnswerCallbackQuery struct { // See https://core.telegram.org/bots/api#answercallbackquery func (api *API) AnswerCallbackQuery(params AnswerCallbackQuery) (bool, error) { req := NewRequest[bool]("answerCallbackQuery", params) - return req.Do(api) + return api.Do(req) } // AnswerCallbackQueryWithContext is the context-aware variant of AnswerCallbackQuery. @@ -1456,7 +1453,7 @@ func (api *API) AnswerCallbackQuery(params AnswerCallbackQuery) (bool, error) { // See https://core.telegram.org/bots/api#answercallbackquery func (api *API) AnswerCallbackQueryWithContext(ctx context.Context, params AnswerCallbackQuery) (bool, error) { req := NewRequest[bool]("answerCallbackQuery", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // AnswerGuestQuery holds parameters for the answerGuestQuery method. @@ -1474,7 +1471,7 @@ type AnswerGuestQuery struct { // See https://core.telegram.org/bots/api#answerguestquery func (api *API) AnswerGuestQuery(params AnswerGuestQuery) (SentGuestMessage, error) { req := NewRequest[SentGuestMessage]("answerGuestQuery", params) - return req.Do(api) + return api.Do(req) } // AnswerGuestQueryWithContext is the context-aware variant of AnswerGuestQuery. @@ -1483,7 +1480,7 @@ func (api *API) AnswerGuestQuery(params AnswerGuestQuery) (SentGuestMessage, err // See https://core.telegram.org/bots/api#answerguestquery func (api *API) AnswerGuestQueryWithContext(ctx context.Context, params AnswerGuestQuery) (SentGuestMessage, error) { req := NewRequest[SentGuestMessage]("answerGuestQuery", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // DeleteAllMessageReactions holds parameters for the deleteAllMessageReactions method. @@ -1507,18 +1504,7 @@ type DeleteAllMessageReactions struct { // See https://core.telegram.org/bots/api#deleteallmessagereactions func (api *API) DeleteAllMessageReactions(params DeleteAllMessageReactions) (bool, error) { req := NewRequest[bool]("deleteAllMessageReactions", params) - return req.Do(api) -} - -// DeleteAllMessageReactionWithContext is the context-aware variant of DeleteAllMessageReactions. -// -// Deprecated: use DeleteAllMessageReactionsWithContext. The misspelled alias is -// retained for v1 compatibility and is subject to removal in v2. -// Since: Bot API 10.0 -// It executes the same request but uses ctx for cancellation and deadlines. -// See https://core.telegram.org/bots/api#deleteallmessagereactions -func (api *API) DeleteAllMessageReactionWithContext(ctx context.Context, params DeleteAllMessageReactions) (bool, error) { - return api.DeleteAllMessageReactionsWithContext(ctx, params) + return api.Do(req) } // DeleteAllMessageReactionsWithContext is the context-aware variant of DeleteAllMessageReactions. @@ -1527,7 +1513,7 @@ func (api *API) DeleteAllMessageReactionWithContext(ctx context.Context, params // See https://core.telegram.org/bots/api#deleteallmessagereactions func (api *API) DeleteAllMessageReactionsWithContext(ctx context.Context, params DeleteAllMessageReactions) (bool, error) { req := NewRequest[bool]("deleteAllMessageReactions", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // DeleteMessageReaction holds parameters for the deleteMessageReaction method. @@ -1553,7 +1539,7 @@ type DeleteMessageReaction struct { // See https://core.telegram.org/bots/api#deletemessagereaction func (api *API) DeleteMessageReaction(params DeleteMessageReaction) (bool, error) { req := NewRequest[bool]("deleteMessageReaction", params) - return req.Do(api) + return api.Do(req) } // DeleteMessageReactionWithContext is the context-aware variant of DeleteMessageReaction. @@ -1562,7 +1548,7 @@ func (api *API) DeleteMessageReaction(params DeleteMessageReaction) (bool, error // See https://core.telegram.org/bots/api#deletemessagereaction func (api *API) DeleteMessageReactionWithContext(ctx context.Context, params DeleteMessageReaction) (bool, error) { req := NewRequest[bool]("deleteMessageReaction", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // SendRichMessage holds parameters for the sendRichMessage method. @@ -1578,6 +1564,8 @@ type SendRichMessage struct { // DirectMessagesTopicID identifies the target direct-messages topic. DirectMessagesTopicID int64 `json:"direct_messages_topic_id,omitempty"` + EphemeralMessageParameters *EphemeralMessageParameters `json:"ephemeral_message_parameters,omitempty"` // Since: Bot API 10.3 + // RichMessage contains structured rich-message content. RichMessage InputRichMessage `json:"rich_message"` // DisableNotification requests delivery without a notification sound. @@ -1601,7 +1589,7 @@ type SendRichMessage struct { // See https://core.telegram.org/bots/api#sendrichmessage func (api *API) SendRichMessage(params SendRichMessage) (Message, error) { req := NewRequestWithChatID[Message]("sendRichMessage", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // SendRichMessageWithContext is the context-aware variant of SendRichMessage. @@ -1610,7 +1598,7 @@ func (api *API) SendRichMessage(params SendRichMessage) (Message, error) { // See https://core.telegram.org/bots/api#sendrichmessage func (api *API) SendRichMessageWithContext(ctx context.Context, params SendRichMessage) (Message, error) { req := NewRequestWithChatID[Message]("sendRichMessage", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // SendRichMessageDraft holds parameters for the sendRichMessageDraft method. @@ -1626,6 +1614,11 @@ type SendRichMessageDraft struct { DraftID int64 `json:"draft_id"` // RichMessage contains structured rich-message content. RichMessage InputRichMessage `json:"rich_message"` + + // CanStop allows the user to stop message generation while the draft is shown. + CanStop bool `json:"can_stop,omitempty"` // Since: Bot API 10.3 + // KeepOnStop preserves the draft as an ephemeral message when generation is stopped. + KeepOnStop bool `json:"keep_on_stop,omitempty"` // Since: Bot API 10.3 } // SendRichMessageDraft streams a partial rich message to a private chat while @@ -1636,7 +1629,7 @@ type SendRichMessageDraft struct { // See https://core.telegram.org/bots/api#sendrichmessagedraft func (api *API) SendRichMessageDraft(params SendRichMessageDraft) (bool, error) { req := NewRequestWithChatID[bool]("sendRichMessageDraft", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // SendRichMessageDraftWithContext is the context-aware variant of SendRichMessageDraft. @@ -1645,7 +1638,7 @@ func (api *API) SendRichMessageDraft(params SendRichMessageDraft) (bool, error) // See https://core.telegram.org/bots/api#sendrichmessagedraft func (api *API) SendRichMessageDraftWithContext(ctx context.Context, params SendRichMessageDraft) (bool, error) { req := NewRequestWithChatID[bool]("sendRichMessageDraft", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // EditEphemeralMessageText holds parameters for editing an ephemeral text message. @@ -1659,12 +1652,13 @@ type EditEphemeralMessageText struct { // EphemeralMessageID identifies the ephemeral message. EphemeralMessageID int64 `json:"ephemeral_message_id"` // Text contains the formatted or plain text content. - Text string `json:"text"` + Text string `json:"text,omitempty"` // ParseMode selects the formatting syntax used by the text or caption. ParseMode ParseMode `json:"parse_mode,omitempty"` // Entities describes explicit formatting entities in Text. - Entities []MessageEntity `json:"entities,omitempty"` + Entities []MessageEntity `json:"entities,omitempty"` + RichMessage *InputRichMessage `json:"rich_message,omitempty"` // Since: Bot API 10.3 // LinkPreviewOptions controls link preview generation for Text. LinkPreviewOptions *LinkPreviewOptions `json:"link_preview_options,omitempty"` // ReplyMarkup defines the message's inline keyboard. @@ -1676,7 +1670,7 @@ type EditEphemeralMessageText struct { // Since: Bot API 10.2 func (api *API) EditEphemeralMessageText(params EditEphemeralMessageText) (bool, error) { req := NewRequestWithChatID[bool]("editEphemeralMessageText", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // EditEphemeralMessageTextWithContext is the context-aware variant of EditEphemeralMessageText. @@ -1684,11 +1678,11 @@ func (api *API) EditEphemeralMessageText(params EditEphemeralMessageText) (bool, // Since: Bot API 10.2 func (api *API) EditEphemeralMessageTextWithContext(ctx context.Context, params EditEphemeralMessageText) (bool, error) { req := NewRequestWithChatID[bool]("editEphemeralMessageText", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // EditEphemeralMessageMedia holds parameters for editing ephemeral message media. -// New files cannot be uploaded; use a file ID or URL. +// Use Uploader.EditEphemeralMessageMedia to upload files referenced through attach:// names. // // Since: Bot API 10.2 type EditEphemeralMessageMedia struct { @@ -1709,7 +1703,7 @@ type EditEphemeralMessageMedia struct { // Since: Bot API 10.2 func (api *API) EditEphemeralMessageMedia(params EditEphemeralMessageMedia) (bool, error) { req := NewRequestWithChatID[bool]("editEphemeralMessageMedia", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // EditEphemeralMessageMediaWithContext is the context-aware variant of EditEphemeralMessageMedia. @@ -1717,7 +1711,7 @@ func (api *API) EditEphemeralMessageMedia(params EditEphemeralMessageMedia) (boo // Since: Bot API 10.2 func (api *API) EditEphemeralMessageMediaWithContext(ctx context.Context, params EditEphemeralMessageMedia) (bool, error) { req := NewRequestWithChatID[bool]("editEphemeralMessageMedia", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // EditEphemeralMessageCaption holds parameters for editing an ephemeral message caption. @@ -1733,6 +1727,8 @@ type EditEphemeralMessageCaption struct { // Caption contains the media or block caption. Caption string `json:"caption,omitempty"` + // ShowCaptionAboveMedia places the caption above the media. + ShowCaptionAboveMedia bool `json:"show_caption_above_media,omitempty"` // Since: Bot API 10.3 // ParseMode selects the formatting syntax used by the text or caption. ParseMode ParseMode `json:"parse_mode,omitempty"` // CaptionEntities describes formatting entities in Caption. @@ -1746,7 +1742,7 @@ type EditEphemeralMessageCaption struct { // Since: Bot API 10.2 func (api *API) EditEphemeralMessageCaption(params EditEphemeralMessageCaption) (bool, error) { req := NewRequestWithChatID[bool]("editEphemeralMessageCaption", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // EditEphemeralMessageCaptionWithContext is the context-aware variant of EditEphemeralMessageCaption. @@ -1754,7 +1750,7 @@ func (api *API) EditEphemeralMessageCaption(params EditEphemeralMessageCaption) // Since: Bot API 10.2 func (api *API) EditEphemeralMessageCaptionWithContext(ctx context.Context, params EditEphemeralMessageCaption) (bool, error) { req := NewRequestWithChatID[bool]("editEphemeralMessageCaption", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // EditEphemeralMessageReplyMarkup holds parameters for editing an ephemeral message's inline keyboard. @@ -1776,7 +1772,7 @@ type EditEphemeralMessageReplyMarkup struct { // Since: Bot API 10.2 func (api *API) EditEphemeralMessageReplyMarkup(params EditEphemeralMessageReplyMarkup) (bool, error) { req := NewRequestWithChatID[bool]("editEphemeralMessageReplyMarkup", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // EditEphemeralMessageReplyMarkupWithContext is the context-aware variant of EditEphemeralMessageReplyMarkup. @@ -1784,7 +1780,7 @@ func (api *API) EditEphemeralMessageReplyMarkup(params EditEphemeralMessageReply // Since: Bot API 10.2 func (api *API) EditEphemeralMessageReplyMarkupWithContext(ctx context.Context, params EditEphemeralMessageReplyMarkup) (bool, error) { req := NewRequestWithChatID[bool]("editEphemeralMessageReplyMarkup", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // DeleteEphemeralMessage holds parameters for deleting an ephemeral message. @@ -1804,7 +1800,7 @@ type DeleteEphemeralMessage struct { // Since: Bot API 10.2 func (api *API) DeleteEphemeralMessage(params DeleteEphemeralMessage) (bool, error) { req := NewRequestWithChatID[bool]("deleteEphemeralMessage", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // DeleteEphemeralMessageWithContext is the context-aware variant of DeleteEphemeralMessage. @@ -1812,5 +1808,5 @@ func (api *API) DeleteEphemeralMessage(params DeleteEphemeralMessage) (bool, err // Since: Bot API 10.2 func (api *API) DeleteEphemeralMessageWithContext(ctx context.Context, params DeleteEphemeralMessage) (bool, error) { req := NewRequestWithChatID[bool]("deleteEphemeralMessage", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } diff --git a/tgapi/messages_types.go b/tgapi/messages_types.go index 89f5d01..cc0ac3b 100644 --- a/tgapi/messages_types.go +++ b/tgapi/messages_types.go @@ -423,6 +423,8 @@ type Message struct { ChecklistTasksAdded *ChecklistTasksAdded `json:"checklist_tasks_added,omitempty"` // Since: Bot API 9.1 // CommunityChatAdded describes a community chat addition service message. CommunityChatAdded *CommunityChatAdded `json:"community_chat_added,omitempty"` // Since: Bot API 10.2 + // CommunityChatJoined Optional. Service message: a chat was joined by a user from a community. + CommunityChatJoined *CommunityChatJoined `json:"community_chat_joined,omitempty"` // Since: Bot API 10.3 // CommunityChatRemoved describes a community chat removal service message. CommunityChatRemoved *CommunityChatRemoved `json:"community_chat_removed,omitempty"` // Since: Bot API 10.2 // DirectMessagePriceChanged Optional. Service message: the price for paid messages in the corresponding @@ -683,8 +685,7 @@ type ReplyParameters struct { Quote string `json:"quote,omitempty"` // QuoteParsingMode Optional. Mode for parsing entities in the quote. See formatting options for more // details. - // Subject to change in v2: the Go field name may be corrected to QuoteParseMode. - QuoteParsingMode string `json:"quote_parse_mode,omitempty"` + QuoteParseMode string `json:"quote_parse_mode,omitempty"` // QuoteEntities 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"` @@ -758,6 +759,8 @@ type ReplyMarkup struct { type InlineKeyboardMarkup struct { // InlineKeyboard Array of button rows, each represented by an Array of InlineKeyboardButton objects InlineKeyboard [][]InlineKeyboardButton `json:"inline_keyboard,omitempty"` + // ForceReply opens the reply interface for the user. + ForceReply bool `json:"force_reply,omitempty"` // Since: Bot API 10.3 } // KeyboardButtonStyle represents the style of a keyboard button. @@ -916,6 +919,8 @@ type InlineKeyboardButton struct { // 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"` // Since: Bot API 9.4 + // Disabled makes the button do nothing. It must be the button's only action. + Disabled *DisabledButton `json:"disabled,omitempty"` // Since: Bot API 10.3 } // ReplyKeyboardMarkup represents a custom keyboard with reply options. @@ -945,6 +950,8 @@ type ReplyKeyboardMarkup struct { // change the bot's language, bot replies to the request with a keyboard to select the new language. Other // users in the group don't see the keyboard. Selective bool `json:"selective,omitempty"` + // ForceReply opens the reply interface for the user. + ForceReply bool `json:"force_reply,omitempty"` // Since: Bot API 10.3 } // CallbackQuery represents an incoming callback query from a callback button in an inline keyboard. @@ -970,6 +977,49 @@ type CallbackQuery struct { GameShortName string `json:"game_short_name,omitempty"` } +// LoginURL configures HTTPS authorization through Telegram Login. +// +// Since: Bot API 4.3 +type LoginURL struct { + // URL is the HTTPS authorization destination with a domain linked to the bot. + URL string `json:"url"` + // ForwardText optionally replaces the button label in forwarded messages. + ForwardText string `json:"forward_text,omitempty"` + // BotUsername optionally selects the authorization bot; unsupported in rich buttons. + BotUsername string `json:"bot_username,omitempty"` + // RequestWriteAccess asks permission for the bot to message the user. + RequestWriteAccess bool `json:"request_write_access,omitempty"` +} + +// SwitchInlineQueryChosenChat restricts the chat picker for an inline query. +// +// Since: Bot API 6.7 +type SwitchInlineQueryChosenChat struct { + // Query is the initial query; an empty value inserts only the bot username. + Query string `json:"query,omitempty"` + // AllowUserChats permits private chats with users. + AllowUserChats bool `json:"allow_user_chats,omitempty"` + // AllowBotChats permits private chats with bots. + AllowBotChats bool `json:"allow_bot_chats,omitempty"` + // AllowGroupChats permits groups and supergroups. + AllowGroupChats bool `json:"allow_group_chats,omitempty"` + // AllowChannelChats permits channels. + AllowChannelChats bool `json:"allow_channel_chats,omitempty"` +} + +// CopyTextButton describes text copied to the clipboard when pressed. +// +// Since: Bot API 7.11 +type CopyTextButton struct { + // Text contains 1-256 characters to copy. + Text string `json:"text"` +} + +// DisabledButton represents a button without an action. +// +// Since: Bot API 10.3 +type DisabledButton struct{} + // ChatActionType represents the type of chat action. type ChatActionType string @@ -1288,3 +1338,15 @@ type InputRichMessage struct { // SkipEntityDetection disables automatic detection of links, mentions, hashtags, commands, phone numbers, and bank cards. SkipEntityDetection bool `json:"skip_entity_detection,omitempty"` } + +// EphemeralMessageParameters identifies the recipient and callback context of an ephemeral message. +// +// Since: Bot API 10.3 +type EphemeralMessageParameters struct { + // ReceiverUserID identifies the user who may receive the message. + ReceiverUserID int64 `json:"receiver_user_id"` + // CallbackQueryID identifies the callback query that triggered the message. + CallbackQueryID string `json:"callback_query_id,omitempty"` + // ReplaceCallbackQueryMessage shows the ephemeral message in place of the original callback message. + ReplaceCallbackQueryMessage bool `json:"replace_callback_query_message,omitempty"` +} diff --git a/tgapi/methods.go b/tgapi/methods.go index 4b21a11..a9d4538 100644 --- a/tgapi/methods.go +++ b/tgapi/methods.go @@ -7,7 +7,7 @@ import ( "math" "net/http" - "git.scuroneko.dev/scuroneko/laniakea/utils" + "git.scuroneko.dev/scuroneko/laniakea/v2/utils" ) // UpdateParams holds parameters for the getUpdates method. @@ -38,7 +38,7 @@ type UpdateParams struct { // See https://core.telegram.org/bots/api#getme func (api *API) GetMe() (User, error) { req := NewRequest[User]("getMe", NoParams) - return req.Do(api) + return api.Do(req) } // GetMeWithContext is the context-aware variant of GetMe. @@ -46,7 +46,7 @@ func (api *API) GetMe() (User, error) { // See https://core.telegram.org/bots/api#getme func (api *API) GetMeWithContext(ctx context.Context) (User, error) { req := NewRequest[User]("getMe", NoParams) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // GetManagedBotToken holds parameters for the getManagedBotToken method. @@ -60,7 +60,7 @@ type GetManagedBotToken struct { // See https://core.telegram.org/bots/api#getmanagedbottoken func (api *API) GetManagedBotToken(params GetManagedBotToken) (string, error) { req := NewRequest[string]("getManagedBotToken", params) - return req.Do(api) + return api.Do(req) } // GetManagedBotTokenWithContext is the context-aware variant of GetManagedBotToken. @@ -68,7 +68,7 @@ func (api *API) GetManagedBotToken(params GetManagedBotToken) (string, error) { // See https://core.telegram.org/bots/api#getmanagedbottoken func (api *API) GetManagedBotTokenWithContext(ctx context.Context, params GetManagedBotToken) (string, error) { req := NewRequest[string]("getManagedBotToken", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // ReplaceManagedBotToken holds parameters for the replaceManagedBotToken method. @@ -82,7 +82,7 @@ type ReplaceManagedBotToken struct { // See https://core.telegram.org/bots/api#replacemanagedbottoken func (api *API) ReplaceManagedBotToken(params ReplaceManagedBotToken) (string, error) { req := NewRequest[string]("replaceManagedBotToken", params) - return req.Do(api) + return api.Do(req) } // ReplaceManagedBotTokenWithContext is the context-aware variant of ReplaceManagedBotToken. @@ -90,7 +90,7 @@ func (api *API) ReplaceManagedBotToken(params ReplaceManagedBotToken) (string, e // See https://core.telegram.org/bots/api#replacemanagedbottoken func (api *API) ReplaceManagedBotTokenWithContext(ctx context.Context, params ReplaceManagedBotToken) (string, error) { req := NewRequest[string]("replaceManagedBotToken", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // LogOut logs the bot out from the cloud Bot API server. @@ -98,7 +98,7 @@ func (api *API) ReplaceManagedBotTokenWithContext(ctx context.Context, params Re // See https://core.telegram.org/bots/api#logout func (api *API) LogOut() (bool, error) { req := NewRequest[bool]("logOut", NoParams) - return req.Do(api) + return api.Do(req) } // LogOutWithContext is the context-aware variant of LogOut. @@ -106,7 +106,7 @@ func (api *API) LogOut() (bool, error) { // See https://core.telegram.org/bots/api#logout func (api *API) LogOutWithContext(ctx context.Context) (bool, error) { req := NewRequest[bool]("logOut", NoParams) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // CloseRemote closes the bot instance on the local server. @@ -114,7 +114,7 @@ func (api *API) LogOutWithContext(ctx context.Context) (bool, error) { // See https://core.telegram.org/bots/api#close func (api *API) CloseRemote() (bool, error) { req := NewRequest[bool]("close", NoParams) - return req.Do(api) + return api.Do(req) } // CloseRemoteWithContext is the context-aware variant of CloseRemote. @@ -122,14 +122,14 @@ func (api *API) CloseRemote() (bool, error) { // See https://core.telegram.org/bots/api#close func (api *API) CloseRemoteWithContext(ctx context.Context) (bool, error) { req := NewRequest[bool]("close", NoParams) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // GetUpdates receives incoming updates using long polling. // See https://core.telegram.org/bots/api#getupdates func (api *API) GetUpdates(params UpdateParams) ([]Update, error) { req := NewRequest[[]Update]("getUpdates", params) - return req.Do(api) + return api.Do(req) } // GetUpdatesWithContext is the context-aware variant of GetUpdates. @@ -137,7 +137,7 @@ func (api *API) GetUpdates(params UpdateParams) ([]Update, error) { // See https://core.telegram.org/bots/api#getupdates func (api *API) GetUpdatesWithContext(ctx context.Context, params UpdateParams) ([]Update, error) { req := NewRequest[[]Update]("getUpdates", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // SetWebhook holds parameters for the setWebhook method. @@ -175,7 +175,7 @@ type SetWebhook struct { // See https://core.telegram.org/bots/api#setwebhook func (api *API) SetWebhook(params SetWebhook) (bool, error) { req := NewRequest[bool]("setWebhook", params) - return req.Do(api) + return api.Do(req) } // SetWebhookWithContext is the context-aware variant of SetWebhook. @@ -184,7 +184,7 @@ func (api *API) SetWebhook(params SetWebhook) (bool, error) { // See https://core.telegram.org/bots/api#setwebhook func (api *API) SetWebhookWithContext(ctx context.Context, params SetWebhook) (bool, error) { req := NewRequest[bool]("setWebhook", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // DeleteWebhook holds parameters for the deleteWebhook method. @@ -199,7 +199,7 @@ type DeleteWebhook struct { // See https://core.telegram.org/bots/api#deletewebhook func (api *API) DeleteWebhook(params DeleteWebhook) (bool, error) { req := NewRequest[bool]("deleteWebhook", params) - return req.Do(api) + return api.Do(req) } // DeleteWebhookWithContext is the context-aware variant of DeleteWebhook. @@ -207,14 +207,14 @@ func (api *API) DeleteWebhook(params DeleteWebhook) (bool, error) { // See https://core.telegram.org/bots/api#deletewebhook func (api *API) DeleteWebhookWithContext(ctx context.Context, params DeleteWebhook) (bool, error) { req := NewRequest[bool]("deleteWebhook", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // GetWebhookInfo returns the current webhook status. // See https://core.telegram.org/bots/api#getwebhookinfo func (api *API) GetWebhookInfo() (WebhookInfo, error) { req := NewRequest[WebhookInfo]("getWebhookInfo", NoParams) - return req.Do(api) + return api.Do(req) } // GetWebhookInfoWithContext is the context-aware variant of GetWebhookInfo. @@ -222,7 +222,7 @@ func (api *API) GetWebhookInfo() (WebhookInfo, error) { // See https://core.telegram.org/bots/api#getwebhookinfo func (api *API) GetWebhookInfoWithContext(ctx context.Context) (WebhookInfo, error) { req := NewRequest[WebhookInfo]("getWebhookInfo", NoParams) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // GetFile holds parameters for the getFile method. @@ -236,7 +236,7 @@ type GetFile struct { // See https://core.telegram.org/bots/api#getfile func (api *API) GetFile(params GetFile) (File, error) { req := NewRequest[File]("getFile", params) - return req.Do(api) + return api.Do(req) } // GetFileWithContext is the context-aware variant of GetFile. @@ -244,25 +244,7 @@ func (api *API) GetFile(params GetFile) (File, error) { // See https://core.telegram.org/bots/api#getfile func (api *API) GetFileWithContext(ctx context.Context, params GetFile) (File, error) { req := NewRequest[File]("getFile", params) - return req.DoWithContext(ctx, api) -} - -// GetFileByLink downloads a file from Telegram's file server using the provided file link. -// The link is usually obtained from File.FilePath. -// For large files, prefer OpenFileByLink or OpenFileByLinkWithContext to stream the response body. -// This unbounded helper is retained for v1 compatibility and is subject to change in v2; -// prefer GetFileByLinkLimit for untrusted or potentially large files. -// See https://core.telegram.org/bots/api#file -func (api *API) GetFileByLink(link string) ([]byte, error) { - return api.getFileByLink(context.Background(), link) -} - -// GetFileByLinkWithContext is the context-aware variant of GetFileByLink. -// It executes the same request but uses ctx for cancellation and deadlines. -// For large files, prefer OpenFileByLinkWithContext to stream the response body. -// See https://core.telegram.org/bots/api#file -func (api *API) GetFileByLinkWithContext(ctx context.Context, link string) ([]byte, error) { - return api.getFileByLink(ctx, link) + return api.DoWithContext(ctx, req) } // GetFileByLinkLimit downloads at most maxBytes from Telegram's file server. @@ -310,17 +292,6 @@ func (api *API) OpenFileByLinkWithContext(ctx context.Context, link string) (io. return api.openFileByLink(ctx, link) } -func (api *API) getFileByLink(ctx context.Context, link string) ([]byte, error) { - body, err := api.openFileByLink(ctx, link) - if err != nil { - return nil, err - } - defer func() { - _ = body.Close() - }() - return io.ReadAll(body) -} - func (api *API) openFileByLink(ctx context.Context, link string) (io.ReadCloser, error) { methodPrefix := "" if api.useTestServer { diff --git a/tgapi/methods_test.go b/tgapi/methods_test.go index bc9f7ff..9210c92 100644 --- a/tgapi/methods_test.go +++ b/tgapi/methods_test.go @@ -9,7 +9,7 @@ import ( "testing" ) -func TestGetFileByLinkUsesConfiguredAPIURL(t *testing.T) { +func TestGetFileByLinkLimitUsesConfiguredAPIURL(t *testing.T) { var gotPath string client := &http.Client{ @@ -33,9 +33,9 @@ func TestGetFileByLinkUsesConfiguredAPIURL(t *testing.T) { } }() - data, err := api.GetFileByLink("files/report.txt") + data, err := api.GetFileByLinkLimit("files/report.txt", 1024) if err != nil { - t.Fatalf("GetFileByLink returned error: %v", err) + t.Fatalf("GetFileByLinkLimit returned error: %v", err) } if string(data) != "payload" { t.Fatalf("unexpected payload: %q", string(data)) @@ -83,7 +83,7 @@ func TestOpenFileByLinkStreamsResponseBody(t *testing.T) { } } -func TestGetFileByLinkReturnsHTTPStatusError(t *testing.T) { +func TestGetFileByLinkLimitReturnsHTTPStatusError(t *testing.T) { client := &http.Client{ Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) { return &http.Response{ @@ -104,7 +104,7 @@ func TestGetFileByLinkReturnsHTTPStatusError(t *testing.T) { } }() - _, err := api.GetFileByLink("files/report.txt") + _, err := api.GetFileByLinkLimit("files/report.txt", 1024) if err == nil { t.Fatal("expected error for non-2xx response") } diff --git a/tgapi/migration_v2_test.go b/tgapi/migration_v2_test.go new file mode 100644 index 0000000..a85da15 --- /dev/null +++ b/tgapi/migration_v2_test.go @@ -0,0 +1,32 @@ +package tgapi + +import ( + "context" + "testing" +) + +func TestV2MigrationSurfaceCompiles(t *testing.T) { + answer := PollAnswer{User: &User{ID: 1}} + if answer.User == nil { + t.Fatal("poll voter is missing") + } + + _ = ChatFullInfo{AvailableReactions: []ReactionType{}} + _ = ReplyParameters{QuoteParseMode: string(ParseHTML)} + _ = InputChecklist{OthersCanAddTasks: true, OthersCanMarkTasksAsDone: true} + _ = SendMessage{EphemeralMessageParameters: &EphemeralMessageParameters{ReceiverUserID: 1}} + + request := NewRequest[bool]("customMethod", NoParams) + upload := NewUploaderRequest[bool]("customUpload", NoParams) + var api *API + var uploader *Uploader + if false { + _, _ = api.Do(request) + _, _ = api.DoWithContext(context.Background(), request) + _, _ = uploader.Do(upload) + _, _ = uploader.DoWithContext(context.Background(), upload) + _, _ = api.DeleteAllMessageReactions(DeleteAllMessageReactions{}) + _, _ = api.GetFileByLinkLimit("file/path", 1<<20) + _, _ = api.OpenFileByLinkWithContext(context.Background(), "file/path") + } +} diff --git a/tgapi/passport_methods.go b/tgapi/passport_methods.go index 20ba39e..772ac9f 100644 --- a/tgapi/passport_methods.go +++ b/tgapi/passport_methods.go @@ -18,7 +18,7 @@ type SetPassportDataErrors struct { // See https://core.telegram.org/bots/api#setpassportdataerrors func (api *API) SetPassportDataErrors(params SetPassportDataErrors) (bool, error) { req := NewRequest[bool]("setPassportDataErrors", params) - return req.Do(api) + return api.Do(req) } // SetPassportDataErrorsWithContext is the context-aware variant of SetPassportDataErrors. @@ -27,5 +27,5 @@ func (api *API) SetPassportDataErrors(params SetPassportDataErrors) (bool, error // See https://core.telegram.org/bots/api#setpassportdataerrors func (api *API) SetPassportDataErrorsWithContext(ctx context.Context, params SetPassportDataErrors) (bool, error) { req := NewRequest[bool]("setPassportDataErrors", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } diff --git a/tgapi/payments_methods.go b/tgapi/payments_methods.go index 6d2d2f8..d14c193 100644 --- a/tgapi/payments_methods.go +++ b/tgapi/payments_methods.go @@ -111,7 +111,7 @@ type SendInvoice struct { // See https://core.telegram.org/bots/api#sendinvoice func (api *API) SendInvoice(params SendInvoice) (Message, error) { req := NewRequestWithChatID[Message]("sendInvoice", params, params.ChatID) - return req.Do(api) + return api.Do(req) } // SendInvoiceWithContext is the context-aware variant of SendInvoice. @@ -120,7 +120,7 @@ func (api *API) SendInvoice(params SendInvoice) (Message, error) { // See https://core.telegram.org/bots/api#sendinvoice func (api *API) SendInvoiceWithContext(ctx context.Context, params SendInvoice) (Message, error) { req := NewRequestWithChatID[Message]("sendInvoice", params, params.ChatID) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // CreateInvoiceLink holds parameters for the createInvoiceLink method. @@ -205,7 +205,7 @@ type CreateInvoiceLink struct { // See https://core.telegram.org/bots/api#createinvoicelink func (api *API) CreateInvoiceLink(params CreateInvoiceLink) (string, error) { req := NewRequest[string]("createInvoiceLink", params) - return req.Do(api) + return api.Do(req) } // CreateInvoiceLinkWithContext is the context-aware variant of CreateInvoiceLink. @@ -214,7 +214,7 @@ func (api *API) CreateInvoiceLink(params CreateInvoiceLink) (string, error) { // See https://core.telegram.org/bots/api#createinvoicelink func (api *API) CreateInvoiceLinkWithContext(ctx context.Context, params CreateInvoiceLink) (string, error) { req := NewRequest[string]("createInvoiceLink", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // AnswerShippingQuery holds parameters for the answerShippingQuery method. @@ -240,7 +240,7 @@ type AnswerShippingQuery struct { // See https://core.telegram.org/bots/api#answershippingquery func (api *API) AnswerShippingQuery(params AnswerShippingQuery) (bool, error) { req := NewRequest[bool]("answerShippingQuery", params) - return req.Do(api) + return api.Do(req) } // AnswerShippingQueryWithContext is the context-aware variant of AnswerShippingQuery. @@ -249,7 +249,7 @@ func (api *API) AnswerShippingQuery(params AnswerShippingQuery) (bool, error) { // See https://core.telegram.org/bots/api#answershippingquery func (api *API) AnswerShippingQueryWithContext(ctx context.Context, params AnswerShippingQuery) (bool, error) { req := NewRequest[bool]("answerShippingQuery", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } // AnswerPreCheckoutQuery holds parameters for the answerPreCheckoutQuery method. @@ -274,7 +274,7 @@ type AnswerPreCheckoutQuery struct { // See https://core.telegram.org/bots/api#answerprecheckoutquery func (api *API) AnswerPreCheckoutQuery(params AnswerPreCheckoutQuery) (bool, error) { req := NewRequest[bool]("answerPreCheckoutQuery", params) - return req.Do(api) + return api.Do(req) } // AnswerPreCheckoutQueryWithContext is the context-aware variant of AnswerPreCheckoutQuery. @@ -283,5 +283,5 @@ func (api *API) AnswerPreCheckoutQuery(params AnswerPreCheckoutQuery) (bool, err // See https://core.telegram.org/bots/api#answerprecheckoutquery func (api *API) AnswerPreCheckoutQueryWithContext(ctx context.Context, params AnswerPreCheckoutQuery) (bool, error) { req := NewRequest[bool]("answerPreCheckoutQuery", params) - return req.DoWithContext(ctx, api) + return api.DoWithContext(ctx, req) } diff --git a/tgapi/rich_103_test.go b/tgapi/rich_103_test.go new file mode 100644 index 0000000..583e950 --- /dev/null +++ b/tgapi/rich_103_test.go @@ -0,0 +1,156 @@ +package tgapi + +import ( + "encoding/json" + "io" + "mime" + "mime/multipart" + "reflect" + "strings" + "testing" +) + +func TestRich103RoundTrip(t *testing.T) { + for _, tc := range []struct { + name, data string + want any + }{ + {"inline button", `{"blocks":[{"type":"paragraph","text":{"type":"button","button":{"text":["Go",{"type":"custom_emoji","custom_emoji_id":"123","alternative_text":"!"}],"switch_inline_query":""}}}]}`, RichBlockWrap{}}, + {"button row", `{"blocks":[{"type":"buttons","align":"right","buttons":[{"text":"Go","switch_inline_query_current_chat":""},{"text":"Choose","switch_inline_query_chosen_chat":{"query":"x","allow_user_chats":true}}]}]}`, RichBlockButtons{}}, + {"expandable quote", `{"blocks":[{"type":"expandable_blockquote","text":{"type":"bold","text":"quote"},"credit":"source"}]}`, RichBlockExpandableBlockQuotation{}}, + {"document", `{"blocks":[{"type":"document","document":{"file_id":"file","file_unique_id":"unique","file_name":"notes.txt"},"caption":{"text":"Notes","credit":"Author"}}]}`, RichBlockDocument{}}, + {"compact table", `{"blocks":[{"type":"table","cells":[],"is_compact":true}]}`, RichBlockTable{}}, + } { + t.Run(tc.name, func(t *testing.T) { + msg, err := UnmarshalRichMessage([]byte(tc.data)) + if err != nil { + t.Fatal(err) + } + if reflect.TypeOf(msg.Blocks[0]) != reflect.TypeOf(tc.want) { + t.Fatalf("got %T", msg.Blocks[0]) + } + data, err := json.Marshal(msg) + if err != nil { + t.Fatal(err) + } + // Compare the second decode to avoid unrelated omitted zero-value media fields. + again, err := UnmarshalRichMessage(data) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(msg, again) { + t.Fatalf("round trip lost data: %s", data) + } + if strings.Contains(tc.data, `"switch_inline_query":""`) && !strings.Contains(string(data), `"switch_inline_query":""`) { + t.Fatalf("lost empty query: %s", data) + } + }) + } +} + +func TestRichButtonActionsRoundTrip(t *testing.T) { + for _, action := range []string{ + `"url":"https://example.com"`, `"callback_data":"callback","style":"link"`, + `"web_app":{"url":"https://example.com"}`, `"login_url":{"url":"https://example.com","forward_text":"Forward","request_write_access":true}`, + `"switch_inline_query":""`, `"switch_inline_query_current_chat":""`, + `"switch_inline_query_chosen_chat":{"allow_user_chats":true}`, `"copy_text":{"text":"Copy"}`, `"disabled":{}`, + } { + raw := `{"text":"Button",` + action + `}` + var button RichMessageButton + if err := json.Unmarshal([]byte(raw), &button); err != nil { + t.Fatal(err) + } + encoded, err := json.Marshal(button) + if err != nil { + t.Fatal(err) + } + var expected, actual any + if err := json.Unmarshal([]byte(raw), &expected); err != nil { + t.Fatal(err) + } + if err := json.Unmarshal(encoded, &actual); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(expected, actual) { + t.Fatalf("wire fields changed: %s -> %s", raw, encoded) + } + var again RichMessageButton + if err := json.Unmarshal(encoded, &again); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(button, again) { + t.Fatalf("action lost: %s", encoded) + } + if button.Text != RichTextPlain("Button") { + t.Fatalf("label lost: %#v", button) + } + } +} + +func TestRich103Malformed(t *testing.T) { + for _, raw := range []string{ + `{"type":"button","button":{"text":null,"disabled":{}}}`, + } { + if _, err := UnmarshalRichText([]byte(raw)); err == nil { + t.Fatalf("accepted %s", raw) + } + } + for _, raw := range []string{ + `{"type":"buttons","buttons":[{"text":null,"disabled":{}}]}`, + `{"type":"document","document":42}`, + } { + if _, err := UnmarshalRichBlock([]byte(raw)); err == nil { + t.Fatalf("accepted %s", raw) + } + } + raw := `"x"` + for range 70 { + raw = `{"type":"button","button":{"text":` + raw + `,"disabled":{}}}` + } + if _, err := UnmarshalRichText([]byte(raw)); err == nil { + t.Fatal("accepted excessive nesting") + } +} + +func TestRichDocumentMultipart(t *testing.T) { + params := SendRichMessage{ChatID: 42, RichMessage: InputRichMessage{ + HTML: ``, + Media: []InputRichMessageMedia{{ID: "notes", Media: InputMedia{Type: InputMediaTypeDocument, Media: "attach://notes"}}}, + }} + body, contentType := prepareMultipartStream([]UploaderFile{NewUploaderFile("notes.txt", []byte("hello")).SetAttachName("notes")}, params) + defer func() { _ = body.Close() }() + _, attrs, err := mime.ParseMediaType(contentType) + if err != nil { + t.Fatal(err) + } + reader := multipart.NewReader(body, attrs["boundary"]) + gotFile, gotMessage := false, false + for { + part, err := reader.NextPart() + if err == io.EOF { + break + } + if err != nil { + t.Fatal(err) + } + data, err := io.ReadAll(part) + if err != nil { + t.Fatal(err) + } + switch part.FormName() { + case "notes": + gotFile = true + if part.FileName() != "notes.txt" || string(data) != "hello" { + t.Fatalf("bad file part: %s", data) + } + case "rich_message": + gotMessage = true + if !strings.Contains(string(data), `"media":"attach://notes"`) || !strings.Contains(string(data), `"type":"document"`) { + t.Fatalf("bad rich payload: %s", data) + } + } + } + if !gotFile || !gotMessage { + t.Fatal("missing multipart parts") + } +} diff --git a/tgapi/rich_block_types.go b/tgapi/rich_block_types.go index f8d2abd..3374a61 100644 --- a/tgapi/rich_block_types.go +++ b/tgapi/rich_block_types.go @@ -2,6 +2,20 @@ package tgapi import "encoding/json" +// RichBlockButtonAlign selects horizontal alignment of a button row. +// +// Since: Bot API 10.3 +type RichBlockButtonAlign string + +const ( + // RichBlockButtonLeft aligns buttons to left. + RichBlockButtonLeft RichBlockButtonAlign = "left" + // RichBlockButtonCenter aligns buttons to center. + RichBlockButtonCenter RichBlockButtonAlign = "center" + // RichBlockButtonRight aligns buttons to right. + RichBlockButtonRight RichBlockButtonAlign = "right" +) + // RichBlock is a block in a structured rich message. // // Since: Bot API 10.1 @@ -9,6 +23,22 @@ type RichBlock interface { isRichBlock() } +// RichBlockUnknown preserves an unrecognized rich-block object for lossless forwarding. +type RichBlockUnknown struct { + // Raw contains the complete JSON object received from Telegram. + Raw json.RawMessage +} + +func (RichBlockUnknown) isRichBlock() {} + +// MarshalJSON implements json.Marshaler. +func (u RichBlockUnknown) MarshalJSON() ([]byte, error) { + if !json.Valid(u.Raw) { + return nil, errInvalidUnknownRichJSON + } + return append([]byte(nil), u.Raw...), nil +} + // RichBlockCaption is the caption of a media block or container. // // Since: Bot API 10.1 @@ -385,6 +415,8 @@ type RichBlockTable struct { IsBordered bool // IsStriped requests alternating table row styling. IsStriped bool + // IsCompact requests smaller table-cell padding. + IsCompact bool // Since: Bot API 10.3 // Caption contains the media or block caption. Caption RichText } @@ -400,8 +432,9 @@ func (b RichBlockTable) MarshalJSON() ([]byte, error) { Cells [][]RichBlockTableCell `json:"cells"` IsBordered bool `json:"is_bordered,omitempty"` IsStriped bool `json:"is_striped,omitempty"` + IsCompact bool `json:"is_compact,omitempty"` Caption RichText `json:"caption,omitempty"` - }{"table", b.Cells, b.IsBordered, b.IsStriped, b.Caption}) + }{"table", b.Cells, b.IsBordered, b.IsStriped, b.IsCompact, b.Caption}) } // RichBlockMap is a location map block. @@ -420,8 +453,6 @@ type RichBlockMap struct { Caption *RichBlockCaption } -func (RichBlockMap) isRichBlock() {} - // MarshalJSON implements json.Marshaler. // // Since: Bot API 10.1 @@ -435,6 +466,29 @@ func (b RichBlockMap) MarshalJSON() ([]byte, error) { Caption *RichBlockCaption `json:"caption,omitempty"` }{"map", b.Location, b.Zoom, b.Width, b.Height, b.Caption}) } +func (RichBlockMap) isRichBlock() {} + +// RichBlockButtons is a row of buttons corresponding to . +// +// Since: Bot API 10.3 +type RichBlockButtons struct { + // Buttons contains the row contents. + Buttons []RichMessageButton `json:"buttons"` + // Align optionally selects left, center, or right alignment. + Align RichBlockButtonAlign `json:"align,omitempty"` +} + +// MarshalJSON adds the buttons type discriminator. +// +// Since: Bot API 10.3 +func (b RichBlockButtons) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Type string `json:"type"` + Buttons []RichMessageButton `json:"buttons"` + Align RichBlockButtonAlign `json:"align,omitempty"` + }{"buttons", b.Buttons, b.Align}) +} +func (RichBlockButtons) isRichBlock() {} // RichBlockPhoto is a photo block. // @@ -615,3 +669,49 @@ func (b RichBlockAnchor) MarshalJSON() ([]byte, error) { Name string `json:"name"` }{"anchor", b.Name}) } + +// RichBlockExpandableBlockQuotation corresponds to
. +// +// Since: Bot API 10.3 +type RichBlockExpandableBlockQuotation struct { + // Text is the quotation content. + Text RichText `json:"text"` + // Credit optionally identifies the source. + Credit RichText `json:"credit,omitempty"` +} + +func (RichBlockExpandableBlockQuotation) isRichBlock() {} + +// MarshalJSON adds the expandable_blockquote type discriminator. +// +// Since: Bot API 10.3 +func (b RichBlockExpandableBlockQuotation) MarshalJSON() ([]byte, error) { + type plain RichBlockExpandableBlockQuotation + return json.Marshal(struct { + Type string `json:"type"` + plain + }{"expandable_blockquote", plain(b)}) +} + +// RichBlockDocument is a general-file block corresponding to . +// +// Since: Bot API 10.3 +type RichBlockDocument struct { + // Document is the attached file. + Document Document `json:"document"` + // Caption optionally supplies the block caption. + Caption *RichBlockCaption `json:"caption,omitempty"` +} + +func (RichBlockDocument) isRichBlock() {} + +// MarshalJSON adds the document type discriminator. +// +// Since: Bot API 10.3 +func (b RichBlockDocument) MarshalJSON() ([]byte, error) { + type plain RichBlockDocument + return json.Marshal(struct { + Type string `json:"type"` + plain + }{"document", plain(b)}) +} diff --git a/tgapi/rich_input_types.go b/tgapi/rich_input_types.go index a3ddbcb..4ff56c8 100644 --- a/tgapi/rich_input_types.go +++ b/tgapi/rich_input_types.go @@ -36,6 +36,10 @@ const ( InputRichTypeDetails InputRichType = "details" // InputRichTypeMap identifies a map block. InputRichTypeMap InputRichType = "map" + // InputRichTypeButtons identifies a button row. + // + // Since: Bot API 10.3 + InputRichTypeButtons InputRichType = "buttons" // InputRichTypeAnimation identifies an animation block. InputRichTypeAnimation InputRichType = "animation" // InputRichTypeAudio identifies an audio block. @@ -297,6 +301,8 @@ type InputRichBlockTable struct { IsBordered bool `json:"is_bordered,omitempty"` // IsStriped requests alternating table row styling. IsStriped bool `json:"is_striped,omitempty"` + // IsCompact requests smaller table-cell padding. + IsCompact bool `json:"is_compact,omitempty"` // Since: Bot API 10.3 // Caption contains the media or block caption. Caption *RichText `json:"caption,omitempty"` } @@ -339,6 +345,20 @@ type InputRichBlockMap struct { func (InputRichBlockMap) isInputRichBlock() {} +// InputRichBlockButtons is a block containing a list of buttons that are shown in one row. +// +// Since: Bot API 10.3 +type InputRichBlockButtons struct { + // Type is always buttons. + Type InputRichType `json:"type"` + // Buttons contains 1-8 buttons in one row. + Buttons []RichMessageButton `json:"buttons"` + // Align optionally selects left, center, or right alignment. + Align RichBlockButtonAlign `json:"align,omitempty"` +} + +func (InputRichBlockButtons) isInputRichBlock() {} + // InputRichBlockAnimation is an animation block corresponding to the HTML