This repository was archived by the owner on May 14, 2026. It is now read-only.
Conversation
ForwardMessages previously used GetInputPeerClassFromID, which only looks up peers already cached in local storage. If either the source or destination chat had not been seen before, the call would fail with ErrPeerNotFound even when the peer is perfectly valid. Switch to ResolveInputPeerByID, which checks storage first and falls back to an API resolve (ChannelsGetChannels / UsersGetUsers) when the peer is missing. This matches the behavior of SendMessage, SendMedia, EditMessage, and other message functions that already resolve on miss.
Add a LevelOff sentinel above LevelFatal in the Level enum. Because emit() short-circuits when level < MinLevel, setting MinLevel to LevelOff guarantees every log call is a no-op with zero allocation. Expose this through a new Nop() constructor that returns a Logger configured with LevelOff. This gives callers an explicit, discoverable way to silence all log output without having to know the internal level constants.
Previously, both NewClient and NewNativeDispatcher fell back to Default(), which writes INFO-level output to stderr. This produced unexpected log noise for users who simply omitted LogConfig, expecting quiet operation. Replace the Default() fallback with Nop() so the framework is silent by default. Users who want logging must now explicitly pass a LogConfig, making log output strictly opt-in.
Add SentCodeType for authflow
# Conflicts: # client.go
Add two new button types to the keyboard builder to cover previously unsupported Telegram inline keyboard features: - Game(text): launches an HTML5 game registered via @Botfather. Useful for bots that serve games directly within Telegram's interface. - Buy(text): initiates a payment flow through Telegram's payment API. Required for bots that sell goods or services using integrated payment providers. Both methods follow the existing builder pattern and return the KeyboardBuilder for chaining. The Build() documentation is updated to list these new types alongside existing ones.
Bump direct and transitive dependencies to their latest versions.
Notable upgrades:
- gotd/td v0.137.0 -> v0.139.0 (adds KeyboardButtonStyle support,
new API layer types)
- gotd/contrib v0.21.0 -> v0.21.1
- opentelemetry v1.39.0 -> v1.40.0
- klauspost/compress v1.18.3 -> v1.18.4
- modernc.org/sqlite v1.44.1 -> v1.45.0
- golang.org/x/{crypto,net,sys,text,mod,tools,time}: latest patch
releases
The gotd/td bump is required for upcoming keyboard button styling
support which depends on the new KeyboardButtonStyle type introduced
in v0.139.0.
… and Icon Telegram introduced button styles via KeyboardButtonStyle in a recent API layer, allowing per-button background colors and custom emoji icons. This commit exposes that capability through the fluent keyboard builder. New chainable methods on KeyboardBuilder: - Primary(): highlights the main action (e.g., confirm, submit) - Danger(): marks destructive actions (e.g., delete, ban) - Success(): marks positive outcomes (e.g., approve, complete) - Icon(documentID): attaches a custom emoji icon to the button Each method modifies the style of the most recently added button and returns the builder for continued chaining. An internal styleSetter interface abstracts over gotd/td's concrete button types so that styling works uniformly regardless of button kind. Buttons that do not implement the interface are silently skipped, keeping the API safe to use.
…Switch Two small improvements to the keyboard builder internals: 1. Pre-allocate row and rows slices with reasonable initial capacities (4 buttons per row, 3 rows per keyboard). Most Telegram keyboards are small, so this avoids the first few slice growths in the common case while keeping the memory footprint negligible. 2. Set a default PeerTypes on Switch buttons when a query is provided or samePeer is false. Without an explicit PeerTypes, some Telegram clients may display an empty peer picker. Defaulting to PM ensures consistent behavior across platforms. Minor doc wording adjustments in the Switch godoc to improve clarity.
…HTML parser Telegram clients emit two additional HTML tag formats for spoilers and custom emojis that the parser did not previously handle: 1. <span class="tg-spoiler">: an alternative to <tg-spoiler> used by some Telegram clients and the Bot API when formatting spoiler text. The parser now recognizes <span> as a supported tag and checks its class attribute — only class="tg-spoiler" produces a spoiler entity; all other <span> variants are treated as no-ops rather than being left as raw HTML in the output. 2. <tg-emoji emoji-id="...">: an alternative to <emoji id="..."> used by newer Telegram clients for custom emoji. The parser now maps this tag to MessageEntityCustomEmoji, reading the document ID from the emoji-id attribute. Both additions preserve zero-length entity handling so that emoji placeholders work correctly even when the visible text is trimmed. Tests cover the new span spoiler variants (with tg-spoiler class, with other classes, and with no class) and tg-emoji tags (standalone and inline with surrounding text). The unsupported-tag test is updated to use <p> instead of <span> since span is now a recognized tag.
Add the ability for the current user to leave a channel or group chat, exposed at three API layers to match the existing pattern for chat member operations (AddChatMembers, PromoteChatMember, etc.): - functions.LeaveChannel: low-level function that dispatches to ChannelsLeaveChannel for channels or MessagesDeleteChatUser for basic groups, based on the InputPeer type. - adapter.Context.Leave: mid-level method that resolves a chat ID to its InputPeer, validates that the target is actually a chat or channel (returns ErrNotChat otherwise), and delegates to LeaveChannel. - generic.Leave: generic helper that accepts ChatUnion (int64, string, or peer object) and resolves it before calling ctx.Leave, consistent with all other generic helpers in gen_cu.go. Also adds a missing default return in AddChatMembers that was surfaced by the gotd/td v0.139.0 upgrade, which made the compiler stricter about exhaustive type switch returns.
The middleware example was using the unexported gotg.ClientType struct literal directly, which broke after an earlier refactor that made the struct type unexported in favor of constructor functions. Replace it with the gotg.AsBot() helper to match the current public API and stay consistent with all other bot examples in the repository.
…implementations
The storage layer was tightly coupled to GORM, making it impossible to
use alternative databases without wrapping them in a GORM dialector.
This became a practical limitation for deployments that rely on Redis
for shared state across instances, or PostgreSQL via pgx for better
connection pooling, or MongoDB for document-oriented storage.
Define a storage.Adapter interface that abstracts all persistence
operations behind a common contract: session read/write, peer CRUD,
conversation state management, schema migration, and connection
lifecycle. PeerStorage now holds an Adapter instead of calling GORM
directly, and all internal consumers (peers, sessions, conversation
state) are updated to route through it.
Two internal compatibility adapters bridge the gap for existing users:
gormAdapterCompat wraps *gorm.DB to satisfy Adapter for the current
SqlSession path, and memoryAdapterCompat provides a thread-safe
in-memory implementation. The public NewPeerStorage constructor
retains its original signature and behavior.
Five standalone backend packages are included:
- storage/backend/gormdb: configurable GORM adapter with connection
pool tuning, usable with any GORM dialector (SQLite, PostgreSQL,
MySQL)
- storage/backend/memory: lightweight in-memory adapter for testing
and ephemeral bots
- storage/backend/pgxdb: native PostgreSQL adapter using pgx/v5
connection pools with proper upsert queries and indexed schemas
- storage/backend/redisdb: Redis adapter for distributed deployments,
using pipelined writes and a secondary index for username lookups
- storage/backend/mongodb: MongoDB adapter with upsert-based writes
and sparse indexes on username and conversation fields
NewPeerStorageWithAdapter is exposed as the primary constructor for
users who want to bring their own backend. GetAdapter() is available
for middleware or tooling that needs access to the underlying store.
Previously, creating a session required passing a GORM dialector, which forced every storage backend to be wrapped in a GORM-compatible layer even when using native drivers like pgx or Redis. Add WithAdapter() as a new session constructor that accepts any storage.Adapter directly. This pairs with the pluggable backend system introduced in the storage refactor, allowing sessions like: session.WithAdapter(pgxdb.New(pool)) session.WithAdapter(redisdb.New(rdb)) session.WithAdapter(myCustomAdapter) Internally, a sessionNameAdapter type carries the adapter through the session loading pipeline, and NewSessionStorage detects it to create PeerStorage via NewPeerStorageWithAdapter instead of the GORM-specific path. The existing SqlSession and SimpleSession constructors are unchanged.
…method The callback query filter set was limited to Prefix, Suffix, and Equal, which made it difficult to match complex callback data patterns like "btn_settings_42" or versioned payloads. The business callback query filters had even fewer options, with only ConnectionID and FromUserID. Add Regex and Contains filters to callbackQueryFilters. Regex compiles the pattern once at registration time and matches against the raw byte data, avoiding per-callback string conversion. Contains uses bytes.Contains for the same reason. Bring business callback query filters to parity with regular callbacks by adding Prefix, Suffix, Equal, Contains, and Regex. All byte-level operations follow the same optimization pattern established in the existing callback filters. For message filters, the standalone Document function was a package-level helper that only checked whether MessageMediaDocument was present. It did not distinguish between actual file documents and media types like video, audio, sticker, or animation that also use MessageMediaDocument internally. Promote it to a method on messageFilters so it is accessible via filters.Message.Document, and add attribute-based exclusion logic to return true only for generic file documents. Add DocumentFilename as a regex-based filter that matches against the filename attribute of document messages, useful for filtering by file extension or naming patterns.
Telegram bots can register slash commands that appear in the client command menu, split by chat type (group vs. private). The raw API for this (bots.setBotCommands) requires building tg.BotCommand slices and selecting the correct scope, which is repetitive and error-prone. Add CommandRegistry with a fluent builder API that handles command registration, clearing, and scope selection. Commands can be added per chat type via AddGroup/AddPrivate (with variadic BotCmd structs) or AddGroupMap/AddPrivateMap (for configuration-driven setups). WithLangCode supports multi-language command sets. Convenience methods on Context (RegisterBotCommands, SetBotCommands) allow registering commands directly from within handlers without manually threading the raw client and context. Register sends the accumulated commands to Telegram in a single call per scope. MustRegister wraps it with a panic for use during bot initialization. Clear removes all registered commands for both scopes.
Required by the new storage backend packages: - github.com/jackc/pgx/v5 for the pgxdb PostgreSQL adapter - github.com/redis/go-redis/v9 for the redisdb distributed adapter - go.mongodb.org/mongo-driver/v2 for the mongodb document adapter These are indirect to the core module and only pulled in when a consumer imports the corresponding storage/backend/* package.
Two new examples demonstrate recently added features. bot-commands shows how to use the CommandRegistry builder to register separate command sets for group and private chats, including map-based registration for configuration-driven setups and multi-language command registration via WithLangCode. It also demonstrates registering commands from within a handler using the Context convenience methods. custom-adapter provides a complete reference implementation of a custom storage.Adapter backed by a single JSON file. The JsonAdapter covers all interface methods (sessions, peers, conversation state) with proper locking and flush-on-write semantics. A test suite verifies session persistence, peer CRUD, conversation state lifecycle, and data durability across adapter restarts. This serves as a starting point for anyone implementing their own backend (BoltDB, BadgerDB, DynamoDB, etc.).
…covery Rewrote the i18n locale loading system to support directory-based layouts (e.g. en/messages.yaml) alongside flat files (e.g. en.yaml). The loader now auto-discovers locale directories from embedded filesystems, removing the need to manually register each locale. Simplified the adapter layer by replacing reflect-based type inspection with direct type assertions. Restructured the example bot locales to use the new directory layout.
…dcoded HTML Replaced all hardcoded HTML parse mode references in adapter message methods with the configurable DefaultParseMode from the update context. Added defaultParseMode field wiring in client.go so users can set their preferred parse mode globally rather than being locked to HTML.
Rewrote the middleware chain handler to use a recursive run() function with a stack-local index instead of sharing a mutable index across goroutines. This eliminates the data race that occurred when multiple updates were processed concurrently through the same chain. Added comprehensive tests covering sequential execution, abort behavior, nested handler calls, and concurrent safety.
Changed NewPeerStorage, NewPeerStorageWithAdapter, and newInMemoryPeerStorage to return (*PeerStorage, error) instead of calling log.Panicln on failure. Updated all call sites in session_maker and conv/manager_test to handle the new error returns properly. This prevents the application from crashing on storage initialization failures and allows callers to handle errors gracefully.
…through write channel
Fixed GetPeerByUsername and cachePeers to return nil instead of an
empty &Peer{} struct when no matching peer is found. This prevents
callers from silently operating on zero-value peers. Removed the dead
verification block from SetPeerLanguage and ensured it routes through
the write channel like other mutation methods.
…face Added WaitPending() to drain in-flight update handlers and CloseDCPools() for DC connection cleanup during shutdown. Wired both into the client Stop() method for proper graceful shutdown sequencing. Extended the Dispatcher interface with SetMaxConcurrentUpdates and ConvManager methods to match the concrete implementation. Added dispatcher tests covering initialization, handler registration, and error group lifecycle.
Removed the github.com/pkg/errors dependency entirely and replaced all usages with stdlib fmt.Errorf and %w verb for error wrapping. This reduces the dependency footprint while maintaining full error chain compatibility with errors.Is and errors.As from the standard library.
…ory leak Added a background cleanup goroutine to the rate limiter that periodically removes stale per-user buckets that have not been accessed within the configured window. Added a Stop() method to terminate the cleanup goroutine. Removed the custom min() helper in favor of the stdlib builtin.
Added RotatingFileWriter that automatically rotates log files when they exceed a configurable size threshold. Supports configurable maximum file size and number of backup files to retain. Integrated the rotation writer into the logging configuration with MaxSize and MaxBackups fields.
Added validation to truncate inline keyboard callback data that exceeds Telegram's 64-byte limit. This prevents silent API errors when callback data is too long, which would otherwise cause buttons to stop working without any visible error to the user.
Added .golangci.yml with curated linter settings appropriate for the project. Configures staticcheck, govet, errcheck, and other linters with sensible defaults and project-specific exclusions.
Include the Fluent (.ftl) translation files for English and Spanish in the new directory-based locale structure. These were part of the i18n rewrite but were missed during staging.
…ereference GetPeerByID can return nil when a peer is not found in storage. All call sites now guard against nil before accessing peer fields, preventing panics on missing or unresolved peers.
Previously, many functions (GetChatMembers, GetChatMember, GetChat, GetFullChat, GetUser, GetFullUser, GetChatInviteLink, DeleteMessages, PinMessage, UnPinMessage, UnPinAllMessages, GetMessages, GetUserProfilePhotos, TransferStarGift) would return ErrPeerNotFound immediately when a peer was missing from local storage. This caused failures in scenarios where the peer hadn't been cached yet, such as during UpdateChannelParticipant events (bot promoted to admin). Now these functions fall back to ResolveInputPeerByID which fetches the peer from the Telegram API and saves it to storage before giving up. This matches the behavior already used by SendMessage, EditMessage, and ForwardMessages. Also fix saveChatsPeers in the dispatcher to save min-flagged channels when no existing entry is present, preventing peer loss from update entities.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.