const WebSocket = require('ws'), https = require('https'), sleep = require("sleep").sleep ,SlackData = require("./slackData.js").SlackData ,SlackHistory = require("./slackHistory.js").SlackHistory ,config = require("../config.js") ; const SLACK_ENDPOINT = "https://slack.com/api/" ,SLACK_HOSTNAME = "slack.com" ,SLACK_ENDPOINT_PATH = "/api/" ,GETAPI = { rtmStart: "rtm.start" ,oauth: "oauth.access" ,channelHistory: "channels.history" ,directHistory: "im.history" ,groupHistory: "groups.history" ,postMsg: "chat.postMessage" ,postFile: "files.upload" ,emojiList: "emoji.list" } ,HISTORY_LENGTH = 35 ; var SLACK_SESSIONS = [] ; setInterval(function() { SLACK_SESSIONS.forEach(function(slackInst) { slackInst.closeIfInnactive(); }); }, 60000); function Slack(sess) { this.token = sess.slackToken; this.rtm = null; this.data = new SlackData(this); this.history = {}; this.connected = false; } Slack.prototype.onRequest = function(knownVersion, cb) { this.lastCb = cb; if (this.connected === false) { this.connect(knownVersion); } else { this.waitForEvent(knownVersion); } }; function httpsRequest(url, cb) { https.get(url, (res) => { if (res.statusCode !== 200) { cb(res.statusCode, null); return; } var body = null; res.on('data', (chunk) => { body = body ? Buffer.concat([body, chunk], body.length +chunk.length) : Buffer.from(chunk); }); res.on('end', () => { try { body = JSON.parse(body.toString("utf8")); } catch (e) {} cb && cb(res.statusCode, body); }); }); } Slack.prototype.connect = function(knownVersion) { var _this = this; this.connected = undefined; httpsRequest(SLACK_ENDPOINT +GETAPI.rtmStart +"?token=" +this.token, (status, body) => { if (status !== 200) { _this.error = body.error; _this.connected = false; console.error("Slack api responded " +status); _this.lastCb(_this); return; } if (!body) { _this.error = "Slack API error"; _this.connected = false; _this.lastCb(_this); return; } if (!body.ok) { _this.error = body.error; _this.connected = false; console.error("Slack api responded !ok with ", body); _this.lastCb(_this); return; } _this.getEmojis((emojis) => { body.emojis = emojis; _this.data.updateStatic(body); _this.connectRtm(body.url); _this.waitForEvent(knownVersion); }); }); }; Slack.prototype.getEmojis = function(cb) { httpsRequest(SLACK_ENDPOINT +GETAPI.emojiList +"?token=" +this.token, (status, body) => { if (!status || !body || !body.ok) cb(null); else cb(body.emoji || {}); }); }; Slack.prototype.waitForEvent = function(knownVersion) { var tick = 0 ,_this = this ,interval; interval = setInterval(() => { tick++; if (!_this.lastCb) { clearInterval(interval); return; } if (_this.connected) { var updatedCtx = _this.data.getUpdates(knownVersion) ,updatedLive = _this.getLiveUpdates(knownVersion); if (updatedCtx || updatedLive) { var updated = { "static": updatedCtx ,"live": updatedLive ,"v": Math.max(_this.data.liveV, _this.data.staticV) // TODO "typing" stream }; clearInterval(interval); _this.lastCb(_this, updated); return; } } if (tick >= 55) { // < 1 minute timeout clearInterval(interval); _this.lastCb(_this, { v: knownVersion }); return; } }, 1000); }; /** @return {Object|undefined} */ Slack.prototype.getLiveUpdates = function(knownVersion) { var result = {}; for (var roomId in this.history) { var history = this.history[roomId]; if (history.isNew) { result[roomId] = history.toStatic(0); history.isNew = false; } else { var roomData = history.toStatic(knownVersion); if (roomData.length) result[roomId] = roomData; } } for (var roomId in result) { return result; } return undefined; }; Slack.prototype.onMessage = function(msg) { this.data.onMessage(msg); if (msg["channel"] && msg["type"] === "message") { var histo = this.history[msg["channel"]]; if (histo) { 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"]); } } }; Slack.prototype.connectRtm = function(url, cb) { var _this = this; this.rtm = new WebSocket(url); this.rtm.on("message", function(msg) { if (!_this.connected && cb) { cb(); } _this.connected = true; _this.onMessage(JSON.parse(msg)); SLACK_SESSIONS.push(_this); }); this.rtm.once("error", function(e) { _this.connected = false; console.error(e); _this.close(); }); this.rtm.once("end", function() { _this.connected = false; console.error("RTM hang up"); _this.close(); }); }; Slack.prototype.closeIfInnactive = function() { //TODO }; Slack.prototype.close = function() { }; Slack.getOauthToken = function(code, redirectUri, cb) { httpsRequest(SLACK_ENDPOINT+GETAPI.oauth +"?client_id=" +config.clientId +"&client_secret=" +config.clientSecret +"&redirect_uri=" +redirectUri +"&code=" +code, (status, resp) => { if (status === 200 && resp.ok) { cb(resp); } else { cb(null); } }); }; /** * @param {SlackChan|SlackGroup|SlackIms} channel * @param {string} contentType * @param {function(string|null)} callback **/ Slack.prototype.openUploadFileStream = function(channel, contentType, callback) { var req = https.request({ hostname: SLACK_HOSTNAME ,method: 'POST' ,path: SLACK_ENDPOINT_PATH +GETAPI.postFile +"?token=" +this.token +"&channels=" +channel.id ,headers: { "Content-Type": contentType } }, (res) => { var errorJson; res.on("data", (chunk) => { errorJson = errorJson ? Buffer.concat([errorJson, chunk], errorJson.length +chunk.length) : Buffer.from(chunk); }); res.once("end", () => { if (res.statusCode === 200) { callback(null); } else { try { errorJson = JSON.parse(errorJson.toString()); } catch(e) { callback("error"); return; } callback(errorJson["error"] || "error"); } }); }); return req; }; /** * @param {SlackChan|SlackGroup|SlackIms} channel * @param {Array.} text * @param {Array.=} attachments **/ Slack.prototype.sendMsg = function(channel, text, attachments) { httpsRequest(SLACK_ENDPOINT +GETAPI.postMsg +"?token=" +this.token +"&channel=" +channel.id +"&text=" +text.join("\n") + (attachments ? ("&attachments=" +encodeURIComponent(JSON.stringify(attachments))) : "") +"&as_user=true"); }; Slack.prototype.fetchHistory = function(targetId) { var _this = this ,baseUrl = ""; if (targetId[0] === 'D') { baseUrl = SLACK_ENDPOINT +GETAPI.directHistory; } else if (targetId[0] === 'C') { baseUrl = SLACK_ENDPOINT +GETAPI.channelHistory; } else if (targetId[0] === 'G') { baseUrl = SLACK_ENDPOINT +GETAPI.groupHistory; } httpsRequest(baseUrl +"?token="+this.token +"&channel=" +targetId +"&count=" +HISTORY_LENGTH, (status, resp) => { if (status === 200 && resp && resp.ok) { var history = _this.history[targetId]; if (!history) { history = _this.history[targetId] = new SlackHistory(targetId, HISTORY_LENGTH); history.isNew = true; } this.data.liveV = Math.max(history.pushAll(resp.messages), this.data.liveV); } }); }; module.exports.Slack = Slack;