diff --git a/lib/channel.js b/lib/channel.js index 5eff2676..c5f09fe7 100644 --- a/lib/channel.js +++ b/lib/channel.js @@ -604,7 +604,7 @@ Channel.prototype.tryNameBan = function(actor, name) { var notice = { username: "[server]", msg: actor.name + " banned " + name, - msgclass: "server-whisper", + meta: { addClass: "server-whisper" }, time: Date.now() }; self.users.forEach(function(u) { @@ -716,7 +716,7 @@ Channel.prototype.tryIPBan = function(actor, name, range) { username: "[server]", msg: actor.name + " banned " + $util.maskIP(ip) + " (" + name + ")", - msgclass: "server-whisper", + meta: { addClass: "server-whisper" }, time: Date.now() }; self.users.forEach(function(u) { @@ -1133,7 +1133,7 @@ Channel.prototype.broadcastNewUser = function(user) { var pkt = { username: "[server]", msg: msg, - msgclass: "server-whisper", + meta: { addClass: "server-whisper" }, time: Date.now() }; self.sendAllWithRank(2, "joinMessage", pkt); @@ -2186,6 +2186,10 @@ Channel.prototype.tryUpdateMotd = function(user, data) { /* REGION Chat */ Channel.prototype.tryChat = function(user, data) { + if (data.meta === undefined) { + data.meta = {}; + } + if(user.name == "") { return; } @@ -2205,6 +2209,15 @@ Channel.prototype.tryChat = function(user, data) { return; } + // Validate meta + var meta = {}; + if (user.rank >= 2) { + if ("modflair" in data.meta && data.meta.modflair === user.rank) { + meta.modflair = data.meta.modflair; + } + } + data.meta = meta; + var msg = data.msg; if(msg.length > 240) { msg = msg.substring(0, 240); @@ -2219,14 +2232,21 @@ Channel.prototype.tryChat = function(user, data) { var msgobj = { username: user.name, msg: msg, - msgclass: "", + meta: data.meta, time: Date.now() }; user.socket.emit("chatMsg", msgobj); return; } - this.chainMessage(user, msg); + if (msg.indexOf("/") === 0) { + ChatCommand.handle(this, user, msg, data.meta); + } else { + if (msg.indexOf(">") === 0) { + data.meta.addClass = "greentext"; + } + this.sendMessage(user, msg, data.meta); + } } Channel.prototype.chainMessage = function(user, msg, data) { @@ -2271,7 +2291,30 @@ Channel.prototype.filterMessage = function(msg) { return subs.join(""); } -Channel.prototype.sendMessage = function(username, msg, msgclass, data) { +Channel.prototype.sendMessage = function (user, msg, meta, filter) { + msg = sanitize(msg).escape(); + msg = this.filterMessage(msg); + var msgobj = { + username: user.name, + msg: msg, + meta: meta, + time: Date.now() + } + + if (filter && filter.byRank !== undefined) { + this.sendAllWithRank("chatMsg", msgobj, filter.byRank); + } else { + this.sendAll("chatMsg", msgobj); + this.chatbuffer.push(msgobj); + if(this.chatbuffer.length > 15) + this.chatbuffer.shift(); + var unescaped = sanitize(msg).entityDecode(); + this.logger.log("<" + user.name + (meta.addClass ? "." + meta.addclass : "") + + "> " + unescaped); + } +}; + +Channel.prototype.sendMessageOld = function(username, msg, msgclass, data) { // I don't want HTML from strangers msg = sanitize(msg).escape(); msg = this.filterMessage(msg); diff --git a/lib/chatcommand.js b/lib/chatcommand.js index d133d094..08d5e047 100644 --- a/lib/chatcommand.js +++ b/lib/chatcommand.js @@ -12,113 +12,172 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI var Logger = require("./logger.js"); var Poll = require("./poll").Poll; -function handle(chan, user, msg, data) { - if(msg.indexOf("/me ") == 0) - chan.sendMessage(user.name, msg.substring(4), "action", data); - else if(msg.indexOf("/sp ") == 0) - chan.sendMessage(user.name, msg.substring(4), "spoiler", data); - else if(msg.indexOf("/say ") == 0) { - if(user.rank >= 1.5) { - chan.sendMessage(user.name, msg.substring(5), "shout", data); +var handlers = { + /* commands that send chat messages */ + "me": function (chan, user, msg, meta) { + meta.addClass = "action"; + meta.action = true; + chan.sendMessage(user, msg, meta); + }, + "sp": function (chan, user, msg, meta) { + meta.addClass = "spoiler"; + chan.sendMessage(user, msg, meta); + }, + "say": function (chan, user, msg, meta) { + if (user.rank >= 1.5) { + meta.addClass = "shout"; + meta.forceShowName = true; + chan.sendMessage(user, msg, meta); } - } - else if(msg.indexOf("/afk") == 0) { - user.setAFK(!user.meta.afk); - } - else if(msg.indexOf("/m ") == 0) { - if(user.rank >= 2) { - chan.chainMessage(user, msg.substring(3), {modflair: user.rank}) + }, + "a": function (chan, user, msg, meta) { + if (user.global_rank < 255) { + return; } - } - else if(msg.indexOf("/a ") == 0) { - if(user.rank >= 255) { - var flair = { - superadminflair: { - labelclass: "label-important", - icon: "icon-globe" - } - }; - var args = msg.substring(3).split(" "); - var cargs = []; - for(var i = 0; i < args.length; i++) { - var a = args[i]; - if(a.indexOf("!icon-") == 0) - flair.superadminflair.icon = a.substring(1); - else if(a.indexOf("!label-") == 0) - flair.superadminflair.labelclass = a.substring(1); - else { - cargs.push(a); - } + var superadminflair = { + labelclass: "label-important", + icon: "icon-globe" + }; + + var args = msg.split(" "); + var cargs = []; + for (var i = 0; i < args.length; i++) { + var a = args[i]; + if (a.indexOf("!icon-") === 0) { + superadminflair.icon = a.substring(1); + } else if (a.indexOf("!label-") === 0) { + superadminflair.labelclass = a.substring(1); + } else { + cargs.push(a); } - chan.chainMessage(user, cargs.join(" "), flair); } - } - else if(msg.indexOf("/mute ") == 0) { - handleMute(chan, user, msg.substring(6).split(" ")); - } - else if(msg.indexOf("/smute ") == 0) { - handleShadowMute(chan, user, msg.substring(7).split(" ")); - } - else if(msg.indexOf("/unmute ") == 0) { - handleUnmute(chan, user, msg.substring(8).split(" ")); - } - else if(msg.indexOf("/kick ") == 0) { - handleKick(chan, user, msg.substring(6).split(" ")); - } - else if(msg.indexOf("/ban ") == 0) { - handleBan(chan, user, msg.substring(5).split(" ")); - } - else if(msg.indexOf("/ipban ") == 0) { - handleIPBan(chan, user, msg.substring(7).split(" ")); - } - else if(msg.indexOf("/unban ") == 0) { - handleUnban(chan, user, msg.substring(7).split(" ")); - } - else if(msg.indexOf("/poll ") == 0) { - handlePoll(chan, user, msg.substring(6), false); - } - else if(msg.indexOf("/hpoll ") == 0) { - handlePoll(chan, user, msg.substring(6), true); - } - else if(msg.indexOf("/d") == 0 && msg.length > 2 && - msg[2].match(/[-0-9 ]/)) { - if(msg[2] == "-") { - if(msg.length == 3) - return; - if(!msg[3].match(/[0-9]/)) - return; - } - handleDrink(chan, user, msg.substring(2), data); - } - else if(msg.indexOf("/clear") == 0) { + + meta.superadminflair = superadminflair; + meta.forceShowName = true; + chan.sendMessage(user, cargs.join(" "), meta); + }, + "poll": function (chan, user, msg, meta) { + handlePoll(chan, user, msg, false); + }, + "hpoll": function (chan, user, msg, meta) { + handlePoll(chan, user, msg, true); + }, + + /* commands that do not send chat messages */ + "afk": function (chan, user, msg, meta) { + user.setAFK(!user.meta.afk); + }, + "mute": function (chan, user, msg, meta) { + handleMute(chan, user, msg.split(" ")); + }, + "smute": function (chan, user, msg, meta) { + handleShadowMute(chan, user, msg.split(" ")); + }, + "unmute": function (chan, user, msg, meta) { + handleUnmute(chan, user, msg.split(" ")); + }, + "kick": function (chan, user, msg, meta) { + handleKick(chan, user, msg.split(" ")); + }, + "ban": function (chan, user, msg, meta) { + handleBan(chan, user, msg.split(" ")); + }, + "ipban": function (chan, user, msg, meta) { + handleIPBan(chan, user, msg.split(" ")); + }, + "unban": function (chan, user, msg, meta) { + handleUnban(chan, user, msg.split(" ")); + }, + "clear": function (chan, user, msg, meta) { handleClear(chan, user); + }, + "clean": function (chan, user, msg, meta) { + handleClean(chan, user, msg); + }, + "cleantitle": function (chan, user, msg, meta) { + handleCleanTitle(chan, user, msg); } - else if(msg.indexOf("/clean ") == 0) { - handleClean(chan, user, msg.substring(7)); +}; + +var handlerList = []; +for (var key in handlers) { + handlerList.push({ + // match /command followed by a space or end of string + re: new RegExp("^\\/" + key + "(?:\\s|$)"), + fn: handlers[key] + }); +} + +function handle(chan, user, msg, meta) { + // Special case because the drink command can vary + var m = msg.match(/^\/d(-?[0-9]*)(?:\s|$)(.*)/); + if (m) { + handleDrink(chan, user, m[1], m[2], meta); + return; } - else if(msg.indexOf("/cleantitle ") == 0) { - handleCleanTitle(chan, user, msg.substring(12)); + for (var i = 0; i < handlerList.length; i++) { + var h = handlerList[i]; + if (msg.match(h.re)) { + var rest; + if (msg.indexOf(" ") >= 0) { + rest = msg.substring(msg.indexOf(" ") + 1); + } else { + rest = ""; + } + h.fn(chan, user, rest, meta); + break; + } } } +function handleDrink(chan, user, count, msg, meta) { + if (!chan.hasPermission(user, "drink")) { + return; + } + + if (count === "") { + count = 1; + } + count = parseInt(count); + if (isNaN(count)) { + return; + } + + meta.drink = true; + meta.forceShowName = true; + meta.addClass = "drink"; + chan.drinks += count; + chan.broadcastDrinks(); + if (count < 0 && msg.trim() === "") { + return; + } + + msg = msg + " drink!"; + if (count !== 1) { + msg += " (x" + count + ")"; + } + chan.sendMessage(user, msg, meta); +} + function handleShadowMute(chan, user, args) { - if(chan.hasPermission(user, "mute") && args.length > 0) { + if (chan.hasPermission(user, "mute") && args.length > 0) { args[0] = args[0].toLowerCase(); var person = false; - for(var i = 0; i < chan.users.length; i++) { - if(chan.users[i].name.toLowerCase() == args[0]) { + for (var i = 0; i < chan.users.length; i++) { + if (chan.users[i].name.toLowerCase() === args[0]) { person = chan.users[i]; break; } } - if(person) { - if(person.rank >= user.rank) { + if (person) { + if (person.rank >= user.rank) { user.socket.emit("errorMsg", { msg: "You don't have permission to mute that person." }); return; } + /* Reset a previous regular mute */ if (chan.mutedUsers.contains(person.name.toLowerCase())) { chan.mutedUsers.remove(person.name.toLowerCase()); @@ -137,7 +196,7 @@ function handleShadowMute(chan, user, args) { var pkt = { username: "[server]", msg: user.name + " shadow muted " + args[0], - msgclass: "server-whisper", + meta: { addClass: "server-whisper" }, time: Date.now() }; chan.users.forEach(function (u) { @@ -150,18 +209,18 @@ function handleShadowMute(chan, user, args) { } function handleMute(chan, user, args) { - if(chan.hasPermission(user, "mute") && args.length > 0) { + if (chan.hasPermission(user, "mute") && args.length > 0) { args[0] = args[0].toLowerCase(); var person = false; - for(var i = 0; i < chan.users.length; i++) { - if(chan.users[i].name.toLowerCase() == args[0]) { + for (var i = 0; i < chan.users.length; i++) { + if (chan.users[i].name.toLowerCase() == args[0]) { person = chan.users[i]; break; } } - if(person) { - if(person.rank >= user.rank) { + if (person) { + if (person.rank >= user.rank) { user.socket.emit("errorMsg", { msg: "You don't have permission to mute that person." }); @@ -179,18 +238,18 @@ function handleMute(chan, user, args) { } function handleUnmute(chan, user, args) { - if(chan.hasPermission(user, "mute") && args.length > 0) { + if (chan.hasPermission(user, "mute") && args.length > 0) { args[0] = args[0].toLowerCase(); var person = false; - for(var i = 0; i < chan.users.length; i++) { - if(chan.users[i].name.toLowerCase() == args[0]) { + for (var i = 0; i < chan.users.length; i++) { + if (chan.users[i].name.toLowerCase() == args[0]) { person = chan.users[i]; break; } } - if(person) { - if(person.rank >= user.rank) { + if (person) { + if (person.rank >= user.rank) { user.socket.emit("errorMsg", { msg: "You don't have permission to unmute that person." }); @@ -209,18 +268,18 @@ function handleUnmute(chan, user, args) { } function handleKick(chan, user, args) { - if(chan.hasPermission(user, "kick") && args.length > 0) { + if (chan.hasPermission(user, "kick") && args.length > 0) { args[0] = args[0].toLowerCase(); - if(args[0] == user.name.toLowerCase()) { + if (args[0] == user.name.toLowerCase()) { user.socket.emit("costanza", { msg: "Kicking yourself?" }); return; } var kickee; - for(var i = 0; i < chan.users.length; i++) { - if(chan.users[i].name.toLowerCase() == args[0]) { - if(chan.users[i].rank >= user.rank) { + for (var i = 0; i < chan.users.length; i++) { + if (chan.users[i].name.toLowerCase() == args[0]) { + if (chan.users[i].rank >= user.rank) { user.socket.emit("errorMsg", { msg: "You don't have permission to kick " + args[0] }); @@ -230,7 +289,7 @@ function handleKick(chan, user, args) { break; } } - if(kickee) { + if (kickee) { chan.logger.log("*** " + user.name + " kicked " + args[0]); args[0] = ""; var reason = args.join(" "); @@ -250,9 +309,9 @@ function handleBan(chan, user, args) { } function handleUnban(chan, user, args) { - if(chan.hasPermission(user, "ban") && args.length > 0) { + if (chan.hasPermission(user, "ban") && args.length > 0) { chan.logger.log("*** " + user.name + " unbanned " + args[0]); - if(args[0].match(/(\d+)\.(\d+)\.(\d+)\.(\d+)/)) { + if (args[0].match(/(\d+)\.(\d+)\.(\d+)\.(\d+)/)) { chan.unbanIP(user, args[0]); } else { @@ -262,7 +321,7 @@ function handleUnban(chan, user, args) { } function handlePoll(chan, user, msg, hidden) { - if(chan.hasPermission(user, "pollctl")) { + if (chan.hasPermission(user, "pollctl")) { var args = msg.split(","); var title = args[0]; args.splice(0, 1); @@ -273,32 +332,8 @@ function handlePoll(chan, user, msg, hidden) { } } -function handleDrink(chan, user, msg, data) { - if(!chan.hasPermission(user, "drink")) { - return; - } - - var count = msg.split(" ")[0]; - msg = msg.substring(count.length + 1); - if(count == "") - count = 1; - else - count = parseInt(count); - - chan.drinks += count; - chan.broadcastDrinks(); - if(count < 0 && msg.trim() == "") { - return; - } - - msg = msg + " drink!"; - if(count != 1) - msg += " (x" + count + ")"; - chan.sendMessage(user.name, msg, "drink", data); -} - function handleClear(chan, user) { - if(user.rank < 2) { + if (user.rank < 2) { return; } diff --git a/www/assets/js/ui.js b/www/assets/js/ui.js index 6858bb7b..cbff5142 100644 --- a/www/assets/js/ui.js +++ b/www/assets/js/ui.js @@ -150,13 +150,20 @@ $("#chatline").keydown(function(ev) { if(ev.keyCode == 13) { var msg = $("#chatline").val(); if(msg.trim()) { + var meta = {}; if (USEROPTS.adminhat && CLIENT.rank >= 255) { msg = "/a " + msg; - } else if(USEROPTS.modhat && CLIENT.rank >= Rank.Moderator) { - msg = "/m " + msg; + } else if (USEROPTS.modhat && CLIENT.rank >= Rank.Moderator) { + meta.modflair = CLIENT.rank; } + if (CLIENT.rank >= 2 && msg.indexOf("/m ") === 0) { + meta.modflair = CLIENT.rank; + msg = msg.substring(4); + } + socket.emit("chatMsg", { - msg: msg + msg: msg, + meta: meta }); CHATHIST.push($("#chatline").val()); CHATHISTIDX = CHATHIST.length; diff --git a/www/assets/js/util.js b/www/assets/js/util.js index c3fa0a21..bef63a3e 100644 --- a/www/assets/js/util.js +++ b/www/assets/js/util.js @@ -1442,67 +1442,79 @@ function sendVideoUpdate() { /* chat */ function formatChatMessage(data) { - var skip = data.username == LASTCHATNAME; - if(data.msgclass == "drink" || data.msgclass == "shout") { - skip = false; - } - if(data.superadminflair) - skip = false; - if(data.msgclass == "server-whisper") + // Phase 1: Determine whether to show the username or not + var skip = data.username === LASTCHATNAME; + if(data.meta.addClass === "server-whisper") skip = true; // Prevent impersonation by abuse of the bold filter if(data.msg.match(/^\s*\w+\s*:\s*<\/strong>\s*/)) skip = false; + if (data.meta.forceShowName) + skip = false; + LASTCHATNAME = data.username; LASTCHATTIME = data.time; var div = $("
"); - if(USEROPTS.show_timestamps) { + /* drink is a special case because the entire container gets the class, not + just the message */ + if (data.meta.addClass === "drink") { + div.addClass("drink"); + data.meta.addClass = ""; + } + + // Add timestamps (unless disabled) + if (USEROPTS.show_timestamps) { var time = $("").addClass("timestamp").appendTo(div); var timestamp = new Date(data.time).toTimeString().split(" ")[0]; time.text("["+timestamp+"] "); - if(data.msgclass == "shout" || data.msgclass == "server-whisper") - time.addClass(data.msgclass); + if (data.meta.addClass && + data.meta.addClass.match(/shout|server-whisper/)) { + time.addClass(data.meta.addClass); + } } + + // Add username var name = $(""); - if(!skip) { + if (!skip) { name.appendTo(div); } $("").addClass("username").text(data.username + ": ").appendTo(name); - var message = $("").appendTo(div); - message[0].innerHTML = data.msg; - if(data.modflair) { - name.addClass(getNameColor(data.modflair)); + if (data.meta.modflair) { + name.addClass(getNameColor(data.meta.modflair)); } - if(data.superadminflair) { + if (data.meta.addClass) { + name.addClass(data.meta.addClass); + } + if (data.meta.superadminflair) { name.addClass("label") - .addClass(data.superadminflair.labelclass); - $("").addClass(data.superadminflair.icon) + .addClass(data.meta.superadminflair.labelclass); + $("").addClass(data.meta.superadminflair.icon) .addClass("icon-white") .prependTo(name); } - if(data.msgclass == "action") { + + // Add the message itself + var message = $("").appendTo(div); + message[0].innerHTML = data.msg; + + // For /me the username is part of the message + if (data.meta.action) { name.remove(); - message.addClass("action"); message[0].innerHTML = data.username + " " + data.msg; } - else if(data.msgclass == "drink") { - div.addClass("drink"); - } - else if(data.msgclass == "shout") { - message.addClass("shout"); - name.addClass("shout"); - } - else { - message.addClass(data.msgclass); + if (data.meta.addClass) { + message.addClass(data.meta.addClass); } return div; } function addChatMessage(data) { - if(IGNORED.indexOf(data.username) != -1) { + if(IGNORED.indexOf(data.username) !== -1) { return; } var div = formatChatMessage(data); + // Incoming: a bunch of crap for the feature where if you hover over + // a message, it highlights messages from that user div.data("sender", data.username); div.appendTo($("#messagebuffer")); div.mouseover(function() {