feat(telegram): register bot commands asynchronously with retry on startup
This commit is contained in:
parent
041a40e804
commit
5db50ee5ca
3 changed files with 123 additions and 0 deletions
84
pkg/channels/telegram/command_registration.go
Normal file
84
pkg/channels/telegram/command_registration.go
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
package telegram
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/mymmrac/telego"
|
||||
|
||||
"github.com/sipeed/picoclaw/pkg/commands"
|
||||
"github.com/sipeed/picoclaw/pkg/logger"
|
||||
)
|
||||
|
||||
var commandRegistrationBackoff = []time.Duration{
|
||||
5 * time.Second,
|
||||
15 * time.Second,
|
||||
60 * time.Second,
|
||||
5 * time.Minute,
|
||||
10 * time.Minute,
|
||||
}
|
||||
|
||||
// RegisterCommands registers bot commands on Telegram platform.
|
||||
func (c *TelegramChannel) RegisterCommands(ctx context.Context, defs []commands.Definition) error {
|
||||
botCommands := make([]telego.BotCommand, 0, len(defs))
|
||||
for _, def := range defs {
|
||||
if def.Name == "" || def.Description == "" {
|
||||
continue
|
||||
}
|
||||
botCommands = append(botCommands, telego.BotCommand{
|
||||
Command: def.Name,
|
||||
Description: def.Description,
|
||||
})
|
||||
}
|
||||
|
||||
return c.bot.SetMyCommands(ctx, &telego.SetMyCommandsParams{
|
||||
Commands: botCommands,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *TelegramChannel) startCommandRegistration(ctx context.Context, defs []commands.Definition) {
|
||||
if len(defs) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
register := c.registerFunc
|
||||
if register == nil {
|
||||
register = c.RegisterCommands
|
||||
}
|
||||
|
||||
regCtx, cancel := context.WithCancel(ctx)
|
||||
c.commandRegCancel = cancel
|
||||
|
||||
go func() {
|
||||
attempt := 0
|
||||
for {
|
||||
err := register(regCtx, defs)
|
||||
if err == nil {
|
||||
logger.InfoCF("telegram", "Telegram commands registered", map[string]any{
|
||||
"count": len(defs),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
delay := commandRegistrationBackoff[minInt(attempt, len(commandRegistrationBackoff)-1)]
|
||||
logger.WarnCF("telegram", "Telegram command registration failed; will retry", map[string]any{
|
||||
"error": err.Error(),
|
||||
"retry_after": delay.String(),
|
||||
})
|
||||
attempt++
|
||||
|
||||
select {
|
||||
case <-regCtx.Done():
|
||||
return
|
||||
case <-time.After(delay):
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func minInt(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
30
pkg/channels/telegram/command_registration_test.go
Normal file
30
pkg/channels/telegram/command_registration_test.go
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
package telegram
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/sipeed/picoclaw/pkg/commands"
|
||||
)
|
||||
|
||||
func TestStartCommandRegistration_DoesNotBlock(t *testing.T) {
|
||||
ch := &TelegramChannel{}
|
||||
started := make(chan struct{}, 1)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
ch.registerFunc = func(context.Context, []commands.Definition) error {
|
||||
started <- struct{}{}
|
||||
return errors.New("temporary failure")
|
||||
}
|
||||
|
||||
ch.startCommandRegistration(ctx, []commands.Definition{{Name: "help"}})
|
||||
|
||||
select {
|
||||
case <-started:
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("registration did not start asynchronously")
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ import (
|
|||
|
||||
"github.com/sipeed/picoclaw/pkg/bus"
|
||||
"github.com/sipeed/picoclaw/pkg/channels"
|
||||
"github.com/sipeed/picoclaw/pkg/commands"
|
||||
"github.com/sipeed/picoclaw/pkg/config"
|
||||
"github.com/sipeed/picoclaw/pkg/identity"
|
||||
"github.com/sipeed/picoclaw/pkg/logger"
|
||||
|
|
@ -47,6 +48,9 @@ type TelegramChannel struct {
|
|||
chatIDs map[string]int64
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
|
||||
registerFunc func(context.Context, []commands.Definition) error
|
||||
commandRegCancel context.CancelFunc
|
||||
}
|
||||
|
||||
func NewTelegramChannel(cfg *config.Config, bus *bus.MessageBus) (*TelegramChannel, error) {
|
||||
|
|
@ -141,6 +145,8 @@ func (c *TelegramChannel) Start(ctx context.Context) error {
|
|||
"username": c.bot.Username(),
|
||||
})
|
||||
|
||||
c.startCommandRegistration(c.ctx, commands.NewRegistry(commands.BuiltinDefinitions(c.config)).ForChannel("telegram"))
|
||||
|
||||
go bh.Start()
|
||||
|
||||
return nil
|
||||
|
|
@ -159,6 +165,9 @@ func (c *TelegramChannel) Stop(ctx context.Context) error {
|
|||
if c.cancel != nil {
|
||||
c.cancel()
|
||||
}
|
||||
if c.commandRegCancel != nil {
|
||||
c.commandRegCancel()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue