Add option to not re-sync chat info and user avatars to avoid rate limits

This commit is contained in:
Tulir Asokan 2021-02-09 23:41:14 +02:00
parent 4304472fc9
commit 2188dc7701
9 changed files with 237 additions and 106 deletions

View file

@ -58,6 +58,8 @@ type BridgeConfig struct {
HistoryDisableNotifs bool `yaml:"initial_history_disable_notifications"`
RecoverChatSync int `yaml:"recovery_chat_sync_count"`
RecoverHistory bool `yaml:"recovery_history_backfill"`
ChatMetaSync bool `yaml:"chat_meta_sync"`
UserAvatarSync bool `yaml:"user_avatar_sync"`
SyncChatMaxAge uint64 `yaml:"sync_max_chat_age"`
SyncWithCustomPuppets bool `yaml:"sync_with_custom_puppets"`
@ -116,6 +118,8 @@ func (bc *BridgeConfig) setDefaults() {
bc.InitialHistoryFill = 20
bc.RecoverChatSync = -1
bc.RecoverHistory = true
bc.ChatMetaSync = true
bc.UserAvatarSync = true
bc.SyncChatMaxAge = 259200
bc.SyncWithCustomPuppets = true

View file

@ -20,6 +20,7 @@ import (
"bytes"
"database/sql"
"encoding/json"
"strings"
waProto "github.com/Rhymen/go-whatsapp/binary/proto"
@ -93,6 +94,10 @@ type Message struct {
Content *waProto.Message
}
func (msg *Message) IsFakeMXID() bool {
return strings.HasPrefix(msg.MXID.String(), "net.maunium.whatsapp.fake::")
}
func (msg *Message) Scan(row Scannable) *Message {
var content []byte
err := row.Scan(&msg.Chat.JID, &msg.Chat.Receiver, &msg.JID, &msg.MXID, &msg.Sender, &msg.Timestamp, &content)

View file

@ -134,6 +134,13 @@ bridge:
recovery_chat_sync_limit: -1
# Whether or not to sync history when recovering from downtime.
recovery_history_backfill: true
# Whether or not portal info should be fetched from the server when syncing,
# instead of relying on finding any changes in the message history.
# If you get 599 errors often, you should try disabling this.
chat_meta_sync: true
# Whether or not puppet avatars should be fetched from the server even if an avatar is already set.
# If you get 599 errors often, you should try disabling this.
user_avatar_sync: true
# Maximum number of seconds since last message in chat to skip
# syncing the chat in any case. This setting will take priority
# over both recovery_chat_sync_limit and initial_chat_sync_count.

4
go.mod
View file

@ -13,7 +13,7 @@ require (
gopkg.in/yaml.v2 v2.3.0
maunium.net/go/mauflag v1.0.0
maunium.net/go/maulogger/v2 v2.1.1
maunium.net/go/mautrix v0.8.0
maunium.net/go/mautrix v0.8.2
)
replace github.com/Rhymen/go-whatsapp => github.com/tulir/go-whatsapp v0.3.19
replace github.com/Rhymen/go-whatsapp => github.com/tulir/go-whatsapp v0.3.20

6
go.sum
View file

@ -141,6 +141,8 @@ github.com/tulir/go-whatsapp v0.3.18 h1:45pkdjEnAp6yV4RTSWCZn2Eenep+MN7Kndf/rRXQ
github.com/tulir/go-whatsapp v0.3.18/go.mod h1:U5+sm33vrv3wz62YyRM/VS7q2ObXkxU4Xqj/3KOmN9o=
github.com/tulir/go-whatsapp v0.3.19 h1:76VtmcjKGX8MbfJN9NNi1f0IVmigTLUcxqE1VRcovcQ=
github.com/tulir/go-whatsapp v0.3.19/go.mod h1:U5+sm33vrv3wz62YyRM/VS7q2ObXkxU4Xqj/3KOmN9o=
github.com/tulir/go-whatsapp v0.3.20 h1:nK92MgruqXwk+QlaAS39xhzHNbFvJIEgUIOUrN3i8Yc=
github.com/tulir/go-whatsapp v0.3.20/go.mod h1:U5+sm33vrv3wz62YyRM/VS7q2ObXkxU4Xqj/3KOmN9o=
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@ -234,3 +236,7 @@ maunium.net/go/mautrix v0.8.0-rc.4 h1:3JXoL2JJPE5nh/YSw9sv9dQA9ulma9yHTMOBMBY1xd
maunium.net/go/mautrix v0.8.0-rc.4/go.mod h1:TtVePxoEaw6+RZDKVajw66Yaj1lqLjH8l4FF3krsqWY=
maunium.net/go/mautrix v0.8.0 h1:G1jlVslNUTWEqaxuatHAMmzTWnGyoCIc4tAF5GpQJd8=
maunium.net/go/mautrix v0.8.0/go.mod h1:TtVePxoEaw6+RZDKVajw66Yaj1lqLjH8l4FF3krsqWY=
maunium.net/go/mautrix v0.8.1 h1:YvJGy7euB+x6Mz74jJ+G4NiyrLiX9pzmXpnQB9vFONg=
maunium.net/go/mautrix v0.8.1/go.mod h1:KiViCshKBUZwrVRvTOXsJBFfstvR/btxckHUbOPdu54=
maunium.net/go/mautrix v0.8.2 h1:E3NudQ/QolmE/yhHau8iCkbmcq6gCLvoEvukdqPFJu4=
maunium.net/go/mautrix v0.8.2/go.mod h1:KiViCshKBUZwrVRvTOXsJBFfstvR/btxckHUbOPdu54=

View file

@ -289,6 +289,10 @@ func (mx *MatrixHandler) HandleMembership(evt *event.Event) {
func (mx *MatrixHandler) HandleRoomMetadata(evt *event.Event) {
defer mx.bridge.Metrics.TrackEvent(evt.Type)()
if mx.shouldIgnoreEvent(evt) {
return
}
user := mx.bridge.GetUserByMXID(evt.Sender)
if user == nil || !user.Whitelisted || !user.IsConnected() {
return
@ -299,22 +303,7 @@ func (mx *MatrixHandler) HandleRoomMetadata(evt *event.Event) {
return
}
var resp <-chan string
var err error
switch content := evt.Content.Parsed.(type) {
case *event.RoomNameEventContent:
resp, err = user.Conn.UpdateGroupSubject(content.Name, portal.Key.JID)
case *event.TopicEventContent:
resp, err = user.Conn.UpdateGroupDescription(portal.Key.JID, content.Topic)
case *event.RoomAvatarEventContent:
return
}
if err != nil {
mx.log.Errorln(err)
} else {
out := <-resp
mx.log.Infoln(out)
}
portal.HandleMatrixMeta(user, evt)
}
func (mx *MatrixHandler) shouldIgnoreEvent(evt *event.Event) bool {

237
portal.go
View file

@ -204,12 +204,12 @@ func (portal *Portal) handleMessageLoop() {
}
}
portal.backfillLock.Lock()
portal.handleMessage(msg)
portal.handleMessage(msg, false)
portal.backfillLock.Unlock()
}
}
func (portal *Portal) handleMessage(msg PortalMessage) {
func (portal *Portal) handleMessage(msg PortalMessage, isBackfill bool) {
if len(portal.MXID) == 0 {
portal.log.Warnln("handleMessage called even though portal.MXID is empty")
return
@ -254,6 +254,8 @@ func (portal *Portal) handleMessage(msg PortalMessage) {
portal.HandleContactMessage(msg.source, data)
case whatsapp.LocationMessage:
portal.HandleLocationMessage(msg.source, data)
case whatsapp.StubMessage:
portal.HandleStubMessage(msg.source, data, isBackfill)
case whatsappExt.MessageRevocation:
portal.HandleMessageRevoke(msg.source, data)
case FakeMessage:
@ -421,30 +423,35 @@ func (portal *Portal) UpdateAvatar(user *User, avatar *whatsappExt.ProfilePicInf
}
}
if avatar.Status != 0 {
if avatar.Status == 404 {
avatar.Tag = "remove"
avatar.Status = 0
}
if avatar.Status != 0 || portal.Avatar == avatar.Tag {
return false
}
if portal.Avatar == avatar.Tag {
return false
if avatar.Tag == "remove" {
portal.AvatarURL = id.ContentURI{}
} else {
data, err := avatar.DownloadBytes()
if err != nil {
portal.log.Warnln("Failed to download avatar:", err)
return false
}
mimeType := http.DetectContentType(data)
resp, err := portal.MainIntent().UploadBytes(data, mimeType)
if err != nil {
portal.log.Warnln("Failed to upload avatar:", err)
return false
}
portal.AvatarURL = resp.ContentURI
}
data, err := avatar.DownloadBytes()
if err != nil {
portal.log.Warnln("Failed to download avatar:", err)
return false
}
mimeType := http.DetectContentType(data)
resp, err := portal.MainIntent().UploadBytes(data, mimeType)
if err != nil {
portal.log.Warnln("Failed to upload avatar:", err)
return false
}
portal.AvatarURL = resp.ContentURI
if len(portal.MXID) > 0 {
_, err = portal.MainIntent().SetRoomAvatar(portal.MXID, resp.ContentURI)
_, err := portal.MainIntent().SetRoomAvatar(portal.MXID, portal.AvatarURL)
if err != nil {
portal.log.Warnln("Failed to set room topic:", err)
return false
@ -457,40 +464,50 @@ func (portal *Portal) UpdateAvatar(user *User, avatar *whatsappExt.ProfilePicInf
return true
}
func (portal *Portal) UpdateName(name string, setBy types.WhatsAppID, updateInfo bool) bool {
func (portal *Portal) UpdateName(name string, setBy types.WhatsAppID, intent *appservice.IntentAPI, updateInfo bool) bool {
if portal.Name != name {
intent := portal.MainIntent()
if len(setBy) > 0 {
intent = portal.bridge.GetPuppetByJID(setBy).IntentFor(portal)
portal.log.Debugfln("Updating name %s -> %s", portal.Name, name)
portal.Name = name
if intent == nil {
intent = portal.MainIntent()
if len(setBy) > 0 {
intent = portal.bridge.GetPuppetByJID(setBy).IntentFor(portal)
}
}
_, err := intent.SetRoomName(portal.MXID, name)
if err == nil {
portal.Name = name
if updateInfo {
portal.UpdateBridgeInfo()
}
return true
} else {
portal.Name = ""
portal.log.Warnln("Failed to set room name:", err)
}
portal.log.Warnln("Failed to set room name:", err)
}
return false
}
func (portal *Portal) UpdateTopic(topic string, setBy types.WhatsAppID, updateInfo bool) bool {
func (portal *Portal) UpdateTopic(topic string, setBy types.WhatsAppID, intent *appservice.IntentAPI, updateInfo bool) bool {
if portal.Topic != topic {
intent := portal.MainIntent()
if len(setBy) > 0 {
intent = portal.bridge.GetPuppetByJID(setBy).IntentFor(portal)
portal.log.Debugfln("Updating topic %s -> %s", portal.Topic, topic)
portal.Topic = topic
if intent == nil {
intent = portal.MainIntent()
if len(setBy) > 0 {
intent = portal.bridge.GetPuppetByJID(setBy).IntentFor(portal)
}
}
_, err := intent.SetRoomTopic(portal.MXID, topic)
if err == nil {
portal.Topic = topic
if updateInfo {
portal.UpdateBridgeInfo()
}
return true
} else {
portal.Topic = ""
portal.log.Warnln("Failed to set room topic:", err)
}
portal.log.Warnln("Failed to set room topic:", err)
}
return false
}
@ -500,8 +517,8 @@ func (portal *Portal) UpdateMetadata(user *User) bool {
return false
} else if portal.IsStatusBroadcastRoom() {
update := false
update = portal.UpdateName("WhatsApp Status Broadcast", "", false) || update
update = portal.UpdateTopic("WhatsApp status updates from your contacts", "", false) || update
update = portal.UpdateName("WhatsApp Status Broadcast", "", nil, false) || update
update = portal.UpdateTopic("WhatsApp status updates from your contacts", "", nil, false) || update
return update
}
metadata, err := user.Conn.GetGroupMetaData(portal.Key.JID)
@ -521,8 +538,8 @@ func (portal *Portal) UpdateMetadata(user *User) bool {
portal.SyncParticipants(metadata)
update := false
update = portal.UpdateName(metadata.Name, metadata.NameSetBy, false) || update
update = portal.UpdateTopic(metadata.Topic, metadata.TopicSetBy, false) || update
update = portal.UpdateName(metadata.Name, metadata.NameSetBy, nil, false) || update
update = portal.UpdateTopic(metadata.Topic, metadata.TopicSetBy, nil, false) || update
portal.RestrictMessageSending(metadata.Announce)
@ -586,7 +603,7 @@ func (portal *Portal) Sync(user *User, contact whatsapp.Contact) {
update := false
update = portal.UpdateMetadata(user) || update
if !portal.IsStatusBroadcastRoom() {
if !portal.IsStatusBroadcastRoom() && portal.Avatar == "" {
update = portal.UpdateAvatar(user, nil, false) || update
}
if update {
@ -620,7 +637,7 @@ func (portal *Portal) GetBasePowerLevels() *event.PowerLevelsEventContent {
}
}
func (portal *Portal) ChangeAdminStatus(jids []string, setAdmin bool) {
func (portal *Portal) ChangeAdminStatus(jids []string, setAdmin bool) id.EventID {
levels, err := portal.MainIntent().PowerLevels(portal.MXID)
if err != nil {
levels = portal.GetBasePowerLevels()
@ -640,14 +657,17 @@ func (portal *Portal) ChangeAdminStatus(jids []string, setAdmin bool) {
}
}
if changed {
_, err = portal.MainIntent().SetPowerLevels(portal.MXID, levels)
resp, err := portal.MainIntent().SetPowerLevels(portal.MXID, levels)
if err != nil {
portal.log.Errorln("Failed to change power levels:", err)
} else {
return resp.EventID
}
}
return ""
}
func (portal *Portal) RestrictMessageSending(restrict bool) {
func (portal *Portal) RestrictMessageSending(restrict bool) id.EventID {
levels, err := portal.MainIntent().PowerLevels(portal.MXID)
if err != nil {
levels = portal.GetBasePowerLevels()
@ -659,17 +679,20 @@ func (portal *Portal) RestrictMessageSending(restrict bool) {
}
if levels.EventsDefault == newLevel {
return
return ""
}
levels.EventsDefault = newLevel
_, err = portal.MainIntent().SetPowerLevels(portal.MXID, levels)
resp, err := portal.MainIntent().SetPowerLevels(portal.MXID, levels)
if err != nil {
portal.log.Errorln("Failed to change power levels:", err)
return ""
} else {
return resp.EventID
}
}
func (portal *Portal) RestrictMetadataChanges(restrict bool) {
func (portal *Portal) RestrictMetadataChanges(restrict bool) id.EventID {
levels, err := portal.MainIntent().PowerLevels(portal.MXID)
if err != nil {
levels = portal.GetBasePowerLevels()
@ -683,11 +706,14 @@ func (portal *Portal) RestrictMetadataChanges(restrict bool) {
changed = levels.EnsureEventLevel(event.StateRoomAvatar, newLevel) || changed
changed = levels.EnsureEventLevel(event.StateTopic, newLevel) || changed
if changed {
_, err = portal.MainIntent().SetPowerLevels(portal.MXID, levels)
resp, err := portal.MainIntent().SetPowerLevels(portal.MXID, levels)
if err != nil {
portal.log.Errorln("Failed to change power levels:", err)
} else {
return resp.EventID
}
}
return ""
}
func (portal *Portal) BackfillHistory(user *User, lastMessageTime uint64) error {
@ -873,7 +899,7 @@ func (portal *Portal) handleHistory(user *User, messages []interface{}) {
if portal.privateChatBackfillInvitePuppet != nil && message.GetKey().GetFromMe() && portal.IsPrivateChat() {
portal.privateChatBackfillInvitePuppet()
}
portal.handleMessage(PortalMessage{portal.Key.JID, user, data, message.GetMessageTimestamp()})
portal.handleMessage(PortalMessage{portal.Key.JID, user, data, message.GetMessageTimestamp()}, true)
}
}
@ -1105,7 +1131,7 @@ func (portal *Portal) SetReply(content *event.MessageEventContent, info whatsapp
return
}
message := portal.bridge.DB.Message.GetByJID(portal.Key, info.QuotedMessageID)
if message != nil {
if message != nil && !message.IsFakeMXID() {
evt, err := portal.MainIntent().GetEvent(portal.MXID, message.MXID)
if err != nil {
portal.log.Warnln("Failed to get reply target:", err)
@ -1128,7 +1154,7 @@ func (portal *Portal) SetReply(content *event.MessageEventContent, info whatsapp
func (portal *Portal) HandleMessageRevoke(user *User, message whatsappExt.MessageRevocation) {
msg := portal.bridge.DB.Message.GetByJID(portal.Key, message.Id)
if msg == nil {
if msg == nil || msg.IsFakeMXID() {
return
}
var intent *appservice.IntentAPI
@ -1198,7 +1224,7 @@ func isGatewayError(err error) bool {
}
func (portal *Portal) sendMessageWithRetry(intent *appservice.IntentAPI, eventType event.Type, content interface{}, timestamp int64, retries int) (*mautrix.RespSendEvent, error) {
for ;;retries-- {
for ; ; retries-- {
resp, err := portal.sendMessageDirect(intent, eventType, content, timestamp)
if retries > 0 && isGatewayError(err) {
portal.log.Warnfln("Got gateway error trying to send message, retrying in %d seconds", int(BadGatewaySleep.Seconds()))
@ -1254,6 +1280,55 @@ func (portal *Portal) HandleTextMessage(source *User, message whatsapp.TextMessa
portal.finishHandling(source, message.Info.Source, resp.EventID)
}
func (portal *Portal) HandleStubMessage(source *User, message whatsapp.StubMessage, isBackfill bool) {
if portal.bridge.Config.Bridge.ChatMetaSync {
// Chat meta sync is enabled, so we use chat update commands and full-syncs instead of message history
return
}
intent := portal.startHandling(source, message.Info)
if intent == nil {
return
}
var senderJID string
if message.Info.FromMe {
senderJID = source.JID
} else {
senderJID = message.Info.SenderJid
}
var eventID id.EventID
// TODO find more real event IDs
// TODO timestamp massaging
switch message.Type {
case waProto.WebMessageInfo_GROUP_CHANGE_SUBJECT:
portal.UpdateName(message.FirstParam, "", intent, true)
case waProto.WebMessageInfo_GROUP_CHANGE_ICON:
portal.UpdateAvatar(source, nil, true)
case waProto.WebMessageInfo_GROUP_CHANGE_DESCRIPTION:
if isBackfill {
// TODO fetch topic from server
}
//portal.UpdateTopic(message.FirstParam, "", intent, true)
case waProto.WebMessageInfo_GROUP_CHANGE_ANNOUNCE:
eventID = portal.RestrictMessageSending(message.FirstParam == "on")
case waProto.WebMessageInfo_GROUP_CHANGE_RESTRICT:
eventID = portal.RestrictMetadataChanges(message.FirstParam == "on")
case waProto.WebMessageInfo_GROUP_PARTICIPANT_ADD, waProto.WebMessageInfo_GROUP_PARTICIPANT_INVITE:
portal.HandleWhatsAppInvite(senderJID, intent, message.Params)
case waProto.WebMessageInfo_GROUP_PARTICIPANT_REMOVE, waProto.WebMessageInfo_GROUP_PARTICIPANT_LEAVE:
portal.HandleWhatsAppKick(senderJID, message.Params)
case waProto.WebMessageInfo_GROUP_PARTICIPANT_PROMOTE:
eventID = portal.ChangeAdminStatus(message.Params, true)
case waProto.WebMessageInfo_GROUP_PARTICIPANT_DEMOTE:
eventID = portal.ChangeAdminStatus(message.Params, false)
default:
return
}
if len(eventID) == 0 {
eventID = id.EventID(fmt.Sprintf("net.maunium.whatsapp.fake::%s", message.Info.Id))
}
portal.markHandled(source, message.Info.Source, eventID)
}
func (portal *Portal) HandleLocationMessage(source *User, message whatsapp.LocationMessage) {
intent := portal.startHandling(source, message.Info)
if intent == nil {
@ -1428,17 +1503,19 @@ func (portal *Portal) HandleWhatsAppKick(senderJID string, jids []string) {
}
}
func (portal *Portal) HandleWhatsAppInvite(senderJID string, jids []string) {
senderIntent := portal.MainIntent()
if senderJID != "unknown" {
sender := portal.bridge.GetPuppetByJID(senderJID)
senderIntent = sender.IntentFor(portal)
func (portal *Portal) HandleWhatsAppInvite(senderJID string, intent *appservice.IntentAPI, jids []string) {
if intent == nil {
intent = portal.MainIntent()
if senderJID != "unknown" {
sender := portal.bridge.GetPuppetByJID(senderJID)
intent = sender.IntentFor(portal)
}
}
for _, jid := range jids {
puppet := portal.bridge.GetPuppetByJID(jid)
_, err := senderIntent.InviteUser(portal.MXID, &mautrix.ReqInviteUser{UserID: puppet.MXID})
_, err := intent.InviteUser(portal.MXID, &mautrix.ReqInviteUser{UserID: puppet.MXID})
if err != nil {
portal.log.Warnfln("Failed to invite %s as %s: %v", puppet.MXID, senderIntent.UserID, err)
portal.log.Warnfln("Failed to invite %s as %s: %v", puppet.MXID, intent.UserID, err)
}
err = puppet.DefaultIntent().EnsureJoined(portal.MXID)
if err != nil {
@ -1465,7 +1542,7 @@ type mediaMessage struct {
}
func (portal *Portal) uploadWithRetry(intent *appservice.IntentAPI, data []byte, mimeType string, retries int) (*mautrix.RespMediaUpload, error) {
for ;;retries-- {
for ; ; retries-- {
uploaded, err := intent.UploadBytes(data, mimeType)
if isGatewayError(err) {
portal.log.Warnfln("Got gateway error trying to upload media, retrying in %d seconds", int(BadGatewaySleep.Seconds()))
@ -1998,8 +2075,6 @@ func (portal *Portal) sendDeliveryReceipt(eventID id.EventID) {
}
}
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) ||
@ -2013,10 +2088,10 @@ func (portal *Portal) HandleMatrixMessage(sender *User, evt *event.Event) {
}
portal.markHandled(sender, info, evt.ID)
portal.log.Debugln("Sending event", evt.ID, "to WhatsApp", info.Key.GetId())
portal.sendRaw(sender, evt, info, false)
portal.sendRaw(sender, evt, info)
}
func (portal *Portal) sendRaw(sender *User, evt *event.Event, info *waProto.WebMessageInfo, isRetry bool) {
func (portal *Portal) sendRaw(sender *User, evt *event.Event, info *waProto.WebMessageInfo) {
errChan := make(chan error, 1)
go sender.Conn.SendRaw(info, errChan)
@ -2024,20 +2099,13 @@ func (portal *Portal) sendRaw(sender *User, evt *event.Event, info *waProto.WebM
var errorEventID id.EventID
select {
case err = <-errChan:
var statusResp whatsapp.StatusResponse
if !isRetry && errors.As(err, &statusResp) && statusResp.Status == 599 {
portal.log.Debugfln("599 status response sending %s to WhatsApp (%+v), retrying...", evt.ID, statusResp)
errorEventID = portal.sendErrorMessage(fmt.Sprintf("%v. The bridge will retry in 5 seconds.", err))
time.Sleep(5 * time.Second)
portal.sendRaw(sender, evt, info, true)
}
case <-time.After(time.Duration(portal.bridge.Config.Bridge.ConnectionTimeout) * time.Second):
if portal.bridge.Config.Bridge.FetchMessageOnTimeout && portal.wasMessageSent(sender, info.Key.GetId()) {
portal.log.Debugln("Matrix event %s was bridged, but response didn't arrive within timeout")
portal.sendDeliveryReceipt(evt.ID)
} else {
portal.log.Warnfln("Response when bridging Matrix event %s is taking long to arrive", evt.ID)
errorEventID = portal.sendErrorMessage(timeout.Error())
errorEventID = portal.sendErrorMessage("message sending timed out")
}
err = <-errChan
}
@ -2045,9 +2113,11 @@ func (portal *Portal) sendRaw(sender *User, evt *event.Event, info *waProto.WebM
portal.log.Errorfln("Error handling Matrix event %s: %v", evt.ID, err)
var statusResp whatsapp.StatusResponse
if errors.As(err, &statusResp) && statusResp.Status == 599 {
portal.log.Debugfln("599 status response data: %+v", statusResp)
portal.log.Debugfln("599 status response extra data: %+v", statusResp.Extra)
portal.sendErrorMessage(fmt.Sprintf("%v. Please try again after a few minutes", err))
} else {
portal.sendErrorMessage(err.Error())
}
portal.sendErrorMessage(err.Error())
} else {
portal.log.Debugfln("Handled Matrix event %s", evt.ID)
portal.sendDeliveryReceipt(evt.ID)
@ -2231,3 +2301,30 @@ func (portal *Portal) HandleMatrixInvite(sender *User, evt *event.Event) {
portal.log.Infoln("Add %s response: %s", puppet.JID, <-resp)
}
}
func (portal *Portal) HandleMatrixMeta(sender *User, evt *event.Event) {
var resp <-chan string
var err error
switch content := evt.Content.Parsed.(type) {
case *event.RoomNameEventContent:
if content.Name == portal.Name {
return
}
portal.Name = content.Name
resp, err = sender.Conn.UpdateGroupSubject(content.Name, portal.Key.JID)
case *event.TopicEventContent:
if content.Topic == portal.Topic {
return
}
portal.Topic = content.Topic
resp, err = sender.Conn.UpdateGroupDescription(portal.Key.JID, content.Topic)
case *event.RoomAvatarEventContent:
return
}
if err != nil {
portal.log.Errorln("Failed to update metadata:", err)
} else {
out := <-resp
portal.log.Debugln("Successfully updated metadata:", out)
}
}

View file

@ -22,9 +22,10 @@ import (
"regexp"
"strings"
"github.com/Rhymen/go-whatsapp"
log "maunium.net/go/maulogger/v2"
"github.com/Rhymen/go-whatsapp"
"maunium.net/go/mautrix/appservice"
"maunium.net/go/mautrix/id"
@ -190,15 +191,18 @@ func (puppet *Puppet) UpdateAvatar(source *User, avatar *whatsappExt.ProfilePicI
}
}
if avatar.Status != 0 {
if avatar.Status == 404 {
avatar.Tag = "remove"
avatar.Status = 0
} else if avatar.Status == 401 && puppet.Avatar != "unauthorized" {
puppet.Avatar = "unauthorized"
return true
}
if avatar.Status != 0 || avatar.Tag == puppet.Avatar {
return false
}
if avatar.Tag == puppet.Avatar {
return false
}
if len(avatar.URL) == 0 {
if avatar.Tag == "remove" || len(avatar.URL) == 0 {
err := puppet.DefaultIntent().SetAvatarURL(id.ContentURI{})
if err != nil {
puppet.log.Warnln("Failed to remove avatar:", err)
@ -296,7 +300,10 @@ func (puppet *Puppet) Sync(source *User, contact whatsapp.Contact) {
update := false
update = puppet.UpdateName(source, contact) || update
update = puppet.UpdateAvatar(source, nil) || update
// TODO figure out how to update avatars after being offline
if len(puppet.Avatar) == 0 || puppet.bridge.Config.Bridge.UserAvatarSync {
update = puppet.UpdateAvatar(source, nil) || update
}
if update {
puppet.Update()
}

36
user.go
View file

@ -641,8 +641,11 @@ func (user *User) syncPortals(chatMap map[string]whatsapp.Chat, createAll bool)
}
create := (chat.LastMessageTime >= user.LastConnection && user.LastConnection > 0) || i < limit
if len(chat.Portal.MXID) > 0 || create || createAll {
chat.Portal.Sync(user, chat.Contact)
err := chat.Portal.BackfillHistory(user, chat.LastMessageTime)
// Don't sync unless chat meta sync is enabled or portal doesn't exist
if user.bridge.Config.Bridge.ChatMetaSync || len(chat.Portal.MXID) == 0 {
chat.Portal.Sync(user, chat.Contact)
}
err = chat.Portal.BackfillHistory(user, chat.LastMessageTime)
if err != nil {
chat.Portal.log.Errorln("Error backfilling history:", err)
}
@ -927,6 +930,10 @@ func (user *User) HandleContactMessage(message whatsapp.ContactMessage) {
user.messageInput <- PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp}
}
func (user *User) HandleStubMessage(message whatsapp.StubMessage) {
user.messageInput <- PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp}
}
func (user *User) HandleLocationMessage(message whatsapp.LocationMessage) {
user.messageInput <- PortalMessage{message.Info.RemoteJid, user, message, message.Info.Timestamp}
}
@ -1015,7 +1022,7 @@ func (user *User) HandleMsgInfo(info whatsappExt.MsgInfo) {
intent := user.bridge.GetPuppetByJID(info.SenderJID).IntentFor(portal)
for _, msgID := range info.IDs {
msg := user.bridge.DB.Message.GetByJID(portal.Key, msgID)
if msg == nil {
if msg == nil || msg.IsFakeMXID() {
continue
}
@ -1066,7 +1073,7 @@ func (user *User) markSelfRead(jid, messageID string) {
user.log.Debugfln("User read chat %s/%s in WhatsApp mobile (last known event: %s/%s)", portal.Key.JID, portal.MXID, message.JID, message.MXID)
} else {
message = user.bridge.DB.Message.GetByJID(portal.Key, messageID)
if message == nil {
if message == nil || message.IsFakeMXID() {
return
}
user.log.Debugfln("User read message %s/%s in %s/%s in WhatsApp mobile", message.JID, message.MXID, portal.Key.JID, portal.MXID)
@ -1118,13 +1125,22 @@ func (user *User) HandleChatUpdate(cmd whatsappExt.ChatUpdate) {
return
}
// These don't come down the message history :(
switch cmd.Data.Action {
case whatsappExt.ChatActionAddTopic:
go portal.UpdateTopic(cmd.Data.AddTopic.Topic, cmd.Data.SenderJID, nil,true)
case whatsappExt.ChatActionRemoveTopic:
go portal.UpdateTopic("", cmd.Data.SenderJID, nil,true)
}
if !user.bridge.Config.Bridge.ChatMetaSync {
// Ignore chat update commands, we're relying on the message history.
return
}
switch cmd.Data.Action {
case whatsappExt.ChatActionNameChange:
go portal.UpdateName(cmd.Data.NameChange.Name, cmd.Data.SenderJID, true)
case whatsappExt.ChatActionAddTopic:
go portal.UpdateTopic(cmd.Data.AddTopic.Topic, cmd.Data.SenderJID, true)
case whatsappExt.ChatActionRemoveTopic:
go portal.UpdateTopic("", cmd.Data.SenderJID, true)
go portal.UpdateName(cmd.Data.NameChange.Name, cmd.Data.SenderJID, nil, true)
case whatsappExt.ChatActionPromote:
go portal.ChangeAdminStatus(cmd.Data.UserChange.JIDs, true)
case whatsappExt.ChatActionDemote:
@ -1136,7 +1152,7 @@ func (user *User) HandleChatUpdate(cmd whatsappExt.ChatUpdate) {
case whatsappExt.ChatActionRemove:
go portal.HandleWhatsAppKick(cmd.Data.SenderJID, cmd.Data.UserChange.JIDs)
case whatsappExt.ChatActionAdd:
go portal.HandleWhatsAppInvite(cmd.Data.SenderJID, cmd.Data.UserChange.JIDs)
go portal.HandleWhatsAppInvite(cmd.Data.SenderJID, nil, cmd.Data.UserChange.JIDs)
case whatsappExt.ChatActionIntroduce:
if cmd.Data.SenderJID != "unknown" {
go portal.Sync(user, whatsapp.Contact{Jid: portal.Key.JID})