UNPKG

mangrove-botkit

Version:

Building blocks for Building Bots

285 lines (256 loc) 8.42 kB
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')); } }); } };