mautrix-go/commands/event.go
Tulir Asokan db62b9a1d8
Some checks are pending
Go / Lint (latest) (push) Waiting to run
Go / Build (old, libolm) (push) Waiting to run
Go / Build (latest, libolm) (push) Waiting to run
Go / Build (old, goolm) (push) Waiting to run
Go / Build (latest, goolm) (push) Waiting to run
commands: ignore notices
2025-04-29 02:27:03 +03:00

151 lines
4.2 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"
"fmt"
"strings"
"github.com/rs/zerolog"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/format"
)
// Event contains the data of a single command event.
// It also provides some helper methods for responding to the command.
type Event[MetaType any] struct {
*event.Event
// RawInput is the entire message before splitting into command and arguments.
RawInput string
// ParentCommands is the chain of commands leading up to this command.
// This is only set if the command is a subcommand.
ParentCommands []string
// Command is the lowercased first word of the message.
Command string
// Args are the rest of the message split by whitespace ([strings.Fields]).
Args []string
// RawArgs is the same as args, but without the splitting by whitespace.
RawArgs string
Ctx context.Context
Proc *Processor[MetaType]
Handler *Handler[MetaType]
Meta MetaType
}
var IDHTMLParser = &format.HTMLParser{
PillConverter: func(displayname, mxid, eventID string, ctx format.Context) string {
if len(mxid) == 0 {
return displayname
}
if eventID != "" {
return fmt.Sprintf("https://matrix.to/#/%s/%s", mxid, eventID)
}
return mxid
},
ItalicConverter: func(s string, c format.Context) string {
return fmt.Sprintf("*%s*", s)
},
Newline: "\n",
}
// ParseEvent parses a message into a command event struct.
func ParseEvent[MetaType any](ctx context.Context, evt *event.Event) *Event[MetaType] {
content := evt.Content.Parsed.(*event.MessageEventContent)
if content.MsgType == event.MsgNotice || content.RelatesTo.GetReplaceID() != "" {
return nil
}
text := content.Body
if content.Format == event.FormatHTML {
text = IDHTMLParser.Parse(content.FormattedBody, format.NewContext(ctx))
}
if len(text) == 0 {
return nil
}
parts := strings.Fields(text)
return &Event[MetaType]{
Event: evt,
RawInput: text,
Command: strings.ToLower(parts[0]),
Args: parts[1:],
RawArgs: strings.TrimLeft(strings.TrimPrefix(text, parts[0]), " "),
Ctx: ctx,
}
}
type ReplyOpts struct {
AllowHTML bool
AllowMarkdown bool
Reply bool
Thread bool
SendAsText bool
}
func (evt *Event[MetaType]) Reply(msg string, args ...any) {
if len(args) > 0 {
msg = fmt.Sprintf(msg, args...)
}
evt.Respond(msg, ReplyOpts{AllowMarkdown: true, Reply: true})
}
func (evt *Event[MetaType]) Respond(msg string, opts ReplyOpts) {
content := format.RenderMarkdown(msg, opts.AllowMarkdown, opts.AllowHTML)
if opts.Thread {
content.SetThread(evt.Event)
}
if opts.Reply {
content.SetReply(evt.Event)
}
if !opts.SendAsText {
content.MsgType = event.MsgNotice
}
_, err := evt.Proc.Client.SendMessageEvent(evt.Ctx, evt.RoomID, event.EventMessage, content)
if err != nil {
zerolog.Ctx(evt.Ctx).Err(err).Msg("Failed to send reply")
}
}
func (evt *Event[MetaType]) React(emoji string) {
_, err := evt.Proc.Client.SendReaction(evt.Ctx, evt.RoomID, evt.ID, emoji)
if err != nil {
zerolog.Ctx(evt.Ctx).Err(err).Msg("Failed to send reaction")
}
}
func (evt *Event[MetaType]) Redact() {
_, err := evt.Proc.Client.RedactEvent(evt.Ctx, evt.RoomID, evt.ID)
if err != nil {
zerolog.Ctx(evt.Ctx).Err(err).Msg("Failed to redact command")
}
}
func (evt *Event[MetaType]) MarkRead() {
err := evt.Proc.Client.MarkRead(evt.Ctx, evt.RoomID, evt.ID)
if err != nil {
zerolog.Ctx(evt.Ctx).Err(err).Msg("Failed to send read receipt")
}
}
// ShiftArg removes the first argument from the Args list and RawArgs data and returns it.
// RawInput will not be modified.
func (evt *Event[MetaType]) ShiftArg() string {
if len(evt.Args) == 0 {
return ""
}
firstArg := evt.Args[0]
evt.RawArgs = strings.TrimLeft(strings.TrimPrefix(evt.RawArgs, evt.Args[0]), " ")
evt.Args = evt.Args[1:]
return firstArg
}
// UnshiftArg reverses ShiftArg by adding the given value to the beginning of the Args list and RawArgs data.
func (evt *Event[MetaType]) UnshiftArg(arg string) {
evt.RawArgs = arg + " " + evt.RawArgs
evt.Args = append([]string{arg}, evt.Args...)
}