diff --git a/src/ember/chatview.js b/src/ember/chatview.js index 21ef860e..15b20a4d 100644 --- a/src/ember/chatview.js +++ b/src/ember/chatview.js @@ -355,6 +355,7 @@ FFZ.prototype.setup_chatview = function() { f = this; if ( Chat ) { + Chat.set('ffz_last_channel_room', Chat.get('currentChannelRoom.id')); Chat.reopen({ ffzUpdateChannels: function() { if ( ! f._chatv || f.has_bttv ) @@ -367,12 +368,29 @@ FFZ.prototype.setup_chatview = function() { }.observes("currentChannelRoom", "connectedPrivateGroupRooms"), ffzSubOwnChannelRoom: function() { - var user = f.get_user(), - room = this.get("currentChannelRoom"), - room_id = room && room.get("id"); + try { + // This logic should keep us subscribed to the current chat room + // at all times. Hopefully. + var last_room_id = this.get("ffz_last_channel_room"), + room_id = this.get("currentChannelRoom.id"), - if ( user && user.login && user.login === room_id ) - room && room.ffzSubscribe && room.ffzSubscribe(); + last_room = f.rooms && f.rooms[last_room_id], + room = f.rooms && f.rooms[room_id]; + + this.set("ffz_last_channel_room", room_id); + + f.update_room_important(last_room_id, this); + f.update_room_important(room_id, this); + + if ( last_room && ! last_room.important ) + f.ws_unsub("room." + last_room_id); + + if ( room && room.important ) + f.ws_sub("room." + room_id); + + } catch(err) { + f.error("Error updating Chat Room Subscriptions", err); + } }.observes("currentChannelRoom"), diff --git a/src/ember/moderation-card.js b/src/ember/moderation-card.js index 05b8c98c..21ac9a5f 100644 --- a/src/ember/moderation-card.js +++ b/src/ember/moderation-card.js @@ -3,6 +3,10 @@ var FFZ = window.FrankerFaceZ, constants = require("../constants"), helpers, + TO_REG = /^\/t(?:imeout)? +([^ ]+)(?: +(\d+)(?: +(.+))?)?$/, + BAN_REG = /^\/b(?:an)? +([^ ]+)(?: +(.+))?$/, + USER_REG = /\{user\}/g, + keycodes = { ESC: 27, P: 80, @@ -166,6 +170,53 @@ FFZ.settings_info.mod_card_history = { }; +FFZ.settings_info.mod_card_reasons = { + type: "button", + value: [ + "One-Man Spam", + "Posting Bad Links", + "Ban Evasion", + "Threats / Personal Info", + "Hate / Harassment", + "Ignoring Broadcaster / Moderators" + ], + + category: "Chat Moderation", + no_bttv: true, + + name: "Moderation Card Ban Reasons", + help: "Change the available options in the chat moderation card ban reasons list.", + + method: function() { + var f = this, + old_val = this.settings.mod_card_reasons.join("\n"), + input = utils.createElement('textarea'); + + input.style.marginBottom = "20px"; + + utils.prompt( + "Moderation Card Ban Reasons", + "Please enter a list of ban reasons to select from in chat moderation cards. One item per line.", + old_val, + function(new_val) { + if ( new_val === null || new_val === undefined ) + return; + + var vals = new_val.trim().split(/\s*\n\s*/g), + i = vals.length; + + while(i--) + if ( vals[i].length === 0 ) + vals.splice(i,1); + + f.settings.set('mod_card_reasons', vals); + }, + 600, input + ); + } +}; + + FFZ.settings_info.mod_buttons = { type: "button", @@ -527,6 +578,7 @@ FFZ.prototype.setup_mod_card = function() { line, is_mod = controller.get('cardInfo.isModeratorOrHigher'), + ban_reasons, chat = utils.ember_lookup('controller:chat'), user = f.get_user(), @@ -534,7 +586,12 @@ FFZ.prototype.setup_mod_card = function() { is_broadcaster = user && room_id === user.login, user_id = controller.get('cardInfo.user.id'), - alias = f.aliases[user_id]; + alias = f.aliases[user_id], + + ban_reason = function() { + return ban_reasons && ban_reasons.value ? ' ' + ban_reasons.value : ""; + }; + this.ffz_room_id = room_id; @@ -576,9 +633,28 @@ FFZ.prototype.setup_mod_card = function() { add_btn_click = function(cmd) { var user_id = controller.get('cardInfo.user.id'), cont = utils.ember_lookup('controller:chat'), - room = cont && cont.get('currentRoom'); + room = cont && cont.get('currentRoom'), - room && room.send(cmd.replace(/{user}/g, user_id), true); + cm = cmd.replace(USER_REG, user_id), + reason = ban_reason(); + + if ( reason ) { + var match = TO_REG.exec(cm); + if ( match ) { + if ( ! match[2] ) + cm += " 600"; + if ( ! match[3] ) + cm += reason; + + } else { + match = BAN_REG.exec(cm); + if ( match && ! match[2] ) { + cm += reason; + } + } + } + + room && room.send(cm, true); }, add_btn_make = function(cmd) { @@ -630,13 +706,13 @@ FFZ.prototype.setup_mod_card = function() { room = utils.ember_lookup('controller:chat').get('currentRoom'); if ( is_mod && key == keycodes.P ) - room.send("/timeout " + user_id + " 1", true); + room.send("/timeout " + user_id + " 1" + ban_reason(), true); else if ( is_mod && key == keycodes.B ) - room.send("/ban " + user_id, true); + room.send("/ban " + user_id + ban_reason(), true); else if ( is_mod && key == keycodes.T ) - room.send("/timeout " + user_id + " 600", true); + room.send("/timeout " + user_id + " 600" + ban_reason(), true); else if ( is_mod && key == keycodes.U ) room.send("/unban " + user_id, true); @@ -660,7 +736,7 @@ FFZ.prototype.setup_mod_card = function() { if ( timeout === -1 ) room.send("/unban " + user_id, true); else - room.send("/timeout " + user_id + " " + timeout, true); + room.send("/timeout " + user_id + " " + timeout + ban_reason(), true); }, btn_make = function(timeout) { @@ -700,6 +776,24 @@ FFZ.prototype.setup_mod_card = function() { this.$("button.timeout").remove(); } + + if ( f.settings.mod_card_reasons && f.settings.mod_card_reasons.length ) { + // Moderation Reasons + line = utils.createElement('div', 'extra-interface interface clearfix'); + ban_reasons = utils.createElement('select', 'ffz-ban-reasons', ''); + line.appendChild(ban_reasons); + + for(var i=0; i < f.settings.mod_card_reasons.length; i++) { + var opt = utils.createElement('option'), r = f.settings.mod_card_reasons[i]; + opt.value = r; + opt.textContent = (i+1) + ') ' + r; + ban_reasons.appendChild(opt); + } + + el.appendChild(line); + } + + var ban_btn = el.querySelector('button.ban'); if ( f.settings.mod_card_hotkeys ) ban_btn.setAttribute('title', '(B)an User'); diff --git a/src/ember/room.js b/src/ember/room.js index 870f1f74..aa95896f 100644 --- a/src/ember/room.js +++ b/src/ember/room.js @@ -554,6 +554,17 @@ FFZ.ffz_commands.help.help = "Usage: /ffz help [command]\nList available command // Room Management // -------------------- +FFZ.prototype.update_room_important = function(id, controller) { + var Chat = controller || utils.ember_lookup('controller:chat'), + room = this.rooms[id]; + + if ( ! room ) + return; + + room.important = (Chat && room.room && Chat.get('currentChannelRoom') === room.room) || (room.room && room.room.get('isGroupRoom')) || (this.settings.pinned_rooms.indexOf(id) !== -1); +}; + + FFZ.prototype.add_room = function(id, room) { if ( this.rooms[id] ) return this.log("Tried to add existing room: " + id); @@ -584,16 +595,21 @@ FFZ.prototype.add_room = function(id, room) { } } - // Let the server know where we are. - room && room.ffzSubscribe && room.ffzSubscribe(); - //this.ws_send("sub", "room." + id); + // Is the room important? + this.update_room_important(id); - // See if we need history? - if ( ! this.has_bttv && this.settings.chat_history && room && (room.get('messages.length') || 0) < 10 ) { - if ( ! this.ws_send("chat_history", [id,25], this._load_history.bind(this, id)) ) - data.needs_history = true; + if ( data.important ) { + // Let the server know where we are. + this.ws_sub("room." + id); + + // Do we want history? + if ( ! this.has_bttv && this.settings.chat_history && room && (room.get('messages.length') || 0) < 10 ) { + if ( ! this.ws_send("chat_history", [id,25], this._load_history.bind(this, id)) ) + data.needs_history = true; + } } + // Why don't we set the scrollback length, too? room.set('messageBufferSize', this.settings.scrollback_length + ((this._roomv && !this._roomv.get('stuckToBottom') && this._roomv.get('controller.model.id') === id) ? 150 : 0)); @@ -620,7 +636,7 @@ FFZ.prototype.remove_room = function(id) { utils.update_css(this._room_style, id, null); // Let the server know we're gone and delete our data for this room. - this.ws_send("unsub", "room." + id); + this.ws_unsub("room." + id); delete this.rooms[id]; // Clean up sets we aren't using any longer. @@ -918,23 +934,6 @@ FFZ.prototype._modify_room = function(room) { }.observes('r9k', 'subsOnly', 'emoteOnly', 'slow', 'ffz_banned'), - ffzShouldSubscribe: function() { - var Chat = utils.ember_lookup('controller:chat'), - room_id = this.get('id'); - - return (Chat && Chat.get('currentChannelRoom') === this) || (f.settings.pinned_rooms && f.settings.pinned_rooms.indexOf(room_id) !== -1); - }, - - ffzSubscribe: function() { - if ( this.ffzShouldSubscribe() ) - f.ws_send("sub", "room." + this.get('id')); - }, - - ffzUnsubscribe: function(not_always) { - if ( ! not_always || ! this.ffzShouldSubscribe() ) - f.ws_send("unsub", "room." + this.get('id')); - }, - // User Level ffzUserLevel: function() { if ( this.get('isStaff') ) @@ -1100,7 +1099,7 @@ FFZ.prototype._modify_room = function(room) { last_ban.end_time = end_time; last_ban.timeouts++; - last_ban.message = message + ' ' + utils.number_commas(last_ban.timeouts) + ' times' + (!show_reason || last_ban.reasons.length === 0 ? '.' : ' with reason' + utils.pluralize(last_ban.reasons.length) + ': ' + last_ban.reasons.join(', ')); + last_ban.message = message + ' (' + utils.number_commas(last_ban.timeouts) + ' times)' + (!show_reason || last_ban.reasons.length === 0 ? '.' : ' with reason' + utils.pluralize(last_ban.reasons.length) + ': ' + last_ban.reasons.join(', ')); last_ban.cachedTokens = [{type: "text", text: last_ban.message}]; // Now that we've reset the tokens, if there's a line for this, @@ -1119,9 +1118,18 @@ FFZ.prototype._modify_room = function(room) { if ( ! last_ban || ! last_ban.is_delete || Math.abs(now - last_ban.date) > 15000 ) last_ban = null; - if ( last_ban ) - last_ban.cachedTokens = [message + ' ' + utils.number_commas(last_ban.timeouts) + ' times' + (last_ban.reasons.length === 0 ? '.' : ' with reason' + utils.pluralize(last_ban.reasons.length) + ': ' + last_ban.reasons.join(', '))]; - else { + if ( last_ban ) { + if ( reason && last_ban.reasons.indexOf(reason) === -1 ) + last_ban.reasons.push(reason); + + if ( last_ban.durations.indexOf(duration) === -1 ) + last_ban.durations.push(duration); + + last_ban.end_time = end_time; + last_ban.timeouts++; + + last_ban.cachedTokens = [message + ' (' + utils.number_commas(last_ban.timeouts) + ' times)' + (last_ban.reasons.length === 0 ? '.' : ' with reason' + utils.pluralize(last_ban.reasons.length) + ': ' + last_ban.reasons.join(', '))]; + } else { user_history.push({ from: 'jtv', is_delete: true, @@ -1344,11 +1352,19 @@ FFZ.prototype._modify_room = function(room) { var f_room = f.rooms && f.rooms[msg.room], ban_history = f_room && f_room.ban_history; - if ( ban_history && msg.from ) - ban_history[msg.from] = false; + if ( ban_history && msg.from ) { + // Is the last ban within 200ms? Chances are Twitch screwed up message order. + if ( ban_history[msg.from] && (new Date - ban_history[msg.from].date) <= 200 ) { + msg.ffz_deleted = true; + msg.deleted = !f.settings.prevent_clear; + + } else + ban_history[msg.from] = false; + } + // Check for message from us. - if ( ! is_whisper ) { + if ( ! is_whisper && ! msg.ffz_deleted ) { var user = f.get_user(); if ( user && user.login === msg.from ) { var was_banned = this.get('ffz_banned'); diff --git a/src/main.js b/src/main.js index a6e0c7ee..00dc18dd 100644 --- a/src/main.js +++ b/src/main.js @@ -37,7 +37,7 @@ FFZ.msg_commands = {}; // Version var VER = FFZ.version_info = { - major: 3, minor: 5, revision: 182, + major: 3, minor: 5, revision: 183, toString: function() { return [VER.major, VER.minor, VER.revision].join(".") + (VER.extra || ""); } diff --git a/src/socket.js b/src/socket.js index 5d46d04b..f8c5e9be 100644 --- a/src/socket.js +++ b/src/socket.js @@ -94,6 +94,38 @@ FFZ.settings_info.socket_server_pool = { }; +// ---------------- +// Subscription +// ---------------- + +FFZ.prototype.ws_sub = function(topic) { + this._ws_topics = this._ws_topics || {}; + if ( this._ws_topics[topic] ) + return true; + + if ( ! this._ws_open ) + return false; + + this.ws_send("sub", topic); + this._ws_topics[topic] = true; + return true; +} + + +FFZ.prototype.ws_unsub = function(topic) { + this._ws_topics = this._ws_topics || {}; + if ( ! this._ws_topics[topic] ) + return true; + + if ( ! this._ws_open ) + return true; + + this.ws_send("unsub", topic); + this._ws_topics[topic] = false; + return true; +} + + // ---------------- // Socket Creation // ---------------- @@ -104,6 +136,7 @@ FFZ.prototype.ws_create = function() { this._ws_last_req = 1; this._ws_callbacks = {1: f._ws_on_hello.bind(f)}; this._ws_pending = this._ws_pending || []; + this._ws_topics = {}; this._ws_recreate_timer = null; var pool_id = this.settings.socket_server_pool, @@ -146,9 +179,8 @@ FFZ.prototype.ws_create = function() { // Join the right channel if we're in the dashboard. if ( f.is_dashboard ) { var match = location.pathname.match(/\/([^\/]+)/); - if ( match ) { - f.ws_send("sub", "channel." + match[1]); - } + if ( match ) + f.ws_sub("channel." + match[1]); } // Send the current rooms. @@ -157,13 +189,14 @@ FFZ.prototype.ws_create = function() { if ( ! f.rooms.hasOwnProperty(room_id) || ! room ) continue; - room.room && room.room.ffzSubscribe && room.room.ffzSubscribe(); - //f.ws_send("sub", "room." + room_id); + if ( room.important ) { + f.ws_sub("room." + room_id); - if ( f.rooms[room_id].needs_history ) { - f.rooms[room_id].needs_history = false; - if ( ! f.has_bttv && f.settings.chat_history ) - f.ws_send("chat_history", [room_id,25], f._load_history.bind(f, room_id)); + if ( room.needs_history ) { + room.needs_history = false; + if ( ! f.has_bttv && f.settings.chat_history ) + f.ws_send("chat_history", [room_id,25], f._load_history.bind(f, room_id)); + } } } @@ -173,10 +206,10 @@ FFZ.prototype.ws_create = function() { hosted_id = f._cindex.get('controller.hostModeTarget.id'); if ( channel_id ) - f.ws_send("sub", "channel." + channel_id); + f.ws_sub("channel." + channel_id); if ( hosted_id ) - f.ws_send("sub", "channel." + hosted_id); + f.ws_sub("channel." + hosted_id); } // Send any pending commands. diff --git a/src/styles/badges-transparent.css b/src/styles/badges-transparent.css index 84dc1e35..92294ef6 100644 --- a/src/styles/badges-transparent.css +++ b/src/styles/badges-transparent.css @@ -4,11 +4,15 @@ /* Invert Some Badges */ -body:not(.ffz-dark) .app-main:not(.theatre) .conversation-window .badges .badge:not(.subscriber):not(.ffz-badge-1), -body:not(.ffz-dark) > .chat-container:not(.dark):not(.force-dark) .badges .badge:not(.subscriber):not(.ffz-badge-1), -body:not(.ffz-dark) > .ember-chat-container:not(.dark):not(.force-dark) .badges .badge:not(.subscriber):not(.ffz-badge-1), -.app-main:not(.theatre) .chat-container:not(.dark):not(.force-dark) .badges .badge:not(.subscriber):not(.ffz-badge-1), -.app-main:not(.theatre) .ember-chat-container:not(.dark):not(.force-dark) .badges .badge:not(.subscriber):not(.ffz-badge-1) { - filter: invert(100%); - -webkit-filter: invert(100%); +.badge:not(.subscriber):not(.ffz-badge-1) { + filter: invert(75%); + -webkit-filter: invert(75%); +} + +.ffz-dark .badge, +.theatre .badge, +.dark .badge, +.force-dark .badge { + filter: none !important; + -webkit-filter: none !important; } \ No newline at end of file diff --git a/src/utils.js b/src/utils.js index d141d9f6..76db9267 100644 --- a/src/utils.js +++ b/src/utils.js @@ -258,22 +258,32 @@ module.exports = FFZ.utils = { show_modal: show_modal, - prompt: function(title, description, old_value, callback, width) { + prompt: function(title, description, old_value, callback, width, input) { var contents = document.createElement('div'), heading = document.createElement('div'), form = document.createElement('form'), - input, close_btn, okay_btn; + close_btn, okay_btn; contents.className = 'text-content'; heading.className = 'content-header'; heading.innerHTML = '
' + description + '
' : '') + '' + description + '
' : '') + ''; + + var ph = form.querySelector('.input-placeholder'), + par = ph.parentElement; + + par.insertBefore(input, ph); + par.removeChild(ph); contents.appendChild(heading); contents.appendChild(form); - input = form.querySelector('input'); close_btn = form.querySelector('.js-subwindow-close'); okay_btn = form.querySelector('.button.primary'); diff --git a/style.css b/style.css index 4a4361ce..4fc4e9c5 100644 --- a/style.css +++ b/style.css @@ -1248,7 +1248,7 @@ img.channel_background[src="null"] { display: none; } .ffz-moderation-card .friend-button { max-height: 30px } .ffz-moderation-card .right button:last-of-type, -.ffz-moderation-card .mod-controls button:last-of-type { margin-right: 0 } +.ffz-moderation-card .mod-controls:last-of-type button:last-of-type { margin-right: 0 } .ffz-moderation-card .follow-button a { text-indent: -9999px; @@ -1298,6 +1298,11 @@ img.channel_background[src="null"] { display: none; } top: 0; } +.ffz-moderation-card .ffz-ban-reasons { + margin-top: 10px; + width: 100%; +} + /* dark moderation card */