From ba0664641e0aabb2bc10a9df37115204ba83bd62 Mon Sep 17 00:00:00 2001 From: calzoneman Date: Fri, 27 Dec 2013 11:08:03 -0500 Subject: [PATCH] Start rewriting channel.js --- lib/channel-new.js | 210 +++++++++++++++++++++++++++++++++++++++++++ lib/channel.js | 23 +++-- lib/user.js | 3 +- lib/web/auth.js | 3 +- templates/login.jade | 8 +- templates/nav.jade | 2 +- 6 files changed, 235 insertions(+), 14 deletions(-) create mode 100644 lib/channel-new.js diff --git a/lib/channel-new.js b/lib/channel-new.js new file mode 100644 index 00000000..8be02c72 --- /dev/null +++ b/lib/channel-new.js @@ -0,0 +1,210 @@ +var DEFAULT_FILTERS = [ + new Filter("monospace", "`(.+?)`", "g", "$1"), + new Filter("bold", "\\*(.+?)\\*", "g", "$1"), + new Filter("italic", "_(.+?)_", "g", "$1"), + new Filter("strike", "~~(.+?)~~", "g", "$1"), + new Filter("inline spoiler", "\\[sp\\](.*?)\\[\\/sp\\]", "ig", "$1") +]; + +function Channel(name) { + var self = this; // Alias `this` to prevent scoping issues + Logger.syslog.log("Loading channel " + name); + + // Defaults + self.ready = false; + self.name = name; + self.uniqueName = name.toLowerCase(); // To prevent casing issues + self.registered = false; // set to true if the channel exists in the database + self.users = []; + self.mutedUsers = new $util.Set(); + self.playlist = new Playlist(self); + self.plqueue = new AsyncQueue(); // For synchronizing playlist actions + self.drinks = 0; + self.leader = null; + self.chatbuffer = []; + self.playlistLock = true; + self.poll = null; + self.voteskip = null; + self.permissions = { + playlistadd: 1.5, // Add video to the playlist + playlistnext: 1.5, // TODO I don't think this is used + playlistmove: 1.5, // Move a video on the playlist + playlistdelete: 2, // Delete a video from the playlist + playlistjump: 1.5, // Start a different video on the playlist + playlistaddlist: 1.5, // Add a list of videos to the playlist + oplaylistadd: -1, // Same as above, but for open (unlocked) playlist + oplaylistnext: 1.5, + oplaylistmove: 1.5, + oplaylistdelete: 2, + oplaylistjump: 1.5, + oplaylistaddlist: 1.5, + playlistaddcustom: 3, // Add custom embed to the playlist + playlistaddlive: 1.5, // Add a livestream to the playlist + exceedmaxlength: 2, // Add a video longer than the maximum length set + addnontemp: 2, // Add a permanent video to the playlist + settemp: 2, // Toggle temporary status of a playlist item + playlistgeturl: 1.5, // TODO is this even used? + playlistshuffle: 2, // Shuffle the playlist + playlistclear: 2, // Clear the playlist + pollctl: 1.5, // Open/close polls + pollvote: -1, // Vote in polls + viewhiddenpoll: 1.5, // View results of hidden polls + voteskip: -1, // Vote to skip the current video + mute: 1.5, // Mute other users + kick: 1.5, // Kick other users + ban: 2, // Ban other users + motdedit: 3, // Edit the MOTD + filteredit: 3, // Control chat filters + drink: 1.5, // Use the /d command + chat: 0 // Send chat messages + }; + self.opts = { + allow_voteskip: true, // Allow users to voteskip + voteskip_ratio: 0.5, // Ratio of skip votes:non-afk users needed to skip the video + afk_timeout: 600, // Number of seconds before a user is automatically marked afk + pagetitle: self.name, // Title of the browser tab + maxlength: 0, // Maximum length (in seconds) of a video queued + externalcss: "", // Link to external stylesheet + externaljs: "", // Link to external script + chat_antiflood: false, // Throttle chat messages + chat_antiflood_params: { + burst: 4, // Number of messages to allow with no throttling + sustained: 1, // Throttle rate (messages/second) + cooldown: 4 // Number of seconds with no messages before burst is reset + }, + show_public: false, // List the channel on the index page + enable_link_regex: true, // Use the built-in link filter + password: false // Channel password (false -> no password required for entry) + }; + self.motd = { + motd: "", // Raw MOTD text + html: "" // Filtered MOTD text (XSS removed; \n replaced by
) + }; + self.filters = DEFAULT_FILTERS; + self.banlist = new Banlist(); + self.logger = new Logger.Logger(path.join(__dirname, "../chanlogs", + self.uniqueName + ".log")); + self.css = ""; // Up to 20KB of inline CSS + self.js = ""; // Up to 20KB of inline Javascript + + self.error = false; // Set to true if something bad happens => don't save state + + self.on("ready", function () { + self.ready = true; + }); + + // Load from database + db.channels.load(self, function (err) { + if (err && err !== "Channel is not registered") { + return; + } else { + // Load state from JSON blob + self.tryLoadState(); + } + }); +}; + +Channel.prototype = EventEmitter; + +Channel.prototype.tryLoadState = function () { + var self = this; + if (self.name === "") { + return; + } + + // Don't load state if the channel isn't registered + if (!self.registered) { + self.emit("ready"); + return; + } + + var file = path.join(__dirname, "../chandump", self.uniqueName); + fs.stat(file, function (err, stats) { + if (!err) { + var mb = stats.size / 1048576; + mb = Math.floor(mb * 100) / 100; + if (mb > 1) { + Logger.errlog.log("Large chandump detected: " + self.uniqueName + + " (" + mb + " MiB)"); + self.setMOTD("Your channel file has exceeded the maximum size of 1MB " + + "and cannot be loaded. Please ask an administrator for " + + "assistance in restoring it."); + self.error = true; + self.emit("ready"); + return; + } + } + + self.loadState(); + }); +}; + +/** + * Load the channel state from disk. + * + * SHOULD ONLY BE CALLED FROM tryLoadState + */ +Channel.prototype.loadState = function () { + var self = this; + if (self.error) { + return; + } + + fs.readFile(path.join(__dirname, "../chandump", self.name), + function (err, data) { + if (err) { + // File didn't exist => start fresh + if (err.code === "ENOENT") { + self.emit("ready"); + self.saveState(); + } else { + Logger.errlog.log("Failed to open channel dump " + self.uniqueName); + Logger.errlog.log(err); + self.setMOTD("Internal error when loading channel"); + self.error = true; + self.emit("ready"); + } + return; + } + + try { + self.logger.log("*** Loading channel state"); + data = JSON.parse(data); + + // Load the playlist + if ("playlist" in data) { + } + + // Playlist lock + self.setLock(data.playlistLock || false); + + // Configurables + if ("opts" in data) { + for (var key in data.opts) { + self.opts[key] = data.opts; + } + } + + // Permissions + if ("permissions" in data) { + for (var key in data.permissions) { + self.permissions[key] = data.permissions[key]; + } + } + + // Chat filters + if ("filters" in data) { + for (var i = 0; i < data.filters.length; i++) { + var f = data.filters[i]; + var filt = new Filter(f.name, f.source, f.flags, f.replace); + filt.active = f.active; + filt.filterlinks = f.filterlinks; + self.updateFilter(filt, false); + } + } + + } catch (e) { + + } + }); +}; diff --git a/lib/channel.js b/lib/channel.js index 19ffc145..16d2e0de 100644 --- a/lib/channel.js +++ b/lib/channel.js @@ -1315,20 +1315,25 @@ Channel.prototype.calcVoteskipMax = function () { }, 0); }; -Channel.prototype.broadcastVoteskipUpdate = function() { +Channel.prototype.getVoteskipPacket = function() { var amt = this.voteskip ? this.voteskip.counts[0] : 0; var count = this.calcVoteskipMax(); var need = this.voteskip ? Math.ceil(count * this.opts.voteskip_ratio) : 0; - for(var i = 0; i < this.users.length; i++) { - if(this.users[i].rank >= 1.5) { - this.users[i].socket.emit("voteskip", { - count: amt, - need: need - }); - } - } + return { + count: amt, + need: need + }; } +Channel.prototype.broadcastVoteskipUpdate = function () { + var pkt = this.getVoteskipPacket(); + this.users.forEach(function (u) { + if (u.rank >= 1.5) { + u.socket.emit("voteskip", pkt); + } + }); +}; + Channel.prototype.broadcastMotd = function() { this.sendAll("setMotd", this.motd); } diff --git a/lib/user.js b/lib/user.js index 9646b0cf..2a607fdf 100644 --- a/lib/user.js +++ b/lib/user.js @@ -32,7 +32,8 @@ var User = function (socket) { this.name = ""; this.meta = { afk: false, - icon: false + icon: false, + aliases: [] }; this.queueLimiter = $util.newRateLimiter(); this.chatLimiter = $util.newRateLimiter(); diff --git a/lib/web/auth.js b/lib/web/auth.js index e4182390..ff4d1aaa 100644 --- a/lib/web/auth.js +++ b/lib/web/auth.js @@ -67,7 +67,8 @@ function handleLoginPage(req, res) { } } sendJade(res, 'login', { - loggedIn: false + loggedIn: false, + redirect: req.header('Referrer') }); } diff --git a/templates/login.jade b/templates/login.jade index cfc1c672..3f965dba 100644 --- a/templates/login.jade +++ b/templates/login.jade @@ -12,13 +12,15 @@ html(lang="en") ul.nav.navbar-nav mixin navdefaultlinks("/login") if loggedIn - mixin navlogoutform("/login") + if redirect + mixin navlogoutform(redirect) + else + mixin navlogoutform("/login") section#mainpage.container if wasAlreadyLoggedIn .col-lg-6.col-lg-offset-3.col-md-6.col-md-offset-3 .alert.alert-info.messagebox.center h3(style="margin: 5px auto") Logged in as #{loginName} - // TODO Link to My Account page else if !loggedIn .col-lg-6.col-lg-offset-3.col-md-6.col-md-offset-3 if loginError @@ -27,6 +29,8 @@ html(lang="en") p= loginError h2 Login form(role="form", action="/login", method="post") + if redirect + input(type="hidden", name="redirect", value=redirect) .form-group label(for="username") Username input#username.form-control(type="text", name="name") diff --git a/templates/nav.jade b/templates/nav.jade index 7c85f05a..9a81e666 100644 --- a/templates/nav.jade +++ b/templates/nav.jade @@ -55,7 +55,7 @@ mixin navloginform(redirect) mixin navlogoutform(redirect) if redirect - - url = "/logout?redirect=#{redirect}" + - url = "/logout?redirect=" + redirect else - url = "/logout" p#logoutform.navbar-text.pull-right