slack.js 23 KB

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