slackData.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445
  1. const ChatContext = require('./context.js').ChatContext
  2. ,ChatInfo = require('./context.js').ChatInfo
  3. ,Command = require('./context.js').Command
  4. ,Room = require('./room.js').Room
  5. ,Chatter = require('./chatter.js').Chatter
  6. ,PrivateMessageRoom = require('./room.js').PrivateMessageRoom;
  7. const SLACK_TYPING_DELAY = 6000;
  8. /**
  9. * @constructor
  10. * @extends {ChatInfo}
  11. **/
  12. function SlackTeam(teamId) {
  13. ChatInfo.call(this, teamId);
  14. /** @type {string} */
  15. this.domain;
  16. /** @type {string} */
  17. this.callApp;
  18. /** @type {string} */
  19. this.callAppName;
  20. /** @type {boolean} */
  21. this.fileUploadPermission;
  22. /** @type {boolean} */
  23. this.fileEditPermission;
  24. /** @type {boolean} */
  25. this.fileDeletePermission;
  26. /** @type {string} */
  27. this.icons = {
  28. small: ""
  29. ,large: ""
  30. };
  31. };
  32. SlackTeam.prototype = Object.create(ChatInfo.prototype);
  33. SlackTeam.prototype.constructor = SlackTeam;
  34. SlackTeam.prototype.update = function(teamData, t) {
  35. ChatInfo.prototype.update.call(this, teamData, t);
  36. if (teamData["domain"] !== undefined) this.domain = teamData["domain"];
  37. if (teamData["prefs"]) {
  38. this.callApp = teamData["prefs"]["calling_app_id"];
  39. this.callAppName = teamData["prefs"]["calling_app_name"];
  40. this.fileUploadPermission = teamData["prefs"]["disable_file_uploads"];
  41. this.fileEditPermission = teamData["prefs"]["disable_file_editing"];
  42. this.fileDeletePermission = teamData["prefs"]["disable_file_deleting"];
  43. }
  44. if (teamData["icon"]) {
  45. this.icons.small = teamData["icon"]["image_34"];
  46. this.icons.large = teamData["icon"]["image_230"];
  47. }
  48. };
  49. /**
  50. * @constructor
  51. **/
  52. function SlackChan(teamId, chanId, isGroup) {
  53. Room.call(this, teamId +chanId);
  54. /** @const @type {string} */
  55. this.remoteId = chanId;
  56. this.isPrivate = isGroup;
  57. };
  58. SlackChan.prototype = Object.create(Room.prototype);
  59. SlackChan.prototype.constructor = SlackChan;
  60. SlackChan.prototype.setNameFromMembers = function() {
  61. var userNames = [];
  62. for (var i in this.users)
  63. if (!this.users[i].deleted)
  64. userNames.push(this.users[i].getName());
  65. userNames.sort();
  66. this.name = userNames.join(", ");
  67. };
  68. /**
  69. * @param {*} chanData
  70. * @param {ChatContext} ctx
  71. * @param {number} t
  72. * @param {string=} idPrefix
  73. **/
  74. SlackChan.prototype.update = function(chanData, ctx, t, idPrefix) {
  75. if (chanData["last_read"] !== undefined)
  76. chanData["last_read"] = parseFloat(chanData["last_read"]) * 1000;
  77. if (chanData["latest"])
  78. chanData["last_msg"] = parseFloat(chanData["latest"]) * 1000;
  79. if (!chanData["last_msg"] && chanData["unread_count"] !== undefined) {
  80. if (chanData["unread_count"])
  81. chanData["last_msg"] = Math.max(chanData["last_msg"] || 0, 0);
  82. else
  83. chanData["last_msg"] = chanData["last_read"];
  84. }
  85. return Room.prototype.update.call(this, chanData, ctx, t, idPrefix);
  86. };
  87. /**
  88. * @constructor
  89. * @extends {PrivateMessageRoom}
  90. * @param {string} id
  91. * @param {SlackChatter} user
  92. **/
  93. function SlackIms(teamId, id, user) {
  94. PrivateMessageRoom.call(this, teamId +id, user);
  95. /** @const @type {string} */
  96. this.remoteId = id;
  97. };
  98. SlackIms.prototype = Object.create(PrivateMessageRoom.prototype);
  99. SlackIms.prototype.constructor = SlackIms;
  100. /**
  101. * @param {*} chanData
  102. * @param {ChatContext} ctx
  103. * @param {number} t
  104. * @param {string=} idPrefix
  105. **/
  106. SlackIms.prototype.update = function(chanData, ctx, t, idPrefix) {
  107. if (chanData["last_read"] !== undefined)
  108. chanData["last_read"] = parseFloat(chanData["last_read"]) * 1000;
  109. if (chanData["latest"])
  110. chanData["last_msg"] = parseFloat(chanData["latest"]) * 1000;
  111. if (!chanData["last_msg"] && chanData["unread_count"] !== undefined) {
  112. if (chanData["unread_count"])
  113. chanData["last_msg"] = Math.max(chanData["last_msg"] || 0, 0);
  114. else
  115. chanData["last_msg"] = chanData["last_read"];
  116. }
  117. return PrivateMessageRoom.prototype.update.call(this, chanData, ctx, t, idPrefix);
  118. };
  119. /**
  120. * @constructor
  121. * @extends {Chatter}
  122. **/
  123. function SlackChatter(teamId, id) {
  124. Chatter.call(this, teamId +id);
  125. /** @type {string} */
  126. this.remoteId = id;
  127. /** @type {Object.<string, string>} */
  128. this.icons = {
  129. small: ""
  130. ,large: ""
  131. };
  132. };
  133. SlackChatter.prototype = Object.create(Chatter.prototype);
  134. SlackChatter.prototype.constructor = SlackChatter;
  135. SlackChatter.prototype.setPresence = function(presenceStr, t) {
  136. this.presence = presenceStr !== 'away';
  137. this.version = Math.max(t, this.version);
  138. };
  139. SlackChatter.prototype.update = function(userData, t) {
  140. if (userData["presence"] !== undefined)
  141. userData["isPresent"] = userData["presence"] !== 'away';
  142. if (userData["profile"]) {
  143. this.email = userData["profile"]["email"];
  144. this.firstName = userData["profile"]["first_name"];
  145. this.lastName = userData["profile"]["last_name"];
  146. userData["name"] = userData["profile"]["display_name"];
  147. userData["real_name"] = userData["profile"]["real_name"];
  148. userData["phone"] = userData["profile"]["phone"];
  149. userData["goal"] = userData["profile"]["title"];
  150. }
  151. Chatter.prototype.update.call(this, userData, t);
  152. };
  153. SlackChatter.prototype.getSmallIcon = function() {
  154. return this.icons.small;
  155. };
  156. SlackChatter.prototype.getLargeIcon = function() {
  157. return this.icons.large;
  158. };
  159. /**
  160. * @constructor
  161. * @extends {SlackChatter}
  162. **/
  163. function SlackUser(teamId, id) {
  164. SlackChatter.call(this, teamId, id);
  165. };
  166. SlackUser.prototype = Object.create(SlackChatter.prototype);
  167. SlackUser.prototype.constructor = SlackUser;
  168. SlackUser.prototype.update = function(userData, t) {
  169. SlackChatter.prototype.update.call(this, userData, t);
  170. if (userData["profile"]) {
  171. this.icons.small = userData["profile"]["image_48"] || this.icons.small;
  172. this.icons.large = userData["profile"]["image_512"] || this.icons.large;
  173. }
  174. };
  175. /**
  176. * @constructor
  177. * @extends {SlackChatter}
  178. **/
  179. function SlackBot(teamId, id) {
  180. SlackChatter.call(this, teamId, id);
  181. /** @type {string} */
  182. this.appId;
  183. this.isBot = true;
  184. };
  185. SlackBot.prototype = Object.create(SlackChatter.prototype);
  186. SlackBot.prototype.constructor = SlackBot;
  187. /** @param {*} botData */
  188. SlackBot.prototype.update = function(botData, t) {
  189. SlackChatter.prototype.update.call(this, botData, t);
  190. if (botData["app_id"] !== undefined) this.appId = botData["app_id"];
  191. this.isBot = true;
  192. if (botData["icons"]) {
  193. this.icons.small = botData["icons"]["image_48"] || this.icons.small;
  194. this.icons.large = botData["icons"]["image_72"] || this.icons.large;
  195. }
  196. };
  197. /**
  198. * @constructor
  199. * @extends {ChatContext}
  200. **/
  201. function SlackData(slack) {
  202. ChatContext.call(this);
  203. /**
  204. * Node serv handler
  205. * @type {*}
  206. **/
  207. this.slack = slack;
  208. this.capacities = {
  209. starMsg: true,
  210. pinMsg: true,
  211. starChannel: true,
  212. reactMsg: true,
  213. editMsg: true,
  214. //editOtherMsg: false, // ability to edit other chatter's msg
  215. removeMsg: true,
  216. //moderateMsg: false, // ability to remove other chatter's msg
  217. replyToMsg: true,
  218. topic: true,
  219. purpose: true
  220. };
  221. };
  222. SlackData.prototype = Object.create(ChatContext.prototype);
  223. SlackData.prototype.constructor = SlackData;
  224. /**
  225. * @param {*} data
  226. **/
  227. SlackData.prototype.updateStatic = function(data, t) {
  228. if (data["team"] && !this.team) this.team = this.teamFactory(data["team"]["id"]);
  229. if (data["bots"]) for (var i =0, nbBots = data["bots"].length; i < nbBots; i++) {
  230. var botObj = this.users[this.team.id +'|' +data["bots"][i]["id"]];
  231. if (!botObj)
  232. botObj = this.users[this.team.id +'|' +data["bots"][i]["id"]] = new SlackBot(this.team.id +'|', data["bots"][i]["id"]);
  233. botObj.update(data["bots"][i], t);
  234. }
  235. if (data.commands) {
  236. var aliasCmd = {};
  237. for (let i in data.commands)
  238. if (data.commands[i].canonical_name && data.commands[i].canonical_name !== i) {
  239. aliasCmd[data.commands[i].canonical_name] = {};
  240. Object.assign(aliasCmd[data.commands[i].canonical_name], data.commands[i]);
  241. aliasCmd[data.commands[i].canonical_name].name = data.commands[i].canonical_name;
  242. }
  243. for (let i in aliasCmd)
  244. data.commands[i] = aliasCmd[i];
  245. }
  246. ChatContext.prototype.updateStatic.call(this, data, t, this.team.id +'|');
  247. if (data["ims"]) for (var i =0, nbIms = data["ims"].length; i < nbIms; i++) {
  248. var user = this.users[this.team.id +'|' +data["ims"][i]["user"]];
  249. if (user) {
  250. if (!this.channels[this.team.id +'|' +data["ims"][i]["id"]])
  251. this.channels[this.team.id +'|' +data["ims"][i]["id"]] = new SlackIms(this.team.id +'|', data["ims"][i]["id"], user);
  252. this.channels[this.team.id +'|' +data["ims"][i]["id"]].update(data["ims"][i], this, t, this.team.id +'|');
  253. }
  254. }
  255. if (data["groups"])
  256. this.updateGroups(data["groups"], t);
  257. if (data["mpims"])
  258. this.updateGroups(data["mpims"], t);
  259. if (data["self"] && data["self"]["manual_presence"]) {
  260. this.self.setPresence(data["self"]["manual_presence"], t);
  261. }
  262. };
  263. SlackData.prototype.updateGroups = function(groupsData, t) {
  264. for (var i =0, nbGroups = groupsData.length; i < nbGroups; i++) {
  265. var groupData = groupsData[i]
  266. ,groupObj = this.channels[this.team.id +'|' +groupData["id"]];
  267. if (!groupObj)
  268. groupObj = this.channels[this.team.id +'|' +groupData["id"]] = new SlackChan(this.team.id +'|', groupData["id"], true);
  269. groupObj.update(groupData, this, t, this.team.id +'|');
  270. groupObj.archived |= groupData["is_open"] === false;
  271. groupObj.isMember = true;
  272. if (groupData["is_mpim"])
  273. groupObj.setNameFromMembers();
  274. }
  275. };
  276. SlackData.prototype.teamFactory = function(id) {
  277. return new SlackTeam('SLACK|' +id);
  278. };
  279. SlackData.prototype.userFactory = function(userData) {
  280. return new SlackUser(this.team.id +'|', userData["id"]);
  281. };
  282. SlackData.prototype.roomFactory = function(roomData) {
  283. roomData["created"] = roomData["created"] ? parseFloat(roomData["created"]) * 1000 : undefined;
  284. return new SlackChan(this.team.id +'|', roomData["id"], false);
  285. };
  286. SlackData.prototype.commandFactory = function(data) {
  287. var getServiceName = (function() {
  288. if (data["service_name"])
  289. return data["service_name"];
  290. else if (data["app"]) {
  291. for (var i in this.users)
  292. if (this.users[i].appId === data["app"] && this.users[i].name)
  293. return this.users[i].name;
  294. console.error("Unknown app " +data["app"]);
  295. return "";
  296. }
  297. return "Slack";
  298. }).bind(this);
  299. data["category"] = data["category"] || getServiceName();
  300. return new Command(data);
  301. };
  302. /**
  303. * @param {*} msg
  304. * @param {number} t
  305. **/
  306. SlackData.prototype.onMessage = function(msg, t) {
  307. if (msg["type"] === "presence_change") {
  308. var member = this.users[this.team.id +'|' +msg["user"]];
  309. if (member)
  310. member.setPresence(msg["presence"], t);
  311. this.staticV = Math.max(this.staticV, t);
  312. } else if (msg["type"] === "manual_presence_change") {
  313. if (this.self) {
  314. this.self.setPresence(msg["presence"], t);
  315. this.staticV = Math.max(this.staticV, t);
  316. }
  317. } else if (msg["type"] === "user_typing") {
  318. var chanId = this.team.id +'|' +msg["channel"];
  319. if (!this.typing[chanId])
  320. this.typing[chanId] = {};
  321. this.typing[chanId][this.team.id +'|' +msg["user"]] = t +SLACK_TYPING_DELAY;
  322. this.staticV = Math.max(this.staticV, t);
  323. } else if (msg["type"] === "im_marked" || msg["type"] === "channel_marked" || msg["type"] === "group_marked") {
  324. var channel = this.channels[this.team.id +'|' +msg["channel"]];
  325. if (channel) {
  326. channel.lastRead = parseFloat(msg["ts"]) * 1000;
  327. this.staticV = channel.version = Math.max(channel.version, t);
  328. }
  329. } else if (msg["type"] === "star_added") {
  330. if (msg["user"] === this.self.remoteId && msg["item"]["type"] !== "message") {
  331. var targetChan = this.getChannelByRemoteId(msg["item"]["channel"]);
  332. if (targetChan && !targetChan.starred) {
  333. targetChan.starred = true;
  334. targetChan.version = Math.max(targetChan.version, t);
  335. this.staticV = Math.max(this.staticV, t);
  336. }
  337. }
  338. } else if (msg["type"] === "star_removed") {
  339. if (msg["user"] === this.self.remoteId && msg["item"]["type"] !== "message") {
  340. var targetChan = this.getChannelByRemoteId(msg["item"]["channel"]);
  341. if (targetChan && targetChan.starred) {
  342. targetChan.starred = false;
  343. targetChan.version = Math.max(targetChan.version, t);
  344. this.staticV = Math.max(this.staticV, t);
  345. }
  346. }
  347. } else if (msg["type"] === "pin_added") {
  348. var targetChan = this.getChannelByRemoteId(msg["channel_id"] || msg["item"]["channel"]);
  349. if (!targetChan.pins) {
  350. this.slack.fetchPinned(targetChan);
  351. } else {
  352. var histo = this.slack.lazyHistory(targetChan);
  353. targetChan.pins.push(histo.messageFactory(msg["item"]["message"], t));
  354. targetChan.version = Math.max(targetChan.version, t);
  355. this.staticV = Math.max(this.staticV, t);
  356. }
  357. } else if (msg["type"] === "pin_removed") {
  358. var targetChan = this.getChannelByRemoteId(msg["channel_id"] || msg["item"]["channel"]);
  359. if (!msg["pin_count"]) {
  360. targetChan.pins = [];
  361. targetChan.version = Math.max(targetChan.version, t);
  362. this.staticV = Math.max(this.staticV, t);
  363. }
  364. if (!targetChan.pins) {
  365. this.slack.fetchPinned(targetChan);
  366. } else {
  367. var found = false;
  368. for (var i =0, nbPins = targetChan.pins.length; i < nbPins; i++) {
  369. if (targetChan.pins[i].id === msg["item"]["message"]["ts"]) {
  370. targetChan.pins.splice(i, 1);
  371. targetChan.version = Math.max(targetChan.version, t);
  372. this.staticV = Math.max(this.staticV, t);
  373. found = true;
  374. break;
  375. }
  376. }
  377. if (!found) // wtf out-of-sync
  378. this.slack.fetchPinned(targetChan);
  379. }
  380. /*
  381. } else {
  382. console.log(msg);
  383. //*/
  384. }
  385. };
  386. SlackData.prototype.getChannelByRemoteId = function(remoteId) {
  387. for (var id in this.channels)
  388. if (this.channels[id].remoteId === remoteId)
  389. return this.channels[id];
  390. };
  391. /**
  392. * @param {number} knownVersion
  393. * @return {Object|undefined}
  394. **/
  395. SlackData.prototype.getUpdates = function(knownVersion) {
  396. if (this.staticV > knownVersion)
  397. return this.toStatic(knownVersion);
  398. return undefined;
  399. };
  400. /** @suppress {undefinedVars,checkTypes} */
  401. (function() {
  402. if (typeof module !== "undefined") {
  403. module.exports.SlackData = SlackData;
  404. }
  405. })();