From 97dbf9e6289d875edd6f02e3b7d66fae2df07397 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sat, 9 Sep 2023 21:50:57 +0300 Subject: [PATCH] Clean up blocklist stuff --- client.go | 5 --- connectionevents.go | 1 - errors.go | 1 - mdtest/main.go | 34 +++++++--------- notification.go | 44 +++++++-------------- send.go | 8 ---- types/events/events.go | 33 +++++++++++++--- types/user.go | 6 +++ user.go | 88 ++++++++++++++---------------------------- 9 files changed, 91 insertions(+), 129 deletions(-) diff --git a/client.go b/client.go index 61fa79dd..6f8b3100 100644 --- a/client.go +++ b/client.go @@ -105,9 +105,6 @@ type Client struct { userDevicesCache map[types.JID][]types.JID userDevicesCacheLock sync.Mutex - blockedContactsCache map[types.JID]bool - blockedContactsCacheLock sync.Mutex - recentMessagesMap map[recentMessageKey]*waProto.Message recentMessagesList [recentMessagesSize]recentMessageKey recentMessagesPtr int @@ -194,8 +191,6 @@ func NewClient(deviceStore *store.Device, log waLog.Logger) *Client { groupParticipantsCache: make(map[types.JID][]types.JID), userDevicesCache: make(map[types.JID][]types.JID), - blockedContactsCache: make(map[types.JID]bool), - recentMessagesMap: make(map[recentMessageKey]*waProto.Message, recentMessagesSize), sessionRecreateHistory: make(map[types.JID]time.Time), GetMessageForRetry: func(requester, to types.JID, id types.MessageID) *waProto.Message { return nil }, diff --git a/connectionevents.go b/connectionevents.go index 20c1025a..c20fe42a 100644 --- a/connectionevents.go +++ b/connectionevents.go @@ -138,7 +138,6 @@ func (cli *Client) handleConnectSuccess(node *waBinary.Node) { if err != nil { cli.Log.Warnf("Failed to send post-connect passive IQ: %v", err) } - cli.GetAllBlockedContacts() cli.dispatchEvent(&events.Connected{}) cli.closeSocketWaitChan() }() diff --git a/errors.go b/errors.go index e1cc7b86..27c2f318 100644 --- a/errors.go +++ b/errors.go @@ -106,7 +106,6 @@ var ( ErrUnknownServer = errors.New("can't send message to unknown server") ErrRecipientADJID = errors.New("message recipient must be a user JID with no device part") ErrServerReturnedError = errors.New("server returned error") - ErrBlockedContact = errors.New("contact is blocked") ) type DownloadHTTPError struct { diff --git a/mdtest/main.go b/mdtest/main.go index 0a75815a..9bb7a413 100644 --- a/mdtest/main.go +++ b/mdtest/main.go @@ -745,47 +745,43 @@ func handleCmd(cmd string, args []string) { if err != nil { log.Errorf("Error changing chat's pin state: %v", err) } - case "listblocked": - blockedContacts, err := cli.GetAllBlockedContacts() + case "getblocklist": + blocklist, err := cli.GetBlocklist() if err != nil { log.Errorf("Failed to get blocked contacts list: %v", err) } else { - for _, blockedContact := range blockedContacts { - log.Infof("%+v", blockedContact) - } + log.Infof("Blocklist: %+v", blocklist) } case "block": if len(args) < 1 { log.Errorf("Usage: block ") return } - target, ok := parseJID(args[0]) + jid, ok := parseJID(args[0]) if !ok { return } - - blockedList, err := cli.BlockContact(target) + resp, err := cli.UpdateBlocklist(jid, events.BlocklistChangeActionBlock) if err != nil { - log.Errorf("Error blocking contact: %v", err) + log.Errorf("Error updating blocklist: %v", err) + } else { + log.Infof("Blocklist updated: %+v", resp) } - - log.Infof("%+v", blockedList) case "unblock": if len(args) < 1 { log.Errorf("Usage: unblock ") return } - target, ok := parseJID(args[0]) + jid, ok := parseJID(args[0]) if !ok { return } - - blockedList, err := cli.UnblockContact(target) + resp, err := cli.UpdateBlocklist(jid, events.BlocklistChangeActionUnblock) if err != nil { - log.Errorf("Error unblocking contact: %v", err) + log.Errorf("Error updating blocklist: %v", err) + } else { + log.Infof("Blocklist updated: %+v", resp) } - - log.Infof("%+v", blockedList) } } @@ -917,7 +913,7 @@ func handler(rawEvt interface{}) { log.Debugf("Keepalive timeout event: %+v", evt) case *events.KeepAliveRestored: log.Debugf("Keepalive restored") - case *events.ContactBlockedStatusChange: - log.Infof("ContactBlockedStatusChange event: %+v", evt) + case *events.Blocklist: + log.Infof("Blocklist event: %+v", evt) } } diff --git a/notification.go b/notification.go index d0c6ebcd..97b8ff7d 100644 --- a/notification.go +++ b/notification.go @@ -165,40 +165,26 @@ func (cli *Client) handleOwnDevicesNotification(node *waBinary.Node) { } } -func (cli *Client) handleContactBlockedStatusChangeNotification(node *waBinary.Node) { - childArray := node.GetChildren() - if len(childArray) == 0 { - ag := node.AttrGetter() - action := ag.String("action") - if !ag.OK() { - cli.Log.Debugf("Ignoring contact blocked status change notification with unexpected attributes: %v", ag.Error()) - return - } - if action == "modify" { - cli.GetAllBlockedContacts() - return - } +func (cli *Client) handleBlocklist(node *waBinary.Node) { + ag := node.AttrGetter() + evt := events.Blocklist{ + Action: events.BlocklistAction(ag.OptionalString("action")), + DHash: ag.String("dhash"), + PrevDHash: ag.OptionalString("prev_dhash"), } - - cli.blockedContactsCacheLock.Lock() - defer cli.blockedContactsCacheLock.Unlock() - for _, child := range childArray { + for _, child := range node.GetChildren() { ag := child.AttrGetter() - var evt events.ContactBlockedStatusChange - evt.From = ag.JID("jid") - evt.Blocked = ag.String("action") == "block" + change := events.BlocklistChange{ + JID: ag.JID("jid"), + Action: events.BlocklistChangeAction(ag.String("action")), + } if !ag.OK() { - cli.Log.Debugf("Ignoring contact blocked status change notification with unexpected attributes: %v", ag.Error()) + cli.Log.Warnf("Unexpected data in blocklist event child %v: %v", child.XMLString(), ag.Error()) continue } - - if evt.Blocked { - cli.blockedContactsCache[evt.From] = true - } else if _, exists := cli.blockedContactsCache[evt.From]; exists { - delete(cli.blockedContactsCache, evt.From) - } - cli.dispatchEvent(&evt) + evt.Changes = append(evt.Changes, change) } + cli.dispatchEvent(&evt) } func (cli *Client) handleAccountSyncNotification(node *waBinary.Node) { @@ -214,7 +200,7 @@ func (cli *Client) handleAccountSyncNotification(node *waBinary.Node) { JID: cli.getOwnID().ToNonAD(), }) case "blocklist": - cli.handleContactBlockedStatusChangeNotification(&child) + cli.handleBlocklist(&child) default: cli.Log.Debugf("Unhandled account sync item %s", child.Tag) } diff --git a/send.go b/send.go index 5044f229..446430ba 100644 --- a/send.go +++ b/send.go @@ -145,14 +145,6 @@ func (cli *Client) SendMessage(ctx context.Context, to types.JID, message *waPro return } - cli.blockedContactsCacheLock.Lock() - _, isBlockedContact := cli.blockedContactsCache[to] - cli.blockedContactsCacheLock.Unlock() - if isBlockedContact { - err = ErrBlockedContact - return - } - if len(req.ID) == 0 { req.ID = cli.GenerateMessageID() } diff --git a/types/events/events.go b/types/events/events.go index 8788a2db..7a39c3b4 100644 --- a/types/events/events.go +++ b/types/events/events.go @@ -461,10 +461,31 @@ type MediaRetry struct { FromMe bool // Whether the message was sent by the current user or someone else. } -// ContactBlockedStatusChange is emitted when the contact blocked status change is received. -type ContactBlockedStatusChange struct { - // The user whose blocked state change event this is - From types.JID - // True if the user is blocked - Blocked bool +type BlocklistAction string + +const ( + BlocklistActionDefault BlocklistAction = "" + BlocklistActionModify BlocklistAction = "modify" +) + +// Blocklist is emitted when the user's blocked user list is changed. +type Blocklist struct { + // Action specifies what happened. If it's empty, there should be a list of changes in the Changes list. + // If it's "modify", then the Changes list will be empty and the whole blocklist should be re-requested. + Action BlocklistAction + DHash string + PrevDHash string + Changes []BlocklistChange +} + +type BlocklistChangeAction string + +const ( + BlocklistChangeActionBlock BlocklistChangeAction = "block" + BlocklistChangeActionUnblock BlocklistChangeAction = "unblock" +) + +type BlocklistChange struct { + JID types.JID + Action BlocklistChangeAction } diff --git a/types/user.go b/types/user.go index 6daa06b5..fe51258b 100644 --- a/types/user.go +++ b/types/user.go @@ -121,3 +121,9 @@ type StatusPrivacy struct { IsDefault bool } + +// Blocklist contains the user's current list of blocked users. +type Blocklist struct { + DHash string // TODO is this just a timestamp? + JIDs []JID +} diff --git a/user.go b/user.go index b0776207..04e13e6b 100644 --- a/user.go +++ b/user.go @@ -518,31 +518,25 @@ func (cli *Client) usync(ctx context.Context, jids []types.JID, mode, context st } } -// cacheBlockedContacts sync the cache with blocked contacts -func (cli *Client) cacheBlockedContacts(blockedContactsResp *waBinary.Node) []types.JID { - blockedContacts := []types.JID{} - if blockedList, ok := blockedContactsResp.GetOptionalChildByTag("list"); ok { - cli.blockedContactsCacheLock.Lock() - defer cli.blockedContactsCacheLock.Unlock() - cli.blockedContactsCache = make(map[types.JID]bool) - for _, child := range blockedList.GetChildren() { - ag := child.AttrGetter() - blockedJID := ag.JID("jid") - if !ag.OK() { - cli.Log.Debugf("Ignoring contact blocked data with unexpected attributes: %v", ag.Error()) - continue - } - - cli.blockedContactsCache[blockedJID] = true - blockedContacts = append(blockedContacts, blockedJID) +func (cli *Client) parseBlocklist(node *waBinary.Node) *types.Blocklist { + output := &types.Blocklist{ + DHash: node.AttrGetter().String("dhash"), + } + for _, child := range node.GetChildren() { + ag := child.AttrGetter() + blockedJID := ag.JID("jid") + if !ag.OK() { + cli.Log.Debugf("Ignoring contact blocked data with unexpected attributes: %v", ag.Error()) + continue } - } - return blockedContacts + output.JIDs = append(output.JIDs, blockedJID) + } + return output } -// GetAllBlockedContacts gets all the contacts that was blocked on connected Whatsapp user phone -func (cli *Client) GetAllBlockedContacts() ([]types.JID, error) { +// GetBlocklist gets the list of users that this user has blocked. +func (cli *Client) GetBlocklist() (*types.Blocklist, error) { resp, err := cli.sendIQ(infoQuery{ Namespace: "blocklist", Type: iqGet, @@ -551,19 +545,15 @@ func (cli *Client) GetAllBlockedContacts() ([]types.JID, error) { if err != nil { return nil, err } - - blockedContacts := cli.cacheBlockedContacts(resp) - - return blockedContacts, nil -} - -// setContactBlockedStatus updates the contact blocked status -func (cli *Client) setContactBlockedStatus(user types.JID, block bool) ([]types.JID, error) { - blockAction := "unblock" - if block { - blockAction = "block" + list, ok := resp.GetOptionalChildByTag("list") + if !ok { + return nil, &ElementMissingError{Tag: "list", In: "response to blocklist query"} } + return cli.parseBlocklist(&list), nil +} +// UpdateBlocklist updates the user's block list and returns the updated list. +func (cli *Client) UpdateBlocklist(jid types.JID, action events.BlocklistChangeAction) (*types.Blocklist, error) { resp, err := cli.sendIQ(infoQuery{ Namespace: "blocklist", Type: iqSet, @@ -571,36 +561,14 @@ func (cli *Client) setContactBlockedStatus(user types.JID, block bool) ([]types. Content: []waBinary.Node{{ Tag: "item", Attrs: waBinary.Attrs{ - "action": blockAction, - "jid": user, + "jid": jid, + "action": string(action), }, }}, }) - if err != nil { - return nil, err - } - - blockedContacts := cli.cacheBlockedContacts(resp) - - return blockedContacts, nil -} - -// BlockContact updates the contact blocked status to true -func (cli *Client) BlockContact(user types.JID) ([]types.JID, error) { - blockedList, err := cli.setContactBlockedStatus(user, true) - if err != nil { - return nil, err - } - - return blockedList, nil -} - -// UnblockContact updates the contact blocked status to false -func (cli *Client) UnblockContact(user types.JID) ([]types.JID, error) { - blockedList, err := cli.setContactBlockedStatus(user, false) - if err != nil { - return nil, err + list, ok := resp.GetOptionalChildByTag("list") + if !ok { + return nil, &ElementMissingError{Tag: "list", In: "response to blocklist update"} } - - return blockedList, nil + return cli.parseBlocklist(&list), err }