fixup! treewide: upgrading to latest mautrix standards
Signed-off-by: Sumner Evans <sumner@beeper.com>
This commit is contained in:
parent
853fad9576
commit
5b3be968d2
@ -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)
|
||||
|
1
main.go
1
main.go
@ -17,6 +17,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"sync"
|
||||
|
||||
"maunium.net/go/mautrix/bridge"
|
||||
|
208
messagetracking.go
Normal file
208
messagetracking.go
Normal 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
|
||||
}
|
18
no-crypto.go
18
no-crypto.go
@ -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
239
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
|
||||
|
@ -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
145
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
|
||||
}
|
||||
|
284
user.go
284
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)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user