slack.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668
  1. const
  2. WebSocket = require('ws'),
  3. https = require('https'),
  4. sleep = require("sleep").sleep
  5. ,SlackData = require("./slackData.js").SlackData
  6. ,SlackHistory = require("./slackHistory.js").SlackHistory
  7. ,config = require("../config.js")
  8. ,httpsRequest = require('./httpsRequest.js').httpsRequest
  9. ;
  10. const SLACK_ENDPOINT = "https://slack.com/api/"
  11. ,SLACK_HOSTNAME = "slack.com"
  12. ,SLACK_ENDPOINT_PATH = "/api/"
  13. ,GETAPI = {
  14. rtmStart: "rtm.start"
  15. ,oauth: "oauth.access"
  16. ,identityEmail: "users.identity"
  17. ,channelHistory: "channels.history"
  18. ,directHistory: "im.history"
  19. ,groupHistory: "groups.history"
  20. ,starChannel: "stars.add"
  21. ,unstarChannel: "stars.remove"
  22. ,postMsg: "chat.postMessage"
  23. ,postMeMsg: "chat.meMessage"
  24. ,editMsg: "chat.update"
  25. ,removeMsg: "chat.delete"
  26. ,postFile: "files.upload"
  27. ,setActive: "users.setActive"
  28. ,emojiList: "emoji.list"
  29. ,slashList: "commands.list"
  30. ,slashExec: "chat.command"
  31. ,addReaction: "reactions.add"
  32. ,removeReaction: "reactions.remove"
  33. ,sendAction: "chat.attachmentAction"
  34. ,read: {
  35. group: "groups.mark"
  36. ,im: "im.mark"
  37. ,group: "mpim.mark"
  38. ,channel: "channels.mark"
  39. }
  40. }
  41. ,HISTORY_LENGTH = 35
  42. ,HISTORY_MAX_AGE = 10000// * 60 * 1000
  43. ,UPDATE_LIVE = [
  44. "message"
  45. ,"pin_added"
  46. ,"pin_removed"
  47. ,"reaction_added"
  48. ,"reaction_removed"
  49. ,"star_added"
  50. ,"star_removed"
  51. ] // Message type that affect live history
  52. ;
  53. /**
  54. * @implements {ChatSystem}
  55. **/
  56. function Slack(slackToken, manager) {
  57. this.token = slackToken;
  58. this.manager = manager;
  59. this.rtm = null;
  60. this.rtmId = 1;
  61. this.data = new SlackData(this);
  62. this.history = {};
  63. this.pendingRtm = {};
  64. this.pendingMessages = [];
  65. this.pendingPing = false;
  66. this.connected = false;
  67. this.closing = false;
  68. }
  69. Slack.prototype.getId = function() {
  70. return this.data.team ? this.data.team.id : null;
  71. };
  72. Slack.prototype.onRequest = function() {
  73. if (this.connected === false) {
  74. this.connect();
  75. }
  76. };
  77. Slack.prototype.connect = function(cb) {
  78. var _this = this;
  79. this.connected = undefined;
  80. httpsRequest(SLACK_ENDPOINT +GETAPI.rtmStart +"?token=" +this.token, (status, body) => {
  81. if (!body || !body.ok) {
  82. _this.error = "Slack API error";
  83. _this.connected = false;
  84. console.error("Slack api responded " +status +" with body " +JSON.stringify(body));
  85. cb && cb(_this);
  86. } else if (status !== 200) {
  87. _this.error = body.error;
  88. _this.connected = false;
  89. console.error("Slack api responded " +status);
  90. cb && cb(_this);
  91. } else {
  92. _this.data.updateStatic({
  93. team: body["team"],
  94. users: body["users"],
  95. bots: body["bots"],
  96. self: body["self"]
  97. }, Date.now());
  98. _this.connectRtm(body.url);
  99. }
  100. });
  101. };
  102. Slack.prototype.sendCommand = function(room, cmd, arg) {
  103. httpsRequest(
  104. SLACK_ENDPOINT
  105. +GETAPI.slashExec
  106. +"?token=" +this.token
  107. +"&command=" +encodeURIComponent(cmd.name)
  108. +"&disp=" +encodeURIComponent(cmd.name)
  109. +"&channel=" +room.remoteId
  110. +"&text=" +arg);
  111. }
  112. Slack.prototype.sendTyping = function(room) {
  113. this.rtm.send('{"id":' +this.rtmId++ +',"type":"typing","channel":"' +room.remoteId +'"}');
  114. }
  115. Slack.prototype.getSlashCommands = function(cb) {
  116. httpsRequest(SLACK_ENDPOINT +GETAPI.slashList +"?token=" +this.token, (status, body) => {
  117. if (!status || !body || !body.ok)
  118. cb(null);
  119. else
  120. cb(body.commands || {});
  121. });
  122. };
  123. Slack.prototype.getEmojis = function(cb) {
  124. httpsRequest(SLACK_ENDPOINT +GETAPI.emojiList +"?token=" +this.token, (status, body) => {
  125. if (!status || !body || !body.ok)
  126. cb(null);
  127. else
  128. cb(body.emoji || {});
  129. });
  130. };
  131. Slack.prototype.poll = function(knownVersion, now) {
  132. if (this.connected) {
  133. var updatedCtx = this.data.getUpdates(knownVersion)
  134. ,updatedTyping = this.data.getWhoIsTyping(now)
  135. ,updatedLive = this.getLiveUpdates(knownVersion);
  136. if (updatedCtx || updatedLive || updatedTyping) {
  137. return {
  138. "static": updatedCtx,
  139. "live": updatedLive,
  140. "typing": updatedTyping,
  141. "v": Math.max(this.data.liveV, this.data.staticV)
  142. };
  143. }
  144. }
  145. };
  146. /** @return {Object|undefined} */
  147. Slack.prototype.getLiveUpdates = function(knownVersion) {
  148. var result = {};
  149. for (var roomId in this.history) {
  150. var history = this.history[roomId];
  151. if (history.isNew) {
  152. result[roomId] = history.toStatic(0);
  153. history.isNew = false;
  154. }
  155. else {
  156. var roomData = history.toStatic(knownVersion);
  157. if (roomData.length)
  158. result[roomId] = roomData;
  159. }
  160. }
  161. for (var roomId in result) {
  162. return result;
  163. }
  164. return undefined;
  165. };
  166. Slack.prototype.unstackPendingMessages = function() {
  167. for (var i = this.pendingMessages.length -1; i >= 0; i--) {
  168. this.onMessage(this.pendingMessages[0], Date.now());
  169. this.pendingMessages.shift();
  170. }
  171. };
  172. Slack.prototype.resetVersions = function(v) {
  173. this.data.team.version = v;
  174. for (var i in this.data.channels)
  175. this.data.channels[i].version = v;
  176. for (var i in this.data.users)
  177. this.data.users[i].version = v;
  178. this.data.staticV = v;
  179. };
  180. Slack.prototype.onMessage = function(msg, t) {
  181. if (msg["reply_to"] && this.pendingRtm[msg["reply_to"]]) {
  182. var ts = msg["ts"]
  183. ,rtmId = msg["reply_to"];
  184. msg = this.pendingRtm[rtmId];
  185. msg["ts"] = ts;
  186. delete this.pendingRtm[rtmId];
  187. }
  188. if (msg["type"] === "hello" && msg["start"] && msg["start"]["rtm_start"]) {
  189. var _this = this;
  190. _this.getEmojis((emojis) => {
  191. _this.getSlashCommands((commands) => {
  192. var msgContent = msg.start.rtm_start;
  193. var now = Date.now();
  194. msgContent.self = msg.self;
  195. msgContent.emojis = emojis;
  196. msgContent.commands = commands;
  197. _this.resetVersions(now);
  198. _this.data.updateStatic(msgContent, now);
  199. _this.connected = true;
  200. _this.unstackPendingMessages();
  201. });
  202. });
  203. } else if (this.connected) {
  204. this.data.onMessage(msg, t);
  205. if ((msg["channel"] || msg["channel_id"] || (msg["item"] && msg["item"]["channel"])) && msg["type"] && UPDATE_LIVE.indexOf(msg["type"]) !== -1) {
  206. var channelId = this.data.team.id +'|' +(msg["channel"] || msg["channel_id"] || msg["item"]["channel"])
  207. ,channel = this.data.channels[channelId]
  208. ,histo = this.history[channelId];
  209. // FIXME remove typing for user
  210. if (!histo) {
  211. histo = this.history[channelId] = new SlackHistory(this, channel.remoteId, channelId, this.data.team.id +'|', HISTORY_LENGTH);
  212. histo.isNew = true;
  213. }
  214. var lastMsg = histo.push(msg, t);
  215. if (lastMsg)
  216. this.data.liveV = t;
  217. histo.resort();
  218. if (channel)
  219. channel.setLastMsg(lastMsg, t);
  220. }
  221. } else {
  222. this.pendingMessages.push(msg);
  223. }
  224. };
  225. /**
  226. * @param {SlackChan|SlackGroup|SlackIms} chan
  227. * @param {string} id
  228. * @param {number} ts
  229. **/
  230. Slack.prototype.markRead = function(chan, id, ts) {
  231. var apiURI;
  232. if (chan.remoteId[0] === 'C')
  233. apiURI = SLACK_ENDPOINT+GETAPI.read.channel;
  234. else if (chan.remoteId[0] === 'G')
  235. apiURI = SLACK_ENDPOINT+GETAPI.read.group;
  236. else if (chan.remoteId[0] === 'D')
  237. apiURI = SLACK_ENDPOINT+GETAPI.read.im;
  238. httpsRequest(apiURI
  239. +"?token=" +this.token
  240. +"&channel="+chan.remoteId
  241. +"&ts="+id);
  242. };
  243. Slack.prototype.connectRtm = function(url, cb) {
  244. var _this = this;
  245. this.rtmId = 1;
  246. var protocol = url.substr(0, url.indexOf('://') +3);
  247. url = url.substr(protocol.length);
  248. url = protocol +url.substr(0, url.indexOf('/'))+
  249. "/?flannel=1&token=" +this.token+
  250. "&start_args="+
  251. encodeURIComponent("?simple_latest=true&presence_sub=true&mpim_aware=false&canonical_avatars=true")
  252. this.rtm = new WebSocket(url);
  253. this.rtm.on("message", function(msg) {
  254. if (!_this.connected && cb) {
  255. cb();
  256. }
  257. try {
  258. msg = JSON.parse(msg);
  259. } catch (ex) {
  260. console.error("WTF invalid JSON ", msg);
  261. }
  262. _this.onMessage(msg, Date.now());
  263. });
  264. this.rtm.once("error", function(e) {
  265. _this.connected = false;
  266. console.error(e);
  267. _this.close();
  268. });
  269. this.rtm.once("end", function() {
  270. console.error("RTM hang up");
  271. _this.onClose();
  272. });
  273. };
  274. Slack.prototype.onClose = function() {
  275. this.manager.suicide(this);
  276. };
  277. Slack.prototype.ping = function() {
  278. httpsRequest(SLACK_ENDPOINT+GETAPI.setActive
  279. +"?token=" +this.token);
  280. };
  281. Slack.prototype.rtmPing = function() {
  282. if (this.connected) {
  283. if (this.pendingPing && this.pendingRtm[this.pendingPing]) {
  284. //FIXME timeout
  285. console.error("Ping timeout");
  286. } else {
  287. var rtmId = this.rtmId++;
  288. this.pendingRtm[rtmId] = { type: 'ping' };
  289. this.pendingPing = rtmId;
  290. this.rtm.send('{"id":' +rtmId +',"type":"ping"}');
  291. }
  292. }
  293. };
  294. Slack.prototype.close = function() {
  295. if (!this.closing) {
  296. this.closing = true;
  297. if (this.rtm)
  298. this.rtm.close();
  299. this.onClose();
  300. }
  301. };
  302. Slack.getUserId = function(code, redirectUri, cb) {
  303. Slack.getOauthToken(code, redirectUri, (token) => {
  304. if (token) {
  305. httpsRequest(SLACK_ENDPOINT+GETAPI.identityEmail +"?token="+token,
  306. (status, resp) => {
  307. if (status === 200 && resp.ok && resp.user && resp.user.email) {
  308. cb(resp.user.id +'_' +resp.team.id);
  309. } else {
  310. cb(null);
  311. }
  312. });
  313. } else {
  314. cb(null);
  315. }
  316. });
  317. };
  318. Slack.getOauthToken = function(code, cb) {
  319. httpsRequest(SLACK_ENDPOINT+GETAPI.oauth
  320. +"?client_id=" +config.services.Slack.clientId
  321. +"&client_secret=" +config.services.Slack.clientSecret
  322. +"&redirect_uri=" +encodeURIComponent(config.rootUrl +"account/addservice/slack")
  323. +"&code=" +code,
  324. (status, resp) => {
  325. if (status === 200 && resp.ok) {
  326. cb(resp["team_name"], resp["team_id"] +resp["user_id"], resp["access_token"]);
  327. } else {
  328. cb(null);
  329. }
  330. });
  331. };
  332. /**
  333. * @param {SlackChan|SlackGroup|SlackIms} channel
  334. * @param {string} contentType
  335. * @param {function(string|null)} callback
  336. **/
  337. Slack.prototype.openUploadFileStream = function(channel, contentType, callback) {
  338. var req = https.request({
  339. hostname: SLACK_HOSTNAME
  340. ,method: 'POST'
  341. ,path: SLACK_ENDPOINT_PATH +GETAPI.postFile
  342. +"?token=" +this.token
  343. +"&channels=" +channel.remoteId
  344. ,headers: {
  345. "Content-Type": contentType
  346. }
  347. }, (res) => {
  348. var errorJson;
  349. res.on("data", (chunk) => {
  350. errorJson = errorJson ? Buffer.concat([errorJson, chunk], errorJson.length +chunk.length) : Buffer.from(chunk);
  351. });
  352. res.once("end", () => {
  353. if (res.statusCode === 200) {
  354. callback(null);
  355. } else {
  356. try {
  357. errorJson = JSON.parse(errorJson.toString());
  358. } catch(e) {
  359. callback("error");
  360. return;
  361. }
  362. callback(errorJson["error"] || "error");
  363. }
  364. });
  365. });
  366. return req;
  367. };
  368. function findBoundary() {
  369. const prefix = '-'.repeat(15)
  370. ,alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
  371. ,doYouKnowWhoManyTheyAre = alphabet.length // 26 letters in da alphabet
  372. ,nbArg = arguments.length;
  373. for (let i =0; i < doYouKnowWhoManyTheyAre; i++)
  374. bLoop: for (let j =0; j < doYouKnowWhoManyTheyAre; j++) {
  375. const boundary = prefix +alphabet[i] +alphabet[j];
  376. for (let argIndex =0; argIndex < nbArg; argIndex++) {
  377. if (arguments[argIndex].indexOf(boundary) >= 0)
  378. continue bLoop;
  379. }
  380. return boundary;
  381. }
  382. }
  383. function encodeWithBoundary(boundary, data) {
  384. var resp = "";
  385. for (var k in data) {
  386. resp += '--' +boundary +'\r\n';
  387. resp += 'Content-Disposition: form-data; name="' +k +'"\r\n\r\n'
  388. +data[k]
  389. +'\r\n';
  390. };
  391. return resp +'--' +boundary +'--\r\n';
  392. }
  393. /**
  394. * @param {string} serviceId
  395. * @param {Object} payload
  396. * @param {function(string|null)=} callback
  397. **/
  398. Slack.prototype.sendAction = function(serviceId, payload, callback) {
  399. var channel = this.data.channels[payload["channel_id"]]
  400. ,service = this.data.users[serviceId];
  401. if (channel && service) {
  402. payload["channel_id"] = channel.remoteId;
  403. var payloadString = JSON.stringify(payload)
  404. ,boundary = findBoundary(service.remoteId, payloadString)
  405. ,body = encodeWithBoundary(boundary, {
  406. "service_id": service.remoteId
  407. ,"payload": payloadString
  408. });
  409. var req = https.request({
  410. hostname: SLACK_HOSTNAME
  411. ,method: 'POST'
  412. ,path: SLACK_ENDPOINT_PATH +GETAPI.sendAction +"?token=" +this.token
  413. ,headers: {
  414. "Content-Type": "multipart/form-data; boundary=" +boundary,
  415. "Content-Length": body.length
  416. }
  417. }, (res) => {
  418. if (callback) {
  419. var resp = [];
  420. res.on("data", (chunk) => { resp.push(chunk); });
  421. res.once("end", () => {
  422. resp = Buffer.concat(resp).toString();
  423. try {
  424. resp = JSON.parse(resp);
  425. } catch (e) {
  426. resp = null;
  427. }
  428. callback(resp && resp.ok ? resp : false);
  429. });
  430. }
  431. });
  432. req.end(body);
  433. return true;
  434. }
  435. return false;
  436. };
  437. /**
  438. * @param {SlackChan|SlackGroup|SlackIms} channel
  439. * @param {string} contentType
  440. * @param {function(string|null)} callback
  441. **/
  442. Slack.prototype.openUploadFileStream = function(channel, contentType, callback) {
  443. var req = https.request({
  444. hostname: SLACK_HOSTNAME
  445. ,method: 'POST'
  446. ,path: SLACK_ENDPOINT_PATH +GETAPI.postFile
  447. +"?token=" +this.token
  448. +"&channels=" +channel.remoteId
  449. ,headers: {
  450. "Content-Type": contentType
  451. }
  452. }, (res) => {
  453. var errorJson;
  454. res.on("data", (chunk) => {
  455. errorJson = errorJson ? Buffer.concat([errorJson, chunk], errorJson.length +chunk.length) : Buffer.from(chunk);
  456. });
  457. res.once("end", () => {
  458. if (res.statusCode === 200) {
  459. callback(null);
  460. } else {
  461. try {
  462. errorJson = JSON.parse(errorJson.toString());
  463. } catch(e) {
  464. callback("error");
  465. return;
  466. }
  467. callback(errorJson["error"] || "error");
  468. }
  469. });
  470. });
  471. return req;
  472. };
  473. /**
  474. * @param {SlackChan|SlackGroup|SlackIms} channel
  475. * @param {string} msgId
  476. * @param {string} reaction
  477. **/
  478. Slack.prototype.addReaction = function(channel, msgId, reaction) {
  479. httpsRequest(SLACK_ENDPOINT +GETAPI.addReaction
  480. +"?token=" +this.token
  481. +"&name=" +reaction
  482. +"&channel="+channel.remoteId
  483. +"&timestamp="+msgId);
  484. }
  485. /**
  486. * @param {SlackChan|SlackGroup|SlackIms} channel
  487. * @param {string} msgId
  488. * @param {string} reaction
  489. **/
  490. Slack.prototype.removeReaction = function(channel, msgId, reaction) {
  491. httpsRequest(SLACK_ENDPOINT +GETAPI.removeReaction
  492. +"?token=" +this.token
  493. +"&name=" +reaction
  494. +"&channel="+channel.remoteId
  495. +"&timestamp="+msgId);
  496. }
  497. /**
  498. * @param {SlackChan|SlackGroup|SlackIms} channel
  499. * @param {Array.<string>} text
  500. **/
  501. Slack.prototype.sendMeMsg = function(channel, text) {
  502. httpsRequest(SLACK_ENDPOINT +GETAPI.postMeMsg
  503. +"?token=" +this.token
  504. +"&channel=" +channel.remoteId
  505. +"&text=" +text.join("\n")
  506. +"&as_user=true");
  507. };
  508. /**
  509. * @param {SlackChan|SlackGroup|SlackIms} channel
  510. **/
  511. Slack.prototype.starChannel = function(channel) {
  512. httpsRequest(SLACK_ENDPOINT +GETAPI.starChannel
  513. +"?token=" +this.token
  514. +"&channel=" +channel.remoteId);
  515. };
  516. /**
  517. * @param {SlackChan|SlackGroup|SlackIms} channel
  518. **/
  519. Slack.prototype.unstarChannel = function(channel) {
  520. httpsRequest(SLACK_ENDPOINT +GETAPI.unstarChannel
  521. +"?token=" +this.token
  522. +"&channel=" +channel.remoteId);
  523. };
  524. /**
  525. * @param {SlackChan|SlackGroup|SlackIms} channel
  526. * @param {Array.<string>} text
  527. * @param {Array.<Object>=} attachments
  528. **/
  529. Slack.prototype.sendMsg = function(channel, text, attachments) {
  530. if (attachments) {
  531. attachments.forEach((attachmentObj) => {
  532. if (attachmentObj["ts"])
  533. attachmentObj["ts"] /= 1000;
  534. });
  535. httpsRequest(SLACK_ENDPOINT +GETAPI.postMsg
  536. +"?token=" +this.token
  537. +"&channel=" +channel.remoteId
  538. +"&text=" +text.join("\n")
  539. + (attachments ? ("&attachments=" +encodeURIComponent(JSON.stringify(attachments))) : "")
  540. +"&as_user=true");
  541. } else {
  542. var decodedText = [];
  543. text.forEach(function(i) {
  544. decodedText.push(decodeURIComponent(i));
  545. });
  546. var fullDecodedText = decodedText.join("\n");
  547. this.pendingRtm[this.rtmId] = {
  548. type: 'message',
  549. channel: channel.remoteId,
  550. user: this.data.self.remoteId,
  551. text: fullDecodedText
  552. };
  553. this.rtm.send('{"id":' +this.rtmId++ +',"type":"message","channel":"' +channel.remoteId +'", "text":' +JSON.stringify(fullDecodedText) +'}');
  554. }
  555. };
  556. /**
  557. * @param {SlackChan|SlackGroup|SlackIms} channel
  558. * @param {string} msgId
  559. **/
  560. Slack.prototype.removeMsg = function(channel, msgId) {
  561. httpsRequest(SLACK_ENDPOINT +GETAPI.removeMsg
  562. +"?token=" +this.token
  563. +"&channel=" +channel.remoteId
  564. +"&ts=" +msgId
  565. +"&as_user=true");
  566. };
  567. /**
  568. * @param {SlackChan|SlackGroup|SlackIms} channel
  569. * @param {string} msgId
  570. * @param {string} text
  571. **/
  572. Slack.prototype.editMsg = function(channel, msgId, text) {
  573. httpsRequest(SLACK_ENDPOINT +GETAPI.editMsg
  574. +"?token=" +this.token
  575. +"&channel=" +channel.remoteId
  576. +"&ts=" +msgId
  577. +"&text=" +text.join("\n")
  578. +"&as_user=true");
  579. };
  580. /**
  581. * @param {SlackChan|SlackGroup|SlackIms} target
  582. **/
  583. Slack.prototype.fetchHistory = function(target, cb, count, firstMsgId) {
  584. var _this = this
  585. ,baseUrl = ""
  586. ,targetId = target.remoteId;
  587. if (targetId[0] === 'D') {
  588. baseUrl = SLACK_ENDPOINT +GETAPI.directHistory;
  589. } else if (targetId[0] === 'C') {
  590. baseUrl = SLACK_ENDPOINT +GETAPI.channelHistory;
  591. } else if (targetId[0] === 'G') {
  592. baseUrl = SLACK_ENDPOINT +GETAPI.groupHistory;
  593. }
  594. httpsRequest(baseUrl
  595. +"?token="+this.token
  596. +"&channel=" +targetId
  597. +(firstMsgId ? ("&inclusive=true&latest=" +firstMsgId) : "")
  598. +"&count=" +(count || 100),
  599. (status, resp) => {
  600. var history = [];
  601. if (status === 200 && resp && resp.ok) {
  602. var histo = this.history[target.id];
  603. if (!histo)
  604. histo = this.history[target.id] = new SlackHistory(_this, target.remoteId, target.id, this.data.team.id +'|', HISTORY_LENGTH, HISTORY_MAX_AGE);
  605. resp.messages.forEach((respMsg) => {
  606. respMsg["id"] = respMsg["ts"];
  607. history.push(histo.messageFactory(histo.prepareMessage(respMsg)));
  608. });
  609. }
  610. cb(history);
  611. });
  612. };
  613. Slack.prototype.getChatContext = function() {
  614. return this.data;
  615. };
  616. module.exports.Slack = Slack;