1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-06-28 05:15:54 +00:00

Convert all files to UNIX line endings. Why do I never pay attention to that when starting a project? ;_;

This commit is contained in:
SirStendec 2015-01-20 01:53:18 -05:00
parent 3d3c1f9716
commit 343a9c927b
21 changed files with 3095 additions and 3087 deletions

4
.gitignore vendored
View file

@ -1,3 +1,3 @@
node_modules node_modules
npm-debug.log npm-debug.log
build build

View file

@ -1,39 +1,39 @@
FrankerFaceZ FrankerFaceZ
============ ============
Copyright (c) 2015 FrankerFaceZ Copyright (c) 2015 FrankerFaceZ
This script is free to modify for personal use. You are not allowed to sell or This script is free to modify for personal use. You are not allowed to sell or
distribute FrankerFaceZ or any components of FrankerFaceZ. distribute FrankerFaceZ or any components of FrankerFaceZ.
Developing Developing
========== ==========
FrankerFaceZ uses node.js to manage development dependencies and to run an HTTP FrankerFaceZ uses node.js to manage development dependencies and to run an HTTP
server for development. To get everything you need: server for development. To get everything you need:
1. Install node.js 1. Install node.js
2. Run ```npm install``` within the FrankerFaceZ directory. 2. Run ```npm install``` within the FrankerFaceZ directory.
From there, you can use gulp to build the extension from source simply by From there, you can use gulp to build the extension from source simply by
running ```gulp```. For development, you can instruct gulp to watch the source running ```gulp```. For development, you can instruct gulp to watch the source
files for changes and re-build automatically with ```gulp watch``` files for changes and re-build automatically with ```gulp watch```
FrankerFaceZ comes with a local development server that listens on port 8000 FrankerFaceZ comes with a local development server that listens on port 8000
and it serves up local development copies of files, falling back to the CDN and it serves up local development copies of files, falling back to the CDN
when a local copy of a file isn't present. To start the server, when a local copy of a file isn't present. To start the server,
run ```npm test``` run ```npm test```
At this time, you will also need to use the included version of the Chrome At this time, you will also need to use the included version of the Chrome
extension. Remove any existing copy of FrankerFaceZ from your browser and load extension. Remove any existing copy of FrankerFaceZ from your browser and load
the unpacked extension in the ```Chrome Extension``` folder. the unpacked extension in the ```Chrome Extension``` folder.
Once you're using that extension, use the command ```/ffz developer_mode on``` Once you're using that extension, use the command ```/ffz developer_mode on```
or ```/ffz developer_mode off``` in Twitch chat to toggle developer mode on or or ```/ffz developer_mode off``` in Twitch chat to toggle developer mode on or
off. You must then refresh the page for changes to take effect. If FFZ is not off. You must then refresh the page for changes to take effect. If FFZ is not
working or the command otherwise fails to work, you can open the JavaScript working or the command otherwise fails to work, you can open the JavaScript
console on twitch.tv and run ```localStorage.ffzDebugMode = true;``` or console on twitch.tv and run ```localStorage.ffzDebugMode = true;``` or
```localStorage.ffzDebugMode = false;``` to enable or disable the feature. ```localStorage.ffzDebugMode = false;``` to enable or disable the feature.

View file

@ -1,40 +1,40 @@
var fs = require('fs'), var fs = require('fs'),
gulp = require('gulp'), gulp = require('gulp'),
browserify = require('gulp-browserify'), browserify = require('gulp-browserify'),
header = require('gulp-header'), header = require('gulp-header'),
footer = require('gulp-footer'), footer = require('gulp-footer'),
concat = require('gulp-concat'), concat = require('gulp-concat'),
clean = require('gulp-clean'), clean = require('gulp-clean'),
util = require('gulp-util'), util = require('gulp-util'),
rename = require('gulp-rename'), rename = require('gulp-rename'),
uglify = require('gulp-uglify'); uglify = require('gulp-uglify');
gulp.task('clean', function() { gulp.task('clean', function() {
return gulp.src('build', {read:false}) return gulp.src('build', {read:false})
.pipe(clean()); .pipe(clean());
}); });
gulp.task('prepare', ['clean'], function() { gulp.task('prepare', ['clean'], function() {
return gulp.src(['src/**/*']) return gulp.src(['src/**/*'])
.pipe(gulp.dest('build/')); .pipe(gulp.dest('build/'));
}); });
gulp.task('scripts', ['prepare'], function() { gulp.task('scripts', ['prepare'], function() {
gulp.src(['build/main.js']) gulp.src(['build/main.js'])
.pipe(browserify()) .pipe(browserify())
.pipe(concat('script.js')) .pipe(concat('script.js'))
.pipe(header('(function(window) {')) .pipe(header('(function(window) {'))
.pipe(footer(';window.ffz = new FrankerFaceZ()}(window));')) .pipe(footer(';window.ffz = new FrankerFaceZ()}(window));'))
.pipe(gulp.dest(__dirname)) .pipe(gulp.dest(__dirname))
.pipe(uglify()) .pipe(uglify())
.pipe(rename('script.min.js')) .pipe(rename('script.min.js'))
.pipe(gulp.dest(__dirname)) .pipe(gulp.dest(__dirname))
.on('error', util.log); .on('error', util.log);
}); });
gulp.task('watch', ['default'], function() { gulp.task('watch', ['default'], function() {
gulp.watch('src/**/*', ['default']); gulp.watch('src/**/*', ['default']);
}); });
gulp.task('default', ['scripts']); gulp.task('default', ['scripts']);

3006
script.js

File diff suppressed because it is too large Load diff

View file

@ -1,191 +1,191 @@
var FFZ = window.FrankerFaceZ, var FFZ = window.FrankerFaceZ,
constants = require('./constants'), constants = require('./constants'),
utils = require('./utils'); utils = require('./utils');
// -------------------- // --------------------
// Initialization // Initialization
// -------------------- // --------------------
FFZ.prototype.setup_badges = function() { FFZ.prototype.setup_badges = function() {
this.log("Preparing badge system."); this.log("Preparing badge system.");
this.badges = {}; this.badges = {};
this.log("Creating badge style element."); this.log("Creating badge style element.");
var s = this._badge_style = document.createElement('style'); var s = this._badge_style = document.createElement('style');
s.id = "ffz-badge-css"; s.id = "ffz-badge-css";
document.head.appendChild(s); document.head.appendChild(s);
this.log("Adding legacy donor badges."); this.log("Adding legacy donor badges.");
this._legacy_add_donors(); this._legacy_add_donors();
} }
// -------------------- // --------------------
// Badge CSS // Badge CSS
// -------------------- // --------------------
var badge_css = function(badge) { var badge_css = function(badge) {
return ".badges .ffz-badge-" + badge.id + " { background-color: " + badge.color + '; background-image: url("' + badge.image + '"); ' + (badge.extra_css || "") + '}'; return ".badges .ffz-badge-" + badge.id + " { background-color: " + badge.color + '; background-image: url("' + badge.image + '"); ' + (badge.extra_css || "") + '}';
} }
// -------------------- // --------------------
// Render Badge // Render Badge
// -------------------- // --------------------
FFZ.prototype.bttv_badges = function(data) { FFZ.prototype.bttv_badges = function(data) {
var user_id = data.sender, var user_id = data.sender,
user = this.users[user_id], user = this.users[user_id],
badges_out = [], badges_out = [],
insert_at = -1; insert_at = -1;
if ( ! user || ! user.badges ) if ( ! user || ! user.badges )
return; return;
// Determine where in the list to insert these badges. // Determine where in the list to insert these badges.
for(var i=0; i < data.badges.length; i++) { for(var i=0; i < data.badges.length; i++) {
var badge = data.badges[i]; var badge = data.badges[i];
if ( badge.type == "subscriber" || badge.type == "turbo" ) { if ( badge.type == "subscriber" || badge.type == "turbo" ) {
insert_at = i; insert_at = i;
break; break;
} }
} }
for (var slot in user.badges) { for (var slot in user.badges) {
if ( ! user.badges.hasOwnProperty(slot) ) if ( ! user.badges.hasOwnProperty(slot) )
continue; continue;
var badge = user.badges[slot], var badge = user.badges[slot],
full_badge = this.badges[badge.id] || {}, full_badge = this.badges[badge.id] || {},
desc = badge.title || full_badge.title, desc = badge.title || full_badge.title,
style = "", style = "",
alpha = BetterTTV.settings.get('alphaTags'); alpha = BetterTTV.settings.get('alphaTags');
if ( badge.image ) if ( badge.image )
style += 'background-image: url(\\"' + badge.image + '\\"); '; style += 'background-image: url(\\"' + badge.image + '\\"); ';
if ( badge.color && ! alpha ) if ( badge.color && ! alpha )
style += 'background-color: ' + badge.color + '; '; style += 'background-color: ' + badge.color + '; ';
if ( badge.extra_css ) if ( badge.extra_css )
style += badge.extra_css; style += badge.extra_css;
if ( style ) if ( style )
desc += '" style="' + style; desc += '" style="' + style;
badges_out.push([(insert_at == -1 ? 1 : -1) * slot, {type: "ffz-badge-" + badge.id + (alpha ? " alpha" : ""), name: "", description: desc}]); badges_out.push([(insert_at == -1 ? 1 : -1) * slot, {type: "ffz-badge-" + badge.id + (alpha ? " alpha" : ""), name: "", description: desc}]);
} }
badges_out.sort(function(a,b){return a[0] - b[0]}); badges_out.sort(function(a,b){return a[0] - b[0]});
if ( insert_at == -1 ) { if ( insert_at == -1 ) {
while(badges_out.length) while(badges_out.length)
data.badges.push(badges_out.shift()[1]); data.badges.push(badges_out.shift()[1]);
} else { } else {
while(badges_out.length) while(badges_out.length)
data.badges.insertAt(insert_at, badges_out.shift()[1]); data.badges.insertAt(insert_at, badges_out.shift()[1]);
} }
} }
FFZ.prototype.render_badge = function(view) { FFZ.prototype.render_badge = function(view) {
var user = view.get('context.model.from'), var user = view.get('context.model.from'),
room_id = view.get('context.parentController.content.id'), room_id = view.get('context.parentController.content.id'),
badges = view.$('.badges'); badges = view.$('.badges');
var data = this.users[user]; var data = this.users[user];
if ( ! data || ! data.badges ) if ( ! data || ! data.badges )
return; return;
// Figure out where to place our badge(s). // Figure out where to place our badge(s).
var before = badges.find('.badge').filter(function(i) { var before = badges.find('.badge').filter(function(i) {
var t = this.title.toLowerCase(); var t = this.title.toLowerCase();
return t == "subscriber" || t == "turbo"; return t == "subscriber" || t == "turbo";
}).first(); }).first();
var badges_out = [], reverse = !(!before.length); var badges_out = [], reverse = !(!before.length);
for ( var slot in data.badges ) { for ( var slot in data.badges ) {
if ( ! data.badges.hasOwnProperty(slot) ) if ( ! data.badges.hasOwnProperty(slot) )
continue; continue;
var badge = data.badges[slot], var badge = data.badges[slot],
full_badge = this.badges[badge.id] || {}; full_badge = this.badges[badge.id] || {};
var el = document.createElement('div'); var el = document.createElement('div');
el.className = 'badge float-left tooltip ffz-badge-' + badge.id; el.className = 'badge float-left tooltip ffz-badge-' + badge.id;
el.setAttribute('title', badge.title || full_badge.title); el.setAttribute('title', badge.title || full_badge.title);
if ( badge.image ) if ( badge.image )
el.style.backgroundImage = 'url("' + badge.image + '")'; el.style.backgroundImage = 'url("' + badge.image + '")';
if ( badge.color ) if ( badge.color )
el.style.backgroundColor = badge.color; el.style.backgroundColor = badge.color;
if ( badge.extra_css ) if ( badge.extra_css )
el.style.cssText += badge.extra_css; el.style.cssText += badge.extra_css;
badges_out.push([((reverse ? 1 : -1) * slot), el]); badges_out.push([((reverse ? 1 : -1) * slot), el]);
} }
badges_out.sort(function(a,b){return a[0] - b[0]}); badges_out.sort(function(a,b){return a[0] - b[0]});
if ( reverse ) { if ( reverse ) {
while(badges_out.length) while(badges_out.length)
before.before(badges_out.shift()[1]); before.before(badges_out.shift()[1]);
} else { } else {
while(badges_out.length) while(badges_out.length)
badges.append(badges_out.shift()[1]); badges.append(badges_out.shift()[1]);
} }
} }
// -------------------- // --------------------
// Legacy Support // Legacy Support
// -------------------- // --------------------
FFZ.prototype._legacy_add_donors = function(tries) { FFZ.prototype._legacy_add_donors = function(tries) {
this.badges[1] = {id: 1, title: "FFZ Donor", color: "#755000", image: "//cdn.frankerfacez.com/channel/global/donoricon.png"}; this.badges[1] = {id: 1, title: "FFZ Donor", color: "#755000", image: "//cdn.frankerfacez.com/channel/global/donoricon.png"};
utils.update_css(this._badge_style, 1, badge_css(this.badges[1])); utils.update_css(this._badge_style, 1, badge_css(this.badges[1]));
// Developer Badges // Developer Badges
// TODO: Upload the badge to the proper CDN. // TODO: Upload the badge to the proper CDN.
this.badges[0] = {id: 0, title: "FFZ Developer", color: "#FAAF19", image: "//cdn.frankerfacez.com/channel/global/devicon.png"}; this.badges[0] = {id: 0, title: "FFZ Developer", color: "#FAAF19", image: "//cdn.frankerfacez.com/channel/global/devicon.png"};
utils.update_css(this._badge_style, 0, badge_css(this.badges[0])); utils.update_css(this._badge_style, 0, badge_css(this.badges[0]));
this.users.sirstendec = {badges: {0: {id:0}}}; this.users.sirstendec = {badges: {0: {id:0}}};
jQuery.ajax(constants.SERVER + "script/donors.txt", {cache: false, context: this}) jQuery.ajax(constants.SERVER + "script/donors.txt", {cache: false, context: this})
.done(function(data) { .done(function(data) {
this._legacy_parse_donors(data); this._legacy_parse_donors(data);
}).fail(function(data) { }).fail(function(data) {
if ( data.status == 404 ) if ( data.status == 404 )
return; return;
tries = (tries || 0) + 1; tries = (tries || 0) + 1;
if ( tries < 10 ) if ( tries < 10 )
return this._legacy_add_donors(tries); return this._legacy_add_donors(tries);
}); });
} }
FFZ.prototype._legacy_parse_donors = function(data) { FFZ.prototype._legacy_parse_donors = function(data) {
var count = 0; var count = 0;
if ( data != null ) { if ( data != null ) {
var lines = data.trim().split(/\W+/); var lines = data.trim().split(/\W+/);
for(var i=0; i < lines.length; i++) { for(var i=0; i < lines.length; i++) {
var user_id = lines[i], var user_id = lines[i],
user = this.users[user_id] = this.users[user_id] || {}, user = this.users[user_id] = this.users[user_id] || {},
badges = user.badges = user.badges || {}; badges = user.badges = user.badges || {};
if ( badges[0] ) if ( badges[0] )
continue; continue;
badges[0] = {id:1}; badges[0] = {id:1};
count += 1; count += 1;
} }
} }
this.log("Added donor badge to " + utils.number_commas(count) + " users."); this.log("Added donor badge to " + utils.number_commas(count) + " users.");
} }

View file

@ -1,11 +1,11 @@
var SVGPATH = '<path d="m120.95 1.74c4.08-0.09 8.33-0.84 12.21 0.82 3.61 1.8 7 4.16 11.01 5.05 2.08 3.61 6.12 5.46 8.19 9.07 3.6 5.67 7.09 11.66 8.28 18.36 1.61 9.51 7.07 17.72 12.69 25.35 3.43 7.74 1.97 16.49 3.6 24.62 2.23 5.11 4.09 10.39 6.76 15.31 1.16 2 4.38 0.63 4.77-1.32 1.2-7.1-2.39-13.94-1.97-21.03 0.38-3.64-0.91-7.48 0.25-10.99 2.74-3.74 4.57-8.05 7.47-11.67 3.55-5.47 10.31-8.34 16.73-7.64 2.26 2.89 5.13 5.21 7.58 7.92 2.88 4.3 6.52 8.01 9.83 11.97 1.89 2.61 3.06 5.64 4.48 8.52 2.81 4.9 4 10.5 6.63 15.49 2.16 6.04 5.56 11.92 5.37 18.5 0.65 1.95 0.78 4 0.98 6.03 1.01 3.95 2.84 8.55 0.63 12.42-2.4 5.23-7.03 8.97-11.55 12.33-6.06 4.66-11.62 10.05-18.37 13.75-4.06 2.65-8.24 5.17-12.71 7.08-3.59 1.57-6.06 4.94-9.85 6.09-2.29 1.71-3.98 4.51-6.97 5.02-4.56 1.35-8.98-3.72-13.5-1.25-2.99 1.83-6.19 3.21-9.39 4.6-8.5 5.61-18.13 9.48-28.06 11.62-8.36-0.2-16.69 0.62-25.05 0.47-3.5-1.87-7.67-1.08-11.22-2.83-6.19-1.52-10.93-6.01-16.62-8.61-2.87-1.39-5.53-3.16-8.11-4.99-2.58-1.88-4.17-4.85-6.98-6.44-3.83-0.11-6.54 3.42-10.24 3.92-2.31 0.28-4.64 0.32-6.96 0.31-3.5-3.65-5.69-8.74-10.59-10.77-5.01-3.68-10.57-6.67-14.84-11.25-2.52-2.55-5.22-4.87-8.24-6.8-4.73-4.07-7.93-9.51-11.41-14.62-3.08-4.41-5.22-9.73-4.6-15.19 0.65-8.01 0.62-16.18 2.55-24.02 4.06-10.46 11.15-19.34 18.05-28.06 3.71-5.31 9.91-10.21 16.8-8.39 3.25 1.61 5.74 4.56 7.14 7.89 1.19 2.7 3.49 4.93 3.87 7.96 0.97 5.85 1.6 11.86 0.74 17.77-1.7 6.12-2.98 12.53-2.32 18.9 0.01 2.92 2.9 5.36 5.78 4.57 3.06-0.68 3.99-4.07 5.32-6.48 1.67-4.06 4.18-7.66 6.69-11.23 3.61-5.28 5.09-11.57 7.63-17.37 2.07-4.56 1.7-9.64 2.56-14.46 0.78-7.65-0.62-15.44 0.7-23.04 1.32-3.78 1.79-7.89 3.8-11.4 3.01-3.66 6.78-6.63 9.85-10.26 1.72-2.12 4.21-3.32 6.55-4.6 7.89-2.71 15.56-6.75 24.06-7z"/>', var SVGPATH = '<path d="m120.95 1.74c4.08-0.09 8.33-0.84 12.21 0.82 3.61 1.8 7 4.16 11.01 5.05 2.08 3.61 6.12 5.46 8.19 9.07 3.6 5.67 7.09 11.66 8.28 18.36 1.61 9.51 7.07 17.72 12.69 25.35 3.43 7.74 1.97 16.49 3.6 24.62 2.23 5.11 4.09 10.39 6.76 15.31 1.16 2 4.38 0.63 4.77-1.32 1.2-7.1-2.39-13.94-1.97-21.03 0.38-3.64-0.91-7.48 0.25-10.99 2.74-3.74 4.57-8.05 7.47-11.67 3.55-5.47 10.31-8.34 16.73-7.64 2.26 2.89 5.13 5.21 7.58 7.92 2.88 4.3 6.52 8.01 9.83 11.97 1.89 2.61 3.06 5.64 4.48 8.52 2.81 4.9 4 10.5 6.63 15.49 2.16 6.04 5.56 11.92 5.37 18.5 0.65 1.95 0.78 4 0.98 6.03 1.01 3.95 2.84 8.55 0.63 12.42-2.4 5.23-7.03 8.97-11.55 12.33-6.06 4.66-11.62 10.05-18.37 13.75-4.06 2.65-8.24 5.17-12.71 7.08-3.59 1.57-6.06 4.94-9.85 6.09-2.29 1.71-3.98 4.51-6.97 5.02-4.56 1.35-8.98-3.72-13.5-1.25-2.99 1.83-6.19 3.21-9.39 4.6-8.5 5.61-18.13 9.48-28.06 11.62-8.36-0.2-16.69 0.62-25.05 0.47-3.5-1.87-7.67-1.08-11.22-2.83-6.19-1.52-10.93-6.01-16.62-8.61-2.87-1.39-5.53-3.16-8.11-4.99-2.58-1.88-4.17-4.85-6.98-6.44-3.83-0.11-6.54 3.42-10.24 3.92-2.31 0.28-4.64 0.32-6.96 0.31-3.5-3.65-5.69-8.74-10.59-10.77-5.01-3.68-10.57-6.67-14.84-11.25-2.52-2.55-5.22-4.87-8.24-6.8-4.73-4.07-7.93-9.51-11.41-14.62-3.08-4.41-5.22-9.73-4.6-15.19 0.65-8.01 0.62-16.18 2.55-24.02 4.06-10.46 11.15-19.34 18.05-28.06 3.71-5.31 9.91-10.21 16.8-8.39 3.25 1.61 5.74 4.56 7.14 7.89 1.19 2.7 3.49 4.93 3.87 7.96 0.97 5.85 1.6 11.86 0.74 17.77-1.7 6.12-2.98 12.53-2.32 18.9 0.01 2.92 2.9 5.36 5.78 4.57 3.06-0.68 3.99-4.07 5.32-6.48 1.67-4.06 4.18-7.66 6.69-11.23 3.61-5.28 5.09-11.57 7.63-17.37 2.07-4.56 1.7-9.64 2.56-14.46 0.78-7.65-0.62-15.44 0.7-23.04 1.32-3.78 1.79-7.89 3.8-11.4 3.01-3.66 6.78-6.63 9.85-10.26 1.72-2.12 4.21-3.32 6.55-4.6 7.89-2.71 15.56-6.75 24.06-7z"/>',
DEBUG = localStorage.ffzDebugMode == "true" && document.body.classList.contains('ffz-dev'); DEBUG = localStorage.ffzDebugMode == "true" && document.body.classList.contains('ffz-dev');
module.exports = { module.exports = {
DEBUG: DEBUG, DEBUG: DEBUG,
SERVER: DEBUG ? "//localhost:8000/" : "//cdn.frankerfacez.com/", SERVER: DEBUG ? "//localhost:8000/" : "//cdn.frankerfacez.com/",
SVGPATH: SVGPATH, SVGPATH: SVGPATH,
ZREKNARF: '<svg style="padding:1.75px 0" class="svg-glyph_views" width="16px" viewBox="0 0 249 195" version="1.1" height="12.5px">' + SVGPATH + '</svg>', ZREKNARF: '<svg style="padding:1.75px 0" class="svg-glyph_views" width="16px" viewBox="0 0 249 195" version="1.1" height="12.5px">' + SVGPATH + '</svg>',
CHAT_BUTTON: '<svg class="svg-emoticons ffz-svg" height="18px" width="24px" viewBox="0 0 249 195" version="1.1">' + SVGPATH + '</svg>' CHAT_BUTTON: '<svg class="svg-emoticons ffz-svg" height="18px" width="24px" viewBox="0 0 249 195" version="1.1">' + SVGPATH + '</svg>'
} }

View file

@ -1,22 +1,22 @@
var FFZ = window.FrankerFaceZ; var FFZ = window.FrankerFaceZ;
// ----------------------- // -----------------------
// Developer Mode Command // Developer Mode Command
// ----------------------- // -----------------------
FFZ.chat_commands.developer_mode = function(room, args) { FFZ.chat_commands.developer_mode = function(room, args) {
var enabled, args = args && args.length ? args[0].toLowerCase() : null; var enabled, args = args && args.length ? args[0].toLowerCase() : null;
if ( args == "y" || args == "yes" || args == "true" || args == "on" ) if ( args == "y" || args == "yes" || args == "true" || args == "on" )
enabled = true; enabled = true;
else if ( args == "n" || args == "no" || args == "false" || args == "off" ) else if ( args == "n" || args == "no" || args == "false" || args == "off" )
enabled = false; enabled = false;
if ( enabled === undefined ) if ( enabled === undefined )
return "Developer Mode is currently " + (localStorage.ffzDebugMode == "true" ? "enabled." : "disabled."); return "Developer Mode is currently " + (localStorage.ffzDebugMode == "true" ? "enabled." : "disabled.");
localStorage.ffzDebugMode = enabled; localStorage.ffzDebugMode = enabled;
return "Developer Mode is now " + (enabled ? "enabled" : "disabled") + ". Please refresh your browser."; 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.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.";

View file

@ -1,55 +1,55 @@
var FFZ = window.FrankerFaceZ; var FFZ = window.FrankerFaceZ;
// -------------------- // --------------------
// Initialization // Initialization
// -------------------- // --------------------
FFZ.prototype.setup_chatview = function() { FFZ.prototype.setup_chatview = function() {
this.log("Hooking the Ember Chat view."); this.log("Hooking the Ember Chat view.");
var Chat = App.__container__.resolve('view:chat'); var Chat = App.__container__.resolve('view:chat');
this._modify_cview(Chat); this._modify_cview(Chat);
// For some reason, this doesn't work unless we create an instance of the // For some reason, this doesn't work unless we create an instance of the
// chat view and then destroy it immediately. // chat view and then destroy it immediately.
Chat.create().destroy(); Chat.create().destroy();
// Modify all existing Chat views. // Modify all existing Chat views.
for(var key in Ember.View.views) { for(var key in Ember.View.views) {
if ( ! Ember.View.views.hasOwnProperty(key) ) if ( ! Ember.View.views.hasOwnProperty(key) )
continue; continue;
var view = Ember.View.views[key]; var view = Ember.View.views[key];
if ( !(view instanceof Chat) ) if ( !(view instanceof Chat) )
continue; continue;
this.log("Adding UI link manually to Chat view.", view); this.log("Adding UI link manually to Chat view.", view);
view.$('.textarea-contain').append(this.build_ui_link(view)); view.$('.textarea-contain').append(this.build_ui_link(view));
} }
} }
// -------------------- // --------------------
// Modify Chat View // Modify Chat View
// -------------------- // --------------------
FFZ.prototype._modify_cview = function(view) { FFZ.prototype._modify_cview = function(view) {
var f = this; var f = this;
view.reopen({ view.reopen({
didInsertElement: function() { didInsertElement: function() {
this._super(); this._super();
this.$() && this.$('.textarea-contain').append(f.build_ui_link(this)); this.$() && this.$('.textarea-contain').append(f.build_ui_link(this));
}, },
willClearRender: function() { willClearRender: function() {
this._super(); this._super();
this.$(".ffz-ui-toggle").remove(); this.$(".ffz-ui-toggle").remove();
}, },
ffzUpdateLink: Ember.observer('controller.currentRoom', function() { ffzUpdateLink: Ember.observer('controller.currentRoom', function() {
f.update_ui_link(); f.update_ui_link();
}) })
}); });
} }

View file

@ -1,167 +1,167 @@
var FFZ = window.FrankerFaceZ; var FFZ = window.FrankerFaceZ;
// --------------------- // ---------------------
// Initialization // Initialization
// --------------------- // ---------------------
FFZ.prototype.setup_line = function() { FFZ.prototype.setup_line = function() {
this.log("Hooking the Ember Line controller."); this.log("Hooking the Ember Line controller.");
var Line = App.__container__.resolve('controller:line'), var Line = App.__container__.resolve('controller:line'),
f = this; f = this;
Line.reopen({ Line.reopen({
tokenizedMessage: function() { tokenizedMessage: function() {
// Add our own step to the tokenization procedure. // Add our own step to the tokenization procedure.
return f._emoticonize(this, this._super()); return f._emoticonize(this, this._super());
}.property("model.message", "isModeratorOrHigher", "controllers.emoticons.emoticons.[]") }.property("model.message", "isModeratorOrHigher", "controllers.emoticons.emoticons.[]")
// TODO: Copy the new properties from the new Twitch! // TODO: Copy the new properties from the new Twitch!
}); });
this.log("Hooking the Ember Line view."); this.log("Hooking the Ember Line view.");
var Line = App.__container__.resolve('view:line'); var Line = App.__container__.resolve('view:line');
Line.reopen({ Line.reopen({
didInsertElement: function() { didInsertElement: function() {
this._super(); this._super();
var el = this.get('element'), var el = this.get('element'),
user = this.get('context.model.from'); user = this.get('context.model.from');
el.setAttribute('data-room', this.get('context.parentController.content.id')); el.setAttribute('data-room', this.get('context.parentController.content.id'));
el.setAttribute('data-sender', user); el.setAttribute('data-sender', user);
f.render_badge(this); f.render_badge(this);
if ( localStorage['ffzCapitalize'] != 'false' ) if ( localStorage['ffzCapitalize'] != 'false' )
f.capitalize(this, user); f.capitalize(this, user);
} }
}); });
// Store the capitalization of our own name. // Store the capitalization of our own name.
var user = this.get_user(); var user = this.get_user();
if ( user && user.name ) if ( user && user.name )
FFZ.capitalization[user.login] = [user.name, Date.now()]; FFZ.capitalization[user.login] = [user.name, Date.now()];
} }
// --------------------- // ---------------------
// Capitalization // Capitalization
// --------------------- // ---------------------
FFZ.capitalization = {}; FFZ.capitalization = {};
FFZ._cap_fetching = 0; FFZ._cap_fetching = 0;
FFZ.get_capitalization = function(name, callback) { FFZ.get_capitalization = function(name, callback) {
name = name.toLowerCase(); name = name.toLowerCase();
if ( name == "jtv" || name == "twitchnotify" ) if ( name == "jtv" || name == "twitchnotify" )
return name; return name;
var old_data = FFZ.capitalization[name]; var old_data = FFZ.capitalization[name];
if ( old_data ) { if ( old_data ) {
if ( Date.now() - old_data[1] < 3600000 ) if ( Date.now() - old_data[1] < 3600000 )
return old_data[0]; return old_data[0];
} }
if ( FFZ._cap_fetching < 5 ) { if ( FFZ._cap_fetching < 5 ) {
FFZ._cap_fetching++; FFZ._cap_fetching++;
Twitch.api.get("users/" + name) Twitch.api.get("users/" + name)
.always(function(data) { .always(function(data) {
var cap_name = data.display_name || name; var cap_name = data.display_name || name;
FFZ.capitalization[name] = [cap_name, Date.now()]; FFZ.capitalization[name] = [cap_name, Date.now()];
FFZ._cap_fetching--; FFZ._cap_fetching--;
callback && callback(cap_name); callback && callback(cap_name);
}); });
} }
return old_data ? old_data[0] : name; return old_data ? old_data[0] : name;
} }
FFZ.prototype.capitalize = function(view, user) { FFZ.prototype.capitalize = function(view, user) {
var name = FFZ.get_capitalization(user, this.capitalize.bind(this, view)); var name = FFZ.get_capitalization(user, this.capitalize.bind(this, view));
if ( name ) if ( name )
view.$('.from').text(name); view.$('.from').text(name);
} }
FFZ.chat_commands.capitalization = function(room, args) { FFZ.chat_commands.capitalization = function(room, args) {
var enabled, args = args && args.length ? args[0].toLowerCase() : null; var enabled, args = args && args.length ? args[0].toLowerCase() : null;
if ( args == "y" || args == "yes" || args == "true" || args == "on" ) if ( args == "y" || args == "yes" || args == "true" || args == "on" )
enabled = true; enabled = true;
else if ( args == "n" || args == "no" || args == "false" || args == "off" ) else if ( args == "n" || args == "no" || args == "false" || args == "off" )
enabled = false; enabled = false;
if ( enabled === undefined ) if ( enabled === undefined )
return "Chat Name Capitalization is currently " + (localStorage.ffzCapitalize != "false" ? "enabled." : "disabled."); return "Chat Name Capitalization is currently " + (localStorage.ffzCapitalize != "false" ? "enabled." : "disabled.");
localStorage.ffzCapitalize = enabled; localStorage.ffzCapitalize = enabled;
return "Chat Name Capitalization is now " + (enabled ? "enabled." : "disabled."); 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."; FFZ.chat_commands.capitalization.help = "Usage: /ffz capitalization <on|off>\nEnable or disable Chat Name Capitalization. This setting does not work with BetterTTV.";
// --------------------- // ---------------------
// Emoticon Replacement // Emoticon Replacement
// --------------------- // ---------------------
FFZ.prototype._emoticonize = function(controller, tokens) { FFZ.prototype._emoticonize = function(controller, tokens) {
var room_id = controller.get("parentController.model.id"), var room_id = controller.get("parentController.model.id"),
user_id = controller.get("model.from"), user_id = controller.get("model.from"),
f = this; f = this;
// Get our sets. // Get our sets.
var sets = this.getEmotes(user_id, room_id), var sets = this.getEmotes(user_id, room_id),
emotes = []; emotes = [];
// Build a list of emotes that match. // Build a list of emotes that match.
_.each(sets, function(set_id) { _.each(sets, function(set_id) {
var set = f.emote_sets[set_id]; var set = f.emote_sets[set_id];
if ( ! set ) if ( ! set )
return; return;
_.each(set.emotes, function(emote) { _.each(set.emotes, function(emote) {
_.any(tokens, function(token) { _.any(tokens, function(token) {
return _.isString(token) && token.match(emote.regex); return _.isString(token) && token.match(emote.regex);
}) && emotes.push(emote); }) && emotes.push(emote);
}); });
}); });
// Don't bother proceeding if we have no emotes. // Don't bother proceeding if we have no emotes.
if ( ! emotes.length ) if ( ! emotes.length )
return tokens; return tokens;
// Now that we have all the matching tokens, do crazy stuff. // Now that we have all the matching tokens, do crazy stuff.
if ( typeof tokens == "string" ) if ( typeof tokens == "string" )
tokens = [tokens]; tokens = [tokens];
// This is weird stuff I basically copied from the old Twitch code. // This is weird stuff I basically copied from the old Twitch code.
// Here, for each emote, we split apart every text token and we // Here, for each emote, we split apart every text token and we
// put it back together with the matching bits of text replaced // put it back together with the matching bits of text replaced
// with an object telling Twitch's line template how to render the // with an object telling Twitch's line template how to render the
// emoticon. // emoticon.
_.each(emotes, function(emote) { _.each(emotes, function(emote) {
//var eo = {isEmoticon:true, cls: emote.klass}; //var eo = {isEmoticon:true, cls: emote.klass};
var eo = {isEmoticon:true, cls: emote.klass, emoticonSrc: emote.url, altText: (emote.hidden ? "???" : emote.name)}; var eo = {isEmoticon:true, cls: emote.klass, emoticonSrc: emote.url, altText: (emote.hidden ? "???" : emote.name)};
tokens = _.compact(_.flatten(_.map(tokens, function(token) { tokens = _.compact(_.flatten(_.map(tokens, function(token) {
if ( _.isObject(token) ) if ( _.isObject(token) )
return token; return token;
var tbits = token.split(emote.regex), bits = []; var tbits = token.split(emote.regex), bits = [];
tbits.forEach(function(val, ind) { tbits.forEach(function(val, ind) {
bits.push(val); bits.push(val);
if ( ind !== tbits.length - 1 ) if ( ind !== tbits.length - 1 )
bits.push(eo); bits.push(eo);
}); });
return bits; return bits;
}))); })));
}); });
return tokens; return tokens;
} }

View file

@ -1,281 +1,281 @@
var FFZ = window.FrankerFaceZ, var FFZ = window.FrankerFaceZ,
CSS = /\.([\w\-_]+)\s*?\{content:\s*?"([^"]+)";\s*?background-image:\s*?url\("([^"]+)"\);\s*?height:\s*?(\d+)px;\s*?width:\s*?(\d+)px;\s*?margin:([^;}]+);?([^}]*)\}/mg, CSS = /\.([\w\-_]+)\s*?\{content:\s*?"([^"]+)";\s*?background-image:\s*?url\("([^"]+)"\);\s*?height:\s*?(\d+)px;\s*?width:\s*?(\d+)px;\s*?margin:([^;}]+);?([^}]*)\}/mg,
MOD_CSS = /[^\n}]*\.badges\s+\.moderator\s*{\s*background-image:\s*url\(\s*['"]([^'"]+)['"][^}]+(?:}|$)/, MOD_CSS = /[^\n}]*\.badges\s+\.moderator\s*{\s*background-image:\s*url\(\s*['"]([^'"]+)['"][^}]+(?:}|$)/,
GROUP_CHAT = /^_([^_]+)_\d+$/, GROUP_CHAT = /^_([^_]+)_\d+$/,
constants = require('../constants'), constants = require('../constants'),
utils = require('../utils'), utils = require('../utils'),
moderator_css = function(room) { moderator_css = function(room) {
if ( ! room.moderator_badge ) if ( ! room.moderator_badge )
return ""; return "";
return '.chat-line[data-room="' + room.id + '"] .badges .moderator { background-image:url("' + room.moderator_badge + '") !important; }'; return '.chat-line[data-room="' + room.id + '"] .badges .moderator { background-image:url("' + room.moderator_badge + '") !important; }';
} }
// -------------------- // --------------------
// Initialization // Initialization
// -------------------- // --------------------
FFZ.prototype.setup_room = function() { FFZ.prototype.setup_room = function() {
this.rooms = {}; this.rooms = {};
this.log("Creating room style element."); this.log("Creating room style element.");
var s = this._room_style = document.createElement("style"); var s = this._room_style = document.createElement("style");
s.id = "ffz-room-css"; s.id = "ffz-room-css";
document.head.appendChild(s); document.head.appendChild(s);
this.log("Hooking the Ember Room model."); this.log("Hooking the Ember Room model.");
var Room = App.__container__.resolve('model:room'); var Room = App.__container__.resolve('model:room');
this._modify_room(Room); this._modify_room(Room);
// Modify all current instances of Room, as the changes to the base // Modify all current instances of Room, as the changes to the base
// class won't be inherited automatically. // class won't be inherited automatically.
var instances = Room.instances; var instances = Room.instances;
for(var key in instances) { for(var key in instances) {
if ( ! instances.hasOwnProperty(key) ) if ( ! instances.hasOwnProperty(key) )
continue; continue;
var inst = instances[key]; var inst = instances[key];
this.add_room(inst.id, inst); this.add_room(inst.id, inst);
this._modify_room(inst); this._modify_room(inst);
} }
} }
// -------------------- // --------------------
// Command System // Command System
// -------------------- // --------------------
FFZ.chat_commands = {}; FFZ.chat_commands = {};
FFZ.prototype.room_message = function(room, text) { FFZ.prototype.room_message = function(room, text) {
var lines = text.split("\n"); var lines = text.split("\n");
if ( this.has_bttv ) { if ( this.has_bttv ) {
for(var i=0; i < lines.length; i++) for(var i=0; i < lines.length; i++)
BetterTTV.chat.handlers.onPrivmsg(room.id, {style: 'admin', date: new Date(), from: 'jtv', message: lines[i]}); BetterTTV.chat.handlers.onPrivmsg(room.id, {style: 'admin', date: new Date(), from: 'jtv', message: lines[i]});
} else { } else {
for(var i=0; i < lines.length; i++) for(var i=0; i < lines.length; i++)
room.room.addMessage({style: 'ffz admin', date: new Date(), from: 'FFZ', message: lines[i]}); room.room.addMessage({style: 'ffz admin', date: new Date(), from: 'FFZ', message: lines[i]});
} }
} }
FFZ.prototype.run_command = function(text, room_id) { FFZ.prototype.run_command = function(text, room_id) {
var room = this.rooms[room_id]; var room = this.rooms[room_id];
if ( ! room || !room.room ) if ( ! room || !room.room )
return; return;
if ( ! text ) { if ( ! text ) {
// Try to pop-up the menu. // Try to pop-up the menu.
var link = document.querySelector('a.ffz-ui-toggle'); var link = document.querySelector('a.ffz-ui-toggle');
if ( link ) if ( link )
return link.click(); return link.click();
text = "help"; text = "help";
} }
var args = text.split(" "), var args = text.split(" "),
cmd = args.shift().toLowerCase(); cmd = args.shift().toLowerCase();
this.log("Received Command: " + cmd, args, true); this.log("Received Command: " + cmd, args, true);
var command = FFZ.chat_commands[cmd], output; var command = FFZ.chat_commands[cmd], output;
if ( command ) { if ( command ) {
try { try {
output = command.bind(this)(room, args); output = command.bind(this)(room, args);
} catch(err) { } catch(err) {
this.log("Error Running Command - " + cmd + ": " + err, room); this.log("Error Running Command - " + cmd + ": " + err, room);
output = "There was an error running the command."; output = "There was an error running the command.";
} }
} else } else
output = 'There is no "' + cmd + '" command.'; output = 'There is no "' + cmd + '" command.';
if ( output ) if ( output )
this.room_message(room, output); this.room_message(room, output);
} }
FFZ.chat_commands.help = function(room, args) { FFZ.chat_commands.help = function(room, args) {
if ( args && args.length ) { if ( args && args.length ) {
var command = FFZ.chat_commands[args[0].toLowerCase()]; var command = FFZ.chat_commands[args[0].toLowerCase()];
if ( ! command ) if ( ! command )
return 'There is no "' + args[0] + '" command.'; return 'There is no "' + args[0] + '" command.';
else if ( ! command.help ) else if ( ! command.help )
return 'No help is available for the command "' + args[0] + '".'; return 'No help is available for the command "' + args[0] + '".';
else else
return command.help; return command.help;
} }
var cmds = []; var cmds = [];
for(var c in FFZ.chat_commands) for(var c in FFZ.chat_commands)
FFZ.chat_commands.hasOwnProperty(c) && cmds.push(c); FFZ.chat_commands.hasOwnProperty(c) && cmds.push(c);
return "The available commands are: " + cmds.join(", "); 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.chat_commands.help.help = "Usage: /ffz help [command]\nList available commands, or show help for a specific command.";
// -------------------- // --------------------
// Room Management // Room Management
// -------------------- // --------------------
FFZ.prototype.add_room = function(id, room) { FFZ.prototype.add_room = function(id, room) {
if ( this.rooms[id] ) if ( this.rooms[id] )
return this.log("Tried to add existing room: " + id); return this.log("Tried to add existing room: " + id);
this.log("Adding Room: " + id); this.log("Adding Room: " + id);
// Create a basic data table for this room. // Create a basic data table for this room.
this.rooms[id] = {id: id, room: room, menu_sets: [], sets: [], css: null}; this.rooms[id] = {id: id, room: room, menu_sets: [], sets: [], css: null};
// Let the server know where we are. // Let the server know where we are.
this.ws_send("sub", id); this.ws_send("sub", id);
// For now, we use the legacy function to grab the .css file. // For now, we use the legacy function to grab the .css file.
this._legacy_add_room(id); this._legacy_add_room(id);
} }
FFZ.prototype.remove_room = function(id) { FFZ.prototype.remove_room = function(id) {
var room = this.rooms[id]; var room = this.rooms[id];
if ( ! room ) if ( ! room )
return; return;
this.log("Removing Room: " + id); this.log("Removing Room: " + id);
// Remove the CSS // Remove the CSS
if ( room.css || room.moderator_badge ) if ( room.css || room.moderator_badge )
utils.update_css(this._room_style, id, null); utils.update_css(this._room_style, id, null);
// Let the server know we're gone and delete our data for this room. // Let the server know we're gone and delete our data for this room.
this.ws_send("unsub", id); this.ws_send("unsub", id);
delete this.rooms[id]; delete this.rooms[id];
// Clean up sets we aren't using any longer. // Clean up sets we aren't using any longer.
for(var i=0; i < room.sets.length; i++) { for(var i=0; i < room.sets.length; i++) {
var set_id = room.sets[i], set = this.emote_sets[set_id]; var set_id = room.sets[i], set = this.emote_sets[set_id];
if ( ! set ) if ( ! set )
continue; continue;
set.users.removeObject(id); set.users.removeObject(id);
if ( !set.global && !set.users.length ) if ( !set.global && !set.users.length )
this.unload_set(set_id); this.unload_set(set_id);
} }
} }
// -------------------- // --------------------
// Receiving Set Info // Receiving Set Info
// -------------------- // --------------------
FFZ.prototype.load_room = function(room_id, callback) { FFZ.prototype.load_room = function(room_id, callback) {
return this._legacy_load_room(room_id, callback); return this._legacy_load_room(room_id, callback);
} }
FFZ.prototype._load_room_json = function(room_id, callback, data) { FFZ.prototype._load_room_json = function(room_id, callback, data) {
// Preserve the pointer to the Room instance. // Preserve the pointer to the Room instance.
if ( this.rooms[room_id] ) if ( this.rooms[room_id] )
data.room = this.rooms[room_id].room; data.room = this.rooms[room_id].room;
this.rooms[room_id] = data; this.rooms[room_id] = data;
if ( data.css || data.moderator_badge ) if ( data.css || data.moderator_badge )
utils.update_css(this._room_style, room_id, moderator_css(data) + (data.css||"")); utils.update_css(this._room_style, room_id, moderator_css(data) + (data.css||""));
for(var i=0; i < data.sets.length; i++) { for(var i=0; i < data.sets.length; i++) {
var set_id = data.sets[i]; var set_id = data.sets[i];
if ( ! this.emote_sets.hasOwnProperty(set_id) ) if ( ! this.emote_sets.hasOwnProperty(set_id) )
this.load_set(set_id); this.load_set(set_id);
} }
this.update_ui_link(); this.update_ui_link();
if ( callback ) if ( callback )
callback(true, data); callback(true, data);
} }
// -------------------- // --------------------
// Ember Modifications // Ember Modifications
// -------------------- // --------------------
FFZ.prototype._modify_room = function(room) { FFZ.prototype._modify_room = function(room) {
var f = this; var f = this;
room.reopen({ room.reopen({
init: function() { init: function() {
this._super(); this._super();
f.add_room(this.id, this); f.add_room(this.id, this);
}, },
willDestroy: function() { willDestroy: function() {
this._super(); this._super();
f.remove_room(this.id); f.remove_room(this.id);
}, },
send: function(text) { send: function(text) {
var cmd = text.split(' ', 1)[0].toLowerCase(); var cmd = text.split(' ', 1)[0].toLowerCase();
if ( cmd === "/ffz" ) { if ( cmd === "/ffz" ) {
this.set("messageToSend", ""); this.set("messageToSend", "");
f.run_command(text.substr(5), this.get('id')); f.run_command(text.substr(5), this.get('id'));
} else } else
return this._super(text); return this._super(text);
} }
}); });
} }
// -------------------- // --------------------
// Legacy Data Support // Legacy Data Support
// -------------------- // --------------------
FFZ.prototype._legacy_add_room = function(room_id, callback, tries) { FFZ.prototype._legacy_add_room = function(room_id, callback, tries) {
jQuery.ajax(constants.SERVER + "channel/" + room_id + ".css", {cache: false, context:this}) jQuery.ajax(constants.SERVER + "channel/" + room_id + ".css", {cache: false, context:this})
.done(function(data) { .done(function(data) {
this._legacy_load_room_css(room_id, callback, data); this._legacy_load_room_css(room_id, callback, data);
}).fail(function(data) { }).fail(function(data) {
if ( data.status == 404 ) if ( data.status == 404 )
return this._legacy_load_room_css(room_id, callback, null); return this._legacy_load_room_css(room_id, callback, null);
tries = tries || 0; tries = tries || 0;
tries++; tries++;
if ( tries < 10 ) if ( tries < 10 )
return this._legacy_add_room(room_id, callback, tries); return this._legacy_add_room(room_id, callback, tries);
}); });
} }
FFZ.prototype._legacy_load_room_css = function(room_id, callback, data) { FFZ.prototype._legacy_load_room_css = function(room_id, callback, data) {
var set_id = room_id, var set_id = room_id,
match = set_id.match(GROUP_CHAT); match = set_id.match(GROUP_CHAT);
if ( match && match[1] ) if ( match && match[1] )
set_id = match[1]; set_id = match[1];
var output = {id: room_id, menu_sets: [set_id], sets: [set_id], moderator_badge: null, css: null}; var output = {id: room_id, menu_sets: [set_id], sets: [set_id], moderator_badge: null, css: null};
if ( data ) if ( data )
data = data.replace(CSS, "").trim(); data = data.replace(CSS, "").trim();
if ( data ) { if ( data ) {
data = data.replace(MOD_CSS, function(match, url) { data = data.replace(MOD_CSS, function(match, url) {
if ( output.moderator_badge || url.substr(-11) !== 'modicon.png' ) if ( output.moderator_badge || url.substr(-11) !== 'modicon.png' )
return match; return match;
output.moderator_badge = url; output.moderator_badge = url;
return ""; return "";
}); });
} }
output.css = data || null; output.css = data || null;
return this._load_room_json(room_id, callback, output); return this._load_room_json(room_id, callback, output);
} }

View file

@ -1,197 +1,197 @@
var FFZ = window.FrankerFaceZ, var FFZ = window.FrankerFaceZ,
CSS = /\.([\w\-_]+)\s*?\{content:\s*?"([^"]+)";\s*?background-image:\s*?url\("([^"]+)"\);\s*?height:\s*?(\d+)px;\s*?width:\s*?(\d+)px;\s*?margin:([^;}]+);?([^}]*)\}/mg, CSS = /\.([\w\-_]+)\s*?\{content:\s*?"([^"]+)";\s*?background-image:\s*?url\("([^"]+)"\);\s*?height:\s*?(\d+)px;\s*?width:\s*?(\d+)px;\s*?margin:([^;}]+);?([^}]*)\}/mg,
MOD_CSS = /[^\n}]*\.badges\s+\.moderator\s*{\s*background-image:\s*url\(\s*['"]([^'"]+)['"][^}]+(?:}|$)/, MOD_CSS = /[^\n}]*\.badges\s+\.moderator\s*{\s*background-image:\s*url\(\s*['"]([^'"]+)['"][^}]+(?:}|$)/,
constants = require('./constants'), constants = require('./constants'),
utils = require('./utils'), utils = require('./utils'),
loaded_global = function(set_id, success, data) { loaded_global = function(set_id, success, data) {
if ( ! success ) if ( ! success )
return; return;
data.global = true; data.global = true;
this.global_sets.push(set_id); this.global_sets.push(set_id);
}, },
check_margins = function(margins, height) { check_margins = function(margins, height) {
var mlist = margins.split(/ +/); var mlist = margins.split(/ +/);
if ( mlist.length != 2 ) if ( mlist.length != 2 )
return margins; return margins;
mlist[0] = parseFloat(mlist[0]); mlist[0] = parseFloat(mlist[0]);
mlist[1] = parseFloat(mlist[1]); mlist[1] = parseFloat(mlist[1]);
if ( mlist[0] == (height - 18) / -2 && mlist[1] == 0 ) if ( mlist[0] == (height - 18) / -2 && mlist[1] == 0 )
return null; return null;
return margins; return margins;
}, },
build_legacy_css = function(emote) { build_legacy_css = function(emote) {
var margin = emote.margins; var margin = emote.margins;
if ( ! margin ) if ( ! margin )
margin = ((emote.height - 18) / -2) + "px 0"; margin = ((emote.height - 18) / -2) + "px 0";
return ".ffz-emote-" + emote.id + ' { background-image: url("' + emote.url + '"); height: ' + emote.height + "px; width: " + emote.width + "px; margin: " + margin + (emote.extra_css ? "; " + emote.extra_css : "") + "}\n"; return ".ffz-emote-" + emote.id + ' { background-image: url("' + emote.url + '"); height: ' + emote.height + "px; width: " + emote.width + "px; margin: " + margin + (emote.extra_css ? "; " + emote.extra_css : "") + "}\n";
}, },
build_new_css = function(emote) { build_new_css = function(emote) {
if ( ! emote.margins && ! emote.extra_css ) if ( ! emote.margins && ! emote.extra_css )
return build_legacy_css(emote); return build_legacy_css(emote);
return build_legacy_css(emote) + 'img[src="' + emote.url + '"] { ' + (emote.margins ? "margin: " + emote.margins + ";" : "") + (emote.extra_css || "") + " }\n"; return build_legacy_css(emote) + 'img[src="' + emote.url + '"] { ' + (emote.margins ? "margin: " + emote.margins + ";" : "") + (emote.extra_css || "") + " }\n";
}, },
build_css = build_new_css; build_css = build_new_css;
// --------------------- // ---------------------
// Initialization // Initialization
// --------------------- // ---------------------
FFZ.prototype.setup_emoticons = function() { FFZ.prototype.setup_emoticons = function() {
this.log("Preparing emoticon system."); this.log("Preparing emoticon system.");
this.emote_sets = {}; this.emote_sets = {};
this.global_sets = []; this.global_sets = [];
this._last_emote_id = 0; this._last_emote_id = 0;
this.log("Creating emoticon style element."); this.log("Creating emoticon style element.");
var s = this._emote_style = document.createElement('style'); var s = this._emote_style = document.createElement('style');
s.id = "ffz-emoticon-css"; s.id = "ffz-emoticon-css";
document.head.appendChild(s); document.head.appendChild(s);
this.log("Loading global emote set."); this.log("Loading global emote set.");
this.load_set("global", loaded_global.bind(this, "global")); this.load_set("global", loaded_global.bind(this, "global"));
} }
// --------------------- // ---------------------
// Set Management // Set Management
// --------------------- // ---------------------
FFZ.prototype.getEmotes = function(user_id, room_id) { FFZ.prototype.getEmotes = function(user_id, room_id) {
var user = this.users[user_id], var user = this.users[user_id],
room = this.rooms[room_id]; room = this.rooms[room_id];
return _.union(user && user.sets || [], room && room.sets || [], this.global_sets); return _.union(user && user.sets || [], room && room.sets || [], this.global_sets);
} }
// --------------------- // ---------------------
// Commands // Commands
// --------------------- // ---------------------
FFZ.ws_commands.reload_set = function(set_id) { FFZ.ws_commands.reload_set = function(set_id) {
this.load_set(set_id); this.load_set(set_id);
} }
// --------------------- // ---------------------
// Set Loading // Set Loading
// --------------------- // ---------------------
FFZ.prototype.load_set = function(set_id, callback) { FFZ.prototype.load_set = function(set_id, callback) {
return this._legacy_load_set(set_id, callback); return this._legacy_load_set(set_id, callback);
} }
FFZ.prototype.unload_set = function(set_id) { FFZ.prototype.unload_set = function(set_id) {
var set = this.emote_sets[set_id]; var set = this.emote_sets[set_id];
if ( ! set ) if ( ! set )
return; return;
this.log("Unloading emoticons for set: " + set_id); this.log("Unloading emoticons for set: " + set_id);
utils.update_css(this._emote_style, set_id, null); utils.update_css(this._emote_style, set_id, null);
delete this.emote_sets[set_id]; delete this.emote_sets[set_id];
for(var i=0; i < set.users.length; i++) { for(var i=0; i < set.users.length; i++) {
var room = this.rooms[set.users[i]]; var room = this.rooms[set.users[i]];
if ( room ) if ( room )
room.sets.removeObject(set_id); room.sets.removeObject(set_id);
} }
} }
FFZ.prototype._load_set_json = function(set_id, callback, data) { FFZ.prototype._load_set_json = function(set_id, callback, data) {
// Store our set. // Store our set.
this.emote_sets[set_id] = data; this.emote_sets[set_id] = data;
data.users = []; data.users = [];
data.global = false; data.global = false;
data.count = 0; data.count = 0;
// Iterate through all the emoticons, building CSS and regex objects as appropriate. // Iterate through all the emoticons, building CSS and regex objects as appropriate.
var output_css = ""; var output_css = "";
for(var key in data.emotes) { for(var key in data.emotes) {
if ( ! data.emotes.hasOwnProperty(key) ) if ( ! data.emotes.hasOwnProperty(key) )
continue; continue;
var emote = data.emotes[key]; var emote = data.emotes[key];
emote.klass = "ffz-emote-" + emote.id; emote.klass = "ffz-emote-" + emote.id;
if ( emote.name[emote.name.length-1] === "!" ) if ( emote.name[emote.name.length-1] === "!" )
emote.regex = new RegExp("\\b" + emote.name + "(?=\\W|$)", "g"); emote.regex = new RegExp("\\b" + emote.name + "(?=\\W|$)", "g");
else else
emote.regex = new RegExp("\\b" + emote.name + "\\b", "g"); emote.regex = new RegExp("\\b" + emote.name + "\\b", "g");
output_css += build_css(emote); output_css += build_css(emote);
data.count++; data.count++;
} }
utils.update_css(this._emote_style, set_id, output_css + (data.extra_css || "")); utils.update_css(this._emote_style, set_id, output_css + (data.extra_css || ""));
this.log("Updated emoticons for set: " + set_id, data); this.log("Updated emoticons for set: " + set_id, data);
this.update_ui_link(); this.update_ui_link();
if ( callback ) if ( callback )
callback(true, data); callback(true, data);
} }
FFZ.prototype._legacy_load_set = function(set_id, callback, tries) { FFZ.prototype._legacy_load_set = function(set_id, callback, tries) {
jQuery.ajax(constants.SERVER + "channel/" + set_id + ".css", {cache: false, context:this}) jQuery.ajax(constants.SERVER + "channel/" + set_id + ".css", {cache: false, context:this})
.done(function(data) { .done(function(data) {
this._legacy_load_css(set_id, callback, data); this._legacy_load_css(set_id, callback, data);
}).fail(function(data) { }).fail(function(data) {
if ( data.status == 404 ) if ( data.status == 404 )
return callback && callback(false); return callback && callback(false);
tries = tries || 0; tries = tries || 0;
tries++; tries++;
if ( tries < 10 ) if ( tries < 10 )
return this._legacy_load_set(set_id, callback, tries); return this._legacy_load_set(set_id, callback, tries);
return callback && callback(false); return callback && callback(false);
}); });
} }
FFZ.prototype._legacy_load_css = function(set_id, callback, data) { FFZ.prototype._legacy_load_css = function(set_id, callback, data) {
var emotes = {}, output = {id: set_id, emotes: emotes, extra_css: null}, f = this; var emotes = {}, output = {id: set_id, emotes: emotes, extra_css: null}, f = this;
data = data.replace(CSS, function(match, klass, name, path, height, width, margins, extra) { data = data.replace(CSS, function(match, klass, name, path, height, width, margins, extra) {
height = parseInt(height); width = parseInt(width); height = parseInt(height); width = parseInt(width);
margins = check_margins(margins, height); margins = check_margins(margins, height);
var hidden = path.substr(path.lastIndexOf("/") + 1, 1) === ".", var hidden = path.substr(path.lastIndexOf("/") + 1, 1) === ".",
id = ++f._last_emote_id, id = ++f._last_emote_id,
emote = {id: id, hidden: hidden, name: name, height: height, width: width, url: path, margins: margins, extra_css: extra}; emote = {id: id, hidden: hidden, name: name, height: height, width: width, url: path, margins: margins, extra_css: extra};
emotes[id] = emote; emotes[id] = emote;
return ""; return "";
}).trim(); }).trim();
if ( data ) if ( data )
data.replace(MOD_CSS, function(match, url) { data.replace(MOD_CSS, function(match, url) {
if ( output.icon || url.substr(-11) !== 'modicon.png' ) if ( output.icon || url.substr(-11) !== 'modicon.png' )
return; return;
output.icon = url; output.icon = url;
}); });
this._load_set_json(set_id, callback, output); this._load_set_json(set_id, callback, output);
} }

View file

@ -1,161 +1,161 @@
// Modify Array and others. // Modify Array and others.
require('./shims'); require('./shims');
// ---------------- // ----------------
// The Constructor // The Constructor
// ---------------- // ----------------
var FFZ = window.FrankerFaceZ = function() { var FFZ = window.FrankerFaceZ = function() {
FFZ.instance = this; FFZ.instance = this;
// Get things started. // Get things started.
this.initialize(); this.initialize();
} }
FFZ.get = function() { return FFZ.instance; } FFZ.get = function() { return FFZ.instance; }
// Version // Version
var VER = FFZ.version_info = { var VER = FFZ.version_info = {
major: 3, minor: 0, revision: 0, major: 3, minor: 0, revision: 0,
toString: function() { toString: function() {
return [VER.major, VER.minor, VER.revision].join(".") + (VER.extra || ""); return [VER.major, VER.minor, VER.revision].join(".") + (VER.extra || "");
} }
} }
// Logging // Logging
FFZ.prototype.log = function(msg, data, to_json) { FFZ.prototype.log = function(msg, data, to_json) {
msg = "FFZ: " + msg + (to_json ? " -- " + JSON.stringify(data) : ""); msg = "FFZ: " + msg + (to_json ? " -- " + JSON.stringify(data) : "");
if ( data !== undefined && console.groupCollapsed && console.dir ) { if ( data !== undefined && console.groupCollapsed && console.dir ) {
console.groupCollapsed(msg); console.groupCollapsed(msg);
if ( navigator.userAgent.indexOf("Firefox/") !== -1 ) if ( navigator.userAgent.indexOf("Firefox/") !== -1 )
console.log(data); console.log(data);
else else
console.dir(data); console.dir(data);
console.groupEnd(msg); console.groupEnd(msg);
} else } else
console.log(msg); console.log(msg);
} }
// ------------------- // -------------------
// User Data // User Data
// ------------------- // -------------------
FFZ.prototype.get_user = function() { FFZ.prototype.get_user = function() {
if ( window.PP && PP.login ) { if ( window.PP && PP.login ) {
return PP; return PP;
} else if ( window.App ) { } else if ( window.App ) {
var nc = App.__container__.lookup("controller:navigation"); var nc = App.__container__.lookup("controller:navigation");
return nc ? nc.get("userData") : undefined; return nc ? nc.get("userData") : undefined;
} }
} }
// ------------------- // -------------------
// Import Everything! // Import Everything!
// ------------------- // -------------------
require('./socket'); require('./socket');
require('./emoticons'); require('./emoticons');
require('./badges'); require('./badges');
require('./ember/router'); require('./ember/router');
require('./ember/room'); require('./ember/room');
require('./ember/line'); require('./ember/line');
require('./ember/chatview'); require('./ember/chatview');
require('./ember/viewers'); require('./ember/viewers');
//require('./ember/teams'); //require('./ember/teams');
require('./tracking'); require('./tracking');
require('./debug'); require('./debug');
require('./ext/betterttv'); require('./ext/betterttv');
require('./ext/emote_menu'); require('./ext/emote_menu');
require('./featurefriday'); require('./featurefriday');
require('./ui/styles'); require('./ui/styles');
require('./ui/notifications'); require('./ui/notifications');
require('./ui/viewer_count'); require('./ui/viewer_count');
require('./ui/menu_button'); require('./ui/menu_button');
require('./ui/menu'); require('./ui/menu');
require('./commands'); require('./commands');
// --------------- // ---------------
// Initialization // Initialization
// --------------- // ---------------
FFZ.prototype.initialize = function(increment, delay) { FFZ.prototype.initialize = function(increment, delay) {
// Make sure that FrankerFaceZ doesn't start setting itself up until the // Make sure that FrankerFaceZ doesn't start setting itself up until the
// Twitch ember application is ready. // Twitch ember application is ready.
// TODO: Special Dashboard check. // TODO: Special Dashboard check.
var loaded = window.App != undefined && var loaded = window.App != undefined &&
App.__container__ != undefined && App.__container__ != undefined &&
App.__container__.resolve('model:room') != undefined; App.__container__.resolve('model:room') != undefined;
if ( !loaded ) { if ( !loaded ) {
increment = increment || 10; increment = increment || 10;
if ( delay >= 60000 ) if ( delay >= 60000 )
this.log("Twitch application not detected in \"" + location.toString() + "\". Aborting."); this.log("Twitch application not detected in \"" + location.toString() + "\". Aborting.");
else else
setTimeout(this.initialize.bind(this, increment, (delay||0) + increment), setTimeout(this.initialize.bind(this, increment, (delay||0) + increment),
increment); increment);
return; return;
} }
this.setup_ember(delay); this.setup_ember(delay);
} }
FFZ.prototype.setup_ember = function(delay) { FFZ.prototype.setup_ember = function(delay) {
var start = (window.performance && performance.now) ? performance.now() : Date.now(); var start = (window.performance && performance.now) ? performance.now() : Date.now();
this.log("Found Twitch application after " + (delay||0) + " ms in \"" + location + "\". Initializing FrankerFaceZ version " + FFZ.version_info); this.log("Found Twitch application after " + (delay||0) + " ms in \"" + location + "\". Initializing FrankerFaceZ version " + FFZ.version_info);
this.users = {}; this.users = {};
// Cleanup localStorage // Cleanup localStorage
for(var key in localStorage) { for(var key in localStorage) {
if ( key.substr(0,4) == "ffz_" ) if ( key.substr(0,4) == "ffz_" )
localStorage.removeItem(key); localStorage.removeItem(key);
} }
// Initialize all the modules. // Initialize all the modules.
this.ws_create(); this.ws_create();
this.setup_emoticons(); this.setup_emoticons();
this.setup_badges(); this.setup_badges();
this.setup_piwik(); this.setup_piwik();
this.setup_router(); this.setup_router();
this.setup_room(); this.setup_room();
this.setup_line(); this.setup_line();
this.setup_chatview(); this.setup_chatview();
this.setup_viewers(); this.setup_viewers();
//this.setup_teams(); //this.setup_teams();
this.setup_css(); this.setup_css();
this.setup_menu(); this.setup_menu();
this.find_bttv(10); this.find_bttv(10);
this.find_emote_menu(10); this.find_emote_menu(10);
this.check_ff(); this.check_ff();
var end = (window.performance && performance.now) ? performance.now() : Date.now(), var end = (window.performance && performance.now) ? performance.now() : Date.now(),
duration = end - start; duration = end - start;
this.log("Initialization complete in " + duration + "ms"); this.log("Initialization complete in " + duration + "ms");
} }

View file

@ -1,24 +1,24 @@
Array.prototype.equals = function (array) { Array.prototype.equals = function (array) {
// if the other array is a falsy value, return // if the other array is a falsy value, return
if (!array) if (!array)
return false; return false;
// compare lengths - can save a lot of time // compare lengths - can save a lot of time
if (this.length != array.length) if (this.length != array.length)
return false; return false;
for (var i = 0, l=this.length; i < l; i++) { for (var i = 0, l=this.length; i < l; i++) {
// Check if we have nested arrays // Check if we have nested arrays
if (this[i] instanceof Array && array[i] instanceof Array) { if (this[i] instanceof Array && array[i] instanceof Array) {
// recurse into the nested arrays // recurse into the nested arrays
if (!this[i].equals(array[i])) if (!this[i].equals(array[i]))
return false; return false;
} }
else if (this[i] != array[i]) { else if (this[i] != array[i]) {
// Warning - two different object instances will never be equal: {x:20} != {x:20} // Warning - two different object instances will never be equal: {x:20} != {x:20}
return false; return false;
} }
} }
return true; return true;
} }

View file

@ -1,110 +1,110 @@
var FFZ = window.FrankerFaceZ; var FFZ = window.FrankerFaceZ;
FFZ.prototype._ws_open = false; FFZ.prototype._ws_open = false;
FFZ.prototype._ws_delay = 0; FFZ.prototype._ws_delay = 0;
FFZ.ws_commands = {}; FFZ.ws_commands = {};
// ---------------- // ----------------
// Socket Creation // Socket Creation
// ---------------- // ----------------
FFZ.prototype.ws_create = function() { FFZ.prototype.ws_create = function() {
var f = this; var f = this;
this._ws_last_req = 0; this._ws_last_req = 0;
this._ws_callbacks = {}; this._ws_callbacks = {};
this._ws_pending = this._ws_pending || []; this._ws_pending = this._ws_pending || [];
var ws = this._ws_sock = new WebSocket("ws://ffz.stendec.me/"); var ws = this._ws_sock = new WebSocket("ws://ffz.stendec.me/");
ws.onopen = function(e) { ws.onopen = function(e) {
f._ws_open = true; f._ws_open = true;
f._ws_delay = 0; f._ws_delay = 0;
f.log("Socket connected."); f.log("Socket connected.");
var user = f.get_user(); var user = f.get_user();
if ( user ) if ( user )
f.ws_send("setuser", user.login); f.ws_send("setuser", user.login);
// Send the current rooms. // Send the current rooms.
for(var room_id in f.rooms) for(var room_id in f.rooms)
f.ws_send("sub", room_id); f.ws_send("sub", room_id);
// Send any pending commands. // Send any pending commands.
var pending = f._ws_pending; var pending = f._ws_pending;
f._ws_pending = []; f._ws_pending = [];
for(var i=0; i < pending.length; i++) { for(var i=0; i < pending.length; i++) {
var d = pending[i]; var d = pending[i];
f.ws_send(d[0], d[1], d[2]); f.ws_send(d[0], d[1], d[2]);
} }
} }
ws.onclose = function(e) { ws.onclose = function(e) {
f.log("Socket closed."); f.log("Socket closed.");
f._ws_open = false; f._ws_open = false;
// We never ever want to not have a socket. // We never ever want to not have a socket.
if ( f._ws_delay < 30000 ) if ( f._ws_delay < 30000 )
f._ws_delay += 5000; f._ws_delay += 5000;
setTimeout(f.ws_create.bind(f), f._ws_delay); setTimeout(f.ws_create.bind(f), f._ws_delay);
} }
ws.onmessage = function(e) { ws.onmessage = function(e) {
// Messages are formatted as REQUEST_ID SUCCESS/FUNCTION_NAME[ JSON_DATA] // Messages are formatted as REQUEST_ID SUCCESS/FUNCTION_NAME[ JSON_DATA]
var cmd, data, ind = e.data.indexOf(" "), var cmd, data, ind = e.data.indexOf(" "),
msg = e.data.substr(ind + 1), msg = e.data.substr(ind + 1),
request = parseInt(e.data.slice(0, ind)); request = parseInt(e.data.slice(0, ind));
ind = msg.indexOf(" "); ind = msg.indexOf(" ");
if ( ind === -1 ) if ( ind === -1 )
ind = msg.length; ind = msg.length;
cmd = msg.slice(0, ind); cmd = msg.slice(0, ind);
msg = msg.substr(ind + 1); msg = msg.substr(ind + 1);
if ( msg ) if ( msg )
data = JSON.parse(msg); data = JSON.parse(msg);
if ( request === -1 ) { if ( request === -1 ) {
// It's a command from the server. // It's a command from the server.
var command = FFZ.ws_commands[cmd]; var command = FFZ.ws_commands[cmd];
if ( command ) if ( command )
command.bind(f)(data); command.bind(f)(data);
else else
f.log("Invalid command: " + cmd, data); f.log("Invalid command: " + cmd, data);
} else { } else {
var success = cmd === 'True', var success = cmd === 'True',
callback = f._ws_callbacks[request]; callback = f._ws_callbacks[request];
f.log("Socket Reply to " + request + " - " + (success ? "SUCCESS" : "FAIL"), data); f.log("Socket Reply to " + request + " - " + (success ? "SUCCESS" : "FAIL"), data);
if ( callback ) { if ( callback ) {
delete f._ws_callbacks[request]; delete f._ws_callbacks[request];
callback(success, data); callback(success, data);
} }
} }
} }
} }
FFZ.prototype.ws_send = function(func, data, callback, can_wait) { FFZ.prototype.ws_send = function(func, data, callback, can_wait) {
if ( ! this._ws_open ) { if ( ! this._ws_open ) {
if ( can_wait ) { if ( can_wait ) {
var pending = this._ws_pending = this._ws_pending || []; var pending = this._ws_pending = this._ws_pending || [];
pending.push([func, data, callback]); pending.push([func, data, callback]);
return true; return true;
} else } else
return false; return false;
} }
var request = ++this._ws_last_req; var request = ++this._ws_last_req;
data = data !== undefined ? " " + JSON.stringify(data) : ""; data = data !== undefined ? " " + JSON.stringify(data) : "";
if ( callback ) if ( callback )
this._ws_callbacks[request] = callback; this._ws_callbacks[request] = callback;
this._ws_sock.send(request + " " + func + data); this._ws_sock.send(request + " " + func + data);
return request; return request;
} }

View file

@ -1,143 +1,143 @@
var FFZ = window.FrankerFaceZ; var FFZ = window.FrankerFaceZ;
// -------------------- // --------------------
// Initializer // Initializer
// -------------------- // --------------------
FFZ.prototype.setup_menu = function() { FFZ.prototype.setup_menu = function() {
this.log("Installing mouse-up event to auto-close menus."); this.log("Installing mouse-up event to auto-close menus.");
var f = this; var f = this;
jQuery(document).mouseup(function(e) { jQuery(document).mouseup(function(e) {
var popup = f._popup, parent; var popup = f._popup, parent;
if ( ! popup ) return; if ( ! popup ) return;
popup = jQuery(popup); popup = jQuery(popup);
parent = popup.parent(); parent = popup.parent();
if ( ! parent.is(e.target) && parent.has(e.target).length === 0 ) { if ( ! parent.is(e.target) && parent.has(e.target).length === 0 ) {
popup.remove(); popup.remove();
delete f._popup; delete f._popup;
} }
}); });
} }
// -------------------- // --------------------
// Create Menu // Create Menu
// -------------------- // --------------------
FFZ.prototype.build_ui_popup = function(view) { FFZ.prototype.build_ui_popup = function(view) {
var popup = this._popup; var popup = this._popup;
if ( popup ) { if ( popup ) {
popup.parentElement.removeChild(popup); popup.parentElement.removeChild(popup);
delete this._popup; delete this._popup;
return; return;
} }
// Start building the DOM. // Start building the DOM.
var container = document.createElement('div'), var container = document.createElement('div'),
inner = document.createElement('div'); inner = document.createElement('div');
container.className = 'emoticon-selector chat-menu ffz-ui-popup'; container.className = 'emoticon-selector chat-menu ffz-ui-popup';
inner.className = 'emoticon-selector-box dropmenu'; inner.className = 'emoticon-selector-box dropmenu';
container.appendChild(inner); container.appendChild(inner);
// TODO: Modularize for multiple menu pages! // TODO: Modularize for multiple menu pages!
// Get the current room. // Get the current room.
var room_id = view.get('controller.currentRoom.id'), var room_id = view.get('controller.currentRoom.id'),
room = this.rooms[room_id]; room = this.rooms[room_id];
this.log("Menu for Room: " + room_id, room); 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. // Add the header and ad button.
var btn = document.createElement('a'); var btn = document.createElement('a');
btn.className = 'button glyph-only ffz-button'; btn.className = 'button glyph-only ffz-button';
btn.title = 'Advertise for FrankerFaceZ in chat!'; btn.title = 'Advertise for FrankerFaceZ in chat!';
btn.href = '#'; btn.href = '#';
btn.innerHTML = '<svg class="svg-followers" height="16px" version="1.1" viewBox="0 0 16 16" width="16px" x="0px" y="0px"><path clip-rule="evenodd" d="M8,13.5L1.5,7V4l2-2h3L8,3.5L9.5,2h3l2,2v3L8,13.5z" fill-rule="evenodd"></path></svg>'; btn.innerHTML = '<svg class="svg-followers" height="16px" version="1.1" viewBox="0 0 16 16" width="16px" x="0px" y="0px"><path clip-rule="evenodd" d="M8,13.5L1.5,7V4l2-2h3L8,3.5L9.5,2h3l2,2v3L8,13.5z" fill-rule="evenodd"></path></svg>';
var hdr = document.createElement('div'); var hdr = document.createElement('div');
hdr.className = 'list-header first'; hdr.className = 'list-header first';
hdr.appendChild(btn); hdr.appendChild(btn);
hdr.appendChild(document.createTextNode('FrankerFaceZ')); hdr.appendChild(document.createTextNode('FrankerFaceZ'));
inner.appendChild(hdr); inner.appendChild(hdr);
var c = this._emotes_for_sets(inner, view, room && room.menu_sets || []); var c = this._emotes_for_sets(inner, view, room && room.menu_sets || []);
if ( c === 0 ) if ( c === 0 )
btn.addEventListener('click', this._add_emote.bind(this, view, "To use custom emoticons in tons of channels, get FrankerFaceZ from http://www.frankerfacez.com")); btn.addEventListener('click', this._add_emote.bind(this, view, "To use custom emoticons in tons of channels, get FrankerFaceZ from http://www.frankerfacez.com"));
else else
btn.addEventListener('click', this._add_emote.bind(this, view, "To view this channel's emoticons, get FrankerFaceZ from http://www.frankerfacez.com")); btn.addEventListener('click', this._add_emote.bind(this, view, "To view this channel's emoticons, get FrankerFaceZ from http://www.frankerfacez.com"));
// Feature Friday! // Feature Friday!
this._feature_friday_ui(room_id, inner, view); this._feature_friday_ui(room_id, inner, view);
// Add the menu to the DOM. // Add the menu to the DOM.
this._popup = container; this._popup = container;
inner.style.maxHeight = Math.max(300, view.$().height() - 171) + "px"; inner.style.maxHeight = Math.max(300, view.$().height() - 171) + "px";
view.$('.chat-interface').append(container); view.$('.chat-interface').append(container);
} }
// -------------------- // --------------------
// Emotes for Sets // Emotes for Sets
// -------------------- // --------------------
FFZ.prototype._emotes_for_sets = function(parent, view, sets, header, btn) { FFZ.prototype._emotes_for_sets = function(parent, view, sets, header, btn) {
if ( header != null ) { if ( header != null ) {
var el_header = document.createElement('div'); var el_header = document.createElement('div');
el_header.className = 'list-header'; el_header.className = 'list-header';
el_header.appendChild(document.createTextNode(header)); el_header.appendChild(document.createTextNode(header));
if ( btn ) if ( btn )
el_header.appendChild(btn); el_header.appendChild(btn);
parent.appendChild(el_header); parent.appendChild(el_header);
} }
var grid = document.createElement('div'), c = 0; var grid = document.createElement('div'), c = 0;
grid.className = 'emoticon-grid'; grid.className = 'emoticon-grid';
for(var i=0; i < sets.length; i++) { for(var i=0; i < sets.length; i++) {
var set = this.emote_sets[sets[i]]; var set = this.emote_sets[sets[i]];
if ( ! set || ! set.emotes ) if ( ! set || ! set.emotes )
continue; continue;
for(var eid in set.emotes) { for(var eid in set.emotes) {
var emote = set.emotes[eid]; var emote = set.emotes[eid];
if ( !set.emotes.hasOwnProperty(eid) || emote.hidden ) if ( !set.emotes.hasOwnProperty(eid) || emote.hidden )
continue; continue;
c++; c++;
var s = document.createElement('span'); var s = document.createElement('span');
s.className = 'emoticon tooltip'; s.className = 'emoticon tooltip';
s.style.backgroundImage = 'url("' + emote.url + '")'; s.style.backgroundImage = 'url("' + emote.url + '")';
s.style.width = emote.width + "px"; s.style.width = emote.width + "px";
s.style.height = emote.height + "px"; s.style.height = emote.height + "px";
s.title = emote.name; s.title = emote.name;
s.addEventListener('click', this._add_emote.bind(this, view, emote.name)); s.addEventListener('click', this._add_emote.bind(this, view, emote.name));
grid.appendChild(s); grid.appendChild(s);
} }
} }
if ( !c ) { if ( !c ) {
grid.innerHTML = "This channel has no emoticons."; grid.innerHTML = "This channel has no emoticons.";
grid.className = "chat-menu-content ffz-no-emotes center"; grid.className = "chat-menu-content ffz-no-emotes center";
} }
parent.appendChild(grid); parent.appendChild(grid);
} }
FFZ.prototype._add_emote = function(view, emote) { FFZ.prototype._add_emote = function(view, emote) {
var room = view.get('controller.currentRoom'), var room = view.get('controller.currentRoom'),
current_text = room.get('messageToSend') || ''; current_text = room.get('messageToSend') || '';
if ( current_text && current_text.substr(-1) !== " " ) if ( current_text && current_text.substr(-1) !== " " )
current_text += ' '; current_text += ' ';
room.set('messageToSend', current_text + (emote.name || emote)); room.set('messageToSend', current_text + (emote.name || emote));
} }

View file

@ -1,50 +1,50 @@
var FFZ = window.FrankerFaceZ, var FFZ = window.FrankerFaceZ,
constants = require('../constants'); constants = require('../constants');
// -------------------- // --------------------
// Initialization // Initialization
// -------------------- // --------------------
FFZ.prototype.build_ui_link = function(view) { FFZ.prototype.build_ui_link = function(view) {
var link = document.createElement('a'); var link = document.createElement('a');
link.className = 'ffz-ui-toggle'; link.className = 'ffz-ui-toggle';
link.innerHTML = constants.CHAT_BUTTON; link.innerHTML = constants.CHAT_BUTTON;
link.addEventListener('click', this.build_ui_popup.bind(this, view)); link.addEventListener('click', this.build_ui_popup.bind(this, view));
this.update_ui_link(link); this.update_ui_link(link);
return link; return link;
} }
FFZ.prototype.update_ui_link = function(link) { FFZ.prototype.update_ui_link = function(link) {
var controller = App.__container__.lookup('controller:chat'); var controller = App.__container__.lookup('controller:chat');
link = link || document.querySelector('a.ffz-ui-toggle'); link = link || document.querySelector('a.ffz-ui-toggle');
if ( !link || !controller ) if ( !link || !controller )
return; return;
var room_id = controller.get('currentRoom.id'), var room_id = controller.get('currentRoom.id'),
room = this.rooms[room_id], room = this.rooms[room_id],
has_emotes = false, has_emotes = false,
dark = (this.has_bttv ? BetterTTV.settings.get('darkenedMode') : false), dark = (this.has_bttv ? BetterTTV.settings.get('darkenedMode') : false),
blue = (this.has_bttv ? BetterTTV.settings.get('showBlueButtons') : false), blue = (this.has_bttv ? BetterTTV.settings.get('showBlueButtons') : false),
live = (this.feature_friday && this.feature_friday.live); live = (this.feature_friday && this.feature_friday.live);
// Check for emoticons. // Check for emoticons.
if ( room && room.sets.length ) { if ( room && room.sets.length ) {
for(var i=0; i < room.sets.length; i++) { for(var i=0; i < room.sets.length; i++) {
var set = this.emote_sets[room.sets[i]]; var set = this.emote_sets[room.sets[i]];
if ( set && set.count > 0 ) { if ( set && set.count > 0 ) {
has_emotes = true; has_emotes = true;
break; break;
} }
} }
} }
link.classList.toggle('no-emotes', ! has_emotes); link.classList.toggle('no-emotes', ! has_emotes);
link.classList.toggle('live', live); link.classList.toggle('live', live);
link.classList.toggle('dark', dark); link.classList.toggle('dark', dark);
link.classList.toggle('blue', blue); link.classList.toggle('blue', blue);
} }

View file

@ -1,15 +1,15 @@
var FFZ = window.FrankerFaceZ; var FFZ = window.FrankerFaceZ;
FFZ.prototype.show_notification = function(message) { FFZ.prototype.show_notification = function(message) {
window.noty({ window.noty({
text: message, text: message,
theme: "ffzTheme", theme: "ffzTheme",
layout: "bottomCenter", layout: "bottomCenter",
closeWith: ["button"] closeWith: ["button"]
}).show(); }).show();
} }
FFZ.ws_commands.message = function(message) { FFZ.ws_commands.message = function(message) {
this.show_notification(message); this.show_notification(message);
} }

View file

@ -1,24 +1,24 @@
var FFZ = window.FrankerFaceZ, var FFZ = window.FrankerFaceZ,
constants = require('../constants'); constants = require('../constants');
FFZ.prototype.setup_css = function() { FFZ.prototype.setup_css = function() {
this.log("Injecting main FrankerFaceZ CSS."); this.log("Injecting main FrankerFaceZ CSS.");
var s = this._main_style = document.createElement('link'); var s = this._main_style = document.createElement('link');
s.id = "ffz-ui-css"; s.id = "ffz-ui-css";
s.setAttribute('rel', 'stylesheet'); s.setAttribute('rel', 'stylesheet');
s.setAttribute('href', constants.SERVER + "script/style.css"); s.setAttribute('href', constants.SERVER + "script/style.css");
document.head.appendChild(s); document.head.appendChild(s);
jQuery.noty.themes.ffzTheme = { jQuery.noty.themes.ffzTheme = {
name: "ffzTheme", name: "ffzTheme",
style: function() { style: function() {
this.$bar.removeClass().addClass("noty_bar").addClass("ffz-noty").addClass(this.options.type); this.$bar.removeClass().addClass("noty_bar").addClass("ffz-noty").addClass(this.options.type);
}, },
callback: { callback: {
onShow: function() {}, onShow: function() {},
onClose: function() {} onClose: function() {}
} }
}; };
} }

View file

@ -1,36 +1,36 @@
var FFZ = window.FrankerFaceZ, var FFZ = window.FrankerFaceZ,
constants = require('../constants'), constants = require('../constants'),
utils = require('../utils'); utils = require('../utils');
// ------------ // ------------
// Set Viewers // Set Viewers
// ------------ // ------------
FFZ.ws_commands.viewers = function(data) { FFZ.ws_commands.viewers = function(data) {
var channel = data[0], count = data[1]; var channel = data[0], count = data[1];
var controller = App.__container__.lookup('controller:channel'), var controller = App.__container__.lookup('controller:channel'),
id = controller && controller.get && controller.get('id'); id = controller && controller.get && controller.get('id');
if ( id !== channel ) if ( id !== channel )
return; return;
var view_count = document.querySelector('.channel-stats .ffz.stat'), var view_count = document.querySelector('.channel-stats .ffz.stat'),
content = constants.ZREKNARF + ' ' + utils.number_commas(count); content = constants.ZREKNARF + ' ' + utils.number_commas(count);
if ( view_count ) if ( view_count )
view_count.innerHTML = content; view_count.innerHTML = content;
else { else {
var parent = document.querySelector('.channel-stats'); var parent = document.querySelector('.channel-stats');
if ( ! parent ) if ( ! parent )
return; return;
view_count = document.createElement('span'); view_count = document.createElement('span');
view_count.className = 'ffz stat'; view_count.className = 'ffz stat';
view_count.title = 'Viewers with FrankerFaceZ'; view_count.title = 'Viewers with FrankerFaceZ';
view_count.innerHTML = content; view_count.innerHTML = content;
parent.appendChild(view_count); parent.appendChild(view_count);
jQuery(view_count).tipsy(); jQuery(view_count).tipsy();
} }
} }

View file

@ -1,30 +1,30 @@
var FFZ = window.FrankerFaceZ, var FFZ = window.FrankerFaceZ,
constants = require('./constants'); constants = require('./constants');
module.exports = { module.exports = {
update_css: function(element, id, css) { update_css: function(element, id, css) {
var all = element.innerHTML, var all = element.innerHTML,
start = "/*BEGIN " + id + "*/", start = "/*BEGIN " + id + "*/",
end = "/*END " + id + "*/", end = "/*END " + id + "*/",
s_ind = all.indexOf(start), s_ind = all.indexOf(start),
e_ind = all.indexOf(end), e_ind = all.indexOf(end),
found = s_ind !== -1 && e_ind !== -1 && e_ind > s_ind; found = s_ind !== -1 && e_ind !== -1 && e_ind > s_ind;
if ( !found && !css ) if ( !found && !css )
return; return;
if ( found ) if ( found )
all = all.substr(0, s_ind) + all.substr(e_ind + end.length); all = all.substr(0, s_ind) + all.substr(e_ind + end.length);
if ( css ) if ( css )
all += start + css + end; all += start + css + end;
element.innerHTML = all; element.innerHTML = all;
}, },
number_commas: function(x) { number_commas: function(x) {
var parts = x.toString().split("."); var parts = x.toString().split(".");
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ","); parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
return parts.join("."); return parts.join(".");
} }
} }

View file

@ -128,4 +128,12 @@
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: 115% -75%; background-position: 115% -75%;
background-size: 50%; background-size: 50%;
} }
.ffz-live-team-channel .ffz-game {
display: inline-block;
max-width: 150px;
text-overflow: ellipsis;
overflow: hidden;
margin-bottom: -5px;
}