Эх сурвалжийг харах

Implement the command parser

Right now this just supports help and version, but will be getting new commands
shortly.
Gary Kramlich 3 жил өмнө
parent
commit
2279916d9c

+ 112 - 0
bridge/commandhandler.go

@@ -0,0 +1,112 @@
+package bridge
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/alecthomas/kong"
+	"github.com/google/shlex"
+
+	"maunium.net/go/maulogger/v2"
+	"maunium.net/go/mautrix/id"
+)
+
+type commandHandler struct {
+	bridge *Bridge
+	log    maulogger.Logger
+}
+
+func newCommandHandler(bridge *Bridge) *commandHandler {
+	return &commandHandler{
+		bridge: bridge,
+		log:    bridge.log.Sub("Commands"),
+	}
+}
+
+func commandsHelpPrinter(options kong.HelpOptions, ctx *kong.Context) error {
+	selected := ctx.Selected()
+
+	if selected == nil {
+		for _, cmd := range ctx.Model.Leaves(true) {
+			fmt.Fprintf(ctx.Stdout, " * %s - %s\n", cmd.Path(), cmd.Help)
+		}
+	} else {
+		fmt.Fprintf(ctx.Stdout, "%s - %s\n", selected.Path(), selected.Help)
+		if selected.Detail != "" {
+			fmt.Fprintf(ctx.Stdout, "\n%s\n", selected.Detail)
+		}
+		if len(selected.Positional) > 0 {
+			fmt.Fprintf(ctx.Stdout, "\nArguments:\n")
+			for _, arg := range selected.Positional {
+				fmt.Fprintf(ctx.Stdout, "%s %s\n", arg.Summary(), arg.Help)
+			}
+		}
+	}
+
+	return nil
+}
+
+func (h *commandHandler) handle(roomID id.RoomID, user *User, message string, replyTo id.EventID) {
+	cmd := commands{
+		globals: globals{
+			bot:     h.bridge.bot,
+			bridge:  h.bridge,
+			portal:  h.bridge.GetPortalByMXID(roomID),
+			handler: h,
+			roomID:  roomID,
+			user:    user,
+			replyTo: replyTo,
+		},
+	}
+
+	buf := &strings.Builder{}
+
+	parse, err := kong.New(
+		&cmd,
+		kong.Exit(func(int) {}),
+		kong.NoDefaultHelp(),
+		kong.Writers(buf, buf),
+		kong.Help(commandsHelpPrinter),
+	)
+
+	if err != nil {
+		h.log.Warnf("Failed to create argument parser for %q: %v", roomID, err)
+
+		cmd.globals.reply("unexpected error, please try again shortly")
+
+		return
+	}
+
+	args, err := shlex.Split(message)
+	if err != nil {
+		h.log.Warnf("Failed to split message %q: %v", message, err)
+
+		cmd.globals.reply("failed to process the command")
+
+		return
+	}
+
+	ctx, err := parse.Parse(args)
+	if err != nil {
+		h.log.Warnf("Failed to parse command %q: %v", message, err)
+
+		cmd.globals.reply("failed to process the command")
+
+		return
+	}
+
+	cmd.globals.context = ctx
+
+	err = ctx.Run(&cmd.globals)
+	if err != nil {
+		h.log.Warnf("Command %q failed: %v", message, err)
+
+		cmd.globals.reply("unexpected failure")
+
+		return
+	}
+
+	if buf.Len() > 0 {
+		cmd.globals.reply(buf.String())
+	}
+}

+ 81 - 0
bridge/commands.go

@@ -0,0 +1,81 @@
+package bridge
+
+import (
+	"fmt"
+
+	"github.com/alecthomas/kong"
+
+	"maunium.net/go/mautrix/appservice"
+	"maunium.net/go/mautrix/event"
+	"maunium.net/go/mautrix/format"
+	"maunium.net/go/mautrix/id"
+
+	"gitlab.com/beeper/discord/consts"
+	"gitlab.com/beeper/discord/version"
+)
+
+type globals struct {
+	context *kong.Context
+
+	bridge  *Bridge
+	bot     *appservice.IntentAPI
+	portal  *Portal
+	handler *commandHandler
+	roomID  id.RoomID
+	user    *User
+	replyTo id.EventID
+}
+
+func (g *globals) reply(msg string) {
+	content := format.RenderMarkdown(msg, true, false)
+	content.MsgType = event.MsgNotice
+	intent := g.bot
+
+	if g.portal != nil && g.portal.IsPrivateChat() {
+		intent = g.portal.MainIntent()
+	}
+
+	_, err := intent.SendMessageEvent(g.roomID, event.EventMessage, content)
+	if err != nil {
+		g.handler.log.Warnfln("Failed to reply to command from %q: %v", g.user.MXID, err)
+	}
+}
+
+type commands struct {
+	globals
+
+	Help    helpCmd    `kong:"cmd,help='Displays this message.'"`
+	Version versionCmd `kong:"cmd,help='Displays the version of the bridge.'"`
+}
+
+type helpCmd struct {
+	Command []string `kong:"arg,optional,help='The command to get help on.'"`
+}
+
+func (c *helpCmd) Run(g *globals) error {
+	ctx, err := kong.Trace(g.context.Kong, c.Command)
+	if err != nil {
+		return err
+	}
+
+	if ctx.Error != nil {
+		return err
+	}
+
+	err = ctx.PrintUsage(true)
+	if err != nil {
+		return err
+	}
+
+	fmt.Fprintln(g.context.Stdout)
+
+	return nil
+}
+
+type versionCmd struct{}
+
+func (c *versionCmd) Run(g *globals) error {
+	fmt.Fprintln(g.context.Stdout, consts.Name, version.String)
+
+	return nil
+}

+ 25 - 1
bridge/matrix.go

@@ -1,6 +1,8 @@
 package bridge
 
 import (
+	"strings"
+
 	"maunium.net/go/maulogger/v2"
 	"maunium.net/go/mautrix"
 	"maunium.net/go/mautrix/appservice"
@@ -13,6 +15,7 @@ type matrixHandler struct {
 	as     *appservice.AppService
 	bridge *Bridge
 	log    maulogger.Logger
+	cmd    *commandHandler
 }
 
 func (b *Bridge) setupEvents() {
@@ -22,6 +25,7 @@ func (b *Bridge) setupEvents() {
 		as:     b.as,
 		bridge: b,
 		log:    b.log.Sub("Matrix"),
+		cmd:    newCommandHandler(b),
 	}
 
 	b.eventProcessor.On(event.EventMessage, b.matrixHandler.handleMessage)
@@ -61,11 +65,31 @@ func (mh *matrixHandler) ignoreEvent(evt *event.Event) bool {
 }
 
 func (mh *matrixHandler) handleMessage(evt *event.Event) {
-	mh.log.Debugfln("received message from %q: %q", evt.Sender, evt.Content.AsMessage())
 	if mh.ignoreEvent(evt) {
 		return
 	}
 
+	user := mh.bridge.GetUserByMXID(evt.Sender)
+	if user == nil {
+		return
+	}
+
+	content := evt.Content.AsMessage()
+	content.RemoveReplyFallback()
+
+	if content.MsgType == event.MsgText {
+		prefix := mh.bridge.config.Bridge.CommandPrefix
+
+		hasPrefix := strings.HasPrefix(content.Body, prefix)
+		if hasPrefix {
+			content.Body = strings.TrimLeft(content.Body[len(prefix):], " ")
+		}
+
+		if hasPrefix || evt.RoomID == user.ManagementRoom {
+			mh.cmd.handle(evt.RoomID, user, content.Body, content.GetReplyTo())
+			return
+		}
+	}
 }
 
 func (mh *matrixHandler) joinAndCheckMembers(evt *event.Event, intent *appservice.IntentAPI) *mautrix.RespJoinedMembers {

+ 14 - 0
bridge/portal.go

@@ -4,6 +4,8 @@ import (
 	"fmt"
 
 	log "maunium.net/go/maulogger/v2"
+
+	"maunium.net/go/mautrix/appservice"
 	"maunium.net/go/mautrix/event"
 	"maunium.net/go/mautrix/id"
 
@@ -93,3 +95,15 @@ func (p *Portal) messageLoop() {
 		}
 	}
 }
+
+func (p *Portal) IsPrivateChat() bool {
+	return false
+}
+
+func (p *Portal) MainIntent() *appservice.IntentAPI {
+	if p.IsPrivateChat() {
+		return p.bridge.GetPuppetByID(p.Key.ID).DefaultIntent()
+	}
+
+	return p.bridge.bot
+}

+ 5 - 0
bridge/puppet.go

@@ -5,6 +5,7 @@ import (
 	"regexp"
 
 	log "maunium.net/go/maulogger/v2"
+	"maunium.net/go/mautrix/appservice"
 	"maunium.net/go/mautrix/id"
 
 	"gitlab.com/beeper/discord/database"
@@ -85,3 +86,7 @@ func (b *Bridge) FormatPuppetMXID(did string) id.UserID {
 		b.config.Homeserver.Domain,
 	)
 }
+
+func (p *Puppet) DefaultIntent() *appservice.IntentAPI {
+	return p.bridge.as.Intent(p.MXID)
+}

+ 6 - 0
config/bridge.go

@@ -8,6 +8,8 @@ import (
 type bridge struct {
 	UsernameTemplate string `yaml:"username_template"`
 
+	CommandPrefix string `yaml:"command_prefix"`
+
 	ManagementRoomText managementRoomText `yaml:"management_root_text"`
 
 	PortalMessageBuffer int `yaml:"portal_message_buffer"`
@@ -31,6 +33,10 @@ func (b *bridge) validate() error {
 		return err
 	}
 
+	if b.CommandPrefix == "" {
+		b.CommandPrefix = "!dis"
+	}
+
 	if err := b.ManagementRoomText.validate(); err != nil {
 		return err
 	}

+ 1 - 0
go.mod

@@ -15,6 +15,7 @@ require (
 require (
 	github.com/btcsuite/btcutil v1.0.2 // indirect
 	github.com/bwmarrin/discordgo v0.23.2 // indirect
+	github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
 	github.com/gorilla/mux v1.8.0 // indirect
 	github.com/gorilla/websocket v1.4.2 // indirect
 	github.com/pkg/errors v0.9.1 // indirect

+ 2 - 0
go.sum

@@ -21,6 +21,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
 github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
 github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
+github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
 github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
 github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
 github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=