diff --git a/config/bridge.go b/config/bridge.go index d650fbc..f44a65c 100644 --- a/config/bridge.go +++ b/config/bridge.go @@ -26,6 +26,8 @@ import ( "maunium.net/go/mautrix/bridge/bridgeconfig" "maunium.net/go/mautrix/event" "maunium.net/go/mautrix/id" + + "github.com/beeper/groupme-lib" ) type DeferredConfig struct { @@ -184,6 +186,15 @@ type UsernameTemplateArgs struct { UserID id.UserID } +func (bc BridgeConfig) FormatDisplayname(gmid groupme.ID, member groupme.Member) string { + var buf strings.Builder + _ = bc.displaynameTemplate.Execute(&buf, map[string]string{ + "Name": member.Nickname, + "GMID": gmid.String(), + }) + return buf.String() +} + func (bc BridgeConfig) FormatUsername(username string) string { var buf strings.Builder _ = bc.ParsedUsernameTemplate.Execute(&buf, username) diff --git a/main.go b/main.go index c1be898..6c9bb7c 100644 --- a/main.go +++ b/main.go @@ -17,6 +17,7 @@ package main import ( + _ "embed" "sync" "maunium.net/go/mautrix/bridge" diff --git a/messagetracking.go b/messagetracking.go new file mode 100644 index 0000000..a264f8f --- /dev/null +++ b/messagetracking.go @@ -0,0 +1,208 @@ +// mautrix-groupme - A Matrix-GroupMe puppeting bridge. +// Copyright (C) 2022 Sumner Evans, Karmanyaah Malhotra +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package main + +import ( + "errors" + "fmt" + "sync" + + log "maunium.net/go/maulogger/v2" + + "maunium.net/go/mautrix" + "maunium.net/go/mautrix/bridge/status" + "maunium.net/go/mautrix/event" + "maunium.net/go/mautrix/id" +) + +var ( + errMessageTakingLong = errors.New("bridging the message is taking longer than usual") +) + +func errorToStatusReason(err error) (reason event.MessageStatusReason, status event.MessageStatus, isCertain, sendNotice bool, humanMessage string) { + switch { + case errors.Is(err, errMessageTakingLong): + return event.MessageStatusTooOld, event.MessageStatusPending, false, true, err.Error() + default: + return event.MessageStatusGenericError, event.MessageStatusRetriable, false, true, "" + } +} + +func (portal *Portal) sendErrorMessage(evt *event.Event, err error, msgType string, confirmed bool, editID id.EventID) id.EventID { + if !portal.bridge.Config.Bridge.MessageErrorNotices { + return "" + } + certainty := "may not have been" + if confirmed { + certainty = "was not" + } + msg := fmt.Sprintf("\u26a0 Your %s %s bridged: %v", msgType, certainty, err) + if errors.Is(err, errMessageTakingLong) { + msg = fmt.Sprintf("\u26a0 Bridging your %s is taking longer than usual", msgType) + } + content := &event.MessageEventContent{ + MsgType: event.MsgNotice, + Body: msg, + } + if editID != "" { + content.SetEdit(editID) + } else { + content.SetReply(evt) + } + resp, err := portal.sendMainIntentMessage(content) + if err != nil { + portal.log.Warnfln("Failed to send bridging error message:", err) + return "" + } + return resp.EventID +} + +func (portal *Portal) sendStatusEvent(evtID, lastRetry id.EventID, err error) { + if !portal.bridge.Config.Bridge.MessageStatusEvents { + return + } + if lastRetry == evtID { + lastRetry = "" + } + intent := portal.bridge.Bot + if !portal.Encrypted { + // Bridge bot isn't present in unencrypted DMs + intent = portal.MainIntent() + } + content := event.BeeperMessageStatusEventContent{ + Network: portal.getBridgeInfoStateKey(), + RelatesTo: event.RelatesTo{ + Type: event.RelReference, + EventID: evtID, + }, + LastRetry: lastRetry, + } + if err == nil { + content.Status = event.MessageStatusSuccess + } else { + content.Reason, content.Status, _, _, content.Message = errorToStatusReason(err) + content.Error = err.Error() + } + content.FillLegacyBooleans() + _, err = intent.SendMessageEvent(portal.MXID, event.BeeperMessageStatus, &content) + if err != nil { + portal.log.Warnln("Failed to send message status event:", err) + } +} + +func (portal *Portal) sendDeliveryReceipt(eventID id.EventID) { + if portal.bridge.Config.Bridge.DeliveryReceipts { + err := portal.bridge.Bot.MarkRead(portal.MXID, eventID) + if err != nil { + portal.log.Debugfln("Failed to send delivery receipt for %s: %v", eventID, err) + } + } +} + +func (portal *Portal) sendMessageMetrics(evt *event.Event, err error, part string, ms *metricSender) { + var msgType string + switch evt.Type { + case event.EventMessage: + msgType = "message" + case event.EventReaction: + msgType = "reaction" + case event.EventRedaction: + msgType = "redaction" + default: + msgType = "unknown event" + } + evtDescription := evt.ID.String() + if evt.Type == event.EventRedaction { + evtDescription += fmt.Sprintf(" of %s", evt.Redacts) + } + origEvtID := evt.ID + if retryMeta := evt.Content.AsMessage().MessageSendRetry; retryMeta != nil { + origEvtID = retryMeta.OriginalEventID + } + if err != nil { + level := log.LevelError + if part == "Ignoring" { + level = log.LevelDebug + } + portal.log.Logfln(level, "%s %s %s from %s: %v", part, msgType, evtDescription, evt.Sender, err) + reason, statusCode, isCertain, sendNotice, _ := errorToStatusReason(err) + checkpointStatus := status.ReasonToCheckpointStatus(reason, statusCode) + portal.bridge.SendMessageCheckpoint(evt, status.MsgStepRemote, err, checkpointStatus, ms.getRetryNum()) + if sendNotice { + ms.setNoticeID(portal.sendErrorMessage(evt, err, msgType, isCertain, ms.getNoticeID())) + } + portal.sendStatusEvent(origEvtID, evt.ID, err) + } else { + portal.log.Debugfln("Handled Matrix %s %s", msgType, evtDescription) + portal.sendDeliveryReceipt(evt.ID) + portal.bridge.SendMessageSuccessCheckpoint(evt, status.MsgStepRemote, ms.getRetryNum()) + portal.sendStatusEvent(origEvtID, evt.ID, nil) + if prevNotice := ms.popNoticeID(); prevNotice != "" { + _, _ = portal.MainIntent().RedactEvent(portal.MXID, prevNotice, mautrix.ReqRedact{ + Reason: "error resolved", + }) + } + } +} + +type metricSender struct { + portal *Portal + previousNotice id.EventID + lock sync.Mutex + completed bool + retryNum int +} + +func (ms *metricSender) getRetryNum() int { + if ms != nil { + return ms.retryNum + } + return 0 +} + +func (ms *metricSender) getNoticeID() id.EventID { + if ms == nil { + return "" + } + return ms.previousNotice +} + +func (ms *metricSender) popNoticeID() id.EventID { + if ms == nil { + return "" + } + evtID := ms.previousNotice + ms.previousNotice = "" + return evtID +} + +func (ms *metricSender) setNoticeID(evtID id.EventID) { + if ms != nil && ms.previousNotice == "" { + ms.previousNotice = evtID + } +} + +func (ms *metricSender) sendMessageMetrics(evt *event.Event, err error, part string, completed bool) { + ms.lock.Lock() + defer ms.lock.Unlock() + if !completed && ms.completed { + return + } + ms.portal.sendMessageMetrics(evt, err, part, ms) + ms.retryNum++ + ms.completed = completed +} diff --git a/no-crypto.go b/no-crypto.go deleted file mode 100644 index 14e49c0..0000000 --- a/no-crypto.go +++ /dev/null @@ -1,18 +0,0 @@ -//go:build !cgo || nocrypto -// +build !cgo nocrypto - -package main - -import ( - "errors" -) - -func NewCryptoHelper(bridge *Bridge) Crypto { - if !bridge.Config.Bridge.Encryption.Allow { - bridge.Log.Warnln("Bridge built without end-to-bridge encryption, but encryption is enabled in config") - } - bridge.Log.Debugln("Bridge built without end-to-bridge encryption") - return nil -} - -var NoSessionFound = errors.New("nil") diff --git a/portal.go b/portal.go index e71f96a..e26b461 100644 --- a/portal.go +++ b/portal.go @@ -366,7 +366,7 @@ func (portal *Portal) SyncParticipants(metadata *groupme.Group) { if user != nil { changed = levels.EnsureUserLevel(user.MXID, expectedLevel) || changed } - puppet.Sync(nil, portal.MXID, *participant) //why nil whynot + puppet.Sync(nil, participant, false, false) } if changed { _, err = portal.MainIntent().SetPowerLevels(portal.MXID, levels) @@ -396,6 +396,10 @@ func (portal *Portal) SyncParticipants(metadata *groupme.Group) { } } +func (user *User) updateAvatar(gmdi groupme.ID, avatarID *string, avatarURL *id.ContentURI, avatarSet *bool, log log.Logger, intent *appservice.IntentAPI) bool { + return false +} + func (portal *Portal) UpdateAvatar(user *User, avatar string, updateInfo bool) bool { // if len(avatar) == 0 { // var err error @@ -670,29 +674,27 @@ type BridgeInfoContent struct { Channel BridgeInfoSection `json:"channel"` } -var ( - StateBridgeInfo = event.Type{Type: "m.bridge", Class: event.StateEventType} - StateHalfShotBridgeInfo = event.Type{Type: "uk.half-shot.bridge", Class: event.StateEventType} -) +func (portal *Portal) getBridgeInfoStateKey() string { + return fmt.Sprintf("com.beeper.groupme://groupme/%s", portal.Key.GMID) +} -func (portal *Portal) getBridgeInfo() (string, BridgeInfoContent) { - bridgeInfo := BridgeInfoContent{ +func (portal *Portal) getBridgeInfo() (string, event.BridgeEventContent) { + bridgeInfo := event.BridgeEventContent{ BridgeBot: portal.bridge.Bot.UserID, Creator: portal.MainIntent().UserID, - Protocol: BridgeInfoSection{ - ID: "whatsapp", - DisplayName: "WhatsApp", - AvatarURL: id.ContentURIString(portal.bridge.Config.AppService.Bot.Avatar), - ExternalURL: "https://www.whatsapp.com/", + Protocol: event.BridgeInfoSection{ + ID: "groupme", + DisplayName: "GroupMe", + AvatarURL: portal.bridge.Config.AppService.Bot.ParsedAvatar.CUString(), + ExternalURL: "https://www.groupme.com/", }, - Channel: BridgeInfoSection{ + Channel: event.BridgeInfoSection{ ID: portal.Key.GMID.String(), DisplayName: portal.Name, AvatarURL: portal.AvatarURL.CUString(), }, } - bridgeInfoStateKey := fmt.Sprintf("net.maunium.whatsapp://whatsapp/%s", portal.Key.GMID) - return bridgeInfoStateKey, bridgeInfo + return portal.getBridgeInfoStateKey(), bridgeInfo } func (portal *Portal) UpdateBridgeInfo() { @@ -702,11 +704,12 @@ func (portal *Portal) UpdateBridgeInfo() { } portal.log.Debugln("Updating bridge info...") stateKey, content := portal.getBridgeInfo() - _, err := portal.MainIntent().SendStateEvent(portal.MXID, StateBridgeInfo, stateKey, content) + _, err := portal.MainIntent().SendStateEvent(portal.MXID, event.StateBridge, stateKey, content) if err != nil { portal.log.Warnln("Failed to update m.bridge:", err) } - _, err = portal.MainIntent().SendStateEvent(portal.MXID, StateHalfShotBridgeInfo, stateKey, content) + // TODO remove this once https://github.com/matrix-org/matrix-doc/pull/2346 is in spec + _, err = portal.MainIntent().SendStateEvent(portal.MXID, event.StateHalfShotBridge, stateKey, content) if err != nil { portal.log.Warnln("Failed to update uk.half-shot.bridge:", err) } @@ -764,12 +767,12 @@ func (portal *Portal) CreateMatrixRoom(user *User) error { Parsed: portal.GetBasePowerLevels(), }, }, { - Type: StateBridgeInfo, + Type: event.StateBridge, Content: event.Content{Parsed: bridgeInfo}, StateKey: &bridgeInfoStateKey, }, { // TODO remove this once https://github.com/matrix-org/matrix-doc/pull/2346 is in spec - Type: StateHalfShotBridgeInfo, + Type: event.StateHalfShotBridge, Content: event.Content{Parsed: bridgeInfo}, StateKey: &bridgeInfoStateKey, }} @@ -911,6 +914,10 @@ func isGatewayError(err error) bool { return errors.As(err, &httpErr) && (httpErr.IsStatus(http.StatusBadGateway) || httpErr.IsStatus(http.StatusGatewayTimeout)) } +func (portal *Portal) sendMainIntentMessage(content *event.MessageEventContent) (*mautrix.RespSendEvent, error) { + return portal.sendMessage(portal.MainIntent(), event.EventMessage, content, nil, 0) +} + func (portal *Portal) encrypt(intent *appservice.IntentAPI, content *event.Content, eventType event.Type) (event.Type, error) { if !portal.Encrypted || portal.bridge.Crypto == nil { return eventType, nil @@ -958,7 +965,7 @@ func (portal *Portal) handleAttachment(intent *appservice.IntentAPI, attachment } data, uploadMimeType, file := portal.encryptFile(*imgData, mime) - uploaded, err := portal.uploadWithRetry(intent, data, uploadMimeType, MediaUploadRetries) + uploaded, err := intent.UploadBytes(data, uploadMimeType) if err != nil { if errors.Is(err, mautrix.MTooLarge) { err = errors.New("homeserver rejected too large file") @@ -1006,7 +1013,7 @@ func (portal *Portal) handleAttachment(intent *appservice.IntentAPI, attachment } data, uploadMimeType, file := portal.encryptFile(vidContents, mime) - uploaded, err := portal.uploadWithRetry(intent, data, uploadMimeType, MediaUploadRetries) + uploaded, err := intent.UploadBytes(data, uploadMimeType) if err != nil { if errors.Is(err, mautrix.MTooLarge) { err = errors.New("homeserver rejected too large file") @@ -1046,7 +1053,7 @@ func (portal *Portal) handleAttachment(intent *appservice.IntentAPI, attachment } data, uploadMimeType, file := portal.encryptFile(fileData, fmime) - uploaded, err := portal.uploadWithRetry(intent, data, uploadMimeType, MediaUploadRetries) + uploaded, err := intent.UploadBytes(data, uploadMimeType) if err != nil { if errors.Is(err, mautrix.MTooLarge) { err = errors.New("homeserver rejected too large file") @@ -1120,149 +1127,6 @@ func (portal *Portal) handleAttachment(intent *appservice.IntentAPI, attachment } // return nil, true, errors.New("Unknown type") } -func (portal *Portal) HandleMediaMessage(source *User, msg mediaMessage) { - // intent := portal.startHandling(source, msg.info) - // if intent == nil { - // return - // } - // - // data, err := msg.download() - // if err == whatsapp.ErrMediaDownloadFailedWith404 || err == whatsapp.ErrMediaDownloadFailedWith410 { - // portal.log.Warnfln("Failed to download media for %s: %v. Calling LoadMediaInfo and retrying download...", msg.info.Id, err) - // _, err = source.Conn.LoadMediaInfo(msg.info.RemoteJid, msg.info.Id, msg.info.FromMe) - // if err != nil { - // portal.sendMediaBridgeFailure(source, intent, msg.info, fmt.Errorf("failed to load media info: %w", err)) - // return - // } - // data, err = msg.download() - // } - // if err == whatsapp.ErrNoURLPresent { - // portal.log.Debugfln("No URL present error for media message %s, ignoring...", msg.info.Id) - // return - // } else if err != nil { - // portal.sendMediaBridgeFailure(source, intent, msg.info, err) - // return - // } - // - // var width, height int - // if strings.HasPrefix(msg.mimeType, "image/") { - // cfg, _, _ := image.DecodeConfig(bytes.NewReader(data)) - // width, height = cfg.Width, cfg.Height - // } - // - // data, uploadMimeType, file := portal.encryptFile(data, msg.mimeType) - // - // uploaded, err := portal.uploadWithRetry(intent, data, uploadMimeType, MediaUploadRetries) - // if err != nil { - // if errors.Is(err, mautrix.MTooLarge) { - // portal.sendMediaBridgeFailure(source, intent, msg.info, errors.New("homeserver rejected too large file")) - // } else if httpErr := err.(mautrix.HTTPError); httpErr.IsStatus(413) { - // portal.sendMediaBridgeFailure(source, intent, msg.info, errors.New("proxy rejected too large file")) - // } else { - // portal.sendMediaBridgeFailure(source, intent, msg.info, fmt.Errorf("failed to upload media: %w", err)) - // } - // return - // } - // - // if msg.fileName == "" { - // mimeClass := strings.Split(msg.mimeType, "/")[0] - // switch mimeClass { - // case "application": - // msg.fileName = "file" - // default: - // msg.fileName = mimeClass - // } - // - // exts, _ := mime.ExtensionsByType(msg.mimeType) - // if exts != nil && len(exts) > 0 { - // msg.fileName += exts[0] - // } - // } - // - // content := &event.MessageEventContent{ - // Body: msg.fileName, - // File: file, - // Info: &event.FileInfo{ - // Size: len(data), - // MimeType: msg.mimeType, - // Width: width, - // Height: height, - // Duration: int(msg.length), - // }, - // } - // if content.File != nil { - // content.File.URL = uploaded.ContentURI.CUString() - // } else { - // content.URL = uploaded.ContentURI.CUString() - // } - // portal.SetReply(content, msg.context) - // - // if msg.thumbnail != nil && portal.bridge.Config.Bridge.WhatsappThumbnail { - // thumbnailMime := http.DetectContentType(msg.thumbnail) - // thumbnailCfg, _, _ := image.DecodeConfig(bytes.NewReader(msg.thumbnail)) - // thumbnailSize := len(msg.thumbnail) - // thumbnail, thumbnailUploadMime, thumbnailFile := portal.encryptFile(msg.thumbnail, thumbnailMime) - // uploadedThumbnail, err := intent.UploadBytes(thumbnail, thumbnailUploadMime) - // if err != nil { - // portal.log.Warnfln("Failed to upload thumbnail for %s: %v", msg.info.Id, err) - // } else if uploadedThumbnail != nil { - // if thumbnailFile != nil { - // thumbnailFile.URL = uploadedThumbnail.ContentURI.CUString() - // content.Info.ThumbnailFile = thumbnailFile - // } else { - // content.Info.ThumbnailURL = uploadedThumbnail.ContentURI.CUString() - // } - // content.Info.ThumbnailInfo = &event.FileInfo{ - // Size: thumbnailSize, - // Width: thumbnailCfg.Width, - // Height: thumbnailCfg.Height, - // MimeType: thumbnailMime, - // } - // } - // } - // - // switch strings.ToLower(strings.Split(msg.mimeType, "/")[0]) { - // case "image": - // if !msg.sendAsSticker { - // content.MsgType = event.MsgImage - // } - // case "video": - // content.MsgType = event.MsgVideo - // case "audio": - // content.MsgType = event.MsgAudio - // default: - // content.MsgType = event.MsgFile - // } - // - // _, _ = intent.UserTyping(portal.MXID, false, 0) - // ts := int64(msg.info.Timestamp * 1000) - // eventType := event.EventMessage - // if msg.sendAsSticker { - // eventType = event.EventSticker - // } - // resp, err := portal.sendMessage(intent, eventType, content, ts) - // if err != nil { - // portal.log.Errorfln("Failed to handle message %s: %v", msg.info.Id, err) - // return - // } - // - // if len(msg.caption) > 0 { - // captionContent := &event.MessageEventContent{ - // Body: msg.caption, - // MsgType: event.MsgNotice, - // } - // - // portal.bridge.Formatter.ParseWhatsApp(captionContent, msg.context.MentionedJID) - // - // _, err := portal.sendMessage(intent, event.EventMessage, captionContent, ts) - // if err != nil { - // portal.log.Warnfln("Failed to handle caption of message %s: %v", msg.info.Id, err) - // } - // // TODO store caption mxid? - // } - // - // portal.finishHandling(source, msg.info.Source, resp.EventID) -} func (portal *Portal) HandleTextMessage(source *User, message *groupme.Message) { intent := portal.startHandling(source, message) @@ -1592,19 +1456,7 @@ func (portal *Portal) convertMatrixMessage(sender *User, evt *event.Event) ([]*g // } } relaybotFormatted := false - if sender.NeedsRelaybot(portal) { - if !portal.HasRelaybot() { - if sender.HasSession() { - portal.log.Debugln("Database says", sender.MXID, "not in chat and no relaybot, but trying to send anyway") - } else { - portal.log.Debugln("Ignoring message from", sender.MXID, "in chat with no relaybot") - return nil, sender - } - } else { - relaybotFormatted = portal.addRelaybotFormat(sender, content) - sender = portal.bridge.Relaybot - } - } + if evt.Type == event.EventSticker { content.MsgType = event.MsgImage } else if content.MsgType == event.MsgImage && content.GetInfo().MimeType == "image/gif" { @@ -1720,34 +1572,9 @@ func (portal *Portal) wasMessageSent(sender *User, id string) bool { return true } -func (portal *Portal) sendErrorMessage(message string) id.EventID { - resp, err := portal.sendMainIntentMessage(event.MessageEventContent{ - MsgType: event.MsgNotice, - Body: fmt.Sprintf("\u26a0 Your message may not have been bridged: %v", message), - }) - if err != nil { - portal.log.Warnfln("Failed to send bridging error message:", err) - return "" - } - return resp.EventID -} - -func (portal *Portal) sendDeliveryReceipt(eventID id.EventID) { - if portal.bridge.Config.Bridge.DeliveryReceipts { - err := portal.bridge.Bot.MarkRead(portal.MXID, eventID) - if err != nil { - portal.log.Debugfln("Failed to send delivery receipt for %s: %v", eventID, err) - } - } -} - var timeout = errors.New("message sending timed out") func (portal *Portal) HandleMatrixMessage(sender *User, evt *event.Event) { - if !portal.HasRelaybot() && ((portal.IsPrivateChat() && sender.JID != portal.Key.Receiver) || - portal.sendMatrixConnectionError(sender, evt.ID)) { - return - } portal.log.Debugfln("Received event %s", evt.ID) info, sender := portal.convertMatrixMessage(sender, evt) if info == nil { @@ -1762,11 +1589,9 @@ func (portal *Portal) HandleMatrixMessage(sender *User, evt *event.Event) { portal.log.Warnln("Unable to handle message from Matrix", evt.ID) //TODO handle deleted room and such } else { - portal.markHandled(sender, i, evt.ID) } } - } func (portal *Portal) sendRaw(sender *User, evt *event.Event, info *groupme.Message, retries int) (*groupme.Message, error) { @@ -1892,7 +1717,7 @@ func (portal *Portal) HandleMatrixRedaction(sender *User, evt *event.Event) { func (portal *Portal) Delete() { portal.Portal.Delete() portal.bridge.portalsLock.Lock() - delete(portal.bridge.portalsByJID, portal.Key) + delete(portal.bridge.portalsByGMID, portal.Key) if len(portal.MXID) > 0 { delete(portal.bridge.portalsByMXID, portal.MXID) } @@ -1976,7 +1801,7 @@ func (portal *Portal) HandleMatrixLeave(sender *User) { return } else { // TODO should we somehow deduplicate this call if this leave was sent by the bridge? - err := sender.Client.RemoveFromGroup(sender.JID, portal.Key.GMID) + err := sender.Client.RemoveFromGroup(sender.GMID, portal.Key.GMID) if err != nil { portal.log.Errorfln("Failed to leave group as %s: %v", sender.MXID, err) return diff --git a/provisioning.go b/provisioning.go index e1d129c..0afa7e3 100644 --- a/provisioning.go +++ b/provisioning.go @@ -34,8 +34,8 @@ type ProvisioningAPI struct { func (prov *ProvisioningAPI) Init() { prov.log = prov.bridge.Log.Sub("Provisioning") - prov.log.Debugln("Enabling provisioning API at", prov.bridge.Config.AppService.Provisioning.Prefix) - r := prov.bridge.AS.Router.PathPrefix(prov.bridge.Config.AppService.Provisioning.Prefix).Subrouter() + prov.log.Debugln("Enabling provisioning API at", prov.bridge.Config.Bridge.Provisioning.Prefix) + r := prov.bridge.AS.Router.PathPrefix(prov.bridge.Config.Bridge.Provisioning.Prefix).Subrouter() r.Use(prov.AuthMiddleware) r.HandleFunc("/ping", prov.Ping).Methods(http.MethodGet) r.HandleFunc("/login", prov.Login) @@ -61,7 +61,7 @@ func (prov *ProvisioningAPI) AuthMiddleware(h http.Handler) http.Handler { } else if strings.HasPrefix(auth, "Bearer ") { auth = auth[len("Bearer "):] } - if auth != prov.bridge.Config.AppService.Provisioning.SharedSecret { + if auth != prov.bridge.Config.Bridge.Provisioning.SharedSecret { jsonResponse(w, http.StatusForbidden, map[string]interface{}{ "error": "Invalid auth token", "errcode": "M_FORBIDDEN", @@ -150,7 +150,7 @@ func (prov *ProvisioningAPI) Disconnect(w http.ResponseWriter, r *http.Request) // } else if len(sess.Wid) > 0 { // user.SetSession(&sess) // } - user.bridge.Metrics.TrackConnectionState(user.JID, false) + user.bridge.Metrics.TrackConnectionState(user.GMID, false) jsonResponse(w, http.StatusOK, Response{true, "Disconnected from WhatsApp"}) } diff --git a/puppet.go b/puppet.go index 15a2dcc..c68d426 100644 --- a/puppet.go +++ b/puppet.go @@ -18,9 +18,8 @@ package main import ( "fmt" - "os" "regexp" - "strings" + "sync" log "maunium.net/go/maulogger/v2" @@ -31,7 +30,6 @@ import ( "maunium.net/go/mautrix/id" "github.com/beeper/groupme/database" - "github.com/beeper/groupme/groupmeext" ) var userIDRegex *regexp.Regexp @@ -162,8 +160,7 @@ func (bridge *GMBridge) dbPuppetsToPuppets(dbPuppets []*database.Puppet) []*Pupp func (bridge *GMBridge) FormatPuppetMXID(gmid groupme.ID) id.UserID { return id.NewUserID( - bridge.Config.Bridge.FormatUsername( - gmid), + bridge.Config.Bridge.FormatUsername(gmid.String()), bridge.Config.Homeserver.Domain) } @@ -191,17 +188,16 @@ type Puppet struct { customIntent *appservice.IntentAPI customTypingIn map[id.RoomID]bool customUser *User + + syncLock sync.Mutex } func (puppet *Puppet) PhoneNumber() string { - println("phone num") - return strings.Replace(puppet.GMID, whatsappExt.NewUserSuffix, "", 1) + return puppet.GMID.String() } func (puppet *Puppet) IntentFor(portal *Portal) *appservice.IntentAPI { - if (!portal.IsPrivateChat() && puppet.customIntent == nil) || - (portal.backfilling && portal.bridge.Config.Bridge.InviteOwnPuppetForBackfilling) || - portal.Key.GMID == puppet.GMID { + if puppet.customIntent == nil || portal.Key.GMID == puppet.GMID { return puppet.DefaultIntent() } return puppet.customIntent @@ -219,132 +215,95 @@ func (puppet *Puppet) DefaultIntent() *appservice.IntentAPI { // //} -func (puppet *Puppet) UpdateAvatar(source *User, portalMXID id.RoomID, avatar string) bool { - memberRaw, _ := puppet.bridge.StateStore.TryGetMemberRaw(portalMXID, puppet.MXID) //TODO Handle - - if memberRaw.Avatar == avatar { - return false // up to date - } - - if len(avatar) == 0 { - var err error - // err = puppet.DefaultIntent().SetRoomAvatarURL(portalMXID, id.ContentURI{}) - - if err != nil { - puppet.log.Warnln("Failed to remove avatar:", err, puppet.MXID) - os.Exit(1) +func (puppet *Puppet) UpdateAvatar(source *User, forcePortalSync bool) bool { + changed := source.updateAvatar(puppet.GMID, &puppet.Avatar, &puppet.AvatarURL, &puppet.AvatarSet, puppet.log, puppet.DefaultIntent()) + if !changed || puppet.Avatar == "unauthorized" { + if forcePortalSync { + go puppet.updatePortalAvatar() } - memberRaw.Avatar = avatar - memberRaw.AvatarURL = "" - - go puppet.updatePortalAvatar() - - puppet.bridge.StateStore.SetMemberRaw(&memberRaw) //TODO handle - return true + return changed } - - //TODO check its actually groupme? - image, mime, err := groupmeext.DownloadImage(avatar + ".large") - if err != nil { - puppet.log.Warnln(err) - return false - } - - resp, err := puppet.DefaultIntent().UploadBytes(*image, mime) - if err != nil { - puppet.log.Warnln("Failed to upload avatar:", err) - return false - } - // err = puppet.DefaultIntent().SetRoomAvatarURL(portalMXID, resp.ContentURI) + err := puppet.DefaultIntent().SetAvatarURL(puppet.AvatarURL) if err != nil { puppet.log.Warnln("Failed to set avatar:", err) + } else { + puppet.AvatarSet = true } - - memberRaw.AvatarURL = resp.ContentURI.String() - memberRaw.Avatar = avatar - - puppet.bridge.StateStore.SetMemberRaw(&memberRaw) //TODO handle - go puppet.updatePortalAvatar() return true } -func (puppet *Puppet) UpdateName(source *User, portalMXID id.RoomID, contact groupme.Member) bool { - newName, _ := puppet.bridge.Config.Bridge.FormatDisplayname(contact) - - memberRaw, _ := puppet.bridge.StateStore.TryGetMemberRaw(portalMXID, puppet.MXID) //TODO Handle - - if memberRaw.DisplayName != newName { //&& quality >= puppet.NameQuality[portalMXID] { - var err error - // err = puppet.DefaultIntent().SetRoomDisplayName(portalMXID, newName) - +func (puppet *Puppet) UpdateName(member groupme.Member, forcePortalSync bool) bool { + newName := puppet.bridge.Config.Bridge.FormatDisplayname(puppet.GMID, member) + if puppet.Displayname != newName || !puppet.NameSet { + oldName := puppet.Displayname + puppet.Displayname = newName + puppet.NameSet = false + err := puppet.DefaultIntent().SetDisplayName(newName) if err == nil { - memberRaw.DisplayName = newName - // puppet.NameQuality[portalMXID] = quality - - puppet.bridge.StateStore.SetMemberRaw(&memberRaw) //TODO handle; maybe .Update() ? + puppet.log.Debugln("Updated name", oldName, "->", newName) + puppet.NameSet = true go puppet.updatePortalName() } else { puppet.log.Warnln("Failed to set display name:", err) } return true + } else if forcePortalSync { + go puppet.updatePortalName() } return false } func (puppet *Puppet) updatePortalMeta(meta func(portal *Portal)) { - if puppet.bridge.Config.Bridge.PrivateChatPortalMeta { - for _, portal := range puppet.bridge.GetAllPortalsByJID(puppet.JID) { + if puppet.bridge.Config.Bridge.PrivateChatPortalMeta || puppet.bridge.Config.Bridge.Encryption.Allow { + for _, portal := range puppet.bridge.GetAllPortalsByGMID(puppet.GMID) { + if !puppet.bridge.Config.Bridge.PrivateChatPortalMeta && !portal.Encrypted { + continue + } + // Get room create lock to prevent races between receiving contact info and room creation. + portal.roomCreateLock.Lock() meta(portal) + portal.roomCreateLock.Unlock() } } } func (puppet *Puppet) updatePortalAvatar() { puppet.updatePortalMeta(func(portal *Portal) { - - m, _ := puppet.bridge.StateStore.TryGetMemberRaw(portal.MXID, puppet.MXID) + if portal.Avatar == puppet.Avatar && portal.AvatarURL == puppet.AvatarURL && portal.AvatarSet { + return + } + portal.AvatarURL = puppet.AvatarURL + portal.Avatar = puppet.Avatar + portal.AvatarSet = false + defer portal.Update(nil) if len(portal.MXID) > 0 { - _, err := portal.MainIntent().SetRoomAvatar(portal.MXID, id.MustParseContentURI(m.AvatarURL)) + _, err := portal.MainIntent().SetRoomAvatar(portal.MXID, puppet.AvatarURL) if err != nil { portal.log.Warnln("Failed to set avatar:", err) + } else { + portal.AvatarSet = true + portal.UpdateBridgeInfo() } } - portal.AvatarURL = id.MustParseContentURI(m.AvatarURL) - portal.Avatar = m.Avatar - portal.Update() }) } func (puppet *Puppet) updatePortalName() { puppet.updatePortalMeta(func(portal *Portal) { - m, _ := puppet.bridge.StateStore.TryGetMemberRaw(portal.MXID, puppet.MXID) - if len(portal.MXID) > 0 { - _, err := portal.MainIntent().SetRoomName(portal.MXID, m.DisplayName) - if err != nil { - portal.log.Warnln("Failed to set name:", err) - } - } - portal.Name = m.DisplayName - portal.Update() + portal.UpdateName(puppet.Displayname, groupme.ID(""), true) }) } -func (puppet *Puppet) Sync(source *User, portalMXID id.RoomID, contact groupme.Member) { - if contact.UserID.String() == "system" { - puppet.log.Warnln("Trying to sync system puppet") - return - } - +func (puppet *Puppet) Sync(source *User, member *groupme.Member, forceAvatarSync, forcePortalSync bool) { + puppet.syncLock.Lock() + defer puppet.syncLock.Unlock() err := puppet.DefaultIntent().EnsureRegistered() if err != nil { puppet.log.Errorln("Failed to ensure registered:", err) } - update := false - update = puppet.UpdateName(source, portalMXID, contact) || update - update = puppet.UpdateAvatar(source, portalMXID, contact.ImageURL) || update - if update { - puppet.Update() - } + puppet.log.Debugfln("Syncing info through %s", source.GMID) + + // TODO } diff --git a/user.go b/user.go index c8accb0..2c0494e 100644 --- a/user.go +++ b/user.go @@ -21,7 +21,6 @@ import ( "errors" "fmt" "net/http" - "sort" "strings" "sync" "time" @@ -200,9 +199,8 @@ func (br *GMBridge) NewUser(dbUser *database.User) *User { chatListReceived: make(chan struct{}, 1), syncPortalsDone: make(chan struct{}, 1), - syncStart: make(chan struct{}, 1), messageInput: make(chan PortalMessage), - messageOutput: make(chan PortalMessage, br.Config.Bridge.UserMessageBuffer), + messageOutput: make(chan PortalMessage, br.Config.Bridge.PortalMessageBuffer), } user.PermissionLevel = user.bridge.Config.Bridge.Permissions.Get(user.MXID) @@ -349,7 +347,7 @@ func (user *User) Connect() bool { return false } - user.log.Debugln("Connecting to WhatsApp") + user.log.Debugln("Connecting to GroupMe") timeout := time.Duration(user.bridge.Config.GroupMe.ConnectionTimeout) if timeout == 0 { timeout = 20 @@ -365,7 +363,7 @@ func (user *User) Connect() bool { func (user *User) RestoreSession() bool { if len(user.Token) > 0 { - err := user.Conn.SubscribeToUser(context.TODO(), groupme.ID(user.GMID), user.Token.String()) + err := user.Conn.SubscribeToUser(context.TODO(), groupme.ID(user.GMID), user.Token) if err != nil { fmt.Println(err) } @@ -447,19 +445,15 @@ func (user *User) PostLogin() { user.bridge.Metrics.TrackConnectionState(user.GMID, true) user.bridge.Metrics.TrackLoginState(user.GMID, true) user.bridge.Metrics.TrackBufferLength(user.MXID, 0) - go user.intPostLogin() + // go user.intPostLogin() } func (user *User) tryAutomaticDoublePuppeting() { - if len(user.bridge.Config.Bridge.LoginSharedSecret) == 0 { - // Automatic login not enabled - return - } else if _, homeserver, _ := user.MXID.Parse(); homeserver != user.bridge.Config.Homeserver.Domain { - // user is on another homeserver + if !user.bridge.Config.CanAutoDoublePuppet(user.MXID) { return } user.log.Debugln("Checking if double puppeting needs to be enabled") - puppet := user.bridge.GetPuppetByGMID(user.JID) + puppet := user.bridge.GetPuppetByGMID(user.GMID) if len(puppet.CustomMXID) > 0 { user.log.Debugln("User already has double-puppeting enabled") // Custom puppet already enabled @@ -519,85 +513,84 @@ func (user *User) postConnPing() bool { return true } -func (user *User) intPostLogin() { - defer user.syncWait.Done() - user.lastReconnection = time.Now().Unix() - user.Client = groupmeext.NewClient(user.Token) - if len(user.JID) == 0 { - myuser, err := user.Client.MyUser(context.TODO()) - if err != nil { - log.Fatal(err) //TODO - } - user.JID = myuser.ID.String() - } - user.Update() +// func (user *User) intPostLogin() { +// defer user.syncWait.Done() +// user.lastReconnection = time.Now().Unix() +// user.Client = groupmeext.NewClient(user.Token) +// if len(user.JID) == 0 { +// myuser, err := user.Client.MyUser(context.TODO()) +// if err != nil { +// log.Fatal(err) //TODO +// } +// user.JID = myuser.ID.String() +// } +// user.Update() - user.createCommunity() - user.tryAutomaticDoublePuppeting() +// user.tryAutomaticDoublePuppeting() - user.log.Debugln("Waiting for chat list receive confirmation") - user.HandleChatList() - select { - case <-user.chatListReceived: - user.log.Debugln("Chat list receive confirmation received in PostLogin") - case <-time.After(time.Duration(user.bridge.Config.Bridge.ChatListWait) * time.Second): - user.log.Warnln("Timed out waiting for chat list to arrive!") - user.postConnPing() - return - } +// user.log.Debugln("Waiting for chat list receive confirmation") +// user.HandleChatList() +// select { +// case <-user.chatListReceived: +// user.log.Debugln("Chat list receive confirmation received in PostLogin") +// case <-time.After(time.Duration(user.bridge.Config.Bridge.ChatListWait) * time.Second): +// user.log.Warnln("Timed out waiting for chat list to arrive!") +// user.postConnPing() +// return +// } - if !user.postConnPing() { - user.log.Debugln("Post-connection ping failed, unlocking processing of incoming messages.") - return - } +// if !user.postConnPing() { +// user.log.Debugln("Post-connection ping failed, unlocking processing of incoming messages.") +// return +// } - user.log.Debugln("Waiting for portal sync complete confirmation") - select { - case <-user.syncPortalsDone: - user.log.Debugln("Post-connection portal sync complete, unlocking processing of incoming messages.") - // TODO this is too short, maybe a per-portal duration? - case <-time.After(time.Duration(user.bridge.Config.Bridge.PortalSyncWait) * time.Second): - user.log.Warnln("Timed out waiting for portal sync to complete! Unlocking processing of incoming messages.") - } -} +// user.log.Debugln("Waiting for portal sync complete confirmation") +// select { +// case <-user.syncPortalsDone: +// user.log.Debugln("Post-connection portal sync complete, unlocking processing of incoming messages.") +// // TODO this is too short, maybe a per-portal duration? +// case <-time.After(time.Duration(user.bridge.Config.Bridge.PortalSyncWait) * time.Second): +// user.log.Warnln("Timed out waiting for portal sync to complete! Unlocking processing of incoming messages.") +// } +// } func (user *User) HandleChatList() { - chatMap := make(map[string]groupme.Group) + chatMap := map[groupme.ID]groupme.Group{} chats, err := user.Client.IndexAllGroups() if err != nil { user.log.Errorln("chat sync error", err) //TODO: handle return } for _, chat := range chats { - chatMap[chat.ID.String()] = *chat + chatMap[chat.ID] = *chat } user.GroupList = chatMap - dmMap := make(map[string]groupme.Chat) + dmMap := map[groupme.ID]groupme.Chat{} dms, err := user.Client.IndexAllChats() if err != nil { user.log.Errorln("chat sync error", err) //TODO: handle return } for _, dm := range dms { - dmMap[dm.OtherUser.ID.String()] = *dm + dmMap[dm.OtherUser.ID] = *dm } user.ChatList = dmMap - userMap := make(map[string]groupme.User) + userMap := map[groupme.ID]groupme.User{} users, err := user.Client.IndexAllRelations() if err != nil { user.log.Errorln("Error syncing user list, continuing sync", err) } for _, u := range users { - puppet := user.bridge.GetPuppetByJID(u.ID.String()) + puppet := user.bridge.GetPuppetByGMID(u.ID) // "" for overall user not related to one group - puppet.Sync(nil, "", groupme.Member{ + puppet.Sync(nil, &groupme.Member{ UserID: u.ID, Nickname: u.Name, ImageURL: u.AvatarURL, - }) - userMap[u.ID.String()] = *u + }, false, false) + userMap[u.ID] = *u } user.RelationList = userMap @@ -607,90 +600,88 @@ func (user *User) HandleChatList() { } func (user *User) syncPortals(createAll bool) { + // user.log.Infoln("Reading chat list") - user.log.Infoln("Reading chat list") + // chats := make(ChatList, 0, len(user.GroupList)+len(user.ChatList)) + // portalKeys := make([]database.PortalKeyWithMeta, 0, cap(chats)) - chats := make(ChatList, 0, len(user.GroupList)+len(user.ChatList)) - existingKeys := user.GetInCommunityMap() - portalKeys := make([]database.PortalKeyWithMeta, 0, cap(chats)) + // for _, group := range user.GroupList { - for _, group := range user.GroupList { + // portal := user.bridge.GetPortalByJID(database.GroupPortalKey(group.ID.String())) - portal := user.bridge.GetPortalByJID(database.GroupPortalKey(group.ID.String())) + // chats = append(chats, Chat{ + // Portal: portal, + // LastMessageTime: uint64(group.UpdatedAt.ToTime().Unix()), + // Group: &group, + // }) + // } + // for _, dm := range user.ChatList { + // portal := user.bridge.GetPortalByJID(database.NewPortalKey(dm.OtherUser.ID.String(), user.JID)) + // chats = append(chats, Chat{ + // Portal: portal, + // LastMessageTime: uint64(dm.UpdatedAt.ToTime().Unix()), + // DM: &dm, + // }) + // } - chats = append(chats, Chat{ - Portal: portal, - LastMessageTime: uint64(group.UpdatedAt.ToTime().Unix()), - Group: &group, - }) - } - for _, dm := range user.ChatList { - portal := user.bridge.GetPortalByJID(database.NewPortalKey(dm.OtherUser.ID.String(), user.JID)) - chats = append(chats, Chat{ - Portal: portal, - LastMessageTime: uint64(dm.UpdatedAt.ToTime().Unix()), - DM: &dm, - }) - } + // for _, chat := range chats { + // var inCommunity, ok bool + // if inCommunity, ok = existingKeys[chat.Portal.Key]; !ok || !inCommunity { + // inCommunity = user.addPortalToCommunity(chat.Portal) + // if chat.Portal.IsPrivateChat() { + // puppet := user.bridge.GetPuppetByJID(chat.Portal.Key.GMID) + // user.addPuppetToCommunity(puppet) + // } + // } + // portalKeys = append(portalKeys, database.PortalKeyWithMeta{PortalKey: chat.Portal.Key, InCommunity: inCommunity}) + // } + // user.log.Infoln("Read chat list, updating user-portal mapping") - for _, chat := range chats { - var inCommunity, ok bool - if inCommunity, ok = existingKeys[chat.Portal.Key]; !ok || !inCommunity { - inCommunity = user.addPortalToCommunity(chat.Portal) - if chat.Portal.IsPrivateChat() { - puppet := user.bridge.GetPuppetByJID(chat.Portal.Key.GMID) - user.addPuppetToCommunity(puppet) - } - } - portalKeys = append(portalKeys, database.PortalKeyWithMeta{PortalKey: chat.Portal.Key, InCommunity: inCommunity}) - } - user.log.Infoln("Read chat list, updating user-portal mapping") + // err := user.SetPortalKeys(portalKeys) + // if err != nil { + // user.log.Warnln("Failed to update user-portal mapping:", err) + // } + // sort.Sort(chats) + // limit := user.bridge.Config.Bridge.InitialChatSync + // if limit < 0 { + // limit = len(chats) + // } + // now := uint64(time.Now().Unix()) + // user.log.Infoln("Syncing portals") - err := user.SetPortalKeys(portalKeys) - if err != nil { - user.log.Warnln("Failed to update user-portal mapping:", err) - } - sort.Sort(chats) - limit := user.bridge.Config.Bridge.InitialChatSync - if limit < 0 { - limit = len(chats) - } - now := uint64(time.Now().Unix()) - user.log.Infoln("Syncing portals") + // wg := sync.WaitGroup{} + // for i, chat := range chats { + // if chat.LastMessageTime+user.bridge.Config.Bridge.SyncChatMaxAge < now { + // break + // } + // wg.Add(1) + // go func(chat Chat, i int) { + // create := (chat.LastMessageTime >= user.LastConnection && user.LastConnection > 0) || i < limit + // if len(chat.Portal.MXID) > 0 || create || createAll { + // chat.Portal.Sync(user, chat.Group) + // err := chat.Portal.BackfillHistory(user, chat.LastMessageTime) + // if err != nil { + // chat.Portal.log.Errorln("Error backfilling history:", err) + // } + // } - wg := sync.WaitGroup{} - for i, chat := range chats { - if chat.LastMessageTime+user.bridge.Config.Bridge.SyncChatMaxAge < now { - break - } - wg.Add(1) - go func(chat Chat, i int) { - create := (chat.LastMessageTime >= user.LastConnection && user.LastConnection > 0) || i < limit - if len(chat.Portal.MXID) > 0 || create || createAll { - chat.Portal.Sync(user, chat.Group) - err := chat.Portal.BackfillHistory(user, chat.LastMessageTime) - if err != nil { - chat.Portal.log.Errorln("Error backfilling history:", err) - } - } + // wg.Done() + // }(chat, i) - wg.Done() - }(chat, i) - - } - wg.Wait() - //TODO: handle leave from groupme side - user.UpdateDirectChats(nil) - user.log.Infoln("Finished syncing portals") - select { - case user.syncPortalsDone <- struct{}{}: - default: - } + // } + // wg.Wait() + // //TODO: handle leave from groupme side + // user.UpdateDirectChats(nil) + // user.log.Infoln("Finished syncing portals") + // select { + // case user.syncPortalsDone <- struct{}{}: + // default: + // } } func (user *User) getDirectChats() map[id.UserID][]id.RoomID { res := make(map[id.UserID][]id.RoomID) - privateChats := user.bridge.DB.Portal.FindPrivateChats(user.JID) + privateChats := user.bridge.DB.Portal.FindPrivateChats(user.GMID) for _, portal := range privateChats { if len(portal.MXID) > 0 { res[user.bridge.FormatPuppetMXID(portal.Key.GMID)] = []id.RoomID{portal.MXID} @@ -715,11 +706,8 @@ func (user *User) UpdateDirectChats(chats map[id.UserID][]id.RoomID) { } user.log.Debugln("Updating m.direct list on homeserver") var err error - if user.bridge.Config.Homeserver.Asmux { - urlPath := intent.BuildBaseURL("_matrix", "client", "unstable", "net.maunium.asmux", "dms") - // _, err = intent.MakeFullRequest(method, urlPath, http.Header{ - // "X-Asmux-Auth": {user.bridge.AS.Registration.AppToken}, - // }, chats, nil) + if user.bridge.Config.Homeserver.Software == bridgeconfig.SoftwareAsmux { + urlPath := intent.BuildClientURL("unstable", "com.beeper.asmux", "dms") _, err = intent.MakeFullRequest(mautrix.FullRequest{ Method: method, URL: urlPath, @@ -772,9 +760,9 @@ func (user *User) syncPuppets(contacts map[string]whatsapp.Contact) { } func (user *User) updateLastConnectionIfNecessary() { - if user.LastConnection+60 < uint64(time.Now().Unix()) { - user.UpdateLastConnection() - } + // if user.LastConnection+60 < uint64(time.Now().Unix()) { + // user.UpdateLastConnection() + // } } func (user *User) HandleError(err error) { @@ -784,7 +772,7 @@ func (user *User) HandleError(err error) { if closed, ok := err.(*whatsapp.ErrConnectionClosed); ok { user.bridge.Metrics.TrackDisconnection(user.MXID) if closed.Code == 1000 && user.cleanDisconnection { - user.bridge.Metrics.TrackConnectionState(user.JID, false) + user.bridge.Metrics.TrackConnectionState(user.GMID, false) user.cleanDisconnection = false user.log.Infoln("Clean disconnection by server") return @@ -869,7 +857,7 @@ func (user *User) PortalKey(gmid groupme.ID) database.PortalKey { return database.NewPortalKey(gmid, user.GMID) } -func (user *User) GetPortalByJID(gmid groupme.ID) *Portal { +func (user *User) GetPortalByGMID(gmid groupme.ID) *Portal { return user.bridge.GetPortalByGMID(user.PortalKey(gmid)) } @@ -894,11 +882,11 @@ func (user *User) handleMessageLoop() { puppet := user.bridge.GetPuppetByGMID(msg.data.UserID) portal := user.bridge.GetPortalByGMID(msg.chat) if puppet != nil { - puppet.Sync(user, portal.MXID, groupme.Member{ + puppet.Sync(user, &groupme.Member{ UserID: msg.data.UserID, Nickname: msg.data.Name, ImageURL: msg.data.AvatarURL, - }) //TODO: add params or docs? + }, false, false) } portal.messages <- msg } @@ -984,19 +972,19 @@ func (user *User) HandleLikeIcon(_ groupme.ID, _, _ int, _ string) { } func (user *User) HandleNewNickname(groupID, userID groupme.ID, name string) { - puppet := user.bridge.GetPuppetByJID(userID.String()) + puppet := user.bridge.GetPuppetByGMID(userID) if puppet != nil { - puppet.UpdateName(user, user.GetPortalByJID(groupID.String()).MXID, groupme.Member{ + puppet.UpdateName(groupme.Member{ Nickname: name, UserID: userID, - }) + }, false) } } func (user *User) HandleNewAvatarInGroup(groupID, userID groupme.ID, url string) { - puppet := user.bridge.GetPuppetByJID(userID.String()) + puppet := user.bridge.GetPuppetByGMID(userID) if puppet != nil { - puppet.UpdateAvatar(user, user.GetPortalByJID(groupID.String()).MXID, url) + puppet.UpdateAvatar(user, false) } } @@ -1262,7 +1250,3 @@ type FakeMessage struct { //func (user *User) HandleUnknownBinaryNode(node *waBinary.Node) { // user.log.Debugfln("Unknown binary message: %+v", node) //} - -func (user *User) NeedsRelaybot(portal *Portal) bool { - return !user.HasSession() || !user.IsInPortal(portal.Key) -}