From 3e52165714dc472aae10bde3dadc957dbc677af1 Mon Sep 17 00:00:00 2001 From: Srdjan Prpa Date: Mon, 23 Jul 2018 16:38:43 +0200 Subject: [PATCH 1/7] Add slack dialog --- lib/bot-builder.js | 2 + lib/slack/format-dialog.js | 212 +++++++++++++++++++++++++++++++++++++ lib/slack/reply.js | 17 ++- lib/slack/setup.js | 2 +- 4 files changed, 231 insertions(+), 2 deletions(-) create mode 100644 lib/slack/format-dialog.js diff --git a/lib/bot-builder.js b/lib/bot-builder.js index 97a44c4..089e8dd 100644 --- a/lib/bot-builder.js +++ b/lib/bot-builder.js @@ -13,6 +13,7 @@ const viberSetup = require('./viber/setup'); const alexaSetup = require('./alexa/setup'); const fbTemplate = require('./facebook/format-message'); const slackTemplate = require('./slack/format-message'); +const slackDialog = require('./slack/format-dialog'); const telegramTemplate = require('./telegram/format-message'); const viberTemplate = require('./viber/format-message'); const skypeTemplate = require('./skype/format-message'); @@ -73,6 +74,7 @@ module.exports = function botBuilder(messageHandler, options, optionalLogError) module.exports.fbTemplate = fbTemplate; module.exports.slackTemplate = slackTemplate; +module.exports.slackDialog = slackDialog; module.exports.telegramTemplate = telegramTemplate; module.exports.viberTemplate = viberTemplate; module.exports.skypeTemplate = skypeTemplate; diff --git a/lib/slack/format-dialog.js b/lib/slack/format-dialog.js new file mode 100644 index 0000000..2e4daca --- /dev/null +++ b/lib/slack/format-dialog.js @@ -0,0 +1,212 @@ +'use strict'; + +class SlackDialog { + constructor(triggerId, title, submitLabel, callbackId, notifyOnCancel = false) { + this.template = { + trigger_id: triggerId, + dialog: { + title: title, + submit_label: submitLabel, + callback_id: callbackId, + notify_on_cancel: notifyOnCancel, + elements: [] + } + }; + } + + addInput(text, name, maxLength = 150, minLength = 0, optional = false, hint, subtype, value, placeholder) { + if (this.template.dialog.elements.length === 5) + throw new Error('You can not add more than 5 elements'); + + if (!text || !name) + throw new Error('Text and name are requeired for addInput method'); + + if (text > 24) + throw new Error('Text needs to be less or equal to 24 characters'); + + if (name > 300) + throw new Error('Name needs to be less or equal to 300 characters'); + + if (maxLength && (Number.isInteger(maxLength) || maxLength > 150)) + throw new Error('Max length needs to be a valid number and less or equal to 150'); + + if (minLength && (Number.isInteger(minLength) || minLength < 150)) + throw new Error('Min length needs to be a valid number and less or equal to 150'); + + if (optional && typeof optional !== 'boolean') + throw new Error('Optional needs to be a boolean'); + + if (hint && (typeof hint !== 'string' || hint.length > 150)) + throw new Error('Hint needs to be a valid string and less or equal to 150 characters'); + + if (subtype && ['text', 'email', 'number', 'tel', 'url'].indexOf(subtype) > -1) + throw new Error('The value of the subtype can be email, number, tel, url or text'); + + if (value && (typeof value !== 'string' || value.length > 150)) + throw new Error('Value needs to be a valid string and less or equal to 150 characters'); + + if (placeholder && (typeof placeholder !== 'string' || placeholder.length > 150)) + throw new Error('Placeholder needs to be a valid string and less or equal to 150 characters'); + + const inputElement = { + label: text, + name: name, + type: 'text', + max_length: maxLength, + min_length: minLength, + optional: optional + }; + + if (hint) + inputElement.hint = hint; + + if (subtype) + inputElement.subtype = subtype; + + if (value) + inputElement.value = value; + + if (placeholder) + inputElement.placeholder = placeholder; + + + this.this.template.dialog.elements.push(inputElement); + + return this; + } + + // addTextarea() { } + addTextarea(text, name, placeholder, maxLength = 3000, minLength = 3000, optional = false, hint, subtype, value) { + if (this.template.dialog.elements.length === 5) + throw new Error('You can not add more than 5 elements'); + + if (!text || !name) + throw new Error('Text and name are requeired for addInput method'); + + if (text > 300) + throw new Error('Text needs to be less or equal to 24 characters'); + + if (name > 24) + throw new Error('Name needs to be less or equal to 300 characters'); + + if (placeholder && (typeof placeholder !== 'string' || placeholder.length > 150)) + throw new Error('Placeholder needs to be a valid string and less or equal to 150 characters'); + + if (maxLength && (Number.isInteger(maxLength) || maxLength > 3000)) + throw new Error('Max length needs to be a valid number and less or equal to 3000'); + + if (minLength && (Number.isInteger(minLength) || minLength < 3000)) + throw new Error('Min length needs to be a valid number and less or equal to 3000'); + + if (optional && typeof optional !== 'boolean') + throw new Error('Optional needs to be a boolean'); + + if (hint && (typeof hint !== 'string' || hint.length > 150)) + throw new Error('Hint needs to be a valid string and less or equal to 150 characters'); + + if (subtype && ['text', 'email', 'number', 'tel', 'url'].indexOf(subtype) > -1) + throw new Error('The value of the subtype can be email, number, tel, url or text'); + + if (value && (typeof value !== 'string' || value.length > 3000)) + throw new Error('Value needs to be a valid string and less or equal to 3000 characters'); + + let textareaElement = { + label: text, + name: name, + type: 'textarea', + max_length: maxLength, + min_length: minLength, + optional: optional + }; + + if (hint) + textareaElement.hint = hint; + + if (subtype) + textareaElement.subtype = subtype; + + if (value) + textareaElement.value = value; + + if (placeholder) + textareaElement.placeholder = placeholder; + + + this.this.template.dialog.elements.push(textareaElement); + + return this; + } + + + addSelect(text, name, dataSource = 'static', minQueryLength, placeholder, optional, value, selectedOptions, options) { + if (this.template.dialog.elements.length === 5) + throw new Error('You can not add more than 5 elements'); + + if (!text || !name) + throw new Error('Text and name are requeired for addInput method'); + + if (text > 300) + throw new Error('Text needs to be less or equal to 24 characters'); + + if (name > 24) + throw new Error('Name needs to be less or equal to 300 characters'); + + if (dataSource && ['static', 'users', 'channels', 'conversations', 'external'].indexOf(dataSource) > -1) + throw new Error('The value of the dataSource can be users, channels, conversations, external or static'); + + if (minQueryLength && !Number.isInteger(minQueryLength)) + throw new Error('minQueryLength needs to be a valid number'); + + if (placeholder && (typeof placeholder !== 'string' || placeholder.length > 150)) + throw new Error('Placeholder needs to be a valid string and less or equal to 150 characters'); + + if (optional && typeof optional !== 'boolean') + throw new Error('Optional needs to be a boolean'); + + if (value && typeof value !== 'string') + throw new Error('Value needs to be a valid string'); + + if (selectedOptions && !Array.isArray(selectedOptions)) + throw new Error('selectedOptions needs to be a valid array'); + + if (options && Array.isArray(options)) + throw new Error('options needs to be a valid array'); + + let selectElement = { + label: text, + name: name, + type: 'textarea', + data_source: dataSource + }; + + if (minQueryLength) + inputElement.minQueryLength = minQueryLength; + + if (placeholder) + inputElement.placeholder = placeholder; + + if (optional) + inputElement.optional = optional; + + if (value) + inputElement.value = value; + + if (selectedOptions) + inputElement.selectedOptions = selectedOptions; + + if (options) + inputElement.options = options; + + + this.this.template.dialog.elements.push(selectElement); + + return this; + } + + get() { + this.template.dialog = JSON.stringify(this.template.dialog); + return this.template + } +} + +module.exports = SlackDialog; diff --git a/lib/slack/reply.js b/lib/slack/reply.js index 69793a5..5110bce 100644 --- a/lib/slack/reply.js +++ b/lib/slack/reply.js @@ -1,10 +1,25 @@ 'use strict'; -module.exports = function slackReply(botResponse) { +const rp = require('minimal-request-promise'); +const qs = require('querystring'); + +module.exports = function slackReply(botResponse, token) { if (typeof botResponse === 'string') return { text: botResponse }; + if (botResponse.dialog && botResponse.trigger_id) { + botResponse.token = token; + const options = { + headers: { + 'Content-type': 'application/json' + }, + body: qs.stringify(botResponse) + }; + + return rp.post('https://slack.com/api/dialog.open', options); + } + return botResponse; }; diff --git a/lib/slack/setup.js b/lib/slack/setup.js index 82b05a5..53e52e0 100644 --- a/lib/slack/setup.js +++ b/lib/slack/setup.js @@ -20,7 +20,7 @@ module.exports = function slackSetup(api, bot, logError, optionalParser, optiona || (request.post.trigger_word && request.post.token === request.env.slackWebhookToken)) return bot(parser(request.post), request) - .then(responder) + .then((botResponse) => responder(botResponse, request.env.slackToken)) .catch(logError); else return responder('unmatched token' + ' ' + request.post.token); From aef5b84913265ca7cc8ddb636312de45d058a613 Mon Sep 17 00:00:00 2001 From: Srdjan Prpa Date: Wed, 24 Oct 2018 13:41:47 +0200 Subject: [PATCH 2/7] Add selected options --- lib/slack/format-message.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/slack/format-message.js b/lib/slack/format-message.js index 4ae5169..e9dbd69 100644 --- a/lib/slack/format-message.js +++ b/lib/slack/format-message.js @@ -198,7 +198,7 @@ class SlackTemplate { return this; } - addSelect(text, name, options, dataSource = 'static', minQueryLength) { + addSelect(text, name, options, dataSource = 'static', selectedOptions, minQueryLength) { if (this.getLatestAttachment().actions.length === 5) throw new Error('You can not add more than 5 actions'); @@ -227,6 +227,12 @@ class SlackTemplate { action.options = options; } + if (!Array.isArray(selectedOptions)) + throw new Error('selectedOptions needs to be a valid array'); + + if (selectedOptions) + action.selected_options = selectedOptions; + if (dataSource) action.data_source = dataSource; From e3dc3d41b3dafacd9e5580514207bcce5e2f37fe Mon Sep 17 00:00:00 2001 From: Srdjan Prpa Date: Wed, 24 Oct 2018 13:49:12 +0200 Subject: [PATCH 3/7] Minor fix --- lib/slack/format-message.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/slack/format-message.js b/lib/slack/format-message.js index e9dbd69..26ac925 100644 --- a/lib/slack/format-message.js +++ b/lib/slack/format-message.js @@ -227,7 +227,7 @@ class SlackTemplate { action.options = options; } - if (!Array.isArray(selectedOptions)) + if (selectedOptions && !Array.isArray(selectedOptions)) throw new Error('selectedOptions needs to be a valid array'); if (selectedOptions) From 38d1e6dd10d9e784a7b59ad7d126b833cf37c85e Mon Sep 17 00:00:00 2001 From: Srdjan Prpa Date: Mon, 22 Jul 2019 10:41:05 +0200 Subject: [PATCH 4/7] Add state attributes for slack dialog --- lib/slack/format-dialog.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/slack/format-dialog.js b/lib/slack/format-dialog.js index e2fb9a5..f1a6e30 100644 --- a/lib/slack/format-dialog.js +++ b/lib/slack/format-dialog.js @@ -1,7 +1,7 @@ 'use strict'; class SlackDialog { - constructor(token, triggerId, title, submitLabel, callbackId, notifyOnCancel = false) { + constructor(token, triggerId, title, submitLabel, callbackId, notifyOnCancel = false, state) { if (!token || !triggerId || !title) throw new Error('token, triggerId and title are requeired for dialog.open method'); @@ -21,6 +21,9 @@ class SlackDialog { if (notifyOnCancel && typeof(notifyOnCancel) !== 'boolean') throw new Error('notify_on_cancel needs to be a boolean'); + if (state && state.length > 3000) + throw new Error('state needs to be less or equal to 3000 characters'); + this.template = { token: token, trigger_id: triggerId, @@ -31,6 +34,9 @@ class SlackDialog { } }; + if (state) + this.template.state = state + if (submitLabel) this.template.dialog.submit_label = submitLabel; From d7f8feed688bf4e8b485ca671509659fb9218efd Mon Sep 17 00:00:00 2001 From: Srdjan Prpa Date: Mon, 22 Jul 2019 10:59:15 +0200 Subject: [PATCH 5/7] Minor fix for state dialog --- lib/slack/format-dialog.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/slack/format-dialog.js b/lib/slack/format-dialog.js index f1a6e30..7f7bc15 100644 --- a/lib/slack/format-dialog.js +++ b/lib/slack/format-dialog.js @@ -35,7 +35,7 @@ class SlackDialog { }; if (state) - this.template.state = state + this.template.dialog.state = state if (submitLabel) this.template.dialog.submit_label = submitLabel; From 84a1ed97befab83a4679560a2761b34bcee49c00 Mon Sep 17 00:00:00 2001 From: Srdjan Prpa Date: Tue, 3 Mar 2020 23:24:50 +0100 Subject: [PATCH 6/7] Slack parse view_submission --- lib/slack/parse.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/slack/parse.js b/lib/slack/parse.js index f2b74d5..9da8f02 100644 --- a/lib/slack/parse.js +++ b/lib/slack/parse.js @@ -26,4 +26,13 @@ module.exports = function(messageObject) { type: messageObject.type === 'dialog_submission' ? 'slack-dialog-confirm' : 'slack-dialog-cancel', postback: true }; + + if (messageObject && messageObject.user && (messageObject.type === 'view_submission')) + return { + sender: messageObject.user.id, + text: '', + originalRequest: messageObject, + type: messageObject.type === 'view_submission', + postback: true + }; }; From ccdf6c40b69124f65752fad08e7a6781aba27871 Mon Sep 17 00:00:00 2001 From: Srdjan Prpa Date: Tue, 3 Mar 2020 23:27:21 +0100 Subject: [PATCH 7/7] Minor fix --- lib/slack/parse.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/slack/parse.js b/lib/slack/parse.js index 9da8f02..1d890dd 100644 --- a/lib/slack/parse.js +++ b/lib/slack/parse.js @@ -32,7 +32,7 @@ module.exports = function(messageObject) { sender: messageObject.user.id, text: '', originalRequest: messageObject, - type: messageObject.type === 'view_submission', + type: 'view_submission', postback: true }; };