From 0df9db09aabbc99f96b914d5df201a79303dc3df Mon Sep 17 00:00:00 2001 From: Akram Lazkanee Date: Mon, 30 Dec 2019 20:10:34 +0200 Subject: [PATCH 1/4] Multi-Pages support added --- lib/BootBot.js | 249 +++++++++++++++++++++++++------------------ lib/Chat.js | 118 ++++++++++---------- lib/Conversation.js | 31 +++--- package-lock.json | 140 ++++++++++++------------ test/BootBot.spec.js | 15 +-- 5 files changed, 297 insertions(+), 256 deletions(-) diff --git a/lib/BootBot.js b/lib/BootBot.js index 5df40f8..acf1c86 100644 --- a/lib/BootBot.js +++ b/lib/BootBot.js @@ -18,7 +18,6 @@ class BootBot extends EventEmitter { * @param {String} options.appSecret * @param {String} [options.webhook=/webhook] * @param {Boolean} [options.broadcastEchoes=false] - * @param {String} [options.graphApiVersion=v2.12] */ constructor(options) { super(); @@ -29,11 +28,10 @@ class BootBot extends EventEmitter { this.verifyToken = options.verifyToken; this.appSecret = options.appSecret; this.broadcastEchoes = options.broadcastEchoes || false; - this.graphApiVersion = options.graphApiVersion || 'v2.12'; this.app = express(); this.webhook = options.webhook || '/webhook'; this.webhook = this.webhook.charAt(0) !== '/' ? `/${this.webhook}` : this.webhook; - this.app.use(this.webhook, bodyParser.json({ verify: this._verifyRequestSignature.bind(this) })); + this.app.use(bodyParser.json({verify: this._verifyRequestSignature.bind(this)})); this._hearMap = []; this._conversations = []; } @@ -84,14 +82,15 @@ class BootBot extends EventEmitter { * @param {String} text * @param {Array.} quickReplies Array of strings or quick_reply objects * @param {SendMessageOptions} [options] + * @param {Number|String} pageId Facebook page ID for multi page bots. */ - sendTextMessage(recipientId, text, quickReplies, options) { - const message = { text }; + sendTextMessage(recipientId, text, quickReplies, options, pageId) { + const message = {text}; const formattedQuickReplies = this._formatQuickReplies(quickReplies); if (formattedQuickReplies && formattedQuickReplies.length > 0) { message.quick_replies = formattedQuickReplies; } - return this.sendMessage(recipientId, message, options); + return this.sendMessage(recipientId, message, options, pageId); } /** @@ -105,15 +104,16 @@ class BootBot extends EventEmitter { * @param {String} text Message to be sent. * @param {Array.} buttons * @param {SendMessageOptions} [options] + * @param {Number|String} pageId Facebook page ID for multi page bots. */ - sendButtonTemplate(recipientId, text, buttons, options) { + sendButtonTemplate(recipientId, text, buttons, options, pageId) { const payload = { template_type: 'button', text }; const formattedButtons = this._formatButtons(buttons); payload.buttons = formattedButtons; - return this.sendTemplate(recipientId, payload, options); + return this.sendTemplate(recipientId, payload, options, pageId); } /** @@ -129,14 +129,15 @@ class BootBot extends EventEmitter { * @param {Recipient|String} recipientId * @param {Array.} elements * @param {SendMessageOptions} [options] + * @param {Number|String} pageId Facebook page ID for multi page bots. */ - sendGenericTemplate(recipientId, elements, options) { + sendGenericTemplate(recipientId, elements, options, pageId) { const payload = { template_type: 'generic', elements }; options && options.imageAspectRatio && (payload.image_aspect_ratio = options.imageAspectRatio) && (delete options.imageAspectRatio); - return this.sendTemplate(recipientId, payload, options); + return this.sendTemplate(recipientId, payload, options, pageId); } /** @@ -145,15 +146,16 @@ class BootBot extends EventEmitter { * @param {Array.} buttons An array with one element: string or button object. * @param {SendMessageOptions} [options] * @param {String} [options.topElementStyle] + * @param {Number|String} pageId Facebook page ID for multi page bots. */ - sendListTemplate(recipientId, elements, buttons, options) { + sendListTemplate(recipientId, elements, buttons, options, pageId) { const payload = { template_type: 'list', elements }; options && options.topElementStyle && (payload.top_element_style = options.topElementStyle) && (delete options.topElementStyle); buttons && buttons.length && (payload.buttons = this._formatButtons([buttons[0]])); - return this.sendTemplate(recipientId, payload, options); + return this.sendTemplate(recipientId, payload, options, pageId); } /** @@ -161,15 +163,16 @@ class BootBot extends EventEmitter { * @param {Recipient|String} recipientId * @param {Object} payload The template payload. * @param {SendMessageOptions} [options] + * @param {Number|String} pageId Facebook page ID for multi page bots. */ - sendTemplate(recipientId, payload, options) { + sendTemplate(recipientId, payload, options, pageId) { const message = { attachment: { type: 'template', payload } }; - return this.sendMessage(recipientId, message, options); + return this.sendMessage(recipientId, message, options, pageId); } /** @@ -178,19 +181,20 @@ class BootBot extends EventEmitter { * @param {String} url URL of the attachment. * @param {Array.} quickReplies * @param {SendMessageOptions} options + * @param {Number|String} pageId Facebook page ID for multi page bots. */ - sendAttachment(recipientId, type, url, quickReplies, options) { + sendAttachment(recipientId, type, url, quickReplies, options, pageId) { const message = { attachment: { type, - payload: { url } + payload: {url} } }; const formattedQuickReplies = this._formatQuickReplies(quickReplies); if (formattedQuickReplies && formattedQuickReplies.length > 0) { message.quick_replies = formattedQuickReplies; } - return this.sendMessage(recipientId, message, options); + return this.sendMessage(recipientId, message, options, pageId); } /** @@ -199,13 +203,14 @@ class BootBot extends EventEmitter { * @param {Recipient|String} recipientId Recipient object or ID. * @param {String} action One of 'mark_seen', 'typing_on' or 'typing_off' * @param {SendMessageOptions} [options] NOT USED. + * @param {Number|String} pageId Facebook page ID for multi page bots. */ - sendAction(recipientId, action, options) { + sendAction(recipientId, action, options, pageId) { const recipient = this._createRecipient(recipientId); return this.sendRequest({ recipient, sender_action: action - }); + }, null, null, pageId); } /** @@ -222,8 +227,9 @@ class BootBot extends EventEmitter { * @param {Recipient|String} recipientId Recipient object or ID. * @param {Message} message The message to send. * @param {SendMessageOptions} [options] + * @param {Number|String} pageId Facebook page ID for multi page bots. */ - sendMessage(recipientId, message, options) { + sendMessage(recipientId, message, options, pageId) { const recipient = this._createRecipient(recipientId); const messagingType = options && options.messagingType; const notificationType = options && options.notificationType; @@ -245,20 +251,20 @@ class BootBot extends EventEmitter { reqBody.tag = tag } const req = () => ( - this.sendRequest(reqBody).then((json) => { - if (typeof onDelivery === 'function') { - this.once('delivery', onDelivery); - } - if (typeof onRead === 'function') { - this.once('read', onRead); - } - return json; - }) + this.sendRequest(reqBody, null, null, pageId).then((json) => { + if (typeof onDelivery === 'function') { + this.once('delivery', onDelivery); + } + if (typeof onRead === 'function') { + this.once('read', onRead); + } + return json; + }) ); if (options && options.typing) { const autoTimeout = (message && message.text) ? message.text.length * 10 : 1000; const timeout = (typeof options.typing === 'number') ? options.typing : autoTimeout; - return this.sendTypingIndicator(recipientId, timeout).then(req); + return this.sendTypingIndicator(recipientId, timeout, pageId).then(req); } return req(); } @@ -267,27 +273,28 @@ class BootBot extends EventEmitter { * @param {Object} body The request body object. * @param {String} endpoint Messenger API endpoint * @param {String} method HTTP method. + * @param {Number|String} pageId Facebook page ID for multi page bots. * @returns {Promise} */ - sendRequest(body, endpoint, method) { + sendRequest(body, endpoint, method, pageId) { endpoint = endpoint || 'messages'; method = method || 'POST'; - return fetch(`https://graph.facebook.com/${this.graphApiVersion}/me/${endpoint}?access_token=${this.accessToken}`, { + return fetch(`https://graph.facebook.com/v2.12/me/${endpoint}?access_token=${this._getAccessToken(pageId)}`, { method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }) - .then(res => res.json()) - .then(res => { - if (res.error) { - console.log('Messenger Error received. For more information about error codes, see: https://goo.gl/d76uvB'); - console.log(res.error); - } - return res; - }) - .catch(err => console.log(`Error sending message: ${err}`)); + .then(res => res.json()) + .then(res => { + if (res.error) { + console.log('Messenger Error received. For more information about error codes, see: https://goo.gl/d76uvB'); + console.log(res.error); + } + return res; + }) + .catch(err => console.log(`Error sending message: ${err}`)); } /** @@ -295,37 +302,40 @@ class BootBot extends EventEmitter { * Please update your code to use the sendProfileRequest() method instead. * @param {Object} body * @param {String} method + * @param {Number|String} pageId Facebook page ID for multi page bots. */ - sendThreadRequest(body, method) { + sendThreadRequest(body, method, pageId) { console.warning(` sendThreadRequest: Dreprecation warning. Thread API has been replaced by the Messenger Profile API. Please update your code to use the sendProfileRequest() method instead.` ); - return this.sendRequest(body, 'thread_settings', method); + return this.sendRequest(body, 'thread_settings', method, pageId); } /** * @param {Object} body The request body. * @param {String} method HTTP method. + * @param {Number|String} pageId Facebook page ID for multi page bots. */ - sendProfileRequest(body, method) { - return this.sendRequest(body, 'messenger_profile', method); + sendProfileRequest(body, method, pageId) { + return this.sendRequest(body, 'messenger_profile', method, pageId); } /** * Convinient method to send a typing_on action and then a typing_off action after milliseconds to simulate the bot is actually typing. Max value is 20000 (20 seconds). * @param {Recipient|String} recipientId * @param {Number} milliseconds + * @param {Number|String} pageId Facebook page ID for multi page bots. */ - sendTypingIndicator(recipientId, milliseconds) { + sendTypingIndicator(recipientId, milliseconds, pageId) { const timeout = isNaN(milliseconds) ? 0 : milliseconds; if (milliseconds > 20000) { milliseconds = 20000; console.error('sendTypingIndicator: max milliseconds value is 20000 (20 seconds)'); } return new Promise((resolve, reject) => { - return this.sendAction(recipientId, 'typing_on').then(() => { - setTimeout(() => this.sendAction(recipientId, 'typing_off').then((json) => resolve(json)), timeout); + return this.sendAction(recipientId, 'typing_on', null, pageId).then(() => { + setTimeout(() => this.sendAction(recipientId, 'typing_off', null, pageId).then((json) => resolve(json)), timeout); }); }); } @@ -333,34 +343,37 @@ class BootBot extends EventEmitter { /** * Returns a Promise that contains the user's profile information. * @param {String} userId + * @param {Number|String} pageId Facebook page ID for multi page bots. * @returns {Promise} */ - getUserProfile(userId) { - const url = `https://graph.facebook.com/${this.graphApiVersion}/${userId}?fields=first_name,last_name,profile_pic,locale,timezone,gender&access_token=${this.accessToken}`; + getUserProfile(userId, pageId) { + const url = `https://graph.facebook.com/v2.12/${userId}?fields=first_name,last_name,profile_pic,locale,timezone,gender&access_token=${this._getAccessToken(pageId)}`; return fetch(url) - .then(res => res.json()) - .catch(err => console.log(`Error getting user profile: ${err}`)); + .then(res => res.json()) + .catch(err => console.log(`Error getting user profile: ${err}`)); } /** * Set a greeting text for new conversations. The Greeting Text is only rendered the first time the user interacts with a the Page on Messenger. * Facebook docs: https://developers.facebook.com/docs/messenger-platform/reference/messenger-profile-api/greeting * @param {String|Array.} text Greeting text, or an array of objects to support multiple locales. For more info on the format of these objects, see: https://developers.facebook.com/docs/messenger-platform/reference/messenger-profile-api/greeting + * @param {Number|String} pageId Facebook page ID for multi page bots. */ - setGreetingText(text) { + setGreetingText(text, pageId) { const greeting = (typeof text !== 'string') ? text : [{ locale: 'default', text }]; - return this.sendProfileRequest({ greeting }); + return this.sendProfileRequest({greeting}, null, pageId); } /** * React to a user starting a conversation with the bot by clicking the Get Started button. * Facebook docs: https://developers.facebook.com/docs/messenger-platform/reference/messenger-profile-api/get-started-button * @param {String|Function} action If action is a string, the Get Started button postback will be set to that string. If it's a function, that callback will be executed when a user clicks the Get Started button. + * @param {Number|String} pageId Facebook page ID for multi page bots. */ - setGetStartedButton(action) { + setGetStartedButton(action, pageId) { const payload = (typeof action === 'string') ? action : 'BOOTBOT_GET_STARTED'; if (typeof action === 'function') { this.on(`postback:${payload}`, action); @@ -369,18 +382,19 @@ class BootBot extends EventEmitter { get_started: { payload } - }); + }, null, pageId); } /** * Removes the Get Started button call to action. + * @param {Number|String} pageId Facebook page ID for multi page bots. */ - deleteGetStartedButton() { + deleteGetStartedButton(pageId) { return this.sendProfileRequest({ fields: [ 'get_started' ] - }, 'DELETE'); + }, 'DELETE', pageId); } /** @@ -388,11 +402,12 @@ class BootBot extends EventEmitter { * Facebook docs: https://developers.facebook.com/docs/messenger-platform/reference/messenger-profile-api/persistent-menu * @param {Array.} buttons If buttons is an array of objects containing a locale attribute, it will be used as-is, expecting it to be an array of localized menues. For more info on the format of these objects, see the documentation. * @param {boolean} [disableInput=false] If disableInput is set to true, it will disable user input in the menu. The user will only be able to interact with the bot via the menu, postbacks, buttons and webviews. + * @param {Number|String} pageId Facebook page ID for multi page bots. */ - setPersistentMenu(buttons, disableInput) { + setPersistentMenu(buttons, disableInput, pageId) { if (buttons && buttons[0] && buttons[0].locale !== undefined) { // Received an array of locales, send it as-is. - return this.sendProfileRequest({ persistent_menu: buttons }); + return this.sendProfileRequest({persistent_menu: buttons}, null, pageId); } // If it's not an array of locales, we'll assume is an array of buttons. const formattedButtons = this._formatButtons(buttons); @@ -402,18 +417,19 @@ class BootBot extends EventEmitter { composer_input_disabled: disableInput || false, call_to_actions: formattedButtons }] - }); + }, null, pageId); } /** * Removes the Persistent Menu. + * @param {Number|String} pageId Facebook page ID for multi page bots. */ - deletePersistentMenu() { + deletePersistentMenu(pageId) { return this.sendProfileRequest({ fields: [ 'persistent_menu' ] - }, 'DELETE'); + }, 'DELETE', pageId); } /** @@ -423,24 +439,24 @@ class BootBot extends EventEmitter { * @param {SendMessageOptions} options * @returns {Promise} */ - say(recipientId, message, options) { + say(recipientId, message, options, pageId) { if (typeof message === 'string') { - return this.sendTextMessage(recipientId, message, [], options); + return this.sendTextMessage(recipientId, message, [], options, pageId); } else if (message && message.text) { if (message.quickReplies && message.quickReplies.length > 0) { - return this.sendTextMessage(recipientId, message.text, message.quickReplies, options); + return this.sendTextMessage(recipientId, message.text, message.quickReplies, options, pageId); } else if (message.buttons && message.buttons.length > 0) { - return this.sendButtonTemplate(recipientId, message.text, message.buttons, options); + return this.sendButtonTemplate(recipientId, message.text, message.buttons, options, pageId); } } else if (message && message.attachment) { - return this.sendAttachment(recipientId, message.attachment, message.url, message.quickReplies, options); + return this.sendAttachment(recipientId, message.attachment, message.url, message.quickReplies, options, pageId); } else if (message && message.elements && message.buttons) { - return this.sendListTemplate(recipientId, message.elements, message.buttons, options); + return this.sendListTemplate(recipientId, message.elements, message.buttons, options, pageId); } else if (message && message.cards) { - return this.sendGenericTemplate(recipientId, message.cards, options); + return this.sendGenericTemplate(recipientId, message.cards, options, pageId); } else if (Array.isArray(message)) { return message.reduce((promise, msg) => { - return promise.then(() => this.say(recipientId, msg, options)); + return promise.then(() => this.say(recipientId, msg, options, pageId)); }, Promise.resolve()); } console.error('Invalid format for .say() message.'); @@ -469,12 +485,13 @@ class BootBot extends EventEmitter { * Starts a new conversation with the user. * @param {Recipient|String} recipientId * @param {Function} factory Executed immediately receiving the convo instance as it's only param. + * @param {Number|String} pageId Facebook page ID for multi page bots. */ - conversation(recipientId, factory) { + conversation(recipientId, factory, pageId) { if (!recipientId || !factory || typeof factory !== 'function') { return console.error(`You need to specify a recipient and a callback to start a conversation`); } - const convo = new Conversation(this, recipientId); + const convo = new Conversation(this, recipientId, pageId); this._conversations.push(convo); convo.on('end', (endedConvo) => { const removeIndex = this._conversations.indexOf(endedConvo); @@ -529,30 +546,35 @@ class BootBot extends EventEmitter { * @param {String} type * @param {Object} event * @param {Object} data + * @param {Number|String} pageId Facebook page ID for multi page bots. */ - _handleEvent(type, event, data) { - const recipient = (type === 'authentication' && !event.sender) ? { user_ref: event.optin.user_ref } : event.sender.id; - const chat = new Chat(this, recipient); + _handleEvent(type, event, data, pageId) { + const recipient = (type === 'authentication' && !event.sender) ? {user_ref: event.optin.user_ref} : event.sender.id; + const chat = new Chat(this, recipient, pageId); this.emit(type, event, chat, data); } - _handleMessageEvent(event) { - if (this._handleConversationResponse('message', event)) { return; } + _handleMessageEvent(event, pageId) { + if (this._handleConversationResponse('message', event)) { + return; + } const text = event.message.text; const senderId = event.sender.id; let captured = false; - if (!text) { return; } + if (!text) { + return; + } this._hearMap.forEach(hear => { if (typeof hear.keyword === 'string' && hear.keyword.toLowerCase() === text.toLowerCase()) { - const res = hear.callback.apply(this, [event, new Chat(this, senderId), { + const res = hear.callback.apply(this, [event, new Chat(this, senderId, pageId), { keyword: hear.keyword, captured }]); captured = true; return res; } else if (hear.keyword instanceof RegExp && hear.keyword.test(text)) { - const res = hear.callback.apply(this, [event, new Chat(this, senderId), { + const res = hear.callback.apply(this, [event, new Chat(this, senderId, pageId), { keyword: hear.keyword, match: text.match(hear.keyword), captured @@ -562,30 +584,36 @@ class BootBot extends EventEmitter { } }); - this._handleEvent('message', event, { captured }); + this._handleEvent('message', event, {captured}, pageId); } - _handleAttachmentEvent(event) { - if (this._handleConversationResponse('attachment', event)) { return; } - this._handleEvent('attachment', event); + _handleAttachmentEvent(event, pageId) { + if (this._handleConversationResponse('attachment', event)) { + return; + } + this._handleEvent('attachment', event, null, pageId); } - _handlePostbackEvent(event) { - if (this._handleConversationResponse('postback', event)) { return; } + _handlePostbackEvent(event, pageId) { + if (this._handleConversationResponse('postback', event)) { + return; + } const payload = event.postback.payload; if (payload) { - this._handleEvent(`postback:${payload}`, event); + this._handleEvent(`postback:${payload}`, event, null, pageId); } - this._handleEvent('postback', event); + this._handleEvent('postback', event, null, pageId); } - _handleQuickReplyEvent(event) { - if (this._handleConversationResponse('quick_reply', event)) { return; } + _handleQuickReplyEvent(event, pageId) { + if (this._handleConversationResponse('quick_reply', event)) { + return; + } const payload = event.message.quick_reply && event.message.quick_reply.payload; if (payload) { - this._handleEvent(`quick_reply:${payload}`, event); + this._handleEvent(`quick_reply:${payload}`, event, null, pageId); } - this._handleEvent('quick_reply', event); + this._handleEvent('quick_reply', event, null, pageId); } _handleConversationResponse(type, event) { @@ -612,9 +640,17 @@ class BootBot extends EventEmitter { * @returns {Recipient} */ _createRecipient(recipient) { - return (typeof recipient === 'object') ? recipient : { id: recipient }; + return (typeof recipient === 'object') ? recipient : {id: recipient}; } + /** + * Returns accessToken if it's a string or uses pageId to get accessToken for multi page bots + * @param {Number|String} pageId Facebook page ID for multi page bots. + * @returns {String} + */ + _getAccessToken(pageId) { + return typeof this.accessToken === 'string' ? this.accessToken : this.accessToken[pageId]; + } _initWebhook() { this.app.get(this.webhook, (req, res) => { @@ -647,30 +683,31 @@ class BootBot extends EventEmitter { handleFacebookData(data) { // Iterate over each entry. There may be multiple if batched. data.entry.forEach((entry) => { + const pageId = entry.id; // Iterate over each messaging event entry.messaging.forEach((event) => { if (event.message && event.message.is_echo && !this.broadcastEchoes) { return; } if (event.optin) { - this._handleEvent('authentication', event); + this._handleEvent('authentication', event, null, pageId); } else if (event.message && event.message.text) { - this._handleMessageEvent(event); + this._handleMessageEvent(event, pageId); if (event.message.quick_reply) { - this._handleQuickReplyEvent(event); + this._handleQuickReplyEvent(event, pageId); } } else if (event.message && event.message.attachments) { - this._handleAttachmentEvent(event); + this._handleAttachmentEvent(event, pageId); } else if (event.postback) { - this._handlePostbackEvent(event); + this._handlePostbackEvent(event, pageId); } else if (event.delivery) { - this._handleEvent('delivery', event); + this._handleEvent('delivery', event, null, pageId); } else if (event.read) { - this._handleEvent('read', event); + this._handleEvent('read', event, null, pageId); } else if (event.account_linking) { - this._handleEvent('account_linking', event); + this._handleEvent('account_linking', event, null, pageId); } else if (event.referral) { - this._handleEvent('referral', event); + this._handleEvent('referral', event, null, pageId); } else { console.log('Webhook received unknown event: ', event); } @@ -687,8 +724,8 @@ class BootBot extends EventEmitter { var method = elements[0]; var signatureHash = elements[1]; var expectedHash = crypto.createHmac('sha1', this.appSecret) - .update(buf) - .digest('hex'); + .update(buf) + .digest('hex'); if (signatureHash != expectedHash) { throw new Error('Couldn\'t validate the request signature.'); @@ -697,4 +734,4 @@ class BootBot extends EventEmitter { } } -module.exports = BootBot; +module.exports = BootBot; \ No newline at end of file diff --git a/lib/Chat.js b/lib/Chat.js index 1f6b1aa..dc79cd6 100644 --- a/lib/Chat.js +++ b/lib/Chat.js @@ -5,15 +5,17 @@ const BootBot = require('./BootBot'); class Chat extends EventEmitter { /** * @param {BootBot} bot Bot instance. - * @param {String} userId - */ - constructor(bot, userId) { - super(); - if (!bot || !userId) { - throw new Error('You need to specify a BootBot instance and a userId'); - } - this.bot = bot; - this.userId = userId; + * @param {String} userId + * @param {Number|String} pageId Facebook page ID for multi page bots. + */ + constructor(bot, userId, pageId) { + super(); + if (!bot || !userId) { + throw new Error('You need to specify a BootBot instance and a userId'); + } + this.bot = bot; + this.userId = userId; + this.pageId = pageId; } /** @@ -37,7 +39,7 @@ class Chat extends EventEmitter { * @param {SendMessageOptions} options */ say(message, options) { - return this.bot.say(this.userId, message, options); + return this.bot.say(this.userId, message, options, this.pageId); } /** @@ -49,13 +51,13 @@ class Chat extends EventEmitter { * @property {String} [image_url] */ - /** - * @param {String} text - * @param {Array.} quickReplies Array of strings or quick_reply objects - * @param {SendMessageOptions} [options] - */ + /** + * @param {String} text + * @param {Array.} quickReplies Array of strings or quick_reply objects + * @param {SendMessageOptions} [options] + */ sendTextMessage(text, quickReplies, options) { - return this.bot.sendTextMessage(this.userId, text, quickReplies, options); + return this.bot.sendTextMessage(this.userId, text, quickReplies, options, this.pageId); } /** @@ -64,13 +66,13 @@ class Chat extends EventEmitter { * @typedef {Object} Button */ - /** - * @param {String} text Message to be sent. - * @param {Array.} buttons - * @param {SendMessageOptions} [options] - */ + /** + * @param {String} text Message to be sent. + * @param {Array.} buttons + * @param {SendMessageOptions} [options] + */ sendButtonTemplate(text, buttons, options) { - return this.bot.sendButtonTemplate(this.userId, text, buttons, options); + return this.bot.sendButtonTemplate(this.userId, text, buttons, options, this.pageId); } /** @@ -82,22 +84,22 @@ class Chat extends EventEmitter { * @property {Array.