feat: send polls (#2377)
* feat: added Poll class * feat: logic for sending polls added * updated poll properties on returned message instance * docs updated * usage example updated * module fixed * 'pollSendOptions' changed to 'options' * window.compareWwebVersions fixed * small update to improve the performance of findImpl fix * added option for custom message secret * usage example updated
This commit is contained in:
parent
49feb3660c
commit
67d305be78
9 changed files with 131 additions and 6 deletions
18
example.js
18
example.js
|
|
@ -1,4 +1,4 @@
|
|||
const { Client, Location, List, Buttons, LocalAuth } = require('./index');
|
||||
const { Client, Location, Poll, List, Buttons, LocalAuth } = require('./index');
|
||||
|
||||
const client = new Client({
|
||||
authStrategy: new LocalAuth(),
|
||||
|
|
@ -290,6 +290,22 @@ client.on('message', async msg => {
|
|||
client.sendMessage(msg.from, list);
|
||||
} else if (msg.body === '!reaction') {
|
||||
msg.react('👍');
|
||||
} else if (msg.body === '!sendpoll') {
|
||||
/** By default the poll is created as a single choice poll: */
|
||||
await msg.reply(new Poll('Winter or Summer?', ['Winter', 'Summer']));
|
||||
/** If you want to provide a multiple choice poll, add allowMultipleAnswers as true: */
|
||||
await msg.reply(new Poll('Cats or Dogs?', ['Cats', 'Dogs'], { allowMultipleAnswers: true }));
|
||||
/**
|
||||
* You can provide a custom message secret, it can be used as a poll ID:
|
||||
* @note It has to be a unique vector with a length of 32
|
||||
*/
|
||||
await msg.reply(
|
||||
new Poll('Cats or Dogs?', ['Cats', 'Dogs'], {
|
||||
messageSecret: [
|
||||
1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
]
|
||||
})
|
||||
);
|
||||
} else if (msg.body === '!edit') {
|
||||
if (msg.hasQuotedMsg) {
|
||||
const quotedMsg = await msg.getQuotedMessage();
|
||||
|
|
|
|||
29
index.d.ts
vendored
29
index.d.ts
vendored
|
|
@ -707,6 +707,7 @@ declare namespace WAWebJS {
|
|||
PROTOCOL = 'protocol',
|
||||
REACTION = 'reaction',
|
||||
TEMPLATE_BUTTON_REPLY = 'template_button_reply',
|
||||
POLL_CREATION = 'poll_creation',
|
||||
}
|
||||
|
||||
/** Client status */
|
||||
|
|
@ -866,6 +867,11 @@ declare namespace WAWebJS {
|
|||
selectedRowId?: string,
|
||||
/** Returns message in a raw format */
|
||||
rawData: object,
|
||||
pollName: string,
|
||||
/** Avaiaible poll voting options */
|
||||
pollOptions: string[],
|
||||
/** False for a single choice poll, true for a multiple choice poll */
|
||||
allowMultipleAnswers: boolean,
|
||||
/*
|
||||
* Reloads this Message object's data in-place with the latest values from WhatsApp Web.
|
||||
* Note that the Message must still be in the web app cache for this to work, otherwise will return null.
|
||||
|
|
@ -946,6 +952,27 @@ declare namespace WAWebJS {
|
|||
constructor(latitude: number, longitude: number, options?: LocationSendOptions)
|
||||
}
|
||||
|
||||
/** Poll send options */
|
||||
export interface PollSendOptions {
|
||||
/** False for a single choice poll, true for a multiple choice poll (false by default) */
|
||||
allowMultipleAnswers?: boolean,
|
||||
/**
|
||||
* The custom message secret, can be used as a poll ID
|
||||
* @note It has to be a unique vector with a length of 32
|
||||
*/
|
||||
messageSecret: ?Array<number>
|
||||
}
|
||||
|
||||
/** Represents a Poll on WhatsApp */
|
||||
export interface Poll {
|
||||
pollName: string,
|
||||
pollOptions: Array<{
|
||||
name: string,
|
||||
localId: number
|
||||
}>,
|
||||
options: PollSendOptions
|
||||
}
|
||||
|
||||
export interface Label {
|
||||
/** Label name */
|
||||
name: string,
|
||||
|
|
@ -1037,7 +1064,7 @@ declare namespace WAWebJS {
|
|||
static fromUrl: (url: string, options?: MediaFromURLOptions) => Promise<MessageMedia>
|
||||
}
|
||||
|
||||
export type MessageContent = string | MessageMedia | Location | Contact | Contact[] | List | Buttons
|
||||
export type MessageContent = string | MessageMedia | Location | Poll | Contact | Contact[] | List | Buttons
|
||||
|
||||
/**
|
||||
* Represents a Contact on WhatsApp
|
||||
|
|
|
|||
1
index.js
1
index.js
|
|
@ -18,6 +18,7 @@ module.exports = {
|
|||
BusinessContact: require('./src/structures/BusinessContact'),
|
||||
ClientInfo: require('./src/structures/ClientInfo'),
|
||||
Location: require('./src/structures/Location'),
|
||||
Poll: require('./src/structures/Poll'),
|
||||
ProductMetadata: require('./src/structures/ProductMetadata'),
|
||||
List: require('./src/structures/List'),
|
||||
Buttons: require('./src/structures/Buttons'),
|
||||
|
|
|
|||
|
|
@ -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, GroupNotification, Label, Call, Buttons, List, Reaction } = require('./structures');
|
||||
const { ClientInfo, Message, MessageMedia, Contact, Location, Poll, GroupNotification, Label, Call, Buttons, List, Reaction } = require('./structures');
|
||||
const LegacySessionAuth = require('./authStrategies/LegacySessionAuth');
|
||||
const NoAuth = require('./authStrategies/NoAuth');
|
||||
|
||||
|
|
@ -820,7 +820,7 @@ class Client extends EventEmitter {
|
|||
/**
|
||||
* Send a message to a specific chatId
|
||||
* @param {string} chatId
|
||||
* @param {string|MessageMedia|Location|Contact|Array<Contact>|Buttons|List} content
|
||||
* @param {string|MessageMedia|Location|Poll|Contact|Array<Contact>|Buttons|List} content
|
||||
* @param {MessageSendOptions} [options] - Options used when sending the message
|
||||
*
|
||||
* @returns {Promise<Message>} Message that was just sent
|
||||
|
|
@ -857,6 +857,9 @@ class Client extends EventEmitter {
|
|||
} else if (content instanceof Location) {
|
||||
internalOptions.location = content;
|
||||
content = '';
|
||||
} else if (content instanceof Poll) {
|
||||
internalOptions.poll = content;
|
||||
content = '';
|
||||
} else if (content instanceof Contact) {
|
||||
internalOptions.contactCard = content.id._serialized;
|
||||
content = '';
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ class Message extends Base {
|
|||
* Message content
|
||||
* @type {string}
|
||||
*/
|
||||
this.body = this.hasMedia ? data.caption || '' : data.body || '';
|
||||
this.body = this.hasMedia ? data.caption || '' : data.body || data.pollName || '';
|
||||
|
||||
/**
|
||||
* Message type
|
||||
|
|
@ -271,6 +271,20 @@ class Message extends Base {
|
|||
this.selectedRowId = data.listResponse.singleSelectReply.selectedRowId;
|
||||
}
|
||||
|
||||
if (this.type === MessageTypes.POLL_CREATION) {
|
||||
this.pollName = data.pollName;
|
||||
this.pollOptions = data.pollOptions;
|
||||
this.allowMultipleAnswers = Boolean(!data.pollSelectableOptionsCount);
|
||||
this.pollInvalidated = data.pollInvalidated;
|
||||
this.isSentCagPollCreation = data.isSentCagPollCreation;
|
||||
|
||||
delete this._data.pollName;
|
||||
delete this._data.pollOptions;
|
||||
delete this._data.pollSelectableOptionsCount;
|
||||
delete this._data.pollInvalidated;
|
||||
delete this._data.isSentCagPollCreation;
|
||||
}
|
||||
|
||||
return super._patch(data);
|
||||
}
|
||||
|
||||
|
|
|
|||
44
src/structures/Poll.js
Normal file
44
src/structures/Poll.js
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* Poll send options
|
||||
* @typedef {Object} PollSendOptions
|
||||
* @property {boolean} [allowMultipleAnswers=false] If false it is a single choice poll, otherwise it is a multiple choice poll (false by default)
|
||||
* @property {?Array<number>} messageSecret The custom message secret, can be used as a poll ID. NOTE: it has to be a unique vector with a length of 32
|
||||
*/
|
||||
|
||||
/** Represents a Poll on WhatsApp */
|
||||
class Poll {
|
||||
/**
|
||||
* @param {string} pollName
|
||||
* @param {Array<string>} pollOptions
|
||||
* @param {PollSendOptions} options
|
||||
*/
|
||||
constructor(pollName, pollOptions, options = {}) {
|
||||
/**
|
||||
* The name of the poll
|
||||
* @type {string}
|
||||
*/
|
||||
this.pollName = pollName.trim();
|
||||
|
||||
/**
|
||||
* The array of poll options
|
||||
* @type {Array.<{name: string, localId: number}>}
|
||||
*/
|
||||
this.pollOptions = pollOptions.map((option, index) => ({
|
||||
name: option.trim(),
|
||||
localId: index
|
||||
}));
|
||||
|
||||
/**
|
||||
* The send options for the poll
|
||||
* @type {PollSendOptions}
|
||||
*/
|
||||
this.options = {
|
||||
allowMultipleAnswers: options.allowMultipleAnswers === true,
|
||||
messageSecret: options.messageSecret
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Poll;
|
||||
|
|
@ -19,4 +19,5 @@ module.exports = {
|
|||
List: require('./List'),
|
||||
Payment: require('./Payment'),
|
||||
Reaction: require('./Reaction'),
|
||||
Poll: require('./Poll'),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -108,6 +108,7 @@ exports.MessageTypes = {
|
|||
PROTOCOL: 'protocol',
|
||||
REACTION: 'reaction',
|
||||
TEMPLATE_BUTTON_REPLY: 'template_button_reply',
|
||||
POLL_CREATION: 'poll_creation',
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -142,7 +142,7 @@ exports.ExposeStore = (moduleRaidStr) => {
|
|||
const modifiedFunction = (...args) => callback(originalFunction, ...args);
|
||||
module[target.index][target.property] = modifiedFunction;
|
||||
};
|
||||
|
||||
|
||||
window.injectToFunction({ moduleId: 'mediaTypeFromProtobuf', index: 0, property: 'mediaTypeFromProtobuf' }, (func, ...args) => { const [proto] = args; return proto.locationMessage ? null : func(...args); });
|
||||
|
||||
window.injectToFunction({ moduleId: 'typeAttributeFromProtobuf', index: 0, property: 'typeAttributeFromProtobuf' }, (func, ...args) => { const [proto] = args; return proto.locationMessage || proto.groupInviteMessage ? 'text' : func(...args); });
|
||||
|
|
@ -215,6 +215,23 @@ exports.LoadUtils = () => {
|
|||
delete options.location;
|
||||
}
|
||||
|
||||
let _pollOptions = {};
|
||||
if (options.poll) {
|
||||
const { pollName, pollOptions } = options.poll;
|
||||
const { allowMultipleAnswers, messageSecret } = options.poll.options;
|
||||
_pollOptions = {
|
||||
type: 'poll_creation',
|
||||
pollName: pollName,
|
||||
pollOptions: pollOptions,
|
||||
pollSelectableOptionsCount: allowMultipleAnswers ? 0 : 1,
|
||||
messageSecret:
|
||||
Array.isArray(messageSecret) && messageSecret.length === 32
|
||||
? new Uint8Array(messageSecret)
|
||||
: window.crypto.getRandomValues(new Uint8Array(32))
|
||||
};
|
||||
delete options.poll;
|
||||
}
|
||||
|
||||
let vcardOptions = {};
|
||||
if (options.contactCard) {
|
||||
let contact = window.Store.Contact.get(options.contactCard);
|
||||
|
|
@ -334,6 +351,7 @@ exports.LoadUtils = () => {
|
|||
type: 'chat',
|
||||
...ephemeralFields,
|
||||
...locationOptions,
|
||||
..._pollOptions,
|
||||
...attOptions,
|
||||
...(attOptions.toJSON ? attOptions.toJSON() : {}),
|
||||
...quotedMsgOptions,
|
||||
|
|
|
|||
Loading…
Reference in a new issue