context.js 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. /** @const */
  2. var TYPING_DELAY = 6500;
  3. /**
  4. * @constructor
  5. **/
  6. function ChatInfo(id) {
  7. /** @const @type {string} */
  8. this.id = id;
  9. /** @type {string} */
  10. this.name;
  11. /** @type {number} */
  12. this.version = 0;
  13. };
  14. ChatInfo.prototype.toStatic = function(t) {
  15. return t >= this.version ? undefined : {
  16. "id": this.id
  17. ,"name": this.name
  18. };
  19. };
  20. ChatInfo.prototype.update = function(data, t) {
  21. if (data["name"] !== undefined) this.name = data["name"];
  22. this.version = Math.max(this.version, t);
  23. };
  24. /**
  25. * @constructor
  26. * @param {*} data
  27. **/
  28. function Command(data) {
  29. /** @const @type {string} */
  30. this.desc = data["desc"];
  31. /** @const @type {string} */
  32. this.name = data["name"];
  33. /** @const @type {string} */
  34. this.type = data["type"];
  35. /** @const @type {string} */
  36. this.usage = data["usage"];
  37. /** @const @type {string} */
  38. this.category = data["category"];
  39. };
  40. /**
  41. * @param {number} t
  42. * @return {Object}
  43. **/
  44. Command.prototype.toStatic = function(t) {
  45. return {
  46. "desc": this.desc
  47. ,"name": this.name
  48. ,"type": this.type
  49. ,"usage": this.usage
  50. ,"category": this.category
  51. };
  52. };
  53. /**
  54. * @constructor
  55. **/
  56. function SelfPreferences() {
  57. /** @type {Object.<string, number>} */
  58. this.favoriteEmojis = {};
  59. /** @type {Array.<string>} */
  60. this.highlights = [];
  61. /** @type {number} */
  62. this.version = 0;
  63. };
  64. /**
  65. * @param {*} prefs
  66. **/
  67. SelfPreferences.prototype.update = function(prefs, t) {
  68. if (prefs["emoji_use"])
  69. this.favoriteEmojis = /** @type {Object<string,number>} */ (JSON.parse(prefs["emoji_use"]));
  70. if (prefs["highlight_words"])
  71. this.highlights = (prefs["highlight_words"]||"").split(',').filter(function(i) {
  72. return i.trim() !== '';
  73. });
  74. else if (prefs["highlights"])
  75. this.highlights = prefs["highlights"];
  76. this.version = Math.max(this.version, t);
  77. };
  78. SelfPreferences.prototype.toStatic = function(t) {
  79. return this.version <= t ? undefined : {
  80. "emoji_use": JSON.stringify(this.favoriteEmojis)
  81. ,"highlights": this.highlights
  82. };
  83. };
  84. /**
  85. * @constructor
  86. **/
  87. function ChatContext() {
  88. /** @type {ChatInfo} */
  89. this.team = null;
  90. /** @type {Object.<string, Room>} */
  91. this.channels = {};
  92. /** @type {Object.<string, Chatter>} */
  93. this.users = {};
  94. /** @type {Chatter} */
  95. this.self = null;
  96. /** @type {{version: number, data: Object.<string, string>}} */
  97. this.emojis = { version: 0, data: {} };
  98. /** @type {{version: number, data: Object.<string, Command>}} */
  99. this.commands = {version: 0, data: {} };
  100. /** @type {Object.<string, Object.<string, number>>} */
  101. this.typing = {};
  102. /** @type {Object<string, boolean>!} capacities */
  103. this.capacities = {};
  104. /** @type {number} */
  105. this.staticV = 0;
  106. /** @type {number} */
  107. this.liveV = 0;
  108. /** @type {number} */
  109. this.typingVersion = 0;
  110. };
  111. ChatContext.prototype.userFactory = function(userData) {
  112. return new Chatter(userData["id"]);
  113. };
  114. ChatContext.prototype.roomFactory = function(roomData) {
  115. return roomData["pv"] ?
  116. new PrivateMessageRoom(roomData["id"], this.users[roomData["user"]]) :
  117. new Room(roomData["id"]);
  118. };
  119. ChatContext.prototype.teamFactory = function(id) {
  120. return new ChatInfo(id);
  121. };
  122. ChatContext.prototype.commandFactory = function(data) {
  123. return new Command(data);
  124. };
  125. /**
  126. * @param {*} data
  127. * @param {number} t
  128. * @param {string=} idPrefix
  129. **/
  130. ChatContext.prototype.updateStatic = function(data, t, idPrefix) {
  131. idPrefix = idPrefix || "";
  132. if (data["team"]) {
  133. if (!this.team) this.team = this.teamFactory(data["team"]["id"]);
  134. this.team.update(data["team"], t);
  135. }
  136. if (data["users"]) for (var i =0, nbUsers = data["users"].length; i < nbUsers; i++) {
  137. var userObj = this.users[idPrefix +data["users"][i]["id"]];
  138. if (!userObj)
  139. userObj = this.users[idPrefix +data["users"][i]["id"]] = this.userFactory(data["users"][i]);
  140. userObj.update(data["users"][i], t);
  141. }
  142. if (data["channels"]) for (var i =0, nbChan = data["channels"].length; i < nbChan; i++) {
  143. var chanObj = this.channels[idPrefix +data["channels"][i]["id"]];
  144. if (!chanObj)
  145. chanObj = this.channels[idPrefix +data["channels"][i]["id"]] = this.roomFactory(data["channels"][i]);
  146. chanObj.update(data["channels"][i], this, t, idPrefix);
  147. }
  148. if (data["emojis"]) {
  149. this.emojis.data = data["emojis"];
  150. this.emojis.version = t;
  151. }
  152. if (data["commands"] !== undefined){
  153. this.commands.data = {};
  154. for (var i in data["commands"]) {
  155. this.commands.data[i] = this.commandFactory(data["commands"][i]);
  156. }
  157. this.commands.version = t;
  158. }
  159. if (data["self"]) {
  160. this.self = this.users[idPrefix +data["self"]["id"]] || null;
  161. if (!this.self.prefs)
  162. this.self.prefs = new SelfPreferences();
  163. if (data["self"]["prefs"]) {
  164. this.self.prefs.update(data["self"]["prefs"], t);
  165. }
  166. }
  167. if (data["capacities"]) {
  168. this.capacities = {};
  169. data["capacities"].forEach(function(i) {
  170. this.capacities[i] = true;
  171. }, this);
  172. }
  173. this.staticV = Math.max(this.staticV, t);
  174. };
  175. ChatContext.prototype.updateTyping = function(typing, now) {
  176. var updated = false;
  177. if (this.typing)
  178. for (var i in this.typing)
  179. if (typing && !typing[i]) {
  180. delete (this.typing[i]);
  181. updated = true;
  182. }
  183. if (typing) {
  184. for (var i in typing) {
  185. if (this.channels[i]) {
  186. if (!this.typing[i])
  187. this.typing[i] = {};
  188. for (var j in typing[i]) {
  189. if (!this.typing[i][j])
  190. updated = true;
  191. this.typing[i][j] = now;
  192. }
  193. }
  194. }
  195. }
  196. if (updated)
  197. this.typingVersion = now;
  198. return updated;
  199. };
  200. /**
  201. * @param {number} t
  202. **/
  203. ChatContext.prototype.toStatic = function(t) {
  204. var channels = []
  205. ,users = [];
  206. var res = {
  207. "team": this.team.toStatic(t)
  208. ,"self": {
  209. "id": this.self.id
  210. ,"prefs": this.self.prefs ? this.self.prefs.toStatic(t) : undefined
  211. }
  212. ,"emojis": this.emojis.version > t ? this.emojis.data : undefined
  213. };
  214. if (this.staticV > t) {
  215. res["capacities"] = Object.keys(this.capacities);
  216. }
  217. if (this.commands.version > t) {
  218. res["commands"] = {};
  219. for (var i in this.commands.data)
  220. res["commands"][i] = this.commands.data[i].toStatic(t);
  221. }
  222. for (var chanId in this.channels) {
  223. var chan = this.channels[chanId].toStatic(t);
  224. if (chan)
  225. channels.push(chan);
  226. }
  227. if (channels.length)
  228. res["channels"] = channels;
  229. for (var userId in this.users) {
  230. var user = this.users[userId].toStatic(t);
  231. if (user)
  232. users.push(user);
  233. }
  234. if (users.length)
  235. res["users"] = users;
  236. return res;
  237. };
  238. /**
  239. * @param {number} version
  240. * @return {Object<Object<string, number>>|undefined}
  241. **/
  242. ChatContext.prototype.getWhoIsTyping = function(version, now) {
  243. var res;
  244. this.cleanTyping(now);
  245. if (this.typingVersion > version) {
  246. res = {};
  247. for (var typingChan in this.typing) {
  248. res[typingChan] = {};
  249. for (var typingUser in this.typing[typingChan])
  250. res[typingChan][typingUser] = 1;
  251. }
  252. }
  253. return res;
  254. };
  255. /**
  256. * @param {string} chanNameOrUserName
  257. * @return {Room|null}
  258. **/
  259. ChatContext.prototype.getRoom = function(chanNameOrUserName) {
  260. var chans = this.getChannelsWithName(chanNameOrUserName);
  261. if (chans.length)
  262. return chans[0];
  263. var users = this.getUsersWithName(chanNameOrUserName);
  264. if (users.length)
  265. for (var i =0, nbUsers = users.length; i < nbUsers; i++)
  266. if (users[i].privateRoom)
  267. return users[i].privateRoom;
  268. return null;
  269. };
  270. ChatContext.prototype.getChannelsWithName = function(name) {
  271. var chans = [];
  272. if (name[0] === '#')
  273. name = name.substr(1);
  274. for (var i in this.channels)
  275. if (this.channels[i].name === name)
  276. chans.push(this.channels[i]);
  277. return chans;
  278. };
  279. ChatContext.prototype.getUsersWithName = function(name) {
  280. var users = [];
  281. if (name[0] === '@')
  282. name = name.substr(1);
  283. for (var i in this.users)
  284. if (this.users[i].getName() === name)
  285. users.push(this.channels[i]);
  286. return users;
  287. };
  288. /**
  289. * @param {number} now
  290. **/
  291. ChatContext.prototype.cleanTyping = function(now) {
  292. var updated = false;
  293. for (var typingChan in this.typing) {
  294. var chanEmpty = true;
  295. for (var typingUser in this.typing[typingChan]) {
  296. if (this.typing[typingChan][typingUser] +TYPING_DELAY < now) {
  297. delete this.typing[typingChan][typingUser];
  298. updated = true;
  299. } else {
  300. chanEmpty = false;
  301. }
  302. }
  303. if (chanEmpty) {
  304. delete this.typing[typingChan];
  305. updated = true;
  306. }
  307. }
  308. if (updated)
  309. this.typingVersion = now;
  310. return updated;
  311. };
  312. ChatContext.prototype.stopTyping = function(chanId, userId, t) {
  313. if (this.typing[chanId] && this.typing[chanId][userId]) {
  314. delete this.typing[chanId][userId];
  315. var empty = true;
  316. for (var i in this.typing[chanId]) {
  317. empty = false;
  318. break;
  319. }
  320. if (empty)
  321. delete this.typing[chanId];
  322. this.typingVersion = t;
  323. }
  324. };
  325. /** @suppress {undefinedVars,checkTypes} */
  326. (function() {
  327. if (typeof module !== "undefined") {
  328. module.exports.ChatContext = ChatContext;
  329. module.exports.ChatInfo = ChatInfo;
  330. module.exports.Command = Command;
  331. }
  332. })();