From 6975ab19459810c8392827125550033481bd3e22 Mon Sep 17 00:00:00 2001 From: mingmxren Date: Sun, 1 Mar 2026 02:26:11 +0800 Subject: [PATCH] feat(commands): add dispatcher and request/result contracts --- pkg/commands/definition.go | 1 + pkg/commands/dispatcher.go | 59 +++++++++++++++++++++++++++++++++ pkg/commands/dispatcher_test.go | 28 ++++++++++++++++ 3 files changed, 88 insertions(+) create mode 100644 pkg/commands/dispatcher.go create mode 100644 pkg/commands/dispatcher_test.go diff --git a/pkg/commands/definition.go b/pkg/commands/definition.go index f1f67bcb8..7b555ac3a 100644 --- a/pkg/commands/definition.go +++ b/pkg/commands/definition.go @@ -6,4 +6,5 @@ type Definition struct { Usage string Aliases []string Channels []string + Handler Handler } diff --git a/pkg/commands/dispatcher.go b/pkg/commands/dispatcher.go new file mode 100644 index 000000000..21a5a587f --- /dev/null +++ b/pkg/commands/dispatcher.go @@ -0,0 +1,59 @@ +package commands + +import ( + "context" + "strings" +) + +type Handler func(ctx context.Context, req Request) error + +type Request struct { + Channel string + ChatID string + SenderID string + Text string + MessageID string +} + +type Result struct { + Matched bool + Command string + Err error +} + +type Dispatcher struct { + reg *Registry +} + +func NewDispatcher(reg *Registry) *Dispatcher { + return &Dispatcher{reg: reg} +} + +func (d *Dispatcher) Dispatch(ctx context.Context, req Request) Result { + token := firstToken(req.Text) + if token == "" { + return Result{Matched: false} + } + + cmdName := strings.TrimPrefix(token, "/") + for _, def := range d.reg.ForChannel(req.Channel) { + if def.Name != cmdName { + continue + } + if def.Handler == nil { + return Result{Matched: true, Command: def.Name} + } + err := def.Handler(ctx, req) + return Result{Matched: true, Command: def.Name, Err: err} + } + + return Result{Matched: false} +} + +func firstToken(input string) string { + parts := strings.Fields(strings.TrimSpace(input)) + if len(parts) == 0 { + return "" + } + return parts[0] +} diff --git a/pkg/commands/dispatcher_test.go b/pkg/commands/dispatcher_test.go new file mode 100644 index 000000000..0e1c1c1c0 --- /dev/null +++ b/pkg/commands/dispatcher_test.go @@ -0,0 +1,28 @@ +package commands + +import ( + "context" + "testing" +) + +func TestDispatcher_MatchSlashCommand(t *testing.T) { + called := false + defs := []Definition{ + { + Name: "help", + Handler: func(context.Context, Request) error { + called = true + return nil + }, + }, + } + d := NewDispatcher(NewRegistry(defs)) + + res := d.Dispatch(context.Background(), Request{ + Channel: "telegram", + Text: "/help", + }) + if !res.Matched || !called || res.Err != nil { + t.Fatalf("dispatch result = %+v, called=%v", res, called) + } +}