mautrix-go/commands/processor.go
Tulir Asokan 89fad2f462
Some checks failed
Go / Lint (latest) (push) Has been cancelled
Go / Build (old, libolm) (push) Has been cancelled
Go / Build (latest, libolm) (push) Has been cancelled
Go / Build (old, goolm) (push) Has been cancelled
Go / Build (latest, goolm) (push) Has been cancelled
commands: add reaction button system
2025-05-24 16:33:36 +03:00

126 lines
3.5 KiB
Go

// Copyright (c) 2025 Tulir Asokan
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package commands
import (
"context"
"runtime/debug"
"strings"
"github.com/rs/zerolog"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/event"
)
// Processor implements boilerplate code for splitting messages into a command and arguments,
// and finding the appropriate handler for the command.
type Processor[MetaType any] struct {
*CommandContainer[MetaType]
Client *mautrix.Client
LogArgs bool
PreValidator PreValidator[MetaType]
Meta MetaType
ReactionCommandPrefix string
}
// UnknownCommandName is the name of the fallback handler which is used if no other handler is found.
// If even the unknown command handler is not found, the command is ignored.
const UnknownCommandName = "__unknown-command__"
func NewProcessor[MetaType any](cli *mautrix.Client) *Processor[MetaType] {
proc := &Processor[MetaType]{
CommandContainer: NewCommandContainer[MetaType](),
Client: cli,
PreValidator: ValidatePrefixSubstring[MetaType]("!"),
}
proc.Register(MakeUnknownCommandHandler[MetaType]("!"))
return proc
}
func (proc *Processor[MetaType]) Process(ctx context.Context, evt *event.Event) {
log := zerolog.Ctx(ctx).With().
Stringer("sender", evt.Sender).
Stringer("room_id", evt.RoomID).
Stringer("event_id", evt.ID).
Logger()
defer func() {
panicErr := recover()
if panicErr != nil {
logEvt := log.Error().
Bytes(zerolog.ErrorStackFieldName, debug.Stack())
if realErr, ok := panicErr.(error); ok {
logEvt = logEvt.Err(realErr)
} else {
logEvt = logEvt.Any(zerolog.ErrorFieldName, panicErr)
}
logEvt.Msg("Panic in command handler")
_, err := proc.Client.SendReaction(ctx, evt.RoomID, evt.ID, "💥")
if err != nil {
log.Err(err).Msg("Failed to send reaction after panic")
}
}
}()
var parsed *Event[MetaType]
switch evt.Type {
case event.EventReaction:
parsed = proc.ParseReaction(ctx, evt)
case event.EventMessage:
parsed = ParseEvent[MetaType](ctx, evt)
}
if parsed == nil || !proc.PreValidator.Validate(parsed) {
return
}
parsed.Proc = proc
parsed.Meta = proc.Meta
parsed.Ctx = ctx
handler := proc.GetHandler(parsed.Command)
if handler == nil {
return
}
parsed.Handler = handler
if handler.PreFunc != nil {
handler.PreFunc(parsed)
}
handlerChain := zerolog.Arr()
handlerChain.Str(handler.Name)
for handler.subcommandContainer != nil && len(parsed.Args) > 0 {
subHandler := handler.subcommandContainer.GetHandler(strings.ToLower(parsed.Args[0]))
if subHandler != nil {
parsed.ParentCommands = append(parsed.ParentCommands, parsed.Command)
parsed.ParentHandlers = append(parsed.ParentHandlers, handler)
handler = subHandler
handlerChain.Str(subHandler.Name)
parsed.Command = strings.ToLower(parsed.ShiftArg())
parsed.Handler = subHandler
if subHandler.PreFunc != nil {
subHandler.PreFunc(parsed)
}
} else {
break
}
}
logWith := log.With().
Str("command", parsed.Command).
Array("handler", handlerChain)
if len(parsed.ParentCommands) > 0 {
logWith = logWith.Strs("parent_commands", parsed.ParentCommands)
}
if proc.LogArgs {
logWith = logWith.Strs("args", parsed.Args)
}
log = logWith.Logger()
parsed.Ctx = log.WithContext(ctx)
parsed.Log = &log
log.Debug().Msg("Processing command")
handler.Func(parsed)
}