mirror of
https://github.com/cheveguerra/whatsapp-web.js.git
synced 2026-04-20 12:39:20 +00:00
Fix buttons list (#1649)
* DOCS: Buttons usage * FIX: Limit buttons amount to avoid crashes * STYLE: Fix for ESLINT test
This commit is contained in:
15
example.js
15
example.js
@@ -189,7 +189,20 @@ client.on('message', async msg => {
|
|||||||
client.interface.openChatWindowAt(quotedMsg.id._serialized);
|
client.interface.openChatWindowAt(quotedMsg.id._serialized);
|
||||||
}
|
}
|
||||||
} else if (msg.body === '!buttons') {
|
} else if (msg.body === '!buttons') {
|
||||||
let button = new Buttons('Button body',[{body:'bt1'},{body:'bt2'},{body:'bt3'}],'title','footer');
|
let button = new Buttons(
|
||||||
|
'Button body',
|
||||||
|
[
|
||||||
|
{ body: 'whatsapp-web.js', url: 'https://wwebjs.dev/' },
|
||||||
|
{ body: 'Call me', number: '+1 (805) 457-4992' },
|
||||||
|
{ body: 'third special button', number: '+1 (202) 968-6161' },// Limited to 2 especial buttons, this one will be ignored
|
||||||
|
{ body: 'Some text' },
|
||||||
|
{ body: 'Another text' },
|
||||||
|
{ body: 'Another another text' },
|
||||||
|
{ body: 'Fourth button' }// Limited to 3 regular buttons, this one will be ignored
|
||||||
|
],
|
||||||
|
'title',
|
||||||
|
'footer'
|
||||||
|
);
|
||||||
client.sendMessage(msg.from, button);
|
client.sendMessage(msg.from, button);
|
||||||
} else if (msg.body === '!list') {
|
} else if (msg.body === '!list') {
|
||||||
let sections = [{title:'sectionTitle',rows:[{title:'ListItem1', description: 'desc'},{title:'ListItem2'}]}];
|
let sections = [{title:'sectionTitle',rows:[{title:'ListItem1', description: 'desc'},{title:'ListItem2'}]}];
|
||||||
|
|||||||
@@ -71,11 +71,18 @@ class Buttons {
|
|||||||
* Returns: [{ buttonId:'customId',buttonText:{'displayText':'button1'},type: 1 },{buttonId:'n3XKsL',buttonText:{'displayText':'button2'},type:1},{buttonId:'NDJk0a',buttonText:{'displayText':'button3'},type:1}]
|
* Returns: [{ buttonId:'customId',buttonText:{'displayText':'button1'},type: 1 },{buttonId:'n3XKsL',buttonText:{'displayText':'button2'},type:1},{buttonId:'NDJk0a',buttonText:{'displayText':'button3'},type:1}]
|
||||||
*/
|
*/
|
||||||
_format(buttons){
|
_format(buttons){
|
||||||
buttons = buttons.slice(0,3); // phone users can only see 3 buttons, so lets limit this
|
// phone users can only see 3 regular buttons (not url or phone) and 2 especial buttons, so lets limit this
|
||||||
|
const especialButtons = buttons.filter(button => button.url || button.number).slice(0,2);
|
||||||
|
const regularButtons = buttons.filter(button => !button.url && !button.number).slice(0,3);
|
||||||
|
buttons = especialButtons.concat(regularButtons);
|
||||||
|
|
||||||
return buttons.map((btn) => {
|
return buttons.map((btn) => {
|
||||||
|
if (btn.url && btn.number) throw 'button can\'t be with url and number together';
|
||||||
return {
|
return {
|
||||||
buttonId: btn.id ? String(btn.id) : Util.generateHash(6),
|
buttonId: btn.id ? String(btn.id) : Util.generateHash(6),
|
||||||
buttonText: {displayText: btn.body},
|
url: btn.url,
|
||||||
|
phoneNumber: btn.number,
|
||||||
|
buttonText: btn.body,
|
||||||
type: 1
|
type: 1
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -77,40 +77,112 @@ exports.ExposeStore = (moduleRaidStr) => {
|
|||||||
window.mR.findModule(selector.name)[selector.index][selector.property] = (...args) => callback(oldFunct, args);
|
window.mR.findModule(selector.name)[selector.index][selector.property] = (...args) => callback(oldFunct, args);
|
||||||
};
|
};
|
||||||
|
|
||||||
window.findProxyModel = (name) => {
|
window.injectToFunction({
|
||||||
const baseName = name.replace(/Model$/, '');
|
index: 0,
|
||||||
|
name: 'createMsgProtobuf',
|
||||||
const names = [baseName];
|
property: 'createMsgProtobuf'
|
||||||
|
}, (func, args) => {
|
||||||
// ChatModel => "chat"
|
const [message] = args;
|
||||||
names.push(baseName.replace(/^(\w)/, (l) => l.toLowerCase()));
|
const proto = func(...args);
|
||||||
|
if (message.hydratedButtons) {
|
||||||
// CartItemModel => "cart-item"
|
const hydratedTemplate = {
|
||||||
// ProductListModel => "product_list"
|
hydratedButtons: message.hydratedButtons,
|
||||||
const parts = baseName.split(/(?=[A-Z])/);
|
|
||||||
|
|
||||||
names.push(parts.join('-').toLowerCase());
|
|
||||||
names.push(parts.join('_').toLowerCase());
|
|
||||||
|
|
||||||
return window.mR.findModule(
|
|
||||||
(m) =>
|
|
||||||
names.includes(
|
|
||||||
m.default?.prototype?.proxyName ||
|
|
||||||
m[name]?.prototype?.proxyName ||
|
|
||||||
m[baseName]?.prototype?.proxyName
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
window.injectToFunction({index: 0, name: 'createMsgProtobuf', property: 'createMsgProtobuf'}, (func, args) => {
|
if (message.footer) {
|
||||||
|
hydratedTemplate.hydratedFooterText = message.footer;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.caption) {
|
||||||
|
hydratedTemplate.hydratedContentText = message.caption;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.title) {
|
||||||
|
hydratedTemplate.hydratedTitleText = message.title;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (proto.conversation) {
|
||||||
|
hydratedTemplate.hydratedContentText = proto.conversation;
|
||||||
|
delete proto.conversation;
|
||||||
|
} else if (proto.extendedTextMessage?.text) {
|
||||||
|
hydratedTemplate.hydratedContentText = proto.extendedTextMessage.text;
|
||||||
|
delete proto.extendedTextMessage;
|
||||||
|
} else {
|
||||||
|
// Search media part in message
|
||||||
|
let found;
|
||||||
|
const mediaPart = [
|
||||||
|
'documentMessage',
|
||||||
|
'imageMessage',
|
||||||
|
'locationMessage',
|
||||||
|
'videoMessage',
|
||||||
|
];
|
||||||
|
for (const part of mediaPart) {
|
||||||
|
if (part in proto) {
|
||||||
|
found = part;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
return proto;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Media message doesn't allow title
|
||||||
|
hydratedTemplate[found] = proto[found];
|
||||||
|
|
||||||
|
// Copy title to caption if not setted
|
||||||
|
if (
|
||||||
|
hydratedTemplate.hydratedTitleText &&
|
||||||
|
!hydratedTemplate.hydratedContentText
|
||||||
|
) {
|
||||||
|
hydratedTemplate.hydratedContentText =
|
||||||
|
hydratedTemplate.hydratedTitleText;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove title for media messages
|
||||||
|
delete hydratedTemplate.hydratedTitleText;
|
||||||
|
|
||||||
|
if (found === 'locationMessage') {
|
||||||
|
if (
|
||||||
|
!hydratedTemplate.hydratedContentText &&
|
||||||
|
(message[found].name || message[found].address)
|
||||||
|
) {
|
||||||
|
hydratedTemplate.hydratedContentText =
|
||||||
|
message[found].name && message[found].address
|
||||||
|
? `${message[found].name}\n${message[found].address}`
|
||||||
|
: message[found].name || message[found].address || '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure a content text;
|
||||||
|
hydratedTemplate.hydratedContentText =
|
||||||
|
hydratedTemplate.hydratedContentText || ' ';
|
||||||
|
|
||||||
|
delete proto[found];
|
||||||
|
}
|
||||||
|
|
||||||
|
proto.templateMessage = {
|
||||||
|
hydratedTemplate,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return proto;
|
||||||
|
});
|
||||||
|
|
||||||
|
window.injectToFunction({
|
||||||
|
index: 0,
|
||||||
|
name: 'createMsgProtobuf',
|
||||||
|
property: 'createMsgProtobuf'
|
||||||
|
},
|
||||||
|
(func, args) => {
|
||||||
const proto = func(...args);
|
const proto = func(...args);
|
||||||
if (proto.listMessage) {
|
if (proto.templateMessage) {
|
||||||
proto.viewOnceMessage = {
|
proto.viewOnceMessage = {
|
||||||
message: {
|
message: {
|
||||||
listMessage: proto.listMessage
|
templateMessage: proto.templateMessage,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
delete proto.listMessage;
|
delete proto.templateMessage;
|
||||||
}
|
}
|
||||||
if (proto.buttonsMessage) {
|
if (proto.buttonsMessage) {
|
||||||
proto.viewOnceMessage = {
|
proto.viewOnceMessage = {
|
||||||
@@ -120,20 +192,91 @@ exports.ExposeStore = (moduleRaidStr) => {
|
|||||||
};
|
};
|
||||||
delete proto.buttonsMessage;
|
delete proto.buttonsMessage;
|
||||||
}
|
}
|
||||||
|
if (proto.listMessage) {
|
||||||
|
proto.viewOnceMessage = {
|
||||||
|
message: {
|
||||||
|
listMessage: proto.listMessage,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
delete proto.listMessage;
|
||||||
|
}
|
||||||
return proto;
|
return proto;
|
||||||
});
|
});
|
||||||
|
|
||||||
window.injectToFunction({index: 0, name: 'typeAttributeFromProtobuf', property: 'typeAttributeFromProtobuf'}, (func, args) => {
|
window.injectToFunction({
|
||||||
|
index: 0,
|
||||||
|
name: 'typeAttributeFromProtobuf',
|
||||||
|
property: 'typeAttributeFromProtobuf'
|
||||||
|
}, (func, args) => {
|
||||||
const [proto] = args;
|
const [proto] = args;
|
||||||
if (
|
if (proto.templateMessage?.hydratedTemplate) {
|
||||||
proto.buttonsMessage?.headerType === 1 ||
|
const keys = Object.keys(proto.templateMessage?.hydratedTemplate);
|
||||||
proto.buttonsMessage?.headerType === 2
|
const messagePart = [
|
||||||
) {
|
'documentMessage',
|
||||||
|
'imageMessage',
|
||||||
|
'locationMessage',
|
||||||
|
'videoMessage',
|
||||||
|
];
|
||||||
|
if (messagePart.some((part) => keys.includes(part))) {
|
||||||
|
return 'media';
|
||||||
|
}
|
||||||
return 'text';
|
return 'text';
|
||||||
}
|
}
|
||||||
|
return func(...args);
|
||||||
|
});
|
||||||
|
|
||||||
if (proto.listMessage) {
|
window.injectToFunction({
|
||||||
return 'text';
|
index: 0,
|
||||||
|
name: 'typeAttributeFromProtobuf',
|
||||||
|
property: 'typeAttributeFromProtobuf'
|
||||||
|
}, (func, args) => {
|
||||||
|
const [proto] = args;
|
||||||
|
|
||||||
|
if (proto.ephemeralMessage) {
|
||||||
|
const { message: n } = proto.ephemeralMessage;
|
||||||
|
return n ? func(n) : 'text';
|
||||||
|
}
|
||||||
|
if (proto.deviceSentMessage) {
|
||||||
|
const { message: n } = proto.deviceSentMessage;
|
||||||
|
return n ? func(n) : 'text';
|
||||||
|
}
|
||||||
|
if (proto.viewOnceMessage) {
|
||||||
|
const { message: n } = proto.viewOnceMessage;
|
||||||
|
return n ? func(n) : 'text';
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(...args);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.injectToFunction({
|
||||||
|
index: 0,
|
||||||
|
name: 'mediaTypeFromProtobuf',
|
||||||
|
property: 'mediaTypeFromProtobuf'
|
||||||
|
}, (func, args) => {
|
||||||
|
const [proto] = args;
|
||||||
|
if (proto.templateMessage?.hydratedTemplate) {
|
||||||
|
return func(proto.templateMessage.hydratedTemplate);
|
||||||
|
}
|
||||||
|
return func(...args);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.injectToFunction({
|
||||||
|
index: 0,
|
||||||
|
name: 'mediaTypeFromProtobuf',
|
||||||
|
property: 'mediaTypeFromProtobuf'
|
||||||
|
}, (func, args) => {
|
||||||
|
const [proto] = args;
|
||||||
|
if (proto.deviceSentMessage) {
|
||||||
|
const {message: n} = proto.deviceSentMessage;
|
||||||
|
return n ? func(n) : null;
|
||||||
|
}
|
||||||
|
if (proto.ephemeralMessage) {
|
||||||
|
const {message: n} = proto.ephemeralMessage;
|
||||||
|
return n ? func(n) : null;
|
||||||
|
}
|
||||||
|
if (proto.viewOnceMessage) {
|
||||||
|
const {message: n} = proto.viewOnceMessage;
|
||||||
|
return n ? func (n) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return func(...args);
|
return func(...args);
|
||||||
@@ -153,6 +296,75 @@ exports.LoadUtils = () => {
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
window.WWebJS.prepareMessageButtons = (buttonsOptions) => {
|
||||||
|
const returnObject = {};
|
||||||
|
|
||||||
|
(window.Store.ReplyButtonModel) || (window.Store.ReplyButtonModel = window.mR.findModule(m => m.default && m?.default?.prototype?.proxyName === 'replyButton')[0].default);
|
||||||
|
(window.Store.TemplateButtonModel) || (window.Store.TemplateButtonModel = window.mR.findModule(m => m.default && m?.default?.prototype?.proxyName === 'templateButton')[0].default);
|
||||||
|
(window.Store.TemplateButtonCollection) || (window.Store.TemplateButtonCollection = window.mR.findModule('TemplateButtonCollection')[0].TemplateButtonCollection);
|
||||||
|
(window.Store.ButtonCollection) || (window.Store.ButtonCollection = window.mR.findModule('ButtonCollection')[0].ButtonCollection);
|
||||||
|
|
||||||
|
if (!buttonsOptions.buttons) {
|
||||||
|
return returnObject;
|
||||||
|
}
|
||||||
|
if (!Array.isArray(buttonsOptions.buttons)) {
|
||||||
|
throw 'Buttons options is not a array';
|
||||||
|
}
|
||||||
|
|
||||||
|
returnObject.title = buttonsOptions.title;
|
||||||
|
returnObject.footer = buttonsOptions.footer;
|
||||||
|
returnObject.isFromTemplate = !0;
|
||||||
|
returnObject.buttons = new window.Store.TemplateButtonCollection;
|
||||||
|
returnObject.hydratedButtons = buttonsOptions.buttons.map((e, t) => {
|
||||||
|
if ('phoneNumber' in e) {
|
||||||
|
return {
|
||||||
|
index: t, callButton: {
|
||||||
|
displayText: e.buttonText, phoneNumber: e.phoneNumber
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else if ('url' in e) {
|
||||||
|
return {
|
||||||
|
index: t, urlButton: {
|
||||||
|
displayText: e.buttonText, url: e.url
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
index: t, quickReplyButton: {
|
||||||
|
displayText: e.buttonText, id: e.buttonId || `${t}`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
returnObject.buttons.add(returnObject.hydratedButtons.map((e, t) => {
|
||||||
|
var r, n, o, i;
|
||||||
|
const s = `${null != e.index ? e.index : t}`;
|
||||||
|
if (e.urlButton) {
|
||||||
|
return new window.Store.TemplateButtonModel({
|
||||||
|
id: s,
|
||||||
|
displayText: null === (r = e.urlButton) || void 0 === r ? void 0 : r.displayText,
|
||||||
|
url: null === (n = e.urlButton) || void 0 === n ? void 0 : n.url,
|
||||||
|
subtype: 'url'
|
||||||
|
});
|
||||||
|
} else if (e.callButton) {
|
||||||
|
return new window.Store.TemplateButtonModel({
|
||||||
|
id: s, displayText: e.callButton.displayText, phoneNumber: e.callButton.phoneNumber, subtype: 'call'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return new window.Store.TemplateButtonModel({
|
||||||
|
id: s,
|
||||||
|
displayText: null === (o = e.quickReplyButton) || void 0 === o ? void 0 : o.displayText,
|
||||||
|
selectionId: null === (i = e.quickReplyButton) || void 0 === i ? void 0 : i.id,
|
||||||
|
subtype: 'quick_reply'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
return returnObject;
|
||||||
|
};
|
||||||
|
|
||||||
window.WWebJS.sendMessage = async (chat, content, options = {}) => {
|
window.WWebJS.sendMessage = async (chat, content, options = {}) => {
|
||||||
let attOptions = {};
|
let attOptions = {};
|
||||||
if (options.attachment) {
|
if (options.attachment) {
|
||||||
@@ -250,22 +462,10 @@ exports.LoadUtils = () => {
|
|||||||
} else {
|
} else {
|
||||||
caption = options.caption ? options.caption : ' '; //Caption can't be empty
|
caption = options.caption ? options.caption : ' '; //Caption can't be empty
|
||||||
}
|
}
|
||||||
// UI needs to stop glitching
|
|
||||||
const ButtonsCollection = window.mR.findModule('ButtonCollection')[0].ButtonCollection;
|
|
||||||
const ReplyButtonModel = window.findProxyModel('ReplyButtonModel')[0].default;
|
|
||||||
const collection = new ButtonsCollection(ReplyButtonModel);
|
|
||||||
|
|
||||||
const quickButtons = options.buttons.buttons.map(a => {
|
|
||||||
return new ReplyButtonModel({id: a.buttonId, displayText: a.buttonText.displayText});
|
|
||||||
});
|
|
||||||
collection.add(quickButtons);
|
|
||||||
|
|
||||||
|
buttonOptions = window.WWebJS.prepareMessageButtons(options.buttons);
|
||||||
buttonOptions = {
|
buttonOptions = {
|
||||||
isDynamicReplyButtonsMsg: true,
|
...buttonOptions,
|
||||||
title: options.buttons.title ? options.buttons.title : undefined,
|
|
||||||
footer: options.buttons.footer ? options.buttons.footer : undefined,
|
|
||||||
dynamicReplyButtons: options.buttons.buttons,
|
|
||||||
replyButtons: collection,
|
|
||||||
caption: caption
|
caption: caption
|
||||||
};
|
};
|
||||||
delete options.buttons;
|
delete options.buttons;
|
||||||
|
|||||||
Reference in New Issue
Block a user