diff --git a/docs/readme-ciscojabber.md b/docs/readme-ciscojabber.md
new file mode 100644
index 000000000..9cc55e495
--- /dev/null
+++ b/docs/readme-ciscojabber.md
@@ -0,0 +1,344 @@
+# BotKit Starter for Cisco Jabber #
+Botkit is designed to ease the process of designing and running useful, creative bots that live inside Cisco Jabber.
+Botkit features a comprehensive set of tools to deal with Cisco Jabber, and allows developers to build interactive bots and applications that send and receive messages just like real humans.
+This document covers the Cisco Jabber-specific implementation details only.
+
+## Getting Started ##
+- Install Botkit
+- Ask admin to create a Jabber user for the bot in either Cisco IM&Presence Server (on-premise deployment) or Cisco Webex Messenger (cloud deployment), then get the jid and password from the admin.
+Jabber bots can send and receive messages, and in many cases, appear alongside their human counterparts as normal Jabber users.
+
+## Working with Cisco Jabber ##
+Cisco Jabber bot uses node-xmpp to connect to Cisco Unified IM & Presence server or Cisco WebEx Messenger and can only use the password authentication methods. The bot is relying on starndard XMPP protocol to send/receive messages. You can refer to [RFC6120](https://tools.ietf.org/html/rfc6120) and [RFC6121](https://tools.ietf.org/html/rfc6121) to learn more about how to connect to XMPP server and how to create the XMPP message stanza.
+
+Cisco Jabber bot can take part in 1-1 chat or group chat conversations and can provide information or other services for the conversations. It’s better to use persist store for the Botkit controller, otherwise when restart the bot, it will lose the group information and cannot received the message from a group chat.
+
+The full code for a simple Cisco Jabber bot is as below and you can find it in ../examples/jabber_bot.js:
+
+~~~ javascript
+ const Botkit = require('./lib/JabberBot.js');
+ var controller = Botkit({
+ json_file_store: './bot_store/'
+ });
+ var bot = controller.spawn({
+ client: {
+ jid: 'xx@domain.com',
+ password: *,
+ host: "hostname.domain.com",
+ port: 5222
+ }
+ });
+ controller.hears(['hello'], ['direct_mention', 'direct_message'], function (bot, message) {
+ bot.reply(message, 'Hi');
+ });
+ controller.on('direct_mention', function (bot, message) {
+ bot.reply(message, 'You mentioned me in a group and said, "' + message.text + '"');
+ });
+ controller.on('direct_message', function (bot, message) {
+ bot.reply(message, 'I got your direct message. You said, "' + message.text + '"');
+ });
+~~~
+
+## Bot Options ##
+When spawn bot from the Botkit controller, there are several options available.
+
+| Argument | Description
+|--- |---
+| jid | Jid of the jabber bot
+| password | Password of the jabber bot
+| host | Host of the Cisco Unified IM & Presence server, not neccessary for bot of Cisco WebEx Messenger
+| port | Port of the Cisco Unified IM & Presence server, not neccessary for bot of Cisco WebEx Messenger
+
+## Jabber Specific Events ##
+Jabber support the following events
+
+| Event | Description
+|--- |---
+| direct_message | Bot has received a direct message from a 1-1 chat
+| direct_mention | Bot has received a message from a group chat and is mentioned by the message sender
+| self_message | Bot has received a message it sent
+
+## Message Formatting ##
+Cisco Jabber bot supports both plain text message and rich text message.
+Below is an example for plain text message.
+~~~ javascript
+ bot.reply(message, 'Hello');
+~~~
+Below is an example for rich text message. Jabber Bots Developer needs to compose the whole stanza for the message. He can create his own UI element to replace the UI part inside the body element in this example.
+Notes: Below example just for show element clearly. It’s better to remove all empty space in the stanza.
+
+~~~ javascript
+ let reply_message = {};
+ let to = message.user;
+ let type = message.group ? 'groupchat' : 'chat';
+ let body = 'html demo(only for Jabber Windows now)';
+ reply_message.text = body;
+ reply_message.stanza = xml`${body}
+
+
+
+
+ ${body}
+
+
+
+
+
+ `;
+ bot.reply(message, reply_message);
+~~~
+
+Cisco Jabber can support all HTML5 elements and bootstrap 3.2 CSS style. However, Bot developer cannot inject any java-script code into the rich text message because of the security reason. In addition, Cisco Jabber introduces a new attribute robot-type for the button element to enhance the interaction between Jabber and the bot.
+Below is a short summary of the allow value and description. More detail and example usage can be found in later section.
+
+| Value | Description
+|--- |---
+| robot-button | Send back a message
+| robot-openlink | Open URL
+| robot-executecommand | Execute predefined command in Jabber, like start group chat, start conf call and start instant meeting
+| robot-submit | Send back a json message with all input name and value in a HTML form.
+
+
+
+- When value is robot-button, when user clicks the button, Jabber will send back a message defined in the attribute robot-message in the same element. Below is an example
+
+~~~ javascript
+ let reply_message = {};
+ let to = message.user;
+ let type = message.group ? 'groupchat' : 'chat';
+ let body = 'robot-button demo(only for Jabber Windows)';
+ reply_message.text = body;
+ reply_message.stanza = xml`${body}
+
${body}
`;
+ bot.reply(message, reply_message);
+~~~
+
+All the below buttons needs to be putted in a form.
+
+- When value is robot-openlink, when user clicks the button, cisco jabber will find an element name with openlink in the form, and open URL defined in value of the element name with openlink. Below is an example
+
+~~~ javascript
+ let reply_message = {};
+ let to = message.user;
+ let type = message.group ? 'groupchat' : 'chat';
+ let body = 'robot-openlink demo(only for Jabber Windows)';
+ reply_message.text = body;
+ reply_message.stanza = xml`${body}
+
+
${body}
+
+
+ `;
+ bot.reply(message, reply_message);
+~~~
+
+- When value is robot-executecommand, when user clicks the button, cisco jabber will find an element name with command in the form, and execute command defined in value of the element name with command.
+Jabber provide 3 kinds of command as below examples to start group chat, start an audio/video conference and start an instant Webex meeting.
+startgroupchat:john@cisco.com;mick@cisco.com
+startconference:john@cisco.com;mick@cisco.com
+startmeeting:john@cisco.com;mick@cisco.com
+Below is an example, jabber need the jid to execute the related action, generally we can extract the jid from the message received from the user, function extractMentionJids is used to help extract @mention jid from the message.
+
+~~~ javascript
+ function extractMentionJids(message) {
+ let direct_mention_reg = /href="xmpp:\s?(\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+)\s?"/ig;
+ let email_reg = /\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+/i;
+ let match = message.stanza.toString().match(direct_mention_reg);
+ let mention_jids = [];
+ if (match) {
+ for (let i = 0; i < match.length; i++) {
+ let jid_match = match[i].match(email_reg);
+ if (jid_match) {
+ let jid = jid_match[0];
+ if (jid != bot.client_jid) {
+ mention_jids.push(jid);
+ }
+ }
+ }
+ }
+ return mention_jids;
+ }
+
+ let reply_message = {};
+ let to = message.user;
+ let type = message.group ? 'groupchat' : 'chat';
+ let mention_jids = extractMentionJids(message);
+ let mentionJids = "";
+ for (let i = 0; i < mention_jids.length; i++) {
+ mentionJids += mention_jids[i];
+ mentionJids += ";";
+ }
+
+ let body = 'robot-executecommand demo(only for Jabber Windows)';
+ reply_message.text = body;
+ reply_message.stanza = xml`${body}
+
+
${body}
+
+
+
+
+
+
+
+
+ `;
+ bot.reply(message, reply_message);
+~~~
+
+
+- When value is robot-submit, click the button, cisco jabber will find all element name in the form, and combine all the input into a JSON message and then send it back to the bot.
+Below is an example
+
+~~~ javascript
+ let reply_message = {};
+ let to = message.user;
+ let type = message.group ? 'groupchat' : 'chat';
+
+ let body = 'robot-submit demo(only for Jabber Windows)';
+ reply_message.text = body;
+ reply_message.stanza = xml`${body}
+
+
Please enter the meeting information:
+
+
+ `;
+
+ bot.startConversation(message, function (err, convo) {
+ if (!err) {
+ convo.ask(reply_message, function (response, convo) {
+ try {
+ if (response.from_jid == bot.client_jid) {
+ return;
+ }
+ let query = JSON.parse(response.text);
+
+ let replay_meeting_info = "You submit the following information:";
+ replay_meeting_info += "Required Attendees:" + query.required_attendees + ";";
+ replay_meeting_info += "Subject:" + query.subject + ";";
+ replay_meeting_info += "Body:" + query.body + ";";
+ replay_meeting_info += "Topics:" + query.topics + ";";
+ replay_meeting_info += "Location:" + query.location + ";";
+ replay_meeting_info += "Start:" + query.start + ";";
+ replay_meeting_info += "End:" + query.end + "";
+ bot.reply(message, replay_meeting_info);
+ convo.stop();
+ }
+ catch (err) {
+ console.log(err.message);
+ convo.stop();
+ }
+ });
+ }
+ });
+~~~
+
+## Extensible Messaging and Presence Protocol – API for jabber bot ##
+Jabber bot uses node-xmpp to connect to Cisco Unified IM & Presence server or Cisco WebEx Messenger and can only use the password authentication methods. The bot is relying on starndard XMPP protocol to send/receive messages. You can refer to [RFC6120](https://tools.ietf.org/html/rfc6120) and [RFC6121](https://tools.ietf.org/html/rfc6121) to learn more about how to connect to XMPP server and how to create the XMPP message stanza.
+Below is an example for an xmpp message stanza with HTML payload. The bot developer can use this kind of message stanza to implement the functions.
+
+```html
+
+ Art thou not Romeo, and a Montague?
+
+
+
+Art thou not Romeo, and a Montague?
+
+
+
+```
+JabberBot uses node-simple-xmpp to build connection and exchange message with Cisco Unified IM & Presence server or Cisco WebEx Messenger. If you would like to have more functions from XMPP, you can integrate more function into jabber_bot.js with the help of node-simple-xmpp and the underlying node-xmpp like what we have done in the JabberGroupManager.js. Just handle stanza and underlying events, the bot can have all the functions that a xmpp client can do.
+
+
+
diff --git a/docs/readme.md b/docs/readme.md
index e95f7093d..2ccac3fb3 100755
--- a/docs/readme.md
+++ b/docs/readme.md
@@ -18,6 +18,7 @@ it is ready to be connected to a stream of incoming messages. Currently, Botkit
* [Slack Incoming Webhooks](http://api.slack.com/incoming-webhooks)
* [Slack Slash Commands](http://api.slack.com/slash-commands)
* [Cisco Spark Webhooks](https://developer.ciscospark.com/webhooks-explained.html)
+* [Cisco Jabber XMPP Protocol](https://tools.ietf.org/html/rfc6120)
* [Microsoft Teams](https://msdn.microsoft.com/en-us/microsoft-teams/bots)
* [Facebook Messenger Webhooks](https://developers.facebook.com/docs/messenger-platform/implementation)
* [Twilio SMS](https://www.twilio.com/console/sms/dashboard)
@@ -27,6 +28,7 @@ it is ready to be connected to a stream of incoming messages. Currently, Botkit
Read more about
[connecting your bot to Slack](readme-slack.md#connecting-your-bot-to-slack),
[connecting your bot to Cisco Spark](readme-ciscospark.md#getting-started),
+[connecting your bot to Cisco Jabber](readme-ciscojabber.md#getting-started),
[connecting your bot to Microsoft Teams](readme-teams.md#getting-started),
[connecting your bot to Facebook](readme-facebook.md#getting-started),
[connecting your bot to Twilio](readme-twilioipm.md#getting-started),
@@ -92,6 +94,9 @@ Due to the multi-channel, multi-user nature of Slack, Botkit does additional fil
Similarly, bots in Cisco Spark will receive `direct_message` events to indicate a message has been sent directly to the bot, while `direct_mention` indicates that the bot has been mentioned in a multi-user channel. Several other Spark-specific events will also fire. [List of Cisco Spark-specific Events](readme-ciscospark.md#spark-specific-events)
+Bots in Cisco Jabber will receive `direct_message` to indicate a message has been sent directly to the bot from a 1-1 chat, while `direct_mention` indicates
+that the bot has received a message from a group chat and is mentioned by the message sender. [List of Cisco Jabber-Specific Events](readme-ciscojabber.md#jabber-specific-events)
+
Twilio IPM bots can also exist in a multi-channel, multi-user environment. As a result, there are many additional events that will fire. In addition, Botkit will filter some messages, so that the bot will not receive it's own messages or messages outside of the channels in which it is present.
[List of Twilio IPM-specific Events](readme-twilioipm.md#twilio-ipm-specific-events)
@@ -1025,6 +1030,7 @@ Our [starter kits](readme-starterkits.md) all include a customizable Express.js
* [Web and Apps](readme-web.md)
* [Slack](readme-slack.md)
* [Cisco Spark](readme-ciscospark.md)
+ * [Cisco Jabber](readme-ciscojabber.md)
* [Microsoft Teams](readme-teams.md)
* [Facebook Messenger](readme-facebook.md)
* [Twilio SMS](readme-twiliosms.md)
diff --git a/examples/jabber_bot.js b/examples/jabber_bot.js
new file mode 100644
index 000000000..bc97f85f8
--- /dev/null
+++ b/examples/jabber_bot.js
@@ -0,0 +1,123 @@
+const Botkit = require('../lib/Botkit.js');
+const xml = require('@xmpp/xml');
+
+
+var controller = Botkit.jabberbot({
+ json_file_store: './jabberbot/'
+});
+
+var bot = controller.spawn({
+ client: {
+ jid: 'xx@domain.com',
+ password: 'xxxxxx',
+ host: "host.domain.com",
+ port: 5222
+ }
+});
+
+function ExtractMentionJids(message) {
+ let direct_mention_reg = /href="xmpp:\s?(\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+)\s?"/ig;
+ let email_reg = /\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+/i;
+ let match = message.stanza.toString().match(direct_mention_reg);
+ let mention_jids = [];
+ if (match) {
+ for (let i = 0; i < match.length; i++) {
+ let jid_match = match[i].match(email_reg);
+ if (jid_match) {
+ let jid = jid_match[0];
+ if (jid != bot.client_jid) {
+ mention_jids.push(jid);
+ }
+ }
+ }
+ }
+ return mention_jids;
+}
+
+function toUTCDateTimeString(date) {
+ var yyyy = date.getUTCFullYear();
+ var mm = date.getUTCMonth() < 9 ? "0" + (date.getUTCMonth() + 1) : (date.getUTCMonth() + 1); // getMonth() is zero-based
+ var dd = date.getUTCDate() < 10 ? "0" + date.getUTCDate() : date.getUTCDate();
+ var hh = date.getUTCHours() < 10 ? "0" + date.getUTCHours() : date.getUTCHours();
+ var min = date.getUTCMinutes() < 10 ? "0" + date.getUTCMinutes() : date.getUTCMinutes();
+ var ss = date.getUTCSeconds() < 10 ? "0" + date.getUTCSeconds() : date.getUTCSeconds();
+ return "".concat(yyyy).concat('-').concat(mm).concat('-').concat(dd).concat('T').concat(hh).concat(':').concat(min).concat(':').concat(ss);
+};
+
+
+controller.hears(['robot-button'], ['direct_mention', 'self_message', 'direct_message'], function (bot, message) {
+ let reply_message = {};
+ let to = message.user;
+ let type = message.group ? 'groupchat' : 'chat';
+
+ let body = 'robot-button demo(only for Jabber Windows)';
+ reply_message.text = body;
+ reply_message.stanza = xml`${body}
${body}
`;
+ bot.reply(message, reply_message);
+});
+
+controller.hears(['robot-openlink'], ['direct_mention', 'self_message', 'direct_message'], function (bot, message) {
+ let reply_message = {};
+ let to = message.user;
+ let type = message.group ? 'groupchat' : 'chat';
+
+ let body = 'robot-openlink demo(only for Jabber Windows)';
+ reply_message.text = body;
+ reply_message.stanza = xml`${body}
${body}
`;
+ bot.reply(message, reply_message);
+});
+
+controller.hears(['robot-executecommand'], ['direct_mention', 'self_message', 'direct_message'], function (bot, message) {
+ let reply_message = {};
+ let to = message.user;
+ let type = message.group ? 'groupchat' : 'chat';
+ let mention_jids = ExtractMentionJids(message);
+ let mentionJids = "";
+ for (let i = 0; i < mention_jids.length; i++) {
+ mentionJids += mention_jids[i];
+ mentionJids += ";";
+ }
+
+ let body = 'robot-executecommand demo(only for Jabber Windows)';
+ reply_message.text = body;
+ reply_message.stanza = xml`${body}
${body}
`;
+ bot.reply(message, reply_message);
+});
+
+controller.hears(['robot-submit'], ['direct_mention', 'self_message', 'direct_message'], function (bot, message) {
+ let reply_message = {};
+ let to = message.user;
+ let type = message.group ? 'groupchat' : 'chat';
+
+ let body = 'robot-submit demo(only for Jabber Windows)';
+ reply_message.text = body;
+ reply_message.stanza = xml`${body}
Please enter the meeting information:
`;
+
+ bot.startConversation(message, function (err, convo) {
+ if (!err) {
+ convo.ask(reply_message, function (response, convo) {
+ try {
+ if (response.from_jid == bot.client_jid) {
+ return;
+ }
+ let query = JSON.parse(response.text);
+
+ let replay_meeting_info = "You submit the following information:";
+ replay_meeting_info += "Required Attendees:" + query.required_attendees + ";";
+ replay_meeting_info += "Subject:" + query.subject + ";";
+ replay_meeting_info += "Body:" + query.body + ";";
+ replay_meeting_info += "Topics:" + query.topics + ";";
+ replay_meeting_info += "Location:" + query.location + ";";
+ replay_meeting_info += "Start:" + query.start + ";";
+ replay_meeting_info += "End:" + query.end + "";
+ bot.reply(message, replay_meeting_info);
+ convo.stop();
+ }
+ catch (err) {
+ console.log(err.message);
+ convo.stop();
+ }
+ });
+ }
+ });
+});
diff --git a/lib/Botkit.js b/lib/Botkit.js
index 69539793e..bf06c9ee9 100755
--- a/lib/Botkit.js
+++ b/lib/Botkit.js
@@ -6,6 +6,7 @@ var TwilioSMSbot = require(__dirname + '/TwilioSMSBot.js');
var BotFrameworkBot = require(__dirname + '/BotFramework.js');
var SparkBot = require(__dirname + '/CiscoSparkbot.js');
var ConsoleBot = require(__dirname + '/ConsoleBot.js');
+var JabberBot = require(__dirname + '/JabberBot.js');
var WebBot = require(__dirname + '/Web.js');
module.exports = {
@@ -18,6 +19,7 @@ module.exports = {
botframeworkbot: BotFrameworkBot,
teamsbot: require(__dirname + '/Teams.js'),
consolebot: ConsoleBot,
+ jabberbot: JabberBot,
socketbot: WebBot,
anywhere: WebBot,
};
diff --git a/lib/JabberBot.js b/lib/JabberBot.js
new file mode 100644
index 000000000..25c0c74c0
--- /dev/null
+++ b/lib/JabberBot.js
@@ -0,0 +1,257 @@
+var Botkit = require(__dirname + '/CoreBot.js');
+const Stanza = require('node-xmpp-client').Stanza;
+const GroupManager = require('./JabberGroupManager.js')
+
+function JabberBot(configuration) {
+ // Create a core botkit bot
+ var controller = Botkit(configuration || {});
+
+
+ function toUTCDateTimeString(date) {
+ var yyyy = date.getUTCFullYear();
+ var mm = date.getUTCMonth() < 9 ? "0" + (date.getUTCMonth() + 1) : (date.getUTCMonth() + 1); // getMonth() is zero-based
+ var dd = date.getUTCDate() < 10 ? "0" + date.getUTCDate() : date.getUTCDate();
+ var hh = date.getUTCHours() < 10 ? "0" + date.getUTCHours() : date.getUTCHours();
+ var min = date.getUTCMinutes() < 10 ? "0" + date.getUTCMinutes() : date.getUTCMinutes();
+ var ss = date.getUTCSeconds() < 10 ? "0" + date.getUTCSeconds() : date.getUTCSeconds();
+ return "".concat(yyyy).concat('-').concat(mm).concat('-').concat(dd).concat('T').concat(hh).concat(':').concat(min).concat(':').concat(ss);
+ };
+
+ controller.middleware.format.use(function (bot, message, platform_message, next) {
+ // clone the incoming message
+ for (var k in message) {
+ platform_message[k] = message[k];
+ }
+ next();
+ });
+
+ // customize the bot definition, which will be used when new connections
+ // spawn!
+ controller.defineBot(function (botkit, config) {
+ var xmpp = require('simple-xmpp');
+
+ var bot = {
+ type: 'xmpp',
+ botkit: botkit,
+ config: config || {},
+ client_jid: config.client.jid,
+ utterances: botkit.utterances,
+ };
+
+ GroupManager(config, xmpp, bot, controller);
+
+ function request_roster() {
+ let roster_stanza = new Stanza('iq', { 'from': config.client, 'type': 'get'});
+ roster_stanza.c('query', { xmlns: 'jabber:iq:roster'});
+ xmpp.conn.send(roster_stanza);
+ }
+
+ xmpp.on('online', function (data) {
+ console.log(toUTCDateTimeString(new Date()) + ':Connected with JID: ' + data.jid.user);
+ console.log('Yes, I\'m connected!');
+ request_roster();
+
+ // send whitespace to keep the connection alive
+ // and prevent timeouts
+ setInterval(function () {
+ xmpp.conn.send(' ');
+ }, 1800000);
+ });
+
+ xmpp.on('close', function () {
+ console.log(toUTCDateTimeString(new Date()) + ':connection has been closed!');
+ process.exit();
+ });
+
+ xmpp.on('error', function (err) {
+ console.log(toUTCDateTimeString(new Date()) + ":" + err);
+ process.exit();
+ });
+
+ xmpp.on('subscribe', function (from) {
+ xmpp.acceptSubscription(from);
+ console.log(toUTCDateTimeString(new Date()) + ':accept subscribe from:' + from);
+ controller.trigger('subscribe', [bot, from]);
+ });
+
+ xmpp.on('unsubscribe', function (from) {
+ console.log(toUTCDateTimeString(new Date()) + ':accept unsubscribe from:' + from);
+ xmpp.acceptUnsubscription(from);
+ });
+
+ function findBotJid(jid) {
+ return jid === bot.client_jid;
+ }
+
+ function IsBotMentioned(message)
+ {
+ let mention_jids = extractMentionJids(message);
+ if (mention_jids.find(findBotJid))
+ {
+ return true;
+ }
+ return false;
+ }
+
+ function extractMentionJids(message) {
+ let direct_mention_reg = /href="xmpp:\s?(\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+)\s?"/ig;
+ let email_reg = /\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+/i;
+ let match = message.stanza.toString().match(direct_mention_reg);
+ let mention_jids = [];
+ if (match) {
+ for (let i = 0; i < match.length; i++) {
+ let jid_match = match[i].match(email_reg);
+ if (jid_match) {
+ let jid = jid_match[0];
+ mention_jids.push(jid);
+ }
+ }
+ }
+ return mention_jids;
+ }
+
+ controller.on('message_received', function (bot, message) {
+ if (message.group == false)
+ {
+ if (message.user === bot.client_jid) {
+ controller.trigger('self_message', [bot, message]);
+ return false;
+ } else {
+ controller.trigger('direct_message', [bot, message]);
+ return false;
+ }
+ }
+ else
+ {
+ if (IsBotMentioned(message))
+ {
+ if (bot.client_jid == message.from_jid)
+ {
+ controller.trigger('self_message', [bot, message]);
+ }
+ else
+ {
+ controller.trigger('direct_mention', [bot, message]);
+ }
+ return false;
+ }
+ }
+ });
+
+ xmpp.on('stanza', function (stanza) {
+ if (stanza.is('message'))
+ {
+ if (stanza.attrs.type == 'chat') {
+ var body = stanza.getChild('body');
+ if (body) {
+ var message = body.getText();
+ var from = stanza.attrs.from;
+ var id = from.split('/')[0];
+
+ var xmpp_message = {};
+ xmpp_message.user = from;
+ xmpp_message.text = message;
+ xmpp_message.group = false;
+ xmpp_message.stanza = stanza;
+ xmpp_message.channel = 'chat',
+ controller.ingest(bot, xmpp_message, null);
+ }
+ }
+ else if (stanza.attrs.type == 'groupchat')
+ {
+ var body = stanza.getChild('body');
+ if (body) {
+ let message = body.getText();
+ let from = stanza.attrs.from;
+ let from_split = from.split('/');
+ let conference = from_split[0];
+ let from_jid = null;
+ if (from_split.length > 1)
+ from_jid = from_split[1];
+ if (!from_jid)
+ return;
+
+ let history_reg = /xmlns="http:\/\/www.jabber.com\/protocol\/muc#history"/i;
+ if (history_reg.test(stanza.toString()))
+ return false;
+
+ var xmpp_message = {};
+ xmpp_message.user = conference;
+ xmpp_message.text = message;
+ xmpp_message.group = true;
+ xmpp_message.channel = 'groupchat';
+ xmpp_message.from_jid = from_jid;
+ xmpp_message.stanza = stanza;
+ controller.ingest(bot, xmpp_message, null);
+ }
+ }
+ }
+ });
+
+ bot.startConversation = function (message, cb) {
+ botkit.startConversation(this, message, cb);
+ };
+
+ bot.createConversation = function (message, cb) {
+ botkit.createConversation(this, message, cb);
+ };
+
+ bot.send = function (message, cb) {
+ if (message.stanza)
+ {
+ xmpp.conn.send(message.stanza);
+ }
+ else
+ {
+ xmpp.send(message.user, message.text, message.group);
+ }
+
+ if (cb) {
+ cb();
+ }
+ };
+
+ bot.reply = function (src, resp, cb) {
+ var msg = {};
+
+ if (typeof (resp) == 'string') {
+ msg.text = resp;
+ } else {
+ msg = resp;
+ }
+ msg.user = src.user;
+ msg.channel = src.channel;
+ msg.group = src.group;
+
+ bot.say(msg, cb);
+ };
+
+ bot.findConversation = function (message, cb) {
+ botkit.debug('CUSTOM FIND CONVO', message.user, message.channel);
+ for (var t = 0; t < botkit.tasks.length; t++) {
+ for (var c = 0; c < botkit.tasks[t].convos.length; c++) {
+ if (
+ botkit.tasks[t].convos[c].isActive() &&
+ botkit.tasks[t].convos[c].source_message.user == message.user &&
+ botkit.tasks[t].convos[c].source_message.channel == message.channel
+ ) {
+ botkit.debug('FOUND EXISTING CONVO!');
+ cb(botkit.tasks[t].convos[c]);
+ return;
+ }
+ }
+ }
+
+ cb();
+ };
+
+ xmpp.connect(config.client);
+ return bot;
+ });
+
+ controller.startTicking();
+
+ return controller;
+}
+
+module.exports = JabberBot;
diff --git a/lib/JabberGroupManager.js b/lib/JabberGroupManager.js
new file mode 100644
index 000000000..cbad23647
--- /dev/null
+++ b/lib/JabberGroupManager.js
@@ -0,0 +1,229 @@
+const caps = require('node-xmpp-caps');
+const Stanza = require('node-xmpp-client').Stanza;
+const MD5 = require('md5');
+
+function JabberGroupManager(config, xmpp, bot, controller) {
+ var group_manager = {
+ config: config || {},
+ };
+
+ var joinedRoomsPasswordMap = new Map();
+ var joinedPersistRoomsId = new Set();
+ var joinedRoomsId = new Set();
+
+ const bot_caps_node_addr = 'http://protocols.cisco.com/jabber-bot';
+ var bot_caps = createCapsNode(bot_caps_node_addr);
+
+ xmpp.on('online', function (data) {
+ publishCapabilities();
+ joinPresetRooms();
+ });
+
+ xmpp.on('stanza', function (stanza) {
+ if (stanza.is('iq')) {
+ handleCapabilityIq(stanza);
+ handleRoomDiscoQueryIq(stanza);
+ }
+ else if (stanza.is('message')) {
+ handleInviteMessage(stanza);
+ }
+ else if (stanza.is('presence')) {
+ handleMembershipPresence(stanza);
+ }
+ });
+
+ function createCapsNode(caps_node_addr) {
+ var bot_caps = new caps.Caps(caps_node_addr);
+ bot_caps.addIdentity('client', 'bot', 'Cisco Jabber Bot');
+ bot_caps.addFeature('http://jabber.org/protocol/caps');
+ bot_caps.addFeature('http://jabber.org/protocol/disco#info');
+ bot_caps.addFeature('http://jabber.org/protocol/disco#items');
+ bot_caps.addFeature('http://jabber.org/protocol/muc');
+ return bot_caps;
+ };
+
+ function publishCapabilities() {
+ let presence_stanza = new Stanza('presence');
+ presence_stanza.cnode(bot_caps.toCapsNode());
+ xmpp.conn.send(presence_stanza);
+ };
+
+ function joinPresetRooms() {
+ if (config.group && config.group.rooms) {
+ for (let i = 0; i < config.group.rooms.length; i++) {
+ let room_id = config.group.rooms[i].id;
+ let password = config.group.rooms[i].password;
+ if (!joinedRoomsId.has(getBareRoomId(room_id))) {
+ joinedRoomsPasswordMap.set(getBareRoomId(room_id), password);
+ xmpp.join(room_id, password);
+ }
+ }
+ }
+
+ controller.storage.teams.all(function (err, rooms) {
+ for (let i = 0; i < rooms.length; i++) {
+ let room = rooms[i];
+ if (room.type === 'joined_persist_rooms') {
+ let room_id = room.room_id + "/" + bot.client_jid;
+ let password = room.password;
+ if (!joinedRoomsId.has(getBareRoomId(room_id))) {
+ joinedRoomsPasswordMap.set(getBareRoomId(room_id), password);
+ xmpp.join(room_id, password);
+ }
+ }
+ }
+ });
+ };
+
+ function handleCapabilityIq(stanza) {
+ if (stanza.type === 'get') {
+ let query = stanza.getChild('query', 'http://jabber.org/protocol/disco#info');
+ if (query) {
+ let caps_node = query.attrs.node;
+ if (caps_node) {
+ let caps_node_addr = caps_node.split('#')[0];
+ if (bot_caps_node_addr == caps_node_addr) {
+ let from = stanza.attrs.from;
+ let to = stanza.attrs.to;
+ let result = bot_caps.toQueryNode();
+ let disco_id = stanza.id ? stanza.id : 'disco1';
+ let iq_stanza = new Stanza('iq', { 'id': disco_id, 'type': 'result', 'to': from, 'from': to });
+ iq_stanza.cnode(bot_caps.toQueryNode());
+ xmpp.conn.send(iq_stanza);
+ }
+ }
+ }
+ }
+ };
+
+ function handleRoomDiscoQueryIq(stanza) {
+ if (stanza.attrs.type !== 'result')
+ return;
+ if (stanza.attrs.id !== 'room_disco1')
+ return;
+ let jid = stanza.attrs.to;
+ if (!jid)
+ return;
+ let bareJid = jid.split('/')[0];
+ if (bareJid !== bot.client_jid)
+ return;
+
+ let query = stanza.getChild('query', 'http://jabber.org/protocol/disco#info');
+ if (!query)
+ return;
+
+ let room_id = stanza.attrs.from;
+ if (!room_id)
+ return;
+
+ let features = query.getChildren('feature');
+ for (let i = 0; i < features.length; i++)
+ {
+ let feature = features[i];
+ if (feature.attrs.var === 'persistent' ||
+ feature.attrs.var === 'muc_persistent')
+ {
+ joinedPersistRoomsId.add(getBareRoomId(room_id));
+ let password = joinedRoomsPasswordMap.get(getBareRoomId(room_id));
+ saveJoinPersistRooms(getBareRoomId(room_id), password)
+ return;
+ }
+ }
+ };
+
+ function handleInviteMessage(stanza) {
+ let muc_message = stanza.getChild('x', 'http://jabber.org/protocol/muc#user');
+ if (!muc_message)
+ return;
+
+ let invite_node = muc_message.getChild('invite');
+ if (!invite_node)
+ return;
+
+ let password = undefined;
+ let password_node = muc_message.getChild('password');
+ if (password_node)
+ password = password_node.getText();
+ let room_id = stanza.attrs.from + "/" + bot.client_jid;
+
+ if (!joinedRoomsId.has(getBareRoomId(room_id))) {
+ joinedRoomsPasswordMap.set(getBareRoomId(room_id), password);
+ xmpp.join(room_id, password);
+ }
+ };
+
+ function handleMembershipPresence(stanza) {
+ let group_type = stanza.getChild('x', 'http://jabber.org/protocol/muc#user');
+ if (!group_type)
+ return;
+
+ let item = group_type.getChild('item');
+ if (!item)
+ return;
+
+ let jid = item.attrs.jid;
+ if (!jid)
+ return;
+ let bareJid = jid.split('/')[0];
+ if (bareJid !== bot.client_jid)
+ return;
+
+ let room_id = stanza.attrs.from;
+ if (item.attrs.role === 'none') {
+ let status = group_type.getChild('status');
+ if (status && (status.attrs.code === '321' || status.attrs.code === '307')) {
+ joinedRoomsId.delete(getBareRoomId(room_id));
+ joinedRoomsPasswordMap.delete(getBareRoomId(room_id));
+ if (joinedPersistRoomsId.has(getBareRoomId(room_id))) {
+ joinedPersistRoomsId.delete(getBareRoomId(room_id));
+ saveLeavePersistRooms(getBareRoomId(room_id));
+ }
+ }
+ }
+ else if (item.attrs.role === 'participant') {
+ joinedRoomsId.add(getBareRoomId(room_id));
+
+ sendRoomDiscoQueryIq(room_id, jid);
+ }
+ };
+
+ function sendRoomDiscoQueryIq(room_id, jid) {
+ let from = jid;
+ let to = room_id.split('/')[0];
+ let disco_id = 'room_disco1';
+ let node = to.split('@')[1];
+ let iq_stanza = new Stanza('iq', { 'id': disco_id, 'type': 'get', 'to': to, 'from': from });
+ iq_stanza.c('query', { xmlns: 'http://jabber.org/protocol/disco#info', 'node' : node});
+ xmpp.conn.send(iq_stanza);
+ };
+
+ function saveJoinPersistRooms(room_id, room_password) {
+ let id = MD5(room_id);
+ controller.storage.teams.get(id, function (err, room) {
+ if (!room)
+ {
+ room = {
+ id: id,
+ };
+ }
+ room.room_id = room_id;
+ room.password = room_password;
+ room.type = 'joined_persist_rooms';
+ controller.storage.teams.save(room, function (err, room) {
+ });
+ });
+ }
+
+ function saveLeavePersistRooms(room_id) {
+ let id = MD5(room_id);
+ controller.storage.teams.delete(id, function (err, room) {
+ });
+ }
+
+ function getBareRoomId(room_id)
+ {
+ return room_id.split('/')[0];
+ }
+}
+
+module.exports = JabberGroupManager;
\ No newline at end of file
diff --git a/package.json b/package.json
index 61ddb6462..d74a26122 100644
--- a/package.json
+++ b/package.json
@@ -20,9 +20,12 @@
"localtunnel": "^1.8.2",
"md5": "^2.2.1",
"mustache": "^2.3.0",
+ "node-ews": "^3.2.2",
+ "node-xmpp-caps": "0.0.2",
"promise": "^8.0.0",
"request": "^2.81.0",
"requestretry": "^1.12.0",
+ "simple-xmpp": "^1.3.0",
"twilio": "^2.11.1",
"vorpal": "^1.12.0",
"ware": "^1.3.0",
diff --git a/readme.md b/readme.md
index a18e08a1a..85b277d91 100644
--- a/readme.md
+++ b/readme.md
@@ -18,7 +18,6 @@ Botkit offers everything you need to design, build and operate an app:
Plus, Botkit works with all the NLP services (like Microsoft LUIS and IBM Watson), can use any type of database you want, and runs on almost any hosting platform.
# Install Botkit
-
Botkit is a Node.js module, and works with Node and npm.
### **Botkit Studio**
@@ -160,6 +159,7 @@ controller.middleware.send.use(function(bot, message, next) {
* [Web and Apps](docs/readme-web.md)
* [Slack](docs/readme-slack.md)
* [Cisco Spark](docs/readme-ciscospark.md)
+ * [Cisco Jabber](docs/readme-ciscojabber.md)
* [Microsoft Teams](docs/readme-teams.md)
* [Facebook Messenger](docs/readme-facebook.md)
* [Twilio SMS](docs/readme-twiliosms.md)