Browse Source

[add][Closes #30] Manage edited messages and remove deleted ones
[add] Hide removed users / chans
[add] Removed raw data from SlackMessage
[add] SlackMessage contains an id and a version
[add] SlackMessage edited, removed, isMeMessage atributes
[add] SlackMessage now hold its reactions server-side

B Thibault 8 years ago
parent
commit
4bd1a75df9
10 changed files with 234 additions and 127 deletions
  1. 13 12
      cli/data.js
  2. 1 0
      cli/lang/en.js
  3. 1 0
      cli/lang/fr.js
  4. 1 0
      cli/resources.js
  5. 41 29
      cli/ui.js
  6. 2 2
      cli/workflow.js
  7. 49 47
      srv/public/slack.min.js
  8. 1 2
      srv/src/slack.js
  9. 6 2
      srv/src/slackData.js
  10. 119 33
      srv/src/slackHistory.js

+ 13 - 12
cli/data.js

@@ -42,9 +42,12 @@ SlackWrapper.prototype.update = function(data) {
                 history.pushAll(data["live"][i]);
         }
         for (var roomId in data["live"]) {
-            onMsgReceived(this.context.getChannel(roomId), data["live"][roomId]);
-            if (SELECTED_ROOM && data["live"][SELECTED_ROOM.id])
-                onRoomUpdated();
+            var chan = this.context.getChannel(roomId);
+            if (chan && !chan.archived) {
+                onMsgReceived(chan, data["live"][roomId]);
+                if (SELECTED_ROOM && data["live"][SELECTED_ROOM.id])
+                    onRoomUpdated();
+            }
         }
     }
 };
@@ -68,7 +71,7 @@ function isHighlighted(text) {
  * @param {Array.<*>} msg
 **/
 function onMsgReceived(chan, msg) {
-    if (chan && (chan !== SELECTED_ROOM || !window.hasFocus)) {
+    if (chan !== SELECTED_ROOM || !window.hasFocus) {
         var selfReg = new RegExp("<@" +SLACK.context.self.id)
             ,highligted = false
             ,newHighlited = false;
@@ -77,14 +80,12 @@ function onMsgReceived(chan, msg) {
         }
         msg.forEach(function(i) {
             // TODO check read
-            if (i.type === "message" && i.text) {
-                if (chan.id[0] === 'D' || i.text.match(selfReg) || isHighlighted(i.text)) {
-                    newHighlited |= !UNREAD_CHANS[chan.id].hl;
-                    UNREAD_CHANS[chan.id].hl++;
-                    highligted = true;
-                } else {
-                    UNREAD_CHANS[chan.id].unread++;
-                }
+            if (chan.id[0] === 'D' || i.text.match(selfReg) || isHighlighted(i.text)) {
+                newHighlited |= !UNREAD_CHANS[chan.id].hl;
+                UNREAD_CHANS[chan.id].hl++;
+                highligted = true;
+            } else {
+                UNREAD_CHANS[chan.id].unread++;
             }
         });
         updateTitle();

+ 1 - 0
cli/lang/en.js

@@ -3,6 +3,7 @@ lang["en"] = {
     ,unknownChannel: "Unknown channel"
     ,newMessage: "New message"
     ,netErrorShort: "Network"
+    ,edited: "edited"
 
     ,dom: {
         "fileUploadCancel": "Cancel"

+ 1 - 0
cli/lang/fr.js

@@ -3,6 +3,7 @@ lang["fr"] = {
     ,unknownChannel: "Channel inconnu"
     ,newMessage: "Nouveau message"
     ,netErrorShort: "Reseau"
+    ,edited: "edit&eacute;"
 
     ,dom: {
         "fileUploadCancel": "Annuler"

+ 1 - 0
cli/resources.js

@@ -66,6 +66,7 @@ var R = {
             ,authorname: "slackmsg-author-name"
             ,authorAvatar: "slackmsg-author-img"
             ,msg: "slackmsg-msg"
+            ,edited: "slackmsg-edited"
 
             ,hover: {
                 container: "slackmsg-hover"

+ 41 - 29
cli/ui.js

@@ -66,13 +66,19 @@ function onContextUpdated() {
         if (a[0] !== b[0]) {
             return a[0] - b[0];
         }
-        return (SLACK.context.channels[a] || SLACK.context.groups[a]).name.localeCompare((SLACK.context.channels[b] || SLACK.context.groups[b]).name);
+        return SLACK.context.getChannel(a).name.localeCompare(SLACK.context.getChannel(b).name);
     });
     sortedChans.forEach(function(chanId) {
-        var chan = SLACK.context.channels[chanId] || SLACK.context.groups[chanId]
-            ,chanListItem = createChanListItem(chan);
-        if (chanListItem) {
-            chanListFram.appendChild(chanListItem);
+        var chan =
+            /**
+             * SortedChan does not contains ims ids
+             * @type {SlackChan|SlackGroup}
+            **/
+            (SLACK.context.getChannel(chanId));
+        if (!chan.archived) {
+            var chanListItem = createChanListItem(chan);
+            if (chanListItem)
+                chanListFram.appendChild(chanListItem);
         }
     });
     var sortedUsers = SLACK.context.users ? Object.keys(SLACK.context.users) : [];
@@ -80,11 +86,15 @@ function onContextUpdated() {
         return SLACK.context.users[a].name.localeCompare(SLACK.context.users[b].name);
     });
     sortedUsers.forEach(function(userId) {
-        var ims = SLACK.context.users[userId].ims
-            ,imsListItem = createImsListItem(ims);
+        var user = SLACK.context.getMember(userId);
 
-        if (imsListItem) {
-            chanListFram.appendChild(imsListItem);
+        if (!user.deleted) {
+            var ims = user.ims
+                ,imsListItem = createImsListItem(ims);
+
+            if (imsListItem) {
+                chanListFram.appendChild(imsListItem);
+            }
         }
     });
     document.getElementById(R.id.chanList).textContent = "";
@@ -210,9 +220,7 @@ function doCreateMessageDom(channelId, msg, skipAttachment) {
         ,hoverReply = document.createElement("li")
         ,attachments = document.createElement("ul")
         ,reactions = document.createElement("ul")
-        ,sender = msg.raw["user"] ?
-            SLACK.context.users[msg.raw["user"]] :
-            SLACK.context.bots[msg.raw["bot_id"]];
+        ,sender = SLACK.context.getMember(msg.userId);
 
     dom.id = channelId +"_" +msg.ts;
     dom.className = R.klass.msg.item;
@@ -224,10 +232,8 @@ function doCreateMessageDom(channelId, msg, skipAttachment) {
     hover.className = R.klass.msg.hover.container;
     hoverReply.className = R.klass.msg.hover.reply;
     ts.innerHTML = formatDate(msg.ts);
-    text.innerHTML = formatSlackText(msg.raw["text"] || "");
-    authorName.textContent = sender ? sender.name : (msg.raw["username"] || "?");
-    if (!sender && !msg.raw["username"])
-        text.textContent = msg.raw["subtype"] || JSON.stringify(msg.raw);
+    text.innerHTML = formatSlackText(msg.text);
+    authorName.textContent = sender ? sender.name : (msg.username || "?");
     authorImg.src = sender ? sender.icons.image_48 : "";
     author.appendChild(authorImg);
     author.appendChild(authorName);
@@ -261,6 +267,12 @@ function doCreateMessageDom(channelId, msg, skipAttachment) {
     dom.appendChild(text);
     dom.appendChild(ts);
     dom.appendChild(attachments);
+    if (msg.edited) {
+        var edited = document.createElement("div");
+        edited.textContent = locale.edited;
+        edited.className = R.klass.msg.edited;
+        dom.appendChild(edited);
+    }
     dom.appendChild(reactions);
     attachments.className = R.klass.msg.attachment.list;
     reactions.className = R.klass.msg.reactions.container;
@@ -269,13 +281,11 @@ function doCreateMessageDom(channelId, msg, skipAttachment) {
             var reac = createReactionDom(channelId, msg.id, reaction, msg.reactions[reaction]);
             reac && reactions.appendChild(reac);
         }
-        if (msg.raw["attachments"]) {
-            msg.raw["attachments"].forEach(function(attachment) {
-                var domAttachment = createAttachmentDom(channelId, msg, attachment);
-                if (domAttachment)
-                    attachments.appendChild(domAttachment);
-            });
-        }
+        msg.attachments.forEach(function(attachment) {
+            var domAttachment = createAttachmentDom(channelId, msg, attachment);
+            if (domAttachment)
+                attachments.appendChild(domAttachment);
+        });
     }
     dom.appendChild(hover);
     return dom;
@@ -639,10 +649,12 @@ function doCreateMeMessageDom(channelId, msg, skipAttachment) {
  * @return {Element}
 **/
 function createMessageDom(channelId, msg, skipAttachment) {
-    if (msg.subtype === "me_message") {
-        return doCreateMeMessageDom(channelId, msg, skipAttachment);
-    }
-    return doCreateMessageDom(channelId, msg, skipAttachment);
+    var dom = (msg.isMeMessage ?
+        doCreateMeMessageDom(channelId, msg, skipAttachment):
+        doCreateMessageDom(channelId, msg, skipAttachment));
+    if (msg.edited)
+        dom.classList.add(R.klass.msg.edited);
+    return dom;
 }
 
 /**
@@ -708,15 +720,15 @@ function onRoomUpdated() {
     var chatFrag = document.createDocumentFragment()
         ,currentRoomId = SELECTED_ROOM.id;
 
-    document.getElementById(R.id.currentRoom.content).textContent = "";
     if (SLACK.history[currentRoomId])
         SLACK.history[currentRoomId].messages.forEach(function(msg) {
-            if (msg.type === "message") {
+            if (!msg.removed) {
                 var dom = createMessageDom(currentRoomId, msg);
                 chatFrag.appendChild(dom);
             }
         });
     var content = document.getElementById(R.id.currentRoom.content);
+    content.textContent = "";
     content.appendChild(chatFrag);
     //TODO scroll lock
     content.scrollTop = content.scrollHeight -content.clientHeight;

+ 2 - 2
cli/workflow.js

@@ -147,10 +147,10 @@ function sendMsg(chan, msg, replyTo) {
             footer = "Group message";
         }
         var attachment = {
-            "fallback": replyTo.raw["text"] || ""
+            "fallback": replyTo.text
             ,"author_name": "<@" +sender.id +"|" +sender.name +">"
             ,"author_icon": sender.icons.image_48
-            ,"text": replyTo.raw["text"] || ""
+            ,"text": replyTo.text
             ,"footer": footer
             ,"ts": replyTo.ts
         };

+ 49 - 47
srv/public/slack.min.js

@@ -1,47 +1,49 @@
-function aa(a){this.id=a;this.a={N:"",P:"",S:"",T:"",H:"",I:"",K:"",U:""}}function ba(a){this.id=a;this.a={}}function ca(a){this.id=a;this.a={}}function da(a,b){this.id=a;this.c=b}function ea(a){this.id=a;this.a={L:"",M:"",i:"",o:"",J:"",R:""};this.f={};this.c=this.b=null}function fa(){this.b={};this.a=[]}function ga(a,b){a.b=JSON.parse(b.emoji_use);b.highlight_words?a.a=(b.highlight_words||"").split(",").filter(function(a){return""!==a.trim()}):b.highlights&&(a.a=b.highlights)}
-function ha(a){this.id=a;this.a={O:"",i:"",o:""};this.c=this.b=null}function n(){this.m=null;this.c={};this.f={};this.u={};this.b={};this.a=null;this.j={};this.h={}}function r(a,b){return a.b[b]||a.j[b]||null}function x(a,b){return a.c[b]||a.u[b]||a.f[b]||null}"undefined"!==typeof module&&(module.G.V=n);function ia(a,b){this.h=a.user;this.id=a.ts;this.c=b||parseFloat(a.ts);this.type=a.type;this.f=a.subtype;this.b=a;this.a={};var d=this;a.reactions&&a.reactions.forEach(function(a){d.a[a.name]=[];a.users.forEach(function(b){d.a[a.name].push(b)})})}function z(a,b,d){this.id="string"===typeof a?a:a.id;this.a=[];this.b=b;d&&A(this,d)}function ja(a,b,d){a.a[b]&&(1===a.a[b].length?delete a.a[b]:a.a[b]=a.a[b].filter(function(a){return a!==d}))}
-function A(a,b){var d=0;b.forEach(function(a){d=Math.max(this.push(a),d)}.bind(a));ka(a)}z.prototype.push=function(a){for(var b=parseFloat(a.ts),d=0,c=this.a.length;d<c;d++)if(this.a[d].c===b)return b;for(this.a.push(new ia(a,b));this.a.length>this.b;)this.a.shift();if("reaction_added"===a.type){if(d=B(this,parseFloat(a.item.ts)))c=a.reaction,a=a.user,d.a[c]||(d.a[c]=[]),d.a[c].push(a)}else"reaction_removed"===a.type&&(d=B(this,parseFloat(a.item.ts)))&&ja(d,a.reaction,a.user);return b};
-function B(a,b){for(var d=0,c=a.a.length;d<c&&b>=a.a[d].c;d++)if(a.a[d].c===b)return a.a[d];return null}function ka(a){a.a.sort(function(a,d){return a.c-d.c})}"undefined"!==typeof module&&(module.G.W=z);var C={},D;function la(){var a;if(!a){for(var b=0,d=navigator.languages.length;b<d;b++)if(C.hasOwnProperty(navigator.languages[b])){a=navigator.languages[b];break}a||(a="en")}D=C[a];console.log("Loading language pack: "+a);if(D.g)for(b in D.g)document.getElementById(b).textContent=D.g[b]};C.fr={F:"Utilisateur inconnu",D:"Channel inconnu",A:"Nouveau message",w:"Reseau",g:{fileUploadCancel:"Annuler",neterror:"Impossible de se connecter au chat !"}};C.en={F:"Unknown member",D:"Unknown channel",A:"New message",w:"Network",g:{fileUploadCancel:"Cancel",neterror:"Cannot connect to chat !"}};var F=null,G=0;
-function ma(){var a=document.createDocumentFragment(),b=H.a.a?Object.keys(H.a.a.f):[];b.sort(function(a,b){return a[0]!==b[0]?a[0]-b[0]:(H.a.c[a]||H.a.f[a]).name.localeCompare((H.a.c[b]||H.a.f[b]).name)});b.forEach(function(b){b=H.a.c[b]||H.a.f[b];var c=document.createElement("li"),d=document.createElement("a");c.id=b.id;d.href="#"+b.id;"D"===b.id[0]?c.className="slack-context-room slack-ims":"G"===b.id[0]?c.className="slack-context-room slack-group":"C"===b.id[0]&&(c.className="slack-context-room slack-channel");d.textContent=
-b.name;c.appendChild(d);c&&a.appendChild(c)});b=H.a.b?Object.keys(H.a.b):[];b.sort(function(a,b){return H.a.b[a].name.localeCompare(H.a.b[b].name)});b.forEach(function(b){b=H.a.b[b].b;var c=document.createElement("li"),d=document.createElement("a");c.id=b.id;d.href="#"+b.id;c.className="slack-context-room";d.textContent=b.c.name;c.appendChild(d);c&&a.appendChild(c)});document.getElementById("chanList").textContent="";document.getElementById("chanList").appendChild(a);L();M()}
-function N(a){a?document.body.classList.remove("no-network"):document.body.classList.add("no-network");M()}function O(){if(F){document.body.classList.add("replyingTo");var a=document.getElementById("replyToContainer"),b=document.createElement("a");b.addEventListener("click",function(){F=null;O()});b.className="replyto-close";b.textContent="x";a.textContent="";a.appendChild(b);a.appendChild(P("reply_"+Q.id,F,!0))}else document.body.classList.remove("replyingTo")}
-function na(a,b,d,c){var g=S(d);if(g){for(var e=document.createElement("li"),l=document.createElement("a"),f=document.createElement("span"),h=document.createElement("span"),m=[],k=0,p=c.length;k<p;k++){var q=r(H.a,c[k]);q&&m.push(q.name)}m.sort();h.textContent=m.join(", ");f.appendChild(g);f.className="emoji-small";l.href="javascript:toggleReaction('"+a+"', '"+b+"', '"+d+"')";l.appendChild(f);l.appendChild(h);e.className="slackmsg-reaction-item";e.appendChild(l);return e}return null}
-window.toggleReaction=function(a,b,d){var c=H.b[a];if(c){a:{for(var g=0,e=c.a.length;g<e;g++)if(c.a[g].id==b){c=c.a[g];break a}c=null}c&&(g=H.a.a.id,c.a[d]&&-1!==c.a[d].indexOf(g)?(c=new XMLHttpRequest,c.open("DELETE","api/reaction?room="+a+"&msg="+b+"&reaction="+encodeURIComponent(d),!0),c.send(null)):T(a,b,d))}};
-function U(a,b,d){var c=document.createElement("div"),g=document.createElement("div"),e=document.createElement("div"),l=document.createElement("div"),f=document.createElement("img"),h=document.createElement("span"),m=document.createElement("ul"),k=document.createElement("li"),p=document.createElement("ul"),q=document.createElement("ul"),t=b.b.user?H.a.b[b.b.user]:H.a.j[b.b.bot_id];c.id=a+"_"+b.c;c.className="slackmsg-item";g.className="slackmsg-ts";e.className="slackmsg-msg";l.className="slackmsg-author";
-f.className="slackmsg-author-img";h.className="slackmsg-author-name";m.className="slackmsg-hover";k.className="slackmsg-hover-reply";g.innerHTML=oa(b.c);e.innerHTML=V(b.b.text||"");h.textContent=t?t.name:b.b.username||"?";t||b.b.username||(e.textContent=b.b.subtype||JSON.stringify(b.b));f.src=t?t.a.i:"";l.appendChild(f);l.appendChild(h);m.appendChild(k);"makeEmoji"in window?(f=document.createElement("li"),h=window.makeEmoji("arrow_heading_down"),t=window.makeEmoji("smile"),f.className="slackmsg-hover-reaction",
-t?(f.classList.add("emoji-small"),f.appendChild(t)):f.style.backgroundImage='url("smile.svg")',h?(k.classList.add("emoji-small"),k.appendChild(h)):k.style.backgroundImage='url("repl.svg")',m.appendChild(f)):k.style.backgroundImage='url("repl.svg")';c.appendChild(l);c.appendChild(e);c.appendChild(g);c.appendChild(p);c.appendChild(q);p.className="slackmsg-attachments";q.className="slackmsg-reactions";if(!0!==d){if(b.a)for(var u in b.a)(d=na(a,b.id,u,b.a[u]))&&q.appendChild(d);b.b.attachments&&b.b.attachments.forEach(function(a){var b=
-document.createElement("li"),c=document.createElement("div"),d=document.createElement("div"),e=document.createElement("a"),g=document.createElement("div"),f=document.createElement("img"),h=document.createElement("a"),l=document.createElement("div"),v=document.createElement("div"),t=document.createElement("img"),k=document.createElement("img"),u=document.createElement("div"),m=document.createElement("img"),q=document.createElement("span"),w=document.createElement("span");b.className="slackmsg-attachment";
-var E="#e3e4e6";a.color&&("#"===a.color[0]?E=a.color[0]:"good"===a.color?E="#2fa44f":"warning"===a.color?E="#de9e31":"danger"===a.color&&(E="#d50200"));c.style.borderColor=E;d.className="slackmsg-attachment-pretext";a.pretext?d.innerHTML=V(a.pretext):d.classList.add("hidden");e.target="_blank";a.title?(e.innerHTML=V(a.title),a.title_link&&(e.href=a.title_link),e.className="slackmsg-attachment-title"):e.className="hidden slackmsg-attachment-title";h.target="_blank";g.className="slackmsg-author";a.author_name?
-(h.innerHTML=V(a.author_name),h.href=a.author_link||"",h.className="slackmsg-author-name",f.className="slackmsg-author-img",a.author_icon?f.src=a.author_icon:f.classList.add("hidden")):g.classList.add("hidden");v.innerHTML=V(a.text||"");v.a="slackmsg-attachment-text";t.className="slackmsg-attachment-thumb";a.thumb_url?t.src=a.thumb_url:t.classList.add("hidden");k.className="slackmsg-attachment-img";a.image_url?k.src=a.image_url:k.classList.add("hidden");u.className="slackmsg-attachment-footer";q.className=
-"slackmsg-attachment-footer-text";m.className="slackmsg-attachment-footer-icon";a.footer?(q.innerHTML=V(a.footer),a.footer_icon?m.src=a.footer_icon:m.classList.add("hidden")):(m.classList.add("hidden"),q.classList.add("hidden"));w.className="slackmsg-ts";a.ts?w.innerHTML=oa(a.ts):w.classList.add("hidden");g.appendChild(f);g.appendChild(h);l.appendChild(v);l.appendChild(t);u.appendChild(m);u.appendChild(q);u.appendChild(w);c.appendChild(e);c.appendChild(g);c.appendChild(l);c.appendChild(k);c.appendChild(u);
-b.appendChild(d);b.appendChild(c);b&&p.appendChild(b)})}c.appendChild(m);return c}function oa(a){"string"!==typeof a&&(a=parseFloat(a));return(new Date(1E3*a)).toLocaleTimeString()}
-function S(a){a:{for(var b=a,d={};!d[b];){if(a=H.a.h[b])if("alias:"==a.substr(0,6))d[b]=!0,b=a.substr(6);else{b=document.createElement("span");b.className="emoji-custom emoji";b.style.backgroundImage="url('"+a+"')";a=b;break a}break}a=b}"string"===typeof a&&"makeEmoji"in window&&(a=window.makeEmoji(a));return"string"===typeof a?null:a}
-function pa(a){return a.replace(/:([^ \t:]+):/g,function(b,d){var c=S(d);if(c){var g=document.createElement("span");g.className=b===a?"emoji-medium":"emoji-small";g.appendChild(c);return g.outerHTML}return b})}
-function V(a){a=a.split(/\r?\n/g);for(var b=0,d=a.length;b<d;b++){for(var c=a[b],g="",e={},l=!1,f=0,c=c.replace(RegExp("<([@#]?)([^>]*)>","g"),function(a,b,c){c=c.split("|");if("@"===b)c[1]?"@"!==c[1][0]&&(c[1]="@"+c[1]):(a=r(H.a,c[0]),c[1]=a?"@"+a.name:D.F),c[0]="#"+c[0],c[2]="slackmsg-link slackmsg-link-user";else if("#"===b)c[1]?"#"!==c[1][0]&&(c[1]="#"+c[1]):(a=x(H.a,c[0]),c[1]=a?"#"+a.name:D.D),c[0]="#"+c[0],c[2]="slackmsg-link slackmsg-link-chan";else if(-1!==c[0].indexOf("://"))c[1]||(c[1]=
-c[0]),c[2]="slackmsg-link";else return a;return'<a href="'+c[0]+'" class="'+c[2]+'"'+(b?"":' target="_blank"')+">"+c[1]+"</a>"}),c=pa(c),h=c.length,m=function(a,b,c){for(;a[b];){if(" "!=a[b]&&a[b]!=c&&a[b+1]==c)return!0;b++}return!1},k=function(a){return Object.keys(e).length?'<span class="'+Object.keys(a).join(" ")+'">':""};f<h&&(" "===c[f]||"\t"===c[f]);)f++;"&gt;"===c.substr(f,4)&&(l=!0,f+=4);for(;f<h;f++){var p=c[f];if("<"===p){do g+=c[f++];while(">"!==c[f-1]);f--}else if(!e["slackmsg-style-bold"]&&
-"*"===p&&c[f+1]&&m(c,f,p))Object.keys(e).length&&(g+="</span>"),e["slackmsg-style-bold"]=!0,g+=k(e);else if(!e["slackmsg-style-strike"]&&"~"===p&&c[f+1]&&m(c,f,p))Object.keys(e).length&&(g+="</span>"),e["slackmsg-style-strike"]=!0,g+=k(e);else if(!e["slackmsg-style-code"]&&"`"===p&&c[f+1]&&m(c,f,p))Object.keys(e).length&&(g+="</span>"),e["slackmsg-style-code"]=!0,g+=k(e);else if(!e["slackmsg-style-italic"]&&"_"===p&&c[f+1]&&m(c,f,p))Object.keys(e).length&&(g+="</span>"),e["slackmsg-style-italic"]=
-!0,g+=k(e);else{var q=!1,g=g+p;do{if(e["slackmsg-style-bold"]&&"*"!==p&&"*"===c[f+1])delete e["slackmsg-style-bold"],q=!0;else if(e["slackmsg-style-strike"]&&"~"!==p&&"~"===c[f+1])delete e["slackmsg-style-strike"],q=!0;else if(e["slackmsg-style-code"]&&"`"!==p&&"`"===c[f+1])delete e["slackmsg-style-code"],q=!0;else if(e["slackmsg-style-italic"]&&"_"!==p&&"_"===c[f+1])delete e["slackmsg-style-italic"],q=!0;else break;p=c[++f]}while(f<h);q&&(g+="</span>"+k(e))}}e&&(g+="</span>");a[b]=l?'<span class="slackmsg-style-quote">'+
-g+"</span>":g}return a.join("<br/>")}function P(a,b,d){"me_message"===b.f?(a=U(a,b,d),a.classList.add("slackmsg-me_message")):a=U(a,b,d);return a}function M(){var a=0,b=0,d="";if(W)d="!"+D.w+" - ",document.getElementById("linkFavicon").href="favicon_err.png";else{for(var c in X)X.hasOwnProperty(c)&&(a+=X[c].s,b+=X[c].l);b?d="(!"+b+") - ":a&&(d="("+a+") - ");document.getElementById("linkFavicon").href=b||a?"favicon.png?h="+b+"&m="+a:"favicon_ok.png"}d+=H.a.m.name;document.title=d}
-function qa(){if("Notification"in window)if("granted"===Notification.permission){var a=Date.now();if(G+3E4<a){var b=new Notification(D.A);G=a;setTimeout(function(){b.close()},5E3)}}else"denied"!==Notification.permission&&Notification.requestPermission()}
-function ra(){var a=document.createDocumentFragment(),b=Q.id;document.getElementById("chatWindow").textContent="";H.b[b]&&H.b[b].a.forEach(function(c){"message"===c.type&&(c=P(b,c),a.appendChild(c))});var d=document.getElementById("chatWindow");d.appendChild(a);d.scrollTop=d.scrollHeight-d.clientHeight}
-function sa(a){function b(a,b){for(b=b||a.target;b!==a.currentTarget&&b;){if(b.classList.contains("slackmsg-item"))return b.id;b=b.parentElement}}for(var d=a.target;d!==a.currentTarget&&d&&!d.classList.contains("slackmsg-hover");){if(d.parentElement&&d.parentElement.classList.contains("slackmsg-hover")){if(a=b(a,d)){a=parseFloat(a.split("_")[1]);var c=B(H.b[Q.id],a);c&&d.classList.contains("slackmsg-hover-reply")?F!==c&&(F=c,O()):c&&d.classList.contains("slackmsg-hover-reaction")&&ta.C(document.body,
-function(a){a&&T(Q.id,c.id,a)})}break}d=d.parentElement}}function Y(){document.getElementById("msgInput").focus()}function L(){var a=document.location.hash.substr(1),b=x(H.a,a),a=r(H.a,a);b&&b!==Q?ua(b):a&&a.b&&ua(a.b)}
-document.addEventListener("DOMContentLoaded",function(){la();document.getElementById("chatWindow").addEventListener("click",sa);window.addEventListener("hashchange",function(){document.location.hash&&"#"===document.location.hash[0]&&L()});document.getElementById("fileUploadCancel").addEventListener("click",function(a){a.preventDefault();document.getElementById("fileUploadError").classList.add("hidden");document.getElementById("fileUploadContainer").classList.add("hidden");document.getElementById("fileUploadInput").value=
-"";return!1});document.getElementById("fileUploadForm").addEventListener("submit",function(a){a.preventDefault();a=document.getElementById("fileUploadInput");var b=a.value;b&&(b=b.substr(b.lastIndexOf("\\")+1),va(b,a.files[0],function(a){var b=document.getElementById("fileUploadError");a?(b.textContent=a,b.classList.remove("hidden")):(b.classList.add("hidden"),document.getElementById("fileUploadInput").value="",document.getElementById("fileUploadContainer").classList.add("hidden"))}));return!1});
-document.getElementById("attachFile").addEventListener("click",function(a){a.preventDefault();Q&&document.getElementById("fileUploadContainer").classList.remove("hidden");return!1});document.getElementById("msgForm").addEventListener("submit",function(a){a.preventDefault();a=document.getElementById("msgInput");if(Q&&a.value){var b=Q,d=F,c=new XMLHttpRequest,g="api/msg?room="+b.id+"&text="+encodeURIComponent(a.value);if(d){var e=r(H.a,d.h),l="Message";"C"===b.id[0]?l="Channel message":"D"===b.id[0]?
-l="Direct message":"G"===b.id[0]&&(l="Group message");g+="&attachments="+encodeURIComponent(JSON.stringify([{fallback:d.b.text||"",author_name:"<@"+e.id+"|"+e.name+">",author_icon:e.a.i,text:d.b.text||"",footer:l,ts:d.c}]))}c.open("POST",g,!0);c.send(null);a.value="";F&&(F=null,O())}Y();return!1});window.addEventListener("blur",function(){window.hasFocus=!1});window.addEventListener("focus",function(){window.hasFocus=!0;G=0;Q&&wa();Y()});window.hasFocus=!0;(function(){var a=document.getElementById("emojiButton");
-if("makeEmoji"in window){var b=window.makeEmoji("smile");b?a.innerHTML="<span class='emoji-small'>"+b.outerHTML+"</span>":a.style.backgroundImage='url("smile.svg")';(b=window.makeEmoji("paperclip"))?document.getElementById("attachFile").innerHTML="<span class='emoji-small'>"+b.outerHTML+"</span>":document.getElementById("attachFile").style.backgroundImage='url("public/paperclip.svg")';a.addEventListener("click",function(){ta.C(document.body,function(a){a&&(document.getElementById("msgInput").value+=
-":"+a+":");Y()})})}else a.classList.add("hidden")})();Z()});var ta=function(){function a(a,b){for(var c=a.target;c!==h&&c&&"LI"!==c.nodeName;)c=c.parentElement;c&&"LI"===c.nodeName&&c.id&&"emojibar-"===c.id.substr(0,9)?b(c.id.substr(9)):b(null)}function b(){if(!d())return!1;y&&y(null);return!0}function d(){return h.parentElement?(h.parentElement.removeChild(m),h.parentElement.removeChild(h),!0):!1}function c(a){var b=0,c;a=void 0===a?t.value:a;if(f()){var d=window.searchEmojis(a);c=g(d);for(var h in u)u[h].visible&&(u[h].visible=!1,p.removeChild(u[h].g));
-h=0;for(var l=c.length;h<l;h++){var k=c[h].name,m=u[k];if(!m){var m=u,R=k,y=k,k=window.makeEmoji(d[k]),w=document.createElement("span");w.appendChild(k);w.className="emoji-medium";k=e(y,w);m=m[R]=k}m.visible||(m.visible=!0,p.appendChild(m.g));b++}}for(h in v)v[h].visible&&(v[h].visible=!1,q.removeChild(v[h].g));c=g(H.a.h);h=0;for(l=c.length;h<l;h++)k=c[h].name,""!==a&&k.substr(0,a.length)!==a||"alias:"===H.a.h[k].substr(0,6)||(m=v[k],m||(d=v,R=m=k,k=H.a.h[k],y=document.createElement("span"),w=document.createElement("span"),
-y.className="emoji emoji-custom",y.style.backgroundImage='url("'+k+'")',w.appendChild(y),w.className="emoji-medium",k=e(R,w),m=d[m]=k),m.visible||(m.visible=!0,q.appendChild(m.g)),b++);return b}function g(a){var b=H.a.a.c.b,c=[],d;for(d in a)c.push({name:d,B:0,count:b[d]||0});return c=c.sort(function(a,b){var c=b.count-a.count;return c?c:a.B-b.B})}function e(a,b){var c=document.createElement("li");c.appendChild(b);c.className="emojibar-list-item";c.id="emojibar-"+a;return{visible:!1,g:c}}function l(a){var b=
-document.createElement("img"),c=document.createElement("div");b.src=a;c.appendChild(b);c.className="emojibar-header";return c}function f(){return"searchEmojis"in window}var h=document.createElement("div"),m=document.createElement("div"),k=document.createElement("div"),p=document.createElement("ul"),q=document.createElement("ul"),t=document.createElement("input"),u={},v={},I=document.createElement("div"),J=document.createElement("span"),K=document.createElement("span"),y;m.addEventListener("click",
-function(a){var c=h.getBoundingClientRect();(a.screenY<c.top||a.screenY>c.bottom||a.screenX<c.left||a.screenX>c.right)&&b()});m.className="emojibar-overlay";h.className="emojibar";k.className="emojibar-emojis";I.className="emojibar-detail";J.className="emojibar-detail-img";K.className="emojibar-detail-name";p.className=q.className="emojibar-list";t.className="emojibar-search";I.appendChild(J);I.appendChild(K);k.appendChild(l(window.emojiProviderHeader));k.appendChild(p);k.appendChild(l("emojicustom.png"));
-k.appendChild(q);h.appendChild(k);h.appendChild(I);h.appendChild(t);t.addEventListener("keyup",function(){c()});h.addEventListener("mousemove",function(b){a(b,function(a){var b=a?u[a]||v[a]:null;b?(J.innerHTML=b.g.outerHTML,K.textContent=":"+a+":"):(J.textContent="",K.textContent="")})});h.addEventListener("click",function(b){a(b,function(a){a&&d()&&y&&y(a)})});return{isSupported:f,C:function(a,b){return f()?(y=b,a.appendChild(m),a.appendChild(h),t.value="",c(),t.focus(),!0):!1},search:c,close:b}}();var H,X={};
-function xa(a,b){if(a&&(a!==Q||!window.hasFocus)){var d=new RegExp("<@"+H.a.a.id),c=!1,g=!1;X[a.id]||(X[a.id]={l:0,s:0});b.forEach(function(b){if("message"===b.type&&b.text){var e;if(!(e="D"===a.id[0]||b.text.match(d)))a:{b=b.text;e=H.a.a.c.a;for(var f=0,h=e.length;f<h;f++)if(-1!==b.indexOf(e[f])){console.log("Found highlight "+e[f]+" in "+b);e=!0;break a}e=!1}e?(g|=!X[a.id].l,X[a.id].l++,c=!0):X[a.id].s++}});M();document.getElementById(a.id).classList.add("unread");c&&document.getElementById(a.id).classList.add("unreadHi");g&&
-!window.hasFocus&&qa()}}function wa(){var a=Q;X[a.id]&&(X[a.id]={l:0,s:0},M());a=document.getElementById(a.id);a.classList.remove("unread");a.classList.remove("unreadHi")}H=new function(){this.c=0;this.a=new n;this.b={}};var W=0,Q=null;function ya(a){var b=new XMLHttpRequest;b.timeout=6E4;b.onreadystatechange=function(){if(4===b.readyState)if(b.status){var d=null,c=2===Math.floor(b.status/100);if(c){W&&(W=0,N(!0));d=b.response;try{d=JSON.parse(d)}catch(g){d=null}}else W?(W+=Math.floor((W||5)/2),W=Math.min(60,W)):(W=5,N(!1));a(c,d)}else W&&(W=0,N(!0)),ya(a)};b.open("GET","api?v="+H.c,!0);b.send(null)}
-function za(a,b){if(a){if(b){var d=H;b.v&&(d.c=b.v);if(b["static"]){var c=d.a,g=b["static"];if(g.bots)for(var e=0,l=g.bots.length;e<l;e++){var f=c.j[g.bots[e].id];f||(f=c.j[g.bots[e].id]=new ha(g.bots[e].id));var h=g.bots[e];f.name=h.name;f.a.O=h.icons.image_36;f.a.i=h.icons.image_48;f.a.o=h.icons.image_72}if(g.users)for(e=0,l=g.users.length;e<l;e++)(f=c.b[g.users[e].id])||(f=c.b[g.users[e].id]=new ea(g.users[e].id)),h=g.users[e],f.name=h.name,f.status=h.status,f.a.L=h.profile.image_24,f.a.M=h.profile.image_32,
-f.a.i=h.profile.image_48,f.a.o=h.profile.image_72,f.a.J=h.profile.image_192,f.a.R=h.profile.image_512;if(g.ims)for(e=0,l=g.ims.length;e<l;e++)if(f=r(c,g.ims[e].user))f.b||(c.u[g.ims[e].id]=f.b=new da(g.ims[e].id,f)),f.b.b=parseFloat(g.ims[e].last_read);if(g.channels)for(e=0,l=g.channels.length;e<l;e++){(f=c.c[g.channels[e].id])||(f=c.c[g.channels[e].id]=new ba(g.channels[e].id));var h=g.channels[e],m=c;f.name=h.name;f.b=parseFloat(h.last_read);f.a={};if(h.members)for(var k=0,p=h.members.length;k<
-p;k++){var q=r(m,h.members[k]);f.a[q.id]=q;q.f[f.id]=f}}e=0;for(l=g.groups.length;e<l;e++){(f=c.f[g.groups[e].id])||(f=c.f[g.groups[e].id]=new ca(g.groups[e].id));h=c;m=g.groups[e];k=[];f.a={};p=0;for(q=m.members.length;p<q;p++){var t=r(h,m.members[p]);f.a[m.members[p]]=t;t.f[f.id]=f;k.push(t.name)}f.name=k.join(", ");f.b=parseFloat(m.last_read)}g.emojis&&(c.h=g.emojis);c.m||(c.m=new aa(g.team.id));e=c.m;l=g.team;e.name=l.name;e.a.N=l.icon.image_34;e.a.P=l.icon.image_44;e.a.S=l.icon.image_68;e.a.T=
-l.icon.image_88;e.a.H=l.icon.image_102;e.a.I=l.icon.image_132;e.a.K=l.icon.image_230;e.a.U=l.icon.image_default;g.self&&(c.a=r(c,g.self.id),c.a.c||(c.a.c=new fa),ga(c.a.c,g.self.prefs));ma()}if(b.live){for(var u in b.live)(c=d.b[u])?A(c,b.live[u]):d.b[u]=new z(u,250,b.live[u]);for(var v in b.live)xa(x(d.a,v),b.live[v]),Q&&b.live[Q.id]&&ra()}}Z()}else setTimeout(Z,1E3*W)}function Z(){ya(za)}
-function ua(a){Q&&document.getElementById(Q.id).classList.remove("selected");document.getElementById(a.id).classList.add("selected");document.body.classList.remove("no-room-selected");Q=a;a=Q.name||(Q.c?Q.c.name:void 0);if(!a){a=[];for(var b in Q.a)a.push(Q.a[b].name);a=a.join(", ")}document.getElementById("currentRoomTitle").textContent=a;ra();Y();document.getElementById("fileUploadContainer").classList.add("hidden");wa();F&&(F=null,O());Q.b&&!H.b[Q.id]&&(b=new XMLHttpRequest,b.open("GET","api/hist?room="+
-Q.id,!0),b.send(null))}function va(a,b,d){var c=Q;new FileReader;var g=new FormData,e=new XMLHttpRequest;g.append("file",b);g.append("filename",a);e.onreadystatechange=function(){4===e.readyState&&(204===e.status?d(null):d(e.statusText))};e.open("POST","api/file?room="+c.id);e.send(g)}function T(a,b,d){var c=new XMLHttpRequest;c.open("POST","api/reaction?room="+a+"&msg="+b+"&reaction="+encodeURIComponent(d),!0);c.send(null)};
+function aa(a){this.id=a;this.a={O:"",R:"",T:"",U:"",I:"",J:"",L:"",V:""}}function ba(a){this.id=a;this.a={}}function ca(a){this.id=a;this.a={}}function da(a,b){this.id=a;this.f=b}function ea(a){this.id=a;this.a={M:"",N:"",m:"",s:"",K:"",S:""};this.f={};this.c=this.b=null}function fa(){this.b={};this.a=[]}function ga(a,b){a.b=JSON.parse(b.emoji_use);b.highlight_words?a.a=(b.highlight_words||"").split(",").filter(function(a){return""!==a.trim()}):b.highlights&&(a.a=b.highlights)}
+function ha(a){this.id=a;this.a={P:"",m:"",s:""};this.c=this.b=null}function n(){this.f=null;this.h={};this.l={};this.w={};this.b={};this.a=null;this.j={};this.c={}}function r(a,b){return a.b[b]||a.j[b]||null}function x(a,b){return a.h[b]||a.w[b]||a.l[b]||null}"undefined"!==typeof module&&(module.H.W=n);function ia(a,b){this.h=a.user||a.bot_id;this.j=a.username;this.id=a.ts;this.b=b||parseFloat(a.ts);this.text=a.text||"";this.f=a.attachments||[];this.i=!!a.edited;this.c=a.removed||!1;this.l="me_message"===a.subtype||a.isMeMessage;this.a={};this.version=this.b;var d=this;a.reactions&&a.reactions.forEach(function(a){d.a[a.name]=[];a.users.forEach(function(b){d.a[a.name].push(b)})})}
+function ja(a,b,d){b?(a.text=b.text||"",b.attachments&&(a.f=b.attachments),a.i=!!b.edited,a.c=!!b.removed,b.reactions&&(a.a={},b.reactions.forEach(function(b){a.a[b.name]=[];b.users.forEach(function(c){a.a[b.name].push(c)})}))):a.c=!0;a.version=d}function z(a,b,d){this.id="string"===typeof a?a:a.id;this.a=[];this.b=b;d&&A(this,d)}function ka(a,b,d,c){var f=!1;a.a[b]&&(1===a.a[b].length&&a.a[b][0]===d?(delete a.a[b],f=!0):a.a[b]=a.a[b].filter(function(a){return a!==d?!1:f=!0}));f&&(a.version=c)}
+function A(a,b){var d=0;b.forEach(function(a){d=Math.max(this.push(a),d)}.bind(a));la(a)}
+z.prototype.push=function(a){var b=parseFloat(a.ts);if(a.type&&"message"!==a.type)if("reaction_added"===a.type){var d=B(this,a.item.ts);if(d){var c=a.reaction;a=a.user;d.a[c]||(d.a[c]=[]);d.a[c].push(a);d.version=b}}else if("reaction_removed"===a.type)(d=B(this,a.item.ts))&&ka(d,a.reaction,a.user,b);else return 0;else{var d=!1,c=a.ts,f=a;"message_changed"===a.subtype&&a.previous_message?(c=a.previous_message.ts,f=a.message):"message_deleted"===a.subtype&&a.previous_message&&(c=a.previous_message.ts,
+f=null);for(var e=0,l=this.a.length;e<l;e++)if(this.a[e].id===c){ja(this.a[e],f,b);d=!0;break}d||this.a.push(new ia(a,b))}for(;this.a.length>this.b;)this.a.shift();return b};function ma(a){for(var b=C.b[D.id],d=0,c=b.a.length;d<c&&a>=b.a[d].b;d++)if(b.a[d].b===a)return b.a[d];return null}function B(a,b){for(var d=0,c=a.a.length;d<c;d++)if(a.a[d].id==b)return a.a[d];return null}function la(a){a.a.sort(function(a,d){return a.b-d.b})}"undefined"!==typeof module&&(module.H.X=z);var E={},G;function na(){var a;if(!a){for(var b=0,d=navigator.languages.length;b<d;b++)if(E.hasOwnProperty(navigator.languages[b])){a=navigator.languages[b];break}a||(a="en")}G=E[a];console.log("Loading language pack: "+a);if(G.g)for(b in G.g)document.getElementById(b).textContent=G.g[b]};E.fr={G:"Utilisateur inconnu",F:"Channel inconnu",B:"Nouveau message",A:"Reseau",i:"edit&eacute;",g:{fileUploadCancel:"Annuler",neterror:"Impossible de se connecter au chat !"}};E.en={G:"Unknown member",F:"Unknown channel",B:"New message",A:"Network",i:"edited",g:{fileUploadCancel:"Cancel",neterror:"Cannot connect to chat !"}};var H=null,I=0;
+function oa(){var a=document.createDocumentFragment(),b=C.a.a?Object.keys(C.a.a.f):[];b.sort(function(a,b){return a[0]!==b[0]?a[0]-b[0]:x(C.a,a).name.localeCompare(x(C.a,b).name)});b.forEach(function(b){b=x(C.a,b);if(!b.b){var c=document.createElement("li"),d=document.createElement("a");c.id=b.id;d.href="#"+b.id;"D"===b.id[0]?c.className="slack-context-room slack-ims":"G"===b.id[0]?c.className="slack-context-room slack-group":"C"===b.id[0]&&(c.className="slack-context-room slack-channel");d.textContent=
+b.name;c.appendChild(d);c&&a.appendChild(c)}});b=C.a.b?Object.keys(C.a.b):[];b.sort(function(a,b){return C.a.b[a].name.localeCompare(C.a.b[b].name)});b.forEach(function(b){b=r(C.a,b);if(!b.h){b=b.b;var c=document.createElement("li"),d=document.createElement("a");c.id=b.id;d.href="#"+b.id;c.className="slack-context-room";d.textContent=b.f.name;c.appendChild(d);c&&a.appendChild(c)}});document.getElementById("chanList").textContent="";document.getElementById("chanList").appendChild(a);M();N()}
+function O(a){a?document.body.classList.remove("no-network"):document.body.classList.add("no-network");N()}function P(){if(H){document.body.classList.add("replyingTo");var a=document.getElementById("replyToContainer"),b=document.createElement("a");b.addEventListener("click",function(){H=null;P()});b.className="replyto-close";b.textContent="x";a.textContent="";a.appendChild(b);a.appendChild(Q("reply_"+D.id,H,!0))}else document.body.classList.remove("replyingTo")}
+function pa(a,b,d,c){var f=S(d);if(f){for(var e=document.createElement("li"),l=document.createElement("a"),g=document.createElement("span"),h=document.createElement("span"),m=[],k=0,p=c.length;k<p;k++){var q=r(C.a,c[k]);q&&m.push(q.name)}m.sort();h.textContent=m.join(", ");g.appendChild(f);g.className="emoji-small";l.href="javascript:toggleReaction('"+a+"', '"+b+"', '"+d+"')";l.appendChild(g);l.appendChild(h);e.className="slackmsg-reaction-item";e.appendChild(l);return e}return null}
+window.toggleReaction=function(a,b,d){var c=C.b[a];if(c&&(c=B(c,b))){var f=C.a.a.id;c.a[d]&&-1!==c.a[d].indexOf(f)?(c=new XMLHttpRequest,c.open("DELETE","api/reaction?room="+a+"&msg="+b+"&reaction="+encodeURIComponent(d),!0),c.send(null)):T(a,b,d)}};
+function U(a,b,d){var c=document.createElement("div"),f=document.createElement("div"),e=document.createElement("div"),l=document.createElement("div"),g=document.createElement("img"),h=document.createElement("span"),m=document.createElement("ul"),k=document.createElement("li"),p=document.createElement("ul"),q=document.createElement("ul"),u=r(C.a,b.h);c.id=a+"_"+b.b;c.className="slackmsg-item";f.className="slackmsg-ts";e.className="slackmsg-msg";l.className="slackmsg-author";g.className="slackmsg-author-img";
+h.className="slackmsg-author-name";m.className="slackmsg-hover";k.className="slackmsg-hover-reply";f.innerHTML=qa(b.b);e.innerHTML=V(b.text);h.textContent=u?u.name:b.j||"?";g.src=u?u.a.m:"";l.appendChild(g);l.appendChild(h);m.appendChild(k);"makeEmoji"in window?(g=document.createElement("li"),h=window.makeEmoji("arrow_heading_down"),u=window.makeEmoji("smile"),g.className="slackmsg-hover-reaction",u?(g.classList.add("emoji-small"),g.appendChild(u)):g.style.backgroundImage='url("smile.svg")',h?(k.classList.add("emoji-small"),
+k.appendChild(h)):k.style.backgroundImage='url("repl.svg")',m.appendChild(g)):k.style.backgroundImage='url("repl.svg")';c.appendChild(l);c.appendChild(e);c.appendChild(f);c.appendChild(p);b.i&&(f=document.createElement("div"),f.textContent=G.i,f.className="slackmsg-edited",c.appendChild(f));c.appendChild(q);p.className="slackmsg-attachments";q.className="slackmsg-reactions";if(!0!==d){if(b.a)for(var t in b.a)(d=pa(a,b.id,t,b.a[t]))&&q.appendChild(d);b.f.forEach(function(a){var b=document.createElement("li"),
+c=document.createElement("div"),d=document.createElement("div"),e=document.createElement("a"),f=document.createElement("div"),g=document.createElement("img"),h=document.createElement("a"),l=document.createElement("div"),v=document.createElement("div"),u=document.createElement("img"),k=document.createElement("img"),t=document.createElement("div"),m=document.createElement("img"),q=document.createElement("span"),w=document.createElement("span");b.className="slackmsg-attachment";var F="#e3e4e6";a.color&&
+("#"===a.color[0]?F=a.color[0]:"good"===a.color?F="#2fa44f":"warning"===a.color?F="#de9e31":"danger"===a.color&&(F="#d50200"));c.style.borderColor=F;d.className="slackmsg-attachment-pretext";a.pretext?d.innerHTML=V(a.pretext):d.classList.add("hidden");e.target="_blank";a.title?(e.innerHTML=V(a.title),a.title_link&&(e.href=a.title_link),e.className="slackmsg-attachment-title"):e.className="hidden slackmsg-attachment-title";h.target="_blank";f.className="slackmsg-author";a.author_name?(h.innerHTML=
+V(a.author_name),h.href=a.author_link||"",h.className="slackmsg-author-name",g.className="slackmsg-author-img",a.author_icon?g.src=a.author_icon:g.classList.add("hidden")):f.classList.add("hidden");v.innerHTML=V(a.text||"");v.a="slackmsg-attachment-text";u.className="slackmsg-attachment-thumb";a.thumb_url?u.src=a.thumb_url:u.classList.add("hidden");k.className="slackmsg-attachment-img";a.image_url?k.src=a.image_url:k.classList.add("hidden");t.className="slackmsg-attachment-footer";q.className="slackmsg-attachment-footer-text";
+m.className="slackmsg-attachment-footer-icon";a.footer?(q.innerHTML=V(a.footer),a.footer_icon?m.src=a.footer_icon:m.classList.add("hidden")):(m.classList.add("hidden"),q.classList.add("hidden"));w.className="slackmsg-ts";a.ts?w.innerHTML=qa(a.ts):w.classList.add("hidden");f.appendChild(g);f.appendChild(h);l.appendChild(v);l.appendChild(u);t.appendChild(m);t.appendChild(q);t.appendChild(w);c.appendChild(e);c.appendChild(f);c.appendChild(l);c.appendChild(k);c.appendChild(t);b.appendChild(d);b.appendChild(c);
+b&&p.appendChild(b)})}c.appendChild(m);return c}function qa(a){"string"!==typeof a&&(a=parseFloat(a));return(new Date(1E3*a)).toLocaleTimeString()}function S(a){a:{for(var b=a,d={};!d[b];){if(a=C.a.c[b])if("alias:"==a.substr(0,6))d[b]=!0,b=a.substr(6);else{b=document.createElement("span");b.className="emoji-custom emoji";b.style.backgroundImage="url('"+a+"')";a=b;break a}break}a=b}"string"===typeof a&&"makeEmoji"in window&&(a=window.makeEmoji(a));return"string"===typeof a?null:a}
+function ra(a){return a.replace(/:([^ \t:]+):/g,function(b,d){var c=S(d);if(c){var f=document.createElement("span");f.className=b===a?"emoji-medium":"emoji-small";f.appendChild(c);return f.outerHTML}return b})}
+function V(a){a=a.split(/\r?\n/g);for(var b=0,d=a.length;b<d;b++){for(var c=a[b],f="",e={},l=!1,g=0,c=c.replace(RegExp("<([@#]?)([^>]*)>","g"),function(a,b,c){c=c.split("|");if("@"===b)c[1]?"@"!==c[1][0]&&(c[1]="@"+c[1]):(a=r(C.a,c[0]),c[1]=a?"@"+a.name:G.G),c[0]="#"+c[0],c[2]="slackmsg-link slackmsg-link-user";else if("#"===b)c[1]?"#"!==c[1][0]&&(c[1]="#"+c[1]):(a=x(C.a,c[0]),c[1]=a?"#"+a.name:G.F),c[0]="#"+c[0],c[2]="slackmsg-link slackmsg-link-chan";else if(-1!==c[0].indexOf("://"))c[1]||(c[1]=
+c[0]),c[2]="slackmsg-link";else return a;return'<a href="'+c[0]+'" class="'+c[2]+'"'+(b?"":' target="_blank"')+">"+c[1]+"</a>"}),c=ra(c),h=c.length,m=function(a,b,c){for(;a[b];){if(" "!=a[b]&&a[b]!=c&&a[b+1]==c)return!0;b++}return!1},k=function(a){return Object.keys(e).length?'<span class="'+Object.keys(a).join(" ")+'">':""};g<h&&(" "===c[g]||"\t"===c[g]);)g++;"&gt;"===c.substr(g,4)&&(l=!0,g+=4);for(;g<h;g++){var p=c[g];if("<"===p){do f+=c[g++];while(">"!==c[g-1]);g--}else if(!e["slackmsg-style-bold"]&&
+"*"===p&&c[g+1]&&m(c,g,p))Object.keys(e).length&&(f+="</span>"),e["slackmsg-style-bold"]=!0,f+=k(e);else if(!e["slackmsg-style-strike"]&&"~"===p&&c[g+1]&&m(c,g,p))Object.keys(e).length&&(f+="</span>"),e["slackmsg-style-strike"]=!0,f+=k(e);else if(!e["slackmsg-style-code"]&&"`"===p&&c[g+1]&&m(c,g,p))Object.keys(e).length&&(f+="</span>"),e["slackmsg-style-code"]=!0,f+=k(e);else if(!e["slackmsg-style-italic"]&&"_"===p&&c[g+1]&&m(c,g,p))Object.keys(e).length&&(f+="</span>"),e["slackmsg-style-italic"]=
+!0,f+=k(e);else{var q=!1,f=f+p;do{if(e["slackmsg-style-bold"]&&"*"!==p&&"*"===c[g+1])delete e["slackmsg-style-bold"],q=!0;else if(e["slackmsg-style-strike"]&&"~"!==p&&"~"===c[g+1])delete e["slackmsg-style-strike"],q=!0;else if(e["slackmsg-style-code"]&&"`"!==p&&"`"===c[g+1])delete e["slackmsg-style-code"],q=!0;else if(e["slackmsg-style-italic"]&&"_"!==p&&"_"===c[g+1])delete e["slackmsg-style-italic"],q=!0;else break;p=c[++g]}while(g<h);q&&(f+="</span>"+k(e))}}e&&(f+="</span>");a[b]=l?'<span class="slackmsg-style-quote">'+
+f+"</span>":f}return a.join("<br/>")}function Q(a,b,d){b.l?(a=U(a,b,d),a.classList.add("slackmsg-me_message")):a=U(a,b,d);b.i&&a.classList.add("slackmsg-edited");return a}
+function N(){var a=0,b=0,d="";if(W)d="!"+G.A+" - ",document.getElementById("linkFavicon").href="favicon_err.png";else{for(var c in X)X.hasOwnProperty(c)&&(a+=X[c].u,b+=X[c].o);b?d="(!"+b+") - ":a&&(d="("+a+") - ");document.getElementById("linkFavicon").href=b||a?"favicon.png?h="+b+"&m="+a:"favicon_ok.png"}d+=C.a.f.name;document.title=d}
+function sa(){if("Notification"in window)if("granted"===Notification.permission){var a=Date.now();if(I+3E4<a){var b=new Notification(G.B);I=a;setTimeout(function(){b.close()},5E3)}}else"denied"!==Notification.permission&&Notification.requestPermission()}
+function ta(){var a=document.createDocumentFragment(),b=D.id;C.b[b]&&C.b[b].a.forEach(function(c){c.c||(c=Q(b,c),a.appendChild(c))});var d=document.getElementById("chatWindow");d.textContent="";d.appendChild(a);d.scrollTop=d.scrollHeight-d.clientHeight}
+function ua(a){function b(a,b){for(b=b||a.target;b!==a.currentTarget&&b;){if(b.classList.contains("slackmsg-item"))return b.id;b=b.parentElement}}for(var d=a.target;d!==a.currentTarget&&d&&!d.classList.contains("slackmsg-hover");){if(d.parentElement&&d.parentElement.classList.contains("slackmsg-hover")){if(a=b(a,d)){a=parseFloat(a.split("_")[1]);var c=ma(a);c&&d.classList.contains("slackmsg-hover-reply")?H!==c&&(H=c,P()):c&&d.classList.contains("slackmsg-hover-reaction")&&va.D(document.body,function(a){a&&
+T(D.id,c.id,a)})}break}d=d.parentElement}}function Y(){document.getElementById("msgInput").focus()}function M(){var a=document.location.hash.substr(1),b=x(C.a,a),a=r(C.a,a);b&&b!==D?wa(b):a&&a.b&&wa(a.b)}
+document.addEventListener("DOMContentLoaded",function(){na();document.getElementById("chatWindow").addEventListener("click",ua);window.addEventListener("hashchange",function(){document.location.hash&&"#"===document.location.hash[0]&&M()});document.getElementById("fileUploadCancel").addEventListener("click",function(a){a.preventDefault();document.getElementById("fileUploadError").classList.add("hidden");document.getElementById("fileUploadContainer").classList.add("hidden");document.getElementById("fileUploadInput").value=
+"";return!1});document.getElementById("fileUploadForm").addEventListener("submit",function(a){a.preventDefault();a=document.getElementById("fileUploadInput");var b=a.value;b&&(b=b.substr(b.lastIndexOf("\\")+1),xa(b,a.files[0],function(a){var b=document.getElementById("fileUploadError");a?(b.textContent=a,b.classList.remove("hidden")):(b.classList.add("hidden"),document.getElementById("fileUploadInput").value="",document.getElementById("fileUploadContainer").classList.add("hidden"))}));return!1});
+document.getElementById("attachFile").addEventListener("click",function(a){a.preventDefault();D&&document.getElementById("fileUploadContainer").classList.remove("hidden");return!1});document.getElementById("msgForm").addEventListener("submit",function(a){a.preventDefault();a=document.getElementById("msgInput");if(D&&a.value){var b=D,d=H,c=new XMLHttpRequest,f="api/msg?room="+b.id+"&text="+encodeURIComponent(a.value);if(d){var e=r(C.a,d.h),l="Message";"C"===b.id[0]?l="Channel message":"D"===b.id[0]?
+l="Direct message":"G"===b.id[0]&&(l="Group message");f+="&attachments="+encodeURIComponent(JSON.stringify([{fallback:d.text,author_name:"<@"+e.id+"|"+e.name+">",author_icon:e.a.m,text:d.text,footer:l,ts:d.b}]))}c.open("POST",f,!0);c.send(null);a.value="";H&&(H=null,P())}Y();return!1});window.addEventListener("blur",function(){window.hasFocus=!1});window.addEventListener("focus",function(){window.hasFocus=!0;I=0;D&&ya();Y()});window.hasFocus=!0;(function(){var a=document.getElementById("emojiButton");
+if("makeEmoji"in window){var b=window.makeEmoji("smile");b?a.innerHTML="<span class='emoji-small'>"+b.outerHTML+"</span>":a.style.backgroundImage='url("smile.svg")';(b=window.makeEmoji("paperclip"))?document.getElementById("attachFile").innerHTML="<span class='emoji-small'>"+b.outerHTML+"</span>":document.getElementById("attachFile").style.backgroundImage='url("public/paperclip.svg")';a.addEventListener("click",function(){va.D(document.body,function(a){a&&(document.getElementById("msgInput").value+=
+":"+a+":");Y()})})}else a.classList.add("hidden")})();Z()});var va=function(){function a(a,b){for(var c=a.target;c!==h&&c&&"LI"!==c.nodeName;)c=c.parentElement;c&&"LI"===c.nodeName&&c.id&&"emojibar-"===c.id.substr(0,9)?b(c.id.substr(9)):b(null)}function b(){if(!d())return!1;y&&y(null);return!0}function d(){return h.parentElement?(h.parentElement.removeChild(m),h.parentElement.removeChild(h),!0):!1}function c(a){var b=0,c;a=void 0===a?u.value:a;if(g()){var d=window.searchEmojis(a);c=f(d);for(var h in t)t[h].visible&&(t[h].visible=!1,p.removeChild(t[h].g));
+h=0;for(var l=c.length;h<l;h++){var k=c[h].name,m=t[k];if(!m){var m=t,R=k,y=k,k=window.makeEmoji(d[k]),w=document.createElement("span");w.appendChild(k);w.className="emoji-medium";k=e(y,w);m=m[R]=k}m.visible||(m.visible=!0,p.appendChild(m.g));b++}}for(h in v)v[h].visible&&(v[h].visible=!1,q.removeChild(v[h].g));c=f(C.a.c);h=0;for(l=c.length;h<l;h++)k=c[h].name,""!==a&&k.substr(0,a.length)!==a||"alias:"===C.a.c[k].substr(0,6)||(m=v[k],m||(d=v,R=m=k,k=C.a.c[k],y=document.createElement("span"),w=document.createElement("span"),
+y.className="emoji emoji-custom",y.style.backgroundImage='url("'+k+'")',w.appendChild(y),w.className="emoji-medium",k=e(R,w),m=d[m]=k),m.visible||(m.visible=!0,q.appendChild(m.g)),b++);return b}function f(a){var b=C.a.a.c.b,c=[],d;for(d in a)c.push({name:d,C:0,count:b[d]||0});return c=c.sort(function(a,b){var c=b.count-a.count;return c?c:a.C-b.C})}function e(a,b){var c=document.createElement("li");c.appendChild(b);c.className="emojibar-list-item";c.id="emojibar-"+a;return{visible:!1,g:c}}function l(a){var b=
+document.createElement("img"),c=document.createElement("div");b.src=a;c.appendChild(b);c.className="emojibar-header";return c}function g(){return"searchEmojis"in window}var h=document.createElement("div"),m=document.createElement("div"),k=document.createElement("div"),p=document.createElement("ul"),q=document.createElement("ul"),u=document.createElement("input"),t={},v={},J=document.createElement("div"),K=document.createElement("span"),L=document.createElement("span"),y;m.addEventListener("click",
+function(a){var c=h.getBoundingClientRect();(a.screenY<c.top||a.screenY>c.bottom||a.screenX<c.left||a.screenX>c.right)&&b()});m.className="emojibar-overlay";h.className="emojibar";k.className="emojibar-emojis";J.className="emojibar-detail";K.className="emojibar-detail-img";L.className="emojibar-detail-name";p.className=q.className="emojibar-list";u.className="emojibar-search";J.appendChild(K);J.appendChild(L);k.appendChild(l(window.emojiProviderHeader));k.appendChild(p);k.appendChild(l("emojicustom.png"));
+k.appendChild(q);h.appendChild(k);h.appendChild(J);h.appendChild(u);u.addEventListener("keyup",function(){c()});h.addEventListener("mousemove",function(b){a(b,function(a){var b=a?t[a]||v[a]:null;b?(K.innerHTML=b.g.outerHTML,L.textContent=":"+a+":"):(K.textContent="",L.textContent="")})});h.addEventListener("click",function(b){a(b,function(a){a&&d()&&y&&y(a)})});return{isSupported:g,D:function(a,b){return g()?(y=b,a.appendChild(m),a.appendChild(h),u.value="",c(),u.focus(),!0):!1},search:c,close:b}}();var C,X={};
+function za(a,b){if(a!==D||!window.hasFocus){var d=new RegExp("<@"+C.a.a.id),c=!1,f=!1;X[a.id]||(X[a.id]={o:0,u:0});b.forEach(function(b){var e;if(!(e="D"===a.id[0]||b.text.match(d)))a:{b=b.text;e=C.a.a.c.a;for(var g=0,h=e.length;g<h;g++)if(-1!==b.indexOf(e[g])){console.log("Found highlight "+e[g]+" in "+b);e=!0;break a}e=!1}e?(f|=!X[a.id].o,X[a.id].o++,c=!0):X[a.id].u++});N();document.getElementById(a.id).classList.add("unread");c&&document.getElementById(a.id).classList.add("unreadHi");f&&!window.hasFocus&&
+sa()}}function ya(){var a=D;X[a.id]&&(X[a.id]={o:0,u:0},N());a=document.getElementById(a.id);a.classList.remove("unread");a.classList.remove("unreadHi")}C=new function(){this.c=0;this.a=new n;this.b={}};var W=0,D=null;function Aa(a){var b=new XMLHttpRequest;b.timeout=6E4;b.onreadystatechange=function(){if(4===b.readyState)if(b.status){var d=null,c=2===Math.floor(b.status/100);if(c){W&&(W=0,O(!0));d=b.response;try{d=JSON.parse(d)}catch(f){d=null}}else W?(W+=Math.floor((W||5)/2),W=Math.min(60,W)):(W=5,O(!1));a(c,d)}else W&&(W=0,O(!0)),Aa(a)};b.open("GET","api?v="+C.c,!0);b.send(null)}
+function Ba(a,b){if(a){if(b){var d=C;b.v&&(d.c=b.v);if(b["static"]){var c=d.a,f=b["static"];if(f.bots)for(var e=0,l=f.bots.length;e<l;e++){var g=c.j[f.bots[e].id];g||(g=c.j[f.bots[e].id]=new ha(f.bots[e].id));var h=f.bots[e];g.h=h.deleted;g.name=h.name;g.a.P=h.icons.image_36;g.a.m=h.icons.image_48;g.a.s=h.icons.image_72}if(f.users)for(e=0,l=f.users.length;e<l;e++)(g=c.b[f.users[e].id])||(g=c.b[f.users[e].id]=new ea(f.users[e].id)),h=f.users[e],g.name=h.name,g.h=h.deleted,g.status=h.status,g.a.M=h.profile.image_24,
+g.a.N=h.profile.image_32,g.a.m=h.profile.image_48,g.a.s=h.profile.image_72,g.a.K=h.profile.image_192,g.a.S=h.profile.image_512;if(f.ims)for(e=0,l=f.ims.length;e<l;e++)if(h=r(c,f.ims[e].user))h.b||(c.w[f.ims[e].id]=h.b=new da(f.ims[e].id,h)),g=h.b,g.c=parseFloat(f.ims[e].last_read),g.b=h.h;if(f.channels)for(e=0,l=f.channels.length;e<l;e++){(g=c.h[f.channels[e].id])||(g=c.h[f.channels[e].id]=new ba(f.channels[e].id));var h=f.channels[e],m=c;g.name=h.name;g.b=h.is_archived;g.c=parseFloat(h.last_read);
+g.a={};if(h.members)for(var k=0,p=h.members.length;k<p;k++){var q=r(m,h.members[k]);g.a[q.id]=q;q.f[g.id]=g}}e=0;for(l=f.groups.length;e<l;e++){(g=c.l[f.groups[e].id])||(g=c.l[f.groups[e].id]=new ca(f.groups[e].id));h=c;m=f.groups[e];k=[];g.a={};p=0;for(q=m.members.length;p<q;p++){var u=r(h,m.members[p]);g.a[m.members[p]]=u;u.f[g.id]=g;k.push(u.name)}g.name=k.join(", ");g.b=m.is_archived;g.c=parseFloat(m.last_read)}f.emojis&&(c.c=f.emojis);c.f||(c.f=new aa(f.team.id));e=c.f;l=f.team;e.name=l.name;
+e.a.O=l.icon.image_34;e.a.R=l.icon.image_44;e.a.T=l.icon.image_68;e.a.U=l.icon.image_88;e.a.I=l.icon.image_102;e.a.J=l.icon.image_132;e.a.L=l.icon.image_230;e.a.V=l.icon.image_default;f.self&&(c.a=r(c,f.self.id),c.a.c||(c.a.c=new fa),ga(c.a.c,f.self.prefs));oa()}if(b.live){for(var t in b.live)(c=d.b[t])?A(c,b.live[t]):d.b[t]=new z(t,250,b.live[t]);for(var v in b.live)(t=x(d.a,v))&&!t.b&&(za(t,b.live[v]),D&&b.live[D.id]&&ta())}}Z()}else setTimeout(Z,1E3*W)}function Z(){Aa(Ba)}
+function wa(a){D&&document.getElementById(D.id).classList.remove("selected");document.getElementById(a.id).classList.add("selected");document.body.classList.remove("no-room-selected");D=a;a=D.name||(D.f?D.f.name:void 0);if(!a){a=[];for(var b in D.a)a.push(D.a[b].name);a=a.join(", ")}document.getElementById("currentRoomTitle").textContent=a;ta();Y();document.getElementById("fileUploadContainer").classList.add("hidden");ya();H&&(H=null,P());D.c&&!C.b[D.id]&&(b=new XMLHttpRequest,b.open("GET","api/hist?room="+
+D.id,!0),b.send(null))}function xa(a,b,d){var c=D;new FileReader;var f=new FormData,e=new XMLHttpRequest;f.append("file",b);f.append("filename",a);e.onreadystatechange=function(){4===e.readyState&&(204===e.status?d(null):d(e.statusText))};e.open("POST","api/file?room="+c.id);e.send(f)}function T(a,b,d){var c=new XMLHttpRequest;c.open("POST","api/reaction?room="+a+"&msg="+b+"&reaction="+encodeURIComponent(d),!0);c.send(null)};

+ 1 - 2
srv/src/slack.js

@@ -189,9 +189,8 @@ Slack.prototype.onMessage = function(msg) {
         var channelId = msg["channel"] || msg["channel_id"] || msg["item"]["channel"]
             ,histo = this.history[channelId];
         if (histo) {
-            histo.push(msg);
+            this.data.liveV = Math.max(this.data.liveV, histo.push(msg));
             histo.resort();
-            this.data.liveV = Math.max(this.data.liveV, parseFloat(msg["ts"]));
         } else if (this.data.getChannel(msg["channel"])) {
             this.fetchHistory(msg["channel"]);
         }

+ 6 - 2
srv/src/slackData.js

@@ -191,14 +191,18 @@ function SlackIms(id, user) {
     this.user = user;
     /** @type {number} */
     this.lastRead;
+    /** @type {boolean} */
+    this.archived;
 }
 
 /**
+ * @param {SlackUser|SlackBot} user
  * @param {*} imsData
 **/
-SlackIms.prototype.update = function(imsData) {
+SlackIms.prototype.update = function(user, imsData) {
     this.created = imsData["created"];
     this.lastRead = parseFloat(imsData["last_read"]);
+    this.archived = user.deleted;
 }
 
 /**
@@ -499,7 +503,7 @@ SlackData.prototype.updateStatic = function(data) {
         if (user) {
             if (!user.ims)
                 this.ims[data["ims"][i]["id"]] = user.ims = new SlackIms(data["ims"][i]["id"], user);
-            user.ims.update(data["ims"][i]);
+            user.ims.update(user, data["ims"][i]);
         }
     }
     if (data["channels"]) for (var i =0, nbChan = data["channels"].length; i < nbChan; i++) {

+ 119 - 33
srv/src/slackHistory.js

@@ -5,23 +5,23 @@
  * @param {number} ts
 **/
 function SlackMessage(e, ts) {
-    /** @type {string} **/
-    this.userId = e["user"];
+    /** @const @type {string} **/
+    this.userId = e["user"] || e["bot_id"];
 
-    /** @type {string} **/
+    /** @type {string} */
+    this.username = e["username"];
+
+    /** @const @type {string} **/
     this.id = e["ts"];
 
-    /** @type {number} **/
+    /** @const @type {number} **/
     this.ts = ts || parseFloat(e["ts"]);
 
-    /** @type {string} **/
-    this.type = e["type"];
-
-    /** @type {string} **/
-    this.subtype = e["subtype"];
+    /** @type {string} */
+    this.text = e["text"] || "";
 
-    /** @type {*} **/
-    this.raw = e;
+    /** @type {Array.<*>} */
+    this.attachments = e["attachments"] || [];
 
     /** @type {boolean} */
     this.starred = !!e["is_starred"];
@@ -29,12 +29,24 @@ function SlackMessage(e, ts) {
     /** @type {boolean} */
     this.pinned = false; // TODO
 
+    /** @type {boolean} */
+    this.edited = !!e["edited"];
+
+    /** @type {boolean} */
+    this.removed = e["removed"] || false;
+
+    /** @const @type {boolean} */
+    this.isMeMessage = e["subtype"] === "me_message" || e["isMeMessage"];
+
     /**
      * for each emoji code, an array of user id
      * @type {Object.<string, Array.<string>>}
     **/
     this.reactions = {};
 
+    /** @type {number} */
+    this.version = this.ts;
+
     var _this = this;
     e["reactions"] && e["reactions"].forEach(function(r) {
         _this.reactions[r["name"]] = [];
@@ -44,6 +56,31 @@ function SlackMessage(e, ts) {
     });
 }
 
+SlackMessage.prototype.update = function(e, ts) {
+    if (e) {
+        this.text = e["text"] || "";
+        if (e["attachments"]) this.attachments = e["attachments"];
+        this.starred = !!e["is_starred"];
+        this.pinned = false; // TODO
+        this.edited = !!e["edited"];
+        this.removed = !!e["removed"];
+
+        var _this = this;
+        if (e["reactions"]) {
+            this.reactions = {};
+            e["reactions"].forEach(function(r) {
+                _this.reactions[r["name"]] = [];
+                r["users"].forEach(function(userId) {
+                    _this.reactions[r["name"]].push(userId);
+                });
+            });
+        }
+    } else {
+        this.removed = true;
+    }
+    this.version = ts;
+}
+
 /**
  * @constructor
  * @param {SlackChan|SlackGroup|SlackIms|string} room or roomId
@@ -66,33 +103,62 @@ function SlackHistory(room, keepMessages, evts) {
 
 /** @return {*} */
 SlackMessage.prototype.toStatic = function() {
-    return this.raw;
+    var reactions = [];
+    for (var reaction in this.reactions) {
+        reactions.push({
+            "name": reaction
+            ,"users": this.reactions[reaction]
+        });
+    }
+    return {
+        "user": this.userId
+        ,"username": this.username
+        ,"ts": this.id
+        ,"text": this.text
+        ,"attachments": this.attachments
+        ,"is_starred": this.is_starred || undefined
+        ,"edited": this.edited
+        ,"removed": this.removed
+        ,"reactions": reactions
+        ,"isMeMessage": this.isMeMessage
+    };
 }
 
 /**
  * @param {string} reaction
  * @param {string} userId
+ * @param {number} ts
 **/
-SlackMessage.prototype.addReaction = function(reaction, userId) {
+SlackMessage.prototype.addReaction = function(reaction, userId, ts) {
     if (!this.reactions[reaction])
         this.reactions[reaction] = [];
     this.reactions[reaction].push(userId);
+    this.version = ts;
 }
 
 /**
  * @param {string} reaction
  * @param {string} userId
+ * @param {number} ts
 **/
-SlackMessage.prototype.removeReaction = function(reaction, userId) {
+SlackMessage.prototype.removeReaction = function(reaction, userId, ts) {
+    var updated = false;
     if (this.reactions[reaction]) {
-        if (this.reactions[reaction].length === 1)
+        if (this.reactions[reaction].length === 1 && this.reactions[reaction][0] === userId) {
             delete this.reactions[reaction];
+            updated = true;
+        }
         else {
             this.reactions[reaction] = this.reactions[reaction].filter(function(i) {
-                return i !== userId;
+                if (i !== userId)
+                    return false;
+                updated = true;
+                return true;
             });
         }
     }
+    if (updated)
+        this.version = ts;
 }
 
 SlackMessage.prototype.hasReactionForUser = function(reaction, userId) {
@@ -121,42 +187,61 @@ SlackHistory.prototype.pushAll = function(evts) {
 SlackHistory.prototype.push = function(ev) {
     var ts = parseFloat(ev["ts"]);
 
-    for (var i =0, nbMsg = this.messages.length; i < nbMsg; i++) {
-        if (this.messages[i].ts === ts) {
-            return ts;
+    if (!ev["type"] || ev["type"] === "message") {
+        var exists = false
+            ,targetId = ev["ts"]
+            ,modifArg = ev;
+
+        if (ev["subtype"] === "message_changed" && ev["previous_message"]) {
+            targetId = ev["previous_message"]["ts"];
+            modifArg = ev["message"]
+        } else if (ev["subtype"] === "message_deleted" && ev["previous_message"]) {
+            targetId = ev["previous_message"]["ts"];
+            modifArg = null;
+        }
+        for (var i =0, nbMsg = this.messages.length; i < nbMsg; i++) {
+            if (this.messages[i].id === targetId) {
+                this.messages[i].update(modifArg, ts);
+                exists = true;
+                break;
+            }
         }
+        if (!exists)
+            this.messages.push(new SlackMessage(ev, ts));
+    } else if (ev["type"] === "reaction_added") {
+        this.addReaction(ev["reaction"], ev["user"], ev["item"]["ts"], ts);
+    } else if (ev["type"] === "reaction_removed") {
+        this.removeReaction(ev["reaction"], ev["user"], ev["item"]["ts"], ts);
+    } else {
+        return 0;
     }
-    this.messages.push(new SlackMessage(ev, ts));
     while (this.messages.length > this.keepMessages)
         this.messages.shift();
-    if (ev["type"] === "reaction_added") {
-        this.addReaction(ev["reaction"], ev["user"], parseFloat(ev["item"]["ts"]));
-    } else if (ev["type"] === "reaction_removed") {
-        this.removeReaction(ev["reaction"], ev["user"], parseFloat(ev["item"]["ts"]));
-    }
     return ts;
 }
 
 /**
  * @param {string} reaction
  * @param {string} userId
+ * @param {string} msgId
  * @param {number} ts
 **/
-SlackHistory.prototype.addReaction = function(reaction, userId, ts) {
-    var msg = this.getMessage(ts);
+SlackHistory.prototype.addReaction = function(reaction, userId, msgId, ts) {
+    var msg = this.getMessageById(msgId);
     if (msg)
-        msg.addReaction(reaction, userId);
+        msg.addReaction(reaction, userId, ts);
 }
 
 /**
  * @param {string} reaction
  * @param {string} userId
+ * @param {string} msgId
  * @param {number} ts
 **/
-SlackHistory.prototype.removeReaction = function(reaction, userId, ts) {
-    var msg = this.getMessage(ts);
+SlackHistory.prototype.removeReaction = function(reaction, userId, msgId, ts) {
+    var msg = this.getMessageById(msgId);
     if (msg)
-        msg.removeReaction(reaction, userId);
+        msg.removeReaction(reaction, userId, ts);
 }
 
 /**
@@ -189,8 +274,9 @@ SlackHistory.prototype.getMessageById = function(id) {
 SlackHistory.prototype.toStatic = function(knownVersion) {
     var result = [];
 
-    for (var i = this.messages.length -1; i >= 0 && this.messages[i].ts > knownVersion; i--) {
-        result.push(this.messages[i].toStatic());
+    for (var i = this.messages.length -1; i >= 0; i--) {
+        if (this.messages[i].version > knownVersion)
+            result.push(this.messages[i].toStatic());
     }
     return result;
 }