fixup! treewide: upgrading to latest mautrix standards

Signed-off-by: Sumner Evans <sumner@beeper.com>
This commit is contained in:
Sumner Evans 2022-10-21 17:04:21 -05:00
parent 853fad9576
commit 5b3be968d2
No known key found for this signature in database
GPG Key ID: 8904527AB50022FD
8 changed files with 442 additions and 472 deletions

View File

@ -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)

View File

@ -17,6 +17,7 @@
package main
import (
_ "embed"
"sync"
"maunium.net/go/mautrix/bridge"

208
messagetracking.go Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
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
}

View File

@ -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")

239
portal.go
View File

@ -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

View File

@ -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"})
}

145
puppet.go
View File

@ -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
}

284
user.go
View File

@ -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)
}