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.
|
// by temporary upstream API failures. Retry stops on success or channel shutdown.
|
||||||
go func() {
|
go func() {
|
||||||
attempt := 0
|
attempt := 0
|
||||||
|
timer := time.NewTimer(0)
|
||||||
|
if !timer.Stop() {
|
||||||
|
select {
|
||||||
|
case <-timer.C:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer timer.Stop()
|
||||||
for {
|
for {
|
||||||
err := register(regCtx, defs)
|
err := register(regCtx, defs)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
@ -64,15 +72,23 @@ func (c *TelegramChannel) startCommandRegistration(ctx context.Context, defs []c
|
||||||
|
|
||||||
delay := commandRegistrationBackoff[min(attempt, len(commandRegistrationBackoff)-1)]
|
delay := commandRegistrationBackoff[min(attempt, len(commandRegistrationBackoff)-1)]
|
||||||
logger.WarnCF("telegram", "Telegram command registration failed; will retry", map[string]any{
|
logger.WarnCF("telegram", "Telegram command registration failed; will retry", map[string]any{
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
"retry_after": delay.String(),
|
"retry_after": delay.String(),
|
||||||
})
|
})
|
||||||
attempt++
|
attempt++
|
||||||
|
|
||||||
|
if !timer.Stop() {
|
||||||
|
select {
|
||||||
|
case <-timer.C:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
timer.Reset(delay)
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-regCtx.Done():
|
case <-regCtx.Done():
|
||||||
return
|
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(),
|
"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
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -278,6 +278,11 @@ func (c *WhatsAppChannel) tryHandleCommand(
|
||||||
"error": res.Err.Error(),
|
"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
|
return res.Matched
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -422,6 +422,11 @@ func (c *WhatsAppNativeChannel) tryHandleCommand(
|
||||||
"error": res.Err.Error(),
|
"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
|
return res.Matched
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,8 @@ func (f DispatchFunc) Dispatch(ctx context.Context, req Request) Result {
|
||||||
return f(ctx, req)
|
return f(ctx, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var commandPrefixes = []string{"/", "!"}
|
||||||
|
|
||||||
// NewDispatcher binds the unified parser/executor flow to one command registry.
|
// NewDispatcher binds the unified parser/executor flow to one command registry.
|
||||||
func NewDispatcher(reg *Registry) *Dispatcher {
|
func NewDispatcher(reg *Registry) *Dispatcher {
|
||||||
return &Dispatcher{reg: reg}
|
return &Dispatcher{reg: reg}
|
||||||
|
|
@ -52,7 +54,7 @@ func (d *Dispatcher) Dispatch(ctx context.Context, req Request) Result {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, def := range d.reg.Definitions() {
|
for _, def := range d.reg.Definitions() {
|
||||||
if def.Name != cmdName && !contains(def.Aliases, cmdName) {
|
if !matchesCommand(def, cmdName) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if def.Handler == nil {
|
if def.Handler == nil {
|
||||||
|
|
@ -73,24 +75,53 @@ func firstToken(input string) string {
|
||||||
return parts[0]
|
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) {
|
func parseCommandName(input string) (string, bool) {
|
||||||
token := firstToken(input)
|
token := firstToken(input)
|
||||||
if token == "" || !strings.HasPrefix(token, "/") {
|
if token == "" {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
name := strings.TrimPrefix(token, "/")
|
name, ok := trimCommandPrefix(token)
|
||||||
|
if !ok {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
if i := strings.Index(name, "@"); i >= 0 {
|
if i := strings.Index(name, "@"); i >= 0 {
|
||||||
name = name[:i]
|
name = name[:i]
|
||||||
}
|
}
|
||||||
name = strings.TrimSpace(name)
|
name = normalizeCommandName(name)
|
||||||
if name == "" {
|
if name == "" {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
return name, true
|
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 {
|
func contains(items []string, target string) bool {
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
if item == target {
|
if item == target {
|
||||||
|
|
|
||||||
|
|
@ -59,3 +59,45 @@ func TestDispatcher_MatchTelegramMentionSyntax(t *testing.T) {
|
||||||
t.Fatalf("dispatch result = %+v, called=%v", res, called)
|
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