mangrove-botkit
Version:
Building blocks for Building Bots
285 lines (256 loc) • 8.42 kB
JavaScript
var request = require('request');
/**
* Does nothing. Takes no params, returns nothing. It's a no-op!
*/
function noop() {}
/**
* Returns an interface to the Slack API in the context of the given bot
*
* @param {Object} bot The botkit bot object
* @param {Object} config A config containing auth credentials.
* @returns {Object} A callback-based Slack API interface.
*/
module.exports = function(bot, config) {
var api_root = bot.config.api_root ? bot.config.api_root : 'https://slack.com';
var slack_api = {
api_url: api_root + '/api/'
};
// Slack API methods: https://api.slack.com/methods
var slackApiMethods = [
'auth.test',
'oauth.access',
'channels.archive',
'channels.create',
'channels.history',
'channels.info',
'channels.invite',
'channels.join',
'channels.kick',
'channels.leave',
'channels.list',
'channels.mark',
'channels.rename',
'channels.replies',
'channels.setPurpose',
'channels.setTopic',
'channels.unarchive',
'chat.delete',
'chat.postMessage',
'chat.postEphemeral',
'chat.update',
'chat.unfurl',
'conversations.archive',
'conversations.close',
'conversations.create',
'conversations.history',
'conversations.info',
'conversations.invite',
'conversations.join',
'conversations.kick',
'conversations.leave',
'conversations.list',
'conversations.members',
'conversations.open',
'conversations.rename',
'conversations.replies',
'conversations.setPurpose',
'conversations.setTopic',
'conversations.unarchive',
'dialog.open',
'dnd.endDnd',
'dnd.endSnooze',
'dnd.info',
'dnd.setSnooze',
'dnd.teamInfo',
'emoji.list',
'files.delete',
'files.info',
'files.list',
'files.upload',
'files.sharedPublicURL',
'groups.archive',
'groups.close',
'groups.create',
'groups.createChild',
'groups.history',
'groups.info',
'groups.invite',
'groups.kick',
'groups.leave',
'groups.list',
'groups.mark',
'groups.open',
'groups.rename',
'groups.replies',
'groups.setPurpose',
'groups.setTopic',
'groups.unarchive',
'im.close',
'im.history',
'im.list',
'im.mark',
'im.open',
'im.replies',
'mpim.close',
'mpim.history',
'mpim.list',
'mpim.mark',
'mpim.open',
'mpim.replies',
'pins.add',
'pins.list',
'pins.remove',
'reactions.add',
'reactions.get',
'reactions.list',
'reactions.remove',
'reminders.add',
'reminders.complete',
'reminders.delete',
'reminders.info',
'reminders.list',
'rtm.start',
'rtm.connect',
'search.all',
'search.files',
'search.messages',
'stars.add',
'stars.list',
'stars.remove',
'team.accessLogs',
'team.info',
'team.billableInfo',
'team.integrationLogs',
'team.profile.get',
'usergroups.create',
'usergroups.disable',
'usergroups.enable',
'usergroups.list',
'usergroups.update',
'usergroups.users.list',
'usergroups.users.update',
'users.getPresence',
'users.info',
'users.identity',
'users.list',
'users.setActive',
'users.setPresence',
'users.deletePhoto',
'users.identity',
'users.setPhoto',
'users.profile.get',
'users.profile.set'
];
/**
* Calls Slack using a Token for authentication/authorization
*
* @param {string} command The Slack API command to call
* @param {Object} data The data to pass to the API call
* @param {function} cb A NodeJS-style callback
*/
slack_api.callAPI = function(command, data, cb, multipart) {
data.token = data.token || config.token;
bot.debug(command, data);
postForm(slack_api.api_url + command, data, cb, multipart);
};
/**
* Calls Slack using OAuth for authentication/authorization
*
* @param {string} command The Slack API command to call
* @param {Object} data The data to pass to the API call
* @param {function} cb A NodeJS-style callback
*/
slack_api.callAPIWithoutToken = function(command, data, cb) {
data.client_id = data.client_id || bot.config.clientId;
data.client_secret = data.client_secret || bot.config.clientSecret;
data.redirect_uri = data.redirect_uri || bot.config.redirectUri;
// DON'T log options: that could expose the client secret!
postForm(slack_api.api_url + command, data, cb);
};
// generate all API methods
slackApiMethods.forEach(function(slackMethod) {
// most slack api methods are only two parts, of the form group.method, e.g. auth.test
// some have three parts: group.subgroup.method, e.g, users.profile.get
// this method loops through all groups in a method, ensures they exist,
// then adds the method to the terminal group
var groups = slackMethod.split('.');
var method = groups.pop();
var currentGroup = slack_api;
groups.forEach(function(nextGroupName) {
currentGroup[nextGroupName] = currentGroup[nextGroupName] || {};
currentGroup = currentGroup[nextGroupName];
});
currentGroup[method] = function(options, cb) {
slack_api.callAPI(slackMethod, options, cb);
};
});
// overwrite default behavior
slack_api.chat.postMessage = function(options, cb) {
sanitizeOptions(options);
slack_api.callAPI('chat.postMessage', options, cb);
};
slack_api.chat.postEphemeral = function(options, cb) {
sanitizeOptions(options);
slack_api.callAPI('chat.postEphemeral', options, cb);
};
slack_api.chat.update = function(options, cb) {
sanitizeOptions(options);
slack_api.callAPI('chat.update', options, cb);
};
// specify that files get uploaded using multipart
slack_api.files.upload = function(options, cb) {
slack_api.callAPI('files.upload', options, cb, !!options.file);
};
function sanitizeOptions(options) {
if (options.attachments && typeof(options.attachments) != 'string') {
try {
options.attachments = JSON.stringify(options.attachments);
} catch (err) {
delete options.attachments;
bot.log.error('Could not parse attachments', err);
}
}
}
return slack_api;
/**
* Makes a POST request as a form to the given url with the options as data
*
* @param {string} url The URL to POST to
* @param {Object} formData The data to POST as a form
* @param {function=} cb An optional NodeJS style callback when the POST completes or errors out.
*/
function postForm(url, formData, cb, multipart) {
cb = cb || noop;
bot.log('** API CALL: ' + url);
var params = {
url: url,
headers: {
'User-Agent': bot.userAgent(),
}
};
if (multipart === true) {
params.formData = formData;
} else {
params.form = formData;
}
request.post(params, function(error, response, body) {
bot.debug('Got response', error, body);
if (error) {
return cb(error);
}
if (response.statusCode == 200) {
var json;
try {
json = JSON.parse(body);
} catch (parseError) {
return cb(parseError);
}
return cb((json.ok ? null : json.error), json);
} else if (response.statusCode == 429) {
return cb(new Error('Rate limit exceeded'));
} else {
return cb(new Error('Invalid response'));
}
});
}
};