'use strict';
const bindLogger = require('./util/bind-logger');
const defaults = require('lodash/defaults');
const keyBy = require('lodash/keyBy');
let Bot; // Not set here to get around circular dependency
/**
* Class representing Slack and bot data.
*/
class DataManager {
/**
* Create a data manager.
* @param {Object} options - The data manager options.
*/
constructor(options) {
this.options = defaults({}, options, DataManager.defaults);
this.slackApi = null;
}
/**
* Register the data manager to a bot.
* @access private
* @param {Bot} bot - The bot to register to.
* @returns {DataManager} The calling data manager instance.
* @throws {TypeError} Will throw if bot is not an instance of Bot.
*/
registerToBot(bot) {
// NOTE: Bot is required here to get around a
// circular dependency when the module is loaded
Bot = Bot || require('./bot');
if (!(bot instanceof Bot)) {
throw new TypeError('Expected an instance of Bot');
}
this.bot = bot;
this.slackApi = bot.botkit.bot.api;
bot.data = this;
this.log = bindLogger(bot.log, `${this.constructor.name}:`);
this.log.info('Registered to bot');
return this;
}
/**
* Get all channels on the Slack instance.
* @returns {Promise} Returns a promise which resolves with an array of channels.
*/
async getChannels() {
if (!this.channelCache) {
let channels = [];
let cursor;
// Function to fetch a page of channels
const fetchChannels = () => {
return new Promise((resolve, reject) => {
this.slackApi.channels.list({
limit: 200,
cursor
}, (error, response) => {
if (error || !response || !response.channels) {
return reject(new Error(`Could not get channels from the Slack API: ${error ? error.message : 'no channels'}`));
}
resolve(response);
});
});
};
// We need to loop until we get all of the channels,
// there's an explit break later to end this
/* eslint-disable no-constant-condition */
while (true) {
/* eslint-enable no-constant-condition */
const response = await fetchChannels(cursor);
channels = channels.concat(response.channels);
if (response.response_metadata && response.response_metadata.next_cursor) {
cursor = response.response_metadata.next_cursor;
} else {
break;
}
}
// Cache the channels
this.channelCache = channels;
this.log.info('Loaded and cached Slack channels');
}
// Resolve with the cached channels
return this.channelCache;
}
/**
* Get all channels on the the Slack instance as an object with channel IDs as keys.
* @returns {Promise} Returns a promise which resolves with indexed channels.
*/
async getChannelsById() {
if (!this.channelsByIdCache) {
this.channelsByIdCache = keyBy(await this.getChannels(), 'id');
}
return this.channelsByIdCache;
}
/**
* Get a single channel on the Slack instance by ID.
* @param {String} channelId - The ID of the channel to get.
* @returns {Promise} Returns a promise which resolves with requested channel or `null` if one is not found.
*/
async getChannelById(channelId) {
return (await this.getChannelsById())[channelId] || null;
}
/**
* Get all users who have access to the Slack instance.
* @returns {Promise} Returns a promise which resolves with an array of users.
*/
async getUsers() {
if (!this.userCache) {
let users = [];
let cursor;
// Function to fetch a page of users
const fetchUsers = () => {
return new Promise((resolve, reject) => {
this.slackApi.users.list({
limit: 200,
cursor
}, (error, response) => {
if (error || !response || !response.members) {
return reject(new Error(`Could not get users from the Slack API: ${error ? error.message : 'no users'}`));
}
resolve(response);
});
});
};
// We need to loop until we get all of the users,
// there's an explit break later to end this
/* eslint-disable no-constant-condition */
while (true) {
/* eslint-enable no-constant-condition */
const response = await fetchUsers(cursor);
users = users.concat(response.members);
if (response.response_metadata && response.response_metadata.next_cursor) {
cursor = response.response_metadata.next_cursor;
} else {
break;
}
}
// Cache the users
this.userCache = users;
this.log.info('Loaded and cached Slack users');
}
// Resolve with the cached users
return this.userCache;
}
/**
* Get all users who have access to the Slack instance as an object with user IDs as keys.
* @returns {Promise} Returns a promise which resolves with indexed users.
*/
async getUsersById() {
if (!this.usersByIdCache) {
this.usersByIdCache = keyBy(await this.getUsers(), 'id');
}
return this.usersByIdCache;
}
/**
* Get a single user who has access to the Slack instance by ID.
* @param {String} userId - The ID of the user to get.
* @returns {Promise} Returns a promise which resolves with requested user or `null` if one is not found.
*/
async getUserById(userId) {
return (await this.getUsersById())[userId] || null;
}
/**
* Clear all object caches.
* @returns {Boolean} Returns `true` if the caches were purged.
*/
clearCaches() {
delete this.channelCache;
delete this.channelsByIdCache;
delete this.userCache;
delete this.usersByIdCache;
return true;
}
/**
* Get the data manager as a string, for use in implicit type conversion.
* @access private
* @returns {String} The string representation.
*/
valueOf() {
return `[object ${this.constructor.name}]`;
}
/**
* Get the data manager as a plain object, for use in JSON conversion.
* @access private
* @returns {Object} The data manager as a plain object.
*/
toJSON() {
return {};
}
/**
* Get console-friendly representation of the data manager.
* @access private
* @returns {String} The console-friendly representation.
*/
inspect() {
return `${this.constructor.name} {}`;
}
/**
* Create a data manager (see {@link DataManager} for parameters).
* @returns {DataManager} The new data manager.
*/
static create(...args) {
return new this(...args);
}
}
/**
* The default options used when constructing a data manager.
* @static
*/
DataManager.defaults = {};
module.exports = DataManager;