From be3982e585ce312f49b8f504141bde74dfbc3fed Mon Sep 17 00:00:00 2001 From: Gary Kramlich Date: Tue, 7 Dec 2021 19:08:58 -0600 Subject: [PATCH] Start of the bot and it's matrix connection --- bridge/bot.go | 41 +++++++++++++++++++++ bridge/bridge.go | 76 +++++++++++++++++++++++++++++++++++--- bridge/matrix.go | 87 ++++++++++++++++++++++++++++++++++++++++++++ config/appservice.go | 7 ++++ run/cmd.go | 4 +- 5 files changed, 209 insertions(+), 6 deletions(-) create mode 100644 bridge/bot.go create mode 100644 bridge/matrix.go diff --git a/bridge/bot.go b/bridge/bot.go new file mode 100644 index 0000000..0239412 --- /dev/null +++ b/bridge/bot.go @@ -0,0 +1,41 @@ +package bridge + +import ( + "maunium.net/go/mautrix/id" +) + +func (b *Bridge) updateBotProfile() { + cfg := b.config.Appservice.Bot + + // Set the bot's avatar. + if cfg.Avatar != "" { + var err error + var mxc id.ContentURI + + if cfg.Avatar == "remove" { + err = b.bot.SetAvatarURL(mxc) + } else { + mxc, err = id.ParseContentURI(cfg.Avatar) + if err == nil { + err = b.bot.SetAvatarURL(mxc) + } + } + + b.log.Warnln("failed to update the bot's avatar: %v", err) + } + + // Update the bot's display name. + if cfg.Displayname != "" { + var err error + + if cfg.Displayname == "remove" { + err = b.bot.SetDisplayName("") + } else { + err = b.bot.SetDisplayName(cfg.Displayname) + } + + if err != nil { + b.log.Warnln("failed to update the bot's display name: %v", err) + } + } +} diff --git a/bridge/bridge.go b/bridge/bridge.go index c7c94de..b7c9911 100644 --- a/bridge/bridge.go +++ b/bridge/bridge.go @@ -1,13 +1,22 @@ package bridge import ( + "errors" + "fmt" + "time" + log "maunium.net/go/maulogger/v2" + "maunium.net/go/mautrix" "maunium.net/go/mautrix/appservice" "gitlab.com/beeper/discord/config" "gitlab.com/beeper/discord/version" ) +const ( + reconnectDelay = 10 * time.Second +) + type Bridge struct { config *config.Config @@ -15,6 +24,7 @@ type Bridge struct { as *appservice.AppService eventProcessor *appservice.EventProcessor + matrixHandler *matrixHandler bot *appservice.IntentAPI } @@ -27,27 +37,83 @@ func New(cfg *config.Config) (*Bridge, error) { logger.Infoln("Initializing version", version.String) - // Create the app service. + // Create and initalize the app service. appservice, err := cfg.CreateAppService() if err != nil { return nil, err } appservice.Log = log.Sub("matrix") + appservice.Init() + + // Create the bot. + bot := appservice.BotIntent() + // Create the bridge. bridge := &Bridge{ + as: appservice, + bot: bot, config: cfg, log: logger, - as: appservice, } + // Setup the event processors + bridge.setupEvents() + return bridge, nil } -func (b *Bridge) Start() { - b.log.Infoln("bridge started") +func (b *Bridge) connect() error { + b.log.Debugln("Checking connection to homeserver") + + for { + resp, err := b.bot.Whoami() + if err != nil { + if errors.Is(err, mautrix.MUnknownToken) { + b.log.Fatalln("Access token invalid. Is the registration installed in your homeserver correctly?") + + return fmt.Errorf("invalid access token") + } + + b.log.Errorfln("Failed to connect to homeserver : %v", err) + b.log.Errorfln("reconnecting in %s", reconnectDelay) + + time.Sleep(reconnectDelay) + } else if resp.UserID != b.bot.UserID { + b.log.Fatalln("Unexpected user ID in whoami call: got %s, expected %s", resp.UserID, b.bot.UserID) + + return fmt.Errorf("expected user id %q but got %q", b.bot.UserID, resp.UserID) + } else { + break + } + } + + b.log.Debugln("Connected to homeserver") + + return nil +} + +func (b *Bridge) Start() error { + b.log.Infoln("Bridge started") + + if err := b.connect(); err != nil { + return err + } + + b.log.Debugln("Starting application service HTTP server") + go b.as.Start() + + b.log.Debugln("Starting event processor") + go b.eventProcessor.Start() + + go b.updateBotProfile() + + // Finally tell the appservice we're ready + b.as.Ready = true + + return nil } func (b *Bridge) Stop() { - b.log.Infoln("bridge stopped") + b.log.Infoln("Bridge stopped") } diff --git a/bridge/matrix.go b/bridge/matrix.go new file mode 100644 index 0000000..b52f609 --- /dev/null +++ b/bridge/matrix.go @@ -0,0 +1,87 @@ +package bridge + +import ( + "maunium.net/go/maulogger/v2" + "maunium.net/go/mautrix/appservice" + "maunium.net/go/mautrix/event" +) + +type matrixHandler struct { + as *appservice.AppService + bridge *Bridge + log maulogger.Logger +} + +func (b *Bridge) setupEvents() { + b.eventProcessor = appservice.NewEventProcessor(b.as) + + b.matrixHandler = &matrixHandler{ + as: b.as, + bridge: b, + log: b.log.Sub("Matrix"), + } + + b.eventProcessor.On(event.EventMessage, b.matrixHandler.handleMessage) + b.eventProcessor.On(event.StateMember, b.matrixHandler.handleMembership) +} + +func (mh *MatrixHandler) join(evt *event.Event, intent *appservice.IntentAPI) *mautrix.RespJoinedMembers { + resp, err := intent.JoinRoomByID(evt.RoomID) + if err != nil { + mh.log.Debugfln("Failed to join room %s as %s with invite from %s: %v", evt.RoomID, intent.UserID, evt.Sender, err) + + return nil + } + + members, err := intent.JoinedMembers(resp.RoomID) + if err != nil { + intent.LeaveRoom(resp.RoomID) + + mh.log.Debugfln("Failed to get members in room %s after accepting invite from %s as %s: %v", resp.RoomID, evt.Sender, intent.UserID, err) + + return nil + } + + if len(members.Joined) < 2 { + intent.LeaveRoom(resp.RoomID) + + mh.log.Debugln("Leaving empty room", resp.RoomID, "after accepting invite from", evt.Sender, "as", intent.UserID) + + return nil + } + + return members +} + +func (mh *matrixHandler) ignoreEvent(evt *event.Event) bool { + return false +} + +func (mh *matrixHandler) handleMessage(evt *event.Event) { + if mh.ignoreEvent(evt) { + return + } + + mh.log.Debugfln("received message from %q: %q", evt.Sender, evt.Content.AsMessage()) +} + +func (mh *matrixHandler) handleMembership(evt *event.Event) { + mh.log.Debugfln("recevied invite %#v\n", evt) + + // Return early if we're supposed to ignore the event. + if mh.ignoreEvent(evt) { + return + } + + // Grab the content of the event. + content := evt.Content.AsMessage() + + // TODO: handle invites from ourselfs? + + isSelf := id.UserID(evt.GetStateKey()) == evt.Sender + + // Handle matrix invites. + if content.Membership == event.MembershipInvite && !isSelf { + // + } +} diff --git a/config/appservice.go b/config/appservice.go index 4c118f9..209f20e 100644 --- a/config/appservice.go +++ b/config/appservice.go @@ -64,5 +64,12 @@ func (cfg *Config) CreateAppService() (*as.AppService, error) { appservice.Host.Port = cfg.Appservice.Port appservice.DefaultHTTPRetries = 4 + reg, err := cfg.getRegistration() + if err != nil { + return nil, err + } + + appservice.Registration = reg + return appservice, nil } diff --git a/run/cmd.go b/run/cmd.go index 341f6ae..c5af845 100644 --- a/run/cmd.go +++ b/run/cmd.go @@ -23,7 +23,9 @@ func (c *Cmd) Run(g *globals.Globals) error { return err } - bridge.Start() + if err := bridge.Start(); err != nil { + return err + } ch := make(chan os.Signal) signal.Notify(ch, os.Interrupt, syscall.SIGTERM)