diff --git a/README.md b/README.md index 805af5a..5beb4c0 100644 --- a/README.md +++ b/README.md @@ -433,6 +433,11 @@ await conn.toggleDisappearingMessages(jid, 0) const response2 = await conn.searchMessages ('so cool', '1234@c.us', 25, 1) // search in given chat ``` +- To block or unblock user + ``` ts + await conn.blockUser ("xyz@c.us", "add") // Block user + await conn.blockUser ("xyz@c.us", "remove") // Unblock user + ``` Of course, replace ``` xyz ``` with an actual ID. ## Groups diff --git a/src/Tests/Tests.Misc.ts b/src/Tests/Tests.Misc.ts index 047f95d..38e6693 100644 --- a/src/Tests/Tests.Misc.ts +++ b/src/Tests/Tests.Misc.ts @@ -258,4 +258,30 @@ WAConnectionTest('Misc', conn => { ) assert.ok(msg.message.extendedTextMessage) }) + it('should block & unblock a user', async () => { + const blockedCount = conn.blocklist.length; + + const waitForEventAdded = new Promise (resolve => { + conn.once ('blocklist-update', ({added}) => { + assert.ok (added.length) + resolve () + }) + }) + + await conn.blockUser (testJid, 'add') + assert.strictEqual(conn.blocklist.length, blockedCount + 1); + await waitForEventAdded + + await delay (2000) + const waitForEventRemoved = new Promise (resolve => { + conn.once ('blocklist-update', ({removed}) => { + assert.ok (removed.length) + resolve () + }) + }) + + await conn.blockUser (testJid, 'remove') + assert.strictEqual(conn.blocklist.length, blockedCount); + await waitForEventRemoved + }) }) \ No newline at end of file diff --git a/src/WAConnection/0.Base.ts b/src/WAConnection/0.Base.ts index d3815c9..e0aa4ed 100644 --- a/src/WAConnection/0.Base.ts +++ b/src/WAConnection/0.Base.ts @@ -66,6 +66,7 @@ export class WAConnection extends EventEmitter { lastChatsReceived: Date chats = new KeyedDB (Utils.waChatKey(false), value => value.jid) contacts: { [k: string]: WAContact } = {} + blocklist: string[] = []; /** Data structure of tokens & IDs used to establish one's identiy to WhatsApp Web */ protected authInfo: AuthenticationCredentials = null diff --git a/src/WAConnection/4.Events.ts b/src/WAConnection/4.Events.ts index df9a103..7a95534 100644 --- a/src/WAConnection/4.Events.ts +++ b/src/WAConnection/4.Events.ts @@ -1,6 +1,6 @@ import * as QR from 'qrcode-terminal' import { WAConnection as Base } from './3.Connect' -import { WAMessageStatusUpdate, WAMessage, WAContact, WAChat, WAMessageProto, WA_MESSAGE_STUB_TYPE, WA_MESSAGE_STATUS_TYPE, PresenceUpdate, BaileysEvent, DisconnectReason, WAOpenResult, Presence, AuthenticationCredentials, WAParticipantAction, WAGroupMetadata, WAUser, WANode, WAPresenceData, WAChatUpdate } from './Constants' +import { WAMessageStatusUpdate, WAMessage, WAContact, WAChat, WAMessageProto, WA_MESSAGE_STUB_TYPE, WA_MESSAGE_STATUS_TYPE, PresenceUpdate, BaileysEvent, DisconnectReason, WAOpenResult, Presence, AuthenticationCredentials, WAParticipantAction, WAGroupMetadata, WAUser, WANode, WAPresenceData, WAChatUpdate, BlocklistUpdate } from './Constants' import { whatsappID, unixTimestampSeconds, isGroupID, GET_MESSAGE_ID, WA_MESSAGE_ID, waMessageKey, newMessagesDB, shallowChanges, toNumber } from './Utils' import KeyedDB from '@adiwajshing/keyed-db' import { Mutex } from './Mutex' @@ -352,6 +352,20 @@ export class WAConnection extends Base { this.on('CB:MsgInfo', func) this.on ('qr', qr => QR.generate(qr, { small: true })) + + // blocklist updates + this.on('CB:Blocklist', json => { + json = json[1] + const initial = this.blocklist + this.blocklist = json.blocklist + + const added = this.blocklist.filter(id => !initial.includes(id)) + const removed = initial.filter(id => !this.blocklist.includes(id)) + + const update: BlocklistUpdate = { added, removed } + + this.emit('blocklist-update', update) + }) } /** Get the URL to download the profile picture of a person/group */ @Mutex (jid => jid) @@ -655,6 +669,8 @@ export class WAConnection extends Base { on (event: 'group-update', listener: (update: Partial & {jid: string, actor?: string}) => void): this /** when WA sends back a pong */ on (event: 'received-pong', listener: () => void): this + /** when a user is blocked or unblockd */ + on (event: 'blocklist-update', listener: (update: BlocklistUpdate) => void): this on (event: BaileysEvent | string, listener: (json: any) => void): this diff --git a/src/WAConnection/5.User.ts b/src/WAConnection/5.User.ts index edf6941..f69d18a 100644 --- a/src/WAConnection/5.User.ts +++ b/src/WAConnection/5.User.ts @@ -1,5 +1,5 @@ import {WAConnection as Base} from './4.Events' -import { Presence, WABroadcastListInfo, WAProfilePictureChange, WALoadChatOptions, WAChatIndex } from './Constants' +import { Presence, WABroadcastListInfo, WAProfilePictureChange, WALoadChatOptions, WAChatIndex, BlocklistUpdate } from './Constants' import { WAMessage, WANode, @@ -159,4 +159,44 @@ export class WAConnection extends Base { } return response } + /** + * Add or remove user from blocklist + * @param jid the ID of the person who you are blocking/unblocking + * @param type type of operation + */ + @Mutex (jid => jid) + async blockUser (jid: string, type: 'add' | 'remove' = 'add') { + jid.replace('@s.whatsapp.net', '@c.us') + + const tag = this.generateMessageTag() + const json: WANode = [ + 'block', + { + type: type, + }, + [ + ['user', { jid }, null] + ], + ] + const result = await this.setQuery ([json], [WAMetric.block, WAFlag.ignore], tag) + + if (result.status === 200) { + if (type === 'add') { + this.blocklist.push(jid) + } else { + const index = this.blocklist.indexOf(jid); + if (index !== -1) { + this.blocklist.splice(index, 1); + } + } + + // Blocklist update event + const update: BlocklistUpdate = { added: [], removed: [] } + let key = type === 'add' ? 'added' : 'removed' + update[key] = [ jid ] + this.emit('blocklist-update', update) + } + + return result + } } diff --git a/src/WAConnection/Constants.ts b/src/WAConnection/Constants.ts index 46f87ba..0922efd 100644 --- a/src/WAConnection/Constants.ts +++ b/src/WAConnection/Constants.ts @@ -428,6 +428,10 @@ export interface PresenceUpdate { type?: Presence deny?: boolean } +export interface BlocklistUpdate { + added: string[] + removed: string[] +} // path to upload the media export const MediaPathMap = { imageMessage: '/mms/image', @@ -467,4 +471,5 @@ export type BaileysEvent = 'group-update' | 'received-pong' | 'credentials-updated' | - 'connection-validated' \ No newline at end of file + 'connection-validated' | + 'blocklist-update' \ No newline at end of file