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

302 lines (289 loc) 14.7 kB
<script type="text/javascript"> RED.nodes.registerType('chatbot-universal-node', { category: 'config', defaults: { botname: { value: '', required: true }, usernames: { value: '', required: false }, connectorParams: { value: '' }, store: { value: '', type: 'chatbot-context-store', required: false }, log: { value: null }, debug: { value: false } }, oneditsave: function() { this.connectorParams = $('#node-config-input-connectorParams').typedInput('value'); }, oneditprepare: function() { $("#node-config-input-polling").spinner({min: 0, step: 100}); var node = this; // init free params var widget = $('#node-config-input-connectorParams'); widget.typedInput({ 'default': 'json', types: ['json'] }); widget.typedInput('value', this.connectorParams); // get globals var nodeRedUrl = ''; if (RED.settings.httpNodeRoot) { nodeRedUrl = RED.settings.httpNodeRoot; } // fetch available context providers $.get(nodeRedUrl + 'redbot/globals') .done(function(response) { if (response != null && response.telegram != null && response.telegram[node.botname] != null) { $('#node-config-input-botname').prop('readonly', true); $('.form-editable').addClass('hidden'); $('.form-warning').removeClass('hidden'); $('.form-warning .bot-name').html('"' + node.botname + '"'); } }); }, paletteName: 'Universal Bot', credentials: { token: { type: 'text' } }, label: function () { return this.botname; } }); </script> <script type="text/x-red" data-template-name="chatbot-universal-node"> <div class="form-row"> <label for="node-config-input-botname"><i class="icon-bookmark"></i> Bot Name</label> <input type="text" id="node-config-input-botname"> </div> <div class="form-editable"> <div class="form-row"> <label for="node-config-input-usernames">Users</label> <input type="text" id="node-config-input-usernames"> <div style="max-width: 460px;font-size: 12px;color: #999999;line-height: 14px;margin-top:5px;"> Comma separated list of userId authorized to use the chatBot </div> </div> <div class="form-row"> <label for="node-config-input-log">Log file</label> <input type="text" id="node-config-input-log"> <div style="max-width: 460px;font-size: 12px;color: #999999;line-height: 14px;margin-top:5px;"> Store inbound and outbound messages to file </div> </div> <div class="form-row"> <label for="node-input-bot">Context</label> <input type="text" id="node-config-input-store" placeholder="Select storage for chat context"> <div class="redbot-form-hint"> Select the chat context provider to use with this bot, if none is selected then non-persistent "memory" will be used.<br> To extend <strong>RedBot</strong> with a new chat context provider see <a href="https://github.com/guidone/node-red-contrib-chatbot/wiki/Creating-a-Chat-Context-Provider" target="_blank">this tutorial</a>. </div> </div> <div class="form-row"> <label for="node-config-input-debug">Debug</label> <input type="checkbox" value="true" id="node-config-input-debug"> <span class="redbot-form-hint"> Show debug information on send/receive </span> </div> <div class="form-row"> <label for="node-config-input-connectorParams">Params</label> <input type="text" id="node-config-input-connectorParams" placeholder="Params"> <div class="redbot-form-hint"> Parameters passed to the <i>Universal Connector</i>. Use the method <code>this.getOptions()</code> in the middlewares to get the parameters. See <a href="https://github.com/guidone/node-red-contrib-chatbot/wiki/Extend-node" target="_blank">the help page</a> </div> </div> </div> <div class="form-warning hidden"> This bot configuration is stored in <b>Node-RED</b> <em>settings.js</em> and cannot be modified from the UI, check the section <code>functionGlobalContext</code> near the key <em class="bot-name">""</em> </div> </script> <script type="text/x-red" data-help-name="chatbot-universal-node"> test </script> <script type="text/javascript"> RED.nodes.registerType('chatbot-universal-receive', { category: 'RedBot Platforms', color: '#FFCC66', defaults: { bot: { value: '', type: 'chatbot-universal-node', required: true }, botProduction: { value: '', type: 'chatbot-universal-node', required: false } }, inputs: 1, outputs: 1, icon: 'chatbot-receiver.png', paletteLabel: 'Universal In', label: function () { return 'Universal Receiver'; } }); </script> <script type="text/x-red" data-template-name="chatbot-universal-receive"> <div class="form-row"> <label for="node-input-bot" style="display:block;width:100%;">Bot configuration <span class="redbot-environment">(development)</span></label> <input type="text" id="node-input-bot" placeholder="Bot"> </div> <div class="form-row" style="margin-top:25px;"> <label for="node-input-botProduction" style="display:block;width:100%;">Bot configuration <span class="redbot-environment">(production)</span></label> <input type="text" id="node-input-botProduction" placeholder="Bot"> </div> <div class="redbot-form-hint"> Bot for <strong>production</strong> will be launched only if the global variable <em>"environment"</em> in <em>settings.js</em> is set to <em>"production"</em>, otherwise will be used the configuration for <strong>development</strong>. </div> </script> <script type="text/x-red" data-help-name="chatbot-universal-receive"><p>The <code>Universal Connector node</code> allows to connect the the <strong>RedBot</strong> ecosystem any kind of messaging service (like email, an SMS gateway, a testing stub, etc). It&#39;s an advanced component, a good kwowledge of <strong>JavaScript</strong> and <em>Promises</em> is required in order to use it.</p> <p>Unlike other receiver nodes the <code>Universal Connector node</code> has an input pin, this is where the external service will send the payload in order to be translated and injected in the <strong>RedBot</strong> flow. The <code>Universal Connector node</code> takes care of providing the chat context, the <em>pass thru</em> and <em>track</em> features, the <em>production</em>/<em>development</em> configuration, etc; while the implementation detail about <strong>how</strong> the incoming message is implemented is left to the user and must be implemented with an [[Extend node|Extend-node]].</p> <p>In order to properly <em>translate</em> an incoming message the implementation in the <code>Universal Connector node</code> must:</p> <ol> <li>Extract a <strong>chatId</strong> from the payload: it&#39;s a unique identifier of the conversation in the connected platform (for example the mobile number for a SMS gateway, the email for an email system, etc)</li> <li>Extract or infer the message type. Some messaging platform support different type of messages (for example Telegrams supports <em>message</em>, <em>audio</em>, <em>photo</em>, etc) while a service like a SMS gateway just support one type of message.</li> <li>Extract the message content it also should (but is not mandatory)</li> <li>Extract the <em>timestamp</em> of the message</li> <li>Extract a <em>messageId</em> to properly reference inbound and outbound messages in the external service timeline</li> </ol> <p>Like explained in [[Extend node|Extend-node]] a connector handles inbound messages with a chain of middlewares: chunck of codes executed sequentially in order to accomplish steps 1 to 3. In order to keep the code simple and maintanable is a good practice to let middleware takes care of detecting and translating one kind of message in each middleware, as soon as a message has been <em>resolved</em> (means that a message <em>type</em> is assigned to the incoming message), the rest of middlewares chain is skipped and the message is injected in the <strong>RedBot</strong>&#39;s flow.</p> <p>For example suppose that a SMS gateway calls the <strong>Node-RED</strong> instance webhook with this payload</p> <pre><code class="lang-javascript">{ sms: { from: &#39;+39347123456&#39;, to: &#39;+39338654321&#39;, id: &#39;_xyz&#39;, text: &#39;Hello there!&#39;, sender_name: &#39;Alan Turing&#39; } } </code></pre> <p>The <em>from</em> key is a good candidate for the <em>chatId</em> while the content of the message is in the <em>text</em> key. the code in the <code>Extend node</code> to handle this</p> <pre><code class="lang-javascript">node.chat.onChatId(payload =&gt; payload.sms.from); node.chat.onMessageId(payload =&gt; payload.sms.id); node.chat.in(message =&gt; { return new Promise((resolve, reject) =&gt; { // check that the incoming payload is an actual &quot;text&quot; message if (message.originalMessage.sms != null &amp;&amp; message.originalMessage.sms.text != &#39;&#39;) { message.payload.type = &#39;message&#39;; message.payload.content = message.originalMessage.sms.text; } resolve(message); }); }); </code></pre> <p>When a new payload arrives to the input pin the <em>chatId</em> value is extracted from the callback <code>.onChatId()</code> (there are callbacks for other information like <em>messageId</em>, <em>timestamp</em>, <em>language</em>, etc) and a <em>neutral</em> version of the <strong>RedBot</strong> message is passed to the middlewares. A <em>neutral message</em> is a regular <strong>RedBot</strong> message (the messages that flow out of a receiver node) except the type and content key are left blank: is up to the developer to fill in this keys using the information contained in <em>originalMessage</em>, all values needed to build a <em>neutral message</em> (<em>chatId</em>, <em>messageId</em>, <em>timestamp</em>, <em>language</em>) are extracted using callbacks. If the callback in <code>.onChatId()</code> fails to extract the <em>chatId</em> then an exception is raised and the message will never go through the middlewares.</p> <p>In the example above there is additional information in the payload, it&#39;s a good practise to use the chat context for storing this information, for example</p> <pre><code class="lang-javascript">node.chat.onChatId(payload =&gt; payload.sms.from); node.chat.onMessageId(payload =&gt; payload.sms.id); node.chat.in(message =&gt; { const chat = message.chat(); // check that the incoming payload is an actual message, if not return and resolve immediately // this will skip to the next middleware if (message.originalMessage.sms == nulll || message.originalMessage.sms.text == null) { return Promise.resolve(message); } // not all context provider return a promise return Promise.resolve(chat.set(&#39;name&#39;, message.originalMessage.sms.sender_name)) .then(() =&gt; { message.payload.type = &#39;message&#39;; message.payload.content = message.originalMessage.sms.text; return message; }); }); </code></pre> <p>Note that a middleware should always return a <em>Promise</em> that returns the received message (passed as parameter of the middleware) and that will be injected in the flow (a warning on the console will log any middleware not returning a Promise).</p> <p>If none of the middlewares in the inbound chain is able to infer a message type and fill in the type and content of the neutral message, then the message is discarded (enable the <em>debug</em> option in the receiver configuration to log the discarded messages in the system console).</p> <p>For a list of all methods available in the <em>msg.chat</em> object see the [[Extend node|Extend-node]].</p> </script> <script type="text/javascript"> RED.nodes.registerType('chatbot-universal-send', { category: 'RedBot Platforms', color: '#FFCC66', defaults: { bot: { value: "", type: 'chatbot-universal-node', required: true }, botProduction: { value: "", type: 'chatbot-universal-node', required: false }, track: { value: false }, passThrough: { value: false }, outputs: { value: 0 } }, inputs: 1, outputs: 1, icon: 'chatbot-sender.png', paletteLabel: 'Universal Out', label: function () { return 'Universal Sender'; }, oneditsave: function() { var track = $('#node-input-track').is(':checked'); var passThrough = $('#node-input-passThrough').is(':checked'); this.outputs = track || passThrough ? 1 : 0; } }); </script> <script type="text/x-red" data-template-name="chatbot-universal-send"> <div class="form-row"> <label for="node-input-bot" style="display:block;width:100%;">Bot configuration <span class="redbot-environment">(development)</span></label> <input type="text" id="node-input-bot" placeholder="Bot"> </div> <div class="form-row" style="margin-top:25px;"> <label for="node-input-botProduction" style="display:block;width:100%;">Bot configuration <span class="redbot-environment">(production)</span></label> <input type="text" id="node-input-botProduction" placeholder="Bot"> </div> <div class="redbot-form-hint"> Bot for <strong>production</strong> will be launched only if the global variable <em>"environment"</em> in <em>settings.js</em> is set to <em>"production"</em>, otherwise will be used the configuration for <strong>development</strong>. </div> <div class="form-row" style="margin-top:25px;"> <label for="node-input-track" style="margin-bottom:0px;">Track</label> <input type="checkbox" value="true" id="node-input-track" style="margin-top:0px;"> <div class="redbot-form-hint"> Track response of the user for this message: any further answer will be redirect to the output pin. </div> <label for="node-input-track" style="margin-bottom:0px;margin-top:15px;">Pass Through</label> <input type="checkbox" value="true" id="node-input-passThrough" style="margin-top:0px;"> <div class="redbot-form-hint"> Forward the message to the output pin after sending (useful to chain messages and keep the right order) </div> </div> </script> <script type="text/x-red" data-help-name="chatbot-universal-send"> <p>Output node for Universal connector.</p> </script>