feat: Send buttons and Lists (#756)

This commit is contained in:
tuyuribr
2021-09-13 18:06:05 -03:00
committed by GitHub
parent ab415ff9be
commit b60029ed09
10 changed files with 259 additions and 6 deletions

View File

@@ -1,5 +1,5 @@
const fs = require('fs');
const { Client, Location } = require('./index');
const { Client, Location, List, Buttons } = require('./index');
const SESSION_FILE_PATH = './session.json';
let sessionCfg;
@@ -204,6 +204,13 @@ client.on('message', async msg => {
const quotedMsg = await msg.getQuotedMessage();
client.interface.openChatWindowAt(quotedMsg.id._serialized);
}
} else if (msg.body === '!buttons') {
let button = new Buttons('Button body',[{body:'bt1'},{body:'bt2'},{body:'bt3'}],'title','footer');
client.sendMessage(msg.from, button);
} else if (msg.body === '!list') {
let sections = [{title:'sectionTitle',rows:[{title:'ListItem1', description: 'desc'},{title:'ListItem2'}]}];
let list = new List('List body','btnText',sections,'Title','footer');
client.sendMessage(msg.from, list);
}
});

27
index.d.ts vendored
View File

@@ -567,6 +567,12 @@ declare namespace WAWebJS {
businessOwnerJid?: string,
/** Product JID */
productId?: string,
/** Message buttons */
dynamicReplyButtons?: object,
/** Selected button ID */
selectedButtonId?: string,
/** Selected list row ID */
selectedRowId?: string,
/** Accept the Group V4 Invite in message */
acceptGroupV4Invite: () => Promise<{status: number}>,
/** Deletes the message from the chat */
@@ -1149,6 +1155,27 @@ declare namespace WAWebJS {
/** Object with participants */
participants: object
}
/** Message type List */
export class List {
body: string
buttonText: string
sections: Array<any>
title?: string | null
footer?: string | null
constructor(body: string, buttonText: string, sections: Array<any>, title?: string | null, footer?: string | null)
}
/** Message type buttons */
export class Buttons {
body: string | MessageMedia
buttons: Array<Array<string>>
title?: string | null
footer?: string | null
constructor(body: string, buttons: Array<Array<string>>, title?: string | null, footer?: string | null)
}
}
export = WAWebJS

View File

@@ -19,5 +19,7 @@ module.exports = {
ClientInfo: require('./src/structures/ClientInfo'),
Location: require('./src/structures/Location'),
ProductMetadata: require('./src/structures/ProductMetadata'),
List: require('./src/structures/List'),
Buttons: require('./src/structures/Buttons'),
...Constants
};

View File

@@ -11,7 +11,7 @@ const { WhatsWebURL, DefaultOptions, Events, WAState } = require('./util/Constan
const { ExposeStore, LoadUtils } = require('./util/Injected');
const ChatFactory = require('./factories/ChatFactory');
const ContactFactory = require('./factories/ContactFactory');
const { ClientInfo, Message, MessageMedia, Contact, Location, GroupNotification , Label, Call } = require('./structures');
const { ClientInfo, Message, MessageMedia, Contact, Location, GroupNotification , Label, Call, Buttons, List} = require('./structures');
/**
* Starting point for interacting with the WhatsApp Web API
* @extends {EventEmitter}
@@ -479,7 +479,7 @@ class Client extends EventEmitter {
/**
* Send a message to a specific chatId
* @param {string} chatId
* @param {string|MessageMedia|Location|Contact|Array<Contact>} content
* @param {string|MessageMedia|Location|Contact|Array<Contact>|Buttons|List} content
* @param {MessageSendOptions} [options] - Options used when sending the message
*
* @returns {Promise<Message>} Message that was just sent
@@ -516,6 +516,13 @@ class Client extends EventEmitter {
} else if(Array.isArray(content) && content.length > 0 && content[0] instanceof Contact) {
internalOptions.contactCardList = content.map(contact => contact.id._serialized);
content = '';
} else if(content instanceof Buttons){
if(content.type !== 'chat'){internalOptions.attachment = content.body;}
internalOptions.buttons = content;
content = '';
} else if(content instanceof List){
internalOptions.list = content;
content = '';
}
if (internalOptions.sendMediaAsSticker && internalOptions.attachment) {

68
src/structures/Buttons.js Normal file
View File

@@ -0,0 +1,68 @@
'use strict';
const MessageMedia = require('./MessageMedia');
const Util = require('../util/Util');
/**
* Message type buttons
*/
class Buttons {
/**
* @param {string|MessageMedia} body
* @param {Array<Array<string>>} buttons
* @param {string?} title
* @param {string?} footer
*/
constructor(body, buttons, title, footer) {
/**
* Message body
* @type {string|MessageMedia}
*/
this.body = body;
/**
* title of message
* @type {string}
*/
this.title = title;
/**
* footer of message
* @type {string}
*/
this.footer = footer;
if (body instanceof MessageMedia) {
this.type = 'media';
this.title = '';
}else{
this.type = 'chat';
}
/**
* buttons of message
* @type {Array<Array<string>>}
*/
this.buttons = this._format(buttons);
if(!this.buttons.length){ throw '[BT01] No buttons';}
}
/**
* Creates button array from simple array
* @param {Array<Array<string>>} buttons
* @returns {Array<Array<string>>}
* @example
* Input: [{id:'customId',body:'button1'},{body:'button2'},{body:'button3'},{body:'button4'}]
* Returns: [{ buttonId:'customId',buttonText:{'displayText':'button1'},type: 1 },{buttonId:'n3XKsL',buttonText:{'displayText':'button2'},type:1},{buttonId:'NDJk0a',buttonText:{'displayText':'button3'},type:1}]
*/
_format(buttons){
buttons = buttons.slice(0,3); // phone users can only see 3 buttons, so lets limit this
return buttons.map((btn) => {
return {'buttonId':btn.id ? btn.id : Util.generateHash(6),'buttonText':{'displayText':btn.body},'type':1};
});
}
}
module.exports = Buttons;

79
src/structures/List.js Normal file
View File

@@ -0,0 +1,79 @@
'use strict';
const Util = require('../util/Util');
/**
* Message type List
*/
class List {
/**
* @param {string} body
* @param {string} buttonText
* @param {Array<any>} sections
* @param {string?} title
* @param {string?} footer
*/
constructor(body, buttonText, sections, title, footer) {
/**
* Message body
* @type {string}
*/
this.description = body;
/**
* List button text
* @type {string}
*/
this.buttonText = buttonText;
/**
* title of message
* @type {string}
*/
this.title = title;
/**
* footer of message
* @type {string}
*/
this.footer = footer;
/**
* sections of message
* @type {Array<any>}
*/
this.sections = this._format(sections);
}
/**
* Creates section array from simple array
* @param {Array<any>} sections
* @returns {Array<any>}
* @example
* Input: [{title:'sectionTitle',rows:[{id:'customId', title:'ListItem2', description: 'desc'},{title:'ListItem2'}]}}]
* Returns: [{'title':'sectionTitle','rows':[{'rowId':'customId','title':'ListItem1','description':'desc'},{'rowId':'oGSRoD','title':'ListItem2','description':''}]}]
*/
_format(sections){
if(!sections.length){throw '[LT02] List without sections';}
if(sections.length > 1){throw '[LT05] Lists with more than one section are having problems';}
return sections.map( (section) =>{
if(!section.rows.length){throw '[LT03] Section without rows';}
return {
title: section.title ? section.title : undefined,
rows: section.rows.map( (row) => {
if(!row.title){throw '[LT04] Row without title';}
return {
rowId: row.id ? row.id : Util.generateHash(6),
title: row.title,
description: row.description ? row.description : ''
};
})
};
});
}
}
module.exports = List;

View File

@@ -206,6 +206,21 @@ class Message extends Base {
*/
this.links = data.links;
/** Buttons */
if (data.dynamicReplyButtons) {
this.dynamicReplyButtons = data.dynamicReplyButtons;
}
/** Selected Button Id **/
if (data.selectedButtonId) {
this.selectedButtonId = data.selectedButtonId;
}
/** Selected List row Id **/
if (data.listResponse && data.listResponse.singleSelectReply.selectedRowId) {
this.selectedRowId = data.listResponse.singleSelectReply.selectedRowId;
}
return super._patch(data);
}

View File

@@ -15,5 +15,7 @@ module.exports = {
Order: require('./Order'),
Product: require('./Product'),
Call: require('./Call'),
Buttons: require('./Buttons'),
List: require('./List'),
Payment: require('./Payment')
};
};

View File

@@ -75,6 +75,8 @@ exports.MessageTypes = {
PRODUCT: 'product',
UNKNOWN: 'unknown',
GROUP_INVITE: 'groups_v4_invite',
LIST: 'list',
BUTTONS_RESPONSE: 'buttons_response',
PAYMENT: 'payment'
};

View File

@@ -153,7 +153,47 @@ exports.LoadUtils = () => {
options = { ...options, ...preview };
}
}
let extraOptions = {};
if(options.buttons){
let caption;
if(options.buttons.type === 'chat') {
content = options.buttons.body;
caption = content;
}else{
caption = options.caption ? options.caption : ' '; //Caption can't be empty
}
extraOptions = {
productHeaderImageRejected: false,
isFromTemplate: false,
isDynamicReplyButtonsMsg: true,
title: options.buttons.title ? options.buttons.title : undefined,
footer: options.buttons.footer ? options.buttons.footer : undefined,
dynamicReplyButtons: options.buttons.buttons,
replyButtons: options.buttons.buttons,
caption: caption
};
delete options.buttons;
}
if(options.list){
if(window.Store.Conn.platform === 'smba' || window.Store.Conn.platform === 'smbi'){
throw '[LT01] Whatsapp business can\'t send this yet';
}
extraOptions = {
...extraOptions,
type: 'list',
footer: options.list.footer,
list: {
...options.list,
listType: 1
},
body: options.list.description
};
delete options.list;
delete extraOptions.list.footer;
}
const newMsgId = new window.Store.MsgKey({
fromMe: true,
remote: chat.id,
@@ -175,7 +215,8 @@ exports.LoadUtils = () => {
...locationOptions,
...attOptions,
...quotedMsgOptions,
...vcardOptions
...vcardOptions,
...extraOptions
};
await window.Store.SendMessage.addAndSendMsgToChat(chat, message);
@@ -282,8 +323,11 @@ exports.LoadUtils = () => {
if (msg.buttons) {
msg.buttons = msg.buttons.serialize();
}
if (msg.dynamicReplyButtons) {
msg.dynamicReplyButtons = JSON.parse(JSON.stringify(msg.dynamicReplyButtons));
}
if(msg.replyButtons) {
msg.replyButtons = msg.replyButtons.serialize();
msg.replyButtons = JSON.parse(JSON.stringify(msg.replyButtons));
}
delete msg.pendingAckUpdate;