From a424efb7344fa39c94f53fb1e4da0e78645d60b9 Mon Sep 17 00:00:00 2001 From: yoworu Date: Sun, 27 Sep 2026 17:24:28 +0700 Subject: [PATCH 1/2] feat(button): add inline button style options --- markup.go | 29 ++++++++++++++++++++++------- markup_to_tg.go | 33 +++++++++++++++++++++++++++------ 2 files changed, 49 insertions(+), 13 deletions(-) diff --git a/markup.go b/markup.go index 67ad30e..79be3f2 100644 --- a/markup.go +++ b/markup.go @@ -15,16 +15,31 @@ type WebAppInfo struct { URL string `json:"url"` } +// KeyboardButtonStyle defines the visual style of an inline keyboard button. +type KeyboardButtonStyle struct { + BgPrimary bool `json:"bg_primary,omitempty"` + BgDanger bool `json:"bg_danger,omitempty"` + BgSuccess bool `json:"bg_success,omitempty"` + Icon *int64 `json:"icon,omitempty"` +} + // InlineKeyboardButton is one button of an inline keyboard. Exactly one of the // optional action fields should be set. type InlineKeyboardButton struct { - Text string `json:"text"` - URL string `json:"url,omitempty"` - CallbackData string `json:"callback_data,omitempty"` - WebApp *WebAppInfo `json:"web_app,omitempty"` - SwitchInlineQuery *string `json:"switch_inline_query,omitempty"` - SwitchInlineQueryCurrentChat *string `json:"switch_inline_query_current_chat,omitempty"` - Pay bool `json:"pay,omitempty"` + Text string `json:"text"` + URL string `json:"url,omitempty"` + CallbackData string `json:"callback_data,omitempty"` + WebApp *WebAppInfo `json:"web_app,omitempty"` + Style *KeyboardButtonStyle `json:"style,omitempty"` + SwitchInlineQuery *string `json:"switch_inline_query,omitempty"` + SwitchInlineQueryCurrentChat *string `json:"switch_inline_query_current_chat,omitempty"` + Pay bool `json:"pay,omitempty"` +} + +func (s *InlineKeyboardButton) Styled(style *KeyboardButtonStyle) InlineKeyboardButton { + s.Style = style + + return *s } // InlineKeyboardMarkup is an inline keyboard that appears beneath a message. diff --git a/markup_to_tg.go b/markup_to_tg.go index 13ab22f..316182a 100644 --- a/markup_to_tg.go +++ b/markup_to_tg.go @@ -71,19 +71,40 @@ func replyMarkupToTg(m ReplyMarkup) (tg.ReplyMarkupClass, error) { } func inlineButtonToTg(btn InlineKeyboardButton) (tg.KeyboardButtonClass, error) { + var opts []markup.StyleOption + + if btn.Style != nil { + s := btn.Style + if s.Icon != nil { + opts = append(opts, markup.StyleIcon(*s.Icon)) + } + + if s.BgDanger { + opts = append(opts, markup.StyleBgDanger()) + } + + if s.BgPrimary { + opts = append(opts, markup.StyleBgPrimary()) + } + + if s.BgSuccess { + opts = append(opts, markup.StyleBgSuccess()) + } + } + switch { case btn.URL != "": - return markup.URL(btn.Text, btn.URL), nil + return markup.URL(btn.Text, btn.URL, opts...), nil case btn.CallbackData != "": - return markup.Callback(btn.Text, []byte(btn.CallbackData)), nil + return markup.Callback(btn.Text, []byte(btn.CallbackData), opts...), nil case btn.WebApp != nil: - return markup.WebView(btn.Text, btn.WebApp.URL), nil + return markup.WebView(btn.Text, btn.WebApp.URL, opts...), nil case btn.SwitchInlineQuery != nil: - return markup.SwitchInline(btn.Text, *btn.SwitchInlineQuery, false), nil + return markup.SwitchInline(btn.Text, *btn.SwitchInlineQuery, false, opts...), nil case btn.SwitchInlineQueryCurrentChat != nil: - return markup.SwitchInline(btn.Text, *btn.SwitchInlineQueryCurrentChat, true), nil + return markup.SwitchInline(btn.Text, *btn.SwitchInlineQueryCurrentChat, true, opts...), nil case btn.Pay: - return markup.Buy(btn.Text), nil + return markup.Buy(btn.Text, opts...), nil default: return nil, &Error{Code: 400, Description: "Bad Request: text buttons are unallowed in the inline keyboard"} } From 705c1e650dde42c216bab0a8feef4c1300db907e Mon Sep 17 00:00:00 2001 From: yoworu Date: Sat, 24 Oct 2026 00:07:57 +0700 Subject: [PATCH 2/2] feat(pool): add bot options --- pool/pool.go | 41 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/pool/pool.go b/pool/pool.go index 74e48e6..3bf6394 100644 --- a/pool/pool.go +++ b/pool/pool.go @@ -40,6 +40,12 @@ type Options struct { // IdleTimeout shuts down a bot that has not been borrowed for this long. // Zero disables idle collection (RunGC then does nothing). IdleTimeout time.Duration + + // BotOptions configures each bot individually. + BotOptions func(token string) botapi.Options + + // Configure registers handlers before Bot.Run. + Configure func(token string, bot *botapi.Bot) error } // Pool is a concurrency-safe set of bots keyed by token. @@ -138,13 +144,32 @@ func (p *Pool) start(tok Token) (*managed, error) { m := &managed{ready: make(chan struct{}), db: db} - bot, err := botapi.New(tok.String(), botapi.Options{ + botOptions := botapi.Options{ AppID: p.opt.AppID, AppHash: p.opt.AppHash, Logger: botLog, Storage: store, - OnStart: func(context.Context) { m.markReady(nil) }, - }) + } + + if p.opt.BotOptions != nil { + botOptions = p.opt.BotOptions(tok.String()) + botOptions.AppID = p.opt.AppID + botOptions.AppHash = p.opt.AppHash + botOptions.Logger = botLog + botOptions.Storage = store + } + + onStart := botOptions.OnStart + + botOptions.OnStart = func(ctx context.Context) { + if onStart != nil { + onStart(ctx) + } + + m.markReady(nil) + } + + bot, err := botapi.New(tok.String(), botOptions) if err != nil { if db != nil { _ = db.Close() @@ -153,6 +178,16 @@ func (p *Pool) start(tok Token) (*managed, error) { return nil, err } + if p.opt.Configure != nil { + if err := p.opt.Configure(tok.String(), bot); err != nil { + if db != nil { + _ = db.Close() + } + + return nil, errors.Wrap(err, "configure bot") + } + } + m.bot = bot ctx, cancel := context.WithCancel(context.Background())