1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-06-29 15:55:33 +00:00

Added "/ffz log" command to upload logs to pastebin. Added extra error handling in Ember-facing code. Added settings menu categories. Added user name color adjustment. Added support for better moderation cards. Added support for extra moderation commands. Fixed menu size limit. Fixed typo in show_message RPC. Fixed CSS for UI button.

This commit is contained in:
SirStendec 2015-02-10 01:34:23 -05:00
parent fe55f43b8e
commit b90a25d794
19 changed files with 1788 additions and 676 deletions

938
script.js

File diff suppressed because it is too large Load diff

4
script.min.js vendored

File diff suppressed because one or more lines are too long

View file

@ -1,11 +1,25 @@
var FFZ = window.FrankerFaceZ;
// -----------------
// Log Export
// -----------------
FFZ.ffz_commands.log = function(room, args) {
this._pastebin(this._log_data.join("\n"), function(url) {
if ( ! url )
return this.room_message(room, "There was an error uploading the FrankerFaceZ log.");
this.room_message(room, "Your FrankerFaceZ log has been pasted to: " + url);
});
};
// -----------------
// Mass Moderation
// -----------------
FFZ.chat_commands.massunmod = function(room, args) {
FFZ.ffz_commands.massunmod = function(room, args) {
args = args.join(" ").trim();
if ( ! args.length )
@ -29,10 +43,10 @@ FFZ.chat_commands.massunmod = function(room, args) {
return "Sent unmod command for " + count + " users.";
}
FFZ.chat_commands.massunmod.help = "Usage: /ffz massunmod <list, of, users>\nBroadcaster only. Unmod all the users in the provided list.";
FFZ.ffz_commands.massunmod.help = "Usage: /ffz massunmod <list, of, users>\nBroadcaster only. Unmod all the users in the provided list.";
FFZ.chat_commands.massmod = function(room, args) {
FFZ.ffz_commands.massmod = function(room, args) {
args = args.join(" ").trim();
if ( ! args.length )
@ -47,7 +61,6 @@ FFZ.chat_commands.massmod = function(room, args) {
if ( args.length > 50 )
return "Each user you mod counts as a single message. To avoid being globally banned, please limit yourself to 50 at a time and wait between uses.";
var count = args.length;
while(args.length) {
var name = args.shift();
@ -57,4 +70,4 @@ FFZ.chat_commands.massmod = function(room, args) {
return "Sent mod command for " + count + " users.";
}
FFZ.chat_commands.massmod.help = "Usage: /ffz massmod <list, of, users>\nBroadcaster only. Mod all the users in the provided list.";
FFZ.ffz_commands.massmod.help = "Usage: /ffz massmod <list, of, users>\nBroadcaster only. Mod all the users in the provided list.";

View file

@ -5,7 +5,7 @@ var FFZ = window.FrankerFaceZ;
// Developer Mode Command
// -----------------------
FFZ.chat_commands.developer_mode = function(room, args) {
FFZ.ffz_commands.developer_mode = function(room, args) {
var enabled, args = args && args.length ? args[0].toLowerCase() : null;
if ( args == "y" || args == "yes" || args == "true" || args == "on" )
enabled = true;
@ -19,4 +19,4 @@ FFZ.chat_commands.developer_mode = function(room, args) {
return "Developer Mode is now " + (enabled ? "enabled" : "disabled") + ". Please refresh your browser.";
}
FFZ.chat_commands.developer_mode.help = "Usage: /ffz developer_mode <on|off>\nEnable or disable Developer Mode. When Developer Mode is enabled, the script will be reloaded from //localhost:8000/script.js instead of from the CDN.";
FFZ.ffz_commands.developer_mode.help = "Usage: /ffz developer_mode <on|off>\nEnable or disable Developer Mode. When Developer Mode is enabled, the script will be reloaded from //localhost:8000/script.js instead of from the CDN.";

View file

@ -25,7 +25,11 @@ FFZ.prototype.setup_chatview = function() {
continue;
this.log("Adding UI link manually to Chat view.", view);
try {
view.$('.textarea-contain').append(this.build_ui_link(view));
} catch(err) {
this.error("setup: build_ui_link: " + err);
}
}
}
@ -40,16 +44,28 @@ FFZ.prototype._modify_cview = function(view) {
view.reopen({
didInsertElement: function() {
this._super();
try {
this.$() && this.$('.textarea-contain').append(f.build_ui_link(this));
} catch(err) {
f.error("didInsertElement: build_ui_link: " + err);
}
},
willClearRender: function() {
this._super();
try {
this.$(".ffz-ui-toggle").remove();
} catch(err) {
f.error("willClearRender: remove ui link: " + err);
}
},
ffzUpdateLink: Ember.observer('controller.currentRoom', function() {
try {
f.update_ui_link();
} catch(err) {
f.error("ffzUpdateLink: update_ui_link: " + err);
}
})
});
}

View file

@ -1,4 +1,5 @@
var FFZ = window.FrankerFaceZ,
utils = require("../utils"),
reg_escape = function(str) {
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
@ -13,6 +14,7 @@ FFZ.settings_info.capitalize = {
type: "boolean",
value: true,
category: "Chat",
visible: function() { return ! this.has_bttv },
name: "Username Capitalization",
@ -24,6 +26,7 @@ FFZ.settings_info.keywords = {
type: "button",
value: [],
category: "Chat",
visible: function() { return ! this.has_bttv },
name: "Highlight Keywords",
@ -33,7 +36,7 @@ FFZ.settings_info.keywords = {
var old_val = this.settings.keywords.join(", "),
new_val = prompt("Highlight Keywords\n\nPlease enter a comma-separated list of words that you would like to be highlighted in chat.", old_val);
if ( ! new_val )
if ( new_val === null || new_val === undefined )
return;
// Split them up.
@ -47,17 +50,40 @@ FFZ.settings_info.keywords = {
};
FFZ.settings_info.fix_color = {
type: "boolean",
value: false,
category: "Chat",
visible: function() { return ! this.has_bttv },
name: "Adjust Username Colors",
help: "Ensure that username colors contrast with the background enough to be readable.",
on_update: function(val) {
if ( this.has_bttv )
return;
document.body.classList.toggle("ffz-chat-colors", val);
}
};
FFZ.settings_info.chat_rows = {
type: "boolean",
value: false,
category: "Chat",
visible: function() { return ! this.has_bttv },
name: "Chat Line Backgrounds",
help: "Display alternating background colors for lines in chat.",
on_update: function(val) {
document.querySelector(".app-main").classList.toggle("ffz-chat-background", val);
if ( this.has_bttv )
return;
document.body.classList.toggle("ffz-chat-background", val);
}
};
@ -67,10 +93,19 @@ FFZ.settings_info.chat_rows = {
// ---------------------
FFZ.prototype.setup_line = function() {
// Alternating Background
document.querySelector('.app-main').classList.toggle('ffz-chat-background', this.settings.chat_rows);
// Chat Enhancements
document.body.classList.toggle("ffz-chat-colors", !this.has_bttv && this.settings.fix_color);
document.body.classList.toggle('ffz-chat-background', !this.has_bttv && this.settings.chat_rows);
this._colors = {};
this._last_row = {};
var s = this._fix_color_style = document.createElement('style');
s.id = "ffz-style-username-colors";
s.type = 'text/css';
document.head.appendChild(s);
this.log("Hooking the Ember Line controller.");
var Line = App.__container__.resolve('controller:line'),
@ -79,12 +114,21 @@ FFZ.prototype.setup_line = function() {
Line.reopen({
tokenizedMessage: function() {
// Add our own step to the tokenization procedure.
var tokens = f._emoticonize(this, this._super()),
user = f.get_user();
var tokens = this._super();
try {
tokens = f._emoticonize(this, tokens);
var user = f.get_user();
if ( ! user || this.get("model.from") != user.login )
tokens = f._mentionize(this, tokens);
} catch(err) {
try {
f.error("LineController tokenizedMessage: " + err);
} catch(err) { }
}
return tokens;
}.property("model.message", "isModeratorOrHigher")
@ -97,32 +141,49 @@ FFZ.prototype.setup_line = function() {
Line.reopen({
didInsertElement: function() {
this._super();
try {
var el = this.get('element'),
user = this.get('context.model.from'),
room = this.get('context.parentController.content.id'),
row_type = this.get('context.model.ffzAlternate');
color = this.get('context.model.color'),
row_type = this.get('context.model.ffz_alternate');
// Color Processing
if ( color )
f._handle_color(color);
// Row Alternation
if ( row_type === undefined ) {
row_type = f._last_row[room] = f._last_row.hasOwnProperty(room) ? !f._last_row[room] : false;
this.set("context.model.ffzAlternate", row_type);
this.set("context.model.ffz_alternate", row_type);
}
el.classList.toggle('ffz-alternate', row_type);
// Basic Data
el.setAttribute('data-room', room);
el.setAttribute('data-sender', user);
// Badge
f.render_badge(this);
// Capitalization
if ( f.settings.capitalize )
f.capitalize(this, user);
// Check for any mentions.
// Mention Highlighting
var mentioned = el.querySelector('span.mentioned');
if ( mentioned ) {
el.classList.add("ffz-mentioned");
if ( ! document.hasFocus() && ! this.get('context.model.ffzNotified') && f.settings.highlight_notifications ) {
if ( ! document.hasFocus() && ! this.get('context.model.ffz_notified') && f.settings.highlight_notifications ) {
var cap_room = FFZ.get_capitalization(room),
cap_user = FFZ.get_capitalization(user),
room_name = cap_room,
@ -147,10 +208,17 @@ FFZ.prototype.setup_line = function() {
}
// Mark that we've checked this message for mentions.
this.set('context.model.ffzNotified', true);
this.set('context.model.ffz_notified', true);
} catch(err) {
try {
f.error("LineView didInsertElement: " + err);
} catch(err) { }
}
}
});
// Store the capitalization of our own name.
var user = this.get_user();
if ( user && user.name )
@ -158,6 +226,69 @@ FFZ.prototype.setup_line = function() {
}
// ---------------------
// Fix Name Colors
// ---------------------
FFZ.prototype._handle_color = function(color) {
if ( ! color || this._colors[color] )
return;
this._colors[color] = true;
// Parse the color.
var raw = parseInt(color.substr(1), 16),
rgb = [
(raw >> 16),
(raw >> 8 & 0x00FF),
(raw & 0x0000FF)
],
lum = utils.get_luminance(rgb),
output = "",
rule = 'span[style="color:' + color + '"]',
matched = false;
if ( lum > 0.3 ) {
// Color Too Bright. We need a lum of 0.3 or less.
matched = true;
var s = 255,
nc = rgb;
while(s--) {
nc = utils.darken(nc);
if ( utils.get_luminance(nc) <= 0.3 )
break;
}
output += '.ffz-chat-colors .ember-chat-container:not(.dark) .chat-line ' + rule + ', .ffz-chat-colors .chat-container:not(.dark) .chat-line ' + rule + ' { color: ' + utils.rgb_to_css(nc) + ' !important; }\n';
} else
output += '.ffz-chat-colors .ember-chat-container:not(.dark) .chat-line ' + rule + ', .ffz-chat-colors .chat-container:not(.dark) .chat-line ' + rule + ' { color: ' + color + ' !important; }\n';
if ( lum < 0.1 ) {
// Color Too Dark. We need a lum of 0.1 or more.
matched = true;
var s = 255,
nc = rgb;
while(s--) {
nc = utils.brighten(nc);
if ( utils.get_luminance(nc) >= 0.1 )
break;
}
output += '.ffz-chat-colors .theatre .chat-container .chat-line ' + rule + ', .ffz-chat-colors .chat-container.dark .chat-line ' + rule + ', .ffz-chat-colors .ember-chat-container.dark .chat-line ' + rule + ' { color: ' + utils.rgb_to_css(nc) + ' !important; }\n';
} else
output += '.ffz-chat-colors .theatre .chat-container .chat-line ' + rule + ', .ffz-chat-colors .chat-container.dark .chat-line ' + rule + ', .ffz-chat-colors .ember-chat-container.dark .chat-line ' + rule + ' { color: ' + color + ' !important; }\n';
if ( matched )
this._fix_color_style.innerHTML += output;
}
// ---------------------
// Capitalization
// ---------------------
@ -205,30 +336,13 @@ FFZ.prototype.capitalize = function(view, user) {
}
FFZ.chat_commands.capitalization = function(room, args) {
var enabled, args = args && args.length ? args[0].toLowerCase() : null;
if ( args == "y" || args == "yes" || args == "true" || args == "on" )
enabled = true;
else if ( args == "n" || args == "no" || args == "false" || args == "off" )
enabled = false;
if ( enabled === undefined )
return "Chat Name Capitalization is currently " + (this.settings.capitalize ? "enabled." : "disabled.");
this.settings.set("capitalize", enabled);
return "Chat Name Capitalization is now " + (enabled ? "enabled." : "disabled.");
}
FFZ.chat_commands.capitalization.help = "Usage: /ffz capitalization <on|off>\nEnable or disable Chat Name Capitalization. This setting does not work with BetterTTV.";
// ---------------------
// Extra Mentions
// ---------------------
FFZ._regex_cache = {};
FFZ._get_rex = function(word) {
FFZ._get_regex = function(word) {
return FFZ._regex_cache[word] = FFZ._regex_cache[word] || RegExp("\\b" + reg_escape(word) + "\\b", "ig");
}
@ -266,25 +380,6 @@ FFZ.prototype._mentionize = function(controller, tokens) {
}
FFZ.chat_commands.mentionize = function(room, args) {
if ( args && args.length ) {
var mention_words = args.join(" ").trim().split(/\W*,\W*/);
if ( mention_words.length == 1 && mention_words[0] == "disable" )
mention_words = [];
this.settings.set("keywords", mention_words);
}
var mention_words = this.settings.keywords;
if ( mention_words.length )
return "The following words will be highlighted: " + mention_words.join(", ");
else
return "There are no words set that will be highlighted.";
}
FFZ.chat_commands.mentionize.help = "Usage: /ffz mentionize <comma, separated, word, list|disable>\nSet a list of words that will also be highlighted in chat.";
// ---------------------
// Emoticon Replacement
// ---------------------

View file

@ -0,0 +1,247 @@
var FFZ = window.FrankerFaceZ,
utils = require("../utils"),
keycodes = {
ESC: 27,
P: 80,
B: 66,
T: 84
},
btns = [
['5m', 300],
['10m', 600],
['1hr', 3600],
['12hr', 43200],
['24hr', 86400]],
MESSAGE = '<svg class="svg-messages" height="16px" version="1.1" viewBox="0 0 18 18" width="16px" x="0px" y="0px"><path clip-rule="evenodd" d="M1,15V3h16v12H1z M15.354,5.354l-0.707-0.707L9,10.293L3.354,4.646L2.646,5.354L6.293,9l-3.646,3.646l0.707,0.707L7,9.707l1.646,1.646h0.707L11,9.707l3.646,3.646l0.707-0.707L11.707,9L15.354,5.354z" fill-rule="evenodd"></path></svg>';
// ----------------
// Settings
// ----------------
FFZ.settings_info.enhanced_moderation = {
type: "boolean",
value: false,
visible: function() { return ! this.has_bttv },
category: "Chat",
name: "Enhanced Moderation",
help: "Use /p, /t, /u and /b in chat to moderator, or use hotkeys with moderation cards."
};
// ----------------
// Initialization
// ----------------
FFZ.prototype.setup_mod_card = function() {
this.log("Hooking the Ember Moderation Card view.");
var Card = App.__container__.resolve('view:moderation-card'),
f = this;
Card.reopen({
didInsertElement: function() {
this._super();
try {
if ( ! f.settings.enhanced_moderation )
return;
var el = this.get('element'),
controller = this.get('context');
// Only do the big stuff if we're mod.
if ( controller.get('parentController.model.isModeratorOrHigher') ) {
el.classList.add('ffz-moderation-card');
el.setAttribute('tabindex', 1);
// Key Handling
el.addEventListener('keyup', function(e) {
var key = e.keyCode || e.which,
user_id = controller.get('model.user.id'),
room = controller.get('parentController.model');
if ( key == keycodes.P )
room.send("/timeout " + user_id + " 1");
else if ( key == keycodes.B )
room.send("/ban " + user_id);
else if ( key == keycodes.T )
room.send("/timeout " + user_id + " 600");
else if ( key != keycodes.ESC )
return;
controller.send('hideModOverlay');
});
// Extra Moderation
var line = document.createElement('div');
line.className = 'interface clearfix';
var btn_click = function(timeout) {
var user_id = controller.get('model.user.id'),
room = controller.get('parentController.model');
if ( timeout === -1 )
room.send("/unban " + user_id);
else
room.send("/timeout " + user_id + " " + timeout);
},
btn_make = function(text, timeout) {
var btn = document.createElement('button');
btn.className = 'button';
btn.innerHTML = text;
btn.title = "Timeout User for " + utils.number_commas(timeout) + " Second" + (timeout != 1 ? "s" : "");
if ( timeout === 600 )
btn.title = "(T)" + btn.title.substr(1);
else if ( timeout === 1 )
btn.title = "(P)urge - " + btn.title;
jQuery(btn).tipsy();
btn.addEventListener('click', btn_click.bind(this, timeout));
return btn;
};
line.appendChild(btn_make('Purge', 1));
var s = document.createElement('span');
s.className = 'right';
line.appendChild(s);
for(var i=0; i < btns.length; i++)
s.appendChild(btn_make(btns[i][0], btns[i][1]));
el.appendChild(line);
// Unban Button
var unban_btn = document.createElement('button');
unban_btn.className = 'unban button glyph-only light';
unban_btn.innerHTML = "&#x2713;";
unban_btn.title = "(U)nban User";
jQuery(unban_btn).tipsy();
unban_btn.addEventListener("click", btn_click.bind(this, -1));
var ban_btn = el.querySelector('button.ban');
ban_btn.setAttribute('title', '(B)an User');
jQuery(ban_btn).after(unban_btn);
// Fix Other Buttons
this.$("button.timeout").remove();
}
// More Fixing Other Buttons
var op_btn = el.querySelector('button.mod');
if ( op_btn ) {
var model = controller.get('parentController.model'),
can_op = model.get('isBroadcaster') || model.get('isStaff') || model.get('isAdmin');
if ( ! can_op )
op_btn.parentElement.removeChild(op_btn);
}
var msg_btn = el.querySelector(".interface > button");
if ( msg_btn && msg_btn.className == "button" ) {
msg_btn.innerHTML = MESSAGE;
msg_btn.classList.add('glyph-only');
msg_btn.classList.add('message');
msg_btn.title = "Message User";
jQuery(msg_btn).tipsy();
}
// Focus the Element
this.$().draggable({
start: function() {
el.focus();
}});
el.focus();
} catch(err) {
try {
f.error("ModerationCardView didInsertElement: " + err);
} catch(err) { }
}
}});
}
// ----------------
// Chat Commands
// ----------------
FFZ.chat_commands.purge = FFZ.chat_commands.p = function(room, args) {
if ( ! args || ! args.length )
return "Purge Usage: /p username [more usernames separated by spaces]";
if ( args.length > 10 )
return "Please only purge up to 10 users at once.";
for(var i=0; i < args.length; i++) {
var name = args[i];
if ( name )
room.room.send("/timeout " + name + " 1");
}
}
FFZ.chat_commands.p.enabled = function() { return this.settings.enhanced_moderation; }
FFZ.chat_commands.t = function(room, args) {
if ( ! args || ! args.length )
return "Timeout Usage: /t username [duration]";
room.room.send("/timeout " + args.join(" "));
}
FFZ.chat_commands.t.enabled = function() { return this.settings.enhanced_moderation; }
FFZ.chat_commands.b = function(room, args) {
if ( ! args || ! args.length )
return "Ban Usage: /b username [more usernames separated by spaces]";
if ( args.length > 10 )
return "Please only ban up to 10 users at once.";
for(var i=0; i < args.length; i++) {
var name = args[i];
if ( name )
room.room.send("/ban " + name);
}
}
FFZ.chat_commands.b.enabled = function() { return this.settings.enhanced_moderation; }
FFZ.chat_commands.u = function(room, args) {
if ( ! args || ! args.length )
return "Unban Usage: /b username [more usernames separated by spaces]";
if ( args.length > 10 )
return "Please only unban up to 10 users at once.";
for(var i=0; i < args.length; i++) {
var name = args[i];
if ( name )
room.room.send("/unban " + name);
}
}
FFZ.chat_commands.u.enabled = function() { return this.settings.enhanced_moderation; }

View file

@ -50,6 +50,7 @@ FFZ.prototype.setup_room = function() {
// --------------------
FFZ.chat_commands = {};
FFZ.ffz_commands = {};
FFZ.prototype.room_message = function(room, text) {
@ -66,6 +67,54 @@ FFZ.prototype.room_message = function(room, text) {
FFZ.prototype.run_command = function(text, room_id) {
var room = this.rooms[room_id];
if ( ! room || ! room.room )
return false;
if ( ! text )
return;
var args = text.split(" "),
cmd = args.shift().substr(1).toLowerCase(),
command = FFZ.chat_commands[cmd],
output;
if ( ! command )
return false;
if ( command.hasOwnProperty('enabled') ) {
var val = command.enabled;
if ( typeof val == "function" ) {
try {
val = command.enabled.bind(this)(room, args);
} catch(err) {
this.error('command "' + cmd + '" enabled: ' + err);
val = false;
}
}
if ( ! val )
return false;
}
this.log("Received Command: " + cmd, args, true);
try {
output = command.bind(this)(room, args);
} catch(err) {
this.error('command "' + cmd + '" runner: ' + err);
output = "There was an error running the command.";
}
if ( output )
this.room_message(room, output);
return true;
}
FFZ.prototype.run_ffz_command = function(text, room_id) {
var room = this.rooms[room_id];
if ( ! room || !room.room )
return;
@ -84,7 +133,7 @@ FFZ.prototype.run_command = function(text, room_id) {
this.log("Received Command: " + cmd, args, true);
var command = FFZ.chat_commands[cmd], output;
var command = FFZ.ffz_commands[cmd], output;
if ( command ) {
try {
output = command.bind(this)(room, args);
@ -100,9 +149,9 @@ FFZ.prototype.run_command = function(text, room_id) {
}
FFZ.chat_commands.help = function(room, args) {
FFZ.ffz_commands.help = function(room, args) {
if ( args && args.length ) {
var command = FFZ.chat_commands[args[0].toLowerCase()];
var command = FFZ.ffz_commands[args[0].toLowerCase()];
if ( ! command )
return 'There is no "' + args[0] + '" command.';
@ -114,13 +163,13 @@ FFZ.chat_commands.help = function(room, args) {
}
var cmds = [];
for(var c in FFZ.chat_commands)
FFZ.chat_commands.hasOwnProperty(c) && cmds.push(c);
for(var c in FFZ.ffz_commands)
FFZ.ffz_commands.hasOwnProperty(c) && cmds.push(c);
return "The available commands are: " + cmds.join(", ");
}
FFZ.chat_commands.help.help = "Usage: /ffz help [command]\nList available commands, or show help for a specific command.";
FFZ.ffz_commands.help.help = "Usage: /ffz help [command]\nList available commands, or show help for a specific command.";
// --------------------
@ -214,12 +263,20 @@ FFZ.prototype._modify_room = function(room) {
// Track which rooms the user is currently in.
init: function() {
this._super();
try {
f.add_room(this.id, this);
} catch(err) {
f.error("add_room: " + err);
}
},
willDestroy: function() {
this._super();
try {
f.remove_room(this.id);
} catch(err) {
f.error("remove_room: " + err);
}
},
getSuggestions: function() {
@ -228,18 +285,34 @@ FFZ.prototype._modify_room = function(room) {
// filteredSuggestions property of the chat-input component would
// be even better, but I was already hooking the room model.
var suggestions = this._super();
if ( this.settings.capitalize )
try {
if ( f.settings.capitalize )
suggestions = _.map(suggestions, FFZ.get_capitalization);
} catch(err) {
f.error("get_suggestions: " + err);
}
return suggestions;
},
send: function(text) {
try {
var cmd = text.split(' ', 1)[0].toLowerCase();
if ( cmd === "/ffz" ) {
this.set("messageToSend", "");
f.run_command(text.substr(5), this.get('id'));
} else
f.run_ffz_command(text.substr(5), this.get('id'));
return;
} else if ( cmd.charAt(0) === "/" && f.run_command(text, this.get('id')) ) {
this.set("messageToSend", "");
return;
}
} catch(err) {
f.error("send: " + err);
}
return this._super(text);
}
});

View file

@ -11,7 +11,11 @@ FFZ.prototype.setup_router = function() {
var f = this;
App.__container__.lookup('router:main').reopen({
ffzTransition: function() {
try {
f.track_page();
} catch(err) {
f.error("ffzTransition: " + err);
}
}.on('didTransition')
});
}

View file

@ -18,8 +18,9 @@ FFZ.prototype._modify_viewers = function(controller) {
controller.reopen({
lines: function() {
var viewers = this._super(),
categories = [],
var viewers = this._super();
try {
var categories = [],
data = {},
last_category = null;
@ -86,6 +87,10 @@ FFZ.prototype._modify_viewers = function(controller) {
}
}
} catch(err) {
f.error("ViewersController lines: " + err);
}
return viewers;
}.property("content.chatters")
});

View file

@ -23,22 +23,27 @@ FFZ.prototype.setup_bttv = function(delay) {
this.log("BetterTTV was detected after " + delay + "ms. Hooking.");
this.has_bttv = true;
this.track('setCustomVariable', '3', 'BetterTTV', BetterTTV.info.versionString());
// this.track('setCustomVariable', '3', 'BetterTTV', BetterTTV.info.versionString());
// Disable Dark if it's enabled.
document.querySelector(".app-main").classList.remove("ffz-dark");
document.body.classList.remove("ffz-dark");
if ( this._dark_style ) {
this._dark_style.parentElement.removeChild(this._dark_style);
delete this._dark_style;
}
// Disable other features too.
document.body.classList.remove("ffz-chat-colors");
document.body.classList.remove("ffz-chat-background");
// Send Message Behavior
var original_send = BetterTTV.chat.helpers.sendMessage, f = this;
BetterTTV.chat.helpers.sendMessage = function(message) {
var cmd = message.split(' ', 1)[0].toLowerCase();
if ( cmd === "/ffz" )
f.run_command(message.substr(5), BetterTTV.chat.store.currentRoom);
f.run_ffz_command(message.substr(5), BetterTTV.chat.store.currentRoom);
else
return original_send(message);
}

View file

@ -75,7 +75,7 @@ FFZ.prototype._feature_friday_ui = function(room_id, parent, view) {
btn.innerHTML = "<span>" + message + "</span>";
// Track the number of users to click this button.
btn.addEventListener('click', function() { f.track('trackLink', this.href, 'link'); });
// btn.addEventListener('click', function() { f.track('trackLink', this.href, 'link'); });
btnc.appendChild(btn);
parent.appendChild(btnc);

View file

@ -9,6 +9,9 @@ require('./shims');
var FFZ = window.FrankerFaceZ = function() {
FFZ.instance = this;
// Logging
this._log_data = [];
// Get things started.
this.initialize();
}
@ -19,7 +22,7 @@ FFZ.get = function() { return FFZ.instance; }
// Version
var VER = FFZ.version_info = {
major: 3, minor: 0, revision: 0,
major: 3, minor: 1, revision: 0,
toString: function() {
return [VER.major, VER.minor, VER.revision].join(".") + (VER.extra || "");
}
@ -30,6 +33,8 @@ var VER = FFZ.version_info = {
FFZ.prototype.log = function(msg, data, to_json) {
msg = "FFZ: " + msg + (to_json ? " -- " + JSON.stringify(data) : "");
this._log_data.push(msg);
if ( data !== undefined && console.groupCollapsed && console.dir ) {
console.groupCollapsed(msg);
if ( navigator.userAgent.indexOf("Firefox/") !== -1 )
@ -43,6 +48,43 @@ FFZ.prototype.log = function(msg, data, to_json) {
}
FFZ.prototype.error = function(msg, data, to_json) {
msg = "FFZ Error: " + msg + (to_json ? " -- " + JSON.stringify(data) : "");
this._log_data.push(msg);
if ( data !== undefined && console.groupCollapsed && console.dir ) {
console.groupCollapsed(msg);
if ( navigator.userAgent.indexOf("Firefox/") !== -1 )
console.log(data);
else
console.dir(data);
console.groupEnd(msg);
} else
console.assert(false, msg);
}
FFZ.prototype.paste_logs = function() {
this._pastebin(this._log_data.join("\n"), function(url) {
if ( ! url )
return console.log("FFZ Error: Unable to upload log to pastebin.");
console.log("FFZ: Your FrankerFaceZ log has been pasted to: " + url);
});
}
FFZ.prototype._pastebin = function(data, callback) {
jQuery.ajax({url: "http://putco.de/", type: "PUT", data: data, context: this})
.success(function(e) {
callback.bind(this)(e.trim() + ".log");
}).fail(function(e) {
callback.bind(this)(null);
});
}
// -------------------
// User Data
// -------------------
@ -69,14 +111,15 @@ require('./socket');
require('./emoticons');
require('./badges');
require('./ember/router');
// Analytics: require('./ember/router');
require('./ember/room');
require('./ember/line');
require('./ember/chatview');
require('./ember/viewers');
require('./ember/moderation-card');
//require('./ember/teams');
require('./tracking');
// Analytics: require('./tracking');
require('./debug');
@ -141,13 +184,14 @@ FFZ.prototype.setup_ember = function(delay) {
this.setup_emoticons();
this.setup_badges();
this.setup_piwik();
//this.setup_piwik();
this.setup_router();
//this.setup_router();
this.setup_room();
this.setup_line();
this.setup_chatview();
this.setup_viewers();
this.setup_mod_card();
//this.setup_teams();

View file

@ -19,7 +19,7 @@ FFZ.settings_info.dark_twitch = {
if ( this.has_bttv )
return;
document.querySelector(".app-main").classList.toggle("ffz-dark", val);
document.body.classList.toggle("ffz-dark", val);
if ( val )
this._load_dark_css();
}
@ -34,7 +34,7 @@ FFZ.prototype.setup_dark = function() {
if ( this.has_bttv )
return;
document.querySelector(".app-main").classList.toggle("ffz-dark", this.settings.dark_twitch);
document.body.classList.toggle("ffz-dark", this.settings.dark_twitch);
if ( this.settings.dark_twitch )
this._load_dark_css();
}

View file

@ -95,7 +95,7 @@ FFZ.prototype.build_ui_popup = function(view) {
// Add the menu to the DOM.
this._popup = container;
sub_container.style.maxHeight = Math.max(300, view.$().height() - 212) + "px";
sub_container.style.maxHeight = Math.max(100, view.$().height() - 162) + "px";
view.$('.chat-interface').append(container);
}
@ -124,14 +124,52 @@ FFZ.prototype._ui_change_page = function(view, menu, container, page) {
FFZ.menu_pages.settings = {
render: function(view, container) {
var menu = document.createElement('div');
var settings = {},
categories = [];
for(var key in FFZ.settings_info) {
var info = FFZ.settings_info[key],
cat = info.category || "Miscellaneous",
cs = settings[cat];
if ( info.visible !== undefined && info.visible !== null ) {
var visible = info.visible;
if ( typeof info.visible == "function" )
visible = info.visible.bind(this)();
if ( ! visible )
continue;
}
if ( ! cs ) {
categories.push(cat);
cs = settings[cat] = [];
}
cs.push([key, info]);
}
categories.sort(function(a,b) {
var a = a.toLowerCase(),
b = b.toLowerCase();
if ( a < b ) return -1;
else if ( a > b ) return 1;
return 0;
});
for(var ci=0; ci < categories.length; ci++) {
var category = categories[ci],
cset = settings[category],
menu = document.createElement('div'),
heading = document.createElement('div');
heading.className = 'heading';
menu.className = 'chat-menu-content';
heading.innerHTML = category;
menu.appendChild(heading);
var settings = [];
for(var key in FFZ.settings_info)
settings.push([key, FFZ.settings_info[key]]);
settings.sort(function(a,b) {
cset.sort(function(a,b) {
var ai = a[1],
bi = b[1],
@ -144,21 +182,12 @@ FFZ.menu_pages.settings = {
});
for(var i=0; i < settings.length; i++) {
var key = settings[i][0],
info = settings[i][1],
for(var i=0; i < cset.length; i++) {
var key = cset[i][0],
info = cset[i][1],
el = document.createElement('p'),
val = this.settings.get(key);
if ( info.visible !== undefined && info.visible !== null ) {
var visible = info.visible;
if ( typeof info.visible == "function" )
visible = info.visible.bind(this)();
if ( ! visible )
continue;
}
el.className = 'clearfix';
if ( info.type == "boolean" ) {
@ -198,6 +227,7 @@ FFZ.menu_pages.settings = {
}
container.appendChild(menu);
}
},
name: "Settings",
@ -236,8 +266,7 @@ FFZ.menu_pages.channel = {
var room_id = view.get('controller.currentRoom.id'),
room = this.rooms[room_id];
this.log("Menu for Room: " + room_id, room);
this.track('trackEvent', 'Menu', 'Open', room_id);
//this.track('trackEvent', 'Menu', 'Open', room_id);
// Add the header and ad button.
/*var btn = document.createElement('a');

View file

@ -19,6 +19,7 @@ FFZ.settings_info.highlight_notifications = {
type: "boolean",
value: false,
category: "Chat",
visible: function() { return ! this.has_bttv },
name: "Highlight Notifications",
@ -54,7 +55,7 @@ FFZ.settings_info.highlight_notifications = {
// ---------------------
FFZ.ws_commands.message = function(message) {
this.show_mesage(message);
this.show_message(message);
}

View file

@ -19,6 +19,8 @@ FFZ.prototype.setup_races = function() {
FFZ.settings_info.srl_races = {
type: "boolean",
value: true,
category: "Channel Metadata",
name: "SRL Race Information",
help: 'Display information about <a href="http://www.speedrunslive.com/" target="_new">SpeedRunsLive</a> races under channels.',
on_update: function(val) {

View file

@ -11,8 +11,42 @@ var sanitize_cache = {},
else if ( num == 3 ) return '3rd';
else if ( num == null ) return '---';
return num + "th";
},
brighten = function(rgb, amount) {
amount = (amount === 0) ? 0 : (amount || 1);
amount = Math.round(255 * -(amount / 100));
var r = Math.max(0, Math.min(255, rgb[0] - amount)),
g = Math.max(0, Math.min(255, rgb[1] - amount)),
b = Math.max(0, Math.min(255, rgb[2] - amount));
return [r,g,b];
},
rgb_to_css = function(rgb) {
return "rgb(" + rgb[0] + ", " + rgb[1] + ", " + rgb[2] + ")";
},
darken = function(rgb, amount) {
amount = (amount === 0) ? 0 : (amount || 1);
return brighten(rgb, -amount);
},
get_luminance = function(rgb) {
rgb = [rgb[0]/255, rgb[1]/255, rgb[2]/255];
for (var i =0; i<rgb.length; i++) {
if (rgb[i] <= 0.03928) {
rgb[i] = rgb[i] / 12.92;
} else {
rgb[i] = Math.pow( ((rgb[i]+0.055)/1.055), 2.4 );
}
}
var l = (0.2126 * rgb[0]) + (0.7152 * rgb[1]) + (0.0722 * rgb[2]);
return l;
};
module.exports = {
update_css: function(element, id, css) {
var all = element.innerHTML,
@ -34,6 +68,11 @@ module.exports = {
element.innerHTML = all;
},
get_luminance: get_luminance,
brighten: brighten,
darken: darken,
rgb_to_css: rgb_to_css,
number_commas: function(x) {
var parts = x.toString().split(".");
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");

155
style.css
View file

@ -16,8 +16,17 @@
.ffz-ui-toggle svg.svg-emoticons path { fill: rgba(0,0,0,0.2); }
.ffz-ui-toggle:hover svg.svg-emoticons path { fill: rgba(0,0,0,0.5); }
.chat-container.dark .ffz-ui-toggle svg.svg-emoticons path,.app-main.theatre .ffz-ui-toggle svg.svg-emoticons path,.ffz-ui-toggle.dark svg.svg-emoticons path { fill: #888; }
.chat-container.dark .ffz-ui-toggle:hover svg.svg-emoticons path,.app-main.theatre .ffz-ui-toggle:hover svg.svg-emoticons path,.ffz-ui-toggle.dark:hover svg.svg-emoticons path { fill: #777; }
.ember-chat-container.dark .ffz-ui-toggle svg.svg-emoticons path,
.chat-container.dark .ffz-ui-toggle svg.svg-emoticons path,
.app-main.theatre .ffz-ui-toggle svg.svg-emoticons path,
.ffz-ui-toggle.dark svg.svg-emoticons path { fill: #888; }
.ember-chat-container.dark .ffz-ui-toggle:hover svg.svg-emoticons path,
.chat-container.dark .ffz-ui-toggle:hover svg.svg-emoticons path,
.app-main.theatre .ffz-ui-toggle:hover svg.svg-emoticons path,
.ffz-ui-toggle.dark:hover svg.svg-emoticons path { fill: #777; }
.ffz-ui-toggle.no-emotes svg.svg-emoticons path { fill: rgba(80,0,0,0.2); }
.ffz-ui-toggle.no-emotes:hover svg.svg-emoticons path { fill: rgba(80,0,0,0.5); }
@ -28,14 +37,37 @@
.ffz-ui-toggle.blue.live svg.svg-emoticons path { fill: rgba(47,88,185,0.5); }
.ffz-ui-toggle.blue.live:hover svg.svg-emoticons path { fill: rgba(47,88,185,1); }
.app-main.theatre .ffz-ui-toggle.no-emotes svg.svg-emoticons path,.chat-container.dark .ffz-ui-toggle.no-emotes svg.svg-emoticons path,.ember-chat .chat-interface .textarea-contain .ffz-ui-toggle.dark.no-emotes svg.svg-emoticons path { fill: #543f3f; }
.app-main.theatre .ffz-ui-toggle.no-emotes:hover svg.svg-emoticons path,.chat-container.dark .ffz-ui-toggle.no-emotes:hover svg.svg-emoticons path,.ember-chat .chat-interface .textarea-contain .ffz-ui-toggle.dark.no-emotes:hover svg.svg-emoticons path { fill: #453434; }
.app-main.theatre .ffz-ui-toggle.live svg.svg-emoticons path,.chat-container.dark .ffz-ui-toggle.live svg.svg-emoticons path,.ember-chat .chat-interface .textarea-contain .ffz-ui-toggle.dark.live svg.svg-emoticons path { fill: #5b4487; }
.app-main.theatre .ffz-ui-toggle.live:hover svg.svg-emoticons path,.chat-container.dark .ffz-ui-toggle.live:hover svg.svg-emoticons path,.ember-chat .chat-interface .textarea-contain .ffz-ui-toggle.dark.live:hover svg.svg-emoticons path { fill: #513c78; }
.ember-chat-container.dark .ffz-ui-toggle.no-emotes svg.svg-emoticons path,
.app-main.theatre .ffz-ui-toggle.no-emotes svg.svg-emoticons path,
.chat-container.dark .ffz-ui-toggle.no-emotes svg.svg-emoticons path,
.ember-chat .chat-interface .textarea-contain .ffz-ui-toggle.dark.no-emotes svg.svg-emoticons path { fill: #453434; }
.ember-chat-container.dark .ffz-ui-toggle.no-emotes:hover svg.svg-emoticons path,
.app-main.theatre .ffz-ui-toggle.no-emotes:hover svg.svg-emoticons path,
.chat-container.dark .ffz-ui-toggle.no-emotes:hover svg.svg-emoticons path,
.ember-chat .chat-interface .textarea-contain .ffz-ui-toggle.dark.no-emotes:hover svg.svg-emoticons path { fill: #543f3f; }
.ember-chat-container.dark .ffz-ui-toggle.live svg.svg-emoticons path,
.app-main.theatre .ffz-ui-toggle.live svg.svg-emoticons path,
.chat-container.dark .ffz-ui-toggle.live svg.svg-emoticons path,
.ember-chat .chat-interface .textarea-contain .ffz-ui-toggle.dark.live svg.svg-emoticons path { fill: #513c78; }
.ember-chat-container.dark .ffz-ui-toggle.live:hover svg.svg-emoticons path,
.app-main.theatre .ffz-ui-toggle.live:hover svg.svg-emoticons path,
.chat-container.dark .ffz-ui-toggle.live:hover svg.svg-emoticons path,
.ember-chat .chat-interface .textarea-contain .ffz-ui-toggle.dark.live:hover svg.svg-emoticons path { fill: #5b4487; }
.ember-chat-container.dark .ffz-ui-toggle.blue.live svg.svg-emoticons path,
.app-main.theatre .ffz-ui-toggle.blue.live svg.svg-emoticons path,
.chat-container.dark .ffz-ui-toggle.blue.live svg.svg-emoticons path,
.ember-chat .chat-interface .textarea-contain .ffz-ui-toggle.dark.blue.live svg.svg-emoticons path { fill: #3c4e78; }
.ember-chat-container.dark .ffz-ui-toggle.blue.live:hover svg.svg-emoticons path,
.app-main.theatre .ffz-ui-toggle.blue.live:hover svg.svg-emoticons path,
.chat-container.dark .ffz-ui-toggle.blue.live:hover svg.svg-emoticons path,
.ember-chat .chat-interface .textarea-contain .ffz-ui-toggle.dark.blue.live:hover svg.svg-emoticons path { fill: #445887; }
.app-main.theatre .ffz-ui-toggle.blue.live svg.svg-emoticons path,.chat-container.dark .ffz-ui-toggle.blue.live svg.svg-emoticons path,.ember-chat .chat-interface .textarea-contain .ffz-ui-toggle.dark.blue.live svg.svg-emoticons path { fill: #445887; }
.app-main.theatre .ffz-ui-toggle.blue.live:hover svg.svg-emoticons path,.chat-container.dark .ffz-ui-toggle.blue.live:hover svg.svg-emoticons path,.ember-chat .chat-interface .textarea-contain .ffz-ui-toggle.dark.blue.live:hover svg.svg-emoticons path { fill: #3c4e78; }
.ffz-ui-toggle.live {
animation: ffzfade 8s linear infinite;
@ -158,7 +190,9 @@
fill: rgba(255,255,255,0.35) !important;
}
.app-main.theatre .follow-button .notify:before, .app-main.theatre .button.drop:after, .app-main.theatre .follow-button .drop.follow:after {
.app-main.theatre .follow-button .notify:before,
.app-main.theatre .button.drop:after,
.app-main.theatre .follow-button .drop.follow:after {
border: 5px solid rgba(255,255,255,0.35);
border-left-color: transparent;
border-right-color: transparent;
@ -258,7 +292,8 @@
color: inherit;
}
#ffz-race-popup a.twitch, #ffz-race-popup a.hitbox {
#ffz-race-popup a.twitch,
#ffz-race-popup a.hitbox {
display: inline-block;
height: 16px;
margin: 0 5px;
@ -287,12 +322,24 @@
/* Menu Options */
@media screen and (max-width: 369px) {
.ember-chat-container .ember-chat .chat-interface .emoticon-selector {
right: -10px;
}
}
.ffz-ui-menu-page {
overflow-y: auto;
padding: 0 20px;
margin: 0 -20px;
}
.chat-menu.ffz-ui-popup .ffz-ui-menu-page .chat-menu-content .heading {
margin-bottom: 5px;
border-bottom: 1px solid rgba(0,0,0, 0.2);
}
.chat-menu.ffz-ui-popup .ffz-ui-menu-page .chat-menu-content {
padding: 10px 0;
background-color: transparent;
@ -341,7 +388,7 @@
margin-bottom: -1px;
cursor: pointer;
border-left: 1px solid rgba(0,0,0,0.2);
border-bottom: 1px solid rgba(0,0,0,0.2);
border-bottom: 1px solid transparent;
}
.ffz-ui-popup ul.menu li.active {
@ -375,13 +422,79 @@
background-color: rgba(255,255,255, 0.75);
}
.ffz-chat-background.theatre .chat-container .chat-line .mentioned, .ffz-chat-background .chat-container.dark .chat-line .mentioned {
.ffz-chat-background .app-main.theatre .chat-container .chat-line .mentioned,
.ffz-chat-background .ember-chat-container.dark .chat-line .mentioned,
.ffz-chat-background .chat-container.dark .chat-line .mentioned {
color: #8c8c9c;
background-color: rgba(16,16,20, 0.75);
}
/* Fix Moderation Cards */
.ember-chat .moderation-card {
box-shadow: #808080 0 0 5px;
}
.ember-chat .moderation-card button {
margin: 0;
padding: 0 5px;
}
.ember-chat .moderation-card button:not(.glyph-only):hover,
.ember-chat .moderation-card button:not(.glyph-only):focus {
color: #fff;
background-color: rgba(117,80,186, 1);
}
.ember-chat .moderation-card button.message {
height: 30px; width: 28px;
}
.ember-chat .ffz-moderation-card .interface .mod-controls:last-of-type,
.ember-chat .moderation-card .interface span.right {
float: right;
}
.ember-chat .moderation-card:focus {
outline: none;
box-shadow: #000 0 0 5px;
}
.ember-chat-container.dark .ember-chat .moderation-card:focus,
.chat-container.dark .ember-chat .moderation-card:focus,
.app-main.theatre .ember-chat .moderation-card:focus {
box-shadow: #fff 0 0 5px;
}
.ember-chat-container.dark .ember-chat .moderation-card .interface,
.chat-container.dark .ember-chat .moderation-card .interface,
.app-main.theatre .ember-chat .moderation-card .interface {
background-color: #232329;
}
.ember-chat .ffz-moderation-card .interface:not(:last-of-type) {
border-bottom: none;
padding-bottom: 0;
}
.ember-chat .moderation-card .interface {
border-top: none;
}
.ember-chat .moderation-card h3.name {
display: inline-block;
text-shadow: black 0 0 5px;
}
.ember-chat .moderation-card .channel_background {
width: 100%;
top: 0;
}
/* Chat Rows */
.ffz-chat-background .more-messages-indicator {
/* This looks better when it's full width. */
margin: 0 -20px;
@ -404,10 +517,6 @@
background-color: rgba(0,0,0, 0.1);
}
.theatre.ffz-chat-background .ember-chat .chat-messages .chat-line.ffz-alternate, .ffz-chat-background .chat-container.dark .ember-chat .chat-messages .chat-line.ffz-alternate {
background-color: rgba(255,255,255, 0.05);
}
.ffz-chat-background .ember-chat .chat-messages .chat-line.ffz-mentioned {
background-color: rgba(255,127,127, 0.2);
}
@ -416,10 +525,20 @@
background-color: rgba(255,127,127, 0.4);
}
.theatre.ffz-chat-background .ember-chat .chat-messages .chat-line.ffz-mentioned, .ffz-chat-background .chat-container.dark .ember-chat .chat-messages .chat-line.ffz-mentioned {
.ffz-chat-background .app-main.theatre .ember-chat .chat-messages .chat-line.ffz-alternate,
.ffz-chat-background .chat-container.dark .ember-chat .chat-messages .chat-line.ffz-alternate,
.ffz-chat-background .ember-chat-container.dark .ember-chat .chat-messages .chat-line.ffz-alternate {
background-color: rgba(255,255,255, 0.05);
}
.ffz-chat-background .app-main.theatre .ember-chat .chat-messages .chat-line.ffz-mentioned,
.ffz-chat-background .chat-container.dark .ember-chat .chat-messages .chat-line.ffz-mentioned,
.ffz-chat-background .ember-chat-container.dark .ember-chat .chat-messages .chat-line.ffz-mentioned {
background-color: rgba(255,0,0, 0.2);
}
.theatre.ffz-chat-background .ember-chat .chat-messages .chat-line.ffz-mentioned.ffz-alternate, .ffz-chat-background .chat-container.dark .ember-chat .chat-messages .chat-line.ffz-mentioned.ffz-alternate {
.ffz-chat-background .app-main.theatre .ember-chat .chat-messages .chat-line.ffz-mentioned.ffz-alternate,
.ffz-chat-background .chat-container.dark .ember-chat .chat-messages .chat-line.ffz-mentioned.ffz-alternate,
.ffz-chat-background .ember-chat-container.dark .ember-chat .chat-messages .chat-line.ffz-mentioned.ffz-alternate {
background-color: rgba(255,0,0, 0.3);
}