| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460 |
- 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"
- ,postMeMsg: "chat.meMessage"
- ,editMsg: "chat.update"
- ,removeMsg: "chat.delete"
- ,postFile: "files.upload"
- ,setActive: "users.setActive"
- ,emojiList: "emoji.list"
- ,slashList: "commands.list"
- ,slashExec: "chat.command"
- ,addReaction: "reactions.add"
- ,removeReaction: "reactions.remove"
- }
- ,HISTORY_LENGTH = 35
- ,INACTIVE_DELAY = 120000
- ,UPDATE_LIVE = [
- "message"
- ,"pin_added"
- ,"pin_removed"
- ,"reaction_added"
- ,"reaction_removed"
- ,"star_added"
- ,"star_removed"
- ] // Message type that affect live history
- ;
- var
- SLACK_SESSIONS = []
- ;
- setInterval(function() {
- var t = Date.now();
- SLACK_SESSIONS.forEach(function(slackInst) {
- if (!slackInst.closeIfInnactive(t) && slackInst.active)
- slackInst.sendActive();
- });
- }, 60000);
- function Slack(sess) {
- this.token = sess.slackToken;
- this.rtm = null;
- this.data = new SlackData(this);
- this.history = {};
- this.connected = false;
- this.active = true;
- this.lastPing = Date.now();
- }
- Slack.prototype.onUserInterract = function(t) {
- this.lastPing = Math.max(t, this.lastPing);
- }
- 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) => {
- _this.getSlashCommands((commands) => {
- body.emojis = emojis;
- body.commands = commands;
- _this.data.updateStatic(body, Date.now());
- _this.connectRtm(body.url);
- _this.waitForEvent(knownVersion);
- });
- });
- });
- };
- Slack.prototype.sendCommand = function(room, cmd, arg) {
- httpsRequest(
- SLACK_ENDPOINT
- +GETAPI.slashExec
- +"?token=" +this.token
- +"&command=" +encodeURIComponent(cmd.name)
- +"&disp=" +encodeURIComponent(cmd.name)
- +"&channel=" +room.id
- +"&text=" +arg);
- }
- Slack.prototype.sendTyping = function(room) {
- this.rtm.send('{"id":' +this.rtmId++ +',"type":"typing","channel":"' +room.id +'"}');
- }
- Slack.prototype.getSlashCommands = function(cb) {
- httpsRequest(SLACK_ENDPOINT +GETAPI.slashList +"?token=" +this.token, (status, body) => {
- if (!status || !body || !body.ok)
- cb(null);
- else
- cb(body.commands || {});
- });
- };
- 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, Date.now())
- ,updatedLive = _this.getLiveUpdates(knownVersion)
- ,updated;
- if (updatedCtx || updatedLive) {
- updated = {};
- updated["static"] = updatedCtx;
- updated["live"] = updatedLive;
- updated["v"] = Math.max(_this.data.liveV, _this.data.staticV);
- }
- if (updated) {
- 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, t) {
- this.data.onMessage(msg, t);
- if ((msg["channel"] || msg["channel_id"] || (msg["item"] && msg["item"]["channel"])) && msg["type"] && UPDATE_LIVE.indexOf(msg["type"]) !== -1) {
- var channelId = msg["channel"] || msg["channel_id"] || msg["item"]["channel"]
- ,channel = this.data.getChannel(msg["channel"])
- ,histo = this.history[channelId];
- if (histo) {
- this.data.liveV = Math.max(this.data.liveV, histo.push(msg, t));
- histo.resort();
- if (channel)
- channel.lastMsg = Math.max(this.data.liveV, channel.lastMsg);
- } else if (channel) {
- this.fetchHistory(msg["channel"]);
- }
- }
- };
- Slack.prototype.connectRtm = function(url, cb) {
- var _this = this;
- this.rtmId = 0;
- this.rtm = new WebSocket(url);
- this.rtm.on("message", function(msg) {
- if (!_this.connected && cb) {
- cb();
- }
- if (!_this.connected) {
- _this.connected = true;
- SLACK_SESSIONS.push(_this);
- }
- _this.onMessage(JSON.parse(msg), Date.now());
- });
- this.rtm.once("error", function(e) {
- _this.connected = false;
- console.error(e);
- _this.close();
- var arrIndex = SLACK_SESSIONS.indexOf(_this);
- if (arrIndex >= 0) SLACK_SESSIONS.splice(arrIndex, 1);
- });
- this.rtm.once("end", function() {
- console.error("RTM hang up");
- _this.onClose();
- });
- };
- Slack.prototype.onClose = function() {
- var arrIndex = SLACK_SESSIONS.indexOf(this);
- this.connected = false;
- if (arrIndex >= 0) SLACK_SESSIONS.splice(arrIndex, 1);
- };
- /**
- * @return {boolean} true if innactive and closeding
- **/
- Slack.prototype.closeIfInnactive = function(t) {
- if (t - this.lastPing >INACTIVE_DELAY) {
- this.close();
- return true;
- }
- return false;
- };
- Slack.prototype.sendActive = function() {
- httpsRequest(SLACK_ENDPOINT+GETAPI.setActive
- +"?token=" +this.token);
- };
- Slack.prototype.close = function() {
- this.rtm.close();
- this.onClose();
- };
- 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 {string} msgId
- * @param {string} reaction
- **/
- Slack.prototype.addReaction = function(channel, msgId, reaction) {
- httpsRequest(SLACK_ENDPOINT +GETAPI.addReaction
- +"?token=" +this.token
- +"&name=" +reaction
- +"&channel="+channel.id
- +"×tamp="+msgId);
- }
- /**
- * @param {SlackChan|SlackGroup|SlackIms} channel
- * @param {string} msgId
- * @param {string} reaction
- **/
- Slack.prototype.removeReaction = function(channel, msgId, reaction) {
- httpsRequest(SLACK_ENDPOINT +GETAPI.removeReaction
- +"?token=" +this.token
- +"&name=" +reaction
- +"&channel="+channel.id
- +"×tamp="+msgId);
- }
- /**
- * @param {SlackChan|SlackGroup|SlackIms} channel
- * @param {Array.<string>} text
- **/
- Slack.prototype.sendMeMsg = function(channel, text) {
- httpsRequest(SLACK_ENDPOINT +GETAPI.postMeMsg
- +"?token=" +this.token
- +"&channel=" +channel.id
- +"&text=" +text.join("\n")
- +"&as_user=true");
- };
- /**
- * @param {SlackChan|SlackGroup|SlackIms} channel
- * @param {Array.<string>} text
- * @param {Array.<Object>=} 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");
- };
- /**
- * @param {SlackChan|SlackGroup|SlackIms} channel
- * @param {string} msgId
- **/
- Slack.prototype.removeMsg = function(channel, msgId) {
- httpsRequest(SLACK_ENDPOINT +GETAPI.removeMsg
- +"?token=" +this.token
- +"&channel=" +channel.id
- +"&ts=" +msgId
- +"&as_user=true");
- };
- /**
- * @param {SlackChan|SlackGroup|SlackIms} channel
- * @param {string} msgId
- * @param {string} text
- **/
- Slack.prototype.editMsg = function(channel, msgId, text) {
- httpsRequest(SLACK_ENDPOINT +GETAPI.editMsg
- +"?token=" +this.token
- +"&channel=" +channel.id
- +"&ts=" +msgId
- +"&text=" +text.join("\n")
- +"&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]
- ,now = Date.now();
- if (!history) {
- history = _this.history[targetId] = new SlackHistory(targetId, HISTORY_LENGTH);
- history.isNew = true;
- }
- this.data.liveV = Math.max(history.pushAll(resp.messages, Date.now()), this.data.liveV);
- }
- });
- };
- module.exports.Slack = Slack;
|