data-manager.js

  1. 'use strict';
  2. const bindLogger = require('./util/bind-logger');
  3. const defaults = require('lodash/defaults');
  4. const keyBy = require('lodash/keyBy');
  5. let Bot; // Not set here to get around circular dependency
  6. /**
  7. * Class representing Slack and bot data.
  8. */
  9. class DataManager {
  10. /**
  11. * Create a data manager.
  12. * @param {Object} options - The data manager options.
  13. */
  14. constructor(options) {
  15. this.options = defaults({}, options, DataManager.defaults);
  16. this.slackApi = null;
  17. }
  18. /**
  19. * Register the data manager to a bot.
  20. * @access private
  21. * @param {Bot} bot - The bot to register to.
  22. * @returns {DataManager} The calling data manager instance.
  23. * @throws {TypeError} Will throw if bot is not an instance of Bot.
  24. */
  25. registerToBot(bot) {
  26. // NOTE: Bot is required here to get around a
  27. // circular dependency when the module is loaded
  28. Bot = Bot || require('./bot');
  29. if (!(bot instanceof Bot)) {
  30. throw new TypeError('Expected an instance of Bot');
  31. }
  32. this.bot = bot;
  33. this.slackApi = bot.botkit.bot.api;
  34. bot.data = this;
  35. this.log = bindLogger(bot.log, `${this.constructor.name}:`);
  36. this.log.info('Registered to bot');
  37. return this;
  38. }
  39. /**
  40. * Get all channels on the Slack instance.
  41. * @returns {Promise} Returns a promise which resolves with an array of channels.
  42. */
  43. async getChannels() {
  44. if (!this.channelCache) {
  45. let channels = [];
  46. let cursor;
  47. // Function to fetch a page of channels
  48. const fetchChannels = () => {
  49. return new Promise((resolve, reject) => {
  50. this.slackApi.channels.list({
  51. limit: 200,
  52. cursor
  53. }, (error, response) => {
  54. if (error || !response || !response.channels) {
  55. return reject(new Error(`Could not get channels from the Slack API: ${error ? error.message : 'no channels'}`));
  56. }
  57. resolve(response);
  58. });
  59. });
  60. };
  61. // We need to loop until we get all of the channels,
  62. // there's an explit break later to end this
  63. /* eslint-disable no-constant-condition */
  64. while (true) {
  65. /* eslint-enable no-constant-condition */
  66. const response = await fetchChannels(cursor);
  67. channels = channels.concat(response.channels);
  68. if (response.response_metadata && response.response_metadata.next_cursor) {
  69. cursor = response.response_metadata.next_cursor;
  70. } else {
  71. break;
  72. }
  73. }
  74. // Cache the channels
  75. this.channelCache = channels;
  76. this.log.info('Loaded and cached Slack channels');
  77. }
  78. // Resolve with the cached channels
  79. return this.channelCache;
  80. }
  81. /**
  82. * Get all channels on the the Slack instance as an object with channel IDs as keys.
  83. * @returns {Promise} Returns a promise which resolves with indexed channels.
  84. */
  85. async getChannelsById() {
  86. if (!this.channelsByIdCache) {
  87. this.channelsByIdCache = keyBy(await this.getChannels(), 'id');
  88. }
  89. return this.channelsByIdCache;
  90. }
  91. /**
  92. * Get a single channel on the Slack instance by ID.
  93. * @param {String} channelId - The ID of the channel to get.
  94. * @returns {Promise} Returns a promise which resolves with requested channel or `null` if one is not found.
  95. */
  96. async getChannelById(channelId) {
  97. return (await this.getChannelsById())[channelId] || null;
  98. }
  99. /**
  100. * Get all users who have access to the Slack instance.
  101. * @returns {Promise} Returns a promise which resolves with an array of users.
  102. */
  103. async getUsers() {
  104. if (!this.userCache) {
  105. let users = [];
  106. let cursor;
  107. // Function to fetch a page of users
  108. const fetchUsers = () => {
  109. return new Promise((resolve, reject) => {
  110. this.slackApi.users.list({
  111. limit: 200,
  112. cursor
  113. }, (error, response) => {
  114. if (error || !response || !response.members) {
  115. return reject(new Error(`Could not get users from the Slack API: ${error ? error.message : 'no users'}`));
  116. }
  117. resolve(response);
  118. });
  119. });
  120. };
  121. // We need to loop until we get all of the users,
  122. // there's an explit break later to end this
  123. /* eslint-disable no-constant-condition */
  124. while (true) {
  125. /* eslint-enable no-constant-condition */
  126. const response = await fetchUsers(cursor);
  127. users = users.concat(response.members);
  128. if (response.response_metadata && response.response_metadata.next_cursor) {
  129. cursor = response.response_metadata.next_cursor;
  130. } else {
  131. break;
  132. }
  133. }
  134. // Cache the users
  135. this.userCache = users;
  136. this.log.info('Loaded and cached Slack users');
  137. }
  138. // Resolve with the cached users
  139. return this.userCache;
  140. }
  141. /**
  142. * Get all users who have access to the Slack instance as an object with user IDs as keys.
  143. * @returns {Promise} Returns a promise which resolves with indexed users.
  144. */
  145. async getUsersById() {
  146. if (!this.usersByIdCache) {
  147. this.usersByIdCache = keyBy(await this.getUsers(), 'id');
  148. }
  149. return this.usersByIdCache;
  150. }
  151. /**
  152. * Get a single user who has access to the Slack instance by ID.
  153. * @param {String} userId - The ID of the user to get.
  154. * @returns {Promise} Returns a promise which resolves with requested user or `null` if one is not found.
  155. */
  156. async getUserById(userId) {
  157. return (await this.getUsersById())[userId] || null;
  158. }
  159. /**
  160. * Clear all object caches.
  161. * @returns {Boolean} Returns `true` if the caches were purged.
  162. */
  163. clearCaches() {
  164. delete this.channelCache;
  165. delete this.channelsByIdCache;
  166. delete this.userCache;
  167. delete this.usersByIdCache;
  168. return true;
  169. }
  170. /**
  171. * Get the data manager as a string, for use in implicit type conversion.
  172. * @access private
  173. * @returns {String} The string representation.
  174. */
  175. valueOf() {
  176. return `[object ${this.constructor.name}]`;
  177. }
  178. /**
  179. * Get the data manager as a plain object, for use in JSON conversion.
  180. * @access private
  181. * @returns {Object} The data manager as a plain object.
  182. */
  183. toJSON() {
  184. return {};
  185. }
  186. /**
  187. * Get console-friendly representation of the data manager.
  188. * @access private
  189. * @returns {String} The console-friendly representation.
  190. */
  191. inspect() {
  192. return `${this.constructor.name} {}`;
  193. }
  194. /**
  195. * Create a data manager (see {@link DataManager} for parameters).
  196. * @returns {DataManager} The new data manager.
  197. */
  198. static create(...args) {
  199. return new this(...args);
  200. }
  201. }
  202. /**
  203. * The default options used when constructing a data manager.
  204. * @static
  205. */
  206. DataManager.defaults = {};
  207. module.exports = DataManager;