const ChatContext = require('./context.js').ChatContext ,ChatInfo = require('./context.js').ChatInfo ,Command = require('./context.js').Command ,Room = require('./room.js').Room ,Chatter = require('./chatter.js').Chatter ,PrivateMessageRoom = require('./room.js').PrivateMessageRoom; const SLACK_TYPING_DELAY = 6000; /** * @constructor * @extends {ChatInfo} **/ function SlackTeam(teamId) { ChatInfo.call(this, teamId); /** @type {string} */ this.domain; /** @type {string} */ this.callApp; /** @type {string} */ this.callAppName; /** @type {boolean} */ this.fileUploadPermission; /** @type {boolean} */ this.fileEditPermission; /** @type {boolean} */ this.fileDeletePermission; /** @type {string} */ this.icons = { small: "" ,large: "" }; }; SlackTeam.prototype = Object.create(ChatInfo.prototype); SlackTeam.prototype.constructor = SlackTeam; SlackTeam.prototype.update = function(teamData, t) { ChatInfo.prototype.update.call(this, teamData, t); if (teamData["domain"] !== undefined) this.domain = teamData["domain"]; if (teamData["prefs"]) { this.callApp = teamData["prefs"]["calling_app_id"]; this.callAppName = teamData["prefs"]["calling_app_name"]; this.fileUploadPermission = teamData["prefs"]["disable_file_uploads"]; this.fileEditPermission = teamData["prefs"]["disable_file_editing"]; this.fileDeletePermission = teamData["prefs"]["disable_file_deleting"]; } if (teamData["icon"]) { this.icons.small = teamData["icon"]["image_34"]; this.icons.large = teamData["icon"]["image_230"]; } }; /** * @constructor **/ function SlackChan(teamId, chanId, isGroup) { Room.call(this, teamId +chanId); /** @const @type {string} */ this.remoteId = chanId; this.isPrivate = isGroup; }; SlackChan.prototype = Object.create(Room.prototype); SlackChan.prototype.constructor = SlackChan; SlackChan.prototype.setNameFromMembers = function() { var userNames = []; for (var i in this.users) if (!this.users[i].deleted) userNames.push(this.users[i].getName()); userNames.sort(); this.name = userNames.join(", "); }; /** * @param {*} chanData * @param {ChatContext} ctx * @param {number} t * @param {string=} idPrefix **/ SlackChan.prototype.update = function(chanData, ctx, t, idPrefix) { if (chanData["last_read"] !== undefined) chanData["last_read"] = parseFloat(chanData["last_read"]) * 1000; if (chanData["latest"]) chanData["last_msg"] = parseFloat(chanData["latest"]) * 1000; if (!chanData["last_msg"] && chanData["unread_count"] !== undefined) { if (chanData["unread_count"]) chanData["last_msg"] = Math.max(chanData["last_msg"] || 0, 0); else chanData["last_msg"] = chanData["last_read"]; } return Room.prototype.update.call(this, chanData, ctx, t, idPrefix); }; /** * @constructor * @extends {PrivateMessageRoom} * @param {string} id * @param {SlackChatter} user **/ function SlackIms(teamId, id, user) { PrivateMessageRoom.call(this, teamId +id, user); /** @const @type {string} */ this.remoteId = id; }; SlackIms.prototype = Object.create(PrivateMessageRoom.prototype); SlackIms.prototype.constructor = SlackIms; /** * @param {*} chanData * @param {ChatContext} ctx * @param {number} t * @param {string=} idPrefix **/ SlackIms.prototype.update = function(chanData, ctx, t, idPrefix) { if (chanData["last_read"] !== undefined) chanData["last_read"] = parseFloat(chanData["last_read"]) * 1000; if (chanData["latest"]) chanData["last_msg"] = parseFloat(chanData["latest"]) * 1000; if (!chanData["last_msg"] && chanData["unread_count"] !== undefined) { if (chanData["unread_count"]) chanData["last_msg"] = Math.max(chanData["last_msg"] || 0, 0); else chanData["last_msg"] = chanData["last_read"]; } return PrivateMessageRoom.prototype.update.call(this, chanData, ctx, t, idPrefix); }; /** * @constructor * @extends {Chatter} **/ function SlackChatter(teamId, id) { Chatter.call(this, teamId +id); /** @type {string} */ this.remoteId = id; /** @type {Object.} */ this.icons = { small: "" ,large: "" }; }; SlackChatter.prototype = Object.create(Chatter.prototype); SlackChatter.prototype.constructor = SlackChatter; SlackChatter.prototype.setPresence = function(presenceStr, t) { this.presence = presenceStr !== 'away'; this.version = Math.max(t, this.version); }; SlackChatter.prototype.update = function(userData, t) { if (userData["presence"] !== undefined) userData["isPresent"] = userData["presence"] !== 'away'; if (userData["profile"]) { this.email = userData["profile"]["email"]; this.firstName = userData["profile"]["first_name"]; this.lastName = userData["profile"]["last_name"]; userData["name"] = userData["profile"]["display_name"]; userData["real_name"] = userData["profile"]["real_name"]; userData["phone"] = userData["profile"]["phone"]; userData["goal"] = userData["profile"]["title"]; } Chatter.prototype.update.call(this, userData, t); }; SlackChatter.prototype.getSmallIcon = function() { return this.icons.small; }; SlackChatter.prototype.getLargeIcon = function() { return this.icons.large; }; /** * @constructor * @extends {SlackChatter} **/ function SlackUser(teamId, id) { SlackChatter.call(this, teamId, id); }; SlackUser.prototype = Object.create(SlackChatter.prototype); SlackUser.prototype.constructor = SlackUser; SlackUser.prototype.update = function(userData, t) { SlackChatter.prototype.update.call(this, userData, t); if (userData["profile"]) { this.icons.small = userData["profile"]["image_48"] || this.icons.small; this.icons.large = userData["profile"]["image_512"] || this.icons.large; } }; /** * @constructor * @extends {SlackChatter} **/ function SlackBot(teamId, id) { SlackChatter.call(this, teamId, id); /** @type {string} */ this.appId; this.isBot = true; }; SlackBot.prototype = Object.create(SlackChatter.prototype); SlackBot.prototype.constructor = SlackBot; /** @param {*} botData */ SlackBot.prototype.update = function(botData, t) { SlackChatter.prototype.update.call(this, botData, t); if (botData["app_id"] !== undefined) this.appId = botData["app_id"]; this.isBot = true; if (botData["icons"]) { this.icons.small = botData["icons"]["image_48"] || this.icons.small; this.icons.large = botData["icons"]["image_72"] || this.icons.large; } }; /** * @constructor * @extends {ChatContext} **/ function SlackData(slack) { ChatContext.call(this); /** * Node serv handler * @type {*} **/ this.slack = slack; this.capacities = { starMsg: true, pinMsg: true, starChannel: true, reactMsg: true, editMsg: true, //editOtherMsg: false, // ability to edit other chatter's msg removeMsg: true, //moderateMsg: false, // ability to remove other chatter's msg replyToMsg: true, topic: true, purpose: true }; }; SlackData.prototype = Object.create(ChatContext.prototype); SlackData.prototype.constructor = SlackData; /** * @param {*} data **/ SlackData.prototype.updateStatic = function(data, t) { if (data["team"] && !this.team) this.team = this.teamFactory(data["team"]["id"]); if (data["bots"]) for (var i =0, nbBots = data["bots"].length; i < nbBots; i++) { var botObj = this.users[this.team.id +'|' +data["bots"][i]["id"]]; if (!botObj) botObj = this.users[this.team.id +'|' +data["bots"][i]["id"]] = new SlackBot(this.team.id +'|', data["bots"][i]["id"]); botObj.update(data["bots"][i], t); } if (data.commands) { var aliasCmd = {}; for (let i in data.commands) if (data.commands[i].canonical_name && data.commands[i].canonical_name !== i) { aliasCmd[data.commands[i].canonical_name] = {}; Object.assign(aliasCmd[data.commands[i].canonical_name], data.commands[i]); aliasCmd[data.commands[i].canonical_name].name = data.commands[i].canonical_name; } for (let i in aliasCmd) data.commands[i] = aliasCmd[i]; } ChatContext.prototype.updateStatic.call(this, data, t, this.team.id +'|'); if (data["ims"]) for (var i =0, nbIms = data["ims"].length; i < nbIms; i++) { var user = this.users[this.team.id +'|' +data["ims"][i]["user"]]; if (user) { if (!this.channels[this.team.id +'|' +data["ims"][i]["id"]]) this.channels[this.team.id +'|' +data["ims"][i]["id"]] = new SlackIms(this.team.id +'|', data["ims"][i]["id"], user); this.channels[this.team.id +'|' +data["ims"][i]["id"]].update(data["ims"][i], this, t, this.team.id +'|'); } } if (data["groups"]) this.updateGroups(data["groups"], t); if (data["mpims"]) this.updateGroups(data["mpims"], t); if (data["self"] && data["self"]["manual_presence"]) { this.self.setPresence(data["self"]["manual_presence"], t); } }; SlackData.prototype.updateGroups = function(groupsData, t) { for (var i =0, nbGroups = groupsData.length; i < nbGroups; i++) { var groupData = groupsData[i] ,groupObj = this.channels[this.team.id +'|' +groupData["id"]]; if (!groupObj) groupObj = this.channels[this.team.id +'|' +groupData["id"]] = new SlackChan(this.team.id +'|', groupData["id"], true); groupObj.update(groupData, this, t, this.team.id +'|'); groupObj.archived |= groupData["is_open"] === false; groupObj.isMember = true; if (groupData["is_mpim"]) groupObj.setNameFromMembers(); } }; SlackData.prototype.teamFactory = function(id) { return new SlackTeam('SLACK|' +id); }; SlackData.prototype.userFactory = function(userData) { return new SlackUser(this.team.id +'|', userData["id"]); }; SlackData.prototype.roomFactory = function(roomData) { roomData["created"] = roomData["created"] ? parseFloat(roomData["created"]) * 1000 : undefined; return new SlackChan(this.team.id +'|', roomData["id"], false); }; SlackData.prototype.commandFactory = function(data) { var getServiceName = (function() { if (data["service_name"]) return data["service_name"]; else if (data["app"]) { for (var i in this.users) if (this.users[i].appId === data["app"] && this.users[i].name) return this.users[i].name; console.error("Unknown app " +data["app"]); return ""; } return "Slack"; }).bind(this); data["category"] = data["category"] || getServiceName(); return new Command(data); }; /** * @param {*} msg * @param {number} t **/ SlackData.prototype.onMessage = function(msg, t) { if (msg["type"] === "presence_change") { var member = this.users[this.team.id +'|' +msg["user"]]; if (member) member.setPresence(msg["presence"], t); this.staticV = Math.max(this.staticV, t); } else if (msg["type"] === "manual_presence_change") { if (this.self) { this.self.setPresence(msg["presence"], t); this.staticV = Math.max(this.staticV, t); } } else if (msg["type"] === "user_typing") { var chanId = this.team.id +'|' +msg["channel"]; if (!this.typing[chanId]) this.typing[chanId] = {}; this.typing[chanId][this.team.id +'|' +msg["user"]] = t +SLACK_TYPING_DELAY; this.staticV = Math.max(this.staticV, t); } else if (msg["type"] === "im_marked" || msg["type"] === "channel_marked" || msg["type"] === "group_marked") { var channel = this.channels[this.team.id +'|' +msg["channel"]]; if (channel) { channel.lastRead = parseFloat(msg["ts"]) * 1000; this.staticV = channel.version = Math.max(channel.version, t); } } else if (msg["type"] === "star_added") { if (msg["user"] === this.self.remoteId && msg["item"]["type"] !== "message") { var targetChan = this.getChannelByRemoteId(msg["item"]["channel"]); if (targetChan && !targetChan.starred) { targetChan.starred = true; targetChan.version = Math.max(targetChan.version, t); this.staticV = Math.max(this.staticV, t); } } } else if (msg["type"] === "star_removed") { if (msg["user"] === this.self.remoteId && msg["item"]["type"] !== "message") { var targetChan = this.getChannelByRemoteId(msg["item"]["channel"]); if (targetChan && targetChan.starred) { targetChan.starred = false; targetChan.version = Math.max(targetChan.version, t); this.staticV = Math.max(this.staticV, t); } } } else if (msg["type"] === "pin_added") { var targetChan = this.getChannelByRemoteId(msg["channel_id"] || msg["item"]["channel"]); if (!targetChan.pins) { this.slack.fetchPinned(targetChan); } else { var histo = this.slack.lazyHistory(targetChan); targetChan.pins.push(histo.messageFactory(msg["item"]["message"], t)); targetChan.version = Math.max(targetChan.version, t); this.staticV = Math.max(this.staticV, t); } } else if (msg["type"] === "pin_removed") { var targetChan = this.getChannelByRemoteId(msg["channel_id"] || msg["item"]["channel"]); if (!msg["pin_count"]) { targetChan.pins = []; targetChan.version = Math.max(targetChan.version, t); this.staticV = Math.max(this.staticV, t); } if (!targetChan.pins) { this.slack.fetchPinned(targetChan); } else { var found = false; for (var i =0, nbPins = targetChan.pins.length; i < nbPins; i++) { if (targetChan.pins[i].id === msg["item"]["message"]["ts"]) { targetChan.pins.splice(i, 1); targetChan.version = Math.max(targetChan.version, t); this.staticV = Math.max(this.staticV, t); found = true; break; } } if (!found) // wtf out-of-sync this.slack.fetchPinned(targetChan); } /* } else { console.log(msg); //*/ } }; SlackData.prototype.getChannelByRemoteId = function(remoteId) { for (var id in this.channels) if (this.channels[id].remoteId === remoteId) return this.channels[id]; }; /** * @param {number} knownVersion * @return {Object|undefined} **/ SlackData.prototype.getUpdates = function(knownVersion) { if (this.staticV > knownVersion) return this.toStatic(knownVersion); return undefined; }; /** @suppress {undefinedVars,checkTypes} */ (function() { if (typeof module !== "undefined") { module.exports.SlackData = SlackData; } })();