Core framework (Handler, Command, Default muxer) in core/ package. Command self-registration via init() with blank imports in cmds/. Context helpers (send, reply, download, upload) in context/ package. Permission system with GROUP_ONLY / PRIVATE_ONLY checks. WhatsApp client configured as Safari with history sync disabled.
140 lines
2.8 KiB
Go
140 lines
2.8 KiB
Go
package core
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"sync"
|
|
|
|
hc "neo/context"
|
|
|
|
"go.mau.fi/whatsmeow"
|
|
"go.mau.fi/whatsmeow/types/events"
|
|
)
|
|
|
|
type CommandFunc func(ctx *hc.Ctx)
|
|
|
|
type Command struct {
|
|
Name string
|
|
Aliases []string
|
|
Description string
|
|
Category string
|
|
Permissions []Permission
|
|
Run CommandFunc
|
|
}
|
|
|
|
// Default is the global command muxer. Each command registers itself
|
|
// via init() — see neo/cmds for the registration pattern.
|
|
var Default = New()
|
|
|
|
type Handler struct {
|
|
mu sync.RWMutex
|
|
commands map[string]*Command
|
|
prefixes []string
|
|
}
|
|
|
|
func New(prefixes ...string) *Handler {
|
|
if len(prefixes) == 0 {
|
|
prefixes = []string{"!", "/", "."}
|
|
}
|
|
return &Handler{
|
|
commands: make(map[string]*Command),
|
|
prefixes: prefixes,
|
|
}
|
|
}
|
|
|
|
func (h *Handler) Register(cmds ...*Command) {
|
|
h.mu.Lock()
|
|
defer h.mu.Unlock()
|
|
for _, cmd := range cmds {
|
|
if cmd.Name == "" {
|
|
panic("core: command name cannot be empty")
|
|
}
|
|
if cmd.Run == nil {
|
|
panic(fmt.Sprintf("core: command %q has no Run function", cmd.Name))
|
|
}
|
|
h.commands[cmd.Name] = cmd
|
|
for _, alias := range cmd.Aliases {
|
|
h.commands[alias] = cmd
|
|
}
|
|
}
|
|
}
|
|
|
|
func (h *Handler) GetCommands() []*Command {
|
|
h.mu.RLock()
|
|
defer h.mu.RUnlock()
|
|
seen := make(map[string]bool)
|
|
var result []*Command
|
|
for _, cmd := range h.commands {
|
|
if !seen[cmd.Name] {
|
|
seen[cmd.Name] = true
|
|
result = append(result, cmd)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (h *Handler) EventHandler(client *whatsmeow.Client) func(any) {
|
|
return func(rawEvt any) {
|
|
evt, ok := rawEvt.(*events.Message)
|
|
if !ok {
|
|
return
|
|
}
|
|
go h.processMessage(client, evt)
|
|
}
|
|
}
|
|
|
|
func (h *Handler) processMessage(client *whatsmeow.Client, evt *events.Message) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
fmt.Printf("[CMD] panic from %s: %v\n", formatSenderInfo(evt), r)
|
|
}
|
|
}()
|
|
|
|
text := hc.ParseMessageText(evt)
|
|
prefix, cmd, args, isCmd := parseCommand(text, h.prefixes)
|
|
if !isCmd {
|
|
return
|
|
}
|
|
|
|
h.mu.RLock()
|
|
command, exists := h.commands[cmd]
|
|
h.mu.RUnlock()
|
|
if !exists {
|
|
return
|
|
}
|
|
|
|
if msg := CheckCommandPermissions(command, evt); msg != "" {
|
|
sendPermissionDenied(client, evt, msg)
|
|
return
|
|
}
|
|
|
|
ctx := hc.NewCtx(client, evt, prefix, cmd, args)
|
|
fmt.Printf("[CMD] %s > %s%s\n", formatSenderInfo(evt), prefix, cmd)
|
|
command.Run(ctx)
|
|
}
|
|
|
|
func parseCommand(text string, prefixes []string) (prefix, cmd string, args []string, ok bool) {
|
|
if text == "" {
|
|
return
|
|
}
|
|
for _, p := range prefixes {
|
|
if strings.HasPrefix(text, p) {
|
|
rest := text[len(p):]
|
|
parts := strings.Fields(rest)
|
|
if len(parts) == 0 {
|
|
return
|
|
}
|
|
return p, strings.ToLower(parts[0]), parts[1:], true
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func formatSenderInfo(evt *events.Message) string {
|
|
sender := evt.Info.Sender.ToNonAD().String()
|
|
if evt.Info.IsGroup {
|
|
return fmt.Sprintf("[%s] %s", evt.Info.Chat.String(), sender)
|
|
}
|
|
return sender
|
|
}
|