mautrix-discord/bridge/commands.go
2022-05-21 00:13:33 +03:00

360 lines
9 KiB
Go

package bridge
import (
"context"
"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"
"go.mau.fi/mautrix-discord/consts"
"go.mau.fi/mautrix-discord/remoteauth"
"go.mau.fi/mautrix-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
Disconnect disconnectCmd `kong:"cmd,help='Disconnect from Discord'"`
Help helpCmd `kong:"cmd,help='Displays this message.'"`
Login loginCmd `kong:"cmd,help='Log in to Discord.'"`
Logout logoutCmd `kong:"cmd,help='Log out of Discord.'"`
Reconnect reconnectCmd `kong:"cmd,help='Reconnect to Discord'"`
Version versionCmd `kong:"cmd,help='Displays the version of the bridge.'"`
Guilds guildsCmd `kong:"cmd,help='Guild bridging management.'"`
LoginMatrix loginMatrixCmd `kong:"cmd,help='Replace the puppet for your Discord account with your real Matrix account.'"`
LogoutMatrix logoutMatrixCmd `kong:"cmd,help='Switch the puppet for your Discord account back to the default one.'"`
PingMatrix pingMatrixCmd `kong:"cmd,help='check if your double puppet is working properly'"`
}
///////////////////////////////////////////////////////////////////////////////
// Help Command
///////////////////////////////////////////////////////////////////////////////
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
}
///////////////////////////////////////////////////////////////////////////////
// Version Command
///////////////////////////////////////////////////////////////////////////////
type versionCmd struct{}
func (c *versionCmd) Run(g *globals) error {
fmt.Fprintln(g.context.Stdout, consts.Name, version.String)
return nil
}
///////////////////////////////////////////////////////////////////////////////
// Login Command
///////////////////////////////////////////////////////////////////////////////
type loginCmd struct{}
func (l *loginCmd) Run(g *globals) error {
if g.user.LoggedIn() {
fmt.Fprintf(g.context.Stdout, "You are already logged in")
return fmt.Errorf("user already logged in")
}
client, err := remoteauth.New()
if err != nil {
return err
}
qrChan := make(chan string)
doneChan := make(chan struct{})
var qrCodeEvent id.EventID
go func() {
code := <-qrChan
resp, err := g.user.sendQRCode(g.bot, g.roomID, code)
if err != nil {
fmt.Fprintln(g.context.Stdout, "Failed to generate the qrcode")
return
}
qrCodeEvent = resp
}()
ctx := context.Background()
if err := client.Dial(ctx, qrChan, doneChan); err != nil {
close(qrChan)
close(doneChan)
return err
}
<-doneChan
if qrCodeEvent != "" {
_, err := g.bot.RedactEvent(g.roomID, qrCodeEvent)
if err != nil {
fmt.Errorf("Failed to redact the qrcode: %v", err)
}
}
user, err := client.Result()
if err != nil {
fmt.Fprintln(g.context.Stdout, "Failed to log in")
return err
}
if err := g.user.Login(user.Token); err != nil {
fmt.Fprintln(g.context.Stdout, "Failed to login", err)
return err
}
g.user.Lock()
g.user.ID = user.UserID
g.user.Update()
g.user.Unlock()
fmt.Fprintln(g.context.Stdout, "Successfully logged in")
return nil
}
///////////////////////////////////////////////////////////////////////////////
// Logout Command
///////////////////////////////////////////////////////////////////////////////
type logoutCmd struct{}
func (l *logoutCmd) Run(g *globals) error {
if !g.user.LoggedIn() {
fmt.Fprintln(g.context.Stdout, "You are not logged in")
return fmt.Errorf("user is not logged in")
}
err := g.user.Logout()
if err != nil {
fmt.Fprintln(g.context.Stdout, "Failed to log out")
return err
}
fmt.Fprintln(g.context.Stdout, "Successfully logged out")
return nil
}
///////////////////////////////////////////////////////////////////////////////
// Disconnect Command
///////////////////////////////////////////////////////////////////////////////
type disconnectCmd struct{}
func (d *disconnectCmd) Run(g *globals) error {
if !g.user.Connected() {
fmt.Fprintln(g.context.Stdout, "You are not connected")
return fmt.Errorf("user is not connected")
}
if err := g.user.Disconnect(); err != nil {
fmt.Fprintln(g.context.Stdout, "Failed to disconnect")
return err
}
fmt.Fprintln(g.context.Stdout, "Successfully disconnected")
return nil
}
///////////////////////////////////////////////////////////////////////////////
// Reconnect Command
///////////////////////////////////////////////////////////////////////////////
type reconnectCmd struct{}
func (r *reconnectCmd) Run(g *globals) error {
if g.user.Connected() {
fmt.Fprintln(g.context.Stdout, "You are already connected")
return fmt.Errorf("user is already connected")
}
if err := g.user.Connect(); err != nil {
fmt.Fprintln(g.context.Stdout, "Failed to connect")
return err
}
fmt.Fprintln(g.context.Stdout, "Successfully connected")
return nil
}
///////////////////////////////////////////////////////////////////////////////
// LoginMatrix Command
///////////////////////////////////////////////////////////////////////////////
type loginMatrixCmd struct {
AccessToken string `kong:"arg,help='The shared secret to use the bridge'"`
}
func (m *loginMatrixCmd) Run(g *globals) error {
puppet := g.bridge.GetPuppetByID(g.user.ID)
err := puppet.SwitchCustomMXID(m.AccessToken, g.user.MXID)
if err != nil {
fmt.Fprintf(g.context.Stdout, "Failed to switch puppet: %v", err)
return err
}
fmt.Fprintf(g.context.Stdout, "Successfully switched puppet")
return nil
}
///////////////////////////////////////////////////////////////////////////////
// LogoutMatrix Command
///////////////////////////////////////////////////////////////////////////////
type logoutMatrixCmd struct{}
func (m *logoutMatrixCmd) Run(g *globals) error {
return nil
}
///////////////////////////////////////////////////////////////////////////////
// PingMatrix Command
///////////////////////////////////////////////////////////////////////////////
type pingMatrixCmd struct{}
func (m *pingMatrixCmd) Run(g *globals) error {
puppet := g.bridge.GetPuppetByCustomMXID(g.user.MXID)
if puppet == nil || puppet.CustomIntent() == nil {
fmt.Fprintf(g.context.Stdout, "You have not changed your Discord account's Matrix puppet.")
return fmt.Errorf("double puppet not configured")
}
resp, err := puppet.CustomIntent().Whoami()
if err != nil {
fmt.Fprintf(g.context.Stdout, "Failed to validate Matrix login: %v", err)
return err
}
fmt.Fprintf(g.context.Stdout, "Confirmed valid access token for %s / %s", resp.UserID, resp.DeviceID)
return nil
}
///////////////////////////////////////////////////////////////////////////////
// Guilds Commands
///////////////////////////////////////////////////////////////////////////////
type guildsCmd struct {
Status guildStatusCmd `kong:"cmd,help='Show the bridge status for the guilds you are in'"`
Bridge guildBridgeCmd `kong:"cmd,help='Bridge a guild'"`
Unbridge guildUnbridgeCmd `kong:"cmd,help='Unbridge a guild'"`
}
type guildStatusCmd struct{}
func (c *guildStatusCmd) Run(g *globals) error {
g.user.guildsLock.Lock()
defer g.user.guildsLock.Unlock()
if len(g.user.guilds) == 0 {
fmt.Fprintf(g.context.Stdout, "you haven't joined any guilds.")
} else {
for _, guild := range g.user.guilds {
status := "not bridged"
if guild.Bridge {
status = "bridged"
}
fmt.Fprintf(g.context.Stdout, "%s %s %s\n", guild.GuildName, guild.GuildID, status)
}
}
return nil
}
type guildBridgeCmd struct {
GuildID string `kong:"arg,help='the id of the guild to unbridge'"`
Entire bool `kong:"flag,help='whether or not to bridge all channels'"`
}
func (c *guildBridgeCmd) Run(g *globals) error {
if err := g.user.bridgeGuild(c.GuildID, c.Entire); err != nil {
return err
}
fmt.Fprintf(g.context.Stdout, "Successfully bridged guild %s", c.GuildID)
return nil
}
type guildUnbridgeCmd struct {
GuildID string `kong:"arg,help='the id of the guild to unbridge'"`
}
func (c *guildUnbridgeCmd) Run(g *globals) error {
if err := g.user.unbridgeGuild(c.GuildID); err != nil {
return err
}
fmt.Fprintf(g.context.Stdout, "Successfully unbridged guild %s", c.GuildID)
return nil
}