mirror of
https://github.com/lukaszraczylo/go-telegram.git
synced 2026-06-05 22:43:59 +00:00
0ee539e991
Move the three conventional Values keys ("command", "command_args", "regex_match") to typed fields on Context. Router and group routing write the fields directly; the Values map is allocated lazily via the new Set method and reserved for user-defined custom keys.
Allocation impact (M4 Max, b.Loop()):
DispatchCommand: 5 allocs/op -> 1, 153ns -> 69ns (-55%)
DispatchTextRegex: 5 allocs/op -> 2, 181ns -> 107ns (-41%)
DispatchFilter: 2 allocs/op -> 1, 32ns -> 19ns (-41%)
NewContext: 5.79ns -> 1.60ns
Trade-off: Context struct grew from ~48B to ~96B (three new fields), so filter-only paths pay ~50B more per dispatch. Command/regex paths save ~320B + 4 allocs each, which dominates for typical bot workloads.
Handlers reading c.Values["command"], c.Values["command_args"], or c.Values["regex_match"] now get nil; the typed fields c.Command, c.CommandArgs, c.RegexMatch are the new accessors. Custom keys still work via c.Set(k, v) and c.Values[k].
146 lines
3.6 KiB
Go
146 lines
3.6 KiB
Go
// Package main demonstrates multi-page inline keyboard navigation.
|
|
//
|
|
// /list shows page 1 of a sample item list with [« Prev] [Next »] buttons.
|
|
// Tapping a button edits the message in-place to show the next/previous page.
|
|
// Page state is encoded directly in callback data ("page:<n>") — no server-side
|
|
// session needed.
|
|
//
|
|
// TELEGRAM_BOT_TOKEN=xxx go run ./examples/pagination
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"os/signal"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
|
|
"github.com/lukaszraczylo/go-telegram/api"
|
|
"github.com/lukaszraczylo/go-telegram/client"
|
|
"github.com/lukaszraczylo/go-telegram/dispatch"
|
|
"github.com/lukaszraczylo/go-telegram/transport"
|
|
)
|
|
|
|
const itemsPerPage = 5
|
|
|
|
// sampleItems is the list being paginated.
|
|
var sampleItems = []string{
|
|
"Alpha", "Bravo", "Charlie", "Delta", "Echo",
|
|
"Foxtrot", "Golf", "Hotel", "India", "Juliet",
|
|
"Kilo", "Lima", "Mike", "November", "Oscar",
|
|
"Papa", "Quebec", "Romeo", "Sierra", "Tango",
|
|
}
|
|
|
|
// renderPage builds the message text and keyboard for the given page number.
|
|
func renderPage(items []string, page int) (string, *api.InlineKeyboardMarkup) {
|
|
start := page * itemsPerPage
|
|
if start >= len(items) {
|
|
start = 0
|
|
page = 0
|
|
}
|
|
end := start + itemsPerPage
|
|
if end > len(items) {
|
|
end = len(items)
|
|
}
|
|
|
|
var sb strings.Builder
|
|
fmt.Fprintf(&sb, "Items (page %d of %d):\n\n", page+1, totalPages(len(items)))
|
|
for i := start; i < end; i++ {
|
|
fmt.Fprintf(&sb, "%d. %s\n", i+1, items[i])
|
|
}
|
|
|
|
var btns []api.InlineKeyboardButton
|
|
if page > 0 {
|
|
btns = append(btns, api.InlineKeyboardButton{
|
|
Text: "« Prev",
|
|
CallbackData: fmt.Sprintf("page:%d", page-1),
|
|
})
|
|
}
|
|
if end < len(items) {
|
|
btns = append(btns, api.InlineKeyboardButton{
|
|
Text: "Next »",
|
|
CallbackData: fmt.Sprintf("page:%d", page+1),
|
|
})
|
|
}
|
|
|
|
var markup *api.InlineKeyboardMarkup
|
|
if len(btns) > 0 {
|
|
markup = &api.InlineKeyboardMarkup{
|
|
InlineKeyboard: [][]api.InlineKeyboardButton{btns},
|
|
}
|
|
}
|
|
return sb.String(), markup
|
|
}
|
|
|
|
func totalPages(total int) int {
|
|
pages := total / itemsPerPage
|
|
if total%itemsPerPage != 0 {
|
|
pages++
|
|
}
|
|
return pages
|
|
}
|
|
|
|
func main() {
|
|
token := os.Getenv("TELEGRAM_BOT_TOKEN")
|
|
if token == "" {
|
|
log.Fatal("TELEGRAM_BOT_TOKEN required")
|
|
}
|
|
|
|
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
|
defer stop()
|
|
|
|
bot := client.New(token,
|
|
client.WithHTTPClient(client.NewRetryDoer(client.NewDefaultHTTPDoer())),
|
|
)
|
|
|
|
router := dispatch.New(bot)
|
|
|
|
// /list — send page 0.
|
|
router.OnCommand("/list", func(c *dispatch.Context, m *api.Message) error {
|
|
text, markup := renderPage(sampleItems, 0)
|
|
_, err := api.SendMessage(c.Ctx, c.Bot, &api.SendMessageParams{
|
|
ChatID: api.ChatIDFromInt(m.Chat.ID),
|
|
Text: text,
|
|
ReplyMarkup: markup,
|
|
})
|
|
return err
|
|
})
|
|
|
|
// page:<n> callbacks — edit message in-place.
|
|
router.OnCallback(`^page:(\d+)$`, func(c *dispatch.Context, q *api.CallbackQuery) error {
|
|
page, _ := strconv.Atoi(c.RegexMatch[1])
|
|
|
|
// Acknowledge the tap first.
|
|
_, _ = api.AnswerCallbackQuery(c.Ctx, c.Bot, &api.AnswerCallbackQueryParams{
|
|
CallbackQueryID: q.ID,
|
|
})
|
|
|
|
if q.Message == nil {
|
|
return nil
|
|
}
|
|
msg, ok := q.Message.(*api.Message)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
text, markup := renderPage(sampleItems, page)
|
|
chatID := api.ChatIDFromInt(msg.Chat.ID)
|
|
mid := msg.MessageID
|
|
_, err := api.EditMessageText(c.Ctx, c.Bot, &api.EditMessageTextParams{
|
|
ChatID: &chatID,
|
|
MessageID: &mid,
|
|
Text: text,
|
|
ReplyMarkup: markup,
|
|
})
|
|
return err
|
|
})
|
|
|
|
poller := transport.NewLongPoller(bot)
|
|
if err := router.Run(ctx, poller); err != nil && err != context.Canceled {
|
|
log.Printf("router exited: %v", err)
|
|
}
|
|
}
|