From 01aab965ad36b5bd812f3d0a16aed404ae5acfd7 Mon Sep 17 00:00:00 2001 From: calzoneman Date: Fri, 27 Dec 2013 21:06:10 -0500 Subject: [PATCH] Continue work on channel.js --- lib/channel-new.js | 424 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 424 insertions(+) diff --git a/lib/channel-new.js b/lib/channel-new.js index f4aed6eb..e677b89c 100644 --- a/lib/channel-new.js +++ b/lib/channel-new.js @@ -341,6 +341,56 @@ Channel.prototype.getRank = function (name, callback) { }); }; +/** + * Looks up the highest rank of any alias of an IP address + */ +Channel.prototype.getIPRank = function (ip, callback) { + var self = this; + db.getAliases(ip, function (err, names) { + if (self.dead) { + return; + } + + db.users.getGlobalRanks(names, function (err, res) { + if (self.dead) { + return; + } + + if (err) { + callback(err, null); + return; + } + + var rank = res.reduce(function (a, b) { + return Math.max(a, b); + }, 0); + + if (!self.registered) { + callback(null, rank); + return; + } + + db.channels.getRanks(self.name, names, + function (err, res) { + if (self.dead) { + return; + } + + if (err) { + callback(err, null); + return; + } + + var rank = res.reduce(function (a, b) { + return Math.max(a, b); + }, rank); + + callback(null, rank); + }); + }); + }); +}; + /** * Called when a user joins a channel */ @@ -479,12 +529,383 @@ Channel.prototype.sendMOTD = function (users) { }); }; +/** + * Sends a message to channel moderators + */ +Channel.prototype.sendModMessage = function (msg, minrank) { + if (isNaN(minrank)) { + minrank = 2; + } + + var notice = { + username: "[server]", + msg: msg + meta: { + addClass: "server-whisper" , + addClassToNameAndTimestamp: true + }, + time: Date.now() + }; + + self.users.forEach(function(u) { + if (u.rank > minrank) { + u.socket.emit("chatMsg", notice); + } + }); +}; + +/** + * Stores a video in the channel's library + */ +Channel.prototype.cacheMedia = function (media) { + // Don't cache Google Drive videos because of their time limit + if (media.type === "gd") { + return false; + } + + if (self.registered) { + db.channels.addToLibrary(self.name, media); + } +}; + +/** + * Attempts to ban a user by name + */ +Channel.prototype.tryNameBan = function (actor, name, reason) { + var self = this; + if (!self.hasPermission(actor, "ban")) { + return false; + } + + name = name.toLowerCase(); + if (name == actor.name.toLowerCase()) { + actor.socket.emit("costanza", { + msg: "Trying to ban yourself?" + }); + return; + } + + // Look up the name's rank so people can't ban others with higher rank than themselves + self.getRank(name, function (err, rank) { + if (self.dead) { + return; + } + + if (err) { + actor.socket.emit("errorMsg", { + msg: "Internal error " + err + }); + return; + } + + if (rank >= actor.rank) { + actor.socket.emit("errorMsg", { + msg: "You don't have permission to ban " + name + }); + return; + } + + if (typeof reason !== "string") { + reason = ""; + } + + reason = reason.substring(0, 255); + self.namebans[name] = { + ip: "*", + name: name, + bannedby: actor.name, + reason: reason + }; + + // If in the channel already, kick the banned user + for (var i = 0; i < self.users.length; i++) { + if (self.users[i].name.toLowerCase() == name) { + self.kick(self.users[i], "You're banned!"); + break; + } + } + self.logger.log("*** " + actor.name + " namebanned " + name); + self.sendModMessage(actor.name + " banned " + name, self.permissions.ban); + + if (!self.registered) { + return; + } + + // channel, ip, name, reason, actor + db.channels.ban(self.name, "*", name, reason, actor.name); + // TODO send banlist? + }); +}; + +/** + * Removes a name ban + */ +Channel.prototype.tryUnbanName = function (actor, name) { + var self = this; + if (!self.hasPermission(actor, "ban")) { + return; + } + + delete self.namebans[name]; + self.logger.log("*** " + actor.name + " un-namebanned " + name); + self.sendModMessage(actor.name + " unbanned " + name, self.permissions.ban); + + if (!self.registered) { + return; + } + + db.channels.unbanName(self.name, name); + // TODO send banlist? +}; + +/** + * Bans all IP addresses associated with a username + */ +Channel.prototype.tryBanAllIP = function (actor, name, reason, range) { + var self = this; + if (!self.hasPermission(actor, "ban")) { + return; + } + + if (typeof name !== "string") { + return; + } + + name = name.toLowerCase(); + if (name === actor.name.toLowerCase()) { + actor.socket.emit("costanza", { + msg: "Trying to ban yourself?" + }); + return; + } + + db.getIPs(name, function (err, ips) { + if (self.dead) { + return; + } + + if (err) { + actor.socket.emit("errorMsg", { + msg: "Internal error: " + err + }); + return; + } + + ips.forEach(function (ip) { + self.tryBanIP(actor, ip, name, range); + }); + }); +}; + +/** + * Bans an individual IP + */ +Channel.prototype.tryBanIP = function (actor, ip, name, reason, range) { + if (range) { + ip = ip.replace(/(\d+)\.(\d+)\.(\d+)\.(\d+)/, "$1.$2.$3"); + } + + if (typeof reason !== "string") { + reason = ""; + } + + reason = reason.substring(0, 255); + + self.getIPRank(ip, function (err, rank) { + if (self.dead) { + return; + } + + if (err) { + actor.socket.emit("errorMsg", { + msg: "Internal error: " + err + }); + return; + } + + if (rank >= actor.rank) { + actor.socket.emit("errorMsg", { + msg: "You don't have permission to ban IP: " + util.maskIP(ip) + }); + return; + } + + self.ipbans[ip] = { + ip: ip, + name: name, + bannedby: actor.name, + reason: reason + }; + + self.logger.log("*** " + actor.name + " banned " + ip + " (" + name + ")"); + self.sendModMessage(actor.name + " banned " + ip + " (" + name + ")", self.permissions.ban); + // If in the channel already, kick the banned user + for (var i = 0; i < self.users.length; i++) { + if (self.users[i].ip === ip) { + self.kick(self.users[i], "You're banned!"); + break; + } + } + + if (!self.registered) { + return; + } + + // channel, ip, name, reason, ban actor + db.channels.ban(self.name, ip, name, reason, actor.name); + }); +}; + +/** + * Removes an IP ban + */ +Channel.prototype.unbanIP = function (actor, ip) { + var self = this; + if (!self.hasPermission(actor, "ban")) { + return; + } + + var record = self.ipbans[ip]; + delete self.ipbans[ip]; + self.logger.log("*** " + actor.name + " unbanned " + ip + " (" + record.name + ")"); + self.sendModMessage(actor.name + " unbanned " + util.maskIP(ip) + " (" + record.name + ")", self.permissions.ban); + + if (!self.registered) { + return; + } + + db.channels.unbanIP(self.name, ip); +}; + +/** + * Sends the banlist + */ +Channel.prototype.sendBanlist = function (users) { + var self = this; + + var bans = []; + var unmaskedbans = []; + for (var ip in self.ipbans) { + bans.push({ + ip: util.maskIP(ip), + name: self.ipbans[ip].name, + reason: self.ipbans[ip].reason, + bannedby: self.ipbans[ip].bannedby + }); + unmaskedbans.push({ + ip: ip, + name: self.ipbans[ip].name, + reason: self.ipbans[ip].reason, + bannedby: self.ipbans[ip].bannedby + }); + } + + users.forEach(function (u) { + if (!self.hasPermission(u, "ban")) { + return; + } + + if (u.rank >= 255) { + u.socket.emit("banlist", unmaskedbans); + } else { + u.socket.emit("banlist", bans); + } + }); +}; + +/** + * Sends the chat filter list + */ +Channel.prototype.sendChatFilters = function (users) { + var self = this; + + var pkt = self.filters.map(function (f) { + return f.pack(); + }); + + users.forEach(function (u) { + if (!self.hasPermission(u, "filteredit")) { + return; + } + + u.socket.emit("chatFilters", f); + }); +}; + +/** + * Sends the playlist + */ +Channel.prototype.sendPlaylist = function (users) { + var self = this; + + var pl = self.playlist.items.toArray(); + var current = null; + if (self.playlist.current) { + current = self.playlist.current.uid; + } + + users.forEach(function (u) { + u.socket.emit("playlist", pl); + u.socket.emit("setPlaylistMeta", self.plmeta); + if (current !== null) { + u.socket.emit("setCurrent", current); + } + }); +}; + +/** + * Searches channel library + */ +Channel.prototype.search = function (query, callback) { + var self = this; + if (!self.registered) { + callback([]); + return; + } + + if (typeof query !== "string") { + query = ""; + } + + query = query.substring(0, 100); + + db.channels.searchLibrary(self.name, query, function (err, res) { + if (err) { + res = []; + } + + res.sort(function(a, b) { + var x = a.title.toLowerCase(); + var y = b.title.toLowerCase(); + + return (x == y) ? 0 : (x < y ? -1 : 1); + }); + + res.forEach(function (r) { + r.duration = util.formatTime(r.seconds); + }); + + callback(res); + }); +}; + +/** + * Sends the result of readLog() to a user if the user has sufficient permission + */ Channel.prototype.tryReadLog = function (user) { if (user.rank < 3) { user.kick("Attempted readChanLog with insufficient permission"); return; } + if (!self.registered) { + user.socket.emit("readChanLog", { + success: false, + data: "Channel log is only available to registered channels." + }); + return; + } + var filterIp = user.global_rank < 255; this.readLog(filterIp, function (err, data) { if (err) { @@ -501,6 +922,9 @@ Channel.prototype.tryReadLog = function (user) { }); }; +/** + * Reads the last 100KiB of the channel's log file, masking IP addresses if desired + */ Channel.prototype.readLog = function (filterIp, callback) { var maxLen = 102400; // Limit to last 100KiB var file = this.logger.filename;