slack.js 21 KB

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