Просмотр исходного кода

Add option to bridge archive and mute status from WhatsApp

Tulir Asokan 4 лет назад
Родитель
Сommit
badea9c547
8 измененных файлов с 139 добавлено и 55 удалено
  1. 15 13
      config/bridge.go
  2. 1 1
      database/message.go
  3. 2 2
      database/user.go
  4. 6 0
      example-config.yaml
  5. 1 1
      go.mod
  6. 2 0
      go.sum
  7. 2 2
      portal.go
  8. 110 36
      user.go

+ 15 - 13
config/bridge.go

@@ -51,15 +51,15 @@ type BridgeConfig struct {
 		End   bool `yaml:"end"`
 		End   bool `yaml:"end"`
 	} `yaml:"call_notices"`
 	} `yaml:"call_notices"`
 
 
-	InitialChatSync      int    `yaml:"initial_chat_sync_count"`
-	InitialHistoryFill   int    `yaml:"initial_history_fill_count"`
-	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"`
-	BridgeMatrixLeave    bool   `yaml:"bridge_matrix_leave"`
-	SyncChatMaxAge       uint64 `yaml:"sync_max_chat_age"`
+	InitialChatSync      int   `yaml:"initial_chat_sync_count"`
+	InitialHistoryFill   int   `yaml:"initial_history_fill_count"`
+	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"`
+	BridgeMatrixLeave    bool  `yaml:"bridge_matrix_leave"`
+	SyncChatMaxAge       int64 `yaml:"sync_max_chat_age"`
 
 
 	SyncWithCustomPuppets bool   `yaml:"sync_with_custom_puppets"`
 	SyncWithCustomPuppets bool   `yaml:"sync_with_custom_puppets"`
 	SyncDirectChatList    bool   `yaml:"sync_direct_chat_list"`
 	SyncDirectChatList    bool   `yaml:"sync_direct_chat_list"`
@@ -67,10 +67,12 @@ type BridgeConfig struct {
 	DefaultBridgePresence bool   `yaml:"default_bridge_presence"`
 	DefaultBridgePresence bool   `yaml:"default_bridge_presence"`
 	LoginSharedSecret     string `yaml:"login_shared_secret"`
 	LoginSharedSecret     string `yaml:"login_shared_secret"`
 
 
-	InviteOwnPuppetForBackfilling bool `yaml:"invite_own_puppet_for_backfilling"`
-	PrivateChatPortalMeta         bool `yaml:"private_chat_portal_meta"`
-	BridgeNotices         		  bool `yaml:"bridge_notices"`
-	ResendBridgeInfo              bool `yaml:"resend_bridge_info"`
+	InviteOwnPuppetForBackfilling bool   `yaml:"invite_own_puppet_for_backfilling"`
+	PrivateChatPortalMeta         bool   `yaml:"private_chat_portal_meta"`
+	BridgeNotices                 bool   `yaml:"bridge_notices"`
+	ResendBridgeInfo              bool   `yaml:"resend_bridge_info"`
+	MuteBridging                  bool   `yaml:"mute_bridging"`
+	ArchiveTag                    string `yaml:"archive_tag"`
 
 
 	WhatsappThumbnail bool `yaml:"whatsapp_thumbnail"`
 	WhatsappThumbnail bool `yaml:"whatsapp_thumbnail"`
 
 

+ 1 - 1
database/message.go

@@ -91,7 +91,7 @@ type Message struct {
 	JID       whatsapp.MessageID
 	JID       whatsapp.MessageID
 	MXID      id.EventID
 	MXID      id.EventID
 	Sender    whatsapp.JID
 	Sender    whatsapp.JID
-	Timestamp uint64
+	Timestamp int64
 	Sent      bool
 	Sent      bool
 	Content   *waProto.Message
 	Content   *waProto.Message
 }
 }

+ 2 - 2
database/user.go

@@ -77,7 +77,7 @@ type User struct {
 	JID            whatsapp.JID
 	JID            whatsapp.JID
 	ManagementRoom id.RoomID
 	ManagementRoom id.RoomID
 	Session        *whatsapp.Session
 	Session        *whatsapp.Session
-	LastConnection uint64
+	LastConnection int64
 }
 }
 
 
 func (user *User) Scan(row Scannable) *User {
 func (user *User) Scan(row Scannable) *User {
@@ -146,7 +146,7 @@ func (user *User) Insert() {
 }
 }
 
 
 func (user *User) UpdateLastConnection() {
 func (user *User) UpdateLastConnection() {
-	user.LastConnection = uint64(time.Now().Unix())
+	user.LastConnection = time.Now().Unix()
 	_, err := user.db.Exec(`UPDATE "user" SET last_connection=$1 WHERE mxid=$2`,
 	_, err := user.db.Exec(`UPDATE "user" SET last_connection=$1 WHERE mxid=$2`,
 		user.LastConnection, user.MXID)
 		user.LastConnection, user.MXID)
 	if err != nil {
 	if err != nil {

+ 6 - 0
example-config.yaml

@@ -183,6 +183,12 @@ bridge:
     # This field will automatically be changed back to false after it,
     # This field will automatically be changed back to false after it,
     # except if the config file is not writable.
     # except if the config file is not writable.
     resend_bridge_info: false
     resend_bridge_info: false
+    # When using double puppeting, should muted chats be muted in Matrix?
+    mute_bridging: false
+    # When using double puppeting, should archived chats be moved to a specific tag in Matrix?
+    # Note that WhatsApp unarchives chats when a message is received, which will also be mirrored to Matrix.
+    # This can be set to a tag (e.g. m.lowpriority), or null to disable.
+    archive_tag: null
 
 
     # Whether or not thumbnails from WhatsApp should be sent.
     # Whether or not thumbnails from WhatsApp should be sent.
     # They're disabled by default due to very low resolution.
     # They're disabled by default due to very low resolution.

+ 1 - 1
go.mod

@@ -16,4 +16,4 @@ require (
 	maunium.net/go/mautrix v0.9.7
 	maunium.net/go/mautrix v0.9.7
 )
 )
 
 
-replace github.com/Rhymen/go-whatsapp => github.com/tulir/go-whatsapp v0.4.3
+replace github.com/Rhymen/go-whatsapp => github.com/tulir/go-whatsapp v0.4.4

+ 2 - 0
go.sum

@@ -323,6 +323,8 @@ github.com/tulir/go-whatsapp v0.4.2 h1:UzBidzRazkbFhM7xyDBLvv4eD37zEtOsVLWK0m2CI
 github.com/tulir/go-whatsapp v0.4.2/go.mod h1:rwwuTh1bKqhgrRvOBAr8hDqtuz8Cc1Quqw/0BeXb+/E=
 github.com/tulir/go-whatsapp v0.4.2/go.mod h1:rwwuTh1bKqhgrRvOBAr8hDqtuz8Cc1Quqw/0BeXb+/E=
 github.com/tulir/go-whatsapp v0.4.3 h1:rQBBT40JHE4eLk5idQ3r/6jNj46nqjLyMnlJTKwyHl0=
 github.com/tulir/go-whatsapp v0.4.3 h1:rQBBT40JHE4eLk5idQ3r/6jNj46nqjLyMnlJTKwyHl0=
 github.com/tulir/go-whatsapp v0.4.3/go.mod h1:rwwuTh1bKqhgrRvOBAr8hDqtuz8Cc1Quqw/0BeXb+/E=
 github.com/tulir/go-whatsapp v0.4.3/go.mod h1:rwwuTh1bKqhgrRvOBAr8hDqtuz8Cc1Quqw/0BeXb+/E=
+github.com/tulir/go-whatsapp v0.4.4 h1:69AIE/CbmVYpBbug75meWFOS8lilzoafZFctt2JzRek=
+github.com/tulir/go-whatsapp v0.4.4/go.mod h1:rwwuTh1bKqhgrRvOBAr8hDqtuz8Cc1Quqw/0BeXb+/E=
 github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
 github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
 github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
 github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=

+ 2 - 2
portal.go

@@ -308,7 +308,7 @@ func (portal *Portal) markHandled(source *User, message *waProto.WebMessageInfo,
 	msg.Chat = portal.Key
 	msg.Chat = portal.Key
 	msg.JID = message.GetKey().GetId()
 	msg.JID = message.GetKey().GetId()
 	msg.MXID = mxid
 	msg.MXID = mxid
-	msg.Timestamp = message.GetMessageTimestamp()
+	msg.Timestamp = int64(message.GetMessageTimestamp())
 	if message.GetKey().GetFromMe() {
 	if message.GetKey().GetFromMe() {
 		msg.Sender = source.JID
 		msg.Sender = source.JID
 	} else if portal.IsPrivateChat() {
 	} else if portal.IsPrivateChat() {
@@ -765,7 +765,7 @@ func (portal *Portal) RestrictMetadataChanges(restrict bool) id.EventID {
 	return ""
 	return ""
 }
 }
 
 
-func (portal *Portal) BackfillHistory(user *User, lastMessageTime uint64) error {
+func (portal *Portal) BackfillHistory(user *User, lastMessageTime int64) error {
 	if !portal.bridge.Config.Bridge.RecoverHistory {
 	if !portal.bridge.Config.Bridge.RecoverHistory {
 		return nil
 		return nil
 	}
 	}

+ 110 - 36
user.go

@@ -23,7 +23,6 @@ import (
 	"fmt"
 	"fmt"
 	"net/http"
 	"net/http"
 	"sort"
 	"sort"
-	"strconv"
 	"strings"
 	"strings"
 	"sync"
 	"sync"
 	"sync/atomic"
 	"sync/atomic"
@@ -31,6 +30,8 @@ import (
 
 
 	"github.com/skip2/go-qrcode"
 	"github.com/skip2/go-qrcode"
 	log "maunium.net/go/maulogger/v2"
 	log "maunium.net/go/maulogger/v2"
+	"maunium.net/go/mautrix/appservice"
+	"maunium.net/go/mautrix/pushrules"
 
 
 	"github.com/Rhymen/go-whatsapp"
 	"github.com/Rhymen/go-whatsapp"
 	waBinary "github.com/Rhymen/go-whatsapp/binary"
 	waBinary "github.com/Rhymen/go-whatsapp/binary"
@@ -440,10 +441,9 @@ func (user *User) Login(ce *CommandEvent) {
 }
 }
 
 
 type Chat struct {
 type Chat struct {
-	Portal          *Portal
-	LastMessageTime uint64
-	MarkAsRead      bool
-	Contact         whatsapp.Contact
+	whatsapp.Chat
+	Portal  *Portal
+	Contact whatsapp.Contact
 }
 }
 
 
 type ChatList []Chat
 type ChatList []Chat
@@ -618,6 +618,16 @@ func (user *User) HandleEvent(event interface{}) {
 		user.HandleChatUpdate(v)
 		user.HandleChatUpdate(v)
 	case whatsapp.ConnInfo:
 	case whatsapp.ConnInfo:
 		user.HandleConnInfo(v)
 		user.HandleConnInfo(v)
+	case whatsapp.MuteMessage:
+		portal := user.bridge.GetPortalByJID(user.PortalKey(v.JID))
+		if portal != nil {
+			go user.updateChatMute(nil, portal, v.MutedUntil)
+		}
+	case whatsapp.ArchiveMessage:
+		portal := user.bridge.GetPortalByJID(user.PortalKey(v.JID))
+		if portal != nil {
+			go user.updateChatArchive(nil, portal, v.IsArchived)
+		}
 	case json.RawMessage:
 	case json.RawMessage:
 		user.HandleJSONMessage(v)
 		user.HandleJSONMessage(v)
 	case *waProto.WebMessageInfo:
 	case *waProto.WebMessageInfo:
@@ -664,9 +674,84 @@ func (user *User) HandleChatList(chats []whatsapp.Chat) {
 	go user.syncPortals(chatMap, false)
 	go user.syncPortals(chatMap, false)
 }
 }
 
 
-func (user *User) syncPortals(chatMap map[string]whatsapp.Chat, createAll bool) {
-	// TODO use contexts instead of checking if user.Conn is the same?
-	connAtStart := user.Conn
+func (user *User) updateChatMute(intent *appservice.IntentAPI, portal *Portal, mutedUntil int64) {
+	if len(portal.MXID) == 0 || !user.bridge.Config.Bridge.MuteBridging {
+		return
+	} else if intent == nil {
+		doublePuppet := user.bridge.GetPuppetByCustomMXID(user.MXID)
+		if doublePuppet == nil || doublePuppet.CustomIntent() == nil {
+			return
+		}
+		intent = doublePuppet.CustomIntent()
+	}
+	var err error
+	if mutedUntil < time.Now().Unix() {
+		user.log.Debugln("Unmuting", portal.MXID)
+		err = intent.DeletePushRule("global", pushrules.RoomRule, string(portal.MXID))
+	} else {
+		user.log.Debugln("Muting", portal.MXID)
+		err = intent.PutPushRule("global", pushrules.RoomRule, string(portal.MXID), &mautrix.ReqPutPushRule{
+			Actions: []pushrules.PushActionType{pushrules.ActionDontNotify},
+		})
+	}
+	if err != nil && !errors.Is(err, mautrix.MNotFound) {
+		user.log.Warnfln("Failed to update push rule for %s through double puppet: %v", portal.MXID, err)
+	}
+}
+
+func (user *User) updateChatArchive(intent *appservice.IntentAPI, portal *Portal, archived bool) {
+	if len(portal.MXID) == 0 || len(user.bridge.Config.Bridge.ArchiveTag) == 0 {
+		return
+	} else if intent == nil {
+		doublePuppet := user.bridge.GetPuppetByCustomMXID(user.MXID)
+		if doublePuppet == nil || doublePuppet.CustomIntent() == nil {
+			return
+		}
+		intent = doublePuppet.CustomIntent()
+	}
+	var err error
+	if archived {
+		user.log.Debugln("Adding tag", user.bridge.Config.Bridge.ArchiveTag, "to", portal.MXID)
+		err = intent.AddTag(portal.MXID, user.bridge.Config.Bridge.ArchiveTag, 0.5)
+	} else {
+		user.log.Debugln("Removing tag", user.bridge.Config.Bridge.ArchiveTag, "from", portal.MXID)
+		err = intent.RemoveTag(portal.MXID, user.bridge.Config.Bridge.ArchiveTag)
+	}
+	if err != nil {
+		user.log.Warnfln("Failed to update tag for %s through double puppet: %v", portal.MXID, err)
+	}
+}
+
+func (user *User) syncChatDoublePuppetDetails(doublePuppet *Puppet, chat Chat) {
+	if doublePuppet == nil || doublePuppet.CustomIntent() == nil {
+		return
+	}
+	intent := doublePuppet.CustomIntent()
+	if chat.UnreadCount == 0 {
+		lastMessage := user.bridge.DB.Message.GetLastInChat(chat.Portal.Key)
+		if lastMessage != nil {
+			err := intent.MarkRead(chat.Portal.MXID, lastMessage.MXID)
+			if err != nil {
+				user.log.Warnln("Failed to mark %s in %s as read after backfill: %v", lastMessage.MXID, chat.Portal.MXID, err)
+			}
+		}
+	}
+	user.updateChatMute(intent, chat.Portal, chat.MutedUntil)
+	user.updateChatArchive(intent, chat.Portal, chat.IsArchived)
+}
+
+func (user *User) syncPortal(chat Chat) {
+	// 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)
+	}
+}
+
+func (user *User) collectChatList(chatMap map[string]whatsapp.Chat) ChatList {
 	if chatMap == nil {
 	if chatMap == nil {
 		chatMap = user.Conn.Store.Chats
 		chatMap = user.Conn.Store.Chats
 	}
 	}
@@ -675,18 +760,12 @@ func (user *User) syncPortals(chatMap map[string]whatsapp.Chat, createAll bool)
 	existingKeys := user.GetInCommunityMap()
 	existingKeys := user.GetInCommunityMap()
 	portalKeys := make([]database.PortalKeyWithMeta, 0, len(chatMap))
 	portalKeys := make([]database.PortalKeyWithMeta, 0, len(chatMap))
 	for _, chat := range chatMap {
 	for _, chat := range chatMap {
-		ts, err := strconv.ParseUint(chat.LastMessageTime, 10, 64)
-		if err != nil {
-			user.log.Warnfln("Non-integer last message time in %s: %s", chat.JID, chat.LastMessageTime)
-			continue
-		}
 		portal := user.GetPortalByJID(chat.JID)
 		portal := user.GetPortalByJID(chat.JID)
 
 
 		chats = append(chats, Chat{
 		chats = append(chats, Chat{
-			Portal:          portal,
-			Contact:         user.Conn.Store.Contacts[chat.JID],
-			LastMessageTime: ts,
-			MarkAsRead:      chat.Unread == "0",
+			Chat:    chat,
+			Portal:  portal,
+			Contact: user.Conn.Store.Contacts[chat.JID],
 		})
 		})
 		var inCommunity, ok bool
 		var inCommunity, ok bool
 		if inCommunity, ok = existingKeys[portal.Key]; !ok || !inCommunity {
 		if inCommunity, ok = existingKeys[portal.Key]; !ok || !inCommunity {
@@ -704,6 +783,15 @@ func (user *User) syncPortals(chatMap map[string]whatsapp.Chat, createAll bool)
 		user.log.Warnln("Failed to update user-portal mapping:", err)
 		user.log.Warnln("Failed to update user-portal mapping:", err)
 	}
 	}
 	sort.Sort(chats)
 	sort.Sort(chats)
+	return chats
+}
+
+func (user *User) syncPortals(chatMap map[string]whatsapp.Chat, createAll bool) {
+	// TODO use contexts instead of checking if user.Conn is the same?
+	connAtStart := user.Conn
+
+	chats := user.collectChatList(chatMap)
+
 	limit := user.bridge.Config.Bridge.InitialChatSync
 	limit := user.bridge.Config.Bridge.InitialChatSync
 	if limit < 0 {
 	if limit < 0 {
 		limit = len(chats)
 		limit = len(chats)
@@ -712,7 +800,7 @@ func (user *User) syncPortals(chatMap map[string]whatsapp.Chat, createAll bool)
 		user.log.Debugln("Connection seems to have changed before sync, cancelling")
 		user.log.Debugln("Connection seems to have changed before sync, cancelling")
 		return
 		return
 	}
 	}
-	now := uint64(time.Now().Unix())
+	now := time.Now().Unix()
 	user.log.Infoln("Syncing portals")
 	user.log.Infoln("Syncing portals")
 	doublePuppet := user.bridge.GetPuppetByCustomMXID(user.MXID)
 	doublePuppet := user.bridge.GetPuppetByCustomMXID(user.MXID)
 	for i, chat := range chats {
 	for i, chat := range chats {
@@ -721,23 +809,8 @@ func (user *User) syncPortals(chatMap map[string]whatsapp.Chat, createAll bool)
 		}
 		}
 		create := (chat.LastMessageTime >= user.LastConnection && user.LastConnection > 0) || i < limit
 		create := (chat.LastMessageTime >= user.LastConnection && user.LastConnection > 0) || i < limit
 		if len(chat.Portal.MXID) > 0 || create || createAll {
 		if len(chat.Portal.MXID) > 0 || create || createAll {
-			// 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)
-			}
-			if chat.MarkAsRead && doublePuppet != nil && doublePuppet.CustomIntent() != nil {
-				lastMessage := user.bridge.DB.Message.GetLastInChat(chat.Portal.Key)
-				if lastMessage != nil {
-					err = doublePuppet.CustomIntent().MarkRead(chat.Portal.MXID, lastMessage.MXID)
-					if err != nil {
-						user.log.Warnln("Failed to mark %s in %s as read after backfill: %v", lastMessage.MXID, chat.Portal.MXID, err)
-					}
-				}
-			}
+			user.syncPortal(chat)
+			user.syncChatDoublePuppetDetails(doublePuppet, chat)
 		}
 		}
 	}
 	}
 	if user.Conn != connAtStart {
 	if user.Conn != connAtStart {
@@ -745,6 +818,7 @@ func (user *User) syncPortals(chatMap map[string]whatsapp.Chat, createAll bool)
 		return
 		return
 	}
 	}
 	user.UpdateDirectChats(nil)
 	user.UpdateDirectChats(nil)
+
 	user.log.Infoln("Finished syncing portals")
 	user.log.Infoln("Finished syncing portals")
 	select {
 	select {
 	case user.syncPortalsDone <- struct{}{}:
 	case user.syncPortalsDone <- struct{}{}:
@@ -846,7 +920,7 @@ func (user *User) syncPuppets(contacts map[whatsapp.JID]whatsapp.Contact) {
 }
 }
 
 
 func (user *User) updateLastConnectionIfNecessary() {
 func (user *User) updateLastConnectionIfNecessary() {
-	if user.LastConnection+60 < uint64(time.Now().Unix()) {
+	if user.LastConnection+60 < time.Now().Unix() {
 		user.UpdateLastConnection()
 		user.UpdateLastConnection()
 	}
 	}
 }
 }