fix(commands): address review feedback on parsing and retry flow
This commit is contained in:
parent
554aa40d09
commit
b4471d6394
6 changed files with 111 additions and 7 deletions
|
|
@ -53,6 +53,14 @@ func (c *TelegramChannel) startCommandRegistration(ctx context.Context, defs []c
|
|||
// by temporary upstream API failures. Retry stops on success or channel shutdown.
|
||||
go func() {
|
||||
attempt := 0
|
||||
timer := time.NewTimer(0)
|
||||
if !timer.Stop() {
|
||||
select {
|
||||
case <-timer.C:
|
||||
default:
|
||||
}
|
||||
}
|
||||
defer timer.Stop()
|
||||
for {
|
||||
err := register(regCtx, defs)
|
||||
if err == nil {
|
||||
|
|
@ -64,15 +72,23 @@ func (c *TelegramChannel) startCommandRegistration(ctx context.Context, defs []c
|
|||
|
||||
delay := commandRegistrationBackoff[min(attempt, len(commandRegistrationBackoff)-1)]
|
||||
logger.WarnCF("telegram", "Telegram command registration failed; will retry", map[string]any{
|
||||
"error": err.Error(),
|
||||
"error": err.Error(),
|
||||
"retry_after": delay.String(),
|
||||
})
|
||||
attempt++
|
||||
|
||||
if !timer.Stop() {
|
||||
select {
|
||||
case <-timer.C:
|
||||
default:
|
||||
}
|
||||
}
|
||||
timer.Reset(delay)
|
||||
|
||||
select {
|
||||
case <-regCtx.Done():
|
||||
return
|
||||
case <-time.After(delay):
|
||||
case <-timer.C:
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
|
|
|||
|
|
@ -53,6 +53,11 @@ func (c *TelegramChannel) dispatchCommand(ctx context.Context, message telego.Me
|
|||
"error": res.Err.Error(),
|
||||
})
|
||||
}
|
||||
if res.Matched && !res.Handled {
|
||||
logger.DebugCF("telegram", "Command matched without handler; passing to normal flow", map[string]any{
|
||||
"command": res.Command,
|
||||
})
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -278,6 +278,11 @@ func (c *WhatsAppChannel) tryHandleCommand(
|
|||
"error": res.Err.Error(),
|
||||
})
|
||||
}
|
||||
if res.Matched && !res.Handled {
|
||||
logger.DebugCF("whatsapp", "Command matched without handler; passing to normal flow", map[string]any{
|
||||
"command": res.Command,
|
||||
})
|
||||
}
|
||||
return res.Matched
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -422,6 +422,11 @@ func (c *WhatsAppNativeChannel) tryHandleCommand(
|
|||
"error": res.Err.Error(),
|
||||
})
|
||||
}
|
||||
if res.Matched && !res.Handled {
|
||||
logger.DebugCF("whatsapp", "Command matched without handler; passing to normal flow", map[string]any{
|
||||
"command": res.Command,
|
||||
})
|
||||
}
|
||||
return res.Matched
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@ func (f DispatchFunc) Dispatch(ctx context.Context, req Request) Result {
|
|||
return f(ctx, req)
|
||||
}
|
||||
|
||||
var commandPrefixes = []string{"/", "!"}
|
||||
|
||||
// NewDispatcher binds the unified parser/executor flow to one command registry.
|
||||
func NewDispatcher(reg *Registry) *Dispatcher {
|
||||
return &Dispatcher{reg: reg}
|
||||
|
|
@ -52,7 +54,7 @@ func (d *Dispatcher) Dispatch(ctx context.Context, req Request) Result {
|
|||
}
|
||||
|
||||
for _, def := range d.reg.Definitions() {
|
||||
if def.Name != cmdName && !contains(def.Aliases, cmdName) {
|
||||
if !matchesCommand(def, cmdName) {
|
||||
continue
|
||||
}
|
||||
if def.Handler == nil {
|
||||
|
|
@ -73,24 +75,53 @@ func firstToken(input string) string {
|
|||
return parts[0]
|
||||
}
|
||||
|
||||
// parseCommandName accepts both "/name" and "/name@bot", then normalizes to "name".
|
||||
// parseCommandName accepts "/name", "!name", and Telegram's "/name@bot", then
|
||||
// normalizes to lowercase command names.
|
||||
func parseCommandName(input string) (string, bool) {
|
||||
token := firstToken(input)
|
||||
if token == "" || !strings.HasPrefix(token, "/") {
|
||||
if token == "" {
|
||||
return "", false
|
||||
}
|
||||
|
||||
name := strings.TrimPrefix(token, "/")
|
||||
name, ok := trimCommandPrefix(token)
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
if i := strings.Index(name, "@"); i >= 0 {
|
||||
name = name[:i]
|
||||
}
|
||||
name = strings.TrimSpace(name)
|
||||
name = normalizeCommandName(name)
|
||||
if name == "" {
|
||||
return "", false
|
||||
}
|
||||
return name, true
|
||||
}
|
||||
|
||||
func trimCommandPrefix(token string) (string, bool) {
|
||||
for _, prefix := range commandPrefixes {
|
||||
if strings.HasPrefix(token, prefix) {
|
||||
return strings.TrimPrefix(token, prefix), true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func normalizeCommandName(name string) string {
|
||||
return strings.ToLower(strings.TrimSpace(name))
|
||||
}
|
||||
|
||||
func matchesCommand(def Definition, cmdName string) bool {
|
||||
if normalizeCommandName(def.Name) == cmdName {
|
||||
return true
|
||||
}
|
||||
for _, alias := range def.Aliases {
|
||||
if normalizeCommandName(alias) == cmdName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func contains(items []string, target string) bool {
|
||||
for _, item := range items {
|
||||
if item == target {
|
||||
|
|
|
|||
|
|
@ -59,3 +59,45 @@ func TestDispatcher_MatchTelegramMentionSyntax(t *testing.T) {
|
|||
t.Fatalf("dispatch result = %+v, called=%v", res, called)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDispatcher_MatchBangPrefix(t *testing.T) {
|
||||
called := false
|
||||
d := NewDispatcher(NewRegistry([]Definition{
|
||||
{
|
||||
Name: "help",
|
||||
Handler: func(context.Context, Request) error {
|
||||
called = true
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
res := d.Dispatch(context.Background(), Request{
|
||||
Channel: "telegram",
|
||||
Text: "!help",
|
||||
})
|
||||
if !res.Matched || !res.Handled || !called || res.Err != nil {
|
||||
t.Fatalf("dispatch result = %+v, called=%v", res, called)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDispatcher_CommandMatchingIsCaseInsensitive(t *testing.T) {
|
||||
called := false
|
||||
d := NewDispatcher(NewRegistry([]Definition{
|
||||
{
|
||||
Name: "show",
|
||||
Handler: func(context.Context, Request) error {
|
||||
called = true
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
res := d.Dispatch(context.Background(), Request{
|
||||
Channel: "telegram",
|
||||
Text: "/SHOW",
|
||||
})
|
||||
if !res.Matched || !res.Handled || !called || res.Err != nil {
|
||||
t.Fatalf("dispatch result = %+v, called=%v", res, called)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue