Skip to content
This repository was archived by the owner on May 14, 2026. It is now read-only.

Dev#2

Merged
pageton merged 37 commits into
betafrom
dev
Feb 19, 2026
Merged

Dev#2
pageton merged 37 commits into
betafrom
dev

Conversation

@pageton

@pageton pageton commented Feb 19, 2026

Copy link
Copy Markdown
Owner

No description provided.

outtime and others added 30 commits January 12, 2026 12:30
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
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.
@pageton pageton merged commit 01cace8 into beta Feb 19, 2026
0 of 3 checks passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants