message.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. /**
  2. * Abstract class Message
  3. * @constructor
  4. * @param {*} e
  5. * @param {number} ts
  6. **/
  7. function Message(e, ts) {
  8. /** @const @type {string} **/
  9. this.userId = e["user"];
  10. /** @type {string} */
  11. this.username = e["userName"];
  12. /** @const @type {string} **/
  13. this.id = e["id"] || e["ts"];
  14. /** @const @type {number} **/
  15. this.ts = parseFloat(e["ts"]);
  16. /** @type {string} */
  17. this.text = "";
  18. /** @type {Array.<*>} */
  19. this.attachments = [];
  20. /** @type {boolean} */
  21. this.starred = false;
  22. /** @type {boolean} */
  23. this.pinned = false;
  24. /** @type {boolean} */
  25. this.edited = false;
  26. /** @type {boolean} */
  27. this.removed = false;
  28. /**
  29. * for each emoji code, an array of user id
  30. * @type {Object.<string, Array.<string>>}
  31. **/
  32. this.reactions = {};
  33. /** @type {number} */
  34. this.version = ts;
  35. this.update(e, ts);
  36. };
  37. /**
  38. * @constructor
  39. * @extends {Message}
  40. **/
  41. function MeMessage(e, ts) {
  42. Message.call(this, e, ts);
  43. };
  44. /**
  45. * @constructor
  46. * @extends {Message}
  47. **/
  48. function NoticeMessage(e, ts) {
  49. Message.call(this, e, ts);
  50. };
  51. Message.prototype.update = function(e, ts) {
  52. if (e) {
  53. this.text = e["text"] || "";
  54. if (e["attachments"]) this.attachments = e["attachments"];
  55. this.starred = !!e["is_starred"];
  56. this.pinned = false; // TODO
  57. this.edited = !!e["edited"];
  58. this.removed = !!e["removed"];
  59. var _this = this;
  60. this.reactions = {};
  61. (e["reactions"] || []).forEach(function(r) {
  62. _this.reactions[r["name"]] = [];
  63. r["users"].forEach(function(userId) {
  64. _this.reactions[r["name"]].push(userId);
  65. });
  66. });
  67. } else {
  68. this.removed = true;
  69. }
  70. this.version = ts;
  71. };
  72. /** @return {*} */
  73. Message.prototype.toStatic = function() {
  74. var reactions = [];
  75. for (var reaction in this.reactions) {
  76. reactions.push({
  77. "name": reaction
  78. ,"users": this.reactions[reaction]
  79. });
  80. }
  81. return {
  82. "id": this.id
  83. ,"user": this.userId
  84. ,"username": this.username
  85. ,"ts": this.ts
  86. ,"text": this.text
  87. ,"attachments": this.attachments.length ? this.attachments : undefined
  88. ,"is_starred": this.is_starred || undefined
  89. ,"edited": this.edited || undefined
  90. ,"removed": this.removed || undefined
  91. ,"reactions": reactions.length ? reactions : undefined
  92. ,"isMeMessage": this instanceof MeMessage || undefined
  93. ,"isNotice": this instanceof NoticeMessage || undefined
  94. };
  95. };
  96. /**
  97. * @param {string} reaction
  98. * @param {string} userId
  99. * @param {number} ts
  100. **/
  101. Message.prototype.addReaction = function(reaction, userId, ts) {
  102. if (!this.reactions[reaction])
  103. this.reactions[reaction] = [];
  104. this.reactions[reaction].push(userId);
  105. this.version = ts;
  106. };
  107. /**
  108. * @param {string} reaction
  109. * @param {string} userId
  110. * @param {number} ts
  111. **/
  112. Message.prototype.removeReaction = function(reaction, userId, ts) {
  113. var updated = false;
  114. if (this.reactions[reaction]) {
  115. if (this.reactions[reaction].length === 1 && this.reactions[reaction][0] === userId) {
  116. delete this.reactions[reaction];
  117. updated = true;
  118. } else {
  119. this.reactions[reaction] = this.reactions[reaction].filter(function(i) {
  120. if (i !== userId)
  121. return true;
  122. updated = true;
  123. return false;
  124. });
  125. }
  126. }
  127. if (updated)
  128. this.version = ts;
  129. };
  130. Message.prototype.hasReactionForUser = function(reaction, userId) {
  131. if (this.reactions[reaction])
  132. return this.reactions[reaction].indexOf(userId) !== -1;
  133. return false;
  134. };
  135. /**
  136. * @constructor
  137. * @param {Room|string} room or roomId
  138. * @param {number} keepMessages number of messages to keep in memory
  139. * @param {Array|undefined} evts
  140. * @param {number|undefined} now
  141. **/
  142. function RoomHistory(room, keepMessages, evts, now) {
  143. /** @type {string} */
  144. this.id = typeof room === "string" ? room : room.id;
  145. /** @type {Array.<Message>} */
  146. this.messages = [];
  147. /** @type number */
  148. this.v = 0;
  149. /** @const @type {number} */
  150. this.keepMessages = keepMessages;
  151. if (evts) this.pushAll(evts, now);
  152. };
  153. /**
  154. * @param {Array} evts
  155. * @return {number} biggest ts
  156. **/
  157. RoomHistory.prototype.pushAll = function(evts, t) {
  158. var result = 0;
  159. evts.forEach(function(e) {
  160. result = Math.max(this.push(e, t), result);
  161. }.bind(this));
  162. this.resort();
  163. return result;
  164. };
  165. /**
  166. * @param {*} ev
  167. * @param {number} ts
  168. * @return {Message}
  169. **/
  170. RoomHistory.prototype.messageFactory = function(ev, ts) {
  171. if (ev["isMeMessage"] === true)
  172. return new MeMessage(ev, ts);
  173. if (ev["isNotice"] === true)
  174. return new NoticeMessage(ev, ts);
  175. return new Message(ev, ts);
  176. };
  177. /**
  178. * @param {*} e
  179. * @return {number} t
  180. **/
  181. RoomHistory.prototype.push = function(e, t) {
  182. var exists = false
  183. ,ts;
  184. for (var i =0, nbMsg = this.messages.length; i < nbMsg; i++) {
  185. var msgObj = this.messages[i];
  186. if (msgObj.id === e["id"]) {
  187. ts = msgObj.update(e, t);
  188. exists = true;
  189. break;
  190. }
  191. }
  192. if (!exists) {
  193. var msgObj = this.messageFactory(e, t);
  194. this.messages.push(msgObj);
  195. ts = msgObj.ts;
  196. }
  197. while (this.messages.length > this.keepMessages)
  198. this.messages.shift();
  199. return ts || 0;
  200. };
  201. RoomHistory.prototype.lastMessage = function() {
  202. return this.messages[this.messages.length -1];
  203. };
  204. /**
  205. * @param {string} reaction
  206. * @param {string} userId
  207. * @param {string} msgId
  208. * @param {number} ts
  209. * @return {Message|null}
  210. **/
  211. RoomHistory.prototype.addReaction = function(reaction, userId, msgId, ts) {
  212. var msg = this.getMessageById(msgId);
  213. if (msg)
  214. msg.addReaction(reaction, userId, ts);
  215. return msg;
  216. };
  217. /**
  218. * @param {string} reaction
  219. * @param {string} userId
  220. * @param {string} msgId
  221. * @param {number} ts
  222. * @return {Message|null}
  223. **/
  224. RoomHistory.prototype.removeReaction = function(reaction, userId, msgId, ts) {
  225. var msg = this.getMessageById(msgId);
  226. if (msg)
  227. msg.removeReaction(reaction, userId, ts);
  228. return msg;
  229. };
  230. /**
  231. * @param {number} ts
  232. * @return {Message|null}
  233. **/
  234. RoomHistory.prototype.getMessage = function(ts) {
  235. for (var i =0, nbMessages = this.messages.length; i < nbMessages && ts >= this.messages[i].ts; i++) {
  236. if (this.messages[i].ts === ts)
  237. return this.messages[i];
  238. }
  239. return null;
  240. };
  241. /**
  242. * @param {string} id
  243. * @return {Message|null}
  244. **/
  245. RoomHistory.prototype.getMessageById = function(id) {
  246. for (var i =0, nbMessages = this.messages.length; i < nbMessages; i++) {
  247. if (this.messages[i].id == id)
  248. return this.messages[i];
  249. }
  250. return null;
  251. };
  252. /**
  253. * @return {Array.<*>}
  254. **/
  255. RoomHistory.prototype.toStatic = function(knownVersion) {
  256. var result = [];
  257. for (var i = this.messages.length -1; i >= 0; i--) {
  258. if (this.messages[i].version > knownVersion)
  259. result.push(this.messages[i].toStatic());
  260. }
  261. return result;
  262. };
  263. RoomHistory.prototype.resort = function() {
  264. this.messages.sort(function(a, b) {
  265. return a.ts -b.ts;
  266. });
  267. };
  268. MeMessage.prototype = Object.create(Message.prototype);
  269. MeMessage.prototype.constructor = MeMessage;
  270. NoticeMessage.prototype = Object.create(Message.prototype);
  271. NoticeMessage.prototype.constructor = NoticeMessage;
  272. /** @suppress {undefinedVars,checkTypes} */
  273. (function() {
  274. if (typeof module !== "undefined") module.exports = {
  275. Message: Message
  276. ,MeMessage: MeMessage
  277. ,NoticeMessage: NoticeMessage
  278. ,RoomHistory: RoomHistory
  279. }
  280. })();