diff --git a/example.js b/example.js index dcefe32..a3ade0e 100644 --- a/example.js +++ b/example.js @@ -593,4 +593,9 @@ client.on('group_membership_request', async (notification) => { /** You can approve or reject the newly appeared membership request: */ await client.approveGroupMembershipRequestss(notification.chatId, notification.author); await client.rejectGroupMembershipRequests(notification.chatId, notification.author); -}); \ No newline at end of file +}); + +client.on('vote_update', (vote) => { + /** The vote that was affected: */ + console.log(vote); +}); diff --git a/index.d.ts b/index.d.ts index 6c1f703..d41db10 100644 --- a/index.d.ts +++ b/index.d.ts @@ -371,6 +371,14 @@ declare namespace WAWebJS { /** Emitted when the RemoteAuth session is saved successfully on the external Database */ on(event: 'remote_session_saved', listener: () => void): this + + /** + * Emitted when some poll option is selected or deselected, + * shows a user's current selected option(s) on the poll + */ + on(event: 'vote_update', listener: ( + vote: PollVote + ) => void): this } /** Current connection information */ @@ -1018,6 +1026,34 @@ declare namespace WAWebJS { constructor(pollName: string, pollOptions: Array, options?: PollSendOptions) } + /** Represents a Poll Vote on WhatsApp */ + export interface PollVote { + /** The person who voted */ + voter: string; + + /** + * The selected poll option(s) + * If it's an empty array, the user hasn't selected any options on the poll, + * may occur when they deselected all poll options + */ + selectedOptions: SelectedPollOption[]; + + /** Timestamp the option was selected or deselected at */ + interractedAtTs: number; + + /** The poll creation message associated with the poll vote */ + parentMessage: Message; + } + + /** Selected poll option structure */ + export interface SelectedPollOption { + /** The local selected option ID */ + id: number; + + /** The option name */ + name: string; + } + export interface Label { /** Label name */ name: string, diff --git a/src/Client.js b/src/Client.js index f2ad706..a0ac1a6 100644 --- a/src/Client.js +++ b/src/Client.js @@ -11,7 +11,7 @@ const { ExposeStore, LoadUtils } = require('./util/Injected'); const ChatFactory = require('./factories/ChatFactory'); const ContactFactory = require('./factories/ContactFactory'); const WebCacheFactory = require('./webCache/WebCacheFactory'); -const { ClientInfo, Message, MessageMedia, Contact, Location, Poll, GroupNotification, Label, Call, Buttons, List, Reaction } = require('./structures'); +const { ClientInfo, Message, MessageMedia, Contact, Location, Poll, PollVote, GroupNotification, Label, Call, Buttons, List, Reaction } = require('./structures'); const LegacySessionAuth = require('./authStrategies/LegacySessionAuth'); const NoAuth = require('./authStrategies/NoAuth'); @@ -54,6 +54,7 @@ const NoAuth = require('./authStrategies/NoAuth'); * @fires Client#contact_changed * @fires Client#group_admin_changed * @fires Client#group_membership_request + * @fires Client#vote_update */ class Client extends EventEmitter { constructor(options = {}) { @@ -685,6 +686,16 @@ class Client extends EventEmitter { this.emit(Events.MESSAGE_CIPHERTEXT, new Message(this, msg)); }); + await page.exposeFunction('onPollVoteEvent', (vote) => { + const _vote = new PollVote(this, vote); + /** + * Emitted when some poll option is selected or deselected, + * shows a user's current selected option(s) on the poll + * @event Client#vote_update + */ + this.emit(Events.VOTE_UPDATE, _vote); + }); + await page.evaluate(() => { window.Store.Msg.on('change', (msg) => { window.onChangeMessageEvent(window.WWebJS.getMessageModel(msg)); }); window.Store.Msg.on('change:type', (msg) => { window.onChangeMessageTypeEvent(window.WWebJS.getMessageModel(msg)); }); @@ -709,6 +720,10 @@ class Client extends EventEmitter { } }); window.Store.Chat.on('change:unreadCount', (chat) => {window.onChatUnreadCountEvent(chat);}); + window.Store.PollVote.on('add', (vote) => { + const pollVoteModel = window.WWebJS.getPollVoteModel(vote); + pollVoteModel && window.onPollVoteEvent(pollVoteModel); + }); { const module = window.Store.createOrUpdateReactionsModule; diff --git a/src/structures/Message.js b/src/structures/Message.js index f26d3fb..b61563a 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -295,6 +295,7 @@ class Message extends Base { this.allowMultipleAnswers = Boolean(!data.pollSelectableOptionsCount); this.pollInvalidated = data.pollInvalidated; this.isSentCagPollCreation = data.isSentCagPollCreation; + this.messageSecret = Object.keys(data.messageSecret).map((key) => data.messageSecret[key]); } return super._patch(data); diff --git a/src/structures/PollVote.js b/src/structures/PollVote.js new file mode 100644 index 0000000..4c7eb02 --- /dev/null +++ b/src/structures/PollVote.js @@ -0,0 +1,61 @@ +'use strict'; + +const Message = require('./Message'); +const Base = require('./Base'); + +/** + * Selected poll option structure + * @typedef {Object} SelectedPollOption + * @property {number} id The local selected or deselected option ID + * @property {string} name The option name + */ + +/** + * Represents a Poll Vote on WhatsApp + * @extends {Base} + */ +class PollVote extends Base { + constructor(client, data) { + super(client); + + if (data) this._patch(data); + } + + _patch(data) { + /** + * The person who voted + * @type {string} + */ + this.voter = data.sender; + + /** + * The selected poll option(s) + * If it's an empty array, the user hasn't selected any options on the poll, + * may occur when they deselected all poll options + * @type {SelectedPollOption[]} + */ + this.selectedOptions = + data.selectedOptionLocalIds.length > 0 + ? data.selectedOptionLocalIds.map((e) => ({ + name: data.parentMessage.pollOptions.find((x) => x.localId === e).name, + localId: e + })) + : []; + + /** + * Timestamp the option was selected or deselected at + * @type {number} + */ + this.interractedAtTs = data.senderTimestampMs; + + /** + * The poll creation message associated with the poll vote + * @type {Message} + */ + this.parentMessage = new Message(this.client, data.parentMessage); + + return super._patch(data); + } +} + +module.exports = PollVote; diff --git a/src/structures/index.js b/src/structures/index.js index 796d514..5568a87 100644 --- a/src/structures/index.js +++ b/src/structures/index.js @@ -20,4 +20,5 @@ module.exports = { Payment: require('./Payment'), Reaction: require('./Reaction'), Poll: require('./Poll'), + PollVote: require('./PollVote') }; diff --git a/src/util/Constants.js b/src/util/Constants.js index 012bd65..e8c5fe3 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -65,7 +65,8 @@ exports.Events = { STATE_CHANGED: 'change_state', BATTERY_CHANGED: 'change_battery', INCOMING_CALL: 'call', - REMOTE_SESSION_SAVED: 'remote_session_saved' + REMOTE_SESSION_SAVED: 'remote_session_saved', + VOTE_UPDATE: 'vote_update' }; /** diff --git a/src/util/Injected.js b/src/util/Injected.js index 3e85a91..c0df187 100644 --- a/src/util/Injected.js +++ b/src/util/Injected.js @@ -556,6 +556,15 @@ exports.LoadUtils = () => { return msg; }; + window.WWebJS.getPollVoteModel = (vote) => { + const _vote = vote.serialize(); + if (vote.parentMsgKey) { + const msg = window.Store.Msg.get(vote.parentMsgKey); + msg && (_vote.parentMessage = window.WWebJS.getMessageModel(msg)); + return _vote; + } + return null; + }; window.WWebJS.getChatModel = async chat => {