Skip to content

Commit

Permalink
Merge pull request #164 from StackStorm/add-msteams
Browse files Browse the repository at this point in the history
Cleanup a bit and use hubot-botframework for MS Teams support
  • Loading branch information
blag authored Mar 29, 2019
2 parents 50e52be + 07da3bd commit efe1f2f
Show file tree
Hide file tree
Showing 12 changed files with 1,684 additions and 92 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
node_modules/

coverage/
npm-debug.log
113 changes: 113 additions & 0 deletions lib/format_data.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ SlackFormatter.prototype.normalizeCommand = function(command) {
return command;
};

SlackFormatter.prototype.normalizeAddressee = function(msg) {
return {
name: msg.message.user.name,
room: msg.message.room
};
};

/*
MattermostFormatter.
*/
Expand Down Expand Up @@ -88,6 +95,48 @@ MattermostFormatter.prototype.normalizeCommand = function(command) {
return command;
};

MattermostFormatter.prototype.normalizeAddressee = function(msg) {
return {
name: msg.message.user.name,
room: msg.message.room
};
};

/*
MSTeamsFormatter
*/
function MSTeamsFormatter(robot) {
this.robot = robot;
}

MSTeamsFormatter.prototype.formatData = function(data) {
this.robot.logger.debug("Got data in formatData: " + JSON.stringify(data));

// Remove starting newlines
data = data.replace(/^\n/g, '');
// Replace single newlines with double newlines
data = data.replace(/([^\n])\n([^\n])/g, "$1\n\n$2");

return data;
};

MSTeamsFormatter.prototype.formatRecepient = function(recepient) {
this.robot.logger.debug("Got recipient in formatRecepient: " + JSON.stringify(recepient));
return recepient;
};

MSTeamsFormatter.prototype.normalizeCommand = function (command) {
this.robot.logger.debug("Got command in normalizeCommand: " + JSON.stringify(command));
return command;
}

MSTeamsFormatter.prototype.normalizeAddressee = function(msg) {
return {
name: msg.message.user.name,
room: msg.message.room
};
};

/*
HipChatFormatter.
*/
Expand All @@ -114,6 +163,13 @@ HipChatFormatter.prototype.normalizeCommand = function(command) {
return command;
};

HipChatFormatter.prototype.normalizeAddressee = function(msg) {
return {
name: msg.message.user.mention_name,
room: msg.message.user.jid
};
};

/*
RocketChatFormatter.
*/
Expand All @@ -138,6 +194,54 @@ RocketChatFormatter.prototype.normalizeCommand = function(command) {
return command;
};

RocketChatFormatter.prototype.normalizeAddressee = function(msg) {
return {
name: msg.message.user.name,
room: msg.message.room
};
};

/*
SparkFormatter.
*/
function SparkFormatter(robot) {
this.robot = robot;

// Limit the size of a message.
this.truncate_length = env.ST2_MAX_MESSAGE_LENGTH;
}

SparkFormatter.prototype.formatData = function(data) {
if (utils.isNull(data)) {
return "";
}
if (this.truncate_length > 0) {
// The ellipsis argument is only to preserve backwards compatibility, as the
// truncate function switched from using '...' (three period characters
// forming and ellipsis) in truncate 1.x to '…' (a single Unicode ellipsis
// character) in truncate 2+.
// Switching to using the new default ellipsis ('…') probably won't break
// anything.
data = truncate(data, this.truncate_length, {ellipsis: '...'});
}
return data;
};

SparkFormatter.prototype.formatRecepient = function(recepient) {
return recepient;
};

SparkFormatter.prototype.normalizeCommand = function(command) {
return command;
};

SparkFormatter.prototype.normalizeAddressee = function(msg) {
return {
name: msg.message.user.name,
room: msg.message.user.room
};
};

/*
DefaultFormatter.
*/
Expand Down Expand Up @@ -172,11 +276,20 @@ DefaultFormatter.prototype.normalizeCommand = function(command) {
return command;
};

DefaultFormatter.prototype.normalizeAddressee = function(msg) {
return {
name: msg.message.user.name,
room: msg.message.room
};
};

var formatters = {
'slack': SlackFormatter,
'matteruser': MattermostFormatter,
'mattermost': MattermostFormatter,
'botframework': MSTeamsFormatter,
'hipchat': HipChatFormatter,
'spark': SparkFormatter,
'rocketchat': RocketChatFormatter,
'default': DefaultFormatter
};
Expand Down
73 changes: 71 additions & 2 deletions lib/post_data.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ function SlackDataPostHandler(robot, formatter) {
SlackDataPostHandler.prototype.postData = function(data) {
var recipient, attachment_color, split_message,
attachment, pretext = "";
var envelope, robot = this.robot;
var envelope,
// We capture robot here so the `sendMessage` closure captures the
// correct `this`
robot = this.robot;

if (data.whisper && data.user) {
recipient = data.user;
Expand Down Expand Up @@ -190,10 +193,54 @@ SlackDataPostHandler.prototype.postData = function(data) {
}
};

/*
MSTeamsPostHandler.
*/
function MSTeamsPostHandler(robot, formatter) {
this.robot = robot;
this.formatter = formatter;
}

MSTeamsPostHandler.prototype.postData = function(data) {
var messages_to_send,
// We capture robot here so the `sendMessage` closure captures the
// correct `this`
robot = this.robot,
split_message = utils.splitMessage(data.message);

if (data.extra && data.extra.botframework) {
robot.logger.warning(util.format('The extra.botframework attribute of aliases is not used yet.'));
}

if (split_message.pretext) {
var text = this.formatter.formatData(split_message.text);
messages_to_send = [split_message.pretext, text];
} else {
messages_to_send = [this.formatter.formatData(data.message)];
}

// We define a recursive closure that calls itself with the next data to send
// after a timeout. This approximates sending synchronous (sequential) HTTP
// requests.
var sendMessage = function (i) {
robot.adapter.send(data.context, messages_to_send[i]);

if (messages_to_send.length > ++i) {
setTimeout(function () { sendMessage(i); }, 300);
}
};

sendMessage(0);

return;
};

/*
MattermostDataPostHandler.
*/
function MattermostDataPostHandler(robot, formatter) {
// We capture robot here so the `sendMessage` closure captures the correct
// `this`
this.robot = robot;
this.formatter = formatter;
}
Expand All @@ -202,18 +249,23 @@ MattermostDataPostHandler.prototype.postData = function(data) {
var recipient, attachment_color, split_message,
attachment, pretext = "";

// If we are supposed to whisper to a single user, use a direct message
if (data.whisper && data.user) {
recipient = data.user;
} else {
} else { // Otherwise, message the channel
recipient = data.channel;
// If we aren't supposed to whisper, then we at least at-mention the user
pretext = (data.user && !data.whisper) ? util.format('@%s: ', data.user) : "";
}

// Use the color specified in the `extra` block
if (data.extra && data.extra.color) {
attachment_color = data.extra.color;
} else {
// Assume success, and use the success color
attachment_color = env.ST2_MATTERMOST_SUCCESS_COLOR;

// Try to detect execution failure and use the failure color instead
if (data.message.indexOf("status : failed") > -1) {
attachment_color = env.ST2_MATTERMOST_FAIL_COLOR;
}
Expand All @@ -222,16 +274,24 @@ MattermostDataPostHandler.prototype.postData = function(data) {
split_message = utils.splitMessage(this.formatter.formatData(data.message));

if (split_message.text) {
// Default values
var content = {
color: attachment_color,
"mrkdwn_in": ["text", "pretext"],
};
// Override the default values with values from `data.extra.mattermost`
if (data.extra && data.extra.mattermost) {
for (var attrname in data.extra.mattermost) { content[attrname] = data.extra.mattermost[attrname]; }
}

// We capture robot here so the `sendMessage` closure captures the correct
// `this`
var robot = this.robot;
var chunks = split_message.text.match(/[\s\S]{1,3800}/g);

// We define a recursive closure that calls itself with the next data to
// send after a timeout. This approximates sending synchronous (sequential)
// HTTP requests.
var sendChunk = function (i) {
content.text = chunks[i];
content.fallback = chunks[i];
Expand All @@ -244,6 +304,10 @@ MattermostDataPostHandler.prototype.postData = function(data) {
attachment = {
room: recipient,
attachments: content.attachments ? content.attachments : content,
// There is likely a bug here - `split_message.text` being a true-y
// value does not imply that `split_message.pretext` is also non-empty,
// but we unconditionally set `text` to
// `pretext + split_message.pretext` on the first message
text: i === 0 ? pretext + split_message.pretext : null
};
robot.emit('slack-attachment', attachment);
Expand All @@ -267,6 +331,9 @@ function HipchatDataPostHandler(robot, formatter) {

HipchatDataPostHandler.prototype.postData = function(data) {
var recipient, split_message, formatted_message,
// Special handler to try and figure out when a hipchat message
// is a whisper:
whisper = data.whisper && (data.channel.indexOf('@') > -1),
pretext = "";

recipient = data.channel;
Expand Down Expand Up @@ -320,6 +387,7 @@ SparkDataPostHandler.prototype.postData = function(data) {
}

recipient = this.formatter.formatRecepient(recipient);
// TODO: Pull attributes from data.extra.spark before pulling them from data.extra
recipient.extra = data.extra;
text += this.formatter.formatData(data.message);

Expand Down Expand Up @@ -429,6 +497,7 @@ DefaultFormatter.prototype.postData = function(data) {

var dataPostHandlers = {
'slack': SlackDataPostHandler,
'botframework': MSTeamsPostHandler,
'matteruser': MattermostDataPostHandler,
'mattermost': MattermostDataPostHandler,
'hipchat': HipchatDataPostHandler,
Expand Down
24 changes: 0 additions & 24 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,37 +110,13 @@ function enable2FA(action_alias) {
action_alias.extra.security.twofactor !== undefined;
}

function normalizeAddressee(msg, adapter) {
var name = msg.message.user.name;
if (adapter === "hipchat") {
// Hipchat users aren't pinged by name, they're
// pinged by mention_name
name = msg.message.user.mention_name;
}
var room = msg.message.room;
if (room === undefined) {
if (adapter === "hipchat") {
room = msg.message.user.jid;
}
}
if (adapter === "spark") {
room = msg.message.user.room;
name = msg.message.user.name;
}
return {
name: name,
room: room
};
}

exports.isNull = isNull;
exports.getExecutionHistoryUrl = getExecutionHistoryUrl;
exports.parseUrl = parseUrl;
exports.getTextChunks = getTextChunks;
exports.splitMessage = splitMessage;
exports.constructFromAttrs = constructFromAttrs;
exports.enable2FA = enable2FA;
exports.normalizeAddressee = normalizeAddressee;
exports.SLACK_MAX_MESSAGE_SIZE = SLACK_MAX_MESSAGE_SIZE;
exports.DISPLAY = DISPLAY;
exports.REPRESENTATION = REPRESENTATION;
12 changes: 8 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "hubot-stackstorm",
"description": "A hubot plugin for integrating with StackStorm event-driven infrastructure automation platform.",
"version": "0.9.2",
"version": "0.9.3",
"author": "StackStorm, Inc. <[email protected]>",
"license": "Apache-2.0",
"keywords": [
Expand All @@ -22,8 +22,8 @@
},
"dependencies": {
"cli-table": "<=1.0.0",
"coffee-register": "^2.2.0",
"coffeescript": "^2.3.2",
"coffee-register": "1.0.0",
"coffee-script": "1.12.7",
"lodash": "^4.17.11",
"rsvp": "^4.8.4",
"st2client": "^1.1.1",
Expand All @@ -41,7 +41,7 @@
"gulp-mocha": "^6.0.0",
"gulp-plumber": "^1.2.0",
"hubot": "^3.1.1",
"hubot-help": "^0.2.2",
"hubot-help": "0.2.2",
"hubot-mock-adapter": "^1.1.1",
"jshint": "^2.7.0",
"jshint-stylish": "^2.0.0",
Expand All @@ -63,6 +63,10 @@
],
"exclude": [
"tests"
],
"reporter": [
"html",
"text"
]
},
"engines": {
Expand Down
Loading

0 comments on commit efe1f2f

Please sign in to comment.