Bläddra i källkod

[add] send messages
[bugfix][wip] do not write on an ended socket

B Thibault 8 år sedan
förälder
incheckning
f34e2332cf
9 ändrade filer med 139 tillägg och 38 borttagningar
  1. 4 0
      cli/resources.js
  2. 11 2
      cli/ui.js
  3. 11 1
      cli/workflow.js
  4. 4 2
      srv/public/index.html
  5. 6 5
      srv/public/slack.min.js
  6. 61 23
      srv/src/httpServ.js
  7. 17 0
      srv/src/slack.js
  8. 5 1
      srv/src/slackHistory.js
  9. 20 4
      srv/src/url.js

+ 4 - 0
cli/resources.js

@@ -6,6 +6,10 @@ var R = {
             title: "currentRoomTitle"
             ,content: "chatWindow"
         }
+        ,message: {
+            form: "msgForm"
+            ,input: "msgInput"
+        }
     }
     ,klass: {
         selected: "selected"

+ 11 - 2
cli/ui.js

@@ -85,8 +85,8 @@ function createMessageDom(msg) {
     ts.textContent = (new Date(msg.ts * 1000)).toLocaleTimeString();
     text.textContent = msg.raw["text"];
     var sender = SLACK.context.getMember(msg.raw["user"]);
-    authorName.textContent = sender.name;
-    authorImg.src = sender.icons.image_48;
+    authorName.textContent = sender ? sender.name : (msg.raw["username"] || "?");
+    authorImg.src = sender ? sender.icons.image_48 : "";
     author.appendChild(authorImg);
     author.appendChild(authorName);
     dom.appendChild(author);
@@ -124,6 +124,15 @@ function onChanClick(e) {
 
 document.addEventListener('DOMContentLoaded', function() {
     document.getElementById(R.id.chatList).addEventListener("click", onChanClick);
+    document.getElementById(R.id.message.form).addEventListener("submit", function(e) {
+        e.preventDefault();
+        var input =document.getElementById(R.id.message.input);
+        if (SELECTED_ROOM && input.value) {
+            sendMsg(SELECTED_ROOM, input.value);
+            input.value = "";
+        }
+        return false;
+    });
 
     startPolling();
 });

+ 11 - 1
cli/workflow.js

@@ -17,7 +17,7 @@ var
 **/
 function fetchHistory(room, cb) {
     var xhr = new XMLHttpRequest();
-    xhr.open('GET', 'api?room=' +room.id, true);
+    xhr.open('GET', 'api/hist?room=' +room.id, true);
     xhr.send(null);
 }
 
@@ -95,3 +95,13 @@ function unselectRoom() {
     document.getElementById(SELECTED_ROOM.id).classList.remove(R.klass.selected);
 }
 
+/**
+ * @param {SlackChan|SlackGroup|SlackIms} chan
+ * @param {string} msg
+**/
+function sendMsg(chan, msg) {
+    var xhr = new XMLHttpRequest();
+    xhr.open('POST', 'api/msg?room=' +chan.id +"&text=" +msg, true);
+    xhr.send(null);
+}
+

+ 4 - 2
srv/public/index.html

@@ -14,8 +14,10 @@
             <div class="slack-chat-title" id="currentRoomTitle"></div>
             <div class="slack-chat-content" id="chatWindow"></div>
             <div class="slack-chat-control">
-                <input type="text" />
-                <input type="submit" />
+                <form id="msgForm">
+                    <input type="text" id="msgInput" />
+                    <input type="submit" />
+                </form>
             </div>
         </div>
         <script src="slack.min.js"></script>

+ 6 - 5
srv/public/slack.min.js

@@ -1,10 +1,11 @@
-function g(a,b){this.id=a.id;this.name=a.name;this.b=parseFloat(a.last_read);this.a={};if(a.members)for(var e=0,c=a.members.length;e<c;e++){var f=h(b,a.members[e]);this.a[f.id]=f;f.f[this.id]=this}}function l(a,b){var e=[];this.id=b.id;this.a={};for(var c=0,f=b.members.length;c<f;c++){var d=h(a,b.members[c]);this.a[b.members[c]]=d;d.f[this.id]=this;e.push(d.name)}this.name=e.join(", ");this.b=parseFloat(b.last_read)}function m(a,b){this.id=b.id;this.c=a;this.b=parseFloat(b.last_read)}
+function g(a,b){this.id=a.id;this.name=a.name;this.b=parseFloat(a.last_read);this.a={};if(a.members)for(var e=0,c=a.members.length;e<c;e++){var f=h(b,a.members[e]);this.a[f.id]=f;f.f[this.id]=this}}function m(a,b){var e=[];this.id=b.id;this.a={};for(var c=0,f=b.members.length;c<f;c++){var d=h(a,b.members[c]);this.a[b.members[c]]=d;d.f[this.id]=this;e.push(d.name)}this.name=e.join(", ");this.b=parseFloat(b.last_read)}function n(a,b){this.id=b.id;this.c=a;this.b=parseFloat(b.last_read)}
 function q(a){this.id=a.id;this.name=a.name;this.status=a.status;this.b={w:a.profile.image_24,A:a.profile.image_32,i:a.profile.image_48,m:a.profile.image_72,u:a.profile.image_192,C:a.profile.image_512};this.f={};this.a=null}function r(a){this.id=a.id;this.name=a.name;this.b={B:a.icons.image_36,i:a.icons.image_48,m:a.icons.image_72};this.f={};this.a=null}function t(){this.f={};this.b={};this.g={};this.a={};this.c=null;this.h={}}function h(a,b){return a.a[b]||a.h[b]||null}
 "undefined"!==typeof module&&(module.l.o=t);function u(a){this.j=parseFloat(a.ts);this.raw=a}function v(a,b,e){this.id="string"===typeof a?a:a.id;this.a=[];this.b=b;e&&w(this,e)}function w(a,b){b.forEach(function(a){this.push(a)}.bind(a))}v.prototype.push=function(a){for(var b=parseFloat(a.ts),e=0,c=this.a.length;e<c;e++)if(this.a[e].j===b)return!1;for(this.a.push(new u(a));this.a.length>this.b;)this.a.shift()};"undefined"!==typeof module&&(module.l.s=v);function x(){var a=document.createDocumentFragment(),b=y.a.c?Object.keys(y.a.c.f):[];b.sort(function(a,b){return a[0]!==b[0]?a[0]-b[0]:(y.a.f[a]||y.a.b[a]).name.localeCompare((y.a.f[b]||y.a.b[b]).name)});b.forEach(function(b){b=y.a.f[b]||y.a.b[b];var c=document.createElement("li");c.id=b.id;c.className="slack-context-room";c.textContent=b.name;c&&a.appendChild(c)});b=y.a.a?Object.keys(y.a.a):[];b.sort(function(a,b){return y.a.a[a].name.localeCompare(y.a.a[b].name)});b.forEach(function(b){b=y.a.a[b].a;
 var c=document.createElement("li");c.id=b.id;c.className="slack-context-room";c.textContent=b.c.name;c&&a.appendChild(c)});document.getElementById("chanList").textContent="";document.getElementById("chanList").appendChild(a)}
-function z(a){var b=document.createElement("div"),e=document.createElement("div"),c=document.createElement("div"),f=document.createElement("div"),d=document.createElement("img"),k=document.createElement("span");b.className="slackmsg-item";e.className="slackmsg-ts";c.className="slackmsg-msg";f.className="slackmsg-author";d.className="slackmsg-author-img";k.className="slackmsg-author-name";e.textContent=(new Date(1E3*a.j)).toLocaleTimeString();c.textContent=a.raw.text;a=h(y.a,a.raw.user);k.textContent=
-a.name;d.src=a.b.i;f.appendChild(d);f.appendChild(k);b.appendChild(f);b.appendChild(c);b.appendChild(e);return b}function A(){var a=document.createDocumentFragment();document.getElementById("chatWindow").textContent="";y.b[B.id]&&y.b[B.id].a.forEach(function(b){a.appendChild(z(b))});document.getElementById("chatWindow").appendChild(a)}
+function z(a){var b=document.createElement("div"),e=document.createElement("div"),c=document.createElement("div"),f=document.createElement("div"),d=document.createElement("img"),k=document.createElement("span");b.className="slackmsg-item";e.className="slackmsg-ts";c.className="slackmsg-msg";f.className="slackmsg-author";d.className="slackmsg-author-img";k.className="slackmsg-author-name";e.textContent=(new Date(1E3*a.j)).toLocaleTimeString();c.textContent=a.raw.text;var l=h(y.a,a.raw.user);k.textContent=
+l?l.name:a.raw.username||"?";d.src=l?l.b.i:"";f.appendChild(d);f.appendChild(k);b.appendChild(f);b.appendChild(c);b.appendChild(e);return b}function A(){var a=document.createDocumentFragment();document.getElementById("chatWindow").textContent="";y.b[B.id]&&y.b[B.id].a.forEach(function(b){a.appendChild(z(b))});document.getElementById("chatWindow").appendChild(a)}
 function C(a){for(;a.target!==a.currentTarget&&a.target;){if(a.target.classList.contains("slack-context-room")){if((a=y.a.f[a.target.id]||y.a.g[a.target.id]||y.a.b[a.target.id])&&a!==B){B&&document.getElementById(B.id).classList.remove("selected");document.getElementById(a.id).classList.add("selected");document.body.classList.remove("no-room-selected");B=a;a=void 0;var b=B.name||(B.c?B.c.name:void 0);if(!b){b=[];for(a in B.a)b.push(B.a[a].name);b=b.join(", ")}document.getElementById("currentRoomTitle").textContent=
-b;A();B.b&&!y.b[B.id]&&(a=new XMLHttpRequest,a.open("GET","api?room="+B.id,!0),a.send(null))}break}a.target=a.target.parentElement}}document.addEventListener("DOMContentLoaded",function(){document.getElementById("chatList").addEventListener("click",C);D()});var y;y=new function(){this.c=0;this.a=new t;this.b={}};var E=5,B=null;function F(a){var b=new XMLHttpRequest;b.timeout=6E4;b.onreadystatechange=function(){if(4===b.readyState)if(b.status){var e=null,c=2===Math.floor(b.status/100);if(c){E=5;e=b.response;try{e=JSON.parse(e)}catch(f){e=null}}else E+=Math.floor(E/2),E=Math.min(60,E);a(c,e)}else F(a),E=5};b.open("GET","api?v="+y.c,!0);b.send(null)}
-function G(a,b){if(a){if(b){var e=y;b.v&&(e.c=b.v);if(b["static"]){for(var c=e.a,f=b["static"],d=0,k=f.bots.length;d<k;d++)c.h[f.bots[d].id]=new r(f.bots[d]);d=0;for(k=f.users.length;d<k;d++)c.a[f.users[d].id]=new q(f.users[d]);d=0;for(k=f.ims.length;d<k;d++){var n=h(c,f.ims[d].user);n&&(n.a=new m(n,f.ims[d]),c.g[n.a.id]=n.a)}d=0;for(k=f.channels.length;d<k;d++)c.f[f.channels[d].id]=new g(f.channels[d],c);d=0;for(k=f.groups.length;d<k;d++)c.b[f.groups[d].id]=new l(c,f.groups[d]);c.c=h(c,f.self.id);
+b;A();B.b&&!y.b[B.id]&&(a=new XMLHttpRequest,a.open("GET","api/hist?room="+B.id,!0),a.send(null))}break}a.target=a.target.parentElement}}
+document.addEventListener("DOMContentLoaded",function(){document.getElementById("chatList").addEventListener("click",C);document.getElementById("msgForm").addEventListener("submit",function(a){a.preventDefault();a=document.getElementById("msgInput");if(B&&a.value){var b=new XMLHttpRequest;b.open("POST","api/msg?room="+B.id+"&text="+a.value,!0);b.send(null);a.value=""}return!1});D()});var y;y=new function(){this.c=0;this.a=new t;this.b={}};var E=5,B=null;function F(a){var b=new XMLHttpRequest;b.timeout=6E4;b.onreadystatechange=function(){if(4===b.readyState)if(b.status){var e=null,c=2===Math.floor(b.status/100);if(c){E=5;e=b.response;try{e=JSON.parse(e)}catch(f){e=null}}else E+=Math.floor(E/2),E=Math.min(60,E);a(c,e)}else F(a),E=5};b.open("GET","api?v="+y.c,!0);b.send(null)}
+function G(a,b){if(a){if(b){var e=y;b.v&&(e.c=b.v);if(b["static"]){for(var c=e.a,f=b["static"],d=0,k=f.bots.length;d<k;d++)c.h[f.bots[d].id]=new r(f.bots[d]);d=0;for(k=f.users.length;d<k;d++)c.a[f.users[d].id]=new q(f.users[d]);d=0;for(k=f.ims.length;d<k;d++){var l=h(c,f.ims[d].user);l&&(l.a=new n(l,f.ims[d]),c.g[l.a.id]=l.a)}d=0;for(k=f.channels.length;d<k;d++)c.f[f.channels[d].id]=new g(f.channels[d],c);d=0;for(k=f.groups.length;d<k;d++)c.b[f.groups[d].id]=new m(c,f.groups[d]);c.c=h(c,f.self.id);
 x()}if(b.live){for(var p in b.live)(c=e.b[p])?w(c,b.live[p]):e.b[p]=new v(p,500,b.live[p]);B&&b.live[B.id]&&A()}}D()}else setTimeout(D,1E3*E)}function D(){F(G)};

+ 61 - 23
srv/src/httpServ.js

@@ -11,7 +11,11 @@ function Server(port) {
     var ctx = this;
 
     this.httpServ = http.createServer(function(req, res) {
-        ctx.onRequest(req, res);
+        res.ended = false;
+        res.on('end', () => {
+            res.ended = true;
+        });
+        ctx.onRequest(req, res););
     });
     this.httpServ.listen(port, ctx.onListen);
 }
@@ -103,34 +107,68 @@ Server.prototype.onRequest = function(req, res) {
         var apiSuccess = false;
 
         res.slack = slackManager.lazyGet(req.session);
-        if (req.urlObj.queryTokens.room) {
-            req.urlObj.queryTokens.room.forEach(function(targetId) {
-                res.slack.fetchHistory(targetId);
-            });
+        if (req.urlObj.match(["api", "hist"])) {
+            if (!req.urlObj.queryTokens.room) {
+                res.writeHeader("400", "Bad request");
+            } else {
+                var allFound = true;
+                req.urlObj.queryTokens.room.forEach(function(targetId) {
+                    if (!res.slack.data.getChannel(targetId)) {
+                        allFound = false;
+                    }
+                    res.slack.fetchHistory(targetId);
+                });
+                if (allFound)
+                    res.writeHeader("204", "No Content");
+                else
+                    res.writeHeader("404", "Channel not found");
+            }
             sessionManager.saveSession(req.session);
-            res.writeHeader("204", "No Content");
             res.end();
-        } else {
-            res.slack.onRequest(req.urlObj.queryTokens.v || 0, (slack, newData) => {
-                try {
-                    if (!slack.connected) {
-                        res.writeHeader("403", {
-                            "Content-Type": "application/json"
-                        });
-                        res.write(slack.error, () => {
-                            res.end();
-                        });
+        } else if (req.urlObj.match(["api", "msg"])) {
+            if (req.method === 'POST') {
+                if (!req.urlObj.queryTokens.room || !req.urlObj.queryTokens.text) {
+                    res.writeHeader("400", "Bad request");
+                } else {
+                    var chan = res.slack.data.getChannel(req.urlObj.queryTokens.room[0]);
+                    if (chan) {
+                        res.slack.sendMsg(chan, req.urlObj.queryTokens.text);
+                        res.writeHeader("204", "No Content");
                     } else {
-                        res.writeHeader("200", {
-                            "Content-Type": "application/json"
-                        });
-                        res.write(JSON.stringify(newData), () => {
-                            res.end();
-                        });
+                        res.writeHeader("404", "Channel not found");
                     }
-                } catch (e) {}
+                }
+            } else {
+                res.writeHeader("400", "Bad request");
+            }
+            sessionManager.saveSession(req.session);
+            res.end();
+        } else if (req.urlObj.match(["api"])) {
+            res.slack.onRequest(req.urlObj.queryTokens.v || 0, (slack, newData) => {
+                if (!res.ended) {
+                    try {
+                        if (!slack.connected) {
+                            res.writeHeader("403", {
+                                "Content-Type": "application/json"
+                            });
+                            res.write(slack.error, () => {
+                                res.end();
+                            });
+                        } else {
+                            res.writeHeader("200", {
+                                "Content-Type": "application/json"
+                            });
+                            res.write(JSON.stringify(newData), () => {
+                                res.end();
+                            });
+                        }
+                    } catch (e) {}
+                }
                 sessionManager.saveSession(req.session);
             });
+        } else {
+            res.writeHeader("404", "Not Found");
+            res.end();
         }
     }
 };

+ 17 - 0
srv/src/slack.js

@@ -16,6 +16,7 @@ const SLACK_ENDPOINT = "https://slack.com/api/"
         ,channelHistory: "channels.history"
         ,directHistory: "im.history"
         ,groupHistory: "groups.history"
+        ,postMsg: "chat.postMessage"
     }
     ,HISTORY_LENGTH = 25
 ;
@@ -38,6 +39,7 @@ Slack.prototype.onRequest = function(knownVersion, cb) {
 };
 
 function httpsRequest(url, cb) {
+    console.log("F querying " +url);
     https.get(url, (res) => {
         if (res.statusCode !== 200) {
             cb(res.statusCode, null);
@@ -197,6 +199,21 @@ Slack.getOauthToken = function(code, cb) {
     });
 };
 
+/**
+ * @param {SlackChan|SlackGroup|SlackIms} channel
+ * @param {Array.<string>} text
+**/
+Slack.prototype.sendMsg = function(channel, text) {
+    httpsRequest(SLACK_ENDPOINT +GETAPI.postMsg
+        +"?token=" +this.token
+        +"&channel=" +channel.id
+        +"&text=" +text.join("\n")
+        +"&as_user=true"
+    ,(status, msg) => {
+        console.log(status, msg);
+    });
+};
+
 Slack.prototype.fetchHistory = function(targetId) {
     var _this = this
         ,baseUrl = "";

+ 5 - 1
srv/src/slackHistory.js

@@ -33,6 +33,7 @@ function SlackHistory(room, keepMessages, evts) {
     if (evts) this.pushAll(evts);
 }
 
+/** @return {*} */
 SlackMessage.prototype.toStatic = function() {
     return this.raw;
 }
@@ -59,13 +60,16 @@ SlackHistory.prototype.push = function(ev) {
         this.messages.shift();
 }
 
+/**
+ * @return {Array.<*>}
+**/
 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());
     }
-    return result;
+    return result.reverse();
 }
 
 SlackHistory.prototype.resort = function() {

+ 20 - 4
srv/src/url.js

@@ -2,10 +2,17 @@ var fs = require('fs');
 
 function Url(url) {
     this.url = url;
+    if (this.url.indexOf('?') >= 0)
+        this.url = this.url.substr(0, this.url.indexOf('?'));
     while (this.url.length && this.url[0] === '/') {
         this.url = this.url.substr(1);
     }
-    this.urlParts = this.url.split("/");
+    urlParts = this.url.split("/");
+    this.urlParts = [];
+    for (var i =0, nbParts = urlParts.length; i < nbParts; i++) {
+        if (urlParts[i])
+            this.urlParts.push(urlParts[i]);
+    }
     var stop = url.indexOf("?");
     if (stop === -1) {
         stop = url.length;
@@ -24,7 +31,7 @@ function Url(url) {
     }
     this.serve = null;
     try{
-        if (this.urlParts.length == 1 && this.urlParts[0] == '') {
+        if (!this.urlParts.length) {
             this.serve = {
                 filename: "public/index.html"
                 ,stat: fs.statSync("public/index.html")
@@ -35,8 +42,7 @@ function Url(url) {
                 ,stat: fs.statSync("public/" +this.urlParts[0])
             };
         }
-    } catch (e) {
-    }
+    } catch (e) {}
 }
 
 Url.prototype.isPublic = function() {
@@ -47,4 +53,14 @@ Url.prototype.getReadStream = function() {
     return fs.createReadStream(this.serve.filename);
 };
 
+Url.prototype.match = function(arr) {
+    if (arr.length !== this.urlParts.length)
+        return false;
+    for (var i=0, arrLen = arr.length; i < arrLen; i++)
+        if (arr[i] !== this.urlParts[i] && arr[i] !== '?')
+            return false;
+    return true;
+}
+
 module.exports = { Url: Url };
+