UNPKG

node-red-contrib-chatbot

Version:

REDBot a Chat bot for a full featured chat bot for Telegram, Facebook Messenger and Slack. Almost no coding skills required

1,265 lines (1,190 loc) 68 kB
<script type="text/javascript"> $.RedBot = {}; $.RedBot.buttonTypes = [ { type: 'url', label: 'URL', className: 'platform-combo-telegram-facebook-viber', platforms: ['facebook', 'telegram', 'viber'] }, { type: 'postback', label: 'Postback', className: 'platform-combo-telegram-facebook-slack-viber', platforms: ['facebook', 'telegram', 'slack', 'viber'] }, { type: 'dialog', label: 'Open Dialog', className: 'platform-combo-slack', platforms: ['slack'] }, { type: 'quick-reply', label: 'Postback', className: 'platform-combo-facebook', platforms: ['facebook'] }, { type: 'location', label: 'Location', className: 'platform-combo-facebook', platforms: ['facebook'] }, { type: 'call', label: 'Phone Call', className: 'platform-combo-facebook', platforms: ['facebook'] }, { type: 'share', label: 'Share', className: 'platform-combo-facebook', platforms: ['facebook'] }, { type: 'login', label: 'Log In', className: 'platform-combo-facebook', platforms: ['facebook'] }, { type: 'logout', label: 'Log Out', className: 'platform-combo-facebook', platforms: ['facebook'] }, { type: 'newline', label: 'New row of buttons', className: 'platform-combo-telegram-viber', platforms: ['telegram', 'viber'] }, { type: 'keyboardButton', label: 'Keyboard Button', className: 'platform-combo-telegram', platforms: ['telegram'] } ]; $.RedBot.elementTypes = [ { type: 'text', label: 'Text', className: 'platform-slack', platforms: ['slack'] }, { type: 'textarea', label: 'Textarea', className: 'platform-slack', platforms: ['slack'] }, { type: 'select', label: 'Select', className: 'platform-slack', platforms: ['slack'] } ]; $.RedBot.validate = {}; $.RedBot.validate.isVariable = function(value) { return $.RedBot.validate.string(value) && value.match(/^\{\{[A-Za-z0-9_-]*\}\}$/) != null; }; $.RedBot.validate.number = function(value) { return typeof value === 'number'; }; $.RedBot.validate.string = function(value) { return typeof value === 'string'; }; $.RedBot.validate.email = function(value) { return typeof value === 'string' && value !== '' && value.indexOf('@') !== -1; }; $.RedBot.validate.privateKey = function(value) { return $.RedBot.validate.string(value) && value.indexOf('-----BEGIN PRIVATE KEY-----') !== -1 && value.indexOf('-----END PRIVATE KEY-----') !== -1; }; $.RedBot.validate.numberOrVariable = function(value) { return $.RedBot.validate.number(value) || ($.RedBot.validate.notEmpty(value) && !isNaN(parseFloat(value))) || ($.RedBot.validate.notEmpty(value) && $.RedBot.validate.isVariable(value)); }; $.RedBot.validate.price = function(price) { return price != null && $.RedBot.validate.notEmpty(price.label) && $.RedBot.validate.numberOrVariable(price.amount); }; $.RedBot.validate.prices = function(prices) { if (prices == null) { return false; } var valid = true; var idx; for(idx = 0; idx < prices.length; idx++) { if (!$.RedBot.validate.price(prices[idx])) { valid = false; } } return valid; }; $.RedBot.validate.notEmpty = function(str) { return str != null && str != ''; }; $.RedBot.validate.isEmpty = function(str) { return str == null || str.length === 0; }; $.RedBot.validate.url = function(url) { return url != null && url.match(/[-a-zA-Z0-9@:%_\+.~#?&//=]{2,256}\.[a-z]{2,4}\b(\/[-a-zA-Z0-9@:%_\+.~#?&//=]*)?/gi); }; $.RedBot.validate.arrayNotEmpty = function(array) { return array != null && array.length !== 0; }; $.RedBot.validate.element = function(element) { if (element == null) { return false; } switch(element.type) { case 'text': case 'textarea': return $.RedBot.validate.notEmpty(element.name) && $.RedBot.validate.notEmpty(element.label); case 'select': return $.RedBot.validate.notEmpty(element.name) && $.RedBot.validate.notEmpty(element.label) && $.RedBot.validate.options(element.options); default: return false; } }; $.RedBot.validate.options = function(options) { if (options == null || options.length == null || options.length === 0) { return false; } var idx; var result = true; for(idx = 0; idx < options.length; idx++) { if (!($.RedBot.validate.notEmpty(options[idx].value) && $.RedBot.validate.notEmpty(options[idx].label))) { result = false; } } return result; }; $.RedBot.validate.button = function(button) { if (button == null) { return false; } switch(button.type) { case 'url': return $.RedBot.validate.notEmpty(button.label) && $.RedBot.validate.url(button.url); case 'postback': return $.RedBot.validate.notEmpty(button.label) && $.RedBot.validate.notEmpty(button.value); case 'dialog': return $.RedBot.validate.notEmpty(button.label) && $.RedBot.validate.notEmpty(button.value); case 'quick-reply': return $.RedBot.validate.notEmpty(button.label) && $.RedBot.validate.notEmpty(button.value); case 'call': return $.RedBot.validate.notEmpty(button.label) && $.RedBot.validate.notEmpty(button.number); case 'keyboardButton': return $.RedBot.validate.notEmpty(button.label); case 'newline': case 'share': case 'logout': case 'location': return true; case 'login': return $.RedBot.validate.url(button.url); default: return false; } }; $.RedBot.validate.confirmationStatus = function(status) { return ['none', 'confirmed', 'denied'].indexOf(status) !== -1; }; $.RedBot.validate.rule = function(rule) { if (rule == null) { return false; } switch(rule.type) { case 'isSlotConfirmationStatus': return $.RedBot.validate.notEmpty(rule.slot) && $.RedBot.validate.confirmationStatus(rule.confirmationStatus); case 'isIntentConfirmationStatus': return $.RedBot.validate.confirmationStatus(rule.confirmationStatus); case 'dialogState': return $.RedBot.validate.notEmpty(rule.state); case 'environment': return $.RedBot.validate.notEmpty(rule.environment); case 'topicIncludes': case 'isNotTopic': case 'isTopic': return $.RedBot.validate.notEmpty(rule.topic); case 'isVariable': return $.RedBot.validate.notEmpty(rule.value) && $.RedBot.validate.notEmpty(rule.variable); case 'hasNotVariable': case 'hasVariable': return $.RedBot.validate.notEmpty(rule.variable); case 'command': return $.RedBot.validate.notEmpty(rule.command); case 'messageType': return $.RedBot.validate.notEmpty(rule.messageType); case 'transport': return $.RedBot.validate.notEmpty(rule.transport); case 'messageEvent': return $.RedBot.validate.notEmpty(rule.event); case 'isIntentName': return $.RedBot.validate.notEmpty(rule.intent); case 'isIntent': case 'outbound': case 'inbound': case 'isTopicEmpty': case 'catchAll': case 'pending': case 'notPending': case 'anyCommand': return true; default: return false; } }; $.RedBot.validate.rules = function(rules) { if (rules == null) { return false; } var valid = true; var idx; for(idx = 0; idx < rules.length; idx++) { if (!$.RedBot.validate.rule(rules[idx])) { valid = false; } } return valid; }; $.RedBot.buttonTypes.findButton = function(type) { var button = null; this.forEach(function(current) { if (current.type === type) { button = current; } }); return button; }; $.RedBot.elementTypes.findElement = function(type) { var element = null; this.forEach(function(current) { if (current.type === type) { element = current; } }); return element; }; $.RedBot.platforms = function(platforms, availablePlatforms) { var match = false; platforms.forEach(function(platform) { if (availablePlatforms.indexOf(platform) !== -1) { match = true; } }); return match; }; $.RedBot.intersect = function(array1, array2) { var result = []; var k = 0; // perhaps use ES6 set for(k = 0; k < array1.length; k++) { if (array2.indexOf(array1[k]) !== -1 && result.indexOf(array1[k]) === -1) { result.push(array1[k]); } } for(k = 0; k < array2.length; k++) { if (array1.indexOf(array2[k]) !== -1 && result.indexOf(array2[k]) === -1) { result.push(array2[k]); } } return result; }; $.RedBot.platformClass = function(control, platforms) { var base = null; switch(control) { case 'select': base = 'platform-combo'; break; case 'icon': base = 'icon-platform'; break; case 'text': base = 'platform'; break; default: console.error('Unrecognized platform icon for type "' + control + '"'); return ''; } var platformClasses = []; if (platforms.indexOf('telegram') !== -1) { platformClasses.push('telegram'); } if (platforms.indexOf('facebook') !== -1) { platformClasses.push('facebook'); } if (platforms.indexOf('smooch') !== -1) { platformClasses.push('smooch'); } if (platforms.indexOf('slack') !== -1) { platformClasses.push('slack'); } if (platforms.indexOf('viber') !== -1) { platformClasses.push('viber'); } if (platforms.indexOf('alexa') !== -1) { platformClasses.push('alexa'); } if (platforms.indexOf('twilio') !== -1) { platformClasses.push('twilio'); } if (platforms.indexOf('discord') !== -1) { platformClasses.push('discord'); } return platformClasses.length !== 0 ? base + '-' + platformClasses.join('-') : ''; }; $.RB_Legend = function() { $('.redbot-form-legend').each(function() { var dom = $(this); dom.html('Some attributes apply for specific platform:<br>\n' + ' <span class="icon-platform-alexa"></span> = <span style="font-size:10px">Alexa</span>\n' + ' <span class="icon-platform-discord"></span> = <span style="font-size:10px">Discord</span>\n' + ' <span class="icon-platform-facebook"></span> = <span style="font-size:10px">Facebook</span>\n' + ' <span class="icon-platform-smooch"></span> = <span style="font-size:10px">Smooch</span>\n' + ' <span class="icon-platform-slack"></span> = <span style="font-size:10px">Slack</span>\n' + ' <span class="icon-platform-twilio"></span> = <span style="font-size:10px">Twilio</span>\n' + ' <span class="icon-platform-telegram"></span> = <span style="font-size:10px">Telegram</span>\n' + ' <span class="icon-platform-universal"></span> = <span style="font-size:10px">Universal</span>\n' + ' <span class="icon-platform-viber"></span> = <span style="font-size:10px">Viber</span>') }); }; $.fn.RB_setComboWithPlatformLabels = function(value) { var combo = $(this); $(this).val(value); var selected = $('option[value=' + value + ']', this); combo .attr('class') .split(' ') .forEach(function(className) { if (className.indexOf('platform-') !== -1) { combo.removeClass(className); } }); if (selected != null) { combo.addClass(selected.attr('class')); } }; $.fn.RB_comboWithPlatformLabels = function() { $(this) .change(function() { var combo = $(this); var selected = $('option[value=' + combo.val() + ']', this); combo .attr('class') .split(' ') .forEach(function(className) { if (className.indexOf('platform-') !== -1) { combo.removeClass(className); } }); if (selected != null) { combo.addClass(selected.attr('class')); } }); }; $.fn.RB_OptionsEditor = function(command, options) { var container = $(this); var defaultOptions = { label: 'Options', labelAdd: 'add' }; if (arguments.length === 1 && typeof arguments[0] === 'object') { options = $.extend({}, defaultOptions, arguments[0]); command = 'init'; } else if (arguments.length === 0) { command = 'init'; options = $.extend({}, defaultOptions); } else if (arguments.length === 1 && arguments[0] === 'value') { command = 'get_value'; } else if (arguments.length === 2 && arguments[0] === 'value') { command = 'set_value'; } var addRow = function(value, label) { var count = $('.option', container); $('<div class="option"/>').css('marginTop','5px') .append('<div style="display:inline-block;width:6%;" class="order">' + (count.length + 1) + '.</div>') .append($('<input style="width:35%" class="node-input-rule-type" type="text" name="value" placeholder="Value" value="' + value + '"/>')) .append($('<input style="width:48%;margin-left: 5%" class="node-input-rule-type" type="text" name="label" placeholder="Label" value="' + label + '"/>')) .append('<a href="#" class="btn-remove" style="width:5%;display:inline-block;"><i class="fa fa-times"></i></a>') .appendTo($('.options', container)); }; switch(command) { case 'init': container .addClass('rb-options-editor') .on('click', '.btn-remove', function() { $(this).parents('.option').remove(); $('.option', container).each(function(idx, item) { $('.order', item).html(idx + 1); }); }); $('<div class="control-description"/>') .html(options.label) .appendTo(container); $('<div class="options"/>') .appendTo(container); $('<div class="button"/>') .append('<a class="btn-add" href="#">' + options.labelAdd+ '</a>') .appendTo(container); $('a.btn-add', container) .click(function(e) { e.preventDefault(); addRow('', ''); }); break; case 'get_value': var values = []; $('.option', container).each(function(idx, item) { var value = $('input[name=value]', item).val(); var label = $('input[name=label]', item).val(); if (value === '' && label !== '') { value = label; } else if (value !== '' && label === '') { label = value; } if (value !== '' && label !== '') { values.push({ value: value, label: label }); } }); return values; case 'set_value': $('.options').empty(); $(arguments[1]).each(function(idx, option) { addRow(option.value, option.label); }); break; } return container; }; $.fn.RB_setElementData = function(obj, options) { options = options || {}; options.badges = options.badges !== false; var container = $(this); var select = $('select[name=type]', container).val(obj.type); var panel = $('.panel-' + obj.type, container).show(); var element = $.RedBot.elementTypes.findElement(obj.type); if (element != null && options.badges) { select.attr('class', element.className); } $('input[name="label"]', panel).val(obj.label); $('input[name="name"]', panel).val(obj.name); $('input[name="value"]', panel).val(obj.value); $('input[name="placeholder"]', panel).val(obj.placeholder); $('input[name="hint"]', panel).val(obj.hint); $('select[name="subtype"]', panel).val(obj.subtype); $('input[name="max_length"]', panel).val(obj.maxLength); $('input[name="min_length"]', panel).val(obj.minLength); $('input[name="optional"]', panel).attr('checked', obj.optional === true); $('.container-options', panel).RB_OptionsEditor('value', obj.options); return container; }; $.fn.RB_setButtonData = function(obj, options) { options = options || {}; options.badges = options.badges !== false; var container = $(this); var availablePlatforms = $.isArray(container.data('platforms')) ? container.data('platforms') : ['facebook', 'telegram']; var select = $('select[name=type]', container).val(obj.type); var panel = $('.panel-' + obj.type, container).show(); var button = $.RedBot.buttonTypes.findButton(obj.type); // update badges of select with intersection of available platforms and button compatibility if (button != null && options.badges) { select.attr('class', $.RedBot.platformClass('select', $.RedBot.intersect(availablePlatforms, button.platforms))); } else { select.attr('class', ''); } $('input[name=label]', panel).val(obj.label); $('input[name=url]', panel).val(obj.url); $('input[name=value]', panel).val(obj.value); $('input[name=number]', panel).val(obj.number); $('input[name=answer]', panel).val(obj.answer); $('input[name=alert]', panel).attr('checked', obj.alert === true); $('select[name=webview_height_ratio]', panel).val(obj.webViewHeightRatio); $('select[name=style]', panel).val(obj.style); $('input[name=messenger_extensions]').attr('checked', obj.extensions === true); return container; }; $.fn.RB_getElementData = function() { var container = $(this); var type = $('select[name="type"]', container); var panel = $('.panel-' + type.val(), container); var label = $('input[name="label"]', panel); var name = $('input[name="name"]', panel); var value = $('input[name="value"]', panel); var placeholder = $('input[name="placeholder"]', panel); var hint = $('input[name="hint"]', panel); var subtype = $('select[name="subtype"]', panel); var maxLength = $('input[name="max_length"]', panel); var minLength = $('input[name="min_length"]', panel); var optional = $('input[name="optional"]', panel); var options = $('.container-options', panel); var result = null; switch(type.val()) { case 'text': case 'textarea': result = { type: type.val(), label: label.val(), name: name.val(), value: value.val(), placeholder: placeholder.val(), hint: hint.val(), optional: optional.is(':checked') }; if (subtype.val() !== '') { result.subtype = subtype.val(); } if (!isNaN(parseInt(maxLength.val(), 10))) { result.maxLength = parseInt(maxLength.val(), 10); } if (!isNaN(parseInt(minLength.val(), 10))) { result.minLength = parseInt(minLength.val(), 10); } break; case 'select': result = { type: 'select', label: label.val(), name: name.val(), options: options.RB_OptionsEditor('value') }; break; } return result; }; $.fn.RB_getButtonData = function() { var container = $(this); var type = $('select[name="type"]', container); var panel = $('.panel-' + type.val(), container); var label = $('input[name="label"]', panel); var url = $('input[name="url"]', panel); var value = $('input[name="value"]', panel); var number = $('input[name="number"]', panel); var answer = $('input[name="answer"]', panel); var alert = $('input[name="alert"]', panel); var style = $('select[name="style"]', panel); var messengerExtensions = $('input[name="messenger_extensions"]', panel); var webViewHeight = $('select[name="webview_height_ratio"]', panel); var result = null; switch(type.val()) { case 'keyboardButton': result = { type: 'keyboardButton', label: label.val() }; break; case 'url': result = { type: 'url', label: label.val(), url: url.val(), webViewHeightRatio: webViewHeight.val(), extensions: messengerExtensions.is(':checked'), answer: answer.val(), alert: alert.is(':checked') }; break; case 'call': result = { type: 'call', label: label.val(), number: number.val() }; break; case 'postback': result = { type: 'postback', label: label.val(), value: value.val(), answer: answer.val(), alert: alert.is(':checked'), style: style.val() }; break; case 'dialog': result = { type: 'dialog', label: label.val(), value: value.val(), answer: answer.val(), alert: alert.is(':checked'), style: style.val() }; break; case 'quick-reply': result = { type: 'quick-reply', label: label.val(), value: value.val(), url: url.val() }; break; case 'newline': result = { type: 'newline' }; break; case 'location': result = { type: 'location' }; break; case 'share': result = { type: 'share' }; break; case 'logout': result = { type: 'logout' }; break; case 'login': result = { type: 'login', url: url.val() }; break; } return result; }; $.fn.RB_hasPlatform = function(platform, platforms) { platform = platform instanceof Array ? platform : [platform]; var hasMatch = false; for(var idx = 0; idx < platform.length; idx++) { if (platforms.indexOf(platform[idx]) !== -1) { hasMatch = true; } } return hasMatch ? $(this) : $(); }; $.fn.RB_mountButtonDialog = function(options) { options = options || {}; options.types = options.types != null ? options.types : ['url', 'postback', 'call', 'share', 'login', 'newline', 'logout', 'location', 'quick-reply', 'dialog', 'key']; options.platforms = options.platforms != null ? options.platforms : ['facebook', 'telegram']; options.badges = options.badges !== false; var container = $(this); // store available platforms in the container container.data('platforms', options.platforms); var main = $('<div/>').appendTo(container); var cx = options.badges ? $.RedBot.platformClass : function() { return ''; }; var selectButtonType = $('<select name="type"/>', {style:"width:200px; margin-left: 5px; text-align: center;"}) .attr('placeholder', 'Select button type') .change(function() { var type = $(this).val(); $('.panel', container).hide(); $('.panel-' + type, container).show(); // find the right button var button = $.RedBot.buttonTypes.findButton(type); // store class only if badges should be shown if (button != null && options.badges) { $(this).attr('class', cx('select', $.RedBot.intersect(options.platforms, button.platforms))); } else { $(this).attr('class', ''); } }) .appendTo(main); selectButtonType.append($('<option value="">Select button type</option>')); $.RedBot.buttonTypes.forEach(function(button) { if (options.types.indexOf(button.type) !== -1 && $.RedBot.platforms(options.platforms, button.platforms)) { selectButtonType.append($('<option value="' + button.type + '">' + button.label + '</option>')); } }); var panelUrl = $('<div class="panel panel-url" style="display:none;"/>').appendTo(container); $('<div/>').css('marginTop','5px') .append($('<input style="width:100%" class="node-input-rule-type" type="text" name="label" placeholder="Button label"/>')) .appendTo(panelUrl); $('<div/>').css('marginTop','5px') .append($('<input style="width:100%" class="node-input-rule-type" type="text" name="url" placeholder="Button URL"/>')) .appendTo(panelUrl); $('<div/>').css('marginTop','5px') .RB_hasPlatform('telegram', options.platforms) .append('<input class="node-input-rule-type ' + cx('text', ['telegram']) + '" type="text" name="answer" placeholder="Button feedback"/>') .append('<input type="checkbox" name="alert" value="on" style="width: auto;margin:0px 10px 5px 10px;"/>') .append('<span>With popup</span><span class="' + cx('icon', ['telegram']) + '"></span>') .appendTo(panelUrl); $('<div/>').css('marginTop','5px') .RB_hasPlatform('facebook', options.platforms) .append('<select name="webview_height_ratio" class="' + cx('select', ['facebook']) + '" style="width:70%">' + '<option value="tall">Tall</option>' + '<option value="compact">Compact</option>' + '<option value="full">Full</option>' + '</select>') .append('<input type="checkbox" name="messenger_extensions" value="on" style="width: auto;margin:0px 10px 5px 10px;"/>') .append('<span>Extensions</span><span class="' + cx('icon', ['facebook']) + '"></span>') .appendTo(panelUrl); var panelPostback = $('<div class="panel panel-postback" style="display:none;"/>').appendTo(container); $('<div/>').css('marginTop','5px') .append($('<input style="width:100%" class="node-input-rule-type" type="text" name="label" placeholder="Button label"/>')) .appendTo(panelPostback); $('<div/>').css('marginTop','5px') .append($('<input style="width:100%" class="node-input-rule-type" type="text" name="value" placeholder="Postback value">')) .appendTo(panelPostback); var feedbackRow = $('<div/>').css('marginTop','5px') .RB_hasPlatform(['telegram', 'slack'], options.platforms) .append('<input class="node-input-rule-type ' + cx('text', ['telegram', 'slack']) + '" type="text" name="answer" placeholder="Button feedback (optional)"/>'); $('<input type="checkbox" name="alert" value="on" style="width: auto;margin:0px 10px 5px 10px;"/>') .RB_hasPlatform(['telegram'], options.platforms) .appendTo(feedbackRow); $('<span>With popup</span><span class="' + cx('icon', ['telegram']) + '"></span>') .RB_hasPlatform(['telegram'], options.platforms) .appendTo(feedbackRow); feedbackRow.appendTo(panelPostback); $('<div/>').css('marginTop','5px') .RB_hasPlatform('slack', options.platforms) .append('<select name="style" class="' + cx('select', ['slack']) + '" style="width:70%">' + '<option value="">Select button style</option>' + '<option value="default">Default</option>' + '<option value="primary">Primary</option>' + '<option value="danger">Danger</option>' + '</select>') .appendTo(panelPostback); var panelDialog = $('<div class="panel panel-dialog" style="display:none;"/>').appendTo(container); $('<div/>').css('marginTop','5px') .append($('<input style="width:100%" class="node-input-rule-type" type="text" name="label" placeholder="Button label"/>')) .appendTo(panelDialog); $('<div/>').css('marginTop','5px') .append($('<input style="width:100%" class="node-input-rule-type" type="text" name="value" placeholder="Dialog Id">')) .appendTo(panelDialog); $('<div/>').css('marginTop','5px') .RB_hasPlatform(['telegram', 'slack'], options.platforms) .append('<input class="node-input-rule-type ' + cx('text', ['slack']) + '" type="text" name="answer" placeholder="Button feedback (optional)"/>') .appendTo(panelDialog); $('<div/>').css('marginTop','5px') .append('<select name="style" class="' + cx('select', ['slack']) + '" style="width:70%">' + '<option value="">Select button style</option>' + '<option value="default">Default</option>' + '<option value="primary">Primary</option>' + '<option value="danger">Danger</option>' + '</select>') .appendTo(panelDialog); var panelQuickReply = $('<div class="panel panel-quick-reply" style="display:none;"/>').appendTo(container); $('<div/>').css('marginTop','5px') .append($('<input style="width:100%" class="node-input-rule-type" type="text" name="label" placeholder="Button label"/>')) .appendTo(panelQuickReply); $('<div/>').css('marginTop','5px') .append($('<input style="width:100%" class="node-input-rule-type" type="text" name="value" placeholder="Postback value">')) .appendTo(panelQuickReply); $('<div/>').css('marginTop','5px') .append($('<input style="width:100%" class="node-input-rule-type" type="text" name="url" placeholder="Image URL"/>')) .appendTo(panelQuickReply); var panelCall = $('<div class="panel panel-call" style="display:none;"/>').appendTo(container); $('<div/>').css('marginTop','5px') .append($('<input style="width:100%" class="node-input-rule-type" type="text" name="label" placeholder="Button label"/>')) .appendTo(panelCall); $('<div/>').css('marginTop','5px') .append($('<input style="width:100%" class="node-input-rule-type" type="text" name="number" placeholder="Phone number"/>')) .appendTo(panelCall); var panelNewline = $('<div class="panel panel-newline" style="display:none;"/>').appendTo(container); $('<div/>').css('marginTop','5px') .html('This creates a new row of buttons') .appendTo(panelNewline); var panelKeyboardButton = $('<div class="panel panel-keyboardButton" style="display:none;"/>').appendTo(container); $('<div/>').css('marginTop','5px') .append($('<input style="width:100%" class="node-input-rule-type" type="text" name="label" placeholder="Button label"/>')) .appendTo(panelKeyboardButton); $('<div class="panel panel-share" style="display:none;"/>').appendTo(container); $('<div class="panel panel-logout" style="display:none;"/>').appendTo(container); $('<div class="panel panel-location" style="display:none;"/>').appendTo(container); var panelLogin = $('<div class="panel panel-login" style="display:none;"/>').appendTo(container); $('<div/>').css('marginTop','5px') .append($('<input style="width:100%" class="node-input-rule-type" type="text" name="url" placeholder="Authentication URL callback"/>')) .appendTo(panelLogin); return container; }; $.fn.RB_mountElementDialog = function(options) { options = options || {}; options.types = options.types != null ? options.types : ['text', 'textarea', 'select']; options.platforms = options.platforms != null ? options.platforms : ['slack']; options.badges = options.badges !== false; var container = $(this); var main = $('<div/>').appendTo(container); var cx = options.badges ? $.RedBot.platformClass : function() { return ''; }; var selectElementType = $('<select name="type"/>', {style:"width:200px; margin-left: 5px; text-align: center;"}) .attr('placeholder', 'Select element type') .change(function() { var type = $(this).val(); $('.panel', container).hide(); $('.panel-' + type, container).show(); // find the right button var button = $.RedBot.buttonTypes.findButton(type); // store class only if badges should be shown if (button != null && options.badges) { $(this).attr('class', button.className); } else { $(this).attr('class', ''); } }) .appendTo(main); selectElementType.append($('<option value="">Select element type</option>')); $.RedBot.elementTypes.forEach(function(button) { if (options.types.indexOf(button.type) !== -1 && $.RedBot.platforms(options.platforms, button.platforms)) { selectElementType.append($('<option value="' + button.type + '">' + button.label + '</option>')); } }); var panelText = $('<div class="panel panel-text" style="display:none;"/>').appendTo(container); $('<div/>').css('marginTop','5px') .append($('<input style="width:100%" class="node-input-rule-type" type="text" name="label" placeholder="Label"/>')) .appendTo(panelText); $('<div/>').css('marginTop','5px') .append($('<input style="width:100%" class="node-input-rule-type" type="text" name="name" placeholder="Name"/>')) .appendTo(panelText); $('<div/>').css('marginTop','5px') .append($('<input style="width:100%" class="node-input-rule-type" type="text" name="value" placeholder="Initial value"/>')) .appendTo(panelText); $('<div/>').css('marginTop','5px') .append($('<input style="width:100%" class="node-input-rule-type" type="text" name="placeholder" placeholder="Placeholder"/>')) .appendTo(panelText); $('<div/>').css('marginTop','5px') .append($('<input style="width:100%" class="node-input-rule-type" type="text" name="hint" placeholder="Hint"/>')) .appendTo(panelText); $('<div/>').css('marginTop','5px') .append('<select name="subtype" class="' + cx('select', ['']) + '" style="width:60%">' + '<option value="">Select sub-type</option>' + '<option value="email">Email</option>' + '<option value="number">Number</option>' + '<option value="tel">Telephone</option>' + '<option value="url">URL</option>' + '</select>') .append('<input type="checkbox" name="optional" value="on" style="width: auto;margin:0px 10px 5px 10px;"/>') .append('<span>Optional</span>') .appendTo(panelText); $('<div/>').css('marginTop','5px') .append($('<input style="width:45%" class="node-input-rule-type" type="number" name="min_length" placeholder="Min Length"/>')) .append($('<input style="width:45%;margin-left: 10%" class="node-input-rule-type" type="number" name="max_length" placeholder="Max Length"/>')) .appendTo(panelText); var panelTextarea = $('<div class="panel panel-textarea" style="display:none;"/>').appendTo(container); $('<div/>').css('marginTop','5px') .append($('<input style="width:100%" class="node-input-rule-type" type="text" name="label" placeholder="Label"/>')) .appendTo(panelTextarea); $('<div/>').css('marginTop','5px') .append($('<input style="width:100%" class="node-input-rule-type" type="text" name="name" placeholder="Name"/>')) .appendTo(panelTextarea); $('<div/>').css('marginTop','5px') .append($('<input style="width:100%" class="node-input-rule-type" type="text" name="value" placeholder="Initial value"/>')) .appendTo(panelTextarea); $('<div/>').css('marginTop','5px') .append($('<input style="width:100%" class="node-input-rule-type" type="text" name="placeholder" placeholder="Placeholder"/>')) .appendTo(panelTextarea); $('<div/>').css('marginTop','5px') .append($('<input style="width:100%" class="node-input-rule-type" type="text" name="hint" placeholder="Hint"/>')) .appendTo(panelTextarea); $('<div/>').css('marginTop','5px') .append('<select name="subtype" class="' + cx('select', ['']) + '" style="width:60%">' + '<option value="">Subtype</option>' + '<option value="email">Email</option>' + '<option value="number">Number</option>' + '<option value="tel">Telephone</option>' + '<option value="url">URL</option>' + '</select>') .append('<input type="checkbox" name="optional" value="on" style="width: auto;margin:0px 10px 5px 10px;"/>') .append('<span>Optional</span>') .appendTo(panelTextarea); var panelSelect = $('<div class="panel panel-select" style="display:none;"/>').appendTo(container); $('<div/>').css('marginTop','5px') .append($('<input style="width:100%" class="node-input-rule-type" type="text" name="label" placeholder="Label"/>')) .appendTo(panelSelect); $('<div/>').css('marginTop','5px') .append($('<input style="width:100%" class="node-input-rule-type" type="text" name="name" placeholder="Name"/>')) .appendTo(panelSelect); $('<div/>').css('marginTop','5px') .append($('<input style="width:100%" class="node-input-rule-type" type="text" name="value" placeholder="Initial value"/>')) .appendTo(panelSelect); $('<div/>').css('marginTop','5px') .append($('<input style="width:100%" class="node-input-rule-type" type="text" name="placeholder" placeholder="Placeholder"/>')) .appendTo(panelSelect); $('<div/>').css('marginTop','5px') .append($('<input style="width:100%" class="node-input-rule-type" type="text" name="hint" placeholder="Hint"/>')) .appendTo(panelSelect); $('<div/>').css('marginTop','5px') .append('<div class="container-options"/>') .appendTo(panelSelect); panelSelect.find('.container-options').RB_OptionsEditor({ label: 'Combo box options'}); }; RED.nodes.registerType('chatbot-inline-buttons', { category: 'RedBot', color: '#FFCC66', defaults: { name: { value: '' }, buttons: { value: [], validate: function(buttons) { if (buttons == null) { return true; // allow no buttons } var valid = true; var idx; for(idx = 0; idx < buttons.length; idx++) { if (!$.RedBot.validate.button(buttons[idx])) { valid = false; } } return valid; } }, message: { value: '' } }, inputs: 1, outputs: 1, paletteLabel: 'Buttons', icon: 'chatbot-inline-button.png', label: function() { return this.name || 'Buttons'; }, oneditsave: function() { var buttons = $("#node-input-buttons-container").editableList('items'); var node = this; node.buttons = []; var idx; for(idx = 0; idx < buttons.length; idx++) { node.buttons.push($(buttons[idx]).RB_getButtonData()); } }, oneditprepare: function() { $.RB_Legend(); $('#node-input-buttons-container') .css('min-height','300px') .css('min-width','450px') .editableList({ addButton: 'Add button', addItem: function(container, i, item) { $(container).RB_mountButtonDialog({ types: ['url', 'postback', 'call', 'login', 'newline', 'logout', 'dialog'], platforms: ['facebook', 'telegram', 'slack'] }); $(container).RB_setButtonData(item); }, removable: true, sortable: true }); var buttons = this.buttons; var idx; for (idx = 0; idx < buttons.length; idx++) { $('#node-input-buttons-container').editableList('addItem', buttons[idx]); } }, oneditresize: function() { var dialogForm = $('#dialog-form'); var rowName = $('.form-row-name', dialogForm); var rowMessage = $('.form-row-message', dialogForm); var rowLabel = $('.form-row-label', dialogForm); var rowHint = $('.form-row-hint', dialogForm); var height = dialogForm.height() - rowName.height() - rowMessage.height() - rowLabel.height() - rowHint.height() - 30; $('#node-input-buttons-container').editableList('height', height); } }); </script> <script type="text/x-red" data-template-name="chatbot-inline-buttons"> <div class="form-row form-row-name"> <label for="node-input-name"><i class="icon-tag"></i> Name</label> <input type="text" id="node-input-name" placeholder="Name"> </div> <div class="form-row form-row-message"> <label for="node-input-message"><i class="icon-envelope"></i> Message</label> <textarea id="node-input-message" placeholder="Message" style="width:93%;height:50px;"></textarea> <div style="max-width: 460px;font-size: 12px;color: #999999;line-height: 14px;clear:both;margin-top:5px;"> Supports handlebars-like variables for chat context like {{firstName}}, {{lastName}}, etc. and emoticons (:smile:, etc.) </div> </div> <div class="form-row form-row-label" style="margin-bottom:0;"> <label><i class="fa fa-list"></i> <span>Buttons</span></label> </div> <div class="form-row node-input-rule-container-row"> <ol id="node-input-buttons-container"></ol> </div> <div class="redbot-form-legend"></div> </script> <script type="text/x-red" data-help-name="chatbot-inline-buttons"><p>Available buttons:</p> <ul> <li><strong>URL</strong>: Open a URL inside the client. Button feedbacks are available for Telegram and Messenger extensions in Facebook <em>[Telegram, Facebook, Viber]</em></li> <li><strong>Postback</strong>: Allow the user to send a predefined message, the message will no be shown in the chat history. Button feedbacks are available for Telegram and Messenger extensions in Facebook <em>[Telegram, Facebook, Smooch, Viber]</em></li> <li><strong>Phone call</strong>: Starts a phone call on mobile. <em>[Facebook]</em></li> <li><strong>Log In</strong>: Starts the account linking flow. <em>[Facebook]</em></li> <li><strong>Log Out</strong>: Starts the account unlinking flow. <em>[Facebook]</em></li> <li><strong>New row</strong>: Telegram supports buttons layout with multiple row, this element just starts a new one. <em>[Telegram, Viber]</em></li> </ul> <p>Platform exceptions</p> <ul> <li><strong>Telegram</strong> supports a visual feedback, visible in the client, when a <code>Postback</code> or <code>URL</code> buttons is pressed</li> <li><strong>Facebook</strong> supports some extra parameters in the <code>URL</code> button, like the size of the web view and <a href="https://developers.facebook.com/docs/messenger-platform/webview">messenger extensions</a> and a maximum od three buttons.</li> </ul> <p>Buttons can be created programmatically by an upstream <code>Function node</code> passing array of buttons in the message payload:</p> <pre><code>msg.payload = [ { type: &#39;url&#39;, url: &#39;http://javascript-jedi.com&#39;, label: &#39;Javascript Jedi&#39; }, { type: &#39;postback&#39;, value: &#39;MY_POSTBACK_MESSAGE&#39;, label: &#39;Click me!&#39; }, { type: &#39;share&#39; } ]; return msg; </code></pre><p>Another way is to specify each config option as keys of an object in the message payload:</p> <pre><code>msg.payload = { message: &#39;This is a message above the buttons&#39;, buttons: [ { type: &#39;url&#39;, url: &#39;http://javascript-jedi.com&#39;, label: &#39;Javascript Jedi&#39; }, { type: &#39;postback&#39;, value: &#39;MY_POSTBACK_MESSAGE&#39;, label: &#39;Click me!&#39; }, { type: &#39;share&#39; } ] }; return msg; </code></pre><p>Available parameters for the <code>msg.payload</code></p> <dl class="message-properties"> <dt>message<span class="property-type">string</span><dd>The message text above the buttons</dd> <dt>buttons<span class="property-type">array of [button]</span><dd>The list of inline buttons</dd> </dl> <p>The <code>[button]</code> object</p> <dl class="message-properties"> <dt>type<span class="property-type">string</span><dd>Type of button: <em>url</em>, <em>postback</em>, <em>quick-reply</em>, <em>location</em>, <em>call</em>, <em>share</em>, <em>login</em>, <em>logout</em>, <em>newline</em></dd> <dt>label<span class="property-type">string</span><dd>Label of the button</dd> <dt>value<span class="property-type">string</span><dd>Value returned as payload in <code>postback</code> buttons</dd> <dt>url<span class="property-type">string</span><dd>Url to redirect to for <code>url</code> buttons or authentication URL for <code>login</code> buttons</dd> <dt>number<span class="property-type">string</span><dd>Phone number to call for <code>call</code> buttons</dd> <dt>messengerExtensions<span class="property-type">boolean</span><dd>Include Messenger Extensions for <code>url</code> buttons</dd> <dt>answer<span class="property-type">string</span><dd>The feedback shown on Telegram client for <code>url</code> and <code>postback</code> buttons</dd> <dt>alert<span class="property-type">boolean</span><dd>Show the feedback as alert on Telegram client for <code>url</code> and <code>postback</code> buttons</dd> <dt>style<span class="property-type">string</span><dd>Style of buttons in Slack: <em>default</em>, <em>primary</em>, <em>danger</em></dd> <dt>webViewHeightRatio<span class="property-type">string</span><dd>Aspect ratio of the webview in Facebook Messenger for <code>url buttons</code>. Valid values: <em>tall</em>, <em>compact</em>, <em>full</em></dd> </dl> <p><img src="https://img.shields.io/badge/platform-Telegram-blue.svg?colorB=7cbaea" alt="Telegram"> <img src="https://img.shields.io/badge/platform-Facebook-blue.svg" alt="Facebook"> <img src="https://img.shields.io/badge/platform-Slack-green.svg" alt="Slack"> <img src="https://img.shields.io/badge/platform-Smooch-orange.svg" alt="Smooch"> <img src="https://img.shields.io/badge/platform-Viber-8079d0.svg" alt="Viber"></p> </script> <style> .form-row-checkbox { margin-bottom: 0px; } .form-row-checkbox input[type=checkbox] { width: auto; margin-top: 0px; margin-right: 5px; } .icon-platform-telegram { display: inline-block; width: 9px; height: 9px; background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAAXNSR0IArs4c6QAAADRJREFUGBlj1F764D8DAcACk78SJQ9jwmmdZQ/BbLgimABIMYwNU80EY+Cjh4QidJ+BPAQAUcEMK/ZfXe8AAAAASUVORK5CYII=); margin-left: 3px; margin-right: 3px; vertical-align: middle; } .icon-platform-discord { display: inline-block; width: 9px; height: 9px; background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAAXNSR0IArs4c6QAAADxJREFUGBljVFPT+s9AALDA5G/evApjgml1dW04H64IJIIsAdIE4zPBleNh0FkRisORfQhzNMipjMSEEwBROQx6SDYaygAAAABJRU5ErkJggg=='); margin-left: 3px; margin-right: 3px; vertical-align: middle; } .icon-platform-facebook { display: inline-block; width: 9px; height: 9px; background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAAXNSR0IArs4c6QAAAEJJREFUGBljtIqc8Z+BAGCByR9dlg5jwmnrqJlgNlwRiAcThKuCMpjQBbDxUUxCthLZVBRFyBLIJhJlHVGKGIkJJwAbVQzJXzC/gwAAAABJRU5ErkJggg==); margin-left: 3px; margin-right: 3px; vertical-align: middle; } .icon-platform-smooch { display: inline-block; width: 9px; height: 9px; background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAAXNSR0IArs4c6QAAAENJREFUGBljXOyx/T8DAcAEk4/Z7gFjgmlkPlwRSAYmAaNhulAULfHcAVYIopEBiiKQBLoCkBiGIpAgOqCeIkZiwgkAEPoRCUXTyWoAAAAASUVORK5CYII=); margin-left: 3px; margin-right: 3px; vertical-align: middle; } .icon-platform-slack { display: inline-block; width: 9px; height: 9px; background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAAXNSR0IArs4c6QAAAEtJREFUGBljDNvW/Z+BAGBBll/pWYLMZQjf3gPmwxWBFMAEUVQCOUzoAtj4cJNApiBbh2wqXBHIBGQJZOtJsw7ZKnRT4dYhW4XueACZqRaCOTsvfwAAAABJRU5ErkJggg==); margin-left: 3px; margin-right: 3px; vertical-align: middle; } .icon-platform-viber { display: inline-block; width: 9px; height: 9px; background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAIAAABv85FHAAAANklEQVQImWNsKL/MgAMwQaj6Dh24EJzNhEsT5XKNFVcg1tR36DRWXCHFTIhWuCZ0fcgSDAwMAO+nENww2Up1AAAAAElFTkSuQmCC); margin-left: 3px; margin-right: 3px; vertical-align: middle; } .icon-platform-universal { display: inline-block; width: 9px; height: 9px; background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAIAAABv85FHAAAAKUlEQVQImWP8v4MBF2CCUP/d/8OF4GwmnLroL8cI9wPceYw7GdHlMAEAfGIMv3ninmsAAAAASUVORK5CYII='); margin-left: 3px; margin-right: 3px; vertical-align: middle; } .icon-platform-twilio { display: inline-block; width: 9px; height: 9px; background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAAXNSR0IArs4c6QAAADNJREFUGBljfGfk9Z+BAGCByQue3Qpjwun3xt5gNiO6SSDFMEmYaiYYAx89GBVh+A6bBwDzEA1NPYkyaAAAAABJRU5ErkJggg=='); margin-left: 3px; margin-right: 3px; vertical-align: middle; } .icon-platform-alexa { display: inline-block; width: 9px; height: 9px; background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAAXNSR0IArs4c6QAAAEJJREFUGBljVD799z8DAcCCLH/HhAnOVTnzD86GKwIpQJZA5iO0wvVhMuAmgaSQrUNWiqII3TqYQqKsI0oRIzHhBABD+hL/gIpxvwAAAABJRU5ErkJggg=='); margin-left: 3px; margin-right: 3px; vertical-align: middle; } .platform-facebook { background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAAXNSR0IArs4c6QAAAEJJREFUGBljtIqc8Z+BAGCByR9dlg5jwmnrqJlgNlwRiAcThKuCMpjQBbDxUUxCthLZVBRFyBLIJhJlHVGKGIkJJwAbVQzJXzC/gwAAAABJRU5ErkJggg==); background-position: right 2px top 2px; background-repeat: no-repeat; } .platform-combo-facebook { background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAAXNSR0IArs4c6QAAAEJJREFUGBljtIqc8Z+BAGCByR9dlg5jwmnrqJlgNlwRiAcThKuCMpjQBbDxUUxCthLZVBRFyBLIJhJlHVGKGIkJJwAbVQzJXzC/gwAAAABJRU5ErkJggg==); background-position: right 12px top 2px; background-repeat: no-repeat; } .platform-telegram { background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAAXNSR0IArs4c6QAAADRJREFUGBlj1F764D8DAcACk78SJQ9jwmmdZQ/BbLgimABIMYwNU80EY+Cjh4QidJ+BPAQAUcEMK/ZfXe8AAAAASUVORK5CYII=); background-position: