slackData.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  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].name);
  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"] !== undefined)
  78. chanData["last_msg"] = parseFloat(chanData["latest"]) * 1000;
  79. return Room.prototype.update.call(this, chanData, ctx, t, idPrefix);
  80. };
  81. /**
  82. * @constructor
  83. * @extends {PrivateMessageRoom}
  84. * @param {string} id
  85. * @param {SlackChatter} user
  86. **/
  87. function SlackIms(teamId, id, user) {
  88. PrivateMessageRoom.call(this, teamId +id, user);
  89. /** @const @type {string} */
  90. this.remoteId = id;
  91. };
  92. SlackIms.prototype = Object.create(PrivateMessageRoom.prototype);
  93. SlackIms.prototype.constructor = SlackIms;
  94. /**
  95. * @param {*} chanData
  96. * @param {ChatContext} ctx
  97. * @param {number} t
  98. * @param {string=} idPrefix
  99. **/
  100. SlackIms.prototype.update = function(chanData, ctx, t, idPrefix) {
  101. if (chanData["last_read"] !== undefined)
  102. chanData["last_read"] = parseFloat(chanData["last_read"]) * 1000;
  103. if (chanData["latest"] !== undefined)
  104. chanData["last_msg"] = parseFloat(chanData["latest"]) * 1000;
  105. return PrivateMessageRoom.prototype.update.call(this, chanData, ctx, t, idPrefix);
  106. };
  107. /**
  108. * @constructor
  109. * @extends {Chatter}
  110. **/
  111. function SlackChatter(teamId, id) {
  112. Chatter.call(this, teamId +id);
  113. /** @type {string} */
  114. this.remoteId = id;
  115. /** @type {Object.<string, string>} */
  116. this.icons = {
  117. small: ""
  118. ,large: ""
  119. };
  120. };
  121. SlackChatter.prototype = Object.create(Chatter.prototype);
  122. SlackChatter.prototype.constructor = SlackChatter;
  123. SlackChatter.prototype.setPresence = function(presenceStr, t) {
  124. this.presence = presenceStr !== 'away';
  125. this.version = Math.max(t, this.version);
  126. };
  127. SlackChatter.prototype.update = function(userData, t) {
  128. if (userData["presence"] !== undefined)
  129. userData["isPresent"] = userData["presence"] !== 'away';
  130. if (userData["profile"]) {
  131. this.email = userData["profile"]["email"];
  132. this.firstName = userData["profile"]["first_name"];
  133. this.lastName = userData["profile"]["last_name"];
  134. userData["name"] = userData["profile"]["display_name"];
  135. userData["real_name"] = userData["profile"]["real_name"];
  136. userData["phone"] = userData["profile"]["phone"];
  137. userData["goal"] = userData["profile"]["title"];
  138. }
  139. Chatter.prototype.update.call(this, userData, t);
  140. };
  141. SlackChatter.prototype.getSmallIcon = function() {
  142. return this.icons.small;
  143. };
  144. SlackChatter.prototype.getLargeIcon = function() {
  145. return this.icons.large;
  146. };
  147. /**
  148. * @constructor
  149. * @extends {SlackChatter}
  150. **/
  151. function SlackUser(teamId, id) {
  152. SlackChatter.call(this, teamId, id);
  153. };
  154. SlackUser.prototype = Object.create(SlackChatter.prototype);
  155. SlackUser.prototype.constructor = SlackUser;
  156. SlackUser.prototype.update = function(userData, t) {
  157. SlackChatter.prototype.update.call(this, userData, t);
  158. if (userData["profile"]) {
  159. this.icons.small = userData["profile"]["image_48"] || this.icons.small;
  160. this.icons.large = userData["profile"]["image_512"] || this.icons.large;
  161. }
  162. };
  163. /**
  164. * @constructor
  165. * @extends {SlackChatter}
  166. **/
  167. function SlackBot(teamId, id) {
  168. SlackChatter.call(this, teamId, id);
  169. /** @type {string} */
  170. this.appId;
  171. this.isBot = true;
  172. };
  173. SlackBot.prototype = Object.create(SlackChatter.prototype);
  174. SlackBot.prototype.constructor = SlackBot;
  175. /** @param {*} botData */
  176. SlackBot.prototype.update = function(botData, t) {
  177. SlackChatter.prototype.update.call(this, botData, t);
  178. if (botData["app_id"] !== undefined) this.appId = botData["app_id"];
  179. this.isBot = true;
  180. if (botData["icons"]) {
  181. this.icons.small = botData["icons"]["image_48"] || this.icons.small;
  182. this.icons.large = botData["icons"]["image_72"] || this.icons.large;
  183. }
  184. };
  185. /**
  186. * @constructor
  187. * @extends {ChatContext}
  188. **/
  189. function SlackData(slack) {
  190. ChatContext.call(this);
  191. /**
  192. * Node serv handler
  193. * @type {*}
  194. **/
  195. this.slack = slack;
  196. this.capacities = {
  197. starMessage: true,
  198. starChannel: true,
  199. msgReactions: true,
  200. editMsg: true,
  201. removeMsg: true,
  202. replyToMsg: true,
  203. topic: true,
  204. purpose: true
  205. };
  206. };
  207. SlackData.prototype = Object.create(ChatContext.prototype);
  208. SlackData.prototype.constructor = SlackData;
  209. /**
  210. * @param {*} data
  211. **/
  212. SlackData.prototype.updateStatic = function(data, t) {
  213. if (data["team"] && !this.team) this.team = this.teamFactory(data["team"]["id"]);
  214. if (data["bots"]) for (var i =0, nbBots = data["bots"].length; i < nbBots; i++) {
  215. var botObj = this.users[this.team.id +'|' +data["bots"][i]["id"]];
  216. if (!botObj)
  217. botObj = this.users[this.team.id +'|' +data["bots"][i]["id"]] = new SlackBot(this.team.id +'|', data["bots"][i]["id"]);
  218. botObj.update(data["bots"][i], t);
  219. }
  220. ChatContext.prototype.updateStatic.call(this, data, t, this.team.id +'|');
  221. if (data["ims"]) for (var i =0, nbIms = data["ims"].length; i < nbIms; i++) {
  222. var user = this.users[this.team.id +'|' +data["ims"][i]["user"]];
  223. if (user) {
  224. if (!this.channels[this.team.id +'|' +data["ims"][i]["id"]])
  225. this.channels[this.team.id +'|' +data["ims"][i]["id"]] = new SlackIms(this.team.id +'|', data["ims"][i]["id"], user);
  226. this.channels[this.team.id +'|' +data["ims"][i]["id"]].update(data["ims"][i], this, t, this.team.id +'|');
  227. }
  228. }
  229. if (data["groups"])
  230. this.updateGroups(data["groups"], t);
  231. if (data["mpims"])
  232. this.updateGroups(data["mpims"], t);
  233. if (data["self"] && data["self"]["manual_presence"]) {
  234. this.self.setPresence(data["self"]["manual_presence"], t);
  235. }
  236. };
  237. SlackData.prototype.updateGroups = function(groupsData, t) {
  238. for (var i =0, nbGroups = groupsData.length; i < nbGroups; i++) {
  239. var groupData = groupsData[i]
  240. ,groupObj = this.channels[this.team.id +'|' +groupData["id"]];
  241. if (!groupObj)
  242. groupObj = this.channels[this.team.id +'|' +groupData["id"]] = new SlackChan(this.team.id +'|', groupData["id"], true);
  243. groupObj.update(groupData, this, t, this.team.id +'|');
  244. groupObj.archived |= groupData["is_open"] === false;
  245. groupObj.isMember = true;
  246. if (groupData["is_mpim"])
  247. groupObj.setNameFromMembers();
  248. }
  249. };
  250. SlackData.prototype.teamFactory = function(id) {
  251. return new SlackTeam('SLACK|' +id);
  252. };
  253. SlackData.prototype.userFactory = function(userData) {
  254. return new SlackUser(this.team.id +'|', userData["id"]);
  255. };
  256. SlackData.prototype.roomFactory = function(roomData) {
  257. roomData["created"] = roomData["created"] ? parseFloat(roomData["created"]) * 1000 : undefined;
  258. return new SlackChan(this.team.id +'|', roomData["id"], false);
  259. };
  260. SlackData.prototype.commandFactory = function(data) {
  261. var getServiceName = (function() {
  262. if (data["service_name"])
  263. return data["service_name"];
  264. else if (data["app"]) {
  265. for (var i in this.users)
  266. if (this.users[i].appId === data["app"] && this.users[i].name)
  267. return this.users[i].name;
  268. console.error("Unknown app " +data["app"]);
  269. return "";
  270. }
  271. return "Slack";
  272. }).bind(this);
  273. data["category"] = data["category"] || getServiceName();
  274. return new Command(data);
  275. };
  276. /**
  277. * @param {*} msg
  278. * @param {number} t
  279. **/
  280. SlackData.prototype.onMessage = function(msg, t) {
  281. if (msg["type"] === "presence_change") {
  282. var member = this.users[this.team.id +'|' +msg["user"]];
  283. if (member)
  284. member.setPresence(msg["presence"], t);
  285. this.staticV = Math.max(this.staticV, t);
  286. } else if (msg["type"] === "manual_presence_change") {
  287. if (this.self) {
  288. this.self.setPresence(msg["presence"], t);
  289. this.staticV = Math.max(this.staticV, t);
  290. }
  291. } else if (msg["type"] === "user_typing") {
  292. var chanId = this.team.id +'|' +msg["channel"];
  293. if (!this.typing[chanId])
  294. this.typing[chanId] = {};
  295. this.typing[chanId][this.team.id +'|' +msg["user"]] = t +SLACK_TYPING_DELAY;
  296. this.staticV = Math.max(this.staticV, t);
  297. } else if (msg["type"] === "im_marked" || msg["type"] === "channel_marked" || msg["type"] === "group_marked") {
  298. var channel = this.channels[this.team.id +'|' +msg["channel"]];
  299. if (channel) {
  300. channel.lastRead = parseFloat(msg["ts"]) * 1000;
  301. this.staticV = channel.version = Math.max(channel.version, t);
  302. }
  303. } else if (msg["type"] === "star_added") {
  304. if (msg["user"] === this.self.remoteId) {
  305. var target = this.getChannelByRemoteId(msg["item"]["channel"]);
  306. if (target && !target.starred) {
  307. target.starred = true;
  308. target.version = Math.max(target.version, t);
  309. this.staticV = Math.max(this.staticV, t);
  310. }
  311. }
  312. } else if (msg["type"] === "star_removed") {
  313. if (msg["user"] === this.self.remoteId) {
  314. var target = this.getChannelByRemoteId(msg["item"]["channel"]);
  315. if (target && target.starred) {
  316. target.starred = false;
  317. target.version = Math.max(target.version, t);
  318. this.staticV = Math.max(this.staticV, t);
  319. }
  320. }
  321. /*
  322. } else {
  323. console.log(msg);
  324. //*/
  325. }
  326. };
  327. SlackData.prototype.getChannelByRemoteId = function(remoteId) {
  328. for (var id in this.channels)
  329. if (this.channels[id].remoteId === remoteId)
  330. return this.channels[id];
  331. };
  332. /**
  333. * @param {number} knownVersion
  334. * @return {Object|undefined}
  335. **/
  336. SlackData.prototype.getUpdates = function(knownVersion) {
  337. if (this.staticV > knownVersion)
  338. return this.toStatic(knownVersion);
  339. return undefined;
  340. };
  341. /** @suppress {undefinedVars,checkTypes} */
  342. (function() {
  343. if (typeof module !== "undefined") {
  344. module.exports.SlackData = SlackData;
  345. }
  346. })();