Skip to content

Commit

Permalink
Added support to block/unblock contacts and receive block/unblock events
Browse files Browse the repository at this point in the history
  • Loading branch information
emacielxp committed Sep 8, 2023
1 parent 9278fde commit 62aaec3
Show file tree
Hide file tree
Showing 8 changed files with 191 additions and 0 deletions.
5 changes: 5 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ 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
Expand Down Expand Up @@ -191,6 +194,8 @@ 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 },
Expand Down
1 change: 1 addition & 0 deletions connectionevents.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ 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()
}()
Expand Down
1 change: 1 addition & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ 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 {
Expand Down
43 changes: 43 additions & 0 deletions mdtest/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,47 @@ 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()
if err != nil {
log.Errorf("Failed to get blocked contacts list: %v", err)
} else {
for _, blockedContact := range blockedContacts {
log.Infof("%+v", blockedContact)
}
}
case "block":
if len(args) < 1 {
log.Errorf("Usage: block <jid>")
return
}
target, ok := parseJID(args[0])
if !ok {
return
}

blockedList, err := cli.BlockContact(target)
if err != nil {
log.Errorf("Error blocking contact: %v", err)
}

log.Infof("%+v", blockedList)
case "unblock":
if len(args) < 1 {
log.Errorf("Usage: unblock <jid>")
return
}
target, ok := parseJID(args[0])
if !ok {
return
}

blockedList, err := cli.UnblockContact(target)
if err != nil {
log.Errorf("Error unblocking contact: %v", err)
}

log.Infof("%+v", blockedList)
}
}

Expand Down Expand Up @@ -876,5 +917,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)
}
}
38 changes: 38 additions & 0 deletions notification.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,42 @@ 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
}
}

cli.blockedContactsCacheLock.Lock()
defer cli.blockedContactsCacheLock.Unlock()
for _, child := range childArray {
ag := child.AttrGetter()
var evt events.ContactBlockedStatusChange
evt.From = ag.JID("jid")
evt.Blocked = ag.String("action") == "block"
if !ag.OK() {
cli.Log.Debugf("Ignoring contact blocked status change notification with unexpected attributes: %v", 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)
}
}

func (cli *Client) handleAccountSyncNotification(node *waBinary.Node) {
for _, child := range node.GetChildren() {
switch child.Tag {
Expand All @@ -177,6 +213,8 @@ func (cli *Client) handleAccountSyncNotification(node *waBinary.Node) {
Timestamp: node.AttrGetter().UnixTime("t"),
JID: cli.getOwnID().ToNonAD(),
})
case "blocklist":
cli.handleContactBlockedStatusChangeNotification(&child)
default:
cli.Log.Debugf("Unhandled account sync item %s", child.Tag)
}
Expand Down
8 changes: 8 additions & 0 deletions send.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,14 @@ 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()
}
Expand Down
8 changes: 8 additions & 0 deletions types/events/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -460,3 +460,11 @@ type MediaRetry struct {
SenderID types.JID // The user who sent the message. Only present in groups.
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
}
87 changes: 87 additions & 0 deletions user.go
Original file line number Diff line number Diff line change
Expand Up @@ -517,3 +517,90 @@ func (cli *Client) usync(ctx context.Context, jids []types.JID, mode, context st
return &list, err
}
}

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

return blockedContacts
}

// GetAllBlockedContacts gets all the contacts that was blocked on connected Whatsapp user phone
func (cli *Client) GetAllBlockedContacts() ([]types.JID, error) {
resp, err := cli.sendIQ(infoQuery{
Namespace: "blocklist",
Type: iqGet,
To: types.ServerJID,
})
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"
}

resp, err := cli.sendIQ(infoQuery{
Namespace: "blocklist",
Type: iqSet,
To: types.ServerJID,
Content: []waBinary.Node{{
Tag: "item",
Attrs: waBinary.Attrs{
"action": blockAction,
"jid": user,
},
}},
})
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
}

return blockedList, nil
}

0 comments on commit 62aaec3

Please sign in to comment.