From 36630f8da1f38623ae07b4e52f8038af28486ce0 Mon Sep 17 00:00:00 2001 From: SirStendec Date: Fri, 14 Aug 2015 12:32:05 -0400 Subject: [PATCH] Removed compiled script files from repository, added them to gitignore. --- .gitignore | 4 +- script.js | 13464 ------------------------------------------------ script.min.js | 9 - 3 files changed, 3 insertions(+), 13474 deletions(-) delete mode 100644 script.js delete mode 100644 script.min.js diff --git a/.gitignore b/.gitignore index 04755b58..96f9db49 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ node_modules npm-debug.log build Extension Building -.idea \ No newline at end of file +.idea +script.js +script.min.js \ No newline at end of file diff --git a/script.js b/script.js deleted file mode 100644 index edfb7c87..00000000 --- a/script.js +++ /dev/null @@ -1,13464 +0,0 @@ -(function(window) {(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 1 ) - badges[slot].title = title_template.replace('{}', line_data[1]); - count += 1; - } - } - - this.log('Added "' + title + '" badge to ' + utils.number_commas(count) + " users."); -} -},{"./constants":5,"./utils":35}],3:[function(require,module,exports){ -var FFZ = window.FrankerFaceZ, - - hue2rgb = function(p, q, t) { - if ( t < 0 ) t += 1; - if ( t > 1 ) t -= 1; - if ( t < 1/6 ) - return p + (q-p) * 6 * t; - if ( t < 1/2 ) - return q; - if ( t < 2/3 ) - return p + (q-p) * (2/3 - t) * 6; - return p; - }; - - -// --------------------- -// Settings -// --------------------- - -FFZ.settings_info.fix_color = { - type: "select", - options: { - '-1': "Disabled", - 0: "Default Colors", - 1: "Luv Adjustment", - 2: "HSL Adjustment (Depreciated)", - 3: "HSV Adjustment (Depreciated)", - 4: "RGB Adjustment (Depreciated)" - }, - value: '1', - - category: "Chat Appearance", - no_bttv: true, - - name: "Username Colors - Brightness", - help: "Ensure that username colors contrast with the background enough to be readable.", - - process_value: function(val) { - // Load legacy setting. - if ( val === false ) - return '0'; - else if ( val === true ) - return '1'; - return val; - }, - - on_update: function(val) { - document.body.classList.toggle("ffz-chat-colors-gray", !this.has_bttv && (val === '-1')); - - if ( ! this.has_bttv && val !== '-1' ) - this._rebuild_colors(); - } - }; - - -FFZ.settings_info.luv_contrast = { - type: "button", - value: 4.5, - - category: "Chat Appearance", - no_bttv: true, - - name: "Username Colors - Luv Minimum Contrast", - help: "Set the minimum contrast ratio used by Luv Adjustment to ensure colors are readable.", - - method: function() { - var old_val = this.settings.luv_contrast, - new_val = prompt("Luv Adjustment Minimum Contrast Ratio\n\nPlease enter a new value for the minimum contrast ratio required between username colors and the background. The default is: 4.5", old_val); - - if ( new_val === null || new_val === undefined ) - return; - - var parsed = parseFloat(new_val); - if ( parsed === NaN || parsed < 1 ) - parsed = 4.5; - - this.settings.set("luv_contrast", parsed); - }, - - on_update: function(val) { - this._rebuild_contrast(); - - if ( ! this.has_bttv && this.settings.fix_color == '1' ) - this._rebuild_colors(); - } - }; - - -FFZ.settings_info.color_blind = { - type: "select", - options: { - 0: "Disabled", - protanope: "Protanope", - deuteranope: "Deuteranope", - tritanope: "Tritanope" - }, - value: '0', - - category: "Chat Appearance", - no_bttv: true, - - name: "Username Colors - Color Blindness", - help: "Adjust username colors in an attempt to make them more distinct for people with color blindness.", - - on_update: function(val) { - if ( ! this.has_bttv && this.settings.fix_color !== '-1' ) - this._rebuild_colors(); - } - }; - - -// -------------------- -// Initialization -// -------------------- - -FFZ.prototype.setup_colors = function() { - this._colors = {}; - this._rebuild_contrast(); - - this._update_colors(); - - // Events for rebuilding colors. - var Layout = window.App && App.__container__.lookup('controller:layout'), - Settings = window.App && App.__container__.lookup('controller:settings'); - - if ( Layout ) - Layout.addObserver("isTheatreMode", this._update_colors.bind(this, true)); - - if ( Settings ) - Settings.addObserver("model.darkMode", this._update_colors.bind(this, true)) - - this._color_old_darkness = (Layout && Layout.get('isTheatreMode')) || (Settings && Settings.get('model.darkMode')); -} - - -// ----------------------- -// Color Handling Classes -// ----------------------- - -FFZ.Color = {}; - -FFZ.Color.CVDMatrix = { - protanope: [ // reds are greatly reduced (1% men) - 0.0, 2.02344, -2.52581, - 0.0, 1.0, 0.0, - 0.0, 0.0, 1.0 - ], - deuteranope: [ // greens are greatly reduced (1% men) - 1.0, 0.0, 0.0, - 0.494207, 0.0, 1.24827, - 0.0, 0.0, 1.0 - ], - tritanope: [ // blues are greatly reduced (0.003% population) - 1.0, 0.0, 0.0, - 0.0, 1.0, 0.0, - -0.395913, 0.801109, 0.0 - ] -} - - -var RGBColor = FFZ.Color.RGB = function(r, g, b) { - this.r = r||0; this.g = g||0; this.b = b||0; -}; - -var HSVColor = FFZ.Color.HSV = function(h, s, v) { - this.h = h||0; this.s = s||0; this.v = v||0; -}; - -var HSLColor = FFZ.Color.HSL = function(h, s, l) { - this.h = h||0; this.s = s||0; this.l = l||0; -}; - -var XYZColor = FFZ.Color.XYZ = function(x, y, z) { - this.x = x||0; this.y = y||0; this.z = z||0; -}; - -var LUVColor = FFZ.Color.LUV = function(l, u, v) { - this.l = l||0; this.u = u||0; this.v = v||0; -}; - - -// RGB Colors - -RGBColor.prototype.eq = function(rgb) { - return rgb.r === this.r && rgb.g === this.g && rgb.b === this.b; -} - -RGBColor.fromHex = function(code) { - var raw = parseInt(code.charAt(0) === '#' ? code.substr(1) : code, 16); - return new RGBColor( - (raw >> 16), // Red - (raw >> 8 & 0x00FF), // Green - (raw & 0x0000FF) // Blue - ) -} - -RGBColor.fromHSV = function(h, s, v) { - var r, g, b, - - i = Math.floor(h * 6), - f = h * 6 - i, - p = v * (1 - s), - q = v * (1 - f * s), - t = v * (1 - (1 - f) * s); - - switch(i % 6) { - case 0: r = v, g = t, b = p; break; - case 1: r = q, g = v, b = p; break; - case 2: r = p, g = v, b = t; break; - case 3: r = p, g = q, b = v; break; - case 4: r = t, g = p, b = v; break; - case 5: r = v, g = p, b = q; - } - - return new RGBColor( - Math.round(Math.min(Math.max(0, r*255), 255)), - Math.round(Math.min(Math.max(0, g*255), 255)), - Math.round(Math.min(Math.max(0, b*255), 255)) - ); -} - -RGBColor.fromXYZ = function(x, y, z) { - var R = 3.240479 * x - 1.537150 * y - 0.498535 * z, - G = -0.969256 * x + 1.875992 * y + 0.041556 * z, - B = 0.055648 * x - 0.204043 * y + 1.057311 * z; - - // Make sure we end up in a real color space - return new RGBColor( - Math.max(0, Math.min(255, 255 * XYZColor.channelConverter(R))), - Math.max(0, Math.min(255, 255 * XYZColor.channelConverter(G))), - Math.max(0, Math.min(255, 255 * XYZColor.channelConverter(B))) - ); -} - -RGBColor.fromHSL = function(h, s, l) { - if ( s === 0 ) { - var v = Math.round(Math.min(Math.max(0, 255*l), 255)); - return new RGBColor(v, v, v); - } - - var q = l < 0.5 ? l * (1 + s) : l + s - l * s, - p = 2 * l - q; - - return new RGBColor( - Math.round(Math.min(Math.max(0, 255 * hue2rgb(p, q, h + 1/3)), 255)), - Math.round(Math.min(Math.max(0, 255 * hue2rgb(p, q, h)), 255)), - Math.round(Math.min(Math.max(0, 255 * hue2rgb(p, q, h - 1/3)), 255)) - ); -} - -RGBColor.prototype.toHSV = function() { return HSVColor.fromRGB(this.r, this.g, this.b); } -RGBColor.prototype.toHSL = function() { return HSLColor.fromRGB(this.r, this.g, this.b); } -RGBColor.prototype.toCSS = function() { return "rgb(" + Math.round(this.r) + "," + Math.round(this.g) + "," + Math.round(this.b) + ")"; } -RGBColor.prototype.toXYZ = function() { return XYZColor.fromRGB(this.r, this.g, this.b); } -RGBColor.prototype.toLUV = function() { return this.toXYZ().toLUV(); } - -RGBColor.prototype.toHex = function() { - var rgb = this.b | (this.g << 8) | (this.r << 16); - return '#' + (0x1000000 + rgb).toString(16).slice(1); -} - - -RGBColor.prototype.luminance = function() { - var rgb = [this.r / 255, this.g / 255, this.b / 255]; - for (var i =0, l = rgb.length; i < l; 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 ); - } - } - return (0.2126 * rgb[0]) + (0.7152 * rgb[1]) + (0.0722 * rgb[2]); -} - - -RGBColor.prototype.brighten = function(amount) { - amount = typeof amount === "number" ? amount : 1; - amount = Math.round(255 * (amount / 100)); - - return new RGBColor( - Math.max(0, Math.min(255, this.r + amount)), - Math.max(0, Math.min(255, this.g + amount)), - Math.max(0, Math.min(255, this.b + amount)) - ); -} - - -RGBColor.prototype.daltonize = function(type, amount) { - amount = typeof amount === "number" ? amount : 1.0; - var cvd; - if ( typeof type === "string" ) { - if ( FFZ.Color.CVDMatrix.hasOwnProperty(type) ) - cvd = FFZ.Color.CVDMatrix[type]; - else - throw "Invalid CVD matrix."; - } else - cvd = type; - - var cvd_a = cvd[0], cvd_b = cvd[1], cvd_c = cvd[2], - cvd_d = cvd[3], cvd_e = cvd[4], cvd_f = cvd[5], - cvd_g = cvd[6], cvd_h = cvd[7], cvd_i = cvd[8], - - L, M, S, l, m, s, R, G, B, RR, GG, BB; - - // RGB to LMS matrix conversion - L = (17.8824 * this.r) + (43.5161 * this.g) + (4.11935 * this.b); - M = (3.45565 * this.r) + (27.1554 * this.g) + (3.86714 * this.b); - S = (0.0299566 * this.r) + (0.184309 * this.g) + (1.46709 * this.b); - // Simulate color blindness - l = (cvd_a * L) + (cvd_b * M) + (cvd_c * S); - m = (cvd_d * L) + (cvd_e * M) + (cvd_f * S); - s = (cvd_g * L) + (cvd_h * M) + (cvd_i * S); - // LMS to RGB matrix conversion - R = (0.0809444479 * l) + (-0.130504409 * m) + (0.116721066 * s); - G = (-0.0102485335 * l) + (0.0540193266 * m) + (-0.113614708 * s); - B = (-0.000365296938 * l) + (-0.00412161469 * m) + (0.693511405 * s); - // Isolate invisible colors to color vision deficiency (calculate error matrix) - R = this.r - R; - G = this.g - G; - B = this.b - B; - // Shift colors towards visible spectrum (apply error modifications) - RR = (0.0 * R) + (0.0 * G) + (0.0 * B); - GG = (0.7 * R) + (1.0 * G) + (0.0 * B); - BB = (0.7 * R) + (0.0 * G) + (1.0 * B); - // Add compensation to original values - R = Math.min(Math.max(0, RR + this.r), 255); - G = Math.min(Math.max(0, GG + this.g), 255); - B = Math.min(Math.max(0, BB + this.b), 255); - - return new RGBColor(R, G, B); -} - -RGBColor.prototype._r = function(r) { return new RGBColor(r, this.g, this.b); } -RGBColor.prototype._g = function(g) { return new RGBColor(this.r, g, this.b); } -RGBColor.prototype._b = function(b) { return new RGBColor(this.r, this.g, b); } - - -// HSL Colors - -HSLColor.prototype.eq = function(hsl) { - return hsl.h === this.h && hsl.s === this.s && hsl.l === this.l; -} - -HSLColor.fromRGB = function(r, g, b) { - r /= 255; g /= 255; b /= 255; - - var max = Math.max(r,g,b), - min = Math.min(r,g,b), - - h, s, l = Math.min(Math.max(0, (max+min) / 2), 1), - d = Math.min(Math.max(0, max - min), 1); - - if ( d === 0 ) - h = s = 0; - else { - s = l > 0.5 ? d / (2 - max - min) : d / (max + min); - switch(max) { - case r: - h = (g - b) / d + (g < b ? 6 : 0); - break; - case g: - h = (b - r) / d + 2; - break; - case b: - h = (r - g) / d + 4; - } - h /= 6; - } - - return new HSLColor(h, s, l); -} - -HSLColor.prototype.toRGB = function() { return RGBColor.fromHSL(this.h, this.s, this.l); } -HSLColor.prototype.toCSS = function() { return "hsl(" + Math.round(this.h*360) + "," + Math.round(this.s*100) + "%," + Math.round(this.l*100) + "%)"; } -HSLColor.prototype.toHex = function() { return RGBColor.fromHSL(this.h, this.s, this.l).toHex(); } -HSLColor.prototype.toHSV = function() { return RGBColor.fromHSL(this.h, this.s, this.l).toHSV(); } -HSLColor.prototype.toXYZ = function() { return RGBColor.fromHSL(this.h, this.s, this.l).toXYZ(); } -HSLColor.prototype.toLUV = function() { return RGBColor.fromHSL(this.h, this.s, this.l).toLUV(); } - - -HSLColor.prototype._h = function(h) { return new HSLColor(h, this.s, this.l); } -HSLColor.prototype._s = function(s) { return new HSLColor(this.h, s, this.l); } -HSLColor.prototype._l = function(l) { return new HSLColor(this.h, this.s, l); } - - -// HSV Colors - -HSVColor.prototype.eq = function(hsv) { return hsv.h === this.h && hsv.s === this.s && hsv.v === this.v; } - -HSVColor.fromRGB = function(r, g, b) { - r /= 255; g /= 255; b /= 255; - - var max = Math.max(r, g, b), - min = Math.min(r, g, b), - d = Math.min(Math.max(0, max - min), 1), - - h, - s = max === 0 ? 0 : d / max, - v = max; - - if ( d === 0 ) - h = 0; - else { - switch(max) { - case r: - h = (g - b) / d + (g < b ? 6 : 0); - break; - case g: - h = (b - r) / d + 2; - break; - case b: - h = (r - g) / d + 4; - } - h /= 6; - } - - return new HSVColor(h, s, v); -} - - -HSVColor.prototype.toRGB = function() { return RGBColor.fromHSV(this.h, this.s, this.v); } -HSVColor.prototype.toHSL = function() { return RGBColor.fromHSV(this.h, this.s, this.v).toHSL(); } -HSVColor.prototype.toXYZ = function() { return RGBColor.fromHSV(this.h, this.s, this.v).toXYZ(); } -HSVColor.prototype.toLUV = function() { return RGBColor.fromHSV(this.h, this.s, this.v).toLUV(); } - - -HSVColor.prototype._h = function(h) { return new HSVColor(h, this.s, this.v); } -HSVColor.prototype._s = function(s) { return new HSVColor(this.h, s, this.v); } -HSVColor.prototype._v = function(v) { return new HSVColor(this.h, this.s, v); } - - -// XYZ Colors - -RGBColor.channelConverter = function (channel) { - // http://www.brucelindbloom.com/Eqn_RGB_to_XYZ.html - // This converts rgb 8bit to rgb linear, lazy because the other algorithm is really really dumb - return Math.pow(channel, 2.2); - - // CSS Colors Level 4 says 0.03928, Bruce Lindbloom who cared to write all algos says 0.04045, used bruce because whynawt - return (channel <= 0.04045) ? channel / 12.92 : Math.pow((channel + 0.055) / 1.055, 2.4); -}; - -XYZColor.channelConverter = function (channel) { - // Using lazy conversion in the other direction as well - return Math.pow(channel, 1/2.2); - - // I'm honestly not sure about 0.0031308, I've only seen it referenced on Bruce Lindbloom's site - return (channel <= 0.0031308) ? channel * 12.92 : Math.pow(1.055 * channel, 1/2.4) - 0.055; -}; - - -XYZColor.prototype.eq = function(xyz) { return xyz.x === this.x && xyz.y === this.y && xyz.z === this.z; } - -XYZColor.fromRGB = function(r, g, b) { - var R = RGBColor.channelConverter(r / 255), - G = RGBColor.channelConverter(g / 255), - B = RGBColor.channelConverter(b / 255); - - return new XYZColor( - 0.412453 * R + 0.357580 * G + 0.180423 * B, - 0.212671 * R + 0.715160 * G + 0.072169 * B, - 0.019334 * R + 0.119193 * G + 0.950227 * B - ); -} - -XYZColor.fromLUV = function(l, u, v) { - var deltaGammaFactor = 1 / (XYZColor.WHITE.x + 15 * XYZColor.WHITE.y + 3 * XYZColor.WHITE.z); - var uDeltaGamma = 4 * XYZColor.WHITE.x * deltaGammaFactor; - var vDeltagamma = 9 * XYZColor.WHITE.y * deltaGammaFactor; - - // XYZColor.EPSILON * XYZColor.KAPPA = 8 - var Y = (l > 8) ? Math.pow((l + 16) / 116, 3) : l / XYZColor.KAPPA; - - var a = 1/3 * (((52 * l) / (u + 13 * l * uDeltaGamma)) - 1); - var b = -5 * Y; - var c = -1/3; - var d = Y * (((39 * l) / (v + 13 * l * vDeltagamma)) - 5); - - var X = (d - b) / (a - c); - var Z = X * a + b; - - return new XYZColor(X, Y, Z); -} - - -XYZColor.prototype.toRGB = function() { return RGBColor.fromXYZ(this.x, this.y, this.z); } -XYZColor.prototype.toLUV = function() { return LUVColor.fromXYZ(this.x, this.y, this.z); } -XYZColor.prototype.toHSL = function() { return RGBColor.fromXYZ(this.x, this.y, this.z).toHSL(); } -XYZColor.prototype.toHSV = function() { return RGBColor.fromXYZ(this.x, this.y, this.z).toHSV(); } - - -XYZColor.prototype._x = function(x) { return new XYZColor(x, this.y, this.z); } -XYZColor.prototype._y = function(y) { return new XYZColor(this.x, y, this.z); } -XYZColor.prototype._z = function(z) { return new XYZColor(this.x, this.y, z); } - - -// LUV Colors - -XYZColor.EPSILON = Math.pow(6 / 29, 3); -XYZColor.KAPPA = Math.pow(29 / 3, 3); -XYZColor.WHITE = (new RGBColor(255, 255, 255)).toXYZ(); - - -LUVColor.prototype.eq = function(luv) { return luv.l === this.l && luv.u === this.u && luv.v === this.v; } - -LUVColor.fromXYZ = function(X, Y, Z) { - var deltaGammaFactor = 1 / (XYZColor.WHITE.x + 15 * XYZColor.WHITE.y + 3 * XYZColor.WHITE.z); - var uDeltaGamma = 4 * XYZColor.WHITE.x * deltaGammaFactor; - var vDeltagamma = 9 * XYZColor.WHITE.y * deltaGammaFactor; - - var yGamma = Y / XYZColor.WHITE.y; - var deltaDivider = (X + 15 * Y + 3 * Z); - - if (deltaDivider === 0) { - deltaDivider = 1; - } - - var deltaFactor = 1 / deltaDivider; - - var uDelta = 4 * X * deltaFactor; - var vDelta = 9 * Y * deltaFactor; - - var L = (yGamma > XYZColor.EPSILON) ? 116 * Math.pow(yGamma, 1/3) - 16 : XYZColor.KAPPA * yGamma; - var u = 13 * L * (uDelta - uDeltaGamma); - var v = 13 * L * (vDelta - vDeltagamma); - - return new LUVColor(L, u, v); -} - - -LUVColor.prototype.toXYZ = function() { return XYZColor.fromLUV(this.l, this.u, this.v); } -LUVColor.prototype.toRGB = function() { return XYZColor.fromLUV(this.l, this.u, this.v).toRGB(); } -LUVColor.prototype.toHSL = function() { return XYZColor.fromLUV(this.l, this.u, this.v).toHSL(); } -LUVColor.prototype.toHSV = function() { return XYZColor.fromLUV(this.l, this.u, this.v).toHSV(); } - - -LUVColor.prototype._l = function(l) { return new LUVColor(l, this.u, this.v); } -LUVColor.prototype._u = function(u) { return new LUVColor(this.l, u, this.v); } -LUVColor.prototype._v = function(v) { return new LUVColor(this.l, this.u, v); } - - -// -------------------- -// Rebuild Colors -// -------------------- - -FFZ.prototype._rebuild_contrast = function() { - this._luv_required_bright = new XYZColor(0, (this.settings.luv_contrast * (new RGBColor(35,35,35).toXYZ().y + 0.05) - 0.05), 0).toLUV().l; - this._luv_required_dark = new XYZColor(0, ((new RGBColor(217,217,217).toXYZ().y + 0.05) / this.settings.luv_contrast - 0.05), 0).toLUV().l; -} - -FFZ.prototype._rebuild_colors = function() { - if ( this.has_bttv ) - return; - - // With update colors, we'll automatically process the colors we care about. - this._colors = {}; - this._update_colors(); -} - - -FFZ.prototype._update_colors = function(darkness_only) { - // Update the lines. ALL of them. - var Layout = window.App && App.__container__.lookup('controller:layout'), - Settings = window.App && App.__container__.lookup('controller:settings'), - - is_dark = (Layout && Layout.get('isTheatreMode')) || (Settings && Settings.get('model.darkMode')); - - if ( darkness_only && this._color_old_darkness === is_dark ) - return; - - this._color_old_darkness = is_dark; - - var colored_bits = document.querySelectorAll('.chat-line .has-color'); - for(var i=0, l=colored_bits.length; i < l; i++) { - var bit = colored_bits[i], - color = bit.getAttribute('data-color'), - colors = color && this._handle_color(color); - - if ( ! colors ) - continue; - - bit.style.color = is_dark ? colors[1] : colors[0]; - } -} - - -FFZ.prototype._handle_color = function(color) { - if ( ! color || this._colors.hasOwnProperty(color) ) - return this._colors[color]; - - var rgb = RGBColor.fromHex(color), - - light_color = color, - dark_color = color; - - // Color Blindness Handling - if ( this.settings.color_blind !== '0' ) { - var new_color = rgb.daltonize(this.settings.color_blind); - if ( ! rgb.eq(new_color) ) { - rgb = new_color; - light_color = dark_color = rgb.toHex(); - } - } - - - // Color Processing - RGB - if ( this.settings.fix_color === '4' ) { - var lum = rgb.luminance(); - - if ( lum > 0.3 ) { - var s = 127, nc = rgb; - while(s--) { - nc = nc.brighten(-1); - if ( nc.luminance() <= 0.3 ) - break; - } - - light_color = nc.toHex(); - } - - if ( lum < 0.15 ) { - var s = 127, nc = rgb; - while(s--) { - nc = nc.brighten(); - if ( nc.luminance() >= 0.15 ) - break; - } - - dark_color = nc.toHex(); - } - } - - - // Color Processing - HSL - if ( this.settings.fix_color === '2' ) { - var hsl = rgb.toHSL(); - - light_color = hsl._l(Math.min(Math.max(0, 0.7 * hsl.l), 1)).toHex(); - dark_color = hsl._l(Math.min(Math.max(0, 0.3 + (0.7 * hsl.l)), 1)).toHex(); - } - - - // Color Processing - HSV - if ( this.settings.fix_color === '3' ) { - var hsv = rgb.toHSV(); - - if ( hsv.s === 0 ) { - // Black and White - light_color = hsv._v(Math.min(Math.max(0.5, 0.5 * hsv.v), 1)).toRGB().toHex(); - dark_color = hsv._v(Math.min(Math.max(0.5, 0.5 + (0.5 * hsv.v)), 1)).toRGB().toHex(); - - } else { - light_color = RGBColor.fromHSV(hsv.h, Math.min(Math.max(0.7, 0.7 + (0.3 * hsv.s)), 1), Math.min(0.7, hsv.v)).toHex(); - dark_color = RGBColor.fromHSV(hsv.h, Math.min(0.7, hsv.s), Math.min(Math.max(0.7, 0.7 + (0.3 * hsv.v)), 1)).toHex(); - } - } - - // Color Processing - LUV - if ( this.settings.fix_color === '1' ) { - var luv = rgb.toLUV(); - - if ( luv.l > this._luv_required_dark ) - light_color = luv._l(this._luv_required_dark).toRGB().toHex(); - - if ( luv.l < this._luv_required_bright ) - dark_color = luv._l(this._luv_required_bright).toRGB().toHex(); - } - - var out = this._colors[color] = [light_color, dark_color]; - return out; -} -},{}],4:[function(require,module,exports){ -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.ffz_commands.massunmod = function(room, args) { - args = args.join(" ").trim(); - - if ( ! args.length ) - return "You must provide a list of users to unmod."; - - args = args.split(/\W*,\W*/); - - var user = this.get_user(); - if ( ! user || ! user.login == room.id ) - return "You must be the broadcaster to use massunmod."; - - if ( args.length > 50 ) - return "Each user you unmod 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(); - room.room.tmiRoom.sendMessage("/unmod " + name); - } - - return "Sent unmod command for " + count + " users."; -} - -FFZ.ffz_commands.massunmod.help = "Usage: /ffz massunmod \nBroadcaster only. Unmod all the users in the provided list."; - - -FFZ.ffz_commands.massmod = function(room, args) { - args = args.join(" ").trim(); - - if ( ! args.length ) - return "You must provide a list of users to mod."; - - args = args.split(/\W*,\W*/); - - var user = this.get_user(); - if ( ! user || ! user.login == room.id ) - return "You must be the broadcaster to use massmod."; - - 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(); - room.room.tmiRoom.sendMessage("/mod " + name); - } - - return "Sent mod command for " + count + " users."; -} - -FFZ.ffz_commands.massmod.help = "Usage: /ffz massmod \nBroadcaster only. Mod all the users in the provided list."; - - -/*FFZ.ffz_commands.massunban = function(room, args) { - args = args.join(" ").trim(); - - - -}*/ -},{}],5:[function(require,module,exports){ -var SVGPATH = '', - DEBUG = localStorage.ffzDebugMode == "true" && document.body.classList.contains('ffz-dev'), - SERVER = DEBUG ? "//localhost:8000/" : "//cdn.frankerfacez.com/"; - -module.exports = { - DEBUG: DEBUG, - SERVER: SERVER, - API_SERVER: "//api.frankerfacez.com/", - API_SERVER_2: "//direct-api.frankerfacez.com/", - - KNOWN_CODES: { - "#-?[\\\\/]": "#-/", - ":-?(?:7|L)": ":-7", - "\\<\\;\\]": "<]", - "\\:-?(S|s)": ":-S", - "\\:-?\\\\": ":-\\", - "\\:\\>\\;": ":>", - "B-?\\)": "B-)", - "\\:-?[z|Z|\\|]": ":-Z", - "\\:-?\\)": ":-)", - "\\:-?\\(": ":-(", - "\\:-?(p|P)": ":-P", - "\\;-?(p|P)": ";-P", - "\\<\\;3": "<3", - "\\:-?[\\\\/]": ":-/", - "\\;-?\\)": ";-)", - "R-?\\)": "R-)", - "[o|O](_|\\.)[o|O]": "O.o", - "\\:-?D": ":-D", - "\\:-?(o|O)": ":-O", - "\\>\\;\\(": ">(", - "Gr(a|e)yFace": "GrayFace" - }, - - EMOTE_REPLACEMENT_BASE: SERVER + "script/replacements/", - EMOTE_REPLACEMENTS: { - 15: "15-JKanStyle.png", - 16: "16-OptimizePrime.png", - 17: "17-StoneLightning.png", - 18: "18-TheRinger.png", - 19: "19-PazPazowitz.png", - 20: "20-EagleEye.png", - 21: "21-CougarHunt.png", - 22: "22-RedCoat.png", - 26: "26-JonCarnage.png", - 27: "27-PicoMause.png", - 30: "30-BCWarrior.png", - 33: "33-DansGame.png", - 36: "36-PJSalt.png" - }, - - EMOJI_REGEX: /((?:\ud83c\udde8\ud83c\uddf3|\ud83c\uddfa\ud83c\uddf8|\ud83c\uddf7\ud83c\uddfa|\ud83c\uddf0\ud83c\uddf7|\ud83c\uddef\ud83c\uddf5|\ud83c\uddee\ud83c\uddf9|\ud83c\uddec\ud83c\udde7|\ud83c\uddeb\ud83c\uddf7|\ud83c\uddea\ud83c\uddf8|\ud83c\udde9\ud83c\uddea|\u0039\ufe0f?\u20e3|\u0038\ufe0f?\u20e3|\u0037\ufe0f?\u20e3|\u0036\ufe0f?\u20e3|\u0035\ufe0f?\u20e3|\u0034\ufe0f?\u20e3|\u0033\ufe0f?\u20e3|\u0032\ufe0f?\u20e3|\u0031\ufe0f?\u20e3|\u0030\ufe0f?\u20e3|\u0023\ufe0f?\u20e3|\ud83d\udeb3|\ud83d\udeb1|\ud83d\udeb0|\ud83d\udeaf|\ud83d\udeae|\ud83d\udea6|\ud83d\udea3|\ud83d\udea1|\ud83d\udea0|\ud83d\ude9f|\ud83d\ude9e|\ud83d\ude9d|\ud83d\ude9c|\ud83d\ude9b|\ud83d\ude98|\ud83d\ude96|\ud83d\ude94|\ud83d\ude90|\ud83d\ude8e|\ud83d\ude8d|\ud83d\ude8b|\ud83d\ude8a|\ud83d\ude88|\ud83d\ude86|\ud83d\ude82|\ud83d\ude81|\ud83d\ude36|\ud83d\ude34|\ud83d\ude2f|\ud83d\ude2e|\ud83d\ude2c|\ud83d\ude27|\ud83d\ude26|\ud83d\ude1f|\ud83d\ude1b|\ud83d\ude19|\ud83d\ude17|\ud83d\ude15|\ud83d\ude11|\ud83d\ude10|\ud83d\ude0e|\ud83d\ude08|\ud83d\ude07|\ud83d\ude00|\ud83d\udd67|\ud83d\udd66|\ud83d\udd65|\ud83d\udd64|\ud83d\udd63|\ud83d\udd62|\ud83d\udd61|\ud83d\udd60|\ud83d\udd5f|\ud83d\udd5e|\ud83d\udd5d|\ud83d\udd5c|\ud83d\udd2d|\ud83d\udd2c|\ud83d\udd15|\ud83d\udd09|\ud83d\udd08|\ud83d\udd07|\ud83d\udd06|\ud83d\udd05|\ud83d\udd04|\ud83d\udd02|\ud83d\udd01|\ud83d\udd00|\ud83d\udcf5|\ud83d\udcef|\ud83d\udced|\ud83d\udcec|\ud83d\udcb7|\ud83d\udcb6|\ud83d\udcad|\ud83d\udc6d|\ud83d\udc6c|\ud83d\udc65|\ud83d\udc2a|\ud83d\udc16|\ud83d\udc15|\ud83d\udc13|\ud83d\udc10|\ud83d\udc0f|\ud83d\udc0b|\ud83d\udc0a|\ud83d\udc09|\ud83d\udc08|\ud83d\udc07|\ud83d\udc06|\ud83d\udc05|\ud83d\udc04|\ud83d\udc03|\ud83d\udc02|\ud83d\udc01|\ud83d\udc00|\ud83c\udfe4|\ud83c\udfc9|\ud83c\udfc7|\ud83c\udf7c|\ud83c\udf50|\ud83c\udf4b|\ud83c\udf33|\ud83c\udf32|\ud83c\udf1e|\ud83c\udf1d|\ud83c\udf1c|\ud83c\udf1a|\ud83c\udf18|\ud83c\udccf|\ud83c\udd70|\ud83c\udd71|\ud83c\udd7e|\ud83c\udd8e|\ud83c\udd91|\ud83c\udd92|\ud83c\udd93|\ud83c\udd94|\ud83c\udd95|\ud83c\udd96|\ud83c\udd97|\ud83c\udd98|\ud83c\udd99|\ud83c\udd9a|\ud83d\udc77|\ud83d\udec5|\ud83d\udec4|\ud83d\udec3|\ud83d\udec2|\ud83d\udec1|\ud83d\udebf|\ud83d\udeb8|\ud83d\udeb7|\ud83d\udeb5|\ud83c\ude01|\ud83c\ude02|\ud83c\ude32|\ud83c\ude33|\ud83c\ude34|\ud83c\ude35|\ud83c\ude36|\ud83c\ude37|\ud83c\ude38|\ud83c\ude39|\ud83c\ude3a|\ud83c\ude50|\ud83c\ude51|\ud83c\udf00|\ud83c\udf01|\ud83c\udf02|\ud83c\udf03|\ud83c\udf04|\ud83c\udf05|\ud83c\udf06|\ud83c\udf07|\ud83c\udf08|\ud83c\udf09|\ud83c\udf0a|\ud83c\udf0b|\ud83c\udf0c|\ud83c\udf0f|\ud83c\udf11|\ud83c\udf13|\ud83c\udf14|\ud83c\udf15|\ud83c\udf19|\ud83c\udf1b|\ud83c\udf1f|\ud83c\udf20|\ud83c\udf30|\ud83c\udf31|\ud83c\udf34|\ud83c\udf35|\ud83c\udf37|\ud83c\udf38|\ud83c\udf39|\ud83c\udf3a|\ud83c\udf3b|\ud83c\udf3c|\ud83c\udf3d|\ud83c\udf3e|\ud83c\udf3f|\ud83c\udf40|\ud83c\udf41|\ud83c\udf42|\ud83c\udf43|\ud83c\udf44|\ud83c\udf45|\ud83c\udf46|\ud83c\udf47|\ud83c\udf48|\ud83c\udf49|\ud83c\udf4a|\ud83c\udf4c|\ud83c\udf4d|\ud83c\udf4e|\ud83c\udf4f|\ud83c\udf51|\ud83c\udf52|\ud83c\udf53|\ud83c\udf54|\ud83c\udf55|\ud83c\udf56|\ud83c\udf57|\ud83c\udf58|\ud83c\udf59|\ud83c\udf5a|\ud83c\udf5b|\ud83c\udf5c|\ud83c\udf5d|\ud83c\udf5e|\ud83c\udf5f|\ud83c\udf60|\ud83c\udf61|\ud83c\udf62|\ud83c\udf63|\ud83c\udf64|\ud83c\udf65|\ud83c\udf66|\ud83c\udf67|\ud83c\udf68|\ud83c\udf69|\ud83c\udf6a|\ud83c\udf6b|\ud83c\udf6c|\ud83c\udf6d|\ud83c\udf6e|\ud83c\udf6f|\ud83c\udf70|\ud83c\udf71|\ud83c\udf72|\ud83c\udf73|\ud83c\udf74|\ud83c\udf75|\ud83c\udf76|\ud83c\udf77|\ud83c\udf78|\ud83c\udf79|\ud83c\udf7a|\ud83c\udf7b|\ud83c\udf80|\ud83c\udf81|\ud83c\udf82|\ud83c\udf83|\ud83c\udf84|\ud83c\udf85|\ud83c\udf86|\ud83c\udf87|\ud83c\udf88|\ud83c\udf89|\ud83c\udf8a|\ud83c\udf8b|\ud83c\udf8c|\ud83c\udf8d|\ud83c\udf8e|\ud83c\udf8f|\ud83c\udf90|\ud83c\udf91|\ud83c\udf92|\ud83c\udf93|\ud83c\udfa0|\ud83c\udfa1|\ud83c\udfa2|\ud83c\udfa3|\ud83c\udfa4|\ud83c\udfa5|\ud83c\udfa6|\ud83c\udfa7|\ud83c\udfa8|\ud83c\udfa9|\ud83c\udfaa|\ud83c\udfab|\ud83c\udfac|\ud83c\udfad|\ud83c\udfae|\ud83c\udfaf|\ud83c\udfb0|\ud83c\udfb1|\ud83c\udfb2|\ud83c\udfb3|\ud83c\udfb4|\ud83c\udfb5|\ud83c\udfb6|\ud83c\udfb7|\ud83c\udfb8|\ud83c\udfb9|\ud83c\udfba|\ud83c\udfbb|\ud83c\udfbc|\ud83c\udfbd|\ud83c\udfbe|\ud83c\udfbf|\ud83c\udfc0|\ud83c\udfc1|\ud83c\udfc2|\ud83c\udfc3|\ud83c\udfc4|\ud83c\udfc6|\ud83c\udfc8|\ud83c\udfca|\ud83c\udfe0|\ud83c\udfe1|\ud83c\udfe2|\ud83c\udfe3|\ud83c\udfe5|\ud83c\udfe6|\ud83c\udfe7|\ud83c\udfe8|\ud83c\udfe9|\ud83c\udfea|\ud83c\udfeb|\ud83c\udfec|\ud83c\udfed|\ud83c\udfee|\ud83c\udfef|\ud83c\udff0|\ud83d\udc0c|\ud83d\udc0d|\ud83d\udc0e|\ud83d\udc11|\ud83d\udc12|\ud83d\udc14|\ud83d\udc17|\ud83d\udc18|\ud83d\udc19|\ud83d\udc1a|\ud83d\udc1b|\ud83d\udc1c|\ud83d\udc1d|\ud83d\udc1e|\ud83d\udc1f|\ud83d\udc20|\ud83d\udc21|\ud83d\udc22|\ud83d\udc23|\ud83d\udc24|\ud83d\udc25|\ud83d\udc26|\ud83d\udc27|\ud83d\udc28|\ud83d\udc29|\ud83d\udc2b|\ud83d\udc2c|\ud83d\udc2d|\ud83d\udc2e|\ud83d\udc2f|\ud83d\udc30|\ud83d\udc31|\ud83d\udc32|\ud83d\udc33|\ud83d\udc34|\ud83d\udc35|\ud83d\udc36|\ud83d\udc37|\ud83d\udc38|\ud83d\udc39|\ud83d\udc3a|\ud83d\udc3b|\ud83d\udc3c|\ud83d\udc3d|\ud83d\udc3e|\ud83d\udc40|\ud83d\udc42|\ud83d\udc43|\ud83d\udc44|\ud83d\udc45|\ud83d\udc46|\ud83d\udc47|\ud83d\udc48|\ud83d\udc49|\ud83d\udc4a|\ud83d\udc4b|\ud83d\udc4c|\ud83d\udc4d|\ud83d\udc4e|\ud83d\udc4f|\ud83d\udc50|\ud83d\udc51|\ud83d\udc52|\ud83d\udc53|\ud83d\udc54|\ud83d\udc55|\ud83d\udc56|\ud83d\udc57|\ud83d\udc58|\ud83d\udc59|\ud83d\udc5a|\ud83d\udc5b|\ud83d\udc5c|\ud83d\udc5d|\ud83d\udc5e|\ud83d\udc5f|\ud83d\udc60|\ud83d\udc61|\ud83d\udc62|\ud83d\udc63|\ud83d\udc64|\ud83d\udc66|\ud83d\udc67|\ud83d\udc68|\ud83d\udc69|\ud83d\udc6a|\ud83d\udc6b|\ud83d\udc6e|\ud83d\udc6f|\ud83d\udc70|\ud83d\udc71|\ud83d\udc72|\ud83d\udc73|\ud83d\udc74|\ud83d\udc75|\ud83d\udc76|\ud83d\udeb4|\ud83d\udc78|\ud83d\udc79|\ud83d\udc7a|\ud83d\udc7b|\ud83d\udc7c|\ud83d\udc7d|\ud83d\udc7e|\ud83d\udc7f|\ud83d\udc80|\ud83d\udc81|\ud83d\udc82|\ud83d\udc83|\ud83d\udc84|\ud83d\udc85|\ud83d\udc86|\ud83d\udc87|\ud83d\udc88|\ud83d\udc89|\ud83d\udc8a|\ud83d\udc8b|\ud83d\udc8c|\ud83d\udc8d|\ud83d\udc8e|\ud83d\udc8f|\ud83d\udc90|\ud83d\udc91|\ud83d\udc92|\ud83d\udc93|\ud83d\udc94|\ud83d\udc95|\ud83d\udc96|\ud83d\udc97|\ud83d\udc98|\ud83d\udc99|\ud83d\udc9a|\ud83d\udc9b|\ud83d\udc9c|\ud83d\udc9d|\ud83d\udc9e|\ud83d\udc9f|\ud83d\udca0|\ud83d\udca1|\ud83d\udca2|\ud83d\udca3|\ud83d\udca4|\ud83d\udca5|\ud83d\udca6|\ud83d\udca7|\ud83d\udca8|\ud83d\udca9|\ud83d\udcaa|\ud83d\udcab|\ud83d\udcac|\ud83d\udcae|\ud83d\udcaf|\ud83d\udcb0|\ud83d\udcb1|\ud83d\udcb2|\ud83d\udcb3|\ud83d\udcb4|\ud83d\udcb5|\ud83d\udcb8|\ud83d\udcb9|\ud83d\udcba|\ud83d\udcbb|\ud83d\udcbc|\ud83d\udcbd|\ud83d\udcbe|\ud83d\udcbf|\ud83d\udcc0|\ud83d\udcc1|\ud83d\udcc2|\ud83d\udcc3|\ud83d\udcc4|\ud83d\udcc5|\ud83d\udcc6|\ud83d\udcc7|\ud83d\udcc8|\ud83d\udcc9|\ud83d\udcca|\ud83d\udccb|\ud83d\udccc|\ud83d\udccd|\ud83d\udcce|\ud83d\udccf|\ud83d\udcd0|\ud83d\udcd1|\ud83d\udcd2|\ud83d\udcd3|\ud83d\udcd4|\ud83d\udcd5|\ud83d\udcd6|\ud83d\udcd7|\ud83d\udcd8|\ud83d\udcd9|\ud83d\udcda|\ud83d\udcdb|\ud83d\udcdc|\ud83d\udcdd|\ud83d\udcde|\ud83d\udcdf|\ud83d\udce0|\ud83d\udce1|\ud83d\udce2|\ud83d\udce3|\ud83d\udce4|\ud83d\udce5|\ud83d\udce6|\ud83d\udce7|\ud83d\udce8|\ud83d\udce9|\ud83d\udcea|\ud83d\udceb|\ud83d\udcee|\ud83d\udcf0|\ud83d\udcf1|\ud83d\udcf2|\ud83d\udcf3|\ud83d\udcf4|\ud83d\udcf6|\ud83d\udcf7|\ud83d\udcf9|\ud83d\udcfa|\ud83d\udcfb|\ud83d\udcfc|\ud83d\udd03|\ud83d\udd0a|\ud83d\udd0b|\ud83d\udd0c|\ud83d\udd0d|\ud83d\udd0e|\ud83d\udd0f|\ud83d\udd10|\ud83d\udd11|\ud83d\udd12|\ud83d\udd13|\ud83d\udd14|\ud83d\udd16|\ud83d\udd17|\ud83d\udd18|\ud83d\udd19|\ud83d\udd1a|\ud83d\udd1b|\ud83d\udd1c|\ud83d\udd1d|\ud83d\udd1e|\ud83d\udd1f|\ud83d\udd20|\ud83d\udd21|\ud83d\udd22|\ud83d\udd23|\ud83d\udd24|\ud83d\udd25|\ud83d\udd26|\ud83d\udd27|\ud83d\udd28|\ud83d\udd29|\ud83d\udd2a|\ud83d\udd2b|\ud83d\udd2e|\ud83d\udd2f|\ud83d\udd30|\ud83d\udd31|\ud83d\udd32|\ud83d\udd33|\ud83d\udd34|\ud83d\udd35|\ud83d\udd36|\ud83d\udd37|\ud83d\udd38|\ud83d\udd39|\ud83d\udd3a|\ud83d\udd3b|\ud83d\udd3c|\ud83d\udd3d|\ud83d\udd50|\ud83d\udd51|\ud83d\udd52|\ud83d\udd53|\ud83d\udd54|\ud83d\udd55|\ud83d\udd56|\ud83d\udd57|\ud83d\udd58|\ud83d\udd59|\ud83d\udd5a|\ud83d\udd5b|\ud83d\uddfb|\ud83d\uddfc|\ud83d\uddfd|\ud83d\uddfe|\ud83d\uddff|\ud83d\ude01|\ud83d\ude02|\ud83d\ude03|\ud83d\ude04|\ud83d\ude05|\ud83d\ude06|\ud83d\ude09|\ud83d\ude0a|\ud83d\ude0b|\ud83d\ude0c|\ud83d\ude0d|\ud83d\ude0f|\ud83d\ude12|\ud83d\ude13|\ud83d\ude14|\ud83d\ude16|\ud83d\ude18|\ud83d\ude1a|\ud83d\ude1c|\ud83d\ude1d|\ud83d\ude1e|\ud83d\ude20|\ud83d\ude21|\ud83d\ude22|\ud83d\ude23|\ud83d\ude24|\ud83d\ude25|\ud83d\ude28|\ud83d\ude29|\ud83d\ude2a|\ud83d\ude2b|\ud83d\ude2d|\ud83d\ude30|\ud83d\ude31|\ud83d\ude32|\ud83d\ude33|\ud83d\ude35|\ud83d\ude37|\ud83d\ude38|\ud83d\ude39|\ud83d\ude3a|\ud83d\ude3b|\ud83d\ude3c|\ud83d\ude3d|\ud83d\ude3e|\ud83d\ude3f|\ud83d\ude40|\ud83d\ude45|\ud83d\ude46|\ud83d\ude47|\ud83d\ude48|\ud83d\ude49|\ud83d\ude4a|\ud83d\ude4b|\ud83d\ude4c|\ud83d\ude4d|\ud83d\ude4e|\ud83d\ude4f|\ud83d\ude80|\ud83d\ude83|\ud83d\ude84|\ud83d\ude85|\ud83d\ude87|\ud83d\ude89|\ud83d\ude8c|\ud83d\ude8f|\ud83d\ude91|\ud83d\ude92|\ud83d\ude93|\ud83d\ude95|\ud83d\ude97|\ud83d\ude99|\ud83d\ude9a|\ud83d\udea2|\ud83d\udea4|\ud83d\udea5|\ud83d\udea7|\ud83d\udea8|\ud83d\udea9|\ud83d\udeaa|\ud83d\udeab|\ud83d\udeac|\ud83d\udead|\ud83d\udeb2|\ud83d\udeb6|\ud83d\udeb9|\ud83d\udeba|\ud83d\udebb|\ud83d\udebc|\ud83d\udebd|\ud83d\udebe|\ud83d\udec0|\ud83c\udde6|\ud83c\udde7|\ud83c\udde8|\ud83c\udde9|\ud83c\uddea|\ud83c\uddeb|\ud83c\uddec|\ud83c\udded|\ud83c\uddee|\ud83c\uddef|\ud83c\uddf0|\ud83c\uddf1|\ud83c\uddf2|\ud83c\uddf3|\ud83c\uddf4|\ud83c\uddf5|\ud83c\uddf6|\ud83c\uddf7|\ud83c\uddf8|\ud83c\uddf9|\ud83c\uddfa|\ud83c\uddfb|\ud83c\uddfc|\ud83c\uddfd|\ud83c\uddfe|\ud83c\uddff|\ud83c\udf0d|\ud83c\udf0e|\ud83c\udf10|\ud83c\udf12|\ud83c\udf16|\ud83c\udf17|\ue50a|\u3030|\u27b0|\u2797|\u2796|\u2795|\u2755|\u2754|\u2753|\u274e|\u274c|\u2728|\u270b|\u270a|\u2705|\u26ce|\u23f3|\u23f0|\u23ec|\u23eb|\u23ea|\u23e9|\u2122|\u27bf|\u00a9|\u00ae)|(?:(?:\ud83c\udc04|\ud83c\udd7f|\ud83c\ude1a|\ud83c\ude2f|\u3299|\u303d|\u2b55|\u2b50|\u2b1c|\u2b1b|\u2b07|\u2b06|\u2b05|\u2935|\u2934|\u27a1|\u2764|\u2757|\u2747|\u2744|\u2734|\u2733|\u2716|\u2714|\u2712|\u270f|\u270c|\u2709|\u2708|\u2702|\u26fd|\u26fa|\u26f5|\u26f3|\u26f2|\u26ea|\u26d4|\u26c5|\u26c4|\u26be|\u26bd|\u26ab|\u26aa|\u26a1|\u26a0|\u2693|\u267f|\u267b|\u3297|\u2666|\u2665|\u2663|\u2660|\u2653|\u2652|\u2651|\u2650|\u264f|\u264e|\u264d|\u264c|\u264b|\u264a|\u2649|\u2648|\u263a|\u261d|\u2615|\u2614|\u2611|\u260e|\u2601|\u2600|\u25fe|\u25fd|\u25fc|\u25fb|\u25c0|\u25b6|\u25ab|\u25aa|\u24c2|\u231b|\u231a|\u21aa|\u21a9|\u2199|\u2198|\u2197|\u2196|\u2195|\u2194|\u2139|\u2049|\u203c|\u2668)([\uFE0E\uFE0F]?)))/g, - - SVGPATH: SVGPATH, - ZREKNARF: '' + SVGPATH + '', - CHAT_BUTTON: '' + SVGPATH + '', - - ROOMS: '', - CAMERA: '', - INVITE: '', - - LIVE: '', - - EYE: '', - CLOCK: '', - GEAR: '', - HEART: '', - EMOTE: '', - STAR: '', - CLOSE: '', - - EDIT: '', - - GRAPH: '' -} -},{}],6:[function(require,module,exports){ -var FFZ = window.FrankerFaceZ; - - -// ----------------------- -// Developer Mode -// ----------------------- - -FFZ.settings_info.developer_mode = { - type: "boolean", - value: false, - storage_key: "ffzDebugMode", - - visible: function() { return this.settings.developer_mode || (Date.now() - parseInt(localStorage.ffzLastDevMode || "0")) < 604800000; }, - category: "Debugging", - name: "Developer Mode", - help: "Load FrankerFaceZ from the local development server instead of the CDN. Please refresh after changing this setting.", - - on_update: function() { - localStorage.ffzLastDevMode = Date.now(); - } - }; - - -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; - else if ( args == "n" || args == "no" || args == "false" || args == "off" ) - enabled = false; - - if ( enabled === undefined ) - return "Developer Mode is currently " + (this.settings.developer_mode ? "enabled." : "disabled."); - - this.settings.set("developer_mode", enabled); - return "Developer Mode is now " + (enabled ? "enabled" : "disabled") + ". Please refresh your browser."; -} - -FFZ.ffz_commands.developer_mode.help = "Usage: /ffz developer_mode \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."; - -},{}],7:[function(require,module,exports){ -var FFZ = window.FrankerFaceZ, - utils = require('../utils'), - constants = require('../constants'); - - -// -------------------- -// Initialization -// -------------------- - -FFZ.prototype.setup_channel = function() { - // Style Stuff! - this.log("Creating channel style element."); - var s = this._channel_style = document.createElement("style"); - s.id = "ffz-channel-css"; - document.head.appendChild(s); - - // Settings stuff! - document.body.classList.toggle("ffz-hide-view-count", !this.settings.channel_views); - - this.log("Creating channel style element."); - var s = this._channel_style = document.createElement('style'); - s.id = "ffz-channel-css"; - document.head.appendChild(s); - - this.log("Hooking the Ember Channel Index view."); - var Channel = App.__container__.resolve('view:channel/index'), - f = this; - - if ( ! Channel ) - return; - - this._modify_cindex(Channel); - - // The Stupid View Fix. Is this necessary still? - try { - Channel.create().destroy(); - } catch(err) { } - - // Update Existing - for(var key in Ember.View.views) { - if ( ! Ember.View.views.hasOwnProperty(key) ) - continue; - - var view = Ember.View.views[key]; - if ( !(view instanceof Channel) ) - continue; - - this.log("Manually updating Channel Index view.", view); - this._modify_cindex(view); - view.ffzInit(); - }; - - - this.log("Hooking the Ember Channel model."); - Channel = App.__container__.resolve('model:channel'); - if ( ! Channel ) - return; - - Channel.reopen({ - ffz_host_target: undefined, - - setHostMode: function(e) { - if ( f.settings.hosted_channels ) { - this.set('ffz_host_target', e.target); - return this._super(e); - } else { - this.set('ffz_host_target', undefined); - return this._super({target: void 0, delay: 0}); - } - } - }); - - - this.log("Hooking the Ember Channel controller."); - - Channel = App.__container__.lookup('controller:channel'); - if ( ! Channel ) - return; - - Channel.reopen({ - ffzUpdateUptime: function() { - if ( f._cindex ) - f._cindex.ffzUpdateUptime(); - - }.observes("isLive", "content.id"), - - ffzUpdateInfo: function() { - if ( this._ffz_update_timer ) - clearTimeout(this._ffz_update_timer); - - if ( ! this.get('content.id') ) - return; - - this._ffz_update_timer = setTimeout(this.ffzCheckUpdate.bind(this), 60000); - }.observes("content.id"), - - ffzCheckUpdate: function() { - var t = this, - id = t.get('content.id'); - - id && Twitch.api && Twitch.api.get("streams/" + id, {}, {version:3}) - .done(function(data) { - if ( ! data || ! data.stream ) { - // If the stream is offline, clear its created_at time and set it to zero viewers. - t.set('stream.created_at', null); - t.set('stream.viewers', 0); - return; - } - - t.set('stream.created_at', data.stream.created_at || null); - t.set('stream.viewers', data.stream.viewers || 0); - - var game = data.stream.game || (data.stream.channel && data.stream.channel.game); - if ( game ) { - t.set('game', game); - t.set('rollbackData.game', game); - } - - if ( data.stream.channel ) { - if ( data.stream.channel.status ) - t.set('status', data.stream.channel.status); - - if ( data.stream.channel.views ) - t.set('views', data.stream.channel.views); - - if ( data.stream.channel.followers && t.get('content.followers.isLoaded') ) - t.set('content.followers.total', data.stream.channel.followers); - } - - }) - .always(function(data) { - t.ffzUpdateInfo(); - }); - }, - - - ffzUpdateTitle: function() { - var name = this.get('content.name'), - display_name = this.get('content.display_name'); - - if ( display_name ) - FFZ.capitalization[name] = [display_name, Date.now()]; - - if ( f._cindex ) - f._cindex.ffzFixTitle(); - }.observes("content.status", "content.id"), - - ffzHostTarget: function() { - var target = this.get('content.hostModeTarget'), - name = target && target.get('name'), - id = target && target.get('id'), - display_name = target && target.get('display_name'); - - if ( id !== f.__old_host_target ) { - if ( f.__old_host_target ) - f.ws_send("unsub_channel", f.__old_host_target); - - if ( id ) { - f.ws_send("sub_channel", id); - f.__old_host_target = id; - } else - delete f.__old_host_target; - } - - if ( display_name ) - FFZ.capitalization[name] = [display_name, Date.now()]; - - if ( f.settings.group_tabs && f._chatv ) - f._chatv.ffzRebuildTabs(); - - if ( f.settings.follow_buttons ) - f.rebuild_following_ui(); - - if ( f.settings.srl_races ) - f.rebuild_race_ui(); - - }.observes("content.hostModeTarget") - }); - - Channel.ffzUpdateInfo(); -} - - -FFZ.prototype._modify_cindex = function(view) { - var f = this; - - view.reopen({ - didInsertElement: function() { - this._super(); - try { - this.ffzInit(); - } catch(err) { - f.error("CIndex didInsertElement: " + err); - } - }, - - willClearRender: function() { - try { - this.ffzTeardown(); - } catch(err) { - f.error("CIndex willClearRender: " + err); - } - return this._super(); - }, - - ffzInit: function() { - var id = this.get('controller.id'), - el = this.get('element'); - - f._cindex = this; - f.ws_send("sub_channel", id); - - el.setAttribute('data-channel', id); - el.classList.add('ffz-channel'); - - // Try changing the theater mode tooltip. - this.$('.theatre-button a').attr('title', 'Theater Mode (Alt+T)'); - - this.ffzFixTitle(); - this.ffzUpdateUptime(); - this.ffzUpdateChatters(); - this.ffzUpdateHostButton(); - this.ffzUpdatePlayerStats(); - - var views = this.get('element').querySelector('.svg-glyph_views:not(.ffz-svg)') - if ( views ) - views.parentNode.classList.add('twitch-channel-views'); - - if ( f.settings.follow_buttons ) - f.rebuild_following_ui(); - - if ( f.settings.srl_races ) - f.rebuild_race_ui(); - - if ( f.settings.auto_theater ) { - var Layout = App.__container__.lookup('controller:layout'); - if ( Layout ) - Layout.set('isTheatreMode', true); - } - }, - - ffzFixTitle: function() { - if ( f.has_bttv || ! f.settings.stream_title ) - return; - - var status = this.get("controller.status"), - channel = this.get("controller.id"); - - status = f.render_tokens(f.tokenize_line(channel, channel, status, true)); - - this.$(".title span").each(function(i, el) { - var scripts = el.querySelectorAll("script"); - if ( ! scripts.length ) - el.innerHTML = status; - else - el.innerHTML = scripts[0].outerHTML + status + scripts[1].outerHTML; - }); - }, - - - ffzUpdateHostButton: function() { - var channel_id = this.get('controller.id'), - hosted_id = this.get('controller.hostModeTarget.id'), - - user = f.get_user(), - room = user && f.rooms && f.rooms[user.login] && f.rooms[user.login].room, - now_hosting = room && room.ffz_host_target, - hosts_left = room && room.ffz_hosts_left, - - el = this.get('element'); - - this.set('ffz_host_updating', false); - - if ( channel_id ) { - var container = el && el.querySelector('.stats-and-actions .channel-actions'), - btn = container && container.querySelector('#ffz-ui-host-button'); - - if ( ! container || ! f.settings.stream_host_button || ! user || user.login === channel_id ) { - if ( btn ) - btn.parentElement.removeChild(btn); - } else { - if ( ! btn ) { - btn = document.createElement('span'); - btn.id = 'ffz-ui-host-button'; - btn.className = 'button action tooltip'; - - btn.addEventListener('click', this.ffzClickHost.bind(btn, this, false)); - - var before; - try { before = container.querySelector(':scope > .theatre-button'); } - catch(err) { before = undefined; } - - if ( before ) - container.insertBefore(btn, before); - else - container.appendChild(btn); - } - - btn.classList.remove('disabled'); - btn.innerHTML = channel_id === now_hosting ? 'Unhost' : 'Host'; - if ( now_hosting ) - btn.title = 'You are now hosting ' + utils.sanitize(FFZ.get_capitalization(now_hosting)) + '.'; - else - btn.title = 'You are not hosting any channel.'; - - if ( typeof hosts_left === "number" ) - btn.title += ' You have ' + hosts_left + ' host command' + utils.pluralize(hosts_left) + ' remaining this half hour.'; - } - } - - - if ( hosted_id ) { - var container = el && el.querySelector('#hostmode .channel-actions'), - btn = container && container.querySelector('#ffz-ui-host-button'); - - if ( ! container || ! f.settings.stream_host_button || ! user || user.login === hosted_id ) { - if ( btn ) - btn.parentElement.removeChild(btn); - } else { - if ( ! btn ) { - btn = document.createElement('span'); - btn.id = 'ffz-ui-host-button'; - btn.className = 'button action tooltip'; - - btn.addEventListener('click', this.ffzClickHost.bind(btn, this, true)); - - var before; - try { before = container.querySelector(':scope > .theatre-button'); } - catch(err) { before = undefined; } - - if ( before ) - container.insertBefore(btn, before); - else - container.appendChild(btn); - } - - btn.classList.remove('disabled'); - btn.innerHTML = hosted_id === now_hosting ? 'Unhost' : 'Host'; - if ( now_hosting ) - btn.title = 'You are currently hosting ' + utils.sanitize(FFZ.get_capitalization(now_hosting)) + '. Click to ' + (hosted_id === now_hosting ? 'unhost' : 'host') + ' this channel.'; - else - btn.title = 'You are not currently hosting any channel. Click to host this channel.'; - - if ( typeof hosts_left === "number" ) - btn.title += ' You have ' + hosts_left + ' host command' + utils.pluralize(hosts_left) + ' remaining this half hour.'; - } - } - }, - - ffzClickHost: function(controller, is_host) { - var target = controller.get(is_host ? 'controller.hostModeTarget.id' : 'controller.id'), - user = f.get_user(), - room = user && f.rooms && f.rooms[user.login] && f.rooms[user.login].room, - now_hosting = room && room.ffz_host_target; - - if ( ! room || controller.get('ffz_host_updating') ) - return; - - this.classList.add('disabled'); - this.title = 'Updating...'; - - controller.set('ffz_host_updating', true); - if ( now_hosting === target ) - room.send("/unhost"); - else - room.send("/host " + target); - }, - - - ffzUpdateChatters: function() { - // Get the counts. - var room_id = this.get('controller.id'), - room = f.rooms && f.rooms[room_id]; - - if ( ! room || ! f.settings.chatter_count ) { - var el = this.get('element').querySelector('#ffz-chatter-display'); - el && el.parentElement.removeChild(el); - el = this.get('element').querySelector('#ffz-ffzchatter-display'); - el && el.parentElement.removeChild(el); - return; - } - - var chatter_count = Object.keys(room.room.get('ffz_chatters') || {}).length, - ffz_chatters = room.ffz_chatters || 0, - ffz_viewers = room.ffz_viewers || 0; - - var el = this.get('element').querySelector('#ffz-chatter-display span'); - if ( ! el ) { - var cont = this.get('element').querySelector('.stats-and-actions .channel-stats'); - if ( ! cont ) - return; - - var stat = document.createElement('span'); - stat.className = 'ffz stat'; - stat.id = 'ffz-chatter-display'; - stat.title = "Currently in Chat"; - - stat.innerHTML = constants.ROOMS + " "; - el = document.createElement("span"); - stat.appendChild(el); - - var other = cont.querySelector("#ffz-ffzchatter-display"); - if ( other ) - cont.insertBefore(stat, other); - else - cont.appendChild(stat); - - jQuery(stat).tipsy(); - } - - el.innerHTML = utils.number_commas(chatter_count); - - if ( ! ffz_chatters && ! ffz_viewers ) { - el = this.get('element').querySelector('#ffz-ffzchatter-display'); - el && el.parentNode.removeChild(el); - return; - } - - el = this.get('element').querySelector('#ffz-ffzchatter-display span'); - if ( ! el ) { - var cont = this.get('element').querySelector('.stats-and-actions .channel-stats'); - if ( ! cont ) - return; - - var stat = document.createElement('span'); - stat.className = 'ffz stat'; - stat.id = 'ffz-ffzchatter-display'; - stat.title = "Viewers (In Chat) with FrankerFaceZ"; - - stat.innerHTML = constants.ZREKNARF + " "; - el = document.createElement("span"); - stat.appendChild(el); - - var other = cont.querySelector("#ffz-chatter-display"); - if ( other ) - cont.insertBefore(stat, other.nextSibling); - else - cont.appendChild(stat); - - jQuery(stat).tipsy(); - } - - el.innerHTML = utils.number_commas(ffz_viewers) + " (" + utils.number_commas(ffz_chatters) + ")"; - }, - - - ffzUpdatePlayerStats: function() { - var channel_id = this.get('controller.id'), - hosted_id = this.get('controller.hostModeTarget.id'), - - el = this.get('element'); - - if ( channel_id ) { - var container = el && el.querySelector('.stats-and-actions .channel-stats'), - stat_el = container && container.querySelector('#ffz-ui-player-stats'), - el = stat_el && stat_el.querySelector('span'), - - player_cont = f.players && f.players[channel_id], - player = player_cont && player_cont.player, - stats = player && player.stats; - - - if ( ! container || ! f.settings.player_stats || ! stats || stats.hlsLatencyBroadcaster === 'NaN' || stats.hlsLatencyBroadcaster === NaN ) { - if ( stat_el ) - stat_el.parentElement.removeChild(stat_el); - } else { - if ( ! stat_el ) { - stat_el = document.createElement('span'); - stat_el.id = 'ffz-ui-player-stats'; - stat_el.className = 'ffz stat tooltip'; - - stat_el.innerHTML = constants.GRAPH + " "; - el = document.createElement('span'); - stat_el.appendChild(el); - - var other = container.querySelector('#ffz-uptime-display'); - if ( other ) - container.insertBefore(stat_el, other.nextSibling); - else - container.appendChild(stat_el); - } - - stat_el.title = 'Stream Latency\nFPS: ' + stats.fps + '\nPlayback Rate: ' + stats.playbackRate + ' Kbps'; - el.textContent = stats.hlsLatencyBroadcaster + 's'; - } - } - - - if ( hosted_id ) { - var container = el && el.querySelector('#hostmode .channel-stats'), - stat_el = container && container.querySelector('#ffz-ui-player-stats'), - el = stat_el && stat_el.querySelector('span'), - - player_cont = f.players && f.players[hosted_id], - player = player_cont && player_cont.player, - stats = player && player.stats; - - - if ( ! container || ! f.settings.player_stats || ! stats || stats.hlsLatencyBroadcaster === 'NaN' || stats.hlsLatencyBroadcaster === NaN ) { - if ( stat_el ) - stat_el.parentElement.removeChild(stat_el); - } else { - if ( ! stat_el ) { - stat_el = document.createElement('span'); - stat_el.id = 'ffz-ui-player-stats'; - stat_el.className = 'ffz stat tooltip'; - - stat_el.innerHTML = constants.GRAPH + " "; - el = document.createElement('span'); - stat_el.appendChild(el); - - var other = container.querySelector('#ffz-uptime-display'); - if ( other ) - container.insertBefore(stat_el, other.nextSibling); - else - container.appendChild(stat_el); - } - - stat_el.title = 'Stream Latency\nFPS: ' + stats.fps + '\nPlayback Rate: ' + stats.playbackRate + ' Kbps'; - el.textContent = stats.hlsLatencyBroadcaster + 's'; - } - } - }, - - - ffzUpdateUptime: function() { - if ( this._ffz_update_uptime ) { - clearTimeout(this._ffz_update_uptime); - delete this._ffz_update_uptime; - } - - if ( ! f.settings.stream_uptime || ! this.get("controller.isLiveAccordingToKraken") ) { - var el = this.get('element').querySelector('#ffz-uptime-display'); - if ( el ) - el.parentElement.removeChild(el); - return; - } - - // Schedule an update. - this._ffz_update_uptime = setTimeout(this.ffzUpdateUptime.bind(this), 1000); - - // Determine when the channel last went live. - var online = this.get("controller.content.stream.created_at"); - online = online && utils.parse_date(online); - - var uptime = online && Math.floor((Date.now() - online.getTime()) / 1000) || -1; - if ( uptime < 0 ) { - var el = this.get('element').querySelector('#ffz-uptime-display'); - if ( el ) - el.parentElement.removeChild(el); - return; - } - - var el = this.get('element').querySelector('#ffz-uptime-display span'); - if ( ! el ) { - var cont = this.get('element').querySelector('.stats-and-actions .channel-stats'); - if ( ! cont ) - return; - - var stat = document.createElement('span'); - stat.className = 'ffz stat'; - stat.id = 'ffz-uptime-display'; - stat.title = "Stream Uptime (since " + online.toLocaleString() + ")"; - - stat.innerHTML = constants.CLOCK + " "; - el = document.createElement("span"); - stat.appendChild(el); - - var viewers = cont.querySelector(".live-count"); - if ( viewers ) - cont.insertBefore(stat, viewers.nextSibling); - else { - try { - viewers = cont.querySelector("script:nth-child(0n+2)"); - cont.insertBefore(stat, viewers.nextSibling); - } catch(err) { - cont.insertBefore(stat, cont.childNodes[0]); - } - } - - jQuery(stat).tipsy({html: true}); - } - - el.innerHTML = utils.time_to_string(uptime); - }, - - ffzTeardown: function() { - var id = this.get('controller.id'); - if ( id ) - f.ws_send("unsub_channel", id); - - this.get('element').setAttribute('data-channel', ''); - f._cindex = undefined; - if ( this._ffz_update_uptime ) - clearTimeout(this._ffz_update_uptime); - - utils.update_css(f._channel_style, id, null); - } - }); -} - - -// --------------- -// Settings -// --------------- - -FFZ.settings_info.auto_theater = { - type: "boolean", - value: false, - - category: "Appearance", - no_mobile: true, - no_bttv: true, - - name: "Automatic Theater Mode", - help: "Automatically enter theater mode when opening a channel." - }; - - -FFZ.settings_info.chatter_count = { - type: "boolean", - value: false, - no_mobile: true, - - category: "Channel Metadata", - - name: "Chatter Count", - help: "Display the current number of users connected to chat beneath the channel.", - - on_update: function(val) { - if ( this._cindex ) - this._cindex.ffzUpdateChatters(); - - if ( ! val || ! this.rooms ) - return; - - // Refresh the data. - for(var room_id in this.rooms) - this.rooms.hasOwnProperty(room_id) && this.rooms[room_id].room && this.rooms[room_id].room.ffzInitChatterCount(); - } - }; - - -FFZ.settings_info.channel_views = { - type: "boolean", - value: true, - no_mobile: true, - - category: "Channel Metadata", - name: "Channel Views", - help: 'Display the number of times the channel has been viewed beneath the stream.', - on_update: function(val) { - document.body.classList.toggle("ffz-hide-view-count", !val); - } - }; - - -FFZ.settings_info.hosted_channels = { - type: "boolean", - value: true, - no_mobile: true, - - category: "Channel Metadata", - name: "Channel Hosting", - help: "Display other channels that have been featured by the current channel.", - on_update: function(val) { - var cb = document.querySelector('input.ffz-setting-hosted-channels'); - if ( cb ) - cb.checked = val; - - if ( ! this._cindex ) - return; - - var chan = this._cindex.get('controller.model'), - room = chan && this.rooms && this.rooms[chan.get('id')], - target = room && room.room && room.room.get('ffz_host_target'); - if ( ! chan || ! room ) - return; - - chan.setHostMode({target: target, delay: 0}); - } - }; - - -FFZ.settings_info.stream_host_button = { - type: "boolean", - value: true, - no_mobile: true, - - category: "Channel Metadata", - name: "Host This Channel Button", - help: "Display a button underneath streams that make it easy to host them with your own channel.", - on_update: function(val) { - if ( this._cindex ) - this._cindex.ffzUpdateHostButton(); - } - }; - - -FFZ.settings_info.stream_uptime = { - type: "boolean", - value: false, - no_mobile: true, - - category: "Channel Metadata", - name: "Stream Uptime", - help: 'Display the stream uptime under a channel by the viewer count.', - on_update: function(val) { - if ( this._cindex ) - this._cindex.ffzUpdateUptime(); - } - }; - - -FFZ.settings_info.stream_title = { - type: "boolean", - value: true, - no_bttv: true, - no_mobile: true, - - category: "Channel Metadata", - name: "Title Links", - help: "Make links in stream titles clickable.", - on_update: function(val) { - if ( this._cindex ) - this._cindex.ffzFixTitle(); - } - }; -},{"../constants":5,"../utils":35}],8:[function(require,module,exports){ -var FFZ = window.FrankerFaceZ, - utils = require("../utils"), - constants = require("../constants"), - - is_android = navigator.userAgent.indexOf('Android') !== -1, - - KEYCODES = { - BACKSPACE: 8, - TAB: 9, - ENTER: 13, - ESC: 27, - SPACE: 32, - LEFT: 37, - UP: 38, - RIGHT: 39, - DOWN: 40, - TWO: 50, - COLON: 59, - FAKE_COLON: 186 - }, - - selection_start = function(e) { - if ( typeof e.selectionStart === "number" ) - return e.selectionStart; - - if ( ! e.createTextRange ) - return -1; - - var n = document.selection.createRange(), - r = e.createTextRange(); - - r.moveToBookmark(n.getBookmark()); - r.moveStart("character", -e.value.length); - return r.text.length; - }, - - move_selection = function(e, pos) { - if ( e.setSelectionRange ) - e.setSelectionRange(pos, pos); - else if ( e.createTextRange ) { - var r = e.createTextRange(); - r.move("character", -e.value.length); - r.move("character", pos); - r.select(); - } - }; - - -// --------------------- -// Settings -// --------------------- - -FFZ.settings_info.input_quick_reply = { - type: "boolean", - value: true, - - category: "Chat Input", - no_bttv: true, - - name: "Reply to Whispers with /r", - help: "Automatically replace /r at the start of the line with the command to whisper to the person you've whispered with most recently." -}; - -FFZ.settings_info.input_mru = { - type: "boolean", - value: true, - - category: "Chat Input", - no_bttv: true, - - name: "Chat Input History", - help: "Use the Up and Down arrows in chat to select previously sent chat messages." -}; - -FFZ.settings_info.input_emoji = { - type: "boolean", - value: false, - - category: "Chat Input", - //visible: false, - no_bttv: true, - - name: "Enter Emoji By Name", - help: "Replace emoji that you type by name with the character. :+1: becomes 👍." -}; - - -// --------------------- -// Initialization -// --------------------- - -FFZ.prototype.setup_chat_input = function() { - this.log("Hooking the Ember Chat Input controller."); - var Input = App.__container__.resolve('component:twitch-chat-input'), - f = this; - - if ( ! Input ) - return; - - this._modify_chat_input(Input); - - if ( this._roomv ) { - for(var i=0; i < this._roomv._childViews.length; i++) { - var v = this._roomv._childViews[i]; - if ( v instanceof Input ) { - this._modify_chat_input(v); - v.ffzInit(); - } - } - } -} - - -FFZ.prototype._modify_chat_input = function(component) { - var f = this; - - component.reopen({ - ffz_mru_index: -1, - - didInsertElement: function() { - this._super(); - - try { - this.ffzInit(); - } catch(err) { f.error("ChatInput didInsertElement: " + err); } - }, - - willClearRender: function() { - try { - this.ffzTeardown(); - } catch(err) { f.error("ChatInput willClearRender: " + err); } - return this._super(); - }, - - ffzInit: function() { - f._inputv = this; - - var s = this._ffz_minimal_style = document.createElement('style'); - s.id = 'ffz-minimal-chat-textarea-height'; - document.head.appendChild(s); - - // Redo our key bindings. - var t = this.$("textarea"); - - t.off("keydown"); - t.on("keydown", this._ffzKeyDown.bind(this)); - - t.attr('rows', 1); - - this.ffzResizeInput(); - setTimeout(this.ffzResizeInput.bind(this), 500); - - /*var suggestions = this._parentView.get('context.model.chatSuggestions'); - this.set('ffz_chatters', suggestions);*/ - }, - - ffzTeardown: function() { - if ( f._inputv === this ) - f._inputv = undefined; - - this.ffzResizeInput(); - - if ( this._ffz_minimal_style ) { - this._ffz_minimal_style.parentElement.removeChild(this._ffz_minimal_style); - this._ffz_minimal_style = undefined; - } - - // Reset normal key bindings. - var t = this.$("textarea"); - - t.attr('rows', undefined); - - t.off("keydown"); - t.on("keydown", this._onKeyDown.bind(this)); - }, - - // Input Control - - ffzOnInput: function() { - if ( ! f._chat_style || ! f.settings.minimal_chat || is_android ) - return; - - var now = Date.now(), - since = now - (this._ffz_last_resize || 0); - - if ( since > 500 ) - this.ffzResizeInput(); - - }.observes('textareaValue'), - - ffzResizeInput: function() { - this._ffz_last_resize = Date.now(); - - var el = this.get('element'), - t = el && el.querySelector('textarea'); - - if ( ! t || ! f._chat_style || ! f.settings.minimal_chat ) - return; - - // Unfortunately, we need to change this with CSS. - this._ffz_minimal_style.innerHTML = 'body.ffz-minimal-chat .ember-chat .chat-interface .textarea-contain textarea { height: auto !important; }'; - var height = Math.max(32, Math.min(128, t.scrollHeight)); - this._ffz_minimal_style.innerHTML = 'body.ffz-minimal-chat .ember-chat .chat-interface .textarea-contain textarea { height: ' + height + 'px !important; }'; - - if ( height !== this._ffz_last_height ) { - utils.update_css(f._chat_style, "input_height", 'body.ffz-minimal-chat .ember-chat .chat-interface { height: ' + height + 'px !important; }' + - 'body.ffz-minimal-chat .ember-chat .chat-messages, body.ffz-minimal-chat .ember-chat .chat-interface .emoticon-selector { bottom: ' + height + 'px !important; }'); - f._roomv && f._roomv.get('stuckToBottom') && f._roomv._scrollToBottom(); - } - - this._ffz_last_height = height; - }, - - _ffzKeyDown: function(event) { - var e = event || window.event, - key = e.charCode || e.keyCode; - - switch(key) { - case KEYCODES.UP: - case KEYCODES.DOWN: - if ( e.shiftKey || e.shiftLeft || e.ctrlKey || e.metaKey ) - return; - else if ( this.get("isShowingSuggestions") ) - e.preventDefault(); - else if ( f.settings.input_mru ) - Ember.run.next(this.ffzCycleMRU.bind(this, key, selection_start(this.get("chatTextArea")))); - else - return this._onKeyDown(event); - break; - - case KEYCODES.SPACE: - if ( f.settings.input_quick_reply && selection_start(this.get("chatTextArea")) === 2 && this.get("textareaValue").substring(0,2) === "/r" ) { - var t = this; - Ember.run.next(function() { - var wt = t.get("uniqueWhisperSuggestions.0"); - if ( wt ) { - var text = "/w " + wt + t.get("textareaValue").substr(2); - t.set("_currentWhisperTarget", 0); - t.set("textareaValue", text); - - Ember.run.next(function() { - move_selection(t.get('chatTextArea'), 4 + wt.length); - }); - } - }); - } else - return this._onKeyDown(event); - break; - - case KEYCODES.COLON: - case KEYCODES.FAKE_COLON: - if ( f.settings.input_emoji && (e.shiftKey || e.shiftLeft) ) { - var t = this, - ind = selection_start(this.get("chatTextArea")); - - ind > 0 && Ember.run.next(function() { - var text = t.get("textareaValue"), - emoji_start = text.lastIndexOf(":", ind - 1); - - if ( emoji_start !== -1 && ind !== -1 && text.charAt(ind) === ":" ) { - var match = text.substr(emoji_start + 1, ind-emoji_start - 1), - emoji_id = f.emoji_names[match], - emoji = f.emoji_data[emoji_id]; - - if ( emoji ) { - var prefix = text.substr(0, emoji_start) + emoji.raw; - t.set('textareaValue', prefix + text.substr(ind + 1)); - Ember.run.next(function() { - move_selection(t.get('chatTextArea'), prefix.length); - }); - } - } - }); - return; - } - return this._onKeyDown(event); - - case KEYCODES.ENTER: - if ( ! e.shiftKey && ! e.shiftLeft ) - this.set('ffz_mru_index', -1); - - default: - return this._onKeyDown(event); - } - }, - - ffzCycleMRU: function(key, start_ind) { - // We don't want to do this if the keys were just moving the cursor around. - var cur_pos = selection_start(this.get("chatTextArea")); - if ( start_ind !== cur_pos ) - return; - - var ind = this.get('ffz_mru_index'), - mru = this._parentView.get('context.model.mru_list') || []; - - if ( key === KEYCODES.UP ) - ind = (ind + 1) % (mru.length + 1); - else - ind = (ind + mru.length) % (mru.length + 1); - - var old_val = this.get('ffz_old_mru'); - if ( old_val === undefined || old_val === null ) { - old_val = this.get('textareaValue'); - this.set('ffz_old_mru', old_val); - } - - var new_val = mru[ind]; - if ( new_val === undefined ) { - this.set('ffz_old_mru', undefined); - new_val = old_val; - } - - this.set('ffz_mru_index', ind); - this.set('textareaValue', new_val); - }, - - completeSuggestion: function(e) { - var r, n, i = this, - o = this.get("textareaValue"), - a = this.get("partialNameStartIndex"); - - r = o.substring(0, a) + (o.charAt(0) === "/" ? e : FFZ.get_capitalization(e)); - n = o.substring(a + this.get("partialName").length); - if ( ! n ) - r += " "; - - this.set("textareaValue", r + n); - this.set("isShowingSuggestions", false); - this.set("partialName", ""); - this.trackSuggestionsCompleted(); - Ember.run.next(function() { - move_selection(i.get('chatTextArea'), r.length); - }); - } - - /*ffz_emoticons: function() { - var output = [], - - room = this._parentView.get('context.model'), - room_id = room && room.get('id'), - tmi = room && room.tmiSession, - - user = f.get_user(), - ffz_sets = f.getEmotes(user && user.login, room_id); - - if ( tmi ) { - var es = tmi.getEmotes(); - if ( es && es.emoticon_sets ) { - for(var set_id in es.emoticon_sets) { - var emote_set = es.emoticon_sets[set_id]; - for(var emote_id in emote_set) { - if ( emote_set[emote_id] ) { - var code = emote_set[emote_id].code; - output.push({id: constants.KNOWN_CODES[code] || code}); - } - } - } - } - } - - for(var i=0; i < ffz_sets.length; i++) { - var emote_set = f.emote_sets[ffz_sets[i]]; - if ( ! emote_set ) - continue; - - for(var emote_id in emote_set.emoticons) { - var emote = emote_set.emoticons[emote_id]; - if ( ! emote.hidden ) - output.push({id:emote.name}); - } - } - - return output; - }.property(), - - ffz_chatters: [], - - suggestions: function(key, value, previousValue) { - if ( arguments.length > 1 ) { - this.set('ffz_chatters', value); - } - - var output = []; - - // Chatters - output = output.concat(this.get('ffz_chatters')); - - // Emoticons - if ( this.get('isSuggestionsTriggeredWithTab') ) { - output = output.concat(this.get('ffz_emoticons')); - } - - return output; - }.property("ffz_emoticons", "ffz_chatters", "isSuggestionsTriggeredWithTab")*/ - }); -} -},{"../constants":5,"../utils":35}],9:[function(require,module,exports){ -var FFZ = window.FrankerFaceZ, - utils = require('../utils'), - constants = require('../constants'); - - -// -------------------- -// Settings -// -------------------- - -FFZ.basic_settings.cure_cancer = { - type: "boolean", - - category: "Chat", - - name: "Cure Cancer", - help: "Destroys all cancerous chat messages before they can even be seen.", - - get: function() { - return this.settings.remove_deleted && - this.settings.remove_bot_ban_notices && - +this.settings.chat_delay; - }, - - set: function(val) { - this.settings.set('remove_deleted', val); - this.settings.set('remove_bot_ban_notices', val); - this.settings.set('chat_delay', val ? ''+(+this.settings.chat_delay || 300) : '0'); - } -}; - - -FFZ.settings_info.minimal_chat = { - type: "boolean", - value: false, - - category: "Chat Appearance", - - name: "Minimalistic Chat", - help: "Hide all of the chat user interface, only showing messages and an input box.", - - on_update: function(val) { - document.body.classList.toggle("ffz-minimal-chat", val); - if ( this.settings.group_tabs && this._chatv && this._chatv._ffz_tabs ) { - var f = this; - setTimeout(function() { - f._chatv && f._chatv.$('.chat-room').css('top', f._chatv._ffz_tabs.offsetHeight + "px"); - f._roomv && f._roomv.get('stuckToBottom') && f._roomv._scrollToBottom(); - },0); - } - - if ( this._chatv && this._chatv.get('controller.showList') ) - this._chatv.set('controller.showList', false); - - // Remove the style if we have it. - if ( ! val && this._chat_style ) { - if ( this._inputv ) { - if ( this._inputv._ffz_minimal_style ) - this._inputv._ffz_minimal_style.innerHTML = ''; - - this._inputv._ffz_last_height = undefined; - } - - utils.update_css(this._chat_style, "input_height", ''); - this._roomv && this._roomv.get('stuckToBottom') && this._roomv._scrollToBottom(); - - } else if ( this._inputv ) - this._inputv.ffzResizeInput(); - } - }; - - -FFZ.settings_info.chat_delay = { - type: "select", - options: { - 0: "No Delay", - 300: "Wait for bot auto-bans (300ms)", - 1200: "Wait for human mods (1200ms)", - 5000: "ESPORTS (5000ms)" - }, - value: 0, - - category: "Chat Appearance", - name: "Artificial Chat Delay", - help: "Delay messages allowing moderators to ban them before you see them.", - - on_update: function (val) { - var delay_badge = document.querySelector('#ffz-stat-delay'); - delay_badge.title = utils.number_commas(+val||300) + "ms of artifical chat delay added."; - delay_badge.classList.toggle('hidden', !+val); - } -}; - - -FFZ.settings_info.remove_deleted = { - type: "boolean", - value: false, - - no_bttv: true, - - category: "Chat Filtering", - name: "Remove Deleted Messages", - help: "Remove deleted messages from chat entirely rather than leaving behind a clickable <deleted message>.", - - on_update: function(val) { - if ( this.has_bttv || ! this.rooms || ! val ) - return; - - for(var room_id in this.rooms) { - var ffz_room = this.rooms[room_id], - room = ffz_room && ffz_room.room; - if ( ! room ) - continue; - - var msgs = room.get('messages'), - total = msgs.get('length'), - i = total, - alternate; - - while(i--) { - var msg = msgs.get(i); - - if ( msg.ffz_deleted || msg.deleted ) { - if ( alternate === undefined ) - alternate = msg.ffz_alternate; - msgs.removeAt(i); - continue; - } - - if ( alternate === undefined ) - alternate = msg.ffz_alternate; - else { - alternate = ! alternate; - room.set('messages.' + i + '.ffz_alternate', alternate); - } - } - } - } - }; - - -FFZ.settings_info.remove_bot_ban_notices = { - type: "boolean", - value: false, - - category: "Chat Filtering", - name: "Remove Bot Ban Notices", - help: "Remove messages from bots announcing who was banned for what reason and for how long.", -}; - - -FFZ.settings_info.prevent_clear = { - type: "boolean", - value: false, - - no_bttv: true, - - category: "Chat Filtering", - name: "Show Deleted Messages", - help: "Fade deleted messages instead of replacing them, and prevent chat from being cleared.", - - on_update: function(val) { - if ( this.has_bttv || ! this.rooms ) - return; - - for(var room_id in this.rooms) { - var ffz_room = this.rooms[room_id], - room = ffz_room && ffz_room.room; - if ( ! room ) - continue; - - room.get("messages").forEach(function(s, n) { - if ( val && ! s.ffz_deleted && s.deleted ) - room.set("messages." + n + ".deleted", false); - - else if ( s.ffz_deleted && ! val && ! s.deleted ) - room.set("messages." + n + ".deleted", true); - }); - } - } - }; - -FFZ.settings_info.chat_history = { - type: "boolean", - value: true, - - visible: false, - category: "Chat Appearance", - name: "Chat History Alpha", - help: "Load previous chat messages when loading a chat room so you can see what people have been talking about. This currently only works in a handful of channels due to server capacity.", - }; - -FFZ.settings_info.group_tabs = { - type: "boolean", - value: false, - - no_bttv: true, - - category: "Chat Moderation", - name: "Chat Room Tabs Beta", - help: "Enhanced UI for switching the current chat room and noticing new messages.", - - on_update: function(val) { - var enabled = !this.has_bttv && val; - if ( ! this._chatv || enabled === this._group_tabs_state ) - return; - - if ( enabled ) - this._chatv.ffzEnableTabs(); - else - this._chatv.ffzDisableTabs(); - } - }; - - -FFZ.settings_info.pinned_rooms = { - value: [], - visible: false, - }; - -FFZ.settings_info.visible_rooms = { - value: [], - visible: false, - }; - - -// -------------------- -// Initialization -// -------------------- - -FFZ.prototype.setup_chatview = function() { - document.body.classList.toggle("ffz-minimal-chat", this.settings.minimal_chat); - - this.log("Hooking the Ember Chat controller."); - - var Chat = App.__container__.lookup('controller:chat'), - f = this; - - if ( Chat ) { - Chat.reopen({ - ffzUpdateChannels: function() { - if ( ! f._chatv ) - return; - - f._chatv.ffzRebuildMenu(); - if ( f.settings.group_tabs ) - f._chatv.ffzRebuildTabs(); - - }.observes("currentChannelRoom", "connectedPrivateGroupRooms"), - - removeCurrentChannelRoom: function() { - if ( ! f.settings.group_tabs || f.has_bttv ) - return this._super(); - - var room = this.get("currentChannelRoom"), - room_id = room && room.get('id'), - user = f.get_user(); - - if ( ! f.settings.pinned_rooms || f.settings.pinned_rooms.indexOf(room_id) === -1 ) { - if ( room === this.get("currentRoom") ) - this.blurRoom(); - - // Don't destroy it if it's the user's room. - if ( room && user && user.login === room_id ) - room.destroy(); - } - - this.set("currentChannelRoom", void 0); - } - }); - } - - - this.log("Hooking the Ember Chat view."); - - var Chat = App.__container__.resolve('view:chat'); - this._modify_cview(Chat); - - // For some reason, this doesn't work unless we create an instance of the - // chat view and then destroy it immediately. - try { - Chat.create().destroy(); - } catch(err) { } - - // Modify all existing Chat views. - for(var key in Ember.View.views) { - if ( ! Ember.View.views.hasOwnProperty(key) ) - continue; - - var view = Ember.View.views[key]; - if ( !(view instanceof Chat) ) - continue; - - this.log("Manually updating existing Chat view.", view); - try { - view.ffzInit(); - } catch(err) { - this.error("setup: build_ui_link: " + err); - } - } - - - this.log("Hooking the Ember 'Right Column' controller. Seriously..."); - var Column = App.__container__.lookup('controller:right-column'); - if ( ! Column ) - return; - - Column.reopen({ - ffzFixTabs: function() { - if ( f.settings.group_tabs && f._chatv && f._chatv._ffz_tabs ) { - setTimeout(function() { - f._chatv && f._chatv.$('.chat-room').css('top', f._chatv._ffz_tabs.offsetHeight + "px"); - },0); - } - }.observes("firstTabSelected") - }); -} - - -// -------------------- -// Modify Chat View -// -------------------- - -FFZ.prototype._modify_cview = function(view) { - var f = this; - - view.reopen({ - didInsertElement: function() { - this._super(); - - try { - this.ffzInit(); - } catch(err) { - f.error("ChatView didInsertElement: " + err); - } - }, - - willClearRender: function() { - try { - this.ffzTeardown(); - } catch(err) { - f.error("ChatView willClearRender: " + err); - } - this._super(); - }, - - ffzInit: function() { - f._chatv = this; - this.$('.textarea-contain').append(f.build_ui_link(this)); - this.$('.chat-messages').find('.html-tooltip').tipsy({live: true, html: true, gravity: jQuery.fn.tipsy.autoNS}); - - if ( !f.has_bttv && f.settings.group_tabs ) - this.ffzEnableTabs(); - - this.ffzRebuildMenu(); - - setTimeout(function() { - if ( f.settings.group_tabs && f._chatv && f._chatv._ffz_tabs ) - f._chatv.$('.chat-room').css('top', f._chatv._ffz_tabs.offsetHeight + "px"); - - var controller = f._chatv.get('controller'); - controller && controller.set('showList', false); - }, 1000); - }, - - ffzTeardown: function() { - if ( f._chatv === this ) - f._chatv = null; - - this.$('.textarea-contain .ffz-ui-toggle').remove(); - - if ( f.settings.group_tabs ) - this.ffzDisableTabs(); - }, - - ffzChangeRoom: Ember.observer('controller.currentRoom', function() { - f.update_ui_link(); - - var room = this.get('controller.currentRoom'), rows; - room && room.resetUnreadCount(); - - if ( this._ffz_chan_table ) { - rows = jQuery(this._ffz_chan_table); - rows.children('.ffz-room-row').removeClass('active'); - if ( room ) - rows.children('.ffz-room-row[data-room="' + room.get('id') + '"]').addClass('active').children('span').text(''); - } - - if ( this._ffz_group_table ) { - rows = jQuery(this._ffz_group_table); - rows.children('.ffz-room-row').removeClass('active'); - if ( room ) - rows.children('.ffz-room-row[data-room="' + room.get('id') + '"]').addClass('active').children('span').text(''); - } - - if ( !f.has_bttv && f.settings.group_tabs && this._ffz_tabs ) { - var tabs = jQuery(this._ffz_tabs); - tabs.children('.ffz-chat-tab').removeClass('active'); - if ( room && room._ffz_tab ) { - room._ffz_tab.classList.remove('tab-mentioned'); - room._ffz_tab.classList.remove('hidden'); - room._ffz_tab.classList.add('active'); - var sp = room._ffz_tab.querySelector('span'); - if ( sp ) - sp.innerHTML = ''; - } - - // Invite Link - var can_invite = room && room.get('canInvite'); - this._ffz_invite && this._ffz_invite.classList.toggle('hidden', !can_invite); - this.set('controller.showInviteUser', can_invite && this.get('controller.showInviteUser')) - - // Now, adjust the chat-room. - this.$('.chat-room').css('top', this._ffz_tabs.offsetHeight + "px"); - } - }), - - // Better Menu - - ffzRebuildMenu: function() { - return; - - var el = this.get('element'), - room_list = el && el.querySelector('.chat-rooms .tse-content'); - - if ( ! room_list ) - return; - - if ( ! room_list.classList.contains('ffz-room-list') ) { - room_list.classList.add('ffz-room-list'); - - // Find the Pending Invitations - var headers = room_list.querySelectorAll('.list-header'), - hdr = headers.length ? headers[headers.length-1] : undefined; - - if ( hdr ) { - hdr.classList.add('ffz'); - if ( hdr.nextSibling && hdr.nextSibling.classList ) - hdr.nextSibling.classList.add('ffz'); - } - } - - - // Channel Table - var t = this, - chan_table = this._ffz_chan_table || room_list.querySelector('#ffz-channel-table tbody'); - - if ( ! chan_table ) { - var tbl = document.createElement('table'); - tbl.setAttribute('cellspacing', 0); - tbl.id = 'ffz-channel-table'; - tbl.className = 'ffz'; - tbl.innerHTML = 'ChannelsJoinPin'; - room_list.insertBefore(tbl, room_list.firstChild); - - chan_table = this._ffz_chan_table = tbl.querySelector('tbody'); - } - - chan_table.innerHTML = ''; - - // Current Channel - var room = this.get('controller.currentChannelRoom'), row; - if ( room ) { - row = this.ffzBuildRow(this, room, true); - row && chan_table.appendChild(row); - } - - // Host Target - if ( this._ffz_host_room ) { - row = this.ffzBuildRow(this, this._ffz_host_room, false, true); - row && chan_table.appendChild(row); - } - - // Pinned Rooms - for(var i=0; i < f.settings.pinned_rooms.length; i++) { - var room_id = f.settings.pinned_rooms[i]; - if ( room && room.get('id') !== room_id && this._ffz_host !== room_id && f.rooms[room_id] && f.rooms[room_id].room ) { - row = this.ffzBuildRow(this, f.rooms[room_id].room); - row && chan_table.appendChild(row); - } - } - - - // Group Chat Table - var group_table = this._ffz_group_table || room_list.querySelector('#ffz-group-table tbody'); - if ( ! group_table ) { - var tbl = document.createElement('table'); - tbl.setAttribute('cellspacing', 0); - tbl.id = 'ffz-group-table'; - tbl.className = 'ffz'; - tbl.innerHTML = 'Group ChatsPin'; - - var before = room_list.querySelector('#ffz-channel-table'); - room_list.insertBefore(tbl, before.nextSibling); - - group_table = this._ffz_group_table = tbl.querySelector('tbody'); - } - - group_table.innerHTML = ''; - - _.each(this.get('controller.connectedPrivateGroupRooms'), function(room) { - var row = t.ffzBuildRow(t, room); - row && group_table && group_table.appendChild(row); - }); - - - // Change Create Tooltip - var create_btn = el.querySelector('.button.create'); - if ( create_btn ) - create_btn.title = 'Create a Group Room'; - }, - - ffzBuildRow: function(view, room, current_channel, host_channel) { - var row = document.createElement('tr'), - icon = document.createElement('td'), - name_el = document.createElement('td'), - - btn, - toggle_pinned = document.createElement('td'), - toggle_visible = document.createElement('td'), - - group = room.get('isGroupRoom'), - current = room === view.get('controller.currentRoom'), - //unread = format_unread(current ? 0 : room.get('unreadCount')), - - name = room.get('tmiRoom.displayName') || (group ? room.get('tmiRoom.name') : FFZ.get_capitalization(room.get('id'), function(name) { - f.log("Name for Row: " + name); - //unread = format_unread(current ? 0 : room.get('unreadCount')); - name_el.innerHTML = utils.sanitize(name); - })); - - name_el.className = 'ffz-room'; - name_el.innerHTML = utils.sanitize(name); - - if ( current_channel ) { - icon.innerHTML = constants.CAMERA; - icon.title = name_el.title = "Current Channel"; - icon.className = name_el.className = 'tooltip'; - } else if ( host_channel ) { - icon.innerHTML = constants.EYE; - icon.title = name_el.title = "Hosted Channel"; - icon.className = name_el.className = 'tooltip'; - } - - toggle_pinned.className = toggle_visible.className = 'ffz-row-switch'; - - toggle_pinned.innerHTML = ''; - toggle_visible.innerHTML = ''; - - row.setAttribute('data-room', room.get('id')); - - row.className = 'ffz-room-row'; - row.classList.toggle('current-channel', current_channel); - row.classList.toggle('host-channel', host_channel); - row.classList.toggle('group-chat', group); - row.classList.toggle('active', current); - - row.appendChild(icon); - row.appendChild(name_el); - - if ( ! group ) { - row.appendChild(toggle_pinned); - btn = toggle_pinned.querySelector('a.switch'); - btn.addEventListener('click', function(e) { - e.preventDefault(); - e.stopPropagation && e.stopPropagation(); - - var room_id = room.get('id'), - is_pinned = f.settings.pinned_rooms.indexOf(room_id) !== -1; - - if ( is_pinned ) - f._leave_room(room_id); - else - f._join_room(room_id); - - this.classList.toggle('active', !is_pinned); - }); - } else { - btn = document.createElement('a'); - btn.className = 'leave-chat tooltip'; - btn.innerHTML = constants.CLOSE; - btn.title = 'Leave Group'; - - name_el.appendChild(btn); - - btn.addEventListener('click', function(e) { - e.preventDefault(); - e.stopPropagation && e.stopPropagation(); - - if ( ! confirm('Are you sure you want to leave the group room "' + name + '"?') ) - return; - - room.get('isGroupRoom') && room.del(); - }); - } - - row.appendChild(toggle_visible); - btn = toggle_visible.querySelector('a.switch'); - btn.addEventListener('click', function(e) { - e.preventDefault(); - e.stopPropagation && e.stopPropagation(); - - var room_id = room.get('id'), - visible_rooms = f.settings.visible_rooms, - is_visible = visible_rooms.indexOf(room_id) !== -1; - - if ( is_visible ) - visible_rooms.removeObject(room_id); - else - visible_rooms.push(room_id); - - f.settings.set('visible_rooms', visible_rooms); - this.classList.toggle('active', !is_visible); - view.ffzRebuildTabs(); - }); - - row.addEventListener('click', function() { - var controller = view.get('controller'); - controller.focusRoom(room); - controller.set('showList', false); - }); - - return row; - }, - - // Group Tabs~! - - ffzEnableTabs: function() { - if ( f.has_bttv || ! f.settings.group_tabs ) - return; - - // Hide the existing chat UI. - this.$(".chat-header").addClass("hidden"); - - // Create our own UI. - var tabs = this._ffz_tabs = document.createElement("div"); - tabs.id = "ffz-group-tabs"; - this.$(".chat-header").after(tabs); - - // List the Rooms - this.ffzRebuildTabs(); - }, - - ffzRebuildTabs: function() { - if ( f.has_bttv || ! f.settings.group_tabs ) - return; - - var tabs = this._ffz_tabs || this.get('element').querySelector('#ffz-group-tabs'); - if ( ! tabs ) - return; - - tabs.innerHTML = ""; - - var link = document.createElement('a'), - view = this; - - link.className = 'button glyph-only tooltip'; - link.title = "Chat Room Management"; - link.innerHTML = constants.ROOMS; - - link.addEventListener('click', function() { - var controller = view.get('controller'); - controller && controller.set('showList', !controller.get('showList')); - }); - - tabs.appendChild(link); - - - link = document.createElement('a'), - link.className = 'button glyph-only tooltip invite'; - link.title = "Invite a User"; - link.innerHTML = constants.INVITE; - - link.addEventListener('click', function() { - var controller = view.get('controller'); - controller && controller.set('showInviteUser', controller.get('currentRoom.canInvite') && !controller.get('showInviteUser')); - }); - - link.classList.toggle('hidden', !this.get("controller.currentRoom.canInvite")); - view._ffz_invite = link; - tabs.appendChild(link); - - var room = this.get('controller.currentChannelRoom'), tab; - if ( room ) { - tab = this.ffzBuildTab(view, room, true); - tab && tabs.appendChild(tab); - } - - // Check Host Target - var Channel = App.__container__.lookup('controller:channel'), - Room = App.__container__.resolve('model:room'); - target = Channel && Channel.get('hostModeTarget'); - - if ( target && Room ) { - var target_id = target.get('id'); - if ( this._ffz_host !== target_id ) { - if ( f.settings.pinned_rooms.indexOf(this._ffz_host) === -1 && this._ffz_host_room ) { - if ( this.get('controller.currentRoom') === this._ffz_host_room ) - this.get('controller').blurRoom(); - this._ffz_host_room.destroy(); - } - - this._ffz_host = target_id; - this._ffz_host_room = Room.findOne(target_id); - } - } else if ( this._ffz_host ) { - if ( f.settings.pinned_rooms.indexOf(this._ffz_host) === -1 && this._ffz_host_room ) { - if ( this.get('controller.currentRoom') === this._ffz_host_room ) - this.get('controller').blurRoom(); - this._ffz_host_room.destroy(); - } - - delete this._ffz_host; - delete this._ffz_host_room; - } - - if ( this._ffz_host_room ) { - tab = view.ffzBuildTab(view, this._ffz_host_room, false, true); - tab && tabs.appendChild(tab); - } - - // Pinned Rooms - for(var i=0; i < f.settings.pinned_rooms.length; i++) { - var room_id = f.settings.pinned_rooms[i]; - if ( room && room.get('id') !== room_id && this._ffz_host !== room_id && f.rooms[room_id] && f.rooms[room_id].room ) { - var tab = view.ffzBuildTab(view, f.rooms[room_id].room, false, false); - tab && tabs.appendChild(tab); - } - } - - _.each(this.get('controller.connectedPrivateGroupRooms'), function(room) { - var tab = view.ffzBuildTab(view, room); - tab && tabs.appendChild(tab); - }); - - // Now, adjust the chat-room. - this.$('.chat-room').css('top', tabs.offsetHeight + "px"); - }, - - ffzTabUnread: function(room_id) { - // TODO: Update menu. - - if ( f.has_bttv || ! f.settings.group_tabs ) - return; - - var tabs = this._ffz_tabs || this.get('element').querySelector('#ffz-group-tabs'), - current_id = this.get('controller.currentRoom.id'); - - if ( ! tabs ) - return; - - if ( room_id ) { - var room = f.rooms && f.rooms[room_id] && f.rooms[room_id].room, - tab = room && room._ffz_tab; - - if ( tab ) { - var unread = utils.format_unread(room_id === current_id ? 0 : room.get('unreadCount')); - tab.querySelector('span').innerHTML = unread; - } - } - - var children = tabs.querySelectorAll('.ffz-chat-tab'); - for(var i=0; i < children.length; i++) { - var tab = children[i], - room_id = tab.getAttribute('data-room'), - room = f.rooms && f.rooms[room_id] && f.rooms[room_id]; - - if ( ! room ) - continue; - - var unread = utils.format_unread(room_id === current_id ? 0 : room.room.get('unreadCount')); - tab.querySelector('span').innerHTML = unread; - } - }, - - ffzBuildTab: function(view, room, current_channel, host_channel) { - var tab = document.createElement('span'), name, unread, icon = '', - room_id = room.get('id'), - group = room.get('isGroupRoom'), - current = room === view.get('controller.currentRoom'), - visible = current || f.settings.visible_rooms.indexOf(room_id) !== -1; - - tab.setAttribute('data-room', room.id); - - tab.className = 'ffz-chat-tab tooltip'; - //tab.classList.toggle('hidden', ! visible); - tab.classList.toggle('current-channel', current_channel); - tab.classList.toggle('host-channel', host_channel); - tab.classList.toggle('group-chat', group); - tab.classList.toggle('active', current); - - unread = utils.format_unread(current ? 0 : room.get('unreadCount')); - - name = room.get('tmiRoom.displayName') || (group ? room.get('tmiRoom.name') : FFZ.get_capitalization(room.get('id'), function(name) { - unread = utils.format_unread(current ? 0 : room.get('unreadCount')); - tab.innerHTML = icon + utils.sanitize(name) + '' + unread + ''; - })); - - if ( current_channel ) { - icon = constants.CAMERA; - tab.title = "Current Channel"; - } else if ( host_channel ) { - icon = constants.EYE; - tab.title = "Hosted Channel"; - } else if ( group ) - tab.title = "Group Chat"; - else - tab.title = "Pinned Channel"; - - tab.innerHTML = icon + utils.sanitize(name) + '' + unread + ''; - - tab.addEventListener('click', function() { - var controller = view.get('controller'); - controller.focusRoom(room); - controller.set('showList', false); - }); - - room._ffz_tab = tab; - return tab; - }, - - ffzDisableTabs: function() { - if ( this._ffz_tabs ) { - this._ffz_tabs.parentElement.removeChild(this._ffz_tabs); - delete this._ffz_tabs; - delete this._ffz_invite; - } - - if ( this._ffz_host ) { - if ( f.settings.pinned_rooms.indexOf(this._ffz_host) === -1 && this._ffz_host_room ) { - if ( this.get('controller.currentRoom') === this._ffz_host_room ) - this.get('controller').blurRoom(); - this._ffz_host_room.destroy(); - } - - delete this._ffz_host; - delete this._ffz_host_room; - } - - // Show the old chat UI. - this.$('.chat-room').css('top', ''); - this.$(".chat-header").removeClass("hidden"); - }, - }); -} - - -// ---------------------- -// Chat Room Connections -// ---------------------- - -FFZ.prototype.connect_extra_chat = function() { - var user = this.get_user(); - if ( user && user.login ) { - // Make sure we're in the user's room. - if ( ! this.rooms[user.login] || this.rooms[user.login].room ) { - var Room = App.__container__.resolve('model:room'), - r = Room && Room.findOne(user.login); - } - } - - if ( this.has_bttv ) - return; - - for(var i=0; i < this.settings.pinned_rooms.length; i++) - this._join_room(this.settings.pinned_rooms[i], true); - - if ( ! this._chatv ) - return; - - if ( ! this.has_bttv && this.settings.group_tabs ) - this._chatv.ffzRebuildTabs(); - - this._chatv.ffzRebuildMenu(); -} - - -FFZ.prototype._join_room = function(room_id, no_rebuild) { - var did_join = false; - if ( this.settings.pinned_rooms.indexOf(room_id) === -1 ) { - this.settings.pinned_rooms.push(room_id); - this.settings.set("pinned_rooms", this.settings.pinned_rooms); - did_join = true; - } - - // Make sure we're not already there. - if ( this.rooms[room_id] && this.rooms[room_id].room ) { - if ( did_join && ! no_rebuild && ! this.has_bttv && this._chatv && this.settings.group_tabs ) - this._chatv.ffzRebuildTabs(); - return did_join; - } - - // Okay, fine. Get it. - var Room = App.__container__.resolve('model:room'), - r = Room && Room.findOne(room_id); - - // Finally, rebuild the chat UI. - if ( ! no_rebuild && ! this.has_bttv && this._chatv && this.settings.group_tabs ) - this._chatv.ffzRebuildTabs(); - - if ( ! no_rebuild && this._chatv ) - this._chatv.ffzRebuildMenu(); - - return did_join; -} - - -FFZ.prototype._leave_room = function(room_id, no_rebuild) { - var did_leave = false; - if ( this.settings.pinned_rooms.indexOf(room_id) !== -1 ) { - this.settings.pinned_rooms.removeObject(room_id); - this.settings.set("pinned_rooms", this.settings.pinned_rooms); - did_leave = true; - } - - if ( ! this.rooms[room_id] || ! this.rooms[room_id].room ) - return did_leave; - - var Chat = App.__container__.lookup('controller:chat'), - r = this.rooms[room_id].room, - user = this.get_user(); - - if ( ! Chat || Chat.get('currentChannelRoom.id') === room_id || (this._chatv && this._chatv._ffz_host === room_id) ) - return did_leave; - - if ( Chat.get('currentRoom.id') === room_id ) - Chat.blurRoom(); - - // Don't leave the user's room, but update the UI. - if ( ! user || user.login !== room_id ) - r.destroy(); - - if ( ! no_rebuild && ! this.has_bttv && this._chatv && this.settings.group_tabs ) - this._chatv.ffzRebuildTabs(); - - if ( ! no_rebuild && this._chatv ) - this._chatv.ffzRebuildMenu(); - - return did_leave; -} - - -// ---------------------- -// Commands -// ---------------------- - -FFZ.chat_commands.join = function(room, args) { - if ( ! args || ! args.length || args.length > 1 ) - return "Join Usage: /join "; - - var room_id = args[0].toLowerCase(); - if ( room_id.charAt(0) === "#" ) - room_id = room_id.substr(1); - - if ( this._join_room(room_id) ) - return "Joining " + room_id + ". You will always connect to this channel's chat unless you later /part from it."; - else - return "You have already joined " + room_id + ". Please use \"/part " + room_id + "\" to leave it."; -} - - -FFZ.chat_commands.part = function(room, args) { - if ( ! args || ! args.length || args.length > 1 ) - return "Part Usage: /part "; - - var room_id = args[0].toLowerCase(); - if ( room_id.charAt(0) === "#" ) - room_id = room_id.substr(1); - - if ( this._leave_room(room_id) ) - return "Leaving " + room_id + "."; - else if ( this.rooms[room_id] ) - return "You do not have " + room_id + " pinned and you cannot leave the current channel or hosted channels via /part."; - else - return "You are not in " + room_id + "."; -} -},{"../constants":5,"../utils":35}],10:[function(require,module,exports){ -var FFZ = window.FrankerFaceZ; - - -// -------------------- -// Settings -// -------------------- - -FFZ.settings_info.swap_sidebars = { - type: "boolean", - value: false, - - category: "Appearance", - no_mobile: true, - no_bttv: true, - - name: "Swap Sidebar Positions", - help: "Swap the positions of the left and right sidebars, placing chat on the left.", - - on_update: function(val) { - if ( this.has_bttv ) - return; - - document.body.classList.toggle("ffz-sidebar-swap", val); - this._fix_menu_position(); - } - }; - - -FFZ.settings_info.right_column_width = { - type: "button", - value: 340, - - category: "Appearance", - no_mobile: true, - no_bttv: true, - - name: "Right Sidebar Width", - help: "Set the width of the right sidebar for chat.", - - method: function() { - var old_val = this.settings.right_column_width || 340, - new_val = prompt("Right Sidebar Width\n\nPlease enter a new width for the right sidebar, in pixels. Minimum: 250, Default: 340", old_val); - - if ( new_val === null || new_val === undefined ) - return; - - var width = parseInt(new_val); - if ( ! width || width === NaN ) - width = 340; - - this.settings.set('right_column_width', Math.max(250, width)); - }, - - on_update: function(val) { - if ( this.has_bttv ) - return; - - var Layout = App.__container__.lookup('controller:layout'); - if ( ! Layout ) - return; - - Layout.set('rightColumnWidth', val); - Ember.propertyDidChange(Layout, 'contentWidth'); - } - }; - - -// -------------------- -// Initialization -// -------------------- - -FFZ.prototype.setup_layout = function() { - if ( this.has_bttv ) - return; - - document.body.classList.toggle("ffz-sidebar-swap", this.settings.swap_sidebars); - - this.log("Creating layout style element."); - var s = this._layout_style = document.createElement('style'); - s.id = 'ffz-layout-css'; - document.head.appendChild(s); - - this.log("Hooking the Ember Layout controller."); - var Layout = App.__container__.lookup('controller:layout'), - f = this; - - if ( ! Layout ) - return; - - Layout.reopen({ - rightColumnWidth: 340, - - isTooSmallForRightColumn: function() { - return this.get("windowWidth") < (1090 - this.get('rightColumnWidth')) - }.property("windowWidth", "rightColumnWidth"), - - contentWidth: function() { - var left_width = this.get("isLeftColumnClosed") ? 50 : 240, - right_width = this.get("isRightColumnClosed") ? 0 : this.get("rightColumnWidth"); - - return this.get("windowWidth") - left_width - right_width - 60; - - }.property("windowWidth", "isRightColumnClosed", "isLeftColumnClosed", "rightColumnWidth"), - - /*ffzUpdateWidth: _.throttle(function() { - var rc = document.querySelector('#right_close'); - if ( ! rc ) - return; - - var left_width = this.get("isLeftColumnClosed") ? 50 : 240, - right_width; - - if ( f.settings.swap_sidebars ) - right_width = rc.offsetLeft; // + this.get('rightColumnWidth') - 5; - else - right_width = document.body.offsetWidth - rc.offsetLeft - left_width - 25; - - if ( right_width < 250 ) { - // Close it! - - } - - this.set('rightColumnWidth', right_width); - Ember.propertyDidChange(Layout, 'contentWidth'); - }, 200),*/ - - ffzUpdateCss: function() { - var width = this.get('rightColumnWidth'); - - f._layout_style.innerHTML = '#main_col.expandRight #right_close { left: none !important; } #right_col { width: ' + width + 'px; } body:not(.ffz-sidebar-swap) #main_col:not(.expandRight) { margin-right: ' + width + 'px; } body.ffz-sidebar-swap #main_col:not(.expandRight) { margin-left: ' + width + 'px; }'; - - }.observes("rightColumnWidth"), - - ffzFixTabs: function() { - if ( f.settings.group_tabs && f._chatv && f._chatv._ffz_tabs ) { - setTimeout(function() { - f._chatv && f._chatv.$('.chat-room').css('top', f._chatv._ffz_tabs.offsetHeight + "px"); - },0); - } - }.observes("isRightColumnClosed", "rightColumnWidth") - }); - - /* - // Try modifying the closer. - var rc = jQuery("#right_close"); - if ( ! rc || ! rc.length ) - return; - - rc.draggable({ - axis: "x", - drag: Layout.ffzUpdateWidth.bind(Layout), - stop: Layout.ffzUpdateWidth.bind(Layout) - });*/ - - - // Force the layout to update. - Layout.set('rightColumnWidth', this.settings.right_column_width); - Ember.propertyDidChange(Layout, 'contentWidth'); -} -},{}],11:[function(require,module,exports){ -var FFZ = window.FrankerFaceZ, - utils = require("../utils"), - constants = require("../constants"), - - SEPARATORS = "[\\s`~<>!-#%-\\x2A,-/:;\\x3F@\\x5B-\\x5D_\\x7B}\\u00A1\\u00A7\\u00AB\\u00B6\\u00B7\\u00BB\\u00BF\\u037E\\u0387\\u055A-\\u055F\\u0589\\u058A\\u05BE\\u05C0\\u05C3\\u05C6\\u05F3\\u05F4\\u0609\\u060A\\u060C\\u060D\\u061B\\u061E\\u061F\\u066A-\\u066D\\u06D4\\u0700-\\u070D\\u07F7-\\u07F9\\u0830-\\u083E\\u085E\\u0964\\u0965\\u0970\\u0AF0\\u0DF4\\u0E4F\\u0E5A\\u0E5B\\u0F04-\\u0F12\\u0F14\\u0F3A-\\u0F3D\\u0F85\\u0FD0-\\u0FD4\\u0FD9\\u0FDA\\u104A-\\u104F\\u10FB\\u1360-\\u1368\\u1400\\u166D\\u166E\\u169B\\u169C\\u16EB-\\u16ED\\u1735\\u1736\\u17D4-\\u17D6\\u17D8-\\u17DA\\u1800-\\u180A\\u1944\\u1945\\u1A1E\\u1A1F\\u1AA0-\\u1AA6\\u1AA8-\\u1AAD\\u1B5A-\\u1B60\\u1BFC-\\u1BFF\\u1C3B-\\u1C3F\\u1C7E\\u1C7F\\u1CC0-\\u1CC7\\u1CD3\\u2010-\\u2027\\u2030-\\u2043\\u2045-\\u2051\\u2053-\\u205E\\u207D\\u207E\\u208D\\u208E\\u2329\\u232A\\u2768-\\u2775\\u27C5\\u27C6\\u27E6-\\u27EF\\u2983-\\u2998\\u29D8-\\u29DB\\u29FC\\u29FD\\u2CF9-\\u2CFC\\u2CFE\\u2CFF\\u2D70\\u2E00-\\u2E2E\\u2E30-\\u2E3B\\u3001-\\u3003\\u3008-\\u3011\\u3014-\\u301F\\u3030\\u303D\\u30A0\\u30FB\\uA4FE\\uA4FF\\uA60D-\\uA60F\\uA673\\uA67E\\uA6F2-\\uA6F7\\uA874-\\uA877\\uA8CE\\uA8CF\\uA8F8-\\uA8FA\\uA92E\\uA92F\\uA95F\\uA9C1-\\uA9CD\\uA9DE\\uA9DF\\uAA5C-\\uAA5F\\uAADE\\uAADF\\uAAF0\\uAAF1\\uABEB\\uFD3E\\uFD3F\\uFE10-\\uFE19\\uFE30-\\uFE52\\uFE54-\\uFE61\\uFE63\\uFE68\\uFE6A\\uFE6B\\uFF01-\\uFF03\\uFF05-\\uFF0A\\uFF0C-\\uFF0F\\uFF1A\\uFF1B\\uFF1F\\uFF20\\uFF3B-\\uFF3D\\uFF3F\\uFF5B\\uFF5D\\uFF5F-\\uFF65]", - SPLITTER = new RegExp(SEPARATORS + "*," + SEPARATORS + "*"); - - -// --------------------- -// Settings -// --------------------- - -FFZ.settings_info.room_status = { - type: "boolean", - value: true, - - category: "Chat Appearance", - no_bttv: true, - - name: "Room Status Indicators", - help: "Display the current room state (slow mode, sub mode, and r9k mode) next to the Chat button.", - - on_update: function() { - if ( this._roomv ) - this._roomv.ffzUpdateStatus(); - } - }; - - -FFZ.settings_info.line_purge_icon = { - type: "boolean", - value: false, - - no_bttv: true, - category: "Chat Moderation", - - name: "Purge Icon in Mod Icons", - help: "Display a Purge Icon in chat line Mod Icons for quickly purging users.", - - on_update: function(val) { - if ( this.has_bttv ) - return; - - document.body.classList.toggle("ffz-chat-purge-icon", val); - } - }; - - -FFZ.settings_info.replace_bad_emotes = { - type: "boolean", - value: true, - - category: "Chat Appearance", - no_bttv: true, - - name: "Fix Low Quality Twitch Global Emoticons", - help: "Replace emoticons such as DansGame and RedCoat with cleaned up versions that don't have pixels around the edges or white backgrounds for nicer display on dark chat." - }; - - -FFZ.settings_info.parse_emoji = { - type: "boolean", - value: true, - - category: "Chat Appearance", - - name: "Replace Emoji with Images", - help: "Replace emoji in chat messages with nicer looking images from the open-source Twitter Emoji project." - }; - - -FFZ.settings_info.room_status = { - type: "boolean", - value: true, - - category: "Chat Appearance", - no_bttv: true, - - name: "Room Status Indicators", - help: "Display the current room state (slow mode, sub mode, and r9k mode) next to the Chat button.", - - on_update: function() { - if ( this._roomv ) - this._roomv.ffzUpdateStatus(); - } - }; - - -FFZ.settings_info.scrollback_length = { - type: "button", - value: 150, - - category: "Chat Appearance", - no_bttv: true, - - name: "Scrollback Length", - help: "Set the maximum number of lines to keep in chat.", - - method: function() { - var new_val = prompt("Scrollback Length\n\nPlease enter a new maximum length for the chat scrollback. Default: 150\n\nNote: Making this too large will cause your browser to lag.", this.settings.scrollback_length); - if ( new_val === null || new_val === undefined ) - return; - - new_val = parseInt(new_val); - if ( new_val === NaN ) - return; - - if ( new_val < 10 ) - new_val = 10; - - this.settings.set("scrollback_length", new_val); - - // Update our everything. - var Chat = App.__container__.lookup('controller:chat'), - current_id = Chat && Chat.get('currentRoom.id'); - - for(var room_id in this.rooms) { - var room = this.rooms[room_id]; - room.room.set('messageBufferSize', new_val + ((this._roomv && !this._roomv.get('stuckToBottom') && current_id === room_id) ? 150 : 0)); - } - } - }; - - -FFZ.settings_info.hosted_sub_notices = { - type: "boolean", - value: true, - - category: "Chat Filtering", - no_bttv: true, - - name: "Show Hosted Channel Subscriber Notices", - help: "Display notices in chat when someone subscribes to the hosted channel." - }; - - -FFZ.settings_info.banned_words = { - type: "button", - value: [], - - category: "Chat Filtering", - no_bttv: true, - //visible: function() { return ! this.has_bttv }, - - name: "Banned Words", - help: "Set a list of words that will be locally removed from chat messages.", - - method: function() { - var old_val = this.settings.banned_words.join(", "), - new_val = prompt("Banned Words\n\nPlease enter a comma-separated list of words that you would like to be removed from chat messages.", old_val); - - if ( new_val === null || new_val === undefined ) - return; - - new_val = new_val.trim().split(SPLITTER); - var vals = []; - - for(var i=0; i < new_val.length; i++) - new_val[i] && vals.push(new_val[i]); - - if ( vals.length == 1 && vals[0] == "disable" ) - vals = []; - - this.settings.set("banned_words", vals); - } - }; - - -FFZ.settings_info.keywords = { - type: "button", - value: [], - - category: "Chat Filtering", - no_bttv: true, - //visible: function() { return ! this.has_bttv }, - - name: "Highlight Keywords", - help: "Set additional keywords that will be highlighted in chat.", - - method: function() { - 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 === null || new_val === undefined ) - return; - - // Split them up. - new_val = new_val.trim().split(SPLITTER); - var vals = []; - - for(var i=0; i < new_val.length; i++) - new_val[i] && vals.push(new_val[i]); - - if ( vals.length == 1 && vals[0] == "disable" ) - vals = []; - - this.settings.set("keywords", vals); - } - }; - - -FFZ.settings_info.clickable_emoticons = { - type: "boolean", - value: false, - - category: "Chat Tooltips", - no_bttv: true, - no_mobile: true, - - name: "Emoticon Information Pages", - help: "When enabled, holding shift and clicking on an emoticon will open it on the FrankerFaceZ website or Twitch Emotes." - }; - - -FFZ.settings_info.link_info = { - type: "boolean", - value: true, - - category: "Chat Tooltips", - no_bttv: true, - - name: "Link Information Beta", - help: "Check links against known bad websites, unshorten URLs, and show YouTube info." - }; - - -FFZ.settings_info.link_image_hover = { - type: "boolean", - value: false, - - category: "Chat Tooltips", - no_bttv: true, - no_mobile: true, - - name: "Image Preview", - help: "Display image thumbnails for links to Imgur and YouTube." - }; - - -FFZ.settings_info.image_hover_all_domains = { - type: "boolean", - value: false, - - category: "Chat Tooltips", - no_bttv: true, - no_mobile: true, - - name: "Image Preview - All Domains", - help: "Requires Image Preview. Attempt to show an image preview for any URL ending in the appropriate extension. Warning: This may be used to leak your IP address to malicious users." - }; - - -FFZ.settings_info.legacy_badges = { - type: "boolean", - value: false, - - category: "Chat Appearance", - - name: "Legacy Badges", - help: "Display the old, pre-vector chat badges from Twitch.", - - on_update: function(val) { document.body.classList.toggle("ffz-legacy-badges", val); } - }; - - -FFZ.settings_info.chat_rows = { - type: "boolean", - value: false, - - category: "Chat Appearance", - no_bttv: true, - - name: "Chat Line Backgrounds", - help: "Display alternating background colors for lines in chat.", - - on_update: function(val) { document.body.classList.toggle("ffz-chat-background", !this.has_bttv && val); } - }; - - -FFZ.settings_info.chat_separators = { - type: "select", - options: { - 0: "Disabled", - 1: "Basic Line (1px solid)", - 2: "3D Line (2px groove)" - }, - value: '0', - - category: "Chat Appearance", - no_bttv: true, - - process_value: function(val) { - if ( val === false ) - return '0'; - else if ( val === true ) - return '1'; - return val; - }, - - name: "Chat Line Separators", - help: "Display thin lines between chat messages for further visual separation.", - - on_update: function(val) { - document.body.classList.toggle("ffz-chat-separator", !this.has_bttv && val !== '0'); - document.body.classList.toggle("ffz-chat-separator-3d", !this.has_bttv && val === '2'); - } - }; - - -FFZ.settings_info.chat_padding = { - type: "boolean", - value: false, - - category: "Chat Appearance", - no_bttv: true, - - name: "Reduced Chat Line Padding", - help: "Reduce the amount of padding around chat messages to fit more on-screen at once.", - - on_update: function(val) { document.body.classList.toggle("ffz-chat-padding", !this.has_bttv && val); } - }; - - -FFZ.settings_info.high_contrast_chat = { - type: "select", - options: { - '222': "Disabled", - '212': "Bold", - '221': "Text", - '211': "Text + Bold", - '122': "Background", - '121': "Background + Text", - '112': "Background + Bold", - '111': 'All' - }, - value: '222', - - category: "Chat Appearance", - no_bttv: true, - - name: "High Contrast", - help: "Display chat using white and black for maximum contrast. This is suitable for capturing and chroma keying chat to display on stream.", - - process_value: function(val) { - if ( val === false ) - return '222'; - else if ( val === true ) - return '111'; - return val; - }, - - on_update: function(val) { - document.body.classList.toggle("ffz-high-contrast-chat-text", !this.has_bttv && val[2] === '1'); - document.body.classList.toggle("ffz-high-contrast-chat-bold", !this.has_bttv && val[1] === '1'); - document.body.classList.toggle("ffz-high-contrast-chat-bg", !this.has_bttv && val[0] === '1'); - } - }; - - -FFZ.settings_info.chat_font_size = { - type: "button", - value: 12, - - category: "Chat Appearance", - no_bttv: true, - - name: "Font Size", - help: "Make the chat font bigger or smaller.", - - method: function() { - var old_val = this.settings.chat_font_size, - new_val = prompt("Chat Font Size\n\nPlease enter a new size for the chat font. The default is 12.", old_val); - - if ( new_val === null || new_val === undefined ) - return; - - var parsed = parseInt(new_val); - if ( ! parsed || parsed === NaN || parsed < 1 ) - parsed = 12; - - this.settings.set("chat_font_size", parsed); - }, - - on_update: function(val) { - if ( this.has_bttv || ! this._chat_style ) - return; - - var css; - if ( val === 12 || ! val ) - css = ""; - else { - var lh = Math.max(20, Math.round((20/12)*val)), - pd = Math.floor((lh - 20) / 2); - css = ".ember-chat .chat-messages .chat-line { font-size: " + val + "px !important; line-height: " + lh + "px !important; }"; - if ( pd ) - css += ".ember-chat .chat-messages .chat-line .mod-icons, .ember-chat .chat-messages .chat-line .badges { padding-top: " + pd + "px; }"; - } - - utils.update_css(this._chat_style, "chat_font_size", css); - FFZ.settings_info.chat_ts_size.on_update.bind(this)(this.settings.chat_ts_size); - } - }; - - -FFZ.settings_info.chat_ts_size = { - type: "button", - value: null, - - category: "Chat Appearance", - no_bttv: true, - - name: "Timestamp Font Size", - help: "Make the chat timestamp font bigger or smaller.", - - method: function() { - var old_val = this.settings.chat_ts_size; - - if ( ! old_val ) - old_val = this.settings.chat_font_size; - - var new_val = prompt("Chat Timestamp Font Size\n\nPlease enter a new size for the chat timestamp font. The default is to match the regular chat font size.", old_val); - - if ( new_val === null || new_val === undefined ) - return; - - var parsed = parseInt(new_val); - if ( ! parsed || parsed === NaN || parsed < 1 ) - parsed = null; - - this.settings.set("chat_ts_size", parsed); - }, - - on_update: function(val) { - if ( this.has_bttv || ! this._chat_style ) - return; - - var css; - if ( val === null ) - css = ""; - else { - var lh = Math.max(20, Math.round((20/12)*val), Math.round((20/12)*this.settings.chat_font_size)); - css = ".ember-chat .chat-messages .timestamp { font-size: " + val + "px !important; line-height: " + lh + "px !important; }"; - } - - utils.update_css(this._chat_style, "chat_ts_font_size", css); - } - }; - - -// --------------------- -// Initialization -// --------------------- - -FFZ.prototype.setup_line = function() { - // Tipsy Handler - jQuery(document.body).on("mouseleave", ".tipsy", function() { - this.parentElement.removeChild(this); - }); - - // Aliases - try { - this.aliases = JSON.parse(localStorage.ffz_aliases || '{}'); - } catch(err) { - this.log("Error Loading Aliases: " + err); - this.aliases = {}; - } - - - // Chat Style - var s = this._chat_style = document.createElement('style'); - s.id = "ffz-style-chat"; - s.type = 'text/css'; - document.head.appendChild(s); - - // Initial calculation. - FFZ.settings_info.chat_font_size.on_update.bind(this)(this.settings.chat_font_size); - - - // Chat Enhancements - document.body.classList.toggle("ffz-chat-colors", !this.has_bttv && this.settings.fix_color !== '-1'); - document.body.classList.toggle("ffz-chat-colors-gray", !this.has_bttv && this.settings.fix_color === '-1'); - - document.body.classList.toggle("ffz-legacy-badges", this.settings.legacy_badges); - document.body.classList.toggle('ffz-chat-background', !this.has_bttv && this.settings.chat_rows); - document.body.classList.toggle("ffz-chat-separator", !this.has_bttv && this.settings.chat_separators !== '0'); - document.body.classList.toggle("ffz-chat-separator-3d", !this.has_bttv && this.settings.chat_separators === '2'); - document.body.classList.toggle("ffz-chat-padding", !this.has_bttv && this.settings.chat_padding); - document.body.classList.toggle("ffz-chat-purge-icon", !this.has_bttv && this.settings.line_purge_icon); - - document.body.classList.toggle("ffz-high-contrast-chat-text", !this.has_bttv && this.settings.high_contrast_chat[2] === '1'); - document.body.classList.toggle("ffz-high-contrast-chat-bold", !this.has_bttv && this.settings.high_contrast_chat[1] === '1'); - document.body.classList.toggle("ffz-high-contrast-chat-bg", !this.has_bttv && this.settings.high_contrast_chat[0] === '1'); - - this._last_row = {}; - - this.log("Hooking the Ember Whisper Line component."); - var Whisper = App.__container__.resolve('component:whisper-line'); - - if ( Whisper ) - this._modify_line(Whisper); - - this.log("Hooking the Ember Message Line component."); - - var Line = App.__container__.resolve('component:message-line'); - - if ( Line ) - this._modify_line(Line); - - // Store the capitalization of our own name. - var user = this.get_user(); - if ( user && user.name ) - FFZ.capitalization[user.login] = [user.name, Date.now()]; -} - - -FFZ.prototype.save_aliases = function() { - this.log("Saving " + Object.keys(this.aliases).length + " aliases to local storage."); - localStorage.ffz_aliases = JSON.stringify(this.aliases); -} - - -FFZ.prototype._modify_line = function(component) { - var f = this, - - Layout = App.__container__.lookup('controller:layout'), - Settings = App.__container__.lookup('controller:settings'); - - - component.reopen({ - tokenizedMessage: function() { - // Add our own step to the tokenization procedure. - var tokens = this.get("msgObject.cachedTokens"); - if ( tokens ) - return tokens; - - tokens = this._super(); - - var start = performance.now(), - user = f.get_user(), - from_me = user && this.get("msgObject.from") === user.login; - - tokens = f._remove_banned(tokens); - tokens = f._emoticonize(this, tokens); - - if ( f.settings.parse_emoji ) - tokens = f.tokenize_emoji(tokens); - - // Store the capitalization. - var display = this.get("msgObject.tags.display-name"); - if ( display && display.length ) - FFZ.capitalization[this.get("msgObject.from")] = [display.trim(), Date.now()]; - - if ( ! from_me ) - tokens = f.tokenize_mentions(tokens); - - for(var i = 0; i < tokens.length; i++) { - var token = tokens[i]; - if ( ! _.isString(token) && token.mentionedUser && ! token.own ) { - this.set('msgObject.ffz_has_mention', true); - break; - } - } - - var end = performance.now(); - if ( end - start > 5 ) - f.log("Tokenizing Message Took Too Long - " + (end-start) + "ms", tokens, false, true); - - this.set("msgObject.cachedTokens", tokens); - return tokens; - - }.property("msgObject.message", "isChannelLinksDisabled", "currentUserNick", "msgObject.from", "msgObject.tags.emotes"), - - ffzUpdated: Ember.observer("msgObject.ffz_deleted", "msgObject.ffz_old_messages", function() { - this.rerender(); - }), - - click: function(e) { - if ( e.target && e.target.classList.contains('ffz-old-messages') ) - return f._show_deleted(this.get('msgObject.room')); - - if ( e.target && e.target.classList.contains('deleted-link') ) - return f._deleted_link_click.bind(e.target)(e); - - if ( e.target && e.target.classList.contains('mod-icon') ) { - jQuery(e.target).trigger('mouseout'); - - if ( e.target.classList.contains('purge') ) { - var i = this.get('msgObject.from'), - room_id = this.get('msgObject.room'), - room = room_id && f.rooms[room_id] && f.rooms[room_id].room; - - if ( room ) { - room.send("/timeout " + i + " 1"); - room.clearMessages(i); - } - return; - } - } - - if ( (e.shiftKey || e.shiftLeft) && f.settings.clickable_emoticons && e.target && e.target.classList.contains('emoticon') ) { - var eid = e.target.getAttribute('data-emote'); - if ( eid ) - window.open("https://twitchemotes.com/emote/" + eid); - else { - eid = e.target.getAttribute("data-ffz-emote"); - window.open("https://www.frankerfacez.com/emoticons/" + eid); - } - } - - return this._super(e); - }, - - ffzUserLevel: function() { - if ( this.get('isStaff') ) - return 5; - else if ( this.get('isAdmin') ) - return 4; - else if ( this.get('isBroadcaster') ) - return 3; - else if ( this.get('isGlobalModerator') ) - return 2; - else if ( this.get('isModerator') ) - return 1; - return 0; - }.property('msgObject.labels.[]'), - - render: function(e) { - var deleted = this.get('msgObject.deleted'), - r = this, - - badges = {}, - - user = this.get('msgObject.from'), - room_id = this.get('msgObject.room'), - room = f.rooms && f.rooms[room_id], - - recipient = this.get('msgObject.to'), - is_whisper = recipient && recipient.length, - - this_ul = this.get('ffzUserLevel'), - other_ul = room && room.room && room.room.get('ffzUserLevel') || 0, - - row_type = this.get('msgObject.ffz_alternate'), - raw_color = this.get('msgObject.color'), - colors = raw_color && f._handle_color(raw_color), - - is_dark = (Layout && Layout.get('isTheatreMode')) || (Settings && Settings.get('model.darkMode')); - - if ( row_type === undefined ) { - row_type = f._last_row[room_id] = f._last_row.hasOwnProperty(room_id) ? !f._last_row[room_id] : false; - this.set("msgObject.ffz_alternate", row_type); - } - - e.push('
'); - e.push('' + this.get("timestamp") + ' '); - - if ( ! is_whisper && this_ul < other_ul ) { - e.push(''); - if ( deleted ) - e.push('Unban'); - else - e.push('Ban'); - - e.push('Timeout'); - e.push('Purge'); - e.push(''); - } - - // Stock Badges - if ( ! is_whisper && this.get('isBroadcaster') ) - badges[0] = {klass: 'broadcaster', title: 'Broadcaster'}; - else if ( this.get('isStaff') ) - badges[0] = {klass: 'staff', title: 'Staff'}; - else if ( this.get('isAdmin') ) - badges[0] = {klass: 'admin', title: 'Admin'}; - else if ( this.get('isGlobalMod') ) - badges[0] = {klass: 'global-moderator', title: 'Global Moderator'}; - else if ( ! is_whisper && this.get('isModerator') ) - badges[0] = {klass: 'moderator', title: 'Moderator'}; - - if ( ! is_whisper && this.get('isSubscriber') ) - badges[10] = {klass: 'subscriber', title: 'Subscriber'}; - if ( this.get('hasTurbo') ) - badges[15] = {klass: 'turbo', title: 'Turbo'}; - - // FFZ Badges - badges = f.render_badges(this, badges); - - // Rendering! - e.push(''); - - for(var key in badges) { - var badge = badges[key], - css = badge.image ? 'background-image:url("' + badge.image + '");' : ''; - - if ( badge.color ) - css += 'background-color:' + badge.color + ';'; - - if ( badge.extra_css ) - css += badge.extra_css; - - e.push('
'); - } - - e.push('
'); - - var alias = f.aliases[user], - name = this.get('msgObject.tags.display-name') || (user && user.capitalize()) || "unknown user", - style = colors && 'color:' + (is_dark ? colors[1] : colors[0]), - colored = style ? ' has-color' : ''; - - if ( alias ) - e.push('' + utils.sanitize(alias) + ''); - else - e.push('' + utils.sanitize(name) + ''); - - if ( is_whisper ) { - var to_alias = f.aliases[recipient], - to_name = this.get('msgObject.tags.recipient-display-name') || (recipient && recipient.capitalize()) || "unknown user", - - to_color = this.get('msgObject.toColor'), - to_colors = to_color && f._handle_color(to_color), - to_style = to_color && 'color:' + (is_dark ? to_colors[1] : to_colors[0]), - to_colored = to_style ? ' has-color' : ''; - - this._renderWhisperArrow(e); - - if ( to_alias ) - e.push('' + utils.sanitize(to_alias) + ''); - else - e.push('' + utils.sanitize(to_name) + ''); - } - - e.push(': '); - - if ( this.get('msgObject.style') !== 'action' ) { - style = ''; - colored = ''; - } - - if ( deleted ) - e.push('<message deleted>'); - else { - e.push(''); - e.push(f.render_tokens(this.get('tokenizedMessage'), true)); - - var old_messages = this.get('msgObject.ffz_old_messages'); - if ( old_messages && old_messages.length ) - e.push('
Show ' + utils.number_commas(old_messages.length) + ' Old
'); - - e.push('
'); - } - }, - - classNameBindings: [ - 'msgObject.ffz_alternate:ffz-alternate', - 'msgObject.ffz_has_mention:ffz-mentioned', - 'ffzWasDeleted:ffz-deleted', - 'ffzHasOldMessages:clearfix', - 'ffzHasOldMessages:ffz-has-deleted' - ], - - - ffzWasDeleted: function() { - return f.settings.prevent_clear && this.get('msgObject.ffz_deleted'); - }.property('msgObject.ffz_deleted'), - - ffzHasOldMessages: function() { - var old_messages = this.get('msgObject.ffz_old_messages'); - return old_messages && old_messages.length; - }.property('msgObject.ffz_old_messages'), - - - didInsertElement: function() { - this._super(); - - var el = this.get('element'); - - el.setAttribute('data-room', this.get('msgObject.room')); - el.setAttribute('data-sender', this.get('msgObject.from')); - el.setAttribute('data-deleted', this.get('msgObject.deleted') || false); - } - }); -} - - -// --------------------- -// Capitalization -// --------------------- - -FFZ.capitalization = {}; -FFZ._cap_fetching = 0; - -FFZ.get_capitalization = function(name, callback) { - if ( ! name ) - return name; - - name = name.toLowerCase(); - if ( name == "jtv" || name == "twitchnotify" ) - return name; - - var old_data = FFZ.capitalization[name]; - if ( old_data ) { - if ( Date.now() - old_data[1] < 3600000 ) - return old_data[0]; - } - - if ( FFZ._cap_fetching < 25 ) { - FFZ._cap_fetching++; - FFZ.get().ws_send("get_display_name", name, function(success, data) { - var cap_name = success ? data : name; - FFZ.capitalization[name] = [cap_name, Date.now()]; - FFZ._cap_fetching--; - typeof callback === "function" && callback(cap_name); - }); - } - - return old_data ? old_data[0] : name; -} - - -// --------------------- -// Banned Words -// --------------------- - -FFZ.prototype._remove_banned = function(tokens) { - var banned_words = this.settings.banned_words, - banned_links = ['j.mp', 'bit.ly'], - - has_banned_words = banned_words && banned_words.length; - - if ( !has_banned_words && (! banned_links || ! banned_links.length) ) - return tokens; - - if ( typeof tokens == "string" ) - tokens = [tokens]; - - var regex = FFZ._words_to_regex(banned_words), - link_regex = FFZ._words_to_regex(banned_links), - new_tokens = []; - - for(var i=0; i < tokens.length; i++) { - var token = tokens[i]; - if ( ! _.isString(token ) ) { - if ( token.emoticonSrc && has_banned_words && regex.test(token.altText) ) - new_tokens.push(token.altText.replace(regex, "$1***")); - else if ( token.isLink && has_banned_words && regex.test(token.href) ) - new_tokens.push({ - isLink: true, - href: token.href, - isDeleted: true, - isLong: false, - censoredHref: token.href.replace(regex, "$1***") - }); - else if ( token.isLink && link_regex.test(token.href) ) - new_tokens.push({ - isLink: true, - href: token.href, - isDeleted: true, - isLong: false, - censoredHref: token.href.replace(link_regex, "$1***") - }); - else - new_tokens.push(token); - - } else if ( has_banned_words ) - new_tokens.push(token.replace(regex, "$1***")); - else - new_tokens.push(token); - } - - return new_tokens; -} - - -// --------------------- -// Emoticon Replacement -// --------------------- - -FFZ.prototype._emoticonize = function(component, tokens) { - var room_id = component.get("msgObject.room"), - user_id = component.get("msgObject.from"); - - return this.tokenize_emotes(user_id, room_id, tokens); -} -},{"../constants":5,"../utils":35}],12:[function(require,module,exports){ -var FFZ = window.FrankerFaceZ, - utils = require("../utils"), - constants = require("../constants"), - helpers, - - keycodes = { - ESC: 27, - P: 80, - B: 66, - T: 84, - U: 85 - }, - - MESSAGE = '', - CHECK = '', - - DURATIONS = {}, - duration_string = function(val) { - if ( val === 1 ) - return 'Purge'; - - if ( DURATIONS[val] ) - return DURATIONS[val]; - - var weeks, days, hours, minutes, seconds; - - weeks = Math.floor(val / 604800); - seconds = val % 604800; - - days = Math.floor(seconds / 86400); - seconds %= 86400; - - hours = Math.floor(seconds / 3600); - seconds %= 3600; - - minutes = Math.floor(seconds / 60); - seconds %= 60; - - var out = DURATIONS[val] = (weeks ? weeks + 'w' : '') + ((days || (weeks && (hours || minutes || seconds))) ? days + 'd' : '') + ((hours || ((weeks || days) && (minutes || seconds))) ? hours + 'h' : '') + ((minutes || ((weeks || days || hours) && seconds)) ? minutes + 'm' : '') + (seconds ? seconds + 's' : ''); - return out; - }; - - -try { - helpers = window.require && window.require("ember-twitch-chat/helpers/chat-line-helpers"); -} catch(err) { } - - -// ---------------- -// Settings -// ---------------- - -FFZ.basic_settings.enhanced_moderation_cards = { - type: "boolean", - - no_bttv: true, - - category: "Chat", - name: "Enhanced Moderation Cards", - help: "Improve moderation cards with hotkeys, additional buttons, chat history, and other information to make moderating easier.", - - get: function() { - return this.settings.mod_card_hotkeys && - this.settings.mod_card_info && - this.settings.mod_card_history; - }, - - set: function(val) { - this.settings.set('mod_card_hotkeys', val); - this.settings.set('mod_card_info', val); - this.settings.set('mod_card_history', val); - } -}; - - -FFZ.basic_settings.chat_hover_pause = { - type: "boolean", - - no_bttv: true, - - category: "Chat", - name: "Pause Chat Scrolling on Mouse Hover", - help: "Automatically prevent the chat from scrolling when moving the mouse over it to prevent moderation mistakes and link misclicks.", - - get: 'chat_hover_pause', - set: 'chat_hover_pause' -}; - - -FFZ.settings_info.chat_hover_pause = { - type: "boolean", - value: false, - - no_bttv: true, - - category: "Chat Moderation", - name: "Pause Chat Scrolling on Mouse Hover", - help: "Automatically prevent the chat from scrolling when moving the mouse over it to prevent moderation mistakes and link misclicks.", - - on_update: function(val) { - if ( ! this._roomv ) - return; - - if ( val ) - this._roomv.ffzEnableFreeze(); - else - this._roomv.ffzDisableFreeze(); - } - }; - - -FFZ.settings_info.short_commands = { - type: "boolean", - value: true, - - no_bttv: true, - category: "Chat Moderation", - - name: "Short Moderation Commands", - help: "Use /t, /b, and /u in chat in place of /timeout, /ban, /unban for quicker moderation, and use /p for 1 second timeouts." - }; - - -FFZ.settings_info.mod_card_hotkeys = { - type: "boolean", - value: false, - - no_bttv: true, - category: "Chat Moderation", - - name: "Moderation Card Hotkeys", - help: "With a moderation card selected, press B to ban the user, T to time them out for 10 minutes, P to time them out for 1 second, or U to unban them. ESC closes the card." - }; - - -FFZ.settings_info.mod_card_info = { - type: "boolean", - value: true, - - no_bttv: true, - category: "Chat Moderation", - - name: "Moderation Card Additional Information", - help: "Display a channel's follower count, view count, and account age on moderation cards." - }; - - -FFZ.settings_info.mod_card_history = { - type: "boolean", - value: false, - - no_bttv: true, - category: "Chat Moderation", - - name: "Moderation Card History", - help: "Display a few of the user's previously sent messages on moderation cards.", - - on_update: function(val) { - if ( val || ! this.rooms ) - return; - - // Delete all history~! - for(var room_id in this.rooms) { - var room = this.rooms[room_id]; - if ( room ) - room.user_history = undefined; - } - } - }; - - -FFZ.settings_info.mod_card_buttons = { - type: "button", - value: [], - - category: "Chat Moderation", - no_bttv: true, - - name: "Moderation Card Additional Buttons", - help: "Add additional buttons to moderation cards for running chat commands on those users.", - - method: function() { - var old_val = ""; - for(var i=0; i < this.settings.mod_card_buttons.length; i++) { - var cmd = this.settings.mod_card_buttons[i]; - if ( cmd.indexOf(' ') !== -1 ) - old_val += ' "' + cmd + '"'; - else - old_val += ' ' + cmd; - } - - var new_val = prompt("Moderation Card Additional Buttons\n\nPlease enter a list of additional commands to display buttons for on moderation cards. Commands are separated by spaces. To include spaces in a command, surround the command with double quotes (\"). Use \"{user}\" to insert the user's username into the command, otherwise it will be appended to the end.\n\nExample: !permit \"!reg add {user}\"", old_val); - - if ( new_val === null || new_val === undefined ) - return; - - var vals = []; - new_val = new_val.trim(); - - while(new_val) { - if ( new_val.charAt(0) === '"' ) { - var end = new_val.indexOf('"', 1); - if ( end === -1 ) - end = new_val.length; - - var segment = new_val.substr(1, end - 1); - if ( segment ) - vals.push(segment); - - new_val = new_val.substr(end + 1); - - } else { - var ind = new_val.indexOf(' '); - if ( ind === -1 ) { - if ( new_val ) - vals.push(new_val); - - new_val = ''; - - } else { - var segment = new_val.substr(0, ind); - if ( segment ) - vals.push(segment); - - new_val = new_val.substr(ind + 1); - } - } - } - - this.settings.set("mod_card_buttons", vals); - } - }; - - -FFZ.settings_info.mod_card_durations = { - type: "button", - value: [300, 600, 3600, 43200, 86400, 604800], - - category: "Chat Moderation", - no_bttv: true, - - name: "Moderation Card Timeout Buttons", - help: "Add additional timeout buttons to moderation cards with specific durations.", - - method: function() { - var old_val = this.settings.mod_card_durations.join(", "), - new_val = prompt("Moderation Card Timeout Buttons\n\nPlease enter a comma-separated list of durations that you would like to have timeout buttons for. Durations must be expressed in seconds.\n\nEnter \"reset\" without quotes to return to the default value.", old_val); - - if ( new_val === null || new_val === undefined ) - return; - - if ( new_val === "reset" ) - new_val = FFZ.settings_info.mod_card_durations.value.join(", "); - - // Split them up. - new_val = new_val.trim().split(/[ ,]+/); - var vals = []; - - for(var i=0; i < new_val.length; i++) { - var val = parseInt(new_val[i]); - if ( val === 0 ) - val = 1; - - if ( val !== NaN && val > 0 ) - vals.push(val); - } - - this.settings.set("mod_card_durations", vals); - } - }; - - -// ---------------- -// Initialization -// ---------------- - -FFZ.prototype.setup_mod_card = function() { - this.log("Modifying Mousetrap stopCallback so we can catch ESC."); - var orig_stop = Mousetrap.stopCallback; - Mousetrap.stopCallback = function(e, element, combo) { - if ( element.classList.contains('no-mousetrap') ) - return true; - - return orig_stop(e, element, combo); - } - - Mousetrap.bind("up up down down left right left right b a enter", function() { - var el = document.querySelector(".app-main") || document.querySelector(".ember-chat-container"); - el && el.classList.toggle('ffz-flip'); - }); - - - this.log("Hooking the Ember Moderation Card view."); - var Card = App.__container__.resolve('component:moderation-card'), - f = this; - - Card.reopen({ - ffzForceRedraw: function() { - this.rerender(); - }.observes("cardInfo.isModeratorOrHigher", "cardInfo.user"), - - ffzRebuildInfo: function() { - var el = this.get('element'), - info = el && el.querySelector('.info'); - if ( ! info ) - return; - - var out = '' + constants.EYE + ' ' + utils.number_commas(this.get('cardInfo.user.views') || 0) + '', - since = utils.parse_date(this.get('cardInfo.user.created_at') || ''), - followers = this.get('cardInfo.user.ffz_followers'); - - if ( typeof followers === "number" ) { - out += '' + constants.HEART + ' ' + utils.number_commas(followers || 0) + ''; - - } else if ( followers === undefined ) { - var t = this; - this.set('cardInfo.user.ffz_followers', false); - Twitch.api.get("channels/" + this.get('cardInfo.user.id') + '/follows', {limit:1}).done(function(data) { - t.set('cardInfo.user.ffz_followers', data._total); - t.ffzRebuildInfo(); - }).fail(function(data) { - t.set('cardInfo.user.ffz_followers', undefined); - }); - } - - if ( since ) { - var age = Math.floor((Date.now() - since.getTime()) / 1000); - if ( age > 0 ) { - out += '' + constants.CLOCK + ' ' + utils.human_time(age, 10) + ''; - } - } - - info.innerHTML = out; - }.observes("cardInfo.user.views"), - - userName: Ember.computed("cardInfo.user.id", "cardInfo.user.display_name", function() { - var user_id = this.get("cardInfo.user.id"), - alias = f.aliases[user_id]; - - return alias || this.get("cardInfo.user.display_name") || user_id.capitalize(); - }), - - didInsertElement: function() { - this._super(); - window._card = this; - try { - if ( f.has_bttv ) - return; - - var el = this.get('element'), - controller = this.get('controller'), - line, - - user_id = controller.get('cardInfo.user.id'), - alias = f.aliases[user_id]; - - // Alias Display - if ( alias ) { - var name = el.querySelector('h3.name'), - link = name && name.querySelector('a'); - - if ( link ) - name = link; - if ( name ) { - name.classList.add('ffz-alias'); - name.title = utils.sanitize(controller.get('cardInfo.user.display_name') || user_id.capitalize()); - jQuery(name).tipsy(); - } - } - - // Style it! - el.classList.add('ffz-moderation-card'); - - // Info-tize it! - if ( f.settings.mod_card_info ) { - var info = document.createElement('div'), - after = el.querySelector('h3.name'); - if ( after ) { - el.classList.add('ffz-has-info'); - info.className = 'info channel-stats'; - after.parentElement.insertBefore(info, after.nextSibling); - this.ffzRebuildInfo(); - } - } - - // Additional Buttons - if ( f.settings.mod_card_buttons && f.settings.mod_card_buttons.length ) { - line = document.createElement('div'); - line.className = 'extra-interface interface clearfix'; - - var cmds = {}, - add_btn_click = function(cmd) { - var user_id = controller.get('cardInfo.user.id'), - cont = App.__container__.lookup('controller:chat'), - room = cont && cont.get('currentRoom'); - - room && room.send(cmd.replace(/{user}/g, user_id)); - }, - - add_btn_make = function(cmd) { - var btn = document.createElement('button'), - segment = cmd.split(' ', 1)[0], - title = cmds[segment] > 1 ? cmd.split(' ', cmds[segment]) : [segment]; - - if ( /^[!~./]/.test(title[0]) ) - title[0] = title[0].substr(1); - - title = _.map(title, function(s){ return s.capitalize() }).join(' '); - - btn.className = 'button'; - btn.innerHTML = utils.sanitize(title); - btn.title = utils.sanitize(cmd.replace(/{user}/g, controller.get('cardInfo.user.id') || '{user}')); - - jQuery(btn).tipsy(); - btn.addEventListener('click', add_btn_click.bind(this, cmd)); - return btn; - }; - - var cmds = {}; - for(var i=0; i < f.settings.mod_card_buttons.length; i++) - cmds[f.settings.mod_card_buttons[i].split(' ',1)[0]] = (cmds[f.settings.mod_card_buttons[i].split(' ',1)[0]] || 0) + 1; - - for(var i=0; i < f.settings.mod_card_buttons.length; i++) { - var cmd = f.settings.mod_card_buttons[i], - ind = cmd.indexOf('{user}'); - - if ( ind === -1 ) - cmd += ' {user}'; - - line.appendChild(add_btn_make(cmd)) - } - - el.appendChild(line); - } - - - // Key Handling - el.setAttribute('tabindex', 1); - if ( f.settings.mod_card_hotkeys ) { - el.classList.add('no-mousetrap'); - - el.addEventListener('keyup', function(e) { - var key = e.keyCode || e.which, - user_id = controller.get('cardInfo.user.id'), - is_mod = controller.get('cardInfo.isModeratorOrHigher'), - room = App.__container__.lookup('controller:chat').get('currentRoom'); - - if ( is_mod && key == keycodes.P ) - room.send("/timeout " + user_id + " 1"); - - else if ( is_mod && key == keycodes.B ) - room.send("/ban " + user_id); - - else if ( is_mod && key == keycodes.T ) - room.send("/timeout " + user_id + " 600"); - - else if ( is_mod && key == keycodes.U ) - room.send("/unban " + user_id); - - else if ( key != keycodes.ESC ) - return; - - controller.send('close'); - }); - } - - - // Only do the big stuff if we're mod. - if ( controller.get('cardInfo.isModeratorOrHigher') ) { - el.classList.add('ffz-is-mod'); - - // Key Handling - if ( f.settings.mod_card_hotkeys ) { - el.classList.add('no-mousetrap'); - - el.addEventListener('keyup', function(e) { - var key = e.keyCode || e.which, - user_id = controller.get('cardInfo.user.id'), - room = App.__container__.lookup('controller:chat').get('currentRoom'); - - 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.U ) - room.send("/unban " + user_id); - - else if ( key != keycodes.ESC ) - return; - - controller.send('close'); - }); - } - - var btn_click = function(timeout) { - var user_id = controller.get('cardInfo.user.id'), - room = App.__container__.lookup('controller:chat').get('currentRoom'); - - if ( timeout === -1 ) - room.send("/unban " + user_id); - else - room.send("/timeout " + user_id + " " + timeout); - }, - - btn_make = function(timeout) { - var btn = document.createElement('button') - btn.className = 'button'; - btn.innerHTML = duration_string(timeout); - btn.title = "Timeout User for " + utils.number_commas(timeout) + " Second" + (timeout != 1 ? "s" : ""); - - if ( f.settings.mod_card_hotkeys && timeout === 600 ) - btn.title = "(T)" + btn.title.substr(1); - else if ( f.settings.mod_card_hotkeys && timeout === 1 ) - btn.title = "(P)urge - " + btn.title; - - jQuery(btn).tipsy(); - - btn.addEventListener('click', btn_click.bind(this, timeout)); - return btn; - }; - - if ( f.settings.mod_card_durations && f.settings.mod_card_durations.length ) { - // Extra Moderation - line = document.createElement('div'); - line.className = 'extra-interface interface clearfix'; - - line.appendChild(btn_make(1)); - - var s = document.createElement('span'); - s.className = 'right'; - line.appendChild(s); - - for(var i=0; i < f.settings.mod_card_durations.length; i++) - s.appendChild(btn_make(f.settings.mod_card_durations[i])); - - el.appendChild(line); - - // Fix Other Buttons - this.$("button.timeout").remove(); - } - - var ban_btn = el.querySelector('button.ban'); - if ( f.settings.mod_card_hotkeys ) - ban_btn.setAttribute('title', '(B)an User'); - - // Unban Button - var unban_btn = document.createElement('button'); - unban_btn.className = 'unban button glyph-only light'; - unban_btn.innerHTML = CHECK; - unban_btn.title = (f.settings.mod_card_hotkeys ? "(U)" : "U") + "nban User"; - - jQuery(unban_btn).tipsy(); - unban_btn.addEventListener("click", btn_click.bind(this, -1)); - - jQuery(ban_btn).after(unban_btn); - } - - - // More Fixing Other Buttons - var op_btn = el.querySelector('button.mod'); - if ( op_btn ) { - var is_owner = controller.get('cardInfo.isChannelOwner'), - user = ffz.get_user(); - can_op = is_owner || (user && user.is_admin) || (user && user.is_staff); - - if ( ! can_op ) - op_btn.parentElement.removeChild(op_btn); - } - - - var msg_btn = el.querySelector(".interface > button.message-button"); - if ( msg_btn ) { - msg_btn.innerHTML = 'W'; - msg_btn.classList.add('glyph-only'); - msg_btn.classList.add('message'); - - msg_btn.title = "Whisper User"; - jQuery(msg_btn).tipsy(); - - - var real_msg = document.createElement('button'); - real_msg.className = 'message-button button glyph-only message tooltip'; - real_msg.innerHTML = MESSAGE; - real_msg.title = "Message User"; - - real_msg.addEventListener('click', function() { - window.open('http://www.twitch.tv/message/compose?to=' + controller.get('cardInfo.user.id')); - }) - - msg_btn.parentElement.insertBefore(real_msg, msg_btn.nextSibling); - } - - - // Alias Button - var alias_btn = document.createElement('button'); - alias_btn.className = 'alias button glyph-only tooltip'; - alias_btn.innerHTML = constants.EDIT; - alias_btn.title = "Set Alias"; - - alias_btn.addEventListener('click', function() { - var user = controller.get('cardInfo.user.id'), - alias = f.aliases[user]; - - var new_val = prompt("Alias for User: " + user + "\n\nPlease enter an alias for the user. Leave it blank to remove the alias.", alias); - if ( new_val === null || new_val === undefined ) - return; - - new_val = new_val.trim(); - if ( ! new_val ) - new_val = undefined; - - f.aliases[user] = new_val; - f.save_aliases(); - - // Update UI - f._update_alias(user); - - Ember.propertyDidChange(controller, 'userName'); - var name = el.querySelector('h3.name'), - link = name && name.querySelector('a'); - - if ( link ) - name = link; - if ( name ) - name.classList.toggle('ffz-alias', new_val); - }); - - if ( msg_btn ) - msg_btn.parentElement.insertBefore(alias_btn, msg_btn); - else { - var follow_btn = el.querySelector(".interface > .follow-button"); - if ( follow_btn ) - follow_btn.parentElement.insertBefore(alias_btn, follow_btn.nextSibling); - } - - - // Message History - if ( f.settings.mod_card_history ) { - var Chat = App.__container__.lookup('controller:chat'), - room = Chat && Chat.get('currentRoom'), - ffz_room = room && f.rooms && f.rooms[room.get('id')], - user_history = ffz_room && ffz_room.user_history && ffz_room.user_history[controller.get('cardInfo.user.id')]; - - if ( user_history && user_history.length ) { - var history = document.createElement('ul'), - alternate = false; - history.className = 'interface clearfix chat-history'; - - for(var i=0; i < user_history.length; i++) { - var line = user_history[i], - l_el = document.createElement('li'); - - l_el.className = 'message-line chat-line clearfix'; - l_el.classList.toggle('ffz-alternate', alternate); - alternate = !alternate; - - if ( line.style ) - l_el.classList.add(line.style); - - l_el.innerHTML = (helpers ? '' + helpers.getTime(line.date) + ' ' : '') + '' + (line.style === 'action' ? '*' + line.from + ' ' : '') + f.render_tokens(line.cachedTokens) + ''; - - // Banned Links - var bad_links = l_el.querySelectorAll('a.deleted-link'); - for(var x=0; x < bad_links.length; x++) - bad_links[x].addEventListener("click", f._deleted_link_click); - - jQuery('.html-tooltip', l_el).tipsy({html:true}); - history.appendChild(l_el); - } - - el.appendChild(history); - - // Lazy scroll-to-bottom - history.scrollTop = history.scrollHeight; - } - } - - // Reposition the menu if it's off-screen. - var el_bound = el.getBoundingClientRect(), - body_bound = document.body.getBoundingClientRect(); - - if ( el_bound.bottom > body_bound.bottom ) { - var offset = el_bound.bottom - body_bound.bottom; - if ( el_bound.top - offset > body_bound.top ) - el.style.top = (el_bound.top - offset) + "px"; - } - - // Focus the Element - this.$().draggable({ - start: function() { - el.focus(); - }}); - - el.focus(); - - } catch(err) { - try { - f.error("ModerationCardView didInsertElement: " + err); - } catch(err) { } - } - }}); -} - - -// ---------------- -// Aliases -// ---------------- - -FFZ.prototype._update_alias = function(user) { - var alias = this.aliases && this.aliases[user], - cap_name = FFZ.get_capitalization(user), - display_name = alias || cap_name, - el = this._roomv && this._roomv.get('element'), - lines = el && el.querySelectorAll('.chat-line[data-sender="' + user + '"]'); - - if ( ! lines ) - return; - - for(var i=0, l = lines.length; i < l; i++) { - var line = lines[i], - el_from = line.querySelector('.from'); - - el_from.classList.toggle('ffz-alias', alias); - el_from.textContent = display_name; - el_from.title = alias ? cap_name : ''; - } -} - - -// ---------------- -// Chat Commands -// ---------------- - -FFZ.chat_commands.purge = 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 = function(room, args) { - return FFZ.chat_commands.purge.bind(this)(room, args); -} - -FFZ.chat_commands.p.enabled = function() { return this.settings.short_commands; } - - -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.short_commands; } - - -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.short_commands; } - - -FFZ.chat_commands.u = function(room, args) { - if ( ! args || ! args.length ) - return "Unban Usage: /u 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.short_commands; } -},{"../constants":5,"../utils":35}],13:[function(require,module,exports){ -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, - MOD_CSS = /[^\n}]*\.badges\s+\.moderator\s*{\s*background-image:\s*url\(\s*['"]([^'"]+)['"][^}]+(?:}|$)/, - GROUP_CHAT = /^_([^_]+)_\d+$/, - HOSTED_SUB = / subscribed to /, - constants = require('../constants'), - utils = require('../utils'), - - // StrimBagZ Support - is_android = navigator.userAgent.indexOf('Android') !== -1, - - moderator_css = function(room) { - if ( ! room.moderator_badge ) - return ""; - - return '.chat-line[data-room="' + room.id + '"] .badges .moderator:not(.ffz-badge-replacement) { background-image:url("' + room.moderator_badge + '") !important; }'; - } - - -// -------------------- -// Initialization -// -------------------- - -FFZ.prototype.setup_room = function() { - this.rooms = {}; - - this.log("Creating room style element."); - var s = this._room_style = document.createElement("style"); - s.id = "ffz-room-css"; - document.head.appendChild(s); - - this.log("Hooking the Ember Room controller."); - - // Responsive ban button. - var f = this, - RC = App.__container__.lookup('controller:room'); - if ( RC ) { - var orig_ban = RC._actions.banUser, - orig_to = RC._actions.timeoutUser; - - RC._actions.banUser = function(e) { - orig_ban.bind(this)(e); - this.get("model").clearMessages(e.user); - } - - RC._actions.timeoutUser = function(e) { - orig_to.bind(this)(e); - this.get("model").clearMessages(e.user); - } - - RC._actions.purgeUser = function(e) { - this.get("model.tmiRoom").sendMessage("/timeout " + e.user + " 1"); - this.get("model").clearMessages(e.user); - } - } - - this.log("Hooking the Ember Room model."); - - var Room = App.__container__.resolve('model:room'); - this._modify_room(Room); - - // Modify all current instances of Room, as the changes to the base - // class won't be inherited automatically. - var instances = Room.instances; - for(var key in instances) { - if ( ! instances.hasOwnProperty(key) ) - continue; - - var inst = instances[key]; - this.add_room(inst.id, inst); - this._modify_room(inst); - inst.ffzPatchTMI(); - } - - this.log("Hooking the Ember Room view."); - - var RoomView = App.__container__.resolve('view:room'); - this._modify_rview(RoomView); - - // For some reason, this doesn't work unless we create an instance of the - // room view and then destroy it immediately. - try { - RoomView.create().destroy(); - } catch(err) { } - - // Modify all existing Room views. - for(var key in Ember.View.views) { - if ( ! Ember.View.views.hasOwnProperty(key) ) - continue; - - var view = Ember.View.views[key]; - if ( !(view instanceof RoomView) ) - continue; - - this.log("Manually updating existing Room view.", view); - try { - view.ffzInit(); - } catch(err) { - this.error("RoomView setup ffzInit: " + err); - } - } -} - - -// -------------------- -// View Customization -// -------------------- - -FFZ.prototype._modify_rview = function(view) { - var f = this; - view.reopen({ - didInsertElement: function() { - this._super(); - - try { - this.ffzInit(); - } catch(err) { - f.error("RoomView didInsertElement: " + err); - } - }, - - willClearRender: function() { - try { - this.ffzTeardown(); - } catch(err) { - f.error("RoomView willClearRender: " + err); - } - this._super(); - }, - - ffzInit: function() { - f._roomv = this; - - this.ffz_frozen = false; - - // Fix scrolling. - this._ffz_mouse_down = this.ffzMouseDown.bind(this); - if ( is_android ) - // We don't unbind scroll because that messes with the scrollbar. ;_; - this._$chatMessagesScroller.bind('scroll', this._ffz_mouse_down); - - this._$chatMessagesScroller.unbind('mousedown'); - this._$chatMessagesScroller.bind('mousedown', this._ffz_mouse_down); - - if ( f.settings.chat_hover_pause ) - this.ffzEnableFreeze(); - - if ( f.settings.room_status ) - this.ffzUpdateStatus(); - - var controller = this.get('controller'); - if ( controller ) { - controller.reopen({ - submitButtonText: function() { - if ( this.get("model.isWhisperMessage") && this.get("model.isWhispersEnabled") ) - return i18n("Whisper"); - - var wait = this.get("model.slowWait"), - msg = this.get("model.messageToSend") || ""; - - if ( (msg.charAt(0) === "/" && msg.substr(0, 4) !== "/me ") || !wait || !f.settings.room_status ) - return i18n("Chat"); - - return utils.time_to_string(wait, false, false, true); - }.property("model.isWhisperMessage", "model.isWhispersEnabled", "model.slowWait") - }); - - Ember.propertyDidChange(controller, 'submitButtonText'); - } - }, - - ffzTeardown: function() { - if ( f._roomv === this ) - f._roomv = undefined; - - this.ffzDisableFreeze(); - }, - - ffzUpdateStatus: function() { - var room = this.get('controller.model'), - - el = this.get('element'), - cont = el && el.querySelector('.chat-buttons-container'); - - if ( ! cont ) - return; - - var r9k_badge = cont.querySelector('#ffz-stat-r9k'), - sub_badge = cont.querySelector('#ffz-stat-sub'), - slow_badge = cont.querySelector('#ffz-stat-slow'), - banned_badge = cont.querySelector('#ffz-stat-banned'), - delay_badge = cont.querySelector('#ffz-stat-delay'), - btn = cont.querySelector('button'); - - if ( f.has_bttv || ! f.settings.room_status ) { - if ( r9k_badge ) - r9k_badge.parentElement.removeChild(r9k_badge); - if ( sub_badge ) - sub_badge.parentElement.removeChild(sub_badge); - if ( slow_badge ) - slow_badge.parentElement.removeChild(slow_badge); - - if ( btn ) - btn.classList.remove('ffz-waiting'); - return; - } - - if ( ! r9k_badge ) { - r9k_badge = document.createElement('span'); - r9k_badge.className = 'ffz room-state stat float-right'; - r9k_badge.id = 'ffz-stat-r9k'; - r9k_badge.innerHTML = 'R9K'; - r9k_badge.title = "This room is in R9K-mode."; - cont.appendChild(r9k_badge); - jQuery(r9k_badge).tipsy({gravity:"s", offset:15}); - } - - if ( ! sub_badge ) { - sub_badge = document.createElement('span'); - sub_badge.className = 'ffz room-state stat float-right'; - sub_badge.id = 'ffz-stat-sub'; - sub_badge.innerHTML = 'SUB'; - sub_badge.title = "This room is in subscribers-only mode."; - cont.appendChild(sub_badge); - jQuery(sub_badge).tipsy({gravity:"s", offset:15}); - } - - if ( ! slow_badge ) { - slow_badge = document.createElement('span'); - slow_badge.className = 'ffz room-state stat float-right'; - slow_badge.id = 'ffz-stat-slow'; - slow_badge.innerHTML = 'SLOW'; - slow_badge.title = "This room is in slow mode. You may send messages every 120 seconds."; - cont.appendChild(slow_badge); - jQuery(slow_badge).tipsy({gravity:"s", offset:15}); - } - - if ( ! banned_badge ) { - banned_badge = document.createElement('span'); - banned_badge.className = 'ffz room-state stat float-right'; - banned_badge.id = 'ffz-stat-banned'; - banned_badge.innerHTML = 'BAN'; - banned_badge.title = "You have been banned from talking in this room."; - cont.appendChild(banned_badge); - jQuery(banned_badge).tipsy({gravity:"s", offset:15}); - } - - if ( ! delay_badge ) { - delay_badge = document.createElement('span'); - delay_badge.className = 'ffz room-state stat float-right'; - delay_badge.id = 'ffz-stat-delay'; - delay_badge.innerHTML = 'DELAY'; - delay_badge.title = "300ms of artifical chat delay added."; - cont.appendChild(delay_badge); - jQuery(delay_badge).tipsy({gravity:"s", offset:15}); - } - - r9k_badge.classList.toggle('hidden', !(room && room.get('r9k'))); - sub_badge.classList.toggle('hidden', !(room && room.get('subsOnly'))); - slow_badge.classList.toggle('hidden', !(room && room.get('slowMode'))); - slow_badge.title = "This room is in slow mode. You may send messages every " + utils.number_commas(room && room.get('slow')||120) + " seconds."; - banned_badge.classList.toggle('hidden', !(room && room.get('ffz_banned'))); - delay_badge.title = utils.number_commas(+f.settings.chat_delay||300) + "ms of artifical chat delay added."; - delay_badge.classList.toggle('hidden', !+f.settings.chat_delay); - - if ( btn ) { - btn.classList.toggle('ffz-waiting', (room && room.get('slowWait') || 0)); - btn.classList.toggle('ffz-banned', (room && room.get('ffz_banned'))); - } - - }.observes('controller.model'), - - ffzEnableFreeze: function() { - var el = this.get('element'), - messages = el.querySelector('.chat-messages'); - - if ( ! messages ) - return; - - this._ffz_interval = setInterval(this.ffzPulse.bind(this), 200); - this._ffz_messages = messages; - - this._ffz_mouse_move = this.ffzMouseMove.bind(this); - this._ffz_mouse_out = this.ffzMouseOut.bind(this); - - messages.addEventListener('mousemove', this._ffz_mouse_move); - messages.addEventListener('touchmove', this._ffz_mouse_move); - messages.addEventListener('mouseout', this._ffz_mouse_out); - document.addEventListener('mouseout', this._ffz_mouse_out); - }, - - ffzDisableFreeze: function() { - if ( this._ffz_interval ) { - clearInterval(this._ffz_interval); - this._ffz_interval = undefined; - } - - this.ffzUnfreeze(); - - var messages = this._ffz_messages; - if ( ! messages ) - return; - - this._ffz_messages = undefined; - - if ( this._ffz_mouse_move ) { - messages.removeEventListener('mousemove', this._ffz_mouse_move); - this._ffz_mouse_move = undefined; - } - - if ( this._ffz_mouse_out ) { - messages.removeEventListener('mouseout', this._ffz_mouse_out); - this._ffz_mouse_out = undefined; - } - }, - - ffzPulse: function() { - if ( this.ffz_frozen ) { - var elapsed = Date.now() - this._ffz_last_move; - if ( elapsed > 750 ) - this.ffzUnfreeze(); - } - }, - - ffzUnfreeze: function() { - this.ffz_frozen = false; - this._ffz_last_move = 0; - this.ffzUnwarnPaused(); - - if ( this.get('stuckToBottom') ) - this._scrollToBottom(); - }, - - ffzMouseDown: function(event) { - var t = this._$chatMessagesScroller; - if ( t && t[0] && ((!this.ffz_frozen && "mousedown" === event.type) || "mousewheel" === event.type || (is_android && "scroll" === event.type) ) ) { - if ( event.type === "mousedown" ) - f.log("Freezing from mouse down!", event); - var r = t[0].scrollHeight - t[0].scrollTop - t[0].offsetHeight; - this._setStuckToBottom(10 >= r); - } - }, - - ffzMouseOut: function(event) { - this._ffz_outside = true; - var e = this; - setTimeout(function() { - if ( e._ffz_outside ) - e.ffzUnfreeze(); - }, 25); - }, - - ffzMouseMove: function(event) { - this._ffz_last_move = Date.now(); - this._ffz_outside = false; - - if ( event.screenX === this._ffz_last_screenx && event.screenY === this._ffz_last_screeny ) - return; - - this._ffz_last_screenx = event.screenX; - this._ffz_last_screeny = event.screenY; - - if ( this.ffz_frozen ) - return; - - this.ffz_frozen = true; - if ( this.get('stuckToBottom') ) { - this.set('controller.model.messageBufferSize', f.settings.scrollback_length + 150); - this.ffzWarnPaused(); - } - }, - - _scrollToBottom: _.throttle(function() { - var e = this, - s = this._$chatMessagesScroller; - - Ember.run.next(function() { - setTimeout(function(){ - if ( e.ffz_frozen || ! s || ! s.length ) - return; - - s[0].scrollTop = s[0].scrollHeight; - e._setStuckToBottom(true); - }) - }) - }, 200), - - _setStuckToBottom: function(val) { - this.set("stuckToBottom", val); - this.get("controller.model") && this.set("controller.model.messageBufferSize", f.settings.scrollback_length + (val ? 0 : 150)); - if ( ! val ) - this.ffzUnfreeze(); - }, - - // Warnings~! - ffzWarnPaused: function() { - var el = this.get('element'), - warning = el && el.querySelector('.chat-interface .more-messages-indicator.ffz-freeze-indicator'); - - if ( ! el ) - return; - - if ( ! warning ) { - warning = document.createElement('div'); - warning.className = 'more-messages-indicator ffz-freeze-indicator'; - warning.innerHTML = '(Chat Paused Due to Mouse Movement)'; - - var cont = el.querySelector('.chat-interface'); - if ( ! cont ) - return; - cont.insertBefore(warning, cont.childNodes[0]) - } - - warning.classList.remove('hidden'); - }, - - - ffzUnwarnPaused: function() { - var el = this.get('element'), - warning = el && el.querySelector('.chat-interface .more-messages-indicator.ffz-freeze-indicator'); - - if ( warning ) - warning.classList.add('hidden'); - } - - }); -} - - -// -------------------- -// Command System -// -------------------- - -FFZ.chat_commands = {}; -FFZ.ffz_commands = {}; - - -FFZ.prototype.room_message = function(room, text) { - var lines = text.split("\n"); - if ( this.has_bttv ) { - for(var i=0; i < lines.length; i++) - BetterTTV.chat.handlers.onPrivmsg(room.id, {style: 'admin', date: new Date(), from: 'jtv', message: lines[i]}); - - } else { - for(var i=0; i < lines.length; i++) - room.room.addMessage({style: 'ffz admin', date: new Date(), from: 'FFZ', message: lines[i]}); - } -} - - -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; - - if ( ! text ) { - // Try to pop-up the menu. - var link = document.querySelector('a.ffz-ui-toggle'); - if ( link ) - return link.click(); - - text = "help"; - } - - var args = text.split(" "), - cmd = args.shift().toLowerCase(); - - this.log("Received Command: " + cmd, args, true); - - var command = FFZ.ffz_commands[cmd], output; - if ( command ) { - try { - output = command.bind(this)(room, args); - } catch(err) { - this.log("Error Running Command - " + cmd + ": " + err, room); - output = "There was an error running the command."; - } - } else - output = 'There is no "' + cmd + '" command.'; - - if ( output ) - this.room_message(room, output); -} - - -FFZ.ffz_commands.help = function(room, args) { - if ( args && args.length ) { - var command = FFZ.ffz_commands[args[0].toLowerCase()]; - if ( ! command ) - return 'There is no "' + args[0] + '" command.'; - - else if ( ! command.help ) - return 'No help is available for the command "' + args[0] + '".'; - - else - return command.help; - } - - var cmds = []; - for(var c in FFZ.ffz_commands) - FFZ.ffz_commands.hasOwnProperty(c) && cmds.push(c); - - return "The available commands are: " + cmds.join(", "); -} - -FFZ.ffz_commands.help.help = "Usage: /ffz help [command]\nList available commands, or show help for a specific command."; - - -// -------------------- -// Room Management -// -------------------- - -FFZ.prototype.add_room = function(id, room) { - if ( this.rooms[id] ) - return this.log("Tried to add existing room: " + id); - - this.log("Adding Room: " + id); - - // Create a basic data table for this room. - var data = this.rooms[id] = {id: id, room: room, menu_sets: [], sets: [], css: null, needs_history: false}; - - if ( this.follow_sets && this.follow_sets[id] ) { - data.extra_sets = this.follow_sets[id]; - delete this.follow_sets[id]; - - for(var i=0; i < data.extra_sets.length; i++) { - var sid = data.extra_sets[i], - set = this.emote_sets && this.emote_sets[sid]; - - if ( set ) { - if ( set.users.indexOf(id) === -1 ) - set.users.push(id); - continue; - } - - this.load_set(sid, function(success, data) { - if ( success ) - data.users.push(id); - }); - } - } - - // Let the server know where we are. - this.ws_send("sub", id); - - // See if we need history? - if ( ! this.has_bttv && this.settings.chat_history && room && (room.get('messages.length') || 0) < 10 ) { - if ( ! this.ws_send("chat_history", [id,25], this._load_history.bind(this, id)) ) - data.needs_history = true; - } - - // Why don't we set the scrollback length, too? - room.set('messageBufferSize', this.settings.scrollback_length + ((this._roomv && !this._roomv.get('stuckToBottom') && this._roomv.get('controller.model.id') === id) ? 150 : 0)); - - // For now, we use the legacy function to grab the .css file. - this.load_room(id); -} - - -FFZ.prototype.remove_room = function(id) { - var room = this.rooms[id]; - if ( ! room ) - return; - - this.log("Removing Room: " + id); - - // Remove the CSS - if ( room.css || room.moderator_badge ) - utils.update_css(this._room_style, id, null); - - // Let the server know we're gone and delete our data for this room. - this.ws_send("unsub", id); - delete this.rooms[id]; - - // Clean up sets we aren't using any longer. - if ( id.charAt(0) === "_" ) - return; - - var set = this.emote_sets[room.set]; - if ( set ) { - set.users.removeObject(id); - if ( ! this.global_sets.contains(room.set) && ! set.users.length ) - this.unload_set(room.set); - } -} - - -// -------------------- -// Chat History -// -------------------- - -FFZ.prototype._load_history = function(room_id, success, data) { - var room = this.rooms[room_id]; - if ( ! room || ! room.room ) - return; - - if ( success ) - this.log("Received " + data.length + " old messages for: " + room_id); - else - return this.log("Error retrieving chat history for: " + room_id); - - if ( ! data.length ) - return; - - return this._insert_history(room_id, data); -} - - -FFZ.prototype._show_deleted = function(room_id) { - var room = this.rooms[room_id]; - if ( ! room || ! room.room ) - return; - - var old_messages = room.room.get('messages.0.ffz_old_messages'); - if ( ! old_messages || ! old_messages.length ) - return; - - room.room.set('messages.0.ffz_old_messages', undefined); - this._insert_history(room_id, old_messages); -} - -FFZ.prototype._insert_history = function(room_id, data) { - var room = this.rooms[room_id]; - if ( ! room || ! room.room ) - return; - - var r = room.room, - messages = r.get('messages'), - tmiSession = r.tmiSession || (TMI._sessions && TMI._sessions[0]), - tmiRoom = r.tmiRoom, - - inserted = 0, - - last_msg = data[data.length - 1], - now = new Date(), - last_date = typeof last_msg.date === "string" ? utils.parse_date(last_msg.date) : last_msg.date, - age = (now - last_date) / 1000, - is_old = age > 300, - - i = data.length, - alternation = r.get('messages.0.ffz_alternate') || false; - - if ( is_old ) - alternation = ! alternation; - - var i = data.length; - while(i--) { - var msg = data[i]; - - if ( typeof msg.date === "string" ) - msg.date = utils.parse_date(msg.date); - - msg.ffz_alternate = alternation = ! alternation; - if ( ! msg.room ) - msg.room = room_id; - - if ( ! msg.color ) - msg.color = msg.tags && msg.tags.color ? msg.tags.color : tmiSession && msg.from ? tmiSession.getColor(msg.from.toLowerCase()) : "#755000"; - - if ( ! msg.labels || ! msg.labels.length ) { - var labels = msg.labels = []; - if ( msg.tags ) { - if ( msg.tags.turbo ) - labels.push("turbo"); - if ( msg.tags.subscriber ) - labels.push("subscriber"); - if ( msg.from === room_id ) - labels.push("owner") - else { - var ut = msg.tags['user-type']; - if ( ut === 'mod' || ut === 'staff' || ut === 'admin' || ut === 'global_mod' ) - labels.push(ut); - } - } - } - - if ( ! msg.style ) { - if ( msg.from === "jtv" ) - msg.style = "admin"; - else if ( msg.from === "twitchnotify" ) - msg.style = "notification"; - } - - if ( ! msg.cachedTokens || ! msg.cachedTokens.length ) - this.tokenize_chat_line(msg, true, r.get('roomProperties.hide_chat_links')); - - if ( r.shouldShowMessage(msg) ) { - if ( messages.length < r.get("messageBufferSize") ) { - // One last thing! Make sure we don't have too many messages. - if ( msg.ffz_old_messages ) { - var max_msgs = r.get("messageBufferSize") - (messages.length + 1); - if ( msg.ffz_old_messages.length > max_msgs ) - msg.ffz_old_messages = msg.ffz_old_messages.slice(msg.ffz_old_messages.length - max_msgs); - } - - messages.unshiftObject(msg); - inserted += 1; - } else - break; - } - } - - if ( is_old ) { - var msg = { - ffz_alternate: ! alternation, - color: "#755000", - date: new Date(), - from: "frankerfacez_admin", - style: "admin", - message: "(Last message is " + utils.human_time(age) + " old.)", - room: room_id - }; - - this.tokenize_chat_line(msg, true, r.get('roomProperties.hide_chat_links')); - if ( r.shouldShowMessage(msg) ) { - messages.insertAt(inserted, msg); - while( messages.length > r.get('messageBufferSize') ) - messages.removeAt(0); - } - } -} - - -// -------------------- -// Receiving Set Info -// -------------------- - -FFZ.prototype.load_room = function(room_id, callback, tries) { - var f = this; - jQuery.getJSON(((tries||0)%2 === 0 ? constants.API_SERVER : constants.API_SERVER_2) + "v1/room/" + room_id) - .done(function(data) { - if ( data.sets ) { - for(var key in data.sets) - data.sets.hasOwnProperty(key) && f._load_set_json(key, undefined, data.sets[key]); - } - - f._load_room_json(room_id, callback, data); - - }).fail(function(data) { - if ( data.status == 404 ) - return typeof callback == "function" && callback(false); - - tries = (tries || 0) + 1; - if ( tries < 10 ) - return f.load_room(room_id, callback, tries); - - return typeof callback == "function" && callback(false); - }); -} - - -FFZ.prototype._load_room_json = function(room_id, callback, data) { - if ( ! data || ! data.room ) - return typeof callback == "function" && callback(false); - - data = data.room; - - // Preserve the pointer to the Room instance. - if ( this.rooms[room_id] ) - data.room = this.rooms[room_id].room; - - // Preserve everything else. - for(var key in this.rooms[room_id]) { - if ( key !== 'room' && this.rooms[room_id].hasOwnProperty(key) && ! data.hasOwnProperty(key) ) - data[key] = this.rooms[room_id][key]; - } - - data.needs_history = this.rooms[room_id] && this.rooms[room_id].needs_history || false; - - this.rooms[room_id] = data; - - if ( data.css || data.moderator_badge ) - utils.update_css(this._room_style, room_id, moderator_css(data) + (data.css||"")); - - if ( ! this.emote_sets.hasOwnProperty(data.set) ) - this.load_set(data.set, function(success, set) { - if ( set.users.indexOf(room_id) === -1 ) - set.users.push(room_id); - }); - else if ( this.emote_sets[data.set].users.indexOf(room_id) === -1 ) - this.emote_sets[data.set].users.push(room_id); - - this.update_ui_link(); - - if ( callback ) - callback(true, data); -} - - -// -------------------- -// Ember Modifications -// -------------------- - -FFZ.prototype._modify_room = function(room) { - var f = this; - room.reopen({ - slowWaiting: false, - slow: 0, - - mru_list: [], - - updateWait: function(value, was_banned) { - var wait = this.get('slowWait') || 0; - this.set('slowWait', value); - if ( wait < 1 && value > 0 ) { - if ( this._ffz_wait_timer ) - clearTimeout(this._ffz_wait_timer); - this._ffz_wait_timer = setTimeout(this.ffzUpdateWait.bind(this), 1000); - f._roomv && f._roomv.ffzUpdateStatus(); - } else if ( (wait > 0 && value < 1) || was_banned ) { - this.set('ffz_banned', false); - f._roomv && f._roomv.ffzUpdateStatus(); - } - }, - - ffzUpdateWait: function() { - this._ffz_wait_timer = undefined; - var wait = this.get('slowWait') || 0; - if ( wait < 1 ) - return; - - this.set('slowWait', --wait); - if ( wait > 0 ) - this._ffz_wait_timer = setTimeout(this.ffzUpdateWait.bind(this), 1000); - else { - this.set('ffz_banned', false); - f._roomv && f._roomv.ffzUpdateStatus(); - } - }, - - ffzUpdateStatus: function() { - if ( f._roomv ) - f._roomv.ffzUpdateStatus(); - }.observes('r9k', 'subsOnly', 'slow', 'ffz_banned'), - - // User Level - ffzUserLevel: function() { - if ( this.get('isStaff') ) - return 5; - else if ( this.get('isAdmin') ) - return 4; - else if ( this.get('isBroadcaster') ) - return 3; - else if ( this.get('isGlobalModerator') ) - return 2; - else if ( this.get('isModerator') ) - return 1; - return 0; - }.property('id', 'chatLabels.[]'), - - // Track which rooms the user is currently in. - init: function() { - this._super(); - - try { - f.add_room(this.id, this); - this.set("ffz_chatters", {}); - } catch(err) { - f.error("add_room: " + err); - } - }, - - willDestroy: function() { - this._super(); - - try { - f.remove_room(this.id); - } catch(err) { - f.error("remove_room: " + err); - } - }, - - clearMessages: function(user) { - var t = this; - if ( user ) { - if (!this.ffzRecentlyBanned) - this.ffzRecentlyBanned = []; - this.ffzRecentlyBanned.push(user); - while (this.ffzRecentlyBanned.length > 100) - this.ffzRecentlyBanned.shift(); - - var msgs = t.get('messages'), - total = msgs.get('length'), - i = total, - alternate; - - // Delete visible messages - while(i--) { - var msg = msgs.get(i); - - if ( msg.from === user ) { - if ( f.settings.remove_deleted ) { - if ( alternate === undefined ) - alternate = msg.ffz_alternate; - msgs.removeAt(i); - continue; - } - - t.set('messages.' + i + '.ffz_deleted', true); - if ( ! f.settings.prevent_clear ) - t.set('messages.' + i + '.deleted', true); - } - - if ( alternate === undefined ) - alternate = msg.ffz_alternate; - else { - alternate = ! alternate; - t.set('messages.' + i + '.ffz_alternate', alternate); - } - } - - // Delete pending messages - if (t.ffzPending) { - msgs = t.ffzPending; - i = msgs.length; - while(i--) { - var msg = msgs.get(i); - if ( msg.from !== user ) continue; - msg.ffz_deleted = true; - msg.deleted = !f.settings.prevent_clear; - msg.removed = f.settings.remove_deleted; - } - } - - if ( f.settings.mod_card_history ) { - var room = f.rooms && f.rooms[t.get('id')], - user_history = room && room.user_history && room.user_history[user] - - if ( user_history !== null && user_history !== undefined ) { - var has_delete = false, - last = user_history.length > 0 ? user_history[user_history.length-1] : null; - - has_delete = last !== null && last.is_delete; - if ( has_delete ) { - last.cachedTokens = ['User has been timed out ' + utils.number_commas(++last.deleted_times) + ' times.']; - } else { - user_history.push({from: 'jtv', is_delete: true, style: 'admin', cachedTokens: ['User has been timed out.'], deleted_times: 1, date: new Date()}); - while ( user_history.length > 20 ) - user_history.shift(); - } - } - } - } else { - if ( f.settings.prevent_clear ) - this.addTmiMessage("A moderator's attempt to clear chat was ignored."); - else { - var msgs = t.get("messages"); - t.set("messages", []); - t.addMessage({ - style: 'admin', - message: i18n("Chat was cleared by a moderator"), - ffz_old_messages: msgs - }); - } - } - }, - - trimMessages: function() { - var messages = this.get("messages"), - len = messages.get("length"), - limit = this.get("messageBufferSize"); - - if ( len > limit ) - messages.removeAt(0, len - limit); - }, - - // Artificial chat delay - pushMessage: function(msg) { - if (+f.settings.chat_delay) { - if (!this.ffzPending) - this.ffzPending = []; - - // uses black magic to ensure messages get flushed, but without a setInterval - if (!this.ffzPending.length) - setTimeout(this.ffzPendingFlush.bind(this), 100); - - msg.time = Date.now(); - this.ffzPending.push(msg); - } else { - this.ffzActualPushMessage(msg); - } - }, - - ffzActualPushMessage: function (msg) { - if ( this.shouldShowMessage(msg) && this.ffzShouldShowMessage(msg) ) { - this.get("messages").pushObject(msg); - this.trimMessages(); - - "admin" === msg.style || ("whisper" === msg.style && ! this.ffz_whisper_room ) || this.incrementProperty("unreadCount", 1); - } - }, - - ffzPendingFlush: function() { - var now = Date.now(); - for (var i = 0, l = this.ffzPending.length; i < l; i++) { - var msg = this.ffzPending[i]; - if (msg.removed) continue; - if (+f.settings.chat_delay + msg.time > now) break; - this.ffzActualPushMessage(msg); - } - this.ffzPending = this.ffzPending.slice(i); - - // uses black magic to ensure messages get flushed, but without a setInterval - if (this.ffzPending.length) - setTimeout(this.ffzPendingFlush.bind(this), 100); - }, - - ffzShouldShowMessage: function (msg) { - if (f.settings.remove_bot_ban_notices && this.ffzRecentlyBanned) { - var banned = '(' + this.ffzRecentlyBanned.join('|') + ')'; - var bots = { - 'nightbot': '^' + banned, - 'moobot': '\\(' + banned + '\\)', - 'xanbot': '^' + banned, - }; - - if (msg.from in bots && (new RegExp(bots[msg.from])).test(msg.message)) { - return false; - } - } - - return true; - }, - - addMessage: function(msg) { - if ( msg ) { - if ( ! f.settings.hosted_sub_notices && msg.style === 'notification' && HOSTED_SUB.test(msg.message) ) - return; - - var is_whisper = msg.style === 'whisper'; - if ( f.settings.group_tabs && f.settings.whisper_room ) { - if ( ( is_whisper && ! this.ffz_whisper_room ) || ( ! is_whisper && this.ffz_whisper_room ) ) - return; - } - - if ( ! is_whisper ) - msg.room = this.get('id'); - - // Tokenization - f.tokenize_chat_line(msg, false, this.get('roomProperties.hide_chat_links')); - - // Keep the history. - if ( ! is_whisper && msg.from && msg.from !== 'jtv' && msg.from !== 'twitchnotify' && f.settings.mod_card_history ) { - var room = f.rooms && f.rooms[msg.room]; - if ( room ) { - var chat_history = room.user_history = room.user_history || {}, - user_history = room.user_history[msg.from] = room.user_history[msg.from] || []; - - user_history.push({ - from: msg.tags && msg.tags['display-name'] || msg.from, - cachedTokens: msg.cachedTokens, - style: msg.style, - date: msg.date - }); - - if ( user_history.length > 20 ) - user_history.shift(); - } - } - - // Check for message from us. - if ( ! is_whisper ) { - var user = f.get_user(); - if ( user && user.login === msg.from ) { - var was_banned = this.get('ffz_banned'); - this.set('ffz_banned', false); - - // Update the wait time. - if ( this.get('isSubscriber') || this.get('isModeratorOrHigher') || ! this.get('slowMode') ) - this.updateWait(0, was_banned) - else if ( this.get('slowMode') ) - this.updateWait(this.get('slow')); - } - } - - // Also update chatters. - if ( ! is_whisper && this.chatters && ! this.chatters[msg.from] && msg.from !== 'twitchnotify' && msg.from !== 'jtv' ) - this.ffzUpdateChatters(msg.from); - } - - var out = this._super(msg); - - // Color processing. - if ( msg.color ) - f._handle_color(msg.color); - - return out; - }, - - setHostMode: function(e) { - this.set('ffz_host_target', e && e.hostTarget || null); - var user = f.get_user(); - if ( user && f._cindex && this.get('id') === user.login ) - f._cindex.ffzUpdateHostButton(); - - var Chat = App.__container__.lookup('controller:chat'); - if ( ! Chat || Chat.get('currentChannelRoom') !== this ) - return; - - return this._super(e); - }, - - send: function(text) { - if ( f.settings.group_tabs && f.settings.whisper_room && this.ffz_whisper_room ) - return; - - try { - if ( text ) { - // Command History - var mru = this.get('mru_list'), - ind = mru.indexOf(text); - - if ( ind !== -1 ) - mru.splice(ind, 1) - else if ( mru.length > 20 ) - mru.pop(); - - mru.unshift(text); - } - - var cmd = text.split(' ', 1)[0].toLowerCase(); - if ( cmd === "/ffz" ) { - this.set("messageToSend", ""); - 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); - }, - - ffzUpdateUnread: function() { - if ( f.settings.group_tabs ) { - var Chat = App.__container__.lookup('controller:chat'); - if ( Chat && Chat.get('currentRoom') === this ) - this.resetUnreadCount(); - else if ( f._chatv ) - f._chatv.ffzTabUnread(this.get('id')); - } - }.observes('unreadCount'), - - - ffzInitChatterCount: function() { - if ( ! this.tmiRoom ) - return; - - if ( this._ffz_chatter_timer ) { - clearTimeout(this._ffz_chatter_timer); - this._ffz_chatter_timer = undefined; - } - - var room = this; - this.tmiRoom.list().done(function(data) { - var chatters = {}; - data = data.data.chatters; - for(var i=0; i < data.admins.length; i++) - chatters[data.admins[i]] = true; - for(var i=0; i < data.global_mods.length; i++) - chatters[data.global_mods[i]] = true; - for(var i=0; i < data.moderators.length; i++) - chatters[data.moderators[i]] = true; - for(var i=0; i < data.staff.length; i++) - chatters[data.staff[i]] = true; - for(var i=0; i < data.viewers.length; i++) - chatters[data.viewers[i]] = true; - - room.set("ffz_chatters", chatters); - room.ffzUpdateChatters(); - }).always(function() { - room._ffz_chatter_timer = setTimeout(room.ffzInitChatterCount.bind(room), 300000); - }); - }, - - - ffzUpdateChatters: function(add, remove) { - var chatters = this.get("ffz_chatters") || {}; - if ( add ) - chatters[add] = true; - if ( remove && chatters[remove] ) - delete chatters[remove]; - - if ( ! f.settings.chatter_count ) - return; - - if ( f._cindex ) - f._cindex.ffzUpdateChatters(); - - try { - if ( window.parent && window.parent.postMessage ) - window.parent.postMessage({from_ffz: true, command: 'chatter_count', message: Object.keys(this.get('ffz_chatters') || {}).length}, "http://www.twitch.tv/"); - } catch(err) { /* Ignore errors because of security */ } - }, - - - ffzPatchTMI: function() { - if ( this.get('ffz_is_patched') || ! this.get('tmiRoom') ) - return; - - if ( f.settings.chatter_count ) - this.ffzInitChatterCount(); - - var tmi = this.get('tmiRoom'), - room = this; - - // Let's get chatter information! - // TODO: Remove this cause it's terrible. - var connection = tmi._roomConn._connection; - if ( ! connection.ffz_cap_patched ) { - connection.ffz_cap_patched = true; - connection._send("CAP REQ :twitch.tv/membership"); - - connection.on("opened", function() { - this._send("CAP REQ :twitch.tv/membership"); - }, connection); - } - - - // NOTICE for catching slow-mode updates - tmi.on('notice', function(msg) { - if ( msg.msgId === 'msg_slowmode' ) { - var match = /in (\d+) seconds/.exec(msg.message); - if ( match ) { - room.updateWait(parseInt(match[1])); - } - } - - if ( msg.msgId === 'msg_timedout' ) { - var match = /for (\d+) more seconds/.exec(msg.message); - if ( match ) { - room.set('ffz_banned', true); - room.updateWait(parseInt(match[1])); - } - } - - if ( msg.msgId === 'msg_banned' ) { - room.set('ffz_banned', true); - f._roomv && f._roomv.ffzUpdateStatus(); - } - - if ( msg.msgId === 'hosts_remaining' ) { - var match = /(\d+) host command/.exec(msg.message); - if ( match ) { - room.set('ffz_hosts_left', parseInt(match[1] || 0)); - f._cindex && f._cindex.ffzUpdateHostButton(); - } - } - }); - - // Check this shit. - tmi._roomConn._connection.off("message", tmi._roomConn._onIrcMessage, tmi._roomConn); - - tmi._roomConn._onIrcMessage = function(ircMsg) { - if ( ircMsg.target != this.ircChannel ) - return; - - switch ( ircMsg.command ) { - case "JOIN": - if ( this._session && this._session.nickname === ircMsg.sender ) { - this._onIrcJoin(ircMsg); - } else - f.settings.chatter_count && room.ffzUpdateChatters(ircMsg.sender); - break; - - case "PART": - if ( this._session && this._session.nickname === ircMsg.sender ) { - this._resetActiveState(); - this._connection._exitedRoomConn(); - this._trigger("exited"); - } else - f.settings.chatter_count && room.ffzUpdateChatters(null, ircMsg.sender); - break; - - default: - break; - } - } - - tmi._roomConn._connection.on("message", tmi._roomConn._onIrcMessage, tmi._roomConn); - - this.set('ffz_is_patched', true); - - }.observes('tmiRoom'), - - // Room State Stuff - - slowMode: function() { - return this.get('slow') > 0; - }.property('slow'), - - onSlowOff: function() { - if ( ! this.get('slowMode') ) - this.updateWait(0); - }.observes('slowMode') - }); -} -},{"../constants":5,"../utils":35}],14:[function(require,module,exports){ -var FFZ = window.FrankerFaceZ; - - -// -------------------- -// Initialization -// -------------------- - -FFZ.prototype.setup_viewers = function() { - this.log("Hooking the Ember Viewers controller."); - - var Viewers = App.__container__.resolve('controller:viewers'); - this._modify_viewers(Viewers); -} - - -FFZ.prototype._modify_viewers = function(controller) { - var f = this; - - controller.reopen({ - lines: function() { - var viewers = this._super(); - try { - var categories = [], - data = {}, - last_category = null; - - // Get the broadcaster name. - var Channel = App.__container__.lookup('controller:channel'), - room_id = this.get('parentController.model.id'), - broadcaster = Channel && Channel.get('id'); - - // We can get capitalization for the broadcaster from the channel. - if ( broadcaster ) { - var display_name = Channel.get('display_name'); - if ( display_name ) - FFZ.capitalization[broadcaster] = [display_name, Date.now()]; - } - - // If the current room isn't the channel's chat, then we shouldn't - // display them as the broadcaster. - if ( room_id != broadcaster ) - broadcaster = null; - - // Now, break the viewer array down into something we can use. - for(var i=0; i < viewers.length; i++) { - var entry = viewers[i]; - if ( entry.category ) { - last_category = entry.category; - categories.push(last_category); - data[last_category] = []; - - } else { - var viewer = entry.chatter.toLowerCase(); - if ( ! viewer ) - continue; - - // If the viewer is the broadcaster, give them their own - // group. Don't put them with normal mods! - if ( viewer == broadcaster ) { - categories.unshift("Broadcaster"); - data["Broadcaster"] = [viewer]; - - } else if ( data.hasOwnProperty(last_category) ) - data[last_category].push(viewer); - } - } - - // Now, rebuild the viewer list. However, we're going to actually - // sort it this time. - viewers = []; - for(var i=0; i < categories.length; i++) { - var category = categories[i], - chatters = data[category]; - - if ( ! chatters || ! chatters.length ) - continue; - - viewers.push({category: category}); - viewers.push({chatter: ""}); - - // Push the chatters, capitalizing them as we go. - chatters.sort(); - while(chatters.length) { - var viewer = chatters.shift(); - viewer = FFZ.get_capitalization(viewer); - viewers.push({chatter: viewer}); - } - } - - } catch(err) { - f.error("ViewersController lines: " + err); - } - - return viewers; - }.property("content.chatters") - }); -} -},{}],15:[function(require,module,exports){ -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, - MOD_CSS = /[^\n}]*\.badges\s+\.moderator\s*{\s*background-image:\s*url\(\s*['"]([^'"]+)['"][^}]+(?:}|$)/, - constants = require('./constants'), - utils = require('./utils'), - - - check_margins = function(margins, height) { - var mlist = margins.split(/ +/); - if ( mlist.length != 2 ) - return margins; - - mlist[0] = parseFloat(mlist[0]); - mlist[1] = parseFloat(mlist[1]); - - if ( mlist[0] == (height - 18) / -2 && mlist[1] == 0 ) - return null; - - return margins; - }, - - - build_legacy_css = function(emote) { - var margin = emote.margins, srcset = ""; - if ( ! margin ) - margin = ((emote.height - 18) / -2) + "px 0"; - - if ( emote.urls[2] || emote.urls[4] ) { - srcset = 'url("' + emote.urls[1] + '") 1x'; - if ( emote.urls[2] ) - srcset += ', url("' + emote.urls[2] + '") 2x'; - if ( emote.urls[4] ) - srcset += ', url("' + emote.urls[4] + '") 4x'; - - srcset = '-webkit-image-set(' + srcset + '); image-set(' + srcset + ');'; - } - - return ".ffz-emote-" + emote.id + ' { background-image: url("' + emote.urls[1] + '"); height: ' + emote.height + "px; width: " + emote.width + "px; margin: " + margin + (srcset ? '; ' + srcset : '') + (emote.css ? "; " + emote.css : "") + "}\n"; - }, - - - build_new_css = function(emote) { - if ( ! emote.margins && ! emote.css ) - return build_legacy_css(emote); - - return build_legacy_css(emote) + 'img[src="' + emote.urls[1] + '"] { ' + (emote.margins ? "margin: " + emote.margins + ";" : "") + (emote.css || "") + " }\n"; - }, - - - build_css = build_new_css, - - from_code_point = function(cp) { - var code = typeof cp === "string" ? parseInt(cp, 16) : cp; - if ( code < 0x10000) - return String.fromCharCode(code); - - code -= 0x10000; - return String.fromCharCode( - 0xD800 + (code >> 10), - 0xDC00 + (code & 0x3FF) - ); - }; - - -// --------------------- -// Initialization -// --------------------- - -FFZ.prototype.setup_emoticons = function() { - this.log("Preparing emoticon system."); - - this.emoji_data = {}; - this.emoji_names = {}; - - this.emote_sets = {}; - this.global_sets = []; - this.default_sets = []; - this._last_emote_id = 0; - - // Usage Data - this.emote_usage = {}; - - - this.log("Creating emoticon style element."); - var s = this._emote_style = document.createElement('style'); - s.id = "ffz-emoticon-css"; - document.head.appendChild(s); - - this.log("Loading global emote sets."); - this.load_global_sets(); - - this.log("Loading emoji data."); - this.load_emoji_data(); - - this.log("Watching Twitch emoticon parser to ensure it loads."); - this._twitch_emote_check = setTimeout(this.check_twitch_emotes.bind(this), 10000); -} - - -// ------------------------ -// Emote Usage -// ------------------------ - -FFZ.prototype.add_usage = function(room_id, emote_id, count) { - var rooms = this.emote_usage[emote_id] = this.emote_usage[emote_id] || {}; - rooms[room_id] = (rooms[room_id] || 0) + (count || 1); - - if ( this._emote_report_scheduled ) - return; - - this._emote_report_scheduled = setTimeout(this._report_emotes.bind(this), 30000); -} - - -FFZ.prototype._report_emotes = function() { - if ( this._emote_report_scheduled ) - delete this._emote_report_scheduled; - - var usage = this.emote_usage; - this.emote_usage = {}; - this.ws_send("emoticon_uses", [usage], function(){}, true); -} - - -// ------------------------ -// Twitch Emoticon Checker -// ------------------------ - -FFZ.prototype.check_twitch_emotes = function() { - if ( this._twitch_emote_check ) { - clearTimeout(this._twitch_emote_check); - delete this._twitch_emote_check; - } - - var room; - if ( this.rooms ) { - for(var key in this.rooms) { - if ( this.rooms.hasOwnProperty(key) ) { - room = this.rooms[key]; - break; - } - } - } - - if ( ! room || ! room.room || ! room.room.tmiSession ) { - this._twitch_emote_check = setTimeout(this.check_twitch_emotes.bind(this), 10000); - return; - } - - var parser = room.room.tmiSession._emotesParser, - emotes = Object.keys(parser.emoticonRegexToIds).length; - - // If we have emotes, we're done! - if ( emotes > 0 ) - return; - - // No emotes. Try loading them. - var sets = parser.emoticonSetIds; - parser.emoticonSetIds = ""; - parser.updateEmoticons(sets); - - // Check again in a bit to see if we've got them. - this._twitch_emote_check = setTimeout(this.check_twitch_emotes.bind(this), 10000); -} - - - -// --------------------- -// Set Management -// --------------------- - -FFZ.prototype.getEmotes = function(user_id, room_id) { - var user = this.users && this.users[user_id], - room = this.rooms && this.rooms[room_id]; - - return _.union(user && user.sets || [], room && room.set && [room.set] || [], room && room.extra_sets || [], this.default_sets); -} - - -// --------------------- -// Commands -// --------------------- - -FFZ.ws_commands.reload_set = function(set_id) { - if ( this.emote_sets.hasOwnProperty(set_id) ) - this.load_set(set_id); -} - - -FFZ.ws_commands.load_set = function(set_id) { - this.load_set(set_id); -} - - -// --------------------- -// Tooltip Powah! -// --------------------- - -FFZ.prototype._emote_tooltip = function(emote) { - if ( ! emote ) - return null; - - if ( emote._tooltip ) - return emote._tooltip; - - var set = this.emote_sets[emote.set_id], - owner = emote.owner, - title = set && set.title || "Global"; - - emote._tooltip = "Emoticon: " + (emote.hidden ? "???" : emote.name) + "\nFFZ " + title + (owner ? "\nBy: " + owner.display_name : ""); - return emote._tooltip; -} - - -// --------------------- -// Emoji Loading -// --------------------- - -FFZ.prototype.load_emoji_data = function(callback, tries) { - var f = this; - jQuery.getJSON(constants.SERVER + "emoji/emoji.json") - .done(function(data) { - var new_data = {}, - by_name = {}; - for(var eid in data) { - var emoji = data[eid]; - eid = eid.toLowerCase(); - emoji.code = eid; - - new_data[eid] = emoji; - by_name[emoji.short_name] = eid; - - emoji.raw = _.map(emoji.code.split("-"), from_code_point).join(""); - - emoji.src = constants.SERVER + 'emoji/' + eid + '-1x.png'; - emoji.srcSet = emoji.src + ' 1x, ' + constants.SERVER + 'emoji/' + eid + '-2x.png 2x, ' + constants.SERVER + 'emoji/' + eid + '-4x.png 4x'; - - emoji.token = { - srcSet: emoji.srcSet, - emoticonSrc: emoji.src, - ffzEmoji: eid, - altText: emoji.raw - }; - - } - - f.emoji_data = new_data; - f.emoji_names = by_name; - - f.log("Loaded data on " + Object.keys(new_data).length + " emoji."); - if ( typeof callback === "function" ) - callback(true, data); - - }).fail(function(data) { - if ( data.status === 404 ) - return typeof callback === "function" && callback(false); - - tries = (tries || 0) + 1; - if ( tries < 50 ) - return f.load_emoji(callback, tries); - - return typeof callback === "function" && callback(false); - }); -} - - -// --------------------- -// Set Loading -// --------------------- - -FFZ.prototype.load_global_sets = function(callback, tries) { - var f = this; - jQuery.getJSON(((tries||0)%2 === 0 ? constants.API_SERVER : constants.API_SERVER_2) + "v1/set/global") - .done(function(data) { - f.default_sets = data.default_sets; - var gs = f.global_sets = [], - sets = data.sets || {}; - - if ( f.feature_friday && f.feature_friday.set ) { - if ( f.global_sets.indexOf(f.feature_friday.set) === -1 ) - f.global_sets.push(f.feature_friday.set); - if ( f.default_sets.indexOf(f.feature_friday.set) === -1 ) - f.default_sets.push(f.feature_friday.set); - } - - for(var key in sets) { - if ( ! sets.hasOwnProperty(key) ) - continue; - - var set = sets[key]; - gs.push(key); - f._load_set_json(key, undefined, set); - } - }).fail(function(data) { - if ( data.status == 404 ) - return typeof callback == "function" && callback(false); - - tries = tries || 0; - tries++; - if ( tries < 50 ) - return f.load_global_sets(callback, tries); - - return typeof callback == "function" && callback(false); - }); -} - - -FFZ.prototype.load_set = function(set_id, callback, tries) { - var f = this; - jQuery.getJSON(((tries||0)%2 === 0 ? constants.API_SERVER : constants.API_SERVER_2) + "v1/set/" + set_id) - .done(function(data) { - f._load_set_json(set_id, callback, data && data.set); - - }).fail(function(data) { - if ( data.status == 404 ) - return typeof callback == "function" && callback(false); - - tries = tries || 0; - tries++; - if ( tries < 10 ) - return f.load_set(set_id, callback, tries); - - return typeof callback == "function" && callback(false); - }); -} - - -FFZ.prototype.unload_set = function(set_id) { - var set = this.emote_sets[set_id]; - if ( ! set ) - return; - - this.log("Unloading emoticons for set: " + set_id); - - utils.update_css(this._emote_style, set_id, null); - delete this.emote_sets[set_id]; -} - - -FFZ.prototype._load_set_json = function(set_id, callback, data) { - if ( ! data ) - return typeof callback == "function" && callback(false); - - // Do we have existing users? - var users = this.emote_sets[set_id] && this.emote_sets[set_id].users || []; - - // Store our set. - this.emote_sets[set_id] = data; - data.users = users; - data.count = 0; - - - // Iterate through all the emoticons, building CSS and regex objects as appropriate. - var output_css = "", - ems = data.emoticons; - - data.emoticons = {}; - - for(var i=0; i < ems.length; i++) { - var emote = ems[i]; - - emote.klass = "ffz-emote-" + emote.id; - emote.set_id = set_id; - - emote.srcSet = emote.urls[1] + " 1x"; - if ( emote.urls[2] ) - emote.srcSet += ", " + emote.urls[2] + " 2x"; - if ( emote.urls[4] ) - emote.srcSet += ", " + emote.urls[4] + " 4x"; - - if ( emote.name[emote.name.length-1] === "!" ) - emote.regex = new RegExp("(^|\\W|\\b)(" + emote.name + ")(?=\\W|$)", "g"); - else - emote.regex = new RegExp("(^|\\W|\\b)(" + emote.name + ")\\b", "g"); - - output_css += build_css(emote); - data.count++; - data.emoticons[emote.id] = emote; - } - - utils.update_css(this._emote_style, set_id, output_css + (data.css || "")); - this.log("Updated emoticons for set #" + set_id + ": " + data.title, data); - - if ( this._cindex ) - this._cindex.ffzFixTitle(); - - this.update_ui_link(); - - if ( callback ) - callback(true, data); -} -},{"./constants":5,"./utils":35}],16:[function(require,module,exports){ -var FFZ = window.FrankerFaceZ, - constants = require('../constants'), - utils = require('../utils'), - SENDER_REGEX = /(\sdata-sender="[^"]*"(?=>))/; - - -// -------------------- -// Initialization -// -------------------- - -FFZ.prototype.find_bttv = function(increment, delay) { - this.has_bttv = false; - if ( window.BTTVLOADED ) - return this.setup_bttv(delay||0); - - if ( delay >= 60000 ) - this.log("BetterTTV was not detected after 60 seconds."); - else - setTimeout(this.find_bttv.bind(this, increment, (delay||0) + increment), - increment); -} - - -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()); - - // Disable Dark if it's enabled. - document.body.classList.remove("ffz-dark"); - if ( this._dark_style ) { - this._dark_style.parentElement.removeChild(this._dark_style); - this._dark_style = undefined; - } - - if ( this._layout_style ) { - this._layout_style.parentElement.removeChild(this._layout_style); - this._layout_style = undefined; - } - - if ( this._chat_style ) { - utils.update_css(this._chat_style, 'chat_font_size', ''); - utils.update_css(this._chat_style, 'chat_ts_font_size', ''); - } - - // Disable Chat Tabs - if ( this.settings.group_tabs && this._chatv ) { - this._chatv.ffzDisableTabs(); - } - - if ( this._roomv ) { - // Disable Chat Pause - if ( this.settings.chat_hover_pause ) - this._roomv.ffzDisableFreeze(); - - // And hide the status - if ( this.settings.room_status ) - this._roomv.ffzUpdateStatus(); - } - - // Disable other features too. - document.body.classList.remove("ffz-chat-colors"); - document.body.classList.remove("ffz-chat-colors-gray"); - document.body.classList.remove("ffz-chat-background"); - document.body.classList.remove("ffz-chat-padding"); - document.body.classList.remove("ffz-chat-separator"); - document.body.classList.remove("ffz-chat-separator-3d"); - document.body.classList.remove("ffz-sidebar-swap"); - document.body.classList.remove("ffz-transparent-badges"); - document.body.classList.remove("ffz-high-contrast-chat-text"); - document.body.classList.remove("ffz-high-contrast-chat-bg"); - document.body.classList.remove("ffz-high-contrast-chat-bold"); - - // Remove Following Count - if ( this.settings.following_count ) { - this._schedule_following_count(); - this._draw_following_count(); - this._draw_following_channels(); - } - - // Remove Sub Count - if ( this.is_dashboard ) - this._update_subscribers(); - - document.body.classList.add('ffz-bttv'); - - // 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_ffz_command(message.substr(5), BetterTTV.chat.store.currentRoom); - else - return original_send(message); - } - - - // Ugly Hack for Current Room, as this is stripped out before we get to - // the actual privmsg renderer. - var original_handler = BetterTTV.chat.handlers.onPrivmsg, - received_room; - BetterTTV.chat.handlers.onPrivmsg = function(room, data) { - received_room = room; - var output = original_handler(room, data); - received_room = null; - return output; - } - - - // Message Display Behavior - var original_privmsg = BetterTTV.chat.templates.privmsg; - BetterTTV.chat.templates.privmsg = function(highlight, action, server, isMod, data) { - try { - // Handle badges. - f.bttv_badges(data); - - // Now, do everything else manually because things are hard-coded. - return '
'+ - BetterTTV.chat.templates.timestamp(data.time)+' '+ - (isMod?BetterTTV.chat.templates.modicons():'')+' '+ - BetterTTV.chat.templates.badges(data.badges)+ - BetterTTV.chat.templates.from(data.nickname, data.color)+ - BetterTTV.chat.templates.message(data.sender, data.message, data.emotes, action?data.color:false)+ - '
'; - } catch(err) { - f.log("Error: ", err); - return original_privmsg(highlight, action, server, isMod, data); - } - } - - // Whispers too! - var original_whisper = BetterTTV.chat.templates.whisper; - BetterTTV.chat.templates.whisper = function(data) { - try { - // Handle badges. - f.bttv_badges(data); - - // Now, do everything else manually because things are hard-coded. - return '
' + - BetterTTV.chat.templates.timestamp(data.time) + ' ' + - (data.badges && data.badges.length ? BetterTTV.chat.templates.badges(data.badges) : '') + - BetterTTV.chat.templates.whisperName(data.sender, data.receiver, data.from, data.to, data.fromColor, data.toColor) + - BetterTTV.chat.templates.message(data.sender, data.message, data.emotes, false) + - '
'; - } catch(err) { - f.log("Error: ", err); - return original_whisper(data); - } - } - - // Message Renderer. I had to completely rewrite this method to get it to - // use my replacement emoticonizer. - var original_message = BetterTTV.chat.templates.message, - received_sender; - BetterTTV.chat.templates.message = function(sender, message, emotes, colored) { - try { - colored = colored || false; - var rawMessage = encodeURIComponent(message); - - if(sender !== 'jtv') { - // Hackilly send our state across. - received_sender = sender; - var tokenizedMessage = BetterTTV.chat.templates.emoticonize(message, emotes); - received_sender = null; - - for(var i=0; i'+message+''; - } catch(err) { - f.log("Error: ", err); - return original_message(sender, message, emotes, colored); - } - }; - - // Emoticonize - var original_emoticonize = BetterTTV.chat.templates.emoticonize; - BetterTTV.chat.templates.emoticonize = function(message, emotes) { - var tokens = original_emoticonize(message, emotes), - - room = (received_room || BetterTTV.getChannel()), - l_room = room && room.toLowerCase(), - l_sender = received_sender && received_sender.toLowerCase(), - sets = f.getEmotes(l_sender, l_room), - emotes = [], - user = f.get_user(), - mine = user && user.login === l_sender; - - // Build a list of emotes that match. - _.each(sets, function(set_id) { - var set = f.emote_sets[set_id]; - if ( ! set ) - return; - - _.each(set.emoticons, function(emote) { - _.any(tokens, function(token) { - return _.isString(token) && token.match(emote.regex); - }) && emotes.push(emote); - }); - }); - - // Don't bother proceeding if we have no emotes. - if ( emotes.length ) { - // Why is emote parsing so bad? ;_; - _.each(emotes, function(emote) { - var tooltip = f._emote_tooltip(emote), - eo = [''], - old_tokens = tokens; - - tokens = []; - - for(var i=0; i < old_tokens.length; i++) { - var token = old_tokens[i]; - if ( typeof token != "string" ) { - tokens.push(token); - continue; - } - - var tbits = token.split(emote.regex); - while(tbits.length) { - var bit = tbits.shift(); - if ( tbits.length ) { - bit += tbits.shift(); - if ( bit ) - tokens.push(bit); - - tbits.shift(); - tokens.push(eo); - - if ( mine && l_room ) - f.add_usage(l_room, emote.id); - - } else - tokens.push(bit); - } - } - }); - } - - // Sneak in Emojicon Processing - /* - if ( f.settings.parse_emoji && f.emoji_data ) { - var old_tokens = tokens; - tokens = []; - - for(var i=0; i < old_tokens.length; i++) { - var token = old_tokens[i]; - if ( typeof token !== "string" ) { - tokens.push(token); - continue; - } - - var tbits = token.split(constants.EMOJI_REGEX); - while(tbits.length) { - var bit = tbits.shift(); - bit && tokens.push(bit); - - if ( tbits.length ) { - var match = tbits.shift(), - variant = tbits.shift(); - - if ( variant === '\uFE0E' ) - bits.push(match); - else { - var eid = utils.emoji_to_codepoint(match, variant), - data = f.emoji_data[eid]; - - if ( data ) { - tokens.push(['' + alt + '']); - } else - tokens.push(match + (variant || "")); - } - } - } - } - }*/ - - return tokens; - } - - this.update_ui_link(); -} -},{"../constants":5,"../utils":35}],17:[function(require,module,exports){ -var FFZ = window.FrankerFaceZ; - - -// -------------------- -// Initialization -// -------------------- - -FFZ.prototype.find_emote_menu = function(increment, delay) { - this.has_emote_menu = false; - if ( window.emoteMenu && emoteMenu.registerEmoteGetter ) - return this.setup_emote_menu(delay||0); - - if ( delay >= 60000 ) - this.log("Emote Menu for Twitch was not detected after 60 seconds."); - else - setTimeout(this.find_emote_menu.bind(this, increment, (delay||0) + increment), - increment); -} - - -FFZ.prototype.setup_emote_menu = function(delay) { - this.log("Emote Menu for Twitch was detected after " + delay + "ms. Registering emote enumerator."); - emoteMenu.registerEmoteGetter("FrankerFaceZ", this._emote_menu_enumerator.bind(this)); -} - - -// -------------------- -// Emote Enumerator -// -------------------- - -FFZ.prototype._emote_menu_enumerator = function() { - var twitch_user = this.get_user(), - user_id = twitch_user ? twitch_user.login : null, - controller = App.__container__.lookup('controller:chat'), - room_id = controller ? controller.get('currentRoom.id') : null, - sets = this.getEmotes(user_id, room_id), - emotes = []; - - for(var x = 0; x < sets.length; x++) { - var set = this.emote_sets[sets[x]]; - if ( ! set || ! set.emoticons ) - continue; - - for(var emote_id in set.emoticons) { - if ( ! set.emoticons.hasOwnProperty(emote_id) ) - continue; - - var emote = set.emoticons[emote_id]; - if ( emote.hidden ) - continue; - - var title = "FrankerFaceZ " + set.title, - badge = set.icon || '//cdn.frankerfacez.com/script/devicon.png'; - - emotes.push({text: emote.name, url: emote.urls[1], - hidden: false, channel: title, badge: badge}); - } - } - - return emotes; -} -},{}],18:[function(require,module,exports){ -// Modify Array and others. -// require('./shims'); - -// ---------------- -// The Constructor -// ---------------- - -var FFZ = window.FrankerFaceZ = function() { - FFZ.instance = this; - - // Logging - this._log_data = []; - - // Get things started. - this.initialize(); -} - - -FFZ.get = function() { return FFZ.instance; } - - -// Version -var VER = FFZ.version_info = { - major: 3, minor: 5, revision: 13, - toString: function() { - return [VER.major, VER.minor, VER.revision].join(".") + (VER.extra || ""); - } -} - - -// Logging - -FFZ.prototype.log = function(msg, data, to_json, log_json) { - msg = "FFZ: " + msg + (to_json ? " -- " + JSON.stringify(data) : ""); - this._log_data.push(msg + ((!to_json && log_json) ? " -- " + JSON.stringify(data) : "")); - - 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.log(msg); -} - - -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 -// ------------------- - -FFZ.prototype.get_user = function() { - if ( window.PP && PP.login ) { - return PP; - } else if ( window.App ) { - var nc = App.__container__.lookup("controller:login"); - return nc ? nc.get("userData") : undefined; - } -} - - -// ------------------- -// Import Everything! -// ------------------- - -// Import these first to set up data structures -require('./ui/menu'); -require('./settings'); -require('./socket'); - -require('./colors'); -require('./emoticons'); -require('./badges'); -require('./tokenize'); - - -// Analytics: require('./ember/router'); -require('./ember/channel'); -//require('./ember/player'); -require('./ember/room'); -require('./ember/layout'); -require('./ember/line'); -require('./ember/chatview'); -require('./ember/viewers'); -require('./ember/moderation-card'); -require('./ember/chat-input'); -//require('./ember/teams'); - -require('./debug'); - -require('./ext/betterttv'); -require('./ext/emote_menu'); - -require('./featurefriday'); - -require('./ui/styles'); -require('./ui/dark'); -require('./ui/notifications'); -require('./ui/viewer_count'); -require('./ui/sub_count'); - -require('./ui/menu_button'); -require('./ui/following'); -require('./ui/following-count'); -require('./ui/races'); -require('./ui/my_emotes'); -require('./ui/about_page'); - -require('./commands'); - - -// --------------- -// Initialization -// --------------- - -FFZ.prototype.initialize = function(increment, delay) { - // Make sure that FrankerFaceZ doesn't start setting itself up until the - // Twitch ember application is ready. - - // Check for the player - if ( location.hostname === 'player.twitch.tv' ) { - //this.init_player(delay); - return; - } - - // Check for special non-ember pages. - if ( /^\/(?:$|search$|user\/|p\/|settings|m\/|messages?\/)/.test(location.pathname) ) { - this.init_normal(delay); - return; - } - - if ( location.hostname === 'passport' && /^\/(?:authorize)/.test(location.pathname) ) { - this.log("Running on passport!"); - this.init_normal(delay, true); - return; - } - - // Check for the dashboard. - if ( /\/[^\/]+\/dashboard/.test(location.pathname) && !/bookmarks$/.test(location.pathname) ) { - this.init_dashboard(delay); - return; - } - - var loaded = window.App != undefined && - App.__container__ != undefined && - App.__container__.resolve('model:room') != undefined; - - if ( !loaded ) { - increment = increment || 10; - if ( delay >= 60000 ) - this.log("Twitch application not detected in \"" + location.toString() + "\". Aborting."); - else - setTimeout(this.initialize.bind(this, increment, (delay||0) + increment), - increment); - return; - } - - this.init_ember(delay); -} - - -FFZ.prototype.init_player = function(delay) { - var start = (window.performance && performance.now) ? performance.now() : Date.now(); - this.log("Found Twitch Player after " + (delay||0) + " ms in \"" + location + "\". Initializing FrankerFaceZ version " + FFZ.version_info); - - this.users = {}; - this.is_dashboard = false; - try { - this.embed_in_dash = window.top !== window && /\/[^\/]+\/dashboard/.test(window.top.location.pathname) && !/bookmarks$/.test(window.top.location.pathname); - } catch(err) { this.embed_in_dash = false; } - - // Literally only make it dark. - this.load_settings(); - this.setup_dark(); - - var end = (window.performance && performance.now) ? performance.now() : Date.now(), - duration = end - start; - - this.log("Initialization complete in " + duration + "ms"); -} - - -FFZ.prototype.init_normal = function(delay, no_socket) { - var start = (window.performance && performance.now) ? performance.now() : Date.now(); - this.log("Found non-Ember Twitch after " + (delay||0) + " ms in \"" + location + "\". Initializing FrankerFaceZ version " + FFZ.version_info); - - this.users = {}; - this.is_dashboard = false; - try { - this.embed_in_dash = window.top !== window && /\/[^\/]+\/dashboard/.test(window.top.location.pathname) && !/bookmarks$/.test(window.top.location.pathname); - } catch(err) { this.embed_in_dash = false; } - - // Initialize all the modules. - this.load_settings(); - - // Start this early, for quick loading. - this.setup_dark(); - - if ( ! no_socket ) - this.ws_create(); - - this.setup_colors(); - this.setup_emoticons(); - this.setup_badges(); - - this.setup_notifications(); - this.setup_following_count(false); - this.setup_css(); - this.setup_menu(); - - this.find_bttv(10); - - var end = (window.performance && performance.now) ? performance.now() : Date.now(), - duration = end - start; - - this.log("Initialization complete in " + duration + "ms"); -} - - -FFZ.prototype.is_dashboard = false; - -FFZ.prototype.init_dashboard = function(delay) { - var start = (window.performance && performance.now) ? performance.now() : Date.now(); - this.log("Found Twitch Dashboard after " + (delay||0) + " ms in \"" + location + "\". Initializing FrankerFaceZ version " + FFZ.version_info); - - this.users = {}; - this.is_dashboard = true; - this.embed_in_dash = false; - - // Initialize all the modules. - this.load_settings(); - - // Start this early, for quick loading. - this.setup_dark(); - - this.ws_create(); - this.setup_colors(); - this.setup_emoticons(); - this.setup_badges(); - - this.setup_tokenization(); - this.setup_notifications(); - this.setup_css(); - - this._update_subscribers(); - - // Set up the FFZ message passer. - this.setup_message_event(); - - this.find_bttv(10); - - var end = (window.performance && performance.now) ? performance.now() : Date.now(), - duration = end - start; - - this.log("Initialization complete in " + duration + "ms"); -} - - -FFZ.prototype.init_ember = function(delay) { - 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.users = {}; - this.is_dashboard = false; - try { - this.embed_in_dash = window.top !== window && /\/[^\/]+\/dashboard/.test(window.top.location.pathname) && !/bookmarks$/.test(window.top.location.pathname); - } catch(err) { this.embed_in_dash = false; } - - // Initialize all the modules. - this.load_settings(); - - // Start this early, for quick loading. - this.setup_dark(); - - this.ws_create(); - this.setup_emoticons(); - this.setup_badges(); - - //this.setup_router(); - this.setup_colors(); - this.setup_tokenization(); - //this.setup_player(); - this.setup_channel(); - this.setup_room(); - this.setup_line(); - this.setup_layout(); - this.setup_chatview(); - this.setup_viewers(); - this.setup_mod_card(); - this.setup_chat_input(); - - //this.setup_teams(); - - this.setup_notifications(); - this.setup_css(); - this.setup_menu(); - this.setup_my_emotes(); - this.setup_following(); - this.setup_following_count(true); - this.setup_races(); - - this.connect_extra_chat(); - - this.find_bttv(10); - this.find_emote_menu(10); - - this.check_ff(); - - var end = (window.performance && performance.now) ? performance.now() : Date.now(), - duration = end - start; - - this.log("Initialization complete in " + duration + "ms"); -} - - -// ------------------------ -// Dashboard Message Event -// ------------------------ - -FFZ.prototype.setup_message_event = function() { - this.log("Listening for Window Messages."); - window.addEventListener("message", this._on_window_message.bind(this), false); -} - - -FFZ.prototype._on_window_message = function(e) { - if ( ! e.data || ! e.data.from_ffz ) - return; - - var msg = e.data; -} -},{"./badges":2,"./colors":3,"./commands":4,"./debug":6,"./ember/channel":7,"./ember/chat-input":8,"./ember/chatview":9,"./ember/layout":10,"./ember/line":11,"./ember/moderation-card":12,"./ember/room":13,"./ember/viewers":14,"./emoticons":15,"./ext/betterttv":16,"./ext/emote_menu":17,"./featurefriday":19,"./settings":20,"./socket":21,"./tokenize":22,"./ui/about_page":23,"./ui/dark":24,"./ui/following":26,"./ui/following-count":25,"./ui/menu":27,"./ui/menu_button":28,"./ui/my_emotes":29,"./ui/notifications":30,"./ui/races":31,"./ui/styles":32,"./ui/sub_count":33,"./ui/viewer_count":34}],19:[function(require,module,exports){ -var FFZ = window.FrankerFaceZ, - constants = require('./constants'); - - -// -------------------- -// Initialization -// -------------------- - -FFZ.prototype.feature_friday = null; - - -// -------------------- -// Check FF -// -------------------- - -FFZ.prototype.check_ff = function(tries) { - if ( ! tries ) - this.log("Checking for Feature Friday data..."); - - jQuery.ajax(constants.SERVER + "script/event.json", {cache: false, dataType: "json", context: this}) - .done(function(data) { - return this._load_ff(data); - }).fail(function(data) { - if ( data.status == 404 ) - return this._load_ff(null); - - tries = tries || 0; - tries++; - if ( tries < 10 ) - return setTimeout(this.check_ff.bind(this, tries), 250); - - return this._load_ff(null); - }); -} - - -FFZ.ws_commands.reload_ff = function() { - this.check_ff(); -} - - -// -------------------- -// Rendering UI -// -------------------- - -FFZ.prototype._feature_friday_ui = function(room_id, parent, view) { - if ( ! this.feature_friday || this.feature_friday.channel == room_id ) - return; - - this._emotes_for_sets(parent, view, [this.feature_friday.set], this.feature_friday.title, this.feature_friday.icon, "FrankerFaceZ"); - - // Before we add the button, make sure the channel isn't the - // current channel. - var Channel = App.__container__.lookup('controller:channel'); - if ( Channel && Channel.get('id') == this.feature_friday.channel ) - return; - - - var ff = this.feature_friday, f = this, - btnc = document.createElement('div'), - btn = document.createElement('a'); - - btnc.className = 'chat-menu-content'; - btnc.style.textAlign = 'center'; - - var message = ff.display_name + (ff.live ? " is live now!" : ""); - - btn.className = 'button primary'; - btn.classList.toggle('live', ff.live); - btn.classList.toggle('blue', this.has_bttv && BetterTTV.settings.get('showBlueButtons')); - - btn.href = "http://www.twitch.tv/" + ff.channel; - btn.title = message; - btn.target = "_new"; - btn.innerHTML = "" + message + ""; - - // Track the number of users to click this button. - // btn.addEventListener('click', function() { f.track('trackLink', this.href, 'link'); }); - - btnc.appendChild(btn); - parent.appendChild(btnc); -} - - -// -------------------- -// Loading Data -// -------------------- - -FFZ.prototype._load_ff = function(data) { - // Check for previous Feature Friday data and remove it. - if ( this.feature_friday ) { - // Remove the global set, delete the data, and reset the UI link. - this.global_sets.removeObject(this.feature_friday.set); - this.default_sets.removeObject(this.feature_friday.set); - - this.feature_friday = null; - this.update_ui_link(); - } - - // If there's no data, just leave. - if ( ! data || ! data.set || ! data.channel ) - return; - - // We have our data! Set it up. - this.feature_friday = {set: data.set, channel: data.channel, live: false, - title: data.title || "Feature Friday", - display_name: FFZ.get_capitalization(data.channel, this._update_ff_name.bind(this))}; - - // Add the set. - this.global_sets.push(data.set); - this.default_sets.push(data.set); - this.load_set(data.set); - - // Check to see if the channel is live. - this._update_ff_live(); -} - - -FFZ.prototype._update_ff_live = function() { - if ( ! this.feature_friday ) - return; - - var f = this; - Twitch.api.get("streams/" + this.feature_friday.channel) - .done(function(data) { - f.feature_friday.live = data.stream != null; - f.update_ui_link(); - }) - .always(function() { - f.feature_friday.timer = setTimeout(f._update_ff_live.bind(f), 120000); - }); -} - - -FFZ.prototype._update_ff_name = function(name) { - if ( this.feature_friday ) - this.feature_friday.display_name = name; -} -},{"./constants":5}],20:[function(require,module,exports){ -var FFZ = window.FrankerFaceZ, - constants = require("./constants"), - FileSaver = require("./FileSaver"); - - - make_ls = function(key) { - return "ffz_setting_" + key; - }, - - toggle_setting = function(swit, key) { - var val = ! this.settings.get(key); - this.settings.set(key, val); - swit.classList.toggle('active', val); - }, - - option_setting = function(select, key) { - this.settings.set(key, JSON.parse(select.options[select.selectedIndex].value)); - }, - - - toggle_basic_setting = function(swit, key) { - var getter = FFZ.basic_settings[key].get, - val = !(typeof getter === 'function' ? getter.bind(this)() : this.settings.get(getter)), - - setter = FFZ.basic_settings[key].set; - - if ( typeof setter === 'function' ) - setter.bind(this)(val); - else - this.settings.set(setter, val); - - swit.classList.toggle('active', val); - }, - - option_basic_setting = function(select, key) { - FFZ.basic_settings[key].set.bind(this)(JSON.parse(select.options[select.selectedIndex].value)); - }; - - -// -------------------- -// Initializer -// -------------------- - -FFZ.settings_info = { - advanced_settings: { value: false, visible: false } -}; - -FFZ.basic_settings = {}; - -FFZ.prototype.load_settings = function() { - this.log("Loading settings."); - - // Build a settings object. - this.settings = {}; - - for(var key in FFZ.settings_info) { - if ( ! FFZ.settings_info.hasOwnProperty(key) ) - continue; - - var info = FFZ.settings_info[key], - ls_key = info.storage_key || make_ls(key), - val = info.hasOwnProperty("value") ? info.value : undefined; - - if ( localStorage.hasOwnProperty(ls_key) ) { - try { - val = JSON.parse(localStorage.getItem(ls_key)); - } catch(err) { - this.log('Error loading value for "' + key + '": ' + err); - } - } - - if ( info.process_value ) - val = info.process_value.bind(this)(val); - - this.settings[key] = val; - } - - // Helpers - this.settings.get = this._setting_get.bind(this); - this.settings.set = this._setting_set.bind(this); - this.settings.del = this._setting_del.bind(this); - - // Listen for Changes - window.addEventListener("storage", this._setting_update.bind(this), false); -} - - -// -------------------- -// Backup and Restore -// -------------------- - -FFZ.prototype.save_settings_file = function() { - var data = { - version: 1, - script_version: FFZ.version_info + '', - aliases: this.aliases, - settings: {} - }; - - for(var key in FFZ.settings_info) { - if ( ! FFZ.settings_info.hasOwnProperty(key) ) - continue; - - var info = FFZ.settings_info[key], - ls_key = info.storage_key || make_ls(key); - - if ( localStorage.hasOwnProperty(ls_key) ) - data.settings[key] = this.settings[key]; - } - - var blob = new Blob([JSON.stringify(data, null, 4)], {type: "application/json;charset=utf-8"}); - FileSaver.saveAs(blob, "ffz-settings.json"); -} - - -FFZ.prototype.load_settings_file = function(file) { - if ( typeof file === "string" ) - this._load_settings_file(file); - else { - var reader = new FileReader(), - f = this; - - reader.onload = function(e) { f._load_settings_file(e.target.result); } - reader.readAsText(file); - } -} - -FFZ.prototype._load_settings_file = function(data) { - try { - data = JSON.parse(data); - } catch(err) { - this.error("Error Loading Settings: " + err); - return alert("There was an error attempting to read the provided settings data."); - } - - this.log("Loading Settings Data", data); - - var skipped = [], - applied = []; - - if ( data.settings ) { - for(var key in data.settings) { - if ( ! FFZ.settings_info.hasOwnProperty(key) ) { - skipped.push(key); - continue; - } - - var info = FFZ.settings_info[key], - val = data.settings[key]; - - if ( info.process_value ) - val = info.process_value.bind(this)(val); - - if ( val !== this.settings.get(key) ) - this.settings.set(key, val); - - applied.push(key); - } - } - - // Do this in a timeout so that any styles have a moment to update. - setTimeout(function(){ - alert('Successfully loaded ' + applied.length + ' settings and skipped ' + skipped.length + ' settings.'); - }); -} - - -// -------------------- -// Menu Page -// -------------------- - -FFZ.menu_pages.settings = { - render: function(view, container) { - // Bottom Bar - var menu = document.createElement('ul'), - page = document.createElement('div'), - - tab_basic = document.createElement('li'), - link_basic = document.createElement('a'), - - tab_adv = document.createElement('li'), - link_adv = document.createElement('a'), - - tab_save = document.createElement('li'), - link_save = document.createElement('a'), - - height = parseInt(container.style.maxHeight || '0'); - - - // Height Calculation - if ( ! height ) - height = Math.max(200, view.$().height() - 172); - - if ( height && height !== NaN ) { - height -= 37; - page.style.maxHeight = height + 'px'; - } - - // Menu Building - page.className = 'ffz-ui-sub-menu-page'; - menu.className = 'menu sub-menu clearfix'; - - tab_basic.className = 'item'; - tab_basic.id = 'ffz-settings-page-basic'; - link_basic.innerHTML = 'Basic'; - tab_basic.appendChild(link_basic); - - tab_adv.className = 'item'; - tab_adv.id = 'ffz-settings-page-advanced'; - link_adv.innerHTML = 'Advanced'; - tab_adv.appendChild(link_adv); - - tab_save.className = 'item'; - tab_save.id = 'ffz-settings-page-save'; - link_save.textContent = 'Backup & Restore'; - tab_save.appendChild(link_save); - - menu.appendChild(tab_basic); - menu.appendChild(tab_adv); - menu.appendChild(tab_save); - - var cp = FFZ.menu_pages.settings.change_page; - - link_basic.addEventListener('click', cp.bind(this, view, container, menu, page, 'basic')); - link_adv.addEventListener('click', cp.bind(this, view, container, menu, page, 'advanced')); - link_save.addEventListener('click', cp.bind(this, view, container, menu, page, 'save')); - - if ( this.settings.advanced_settings ) - link_adv.click(); - else - link_basic.click(); - - container.appendChild(page); - container.appendChild(menu); - }, - - change_page: function(view, container, menu, page, key) { - page.innerHTML = ''; - page.setAttribute('data-page', key); - - var els = menu.querySelectorAll('li.active'); - for(var i=0, l = els.length; i < l; i++) - els[i].classList.remove('active'); - - var el = menu.querySelector('#ffz-settings-page-' + key); - if ( el ) - el.classList.add('active'); - - FFZ.menu_pages.settings['render_' + key].bind(this)(view, page); - - if ( key === 'advanced' ) - this.settings.set('advanced_settings', true); - else if ( key === 'basic' ) - this.settings.set('advanced_settings', false); - }, - - render_save: function(view, container) { - var backup_head = document.createElement('div'), - restore_head = document.createElement('div'), - backup_cont = document.createElement('div'), - restore_cont = document.createElement('div'), - - backup_para = document.createElement('p'), - backup_link = document.createElement('a'), - backup_help = document.createElement('span'), - - restore_para = document.createElement('p'), - restore_input = document.createElement('input'), - restore_link = document.createElement('a'), - restore_help = document.createElement('span'), - f = this; - - - backup_cont.className = 'chat-menu-content'; - backup_head.className = 'heading'; - backup_head.innerHTML = 'Backup Settings'; - backup_cont.appendChild(backup_head); - - backup_para.className = 'clearfix option'; - - backup_link.href = '#'; - backup_link.innerHTML = 'Save to File'; - backup_link.addEventListener('click', this.save_settings_file.bind(this)); - - backup_help.className = 'help'; - backup_help.innerHTML = 'This generates a JSON file containing all of your settings and prompts you to save it.'; - - backup_para.appendChild(backup_link); - backup_para.appendChild(backup_help); - backup_cont.appendChild(backup_para); - - restore_cont.className = 'chat-menu-content'; - restore_head.className = 'heading'; - restore_head.innerHTML = 'Restore Settings'; - restore_cont.appendChild(restore_head); - - restore_para.className = 'clearfix option'; - - restore_input.type = 'file'; - restore_input.addEventListener('change', function() { f.load_settings_file(this.files[0]); }) - - restore_link.href = '#'; - restore_link.innerHTML = 'Restore from File'; - restore_link.addEventListener('click', function(e) { e.preventDefault(); restore_input.click(); }); - - restore_help.className = 'help'; - restore_help.innerHTML = 'This loads settings from a previously generated JSON file.'; - - restore_para.appendChild(restore_link); - restore_para.appendChild(restore_help); - restore_cont.appendChild(restore_para); - - container.appendChild(backup_cont); - container.appendChild(restore_cont); - }, - - render_basic: function(view, container) { - var settings = {}, - categories = [], - is_android = navigator.userAgent.indexOf('Android') !== -1; - - for(var key in FFZ.basic_settings) { - if ( ! FFZ.basic_settings.hasOwnProperty(key) ) - continue; - - var info = FFZ.basic_settings[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 ( is_android && info.no_mobile ) - 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 === "debugging" ) - a = "zzz" + a; - - if ( b === "debugging" ) - b = "zzz" + b; - - if ( a < b ) return -1; - else if ( a > b ) return 1; - return 0; - }); - - var f = this, - current_page = this._ffz_basic_settings_page || categories[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'; // collapsable'; - - menu.setAttribute('data-category', category); - //menu.classList.toggle('collapsed', current_page !== category); - - heading.innerHTML = category; - menu.appendChild(heading); - - /*menu.addEventListener('click', function() { - if ( ! this.classList.contains('collapsed') ) - return; - - var t = this, - old_selection = container.querySelectorAll('.chat-menu-content:not(.collapsed)'); - for(var i=0; i < old_selection.length; i++) - old_selection[i].classList.add('collapsed'); - - f._ffz_basic_settings_page = t.getAttribute('data-category'); - t.classList.remove('collapsed'); - setTimeout(function(){t.scrollIntoViewIfNeeded()}); - });*/ - - cset.sort(function(a,b) { - var a = a[1], - b = b[1], - - at = a.type === "boolean" ? 1 : 2, - bt = b.type === "boolean" ? 1 : 2, - - an = a.name.toLowerCase(), - bn = b.name.toLowerCase(); - - if ( at < bt ) return -1; - else if ( at > bt ) return 1; - - else if ( an < bn ) return -1; - else if ( an > bn ) return 1; - - return 0; - }); - - for(var i=0; i < cset.length; i++) { - var key = cset[i][0], - info = cset[i][1], - el = document.createElement('p'), - val = info.type !== "button" && typeof info.get === 'function' ? info.get.bind(this)() : this.settings.get(info.get); - - el.className = 'clearfix'; - - if ( this.has_bttv && info.no_bttv ) { - var label = document.createElement('span'), - help = document.createElement('span'); - label.className = 'switch-label'; - label.innerHTML = info.name; - - help = document.createElement('span'); - help.className = 'help'; - help.innerHTML = 'Disabled due to incompatibility with BetterTTV.'; - - el.classList.add('disabled'); - el.appendChild(label); - el.appendChild(help); - - } else { - if ( info.type == "boolean" ) { - var swit = document.createElement('a'), - label = document.createElement('span'); - - swit.className = 'switch'; - swit.classList.toggle('active', val); - swit.innerHTML = ""; - - label.className = 'switch-label'; - label.innerHTML = info.name; - - el.appendChild(swit); - el.appendChild(label); - - swit.addEventListener("click", toggle_basic_setting.bind(this, swit, key)); - - } else if ( info.type === "select" ) { - var select = document.createElement('select'), - label = document.createElement('span'); - - label.className = 'option-label'; - label.innerHTML = info.name; - - for(var ok in info.options) { - var op = document.createElement('option'); - op.value = JSON.stringify(ok); - if ( val === ok ) - op.setAttribute('selected', true); - op.innerHTML = info.options[ok]; - select.appendChild(op); - } - - select.addEventListener('change', option_basic_setting.bind(this, select, key)); - - el.appendChild(label); - el.appendChild(select); - - } else { - el.classList.add("option"); - var link = document.createElement('a'); - link.innerHTML = info.name; - link.href = "#"; - el.appendChild(link); - - link.addEventListener("click", info.method.bind(this)); - } - - if ( info.help ) { - var help = document.createElement('span'); - help.className = 'help'; - help.innerHTML = info.help; - el.appendChild(help); - } - } - - menu.appendChild(el); - } - - container.appendChild(menu); - } - }, - - render_advanced: function(view, container) { - var settings = {}, - categories = [], - is_android = navigator.userAgent.indexOf('Android') !== -1; - - for(var key in FFZ.settings_info) { - if ( ! FFZ.settings_info.hasOwnProperty(key) ) - continue; - - 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 ( is_android && info.no_mobile ) - 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 === "debugging" ) - a = "zzz" + a; - - if ( b === "debugging" ) - b = "zzz" + b; - - if ( a < b ) return -1; - else if ( a > b ) return 1; - return 0; - }); - - var f = this, - current_page = this._ffz_settings_page || categories[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 collapsable'; - - menu.setAttribute('data-category', category); - menu.classList.toggle('collapsed', current_page !== category); - - heading.innerHTML = category; - menu.appendChild(heading); - - menu.addEventListener('click', function() { - if ( ! this.classList.contains('collapsed') ) - return; - - var t = this, - old_selection = container.querySelectorAll('.chat-menu-content:not(.collapsed)'); - for(var i=0; i < old_selection.length; i++) - old_selection[i].classList.add('collapsed'); - - f._ffz_settings_page = t.getAttribute('data-category'); - t.classList.remove('collapsed'); - setTimeout(function(){t.scrollIntoViewIfNeeded()}); - }); - - cset.sort(function(a,b) { - var a = a[1], - b = b[1], - - at = a.type === "boolean" ? 1 : 2, - bt = b.type === "boolean" ? 1 : 2, - - an = a.name.toLowerCase(), - bn = b.name.toLowerCase(); - - if ( at < bt ) return -1; - else if ( at > bt ) return 1; - - else if ( an < bn ) return -1; - else if ( an > bn ) return 1; - - return 0; - }); - - 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); - - el.className = 'clearfix'; - - if ( this.has_bttv && info.no_bttv ) { - var label = document.createElement('span'), - help = document.createElement('span'); - label.className = 'switch-label'; - label.innerHTML = info.name; - - help = document.createElement('span'); - help.className = 'help'; - help.innerHTML = 'Disabled due to incompatibility with BetterTTV.'; - - el.classList.add('disabled'); - el.appendChild(label); - el.appendChild(help); - - } else { - if ( info.type == "boolean" ) { - var swit = document.createElement('a'), - label = document.createElement('span'); - - swit.className = 'switch'; - swit.classList.toggle('active', val); - swit.innerHTML = ""; - - label.className = 'switch-label'; - label.innerHTML = info.name; - - el.appendChild(swit); - el.appendChild(label); - - swit.addEventListener("click", toggle_setting.bind(this, swit, key)); - - } else if ( info.type === "select" ) { - var select = document.createElement('select'), - label = document.createElement('span'); - - label.className = 'option-label'; - label.innerHTML = info.name; - - for(var ok in info.options) { - var op = document.createElement('option'); - op.value = JSON.stringify(ok); - if ( val === ok ) - op.setAttribute('selected', true); - op.innerHTML = info.options[ok]; - select.appendChild(op); - } - - select.addEventListener('change', option_setting.bind(this, select, key)); - - el.appendChild(label); - el.appendChild(select); - - } else { - el.classList.add("option"); - var link = document.createElement('a'); - link.innerHTML = info.name; - link.href = "#"; - el.appendChild(link); - - link.addEventListener("click", info.method.bind(this)); - } - - if ( info.help ) { - var help = document.createElement('span'); - help.className = 'help'; - help.innerHTML = info.help; - el.appendChild(help); - } - } - - menu.appendChild(el); - } - - container.appendChild(menu); - } - }, - - name: "Settings", - icon: constants.GEAR, - sort_order: 99999, - wide: true, - sub_menu: true - }; - - -// -------------------- -// Tracking Updates -// -------------------- - -FFZ.prototype._setting_update = function(e) { - if ( ! e ) - e = window.event; - - if ( ! e.key || e.key.substr(0, 12) !== "ffz_setting_" ) - return; - - var ls_key = e.key, - key = ls_key.substr(12), - val = undefined, - info = FFZ.settings_info[key]; - - if ( ! info ) { - // Try iterating to find the key. - for(key in FFZ.settings_info) { - if ( ! FFZ.settings_info.hasOwnProperty(key) ) - continue; - - info = FFZ.settings_info[key]; - if ( info.storage_key == ls_key ) - break; - } - - // Not us. - if ( info.storage_key != ls_key ) - return; - } - - this.log("Updated Setting: " + key); - - try { - val = JSON.parse(e.newValue); - } catch(err) { - this.log('Error loading new value for "' + key + '": ' + err); - val = info.value || undefined; - } - - this.settings[key] = val; - if ( info.on_update ) - try { - info.on_update.bind(this)(val, false); - } catch(err) { - this.log('Error running updater for setting "' + key + '": ' + err); - } -} - - - -// -------------------- -// Settings Access -// -------------------- - -FFZ.prototype._setting_get = function(key) { - return this.settings[key]; -} - - -FFZ.prototype._setting_set = function(key, val) { - var info = FFZ.settings_info[key], - ls_key = info.storage_key || make_ls(key), - jval = JSON.stringify(val); - - this.settings[key] = val; - localStorage.setItem(ls_key, jval); - - this.log('Changed Setting "' + key + '" to: ' + jval); - - if ( info.on_update ) - try { - info.on_update.bind(this)(val, true); - } catch(err) { - this.log('Error running updater for setting "' + key + '": ' + err); - } -} - - -FFZ.prototype._setting_del = function(key) { - var info = FFZ.settings_info[key], - ls_key = info.storage_key || make_ls(key), - val = undefined; - - if ( localStorage.hasOwnProperty(ls_key) ) - localStorage.removeItem(ls_key); - - delete this.settings[key]; - - if ( info ) - val = this.settings[key] = info.hasOwnProperty("value") ? info.value : undefined; - - if ( info.on_update ) - try { - info.on_update.bind(this)(val, true); - } catch(err) { - this.log('Error running updater for setting "' + key + '": ' + err); - } -} -},{"./FileSaver":1,"./constants":5}],21:[function(require,module,exports){ -var FFZ = window.FrankerFaceZ; - -FFZ.prototype._ws_open = false; -FFZ.prototype._ws_delay = 0; -FFZ.prototype._ws_last_iframe = 0; - -FFZ.ws_commands = {}; -FFZ.ws_on_close = []; - - -// ---------------- -// Socket Creation -// ---------------- - -FFZ.prototype.ws_iframe = function() { - this._ws_last_iframe = Date.now(); - var ifr = document.createElement('iframe'), - f = this; - - ifr.src = 'http://catbag.frankerfacez.com'; - ifr.style.visibility = 'hidden'; - document.body.appendChild(ifr); - setTimeout(function() { - document.body.removeChild(ifr); - if ( ! f._ws_open ) - f.ws_create(); - }, 2000); -} - - -FFZ.prototype.ws_create = function() { - var f = this, ws; - - this._ws_last_req = 0; - this._ws_callbacks = {}; - this._ws_pending = this._ws_pending || []; - - try { - ws = this._ws_sock = new WebSocket("ws://catbag.frankerfacez.com/"); - } catch(err) { - this._ws_exists = false; - return this.log("Error Creating WebSocket: " + err); - } - - this._ws_exists = true; - - ws.onopen = function(e) { - f._ws_open = true; - f._ws_delay = 0; - f._ws_last_iframe = Date.now(); - f.log("Socket connected."); - - // Check for incognito. We don't want to do a hello in incognito mode. - var fs = window.RequestFileSystem || window.webkitRequestFileSystem; - if (!fs) - // Assume not. - f.ws_send("hello", ["ffz_" + FFZ.version_info, localStorage.ffzClientId], f._ws_on_hello.bind(f)); - - else - fs(window.TEMPORARY, 100, - f.ws_send.bind(f, "hello", ["ffz_" + FFZ.version_info, localStorage.ffzClientId], f._ws_on_hello.bind(f)), - f.log.bind(f, "Operating in Incognito Mode.")); - - - var user = f.get_user(); - if ( user ) - f.ws_send("setuser", user.login); - - // Join the right channel if we're in the dashboard. - if ( f.is_dashboard ) { - var match = location.pathname.match(/\/([^\/]+)/); - if ( match ) { - f.ws_send("sub", match[1]); - f.ws_send("sub_channel", match[1]); - } - } - - // Send the current rooms. - for(var room_id in f.rooms) { - if ( ! f.rooms.hasOwnProperty(room_id) || ! f.rooms[room_id] ) - continue; - - f.ws_send("sub", room_id); - - if ( f.rooms[room_id].needs_history ) { - f.rooms[room_id].needs_history = false; - if ( ! f.has_bttv && f.settings.chat_history ) - f.ws_send("chat_history", [room_id,25], f._load_history.bind(f, room_id)); - } - } - - // Send the channel(s). - if ( f._cindex ) { - var channel_id = f._cindex.get('controller.id'), - hosted_id = f._cindex.get('controller.hostModeTarget.id'); - - if ( channel_id ) - f.ws_send("sub_channel", channel_id); - - if ( hosted_id ) - f.ws_send("sub_channel", hosted_id); - } - - // Send any pending commands. - var pending = f._ws_pending; - f._ws_pending = []; - - for(var i=0; i < pending.length; i++) { - var d = pending[i]; - f.ws_send(d[0], d[1], d[2]); - } - } - - ws.onclose = function(e) { - f.log("Socket closed. (Code: " + e.code + ", Reason: " + e.reason + ")"); - f._ws_open = false; - - // When the connection closes, run our callbacks. - for(var i=0; i < FFZ.ws_on_close.length; i++) { - try { - FFZ.ws_on_close[i].bind(f)(); - } catch(err) { - f.log("Error on Socket Close Callback: " + err); - } - } - - if ( f._ws_delay > 10000 ) { - var ua = navigator.userAgent.toLowerCase(); - if ( Date.now() - f._ws_last_iframe > 1800000 && !(ua.indexOf('chrome') === -1 && ua.indexOf('safari') !== -1) ) - return f.ws_iframe(); - } - - // We never ever want to not have a socket. - if ( f._ws_delay < 60000 ) - f._ws_delay += (Math.floor(Math.random()*10) + 5) * 1000; - else - // Randomize delay. - f._ws_delay = (Math.floor(Math.random()*60)+30)*1000; - - setTimeout(f.ws_create.bind(f), f._ws_delay); - } - - ws.onmessage = function(e) { - // Messages are formatted as REQUEST_ID SUCCESS/FUNCTION_NAME[ JSON_DATA] - var cmd, data, ind = e.data.indexOf(" "), - msg = e.data.substr(ind + 1), - request = parseInt(e.data.slice(0, ind)); - - ind = msg.indexOf(" "); - if ( ind === -1 ) - ind = msg.length; - - cmd = msg.slice(0, ind); - msg = msg.substr(ind + 1); - if ( msg ) - data = JSON.parse(msg); - - if ( request === -1 ) { - // It's a command from the server. - var command = FFZ.ws_commands[cmd]; - if ( command ) - command.bind(f)(data); - else - f.log("Invalid command: " + cmd, data, false, true); - - } else { - var success = cmd === 'True', - has_callback = typeof f._ws_callbacks[request] === "function"; - - if ( ! has_callback ) - f.log("Socket Reply to " + request + " - " + (success ? "SUCCESS" : "FAIL"), data, false, true); - - else { - try { - f._ws_callbacks[request](success, data); - } catch(err) { - f.error("Callback for " + request + ": " + err); - } - - f._ws_callbacks[request] = undefined; - } - } - } -} - - -FFZ.prototype.ws_send = function(func, data, callback, can_wait) { - if ( ! this._ws_open ) { - if ( can_wait ) { - var pending = this._ws_pending = this._ws_pending || []; - pending.push([func, data, callback]); - return true; - } else - return false; - } - - var request = ++this._ws_last_req; - data = data !== undefined ? " " + JSON.stringify(data) : ""; - - if ( callback ) - this._ws_callbacks[request] = callback; - - try { - this._ws_sock.send(request + " " + func + data); - } catch(err) { - this.log("Socket Send Error: " + err); - return false; - } - - return request; -} - - -// ---------------- -// HELLO Response -// ---------------- - -FFZ.prototype._ws_on_hello = function(success, data) { - if ( ! success ) - return this.log("Error Saying Hello: " + data); - - localStorage.ffzClientId = data; - this.log("Client ID: " + data); - - var survey = {}, - set = survey['settings'] = {}; - - for(var key in FFZ.settings_info) - set[key] = this.settings[key]; - - set["keywords"] = this.settings.keywords.length; - set["banned_words"] = this.settings.banned_words.length; - - - // Detect BTTV. - survey['bttv'] = this.has_bttv || !!document.head.querySelector('script[src*="betterttv"]'); - - - // Client Info - survey['user-agent'] = navigator.userAgent; - survey['screen'] = [screen.width, screen.height]; - survey['language'] = navigator.language; - survey['platform'] = navigator.platform; - - this.ws_send("survey", [survey]); -} - - - -// ---------------- -// Authorization -// ---------------- - -FFZ.ws_commands.do_authorize = function(data) { - // Try finding a channel we can send on. - var conn; - for(var room_id in this.rooms) { - if ( ! this.rooms.hasOwnProperty(room_id) ) - continue; - - var r = this.rooms[room_id]; - if ( r && r.room && !r.room.get('roomProperties.eventchat') && !r.room.get('isGroupRoom') && r.room.tmiRoom ) { - var c = r.room.tmiRoom._getConnection(); - if ( c.isConnected ) { - conn = c; - break; - } - } - } - - if ( conn ) - conn._send("PRIVMSG #frankerfacezauthorizer :AUTH " + data); - else - // Try again shortly. - setTimeout(FFZ.ws_commands.do_authorize.bind(this, data), 5000); -} -},{}],22:[function(require,module,exports){ -var FFZ = window.FrankerFaceZ, - utils = require("./utils"), - constants = require("./constants"), - TWITCH_BASE = "http://static-cdn.jtvnw.net/emoticons/v1/", - helpers, - - SRCSETS = {}; - build_srcset = function(id) { - if ( SRCSETS[id] ) - return SRCSETS[id]; - var out = SRCSETS[id] = TWITCH_BASE + id + "/1.0 1x, " + TWITCH_BASE + id + "/2.0 2x, " + TWITCH_BASE + id + "/3.0 4x"; - return out; - }, - - - data_to_tooltip = function(data) { - var set = data.set, - set_type = data.set_type, - owner = data.owner; - - if ( set_type === undefined ) - set_type = "Channel"; - - if ( ! set ) - return data.code; - - else if ( set === "--global--" ) { - set = "Twitch Global"; - set_type = null; - - } else if ( set == "--twitch-turbo--" || set == "turbo" || set == "--turbo-faces--" ) { - set = "Twitch Turbo"; - set_type = null; - } - - return "Emoticon: " + data.code + "\n" + (set_type ? set_type + ": " : "") + set + (owner ? "\nBy: " + owner.display_name : ""); - }, - - build_tooltip = function(id) { - var emote_data = this._twitch_emotes[id], - set = emote_data ? emote_data.set : null; - - if ( ! emote_data ) - return "???"; - - if ( typeof emote_data == "string" ) - return emote_data; - - if ( emote_data.tooltip ) - return emote_data.tooltip; - - return emote_data.tooltip = data_to_tooltip(emote_data); - }, - - load_emote_data = function(id, code, success, data) { - if ( ! success ) - return code; - - if ( code ) - data.code = code; - - this._twitch_emotes[id] = data; - var tooltip = build_tooltip.bind(this)(id); - - var images = document.querySelectorAll('img[data-emote="' + id + '"]'); - for(var x=0; x < images.length; x++) - images[x].title = tooltip; - - return tooltip; - }, - - - reg_escape = function(str) { - return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); - }, - - LINK = /(?:https?:\/\/)?(?:[-a-zA-Z0-9@:%_\+~#=]+\.)+[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#!?&//=]*)/g, - - SEPARATORS = "[\\s`~<>!-#%-\\x2A,-/:;\\x3F@\\x5B-\\x5D_\\x7B}\\u00A1\\u00A7\\u00AB\\u00B6\\u00B7\\u00BB\\u00BF\\u037E\\u0387\\u055A-\\u055F\\u0589\\u058A\\u05BE\\u05C0\\u05C3\\u05C6\\u05F3\\u05F4\\u0609\\u060A\\u060C\\u060D\\u061B\\u061E\\u061F\\u066A-\\u066D\\u06D4\\u0700-\\u070D\\u07F7-\\u07F9\\u0830-\\u083E\\u085E\\u0964\\u0965\\u0970\\u0AF0\\u0DF4\\u0E4F\\u0E5A\\u0E5B\\u0F04-\\u0F12\\u0F14\\u0F3A-\\u0F3D\\u0F85\\u0FD0-\\u0FD4\\u0FD9\\u0FDA\\u104A-\\u104F\\u10FB\\u1360-\\u1368\\u1400\\u166D\\u166E\\u169B\\u169C\\u16EB-\\u16ED\\u1735\\u1736\\u17D4-\\u17D6\\u17D8-\\u17DA\\u1800-\\u180A\\u1944\\u1945\\u1A1E\\u1A1F\\u1AA0-\\u1AA6\\u1AA8-\\u1AAD\\u1B5A-\\u1B60\\u1BFC-\\u1BFF\\u1C3B-\\u1C3F\\u1C7E\\u1C7F\\u1CC0-\\u1CC7\\u1CD3\\u2010-\\u2027\\u2030-\\u2043\\u2045-\\u2051\\u2053-\\u205E\\u207D\\u207E\\u208D\\u208E\\u2329\\u232A\\u2768-\\u2775\\u27C5\\u27C6\\u27E6-\\u27EF\\u2983-\\u2998\\u29D8-\\u29DB\\u29FC\\u29FD\\u2CF9-\\u2CFC\\u2CFE\\u2CFF\\u2D70\\u2E00-\\u2E2E\\u2E30-\\u2E3B\\u3001-\\u3003\\u3008-\\u3011\\u3014-\\u301F\\u3030\\u303D\\u30A0\\u30FB\\uA4FE\\uA4FF\\uA60D-\\uA60F\\uA673\\uA67E\\uA6F2-\\uA6F7\\uA874-\\uA877\\uA8CE\\uA8CF\\uA8F8-\\uA8FA\\uA92E\\uA92F\\uA95F\\uA9C1-\\uA9CD\\uA9DE\\uA9DF\\uAA5C-\\uAA5F\\uAADE\\uAADF\\uAAF0\\uAAF1\\uABEB\\uFD3E\\uFD3F\\uFE10-\\uFE19\\uFE30-\\uFE52\\uFE54-\\uFE61\\uFE63\\uFE68\\uFE6A\\uFE6B\\uFF01-\\uFF03\\uFF05-\\uFF0A\\uFF0C-\\uFF0F\\uFF1A\\uFF1B\\uFF1F\\uFF20\\uFF3B-\\uFF3D\\uFF3F\\uFF5B\\uFF5D\\uFF5F-\\uFF65]", - SPLITTER = new RegExp(SEPARATORS + "*," + SEPARATORS + "*"), - - - LINK_SPLIT = /^(?:(https?):\/\/)?(?:(.*?)@)?([^\/:]+)(?::(\d+))?(.*?)(?:\?(.*?))?(?:\#(.*?))?$/, - YOUTUBE_CHECK = /^(?:https?:\/\/)?(?:m\.|www\.)?youtu(?:be\.com|\.be)\/(?:v\/|watch\/|.*?(?:embed|watch).*?v=)?([a-zA-Z0-9\-_]+)$/, - IMGUR_PATH = /^\/(?:gallery\/)?[A-Za-z0-9]+(?:\.(?:png|jpg|jpeg|gif|gifv|bmp))?$/, - IMAGE_EXT = /\.(?:png|jpg|jpeg|gif|bmp)$/i, - IMAGE_DOMAINS = [], - - is_image = function(href, any_domain) { - var match = href.match(LINK_SPLIT); - if ( ! match ) - return; - - var domain = match[3].toLowerCase(), port = match[4], - path = match[5]; - - // Don't allow non-standard ports. - if ( port && port !== '80' && port !== '443' ) - return false; - - // imgur-specific checks. - if ( domain === 'i.imgur.com' || domain === 'imgur.com' || domain === 'www.imgur.com' || domain === 'm.imgur.com' ) - return IMGUR_PATH.test(path); - - return any_domain ? IMAGE_EXT.test(path) : IMAGE_DOMAINS.indexOf(domain) !== -1; - } - - image_iframe = function(href, extra_class) { - return ''; - }, - - - build_link_tooltip = function(href) { - var link_data = this._link_data[href], - tooltip; - - if ( ! link_data ) - return ""; - - if ( link_data.tooltip ) - return link_data.tooltip; - - if ( link_data.type == "youtube" ) { - tooltip = this.settings.link_image_hover ? image_iframe(link_data.full || href, 'ffz-yt-thumb') : ''; - tooltip += "YouTube: " + utils.sanitize(link_data.title) + "
"; - tooltip += "Channel: " + utils.sanitize(link_data.channel) + " | " + utils.time_to_string(link_data.duration) + "
"; - tooltip += utils.number_commas(link_data.views||0) + " Views | 👍 " + utils.number_commas(link_data.likes||0) + " 👎 " + utils.number_commas(link_data.dislikes||0); - - } else if ( link_data.type == "strawpoll" ) { - tooltip = "Strawpoll: " + utils.sanitize(link_data.title) + "
"; - for(var key in link_data.items) { - var votes = link_data.items[key], - percentage = Math.floor((votes / link_data.total) * 100); - tooltip += '"; - } - tooltip += "
' + utils.sanitize(key) + '' + utils.number_commas(votes) + "

Total: " + utils.number_commas(link_data.total); - var fetched = utils.parse_date(link_data.fetched); - if ( fetched ) { - var age = Math.floor((fetched.getTime() - Date.now()) / 1000); - if ( age > 60 ) - tooltip += "
Data was cached " + utils.time_to_string(age) + " ago."; - } - - - } else if ( link_data.type == "twitch" ) { - tooltip = "Twitch: " + utils.sanitize(link_data.display_name) + "
"; - var since = utils.parse_date(link_data.since); - if ( since ) - tooltip += "Member Since: " + utils.date_string(since) + "
"; - tooltip += "Views: " + utils.number_commas(link_data.views) + " | Followers: " + utils.number_commas(link_data.followers) + ""; - - - } else if ( link_data.type == "twitch_vod" ) { - tooltip = "Twitch " + (link_data.broadcast_type == "highlight" ? "Highlight" : "Broadcast") + ": " + utils.sanitize(link_data.title) + "
"; - tooltip += "By: " + utils.sanitize(link_data.display_name) + (link_data.game ? " | Playing: " + utils.sanitize(link_data.game) : " | Not Playing") + "
"; - tooltip += "Views: " + utils.number_commas(link_data.views) + " | " + utils.time_to_string(link_data.length); - - - } else if ( link_data.type == "twitter" ) { - tooltip = "Tweet By: " + utils.sanitize(link_data.user) + "
"; - tooltip += utils.sanitize(link_data.tweet); - - - } else if ( link_data.type == "reputation" ) { - tooltip = (this.settings.link_image_hover && is_image(link_data.full || href, this.settings.image_hover_all_domains)) ? image_iframe(link_data.full || href) : ''; - tooltip += '' + utils.sanitize(link_data.full.toLowerCase()) + ''; - if ( link_data.trust < 50 || link_data.safety < 50 || (link_data.tags && link_data.tags.length > 0) ) { - tooltip += "
"; - var had_extra = false; - if ( link_data.trust < 50 || link_data.safety < 50 ) { - link_data.unsafe = true; - tooltip += "Potentially Unsafe Link
"; - tooltip += "Trust: " + link_data.trust + "% | Child Safety: " + link_data.safety + "%"; - had_extra = true; - } - - if ( link_data.tags && link_data.tags.length > 0 ) - tooltip += (had_extra ? "
" : "") + "Tags: " + link_data.tags.join(", "); - - tooltip += "
Data Source: WOT"; - } - - - } else if ( link_data.full ) { - tooltip = (this.settings.link_image_hover && is_image(link_data.full || href, this.settings.image_hover_all_domains)) ? image_iframe(link_data.full || href) : ''; - tooltip += '' + utils.sanitize(link_data.full.toLowerCase()) + ''; - } - - if ( ! tooltip ) - tooltip = '' + utils.sanitize(href.toLowerCase()) + ''; - - link_data.tooltip = tooltip; - return tooltip; - }, - - load_link_data = function(href, success, data) { - if ( ! success ) - return; - - this._link_data[href] = data; - data.unsafe = false; - - var tooltip = build_link_tooltip.bind(this)(href), links, - no_trail = href.charAt(href.length-1) == "/" ? href.substr(0, href.length-1) : null; - - if ( no_trail ) - links = document.querySelectorAll('span.message a[href="' + href + '"], span.message a[href="' + no_trail + '"], span.message a[data-url="' + href + '"], span.message a[data-url="' + no_trail + '"]'); - else - links = document.querySelectorAll('span.message a[href="' + href + '"], span.message a[data-url="' + href + '"]'); - - if ( ! this.settings.link_info ) - return; - - for(var x=0; x < links.length; x++) { - if ( data.unsafe ) - links[x].classList.add('unsafe-link'); - - if ( ! links[x].classList.contains('deleted-link') ) - links[x].title = tooltip; - } - }; - - -FFZ.SRC_IDS = {}, -FFZ.src_to_id = function(src) { - if ( FFZ.SRC_IDS.hasOwnProperty(src) ) - return FFZ.SRC_IDS[src]; - - var match = /\/emoticons\/v1\/(\d+)\/1\.0/.exec(src), - id = match ? parseInt(match[1]) : null; - - if ( id === NaN ) - id = null; - - FFZ.SRC_IDS[src] = id; - return id; -}; - - -// --------------------- -// Settings -// --------------------- - -var ts = new Date(0).toLocaleTimeString().toUpperCase(); - -FFZ.settings_info.twenty_four_timestamps = { - type: "boolean", - value: ts.lastIndexOf('PM') === -1 && ts.lastIndexOf('AM') === -1, - - category: "Chat Appearance", - no_bttv: true, - - name: "24hr Timestamps", - help: "Display timestamps in chat in the 24 hour format rather than 12 hour." - }; - - -FFZ.settings_info.show_deleted_links = { - type: "boolean", - value: false, - - category: "Chat Moderation", - no_bttv: true, - - name: "Show Deleted Links", - help: "Do not delete links based on room settings or link length." - }; - - -// --------------------- -// Setup -// --------------------- - -FFZ.prototype.setup_tokenization = function() { - // Tooltip Data - this._twitch_emotes = {}; - this._twitch_emote_to_set = {}; - this._twitch_set_to_channel = {}; - this._link_data = {}; - - this.load_twitch_emote_data(); - - helpers = window.require && window.require("ember-twitch-chat/helpers/chat-line-helpers"); - if ( ! helpers ) - return this.log("Unable to get chat helper functions."); - - this.log("Hooking Ember chat line helpers."); - - var f = this; - - // Timestamp Display - helpers.getTime = function(e) { - if ( e === undefined || e === null ) - return '?:??'; - - var hours = e.getHours(), - minutes = e.getMinutes(); - - if ( hours > 12 && ! f.settings.twenty_four_timestamps ) - hours -= 12; - else if ( hours === 0 && ! f.settings.twenty_four_timestamps ) - hours = 12; - - return hours + ':' + (minutes < 10 ? '0' : '') + minutes; - }; - - - // Linkify Messages - helpers.linkifyMessage = function(tokens, delete_links) { - var show_deleted = f.settings.show_deleted_links; - - return _.chain(tokens).map(function(token) { - if ( ! _.isString(token) ) - return token; - - var matches = token.match(LINK); - if ( ! matches || ! matches.length ) - return [token]; - - return _.zip( - token.split(LINK), - _.map(matches, function(e) { - var long = e.length > 255; - if ( ! show_deleted && (delete_links || long) ) - return {isLink: true, isDeleted: true, isLong: long, href: e}; - //return {mentionedUser: '<' + (e.length > 255 ? 'long link' : 'deleted link') + '>', own: true} - return {isLink: true, href: e}; - }) - ); - }).flatten().compact().value(); - }; -} - - -// --------------------- -// Twitch Emote Data -// --------------------- - -FFZ.prototype.load_twitch_emote_data = function(tries) { - jQuery.ajax(constants.SERVER + "script/twitch_emotes.json", {cache: false, context: this}) - .done(function(data) { - for(var set_id in data) { - var set = data[set_id]; - if ( ! set ) - continue; - - this._twitch_set_to_channel[set_id] = set.name; - for(var i=0, l = set.emotes.length; i < l; i++) - this._twitch_emote_to_set[set.emotes[i]] = set_id; - } - - this._twitch_set_to_channel[0] = "--global--"; - this._twitch_set_to_channel[33] = "--turbo-faces--"; - this._twitch_set_to_channel[42] = "--turbo-faces--"; - - }).fail(function(data) { - if ( data.status === 404 ) - return; - - tries = (tries || 0) + 1; - if ( tries < 10 ) - setTimeout(this.load_twitch_emote_data.bind(this, tries), 1000); - }); -} - - -// --------------------- -// Tokenization -// --------------------- - -FFZ.prototype.tokenize_chat_line = function(msgObject, prevent_notification, delete_links) { - if ( msgObject.cachedTokens ) - return msgObject.cachedTokens; - - var msg = msgObject.message, - user = this.get_user(), - room_id = msgObject.room, - from_me = user && msgObject.from === user.login, - emotes = msgObject.tags && msgObject.tags.emotes, - - tokens = [msg]; - - // Standard tokenization - if ( helpers && helpers.linkifyMessage ) { - var labels = msg.labels || [], - mod_or_higher = labels.indexOf("owner") !== -1 || - labels.indexOf("staff") !== -1 || - labels.indexOf("admin") !== -1 || - labels.indexOf("global_mod") !== -1 || - labels.indexOf("mod") !== -1 || - msg.style === 'admin'; - - tokens = helpers.linkifyMessage(tokens, delete_links && !mod_or_higher); - } - - - if ( user && user.login && helpers && helpers.mentionizeMessage ) - tokens = helpers.mentionizeMessage(tokens, user.login, from_me); - - if ( helpers && helpers.emoticonizeMessage ) - tokens = helpers.emoticonizeMessage(tokens, emotes); - - if ( this.settings.replace_bad_emotes ) - tokens = this.tokenize_replace_emotes(tokens); - - // FrankerFaceZ Extras - tokens = this._remove_banned(tokens); - tokens = this.tokenize_emotes(msgObject.from, room_id, tokens, from_me); - - if ( this.settings.parse_emoji ) - tokens = this.tokenize_emoji(tokens); - - // Capitalization - var display = msgObject.tags && msgObject.tags['display-name']; - if ( display && display.length ) - FFZ.capitalization[msgObject.from] = [display.trim(), Date.now()]; - - - // Mentions! - if ( ! from_me ) { - tokens = this.tokenize_mentions(tokens); - - for(var i=0; i < tokens.length; i++) { - var token = tokens[i]; - if ( msgObject.style !== 'whisper' && (_.isString(token) || ! token.mentionedUser || token.own) ) - continue; - - // We have a mention! - msgObject.ffz_has_mention = true; - - // If we have chat tabs, update the status. - if ( room_id && ! this.has_bttv && this.settings.group_tabs && this._chatv && this._chatv._ffz_tabs ) { - var el = this._chatv._ffz_tabs.querySelector('.ffz-chat-tab[data-room="' + room_id + '"]'); - if ( el && ! el.classList.contains('active') ) - el.classList.add('tab-mentioned'); - } - - // Display notifications if that setting is enabled. Also make sure - // that we have a chat view because showing a notification when we - // can't actually go to it is a bad thing. - if ( this._chatv && this.settings.highlight_notifications && ! this.embed_in_dash && ! document.hasFocus() && ! prevent_notification ) { - var room = this.rooms[room_id] && this.rooms[room_id].room, - room_name; - - // Make sure we have UI for this channel. - if ( (this.settings.group_tabs && (this.settings.pinned_rooms.indexOf(room_id) !== -1 || this._chatv._ffz_host )) || room.get('isGroupRoom') || room === this._chatv.get('controller.currentChannelRoom') ) { - if ( room && room.get('isGroupRoom') ) - room_name = room.get('tmiRoom.displayName'); - else - room_name = FFZ.get_capitalization(room_id); - - display = display || Twitch.display.capitalize(msgObject.from); - - if ( msgObject.style === 'action' ) - msg = '* ' + display + ' ' + msg; - else - msg = display + ': ' + msg; - - var f = this; - if ( msgObject.style === 'whisper' ) - this.show_notification( - msg, - "Twitch Chat Whisper", - "ffz_whisper_notice", - (this.settings.notification_timeout*1000), - function() { - window.focus(); - } - ); - else - this.show_notification( - msg, - "Twitch Chat Mention in " + room_name, - room_id, - (this.settings.notification_timeout*1000), - function() { - window.focus(); - var cont = App.__container__.lookup('controller:chat'); - room && cont && cont.focusRoom(room); - } - ); - } - } - - break; - } - } - - msgObject.cachedTokens = tokens; - return tokens; -} - - -FFZ.prototype.tokenize_line = function(user, room, message, no_emotes, no_emoji) { - if ( typeof message === "string" ) - message = [message]; - - if ( helpers && helpers.linkifyMessage ) - message = helpers.linkifyMessage(message); - - if ( helpers && helpers.mentionizeMessage ) { - var u = this.get_user(); - if ( u && u.login ) - message = helpers.mentionizeMessage(message, u.login, user === u.login); - } - - if ( ! no_emotes ) { - message = this.tokenize_emotes(user, room, message); - if ( this.settings.replace_bad_emotes ) - message = this.tokenize_replace_emotes(message); - } - - if ( this.settings.parse_emoji && ! no_emoji ) - message = this.tokenize_emoji(message); - - return message; -} - - -FFZ.prototype.render_tokens = function(tokens, render_links) { - var f = this; - return _.map(tokens, function(token) { - if ( token.emoticonSrc ) { - var tooltip, srcset, extra; - if ( token.ffzEmote ) { - var emote_set = f.emote_sets && f.emote_sets[token.ffzEmoteSet], - emote = emote_set && emote_set.emoticons && emote_set.emoticons[token.ffzEmote]; - - tooltip = emote ? utils.sanitize(f._emote_tooltip(emote)) : token.altText; - srcset = emote ? emote.srcSet : token.srcSet; - extra = ' data-ffz-emote="' + emote.id + '"'; - - } else if ( token.ffzEmoji ) { - var eid = token.ffzEmoji, - emoji = f.emoji_data && f.emoji_data[eid]; - - tooltip = emoji ? "Emoji: " + token.altText + "\nName: :" + emoji.short_name + ":" : token.altText; - srcset = emoji ? emoji.srcSet : token.srcSet; - extra = ' data-ffz-emoji="' + eid + '"'; - - } else { - var id = token.replacedId || FFZ.src_to_id(token.emoticonSrc), - data = id && f._twitch_emotes && f._twitch_emotes[id]; - - if ( data ) - tooltip = data.tooltip ? data.tooltip : token.altText; - else { - try { - var set_id = f._twitch_emote_to_set[id]; - if ( set_id ) { - tooltip = load_emote_data.bind(f)(id, token.altText, true, { - code: token.altText, - id: id, - set: f._twitch_set_to_channel[set_id], - set_id: set_id - }); - } else { - tooltip = f._twitch_emotes[id] = token.altText; - f.ws_send("twitch_emote", id, load_emote_data.bind(f, id, token.altText)); - } - } catch(err) { - f.error("Error Generating Emote Tooltip: " + err); - } - } - - extra = ' data-emote="' + id + '"'; - - if ( ! constants.EMOTE_REPLACEMENTS[id] ) - srcset = build_srcset(id); - } - - return ''; - } - - if ( token.isLink ) { - var text = token.title || (token.isLong && '') || (token.isDeleted && '') || token.href; - - if ( ! render_links && render_links !== undefined ) - return utils.sanitize(text); - - var href = token.href, - tooltip, cls = '', - - ind_at = href.indexOf("@"), - ind_sl = href.indexOf("/"); - - if ( ind_at !== -1 && (ind_sl === -1 || ind_at < ind_sl) ) { - // E-Mail Link - cls = 'email-link'; - - if ( f.settings.link_info ) { - cls += ' tooltip'; - tooltip = 'E-Mail ' + href; - } - - href = 'mailto:' + href; - - } else { - // Web Link - if ( ! href.match(/^https?:\/\//) ) - href = 'http://' + href; - - if ( f.settings.link_info ) { - cls = 'html-tooltip'; - - var data = f._link_data && f._link_data[href]; - if ( data ) { - tooltip = data.tooltip; - if ( data.unsafe ) - cls += ' unsafe-link'; - - } else { - f._link_data = f._link_data || {}; - f._link_data[href] = true; - f.ws_send("get_link", href, load_link_data.bind(f, href)); - if ( f.settings.link_image_hover && is_image(href, f.settings.image_hover_all_domains) ) - tooltip = image_iframe(href); - } - - } else if ( f.settings.link_image_hover ) { - cls = 'html-tooltip'; - if ( is_image(href, f.settings.image_hover_all_domains) ) - tooltip = image_iframe(href); - } - } - - - // Deleted Links - var actual_href = href; - if ( token.isDeleted ) { - cls = 'deleted-link ' + cls; - tooltip = utils.sanitize(token.censoredHref || token.href); - href = '#'; - } - - return '' + utils.sanitize(text) + ''; - } - - if ( token.mentionedUser ) - return '' + utils.sanitize(token.mentionedUser) + ""; - - if ( token.deletedLink ) - return utils.sanitize(token.text); - - return utils.sanitize(token); - }).join(""); -} - - -// --------------------- -// Emoticon Processing -// --------------------- - -FFZ.prototype.tokenize_replace_emotes = function(tokens) { - // Replace bad Twitch emoticons with custom emoticons. - var f = this; - - if ( _.isString(tokens) ) - tokens = [tokens]; - - for(var i=0; i < tokens.length; i++) { - var token = tokens[i]; - if ( ! token || ! token.emoticonSrc || token.ffzEmote ) - continue; - - // Check for a few specific emoticon IDs. - var emote_id = FFZ.src_to_id(token.emoticonSrc); - if ( constants.EMOTE_REPLACEMENTS.hasOwnProperty(emote_id) ) { - token.replacedId = emote_id; - token.emoticonSrc = constants.EMOTE_REPLACEMENT_BASE + constants.EMOTE_REPLACEMENTS[emote_id]; - } - } - - return tokens; -} - - -FFZ.prototype.tokenize_title_emotes = function(tokens) { - var f = this, - Channel = App.__container__.lookup('controller:channel'), - possible = Channel && Channel.get('product.emoticons'), - emotes = []; - - if ( _.isString(tokens) ) - tokens = [tokens]; - - // Build a list of emotes that match. - _.each(_.union(f.__twitch_global_emotes||[], possible), function(emote) { - if ( ! emote || emote.state === "inactive" ) - return; - - var r = new RegExp("\\b" + emote.regex + "\\b"); - - _.any(tokens, function(token) { - return _.isString(token) && token.match(r); - }) && emotes.push(emote); - }); - - // Include Global Emotes~! - if ( f.__twitch_global_emotes === undefined || f.__twitch_global_emotes === null ) { - f.__twitch_global_emotes = false; - Twitch.api.get("chat/emoticon_images", {emotesets:"0,42"}).done(function(data) { - if ( ! data || ! data.emoticon_sets || ! data.emoticon_sets[0] ) { - f.__twitch_global_emotes = []; - return; - } - - var emotes = f.__twitch_global_emotes = []; - data = data.emoticon_sets[0]; - for(var i=0; i < data.length; i++) { - var em = data[i]; - emotes.push({regex: em.code, url: TWITCH_BASE + em.id + "/1.0"}); - } - - if ( f._cindex ) - f._cindex.ffzFixTitle(); - }).fail(function() { - setTimeout(function(){f.__twitch_global_emotes = null;},5000); - });; - } - - if ( ! emotes.length ) - return tokens; - - if ( typeof tokens === "string" ) - tokens = [tokens]; - - _.each(emotes, function(emote) { - var eo = {isEmoticon:true, srcSet: emote.url + ' 1x', emoticonSrc: emote.url, altText: emote.regex}; - var r = new RegExp("\\b" + emote.regex + "\\b"); - - tokens = _.compact(_.flatten(_.map(tokens, function(token) { - if ( _.isObject(token) ) - return token; - - var tbits = token.split(r), bits = []; - tbits.forEach(function(val, ind) { - bits.push(val); - if ( ind !== tbits.length - 1 ) - bits.push(eo); - }); - return bits; - }))); - }); - - return tokens; -} - - -FFZ.prototype.tokenize_emotes = function(user, room, tokens, do_report) { - var f = this; - - // Get our sets. - var sets = this.getEmotes(user, room), - emotes = []; - - // Build a list of emotes that match. - _.each(sets, function(set_id) { - var set = f.emote_sets[set_id]; - if ( ! set ) - return; - - _.each(set.emoticons, function(emote) { - _.any(tokens, function(token) { - return _.isString(token) && token.match(emote.regex); - }) && emotes.push(emote); - }); - }); - - // Don't bother proceeding if we have no emotes. - if ( ! emotes.length ) - return tokens; - - // Now that we have all the matching tokens, do crazy stuff. - if ( typeof tokens === "string" ) - tokens = [tokens]; - - // This is weird stuff I basically copied from the old Twitch code. - // Here, for each emote, we split apart every text token and we - // put it back together with the matching bits of text replaced - // with an object telling Twitch's line template how to render the - // emoticon. - _.each(emotes, function(emote) { - var eo = { - srcSet: emote.srcSet, - emoticonSrc: emote.urls[1], - ffzEmote: emote.id, - ffzEmoteSet: emote.set_id, - altText: (emote.hidden ? "???" : emote.name) - }; - - tokens = _.compact(_.flatten(_.map(tokens, function(token) { - if ( _.isObject(token) ) - return token; - - var tbits = token.split(emote.regex), bits = []; - while(tbits.length) { - var bit = tbits.shift(); - if ( tbits.length ) { - bit += tbits.shift(); - if ( bit ) - bits.push(bit); - - tbits.shift(); - bits.push(eo); - - if ( do_report && room ) - f.add_usage(room, emote.id); - - } else - bits.push(bit); - } - return bits; - }))); - }); - - return tokens; -} - - -// --------------------- -// Emoji Processing -// --------------------- - -FFZ.prototype.tokenize_emoji = function(tokens) { - if ( typeof tokens === "string" ) - tokens = [tokens]; - - if ( ! this.emoji_data ) - return tokens; - - var f = this; - - return _.compact(_.flatten(_.map(tokens, function(token) { - if ( _.isObject(token) ) - return token; - - var tbits = token.split(constants.EMOJI_REGEX), bits = []; - while(tbits.length) { - // Deal with the unmatched string first. - var bit = tbits.shift(); - bit && bits.push(bit); - - if ( tbits.length ) { - // We have an emoji too, so let's handle that. - var match = tbits.shift(), - variant = tbits.shift(); - - if ( variant === '\uFE0E' ) { - // Text Variant - bits.push(match); - - } else { - // Find the right image~! - var eid = utils.emoji_to_codepoint(match, variant), - data = f.emoji_data[eid]; - - if ( data ) - bits.push(data.token); - else - bits.push(match + (variant || "")); - } - } - } - - return bits; - }))); -} - - -// --------------------- -// Mention Parsing -// --------------------- - -FFZ._regex_cache = {}; - -FFZ._get_regex = function(word) { - return FFZ._regex_cache[word] = FFZ._regex_cache[word] || RegExp("\\b" + reg_escape(word) + "\\b", "ig"); -} - -FFZ._words_to_regex = function(list) { - var regex = FFZ._regex_cache[list]; - if ( ! regex ) { - var reg = ""; - for(var i=0; i < list.length; i++) { - if ( ! list[i] ) - continue; - - reg += (reg ? "|" : "") + reg_escape(list[i]); - } - - regex = FFZ._regex_cache[list] = new RegExp("(^|.*?" + SEPARATORS + ")(" + reg + ")(?=$|" + SEPARATORS + ")", "ig"); - } - - return regex; -} - - -FFZ.prototype.tokenize_mentions = function(tokens) { - var mention_words = this.settings.keywords; - if ( ! mention_words || ! mention_words.length ) - return tokens; - - if ( typeof tokens === "string" ) - tokens = [tokens]; - - var regex = FFZ._words_to_regex(mention_words), - new_tokens = []; - - for(var i=0; i < tokens.length; i++) { - var token = tokens[i]; - if ( ! _.isString(token) ) { - new_tokens.push(token); - continue; - } - - if ( ! token.match(regex) ) { - new_tokens.push(token); - continue; - } - - token = token.replace(regex, function(all, prefix, match) { - new_tokens.push(prefix); - new_tokens.push({ - mentionedUser: match, - own: false - }); - - return ""; - }); - - if ( token ) - new_tokens.push(token); - } - - return new_tokens; -} - - -// --------------------- -// Handling Bad Stuff -// --------------------- - -FFZ.prototype._deleted_link_click = function(e) { - if ( ! this.classList.contains("deleted-link") ) - return true; - - // Get the URL - var href = this.getAttribute('data-url'), - link = href, - f = FrankerFaceZ.get(); - - // Delete Old Stuff - this.classList.remove('deleted-link'); - this.removeAttribute("data-url"); - this.removeAttribute("title"); - this.removeAttribute("original-title"); - - // Process URL - if ( href.indexOf("@") > -1 && (-1 === href.indexOf("/") || href.indexOf("@") < href.indexOf("/")) ) - href = "mailto:" + href; - else if ( ! href.match(/^https?:\/\//) ) - href = "http://" + href; - - // Set up the Link - this.href = href; - this.target = "_new"; - this.textContent = link; - - // Now, check for a tooltip. - var link_data = f._link_data[link]; - if ( link_data && typeof link_data != "boolean" ) { - this.title = link_data.tooltip; - if ( link_data.unsafe ) - this.classList.add('unsafe-link'); - } - - // Stop from Navigating - e.preventDefault(); -} -},{"./constants":5,"./utils":35}],23:[function(require,module,exports){ -var FFZ = window.FrankerFaceZ, - constants = require("../constants"); - - -// ------------------- -// About Page -// ------------------- - -FFZ.menu_pages.about_changelog = { - name: "Changelog", - visible: false, - wide: true, - - render: function(view, container) { - var heading = document.createElement('div'); - - heading.className = 'chat-menu-content center'; - heading.innerHTML = '

FrankerFaceZ

change log
'; - - jQuery.ajax(constants.SERVER + "script/changelog.html", {cache: false, context: this}) - .done(function(data) { - container.appendChild(heading); - container.innerHTML += data; - - }).fail(function(data) { - var content = document.createElement('div'); - content.className = 'chat-menu-content menu-side-padding'; - content.textContent = 'There was an error loading the change log from the server.'; - - container.appendChild(heading); - container.appendChild(content); - }); - } -}; - - -FFZ.menu_pages.about = { - name: "About", - icon: constants.HEART, - sort_order: 100000, - - render: function(view, container, inner, menu) { - var room = this.rooms[view.get("context.currentRoom.id")], - has_emotes = false, f = this; - - // Check for emoticons. - if ( room && room.set ) { - var set = this.emote_sets[room.set]; - if ( set && set.count > 0 ) - has_emotes = true; - } - - // Heading - var heading = document.createElement('div'), - content = ''; - - content += "

FrankerFaceZ

"; - content += '
new ways to woof
'; - - heading.className = 'chat-menu-content center'; - heading.innerHTML = content; - container.appendChild(heading); - - var clicks = 0, head = heading.querySelector("h1"); - head && head.addEventListener("click", function() { - head.style.cursor = "pointer"; - clicks++; - if ( clicks >= 3 ) { - clicks = 0; - var el = document.querySelector(".app-main") || document.querySelector(".ember-chat-container"); - el && el.classList.toggle('ffz-flip'); - } - setTimeout(function(){clicks=0;head.style.cursor=""},2000); - }); - - - // Advertising - var btn_container = document.createElement('div'), - ad_button = document.createElement('a'), - message = "To use custom emoticons in " + (has_emotes ? "this channel" : "tons of channels") + ", get FrankerFaceZ from http://www.frankerfacez.com"; - - ad_button.className = 'button primary'; - ad_button.innerHTML = "Advertise in Chat"; - ad_button.addEventListener('click', this._add_emote.bind(this, view, message)); - - btn_container.appendChild(ad_button); - - // Donate - var donate_button = document.createElement('a'); - - donate_button.className = 'button ffz-donate'; - donate_button.href = "https://www.frankerfacez.com/donate"; - donate_button.target = "_new"; - donate_button.innerHTML = "Donate"; - - btn_container.appendChild(donate_button); - btn_container.className = 'chat-menu-content center'; - container.appendChild(btn_container); - - // Credits - var credits = document.createElement('div'); - - content = ''; - content += ''; - content += ''; - content += ''; - - content += ''; - - credits.className = 'chat-menu-content center'; - credits.innerHTML = content; - - // Functional Changelog - credits.querySelector('#ffz-changelog').addEventListener('click', function() { - f._ui_change_page(view, inner, menu, container, 'about_changelog'); - }); - - // Make the Logs button functional. - var getting_logs = false; - credits.querySelector('#ffz-debug-logs').addEventListener('click', function() { - if ( getting_logs ) - return; - - getting_logs = true; - f._pastebin(f._log_data.join("\n"), function(url) { - getting_logs = false; - if ( ! url ) - alert("There was an error uploading the FrankerFaceZ logs."); - else - prompt("Your FrankerFaceZ logs have been uploaded to the URL:", url); - }); - }); - - container.appendChild(credits); - } -} -},{"../constants":5}],24:[function(require,module,exports){ -var FFZ = window.FrankerFaceZ, - constants = require("../constants"); - - -// --------------------- -// Settings -// --------------------- - -FFZ.basic_settings.dark_twitch = { - type: "boolean", - no_bttv: true, - - category: "General", - name: "Dark Twitch", - help: "Apply a dark background to channels and other related pages for easier viewing.", - - get: function() { - return this.settings.dark_twitch; - }, - - set: function(val) { - this.settings.set('dark_twitch', val); - this.settings.set('dark_no_blue', val); - } -}; - -FFZ.basic_settings.separated_chat = { - type: "boolean", - no_bttv: true, - - category: "Chat", - name: "Separated Lines", - help: "Use alternating rows and thin lines to visually separate chat messages for easier reading.", - - get: function() { - return this.settings.chat_rows && this.settings.chat_separators !== '0'; - }, - - set: function(val) { - this.settings.set('chat_rows', val); - this.settings.set('chat_separators', val ? '2' : '0'); - } -}; - -FFZ.basic_settings.minimalistic_chat = { - type: "boolean", - - category: "Chat", - name: "Minimalistic UI", - help: "Hide all of chat except messages and the input box and reduce chat margins.", - - get: function() { - return this.settings.minimal_chat && this.settings.chat_padding; - }, - - set: function(val) { - this.settings.set('minimal_chat', val); - this.settings.set('chat_padding', val); - } -}; - -FFZ.basic_settings.high_contrast = { - type: "boolean", - - category: "Chat", - no_bttv: true, - - name: "High Contrast", - help: "Display chat using white and black for maximum contrast. This is suitable for capturing and chroma keying chat to display on stream.", - - get: function() { - return this.settings.high_contrast_chat !== '222'; - }, - - set: function(val) { - this.settings.set('high_contrast_chat', val ? '111': '222'); - } -}; - -FFZ.basic_settings.keywords = { - type: "button", - - category: "Chat", - no_bttv: true, - - name: "Highlight Keywords", - help: "Set additional keywords that will be highlighted in chat.", - - method: function() { - FFZ.settings_info.keywords.method.bind(this)(); - } -}; - -FFZ.basic_settings.banned_words = { - type: "button", - - category: "Chat", - no_bttv: true, - - name: "Banned Keywords", - help: "Set a list of words that will be removed from chat messages, locally.", - - method: function() { - FFZ.settings_info.banned_words.method.bind(this)(); - } -}; - - - -FFZ.settings_info.twitch_chat_dark = { - type: "boolean", - value: false, - visible: false - }; - - -FFZ.settings_info.dark_twitch = { - type: "boolean", - value: false, - - no_bttv: true, - //visible: function() { return ! this.has_bttv }, - - category: "Appearance", - name: "Dark Twitch", - help: "Apply a dark background to channels and other related pages for easier viewing.", - - on_update: function(val) { - var cb = document.querySelector('input.ffz-setting-dark-twitch'); - if ( cb ) - cb.checked = val; - - if ( this.has_bttv ) - return; - - document.body.classList.toggle("ffz-dark", val); - - var model = window.App ? App.__container__.lookup('controller:settings').get('model') : undefined; - - if ( val ) { - this._load_dark_css(); - model && this.settings.set('twitch_chat_dark', model.get('darkMode')); - model && model.set('darkMode', true); - } else - model && model.set('darkMode', this.settings.twitch_chat_dark); - } - }; - - -FFZ.settings_info.dark_no_blue = { - type: "boolean", - value: false, - - //no_bttv: true, - - category: "Appearance", - name: "Gray Chat (no blue)", - help: "Make the dark theme for chat and a few other places on Twitch a bit darker and not at all blue.", - - on_update: function(val) { - document.body.classList.toggle("ffz-no-blue", val); - } - }; - - -FFZ.settings_info.hide_recent_past_broadcast = { - type: "boolean", - value: false, - - //no_bttv: true, - no_mobile: true, - - category: "Channel Metadata", - name: "Hide \"Watch Last Broadcast\"", - help: "Hide the \"Watch Last Broadcast\" banner at the top of offline Twitch channels.", - - on_update: function(val) { - document.body.classList.toggle("ffz-hide-recent-past-broadcast", val); - } - }; - - -// --------------------- -// Initialization -// --------------------- - -FFZ.prototype.setup_dark = function() { - document.body.classList.toggle("ffz-hide-recent-past-broadcast", this.settings.hide_recent_past_broadcast); - document.body.classList.toggle("ffz-no-blue", this.settings.dark_no_blue); - - if ( this.has_bttv ) - return; - - document.body.classList.toggle("ffz-dark", this.settings.dark_twitch); - if ( ! this.settings.dark_twitch ) - return; - - window.App && App.__container__.lookup('controller:settings').set('model.darkMode', true); - this._load_dark_css(); -} - - -FFZ.prototype._load_dark_css = function() { - if ( this._dark_style ) - return; - - this.log("Injecting FrankerFaceZ Dark Twitch CSS."); - - var s = this._dark_style = document.createElement('link'); - - s.id = "ffz-dark-css"; - s.setAttribute('rel', 'stylesheet'); - s.setAttribute('href', constants.SERVER + "script/dark.css?_=" + (constants.DEBUG ? Date.now() : FFZ.version_info)); - document.head.appendChild(s); -} -},{"../constants":5}],25:[function(require,module,exports){ -var FFZ = window.FrankerFaceZ, - utils = require('../utils'), - constants = require('../constants'), - - FOLLOW_GRAVITY = function(f, el) { - return (f.settings.following_count && el.parentElement.getAttribute('data-name') === 'following' ? 'n' : '') + (f.settings.swap_sidebars ? 'e' : 'w'); - }, - - WIDE_TIP = function(f, el) { - return ( ! f.settings.following_count || (el.id !== 'header_following' && el.parentElement.getAttribute('data-name') !== 'following') ) ? '' : 'ffz-wide-tip'; - }; - - -FFZ.settings_info.following_count = { - type: "boolean", - value: true, - - no_mobile: true, - - category: "Appearance", - name: "Sidebar Following Data", - help: "Display the number of live channels you're following on the sidebar, and list the channels in a tooltip.", - - on_update: function(val) { - this._schedule_following_count(); - - var Stream = window.App && App.__container__.resolve('model:stream'), - Live = Stream && Stream.find("live"); - - if ( Live ) { - var total = Live.get('total') || 0; - this._draw_following_count(total); - this._draw_following_channels(Live.get('content'), total);; - } else { - this._update_following_count(); - this._draw_following_channels(); - } - } - }; - -// --------------- -// Initialization -// --------------- - -FFZ.prototype.setup_following_count = function(has_ember) { - // Start it updating. - if ( this.settings.following_count ) - this._schedule_following_count(); - - // Tooltips~! - this._install_following_tooltips(); - - // If we don't have Ember, no point in trying this stuff. - if ( ! has_ember ) - return this._update_following_count(); - - this.log("Connecting to Live Streams model."); - var Stream = window.App && App.__container__.resolve('model:stream'); - if ( ! Stream ) - return this.log("Unable to find Stream model."); - - var Live = Stream.find("live"), - f = this; - - if ( ! Live ) - return this.log("Unable to find Live Streams collection."); - - Live.addObserver('total', function() { f._draw_following_count(this.get('total')); }); - Live.addObserver('content.length', function() { f._draw_following_channels(this.get('content'), this.get('total')); }) - - Live.load(); - - var total = Live.get('total'), - streams = Live.get('content'); - if ( typeof total === "number" ) { - this._draw_following_count(total); - if ( streams && streams.length ) - this._draw_following_channels(streams, total); - } -} - - -FFZ.prototype._schedule_following_count = function() { - if ( ! this.settings.following_count ) { - if ( this._following_count_timer ) { - clearTimeout(this._following_count_timer); - this._following_count_timer = undefined; - } - return; - } - - if ( ! this._following_count_timer ) - this._following_count_timer = setTimeout(this._update_following_count.bind(this), 55000 + (10000*Math.random())); -} - - -FFZ.prototype._update_following_count = function() { - if ( ! this.settings.following_count ) { - if ( this._following_count_timer ) { - clearTimeout(this._following_count_timer); - this._following_count_timer = undefined; - } - return; - } - - this._following_count_timer = setTimeout(this._update_following_count.bind(this), 55000 + (10000*Math.random())); - - var Stream = window.App && App.__container__.resolve('model:stream'), - Live = Stream && Stream.find("live"), - f = this; - - if ( Live ) - Live.load(); - else - Twitch.api && Twitch.api.get("streams/followed", {limit:5, offset:0}, {version:3}) - .done(function(data) { - f._draw_following_count(data._total); - f._draw_following_channels(data.streams, data._total); - }).fail(function() { - f._draw_following_count(); - f._draw_following_channels(); - }) -} - - -FFZ.prototype._build_following_tooltip = function(el) { - if ( el.id !== 'header_following' && el.parentElement.getAttribute('data-name') !== 'following' ) - return el.getAttribute('original-title'); - - if ( ! this.settings.following_count ) - return 'Following'; - - var tooltip = (this.has_bttv ? 'FrankerFaceZ' : '') + 'Following', - bb = el.getBoundingClientRect(), - height = document.body.clientHeight - (bb.bottom + 54), - max_lines = Math.max(Math.floor(height / 36) - 1, 2), - - streams = this._tooltip_streams, - total = this._tooltip_total || (streams && streams.length) || 0; - - - if ( streams && streams.length ) { - var c = 0; - for(var i=0, l = streams.length; i < l; i++) { - var stream = streams[i]; - if ( ! stream || ! stream.channel ) - continue; - - c += 1; - if ( c > max_lines ) { - tooltip += '
And ' + utils.number_commas(total - max_lines) + ' more...'; - break; - } - - var up_since = this.settings.stream_uptime && stream.created_at && utils.parse_date(stream.created_at), - uptime = up_since && Math.floor((Date.now() - up_since.getTime()) / 1000) || 0, - minutes = Math.floor(uptime / 60) % 60, - hours = Math.floor(uptime / 3600); - - tooltip += (i === 0 ? '
' : '') + - (uptime > 0 ? '' + constants.CLOCK + ' ' + (hours > 0 ? hours + 'h' : '') + minutes + 'm' : '') + - '' + constants.LIVE + ' ' + utils.number_commas(stream.viewers) + '' + - '' + utils.sanitize(stream.channel.display_name || stream.channel.name) + '
' + - '' + (stream.channel.game ? 'Playing ' + utils.sanitize(stream.channel.game) : 'Not Playing') + ''; - } - } else - tooltip += "
No one you're following is online."; - - - // Reposition the tooltip. - setTimeout(function() { - var tip = document.querySelector('.tipsy'), - bb = tip.getBoundingClientRect(), - - left = parseInt(tip.style.left || '0'), - right = bb.left + tip.scrollWidth; - - if ( bb.left < 5 ) - tip.style.left = (left - bb.left) + 5 + 'px'; - else if ( right > document.body.clientWidth - 5 ) - tip.style.left = (left - (5 + right - document.body.clientWidth)) + 'px'; - }); - - return tooltip; -} - - -FFZ.prototype._install_following_tooltips = function() { - var f = this, - data = { - html: true, - className: function() { return WIDE_TIP(f, this); }, - title: function() { return f._build_following_tooltip(this); } - }; - - // Small - var small_following = jQuery('#small_nav ul.game_filters li[data-name="following"] a'); - if ( small_following && small_following.length ) { - var td = small_following.data('tipsy'); - if ( td && td.options ) { - td.options = _.extend(td.options, data); - td.options.gravity = function() { return FOLLOW_GRAVITY(f, this); }; - } else - small_following.tipsy(_.extend({gravity: function() { return FOLLOW_GRAVITY(f, this); }}, data)); - } - - - // Large - var large_following = jQuery('#large_nav #nav_personal li[data-name="following"] a'); - if ( large_following && large_following.length ) { - var td = large_following.data('tipsy'); - if ( td && td.options ) - td.options = _.extend(td.options, data); - else - large_following.tipsy(data); - } - - - // Heading - var head_following = jQuery('#header_actions #header_following'); - if ( head_following && head_following.length ) { - var td = head_following.data('tipsy'); - if ( td && td.options ) - td.options = _.extend(td.options, data); - else - head_following.tipsy(data); - } -} - - -FFZ.prototype._draw_following_channels = function(streams, total) { - this._tooltip_streams = streams; - this._tooltip_total = total; -} - - -FFZ.prototype._draw_following_count = function(count) { - // Small - var small_following = document.querySelector('#small_nav ul.game_filters li[data-name="following"] a'); - if ( small_following ) { - var badge = small_following.querySelector('.ffz-follow-count'); - if ( this.has_bttv || ! this.settings.following_count ) { - if ( badge ) - badge.parentElement.removeChild(badge); - } else { - if ( ! badge ) { - badge = document.createElement('span'); - badge.className = 'ffz-follow-count'; - small_following.appendChild(badge); - } - badge.innerHTML = count ? utils.format_unread(count) : ''; - } - } - - - // Large - var large_following = document.querySelector('#large_nav #nav_personal li[data-name="following"] a'); - if ( large_following ) { - var badge = large_following.querySelector('.ffz-follow-count'); - if ( this.has_bttv || ! this.settings.following_count ) { - if ( badge ) - badge.parentElement.removeChild(badge); - } else { - if ( ! badge ) { - badge = document.createElement('span'); - badge.className = 'ffz-follow-count'; - large_following.appendChild(badge); - } - badge.innerHTML = count ? utils.format_unread(count) : ''; - } - } - - // Heading - var head_following = document.querySelector('#header_actions #header_following'); - if ( head_following ) { - var badge = head_following.querySelector('.ffz-follow-count'); - if ( this.has_bttv || ! this.settings.following_count ) { - if ( badge ) - badge.parentElement.removeChild(badge); - } else { - if ( ! badge ) { - badge = document.createElement('span'); - badge.className = 'ffz-follow-count'; - head_following.appendChild(badge); - } - badge.innerHTML = count ? utils.format_unread(count) : ''; - } - } -} -},{"../constants":5,"../utils":35}],26:[function(require,module,exports){ -var FFZ = window.FrankerFaceZ, - utils = require('../utils'), - - VALID_CHANNEL = /^[A-Za-z0-9_]+$/, - TWITCH_URL = /^(?:https?:\/\/)?(?:www\.)?twitch\.tv\/([A-Za-z0-9_]+)/i; - - -// --------------- -// Initialization -// --------------- - -FFZ.prototype.setup_following = function() { - this.log("Initializing following support."); - this.follow_data = {}; - this.follow_sets = {}; -} - - -// --------------- -// Settings -// --------------- - -FFZ.settings_info.follow_buttons = { - type: "boolean", - value: true, - no_mobile: true, - - category: "Channel Metadata", - name: "Relevant Follow Buttons", - help: 'Display additional Follow buttons for channels relevant to the stream, such as people participating in co-operative gameplay.', - on_update: function(val) { - this.rebuild_following_ui(); - } - }; - - -// --------------- -// Command -// --------------- - -FFZ.ffz_commands.following = function(room, args) { - args = args.join(" ").trim().toLowerCase().split(/[ ,]+/); - - var out = []; - for(var i=0,l=args.length; i span'); } - catch(err) { before = undefined; } - - if ( before ) - container.insertBefore(cont, before); - else - container.appendChild(cont); - } else - cont.innerHTML = ''; - - var processed = [channel_id]; - for(var i=0; i < data.length && i < 10; i++) { - var cid = data[i]; - if ( processed.indexOf(cid) !== -1 ) - continue; - this._build_following_button(cont, cid); - processed.push(cid); - } - } - } - - - if ( hosted_id ) { - var data = this.follow_data && this.follow_data[hosted_id], - - el = this._cindex.get('element'), - container = el && el.querySelector('#hostmode .channel-actions'), - cont = container && container.querySelector('#ffz-ui-following'); - - if ( ! container || ! this.settings.follow_buttons || ! data || ! data.length ) { - if ( cont ) - cont.parentElement.removeChild(cont); - - } else { - if ( ! cont ) { - cont = document.createElement('span'); - cont.id = 'ffz-ui-following'; - - var before; - try { before = container.querySelector(':scope > span'); } - catch(err) { before = undefined; } - - if ( before ) - container.insertBefore(cont, before); - else - container.appendChild(cont); - } else - cont.innerHTML = ''; - - var processed = [hosted_id]; - for(var i=0; i < data.length && i < 10; i++) { - var cid = data[i]; - if ( processed.indexOf(cid) !== -1 ) - continue; - this._build_following_button(cont, cid); - processed.push(cid); - } - } - } -} - - -// --------------- -// UI Construction -// --------------- - -FFZ.prototype._build_following_button = function(container, channel_id) { - if ( ! VALID_CHANNEL.test(channel_id) ) - return this.log("Ignoring Invalid Channel: " + utils.sanitize(channel_id)); - - var btn = document.createElement('a'), f = this, - btn_c = document.createElement('div'), - noti = document.createElement('a'), - noti_c = document.createElement('div'), - - display_name, - following = false, - notifications = false, - - update = function() { - btn_c.classList.toggle('is-following', following); - btn.title = (following ? "Unf" : "F") + "ollow " + utils.sanitize(display_name); - btn.innerHTML = (following ? "" : "Follow ") + utils.sanitize(display_name); - noti_c.classList.toggle('hidden', !following); - }, - - check_following = function() { - var user = f.get_user(); - if ( ! user || ! user.login ) { - following = false; - notification = false; - btn_c.classList.add('is-initialized'); - return update(); - } - - Twitch.api.get("users/" + user.login + "/follows/channels/" + channel_id) - .done(function(data) { - following = true; - notifications = data.notifications; - btn_c.classList.add('is-initialized'); - update(); - }).fail(function(data) { - following = false; - notifications = false; - btn_c.classList.add('is-initialized'); - update(); - }); - }, - - do_follow = function(notice) { - if ( notice !== false ) - notice = true; - - var user = f.get_user(); - if ( ! user || ! user.login ) - return null; - - notifications = notice; - return Twitch.api.put("users/:login/follows/channels/" + channel_id, {notifications: notifications}) - .fail(check_following); - }, - - on_name = function(cap_name) { - display_name = cap_name || channel_id; - update(); - }; - - btn_c.className = 'ember-follow follow-button'; - btn_c.appendChild(btn); - - // The drop-down button! - noti.className = 'toggle-notification-menu js-toggle-notification-menu'; - noti.href = '#'; - - noti_c.className = 'notification-controls v2 hidden'; - noti_c.appendChild(noti); - - // Event Listeners! - btn.addEventListener('click', function(e) { - var user = f.get_user(); - if ( ! user || ! user.login ) - // Show the login dialog~! - return Ember.$.login({mpSourceAction: "follow-button", follow: channel_id}); - - // Immediate update for nice UI. - following = ! following; - update(); - - // Report it! - f.ws_send("track_follow", [channel_id, following]); - - // Do it, and make sure it happened. - if ( following ) - do_follow() - else - Twitch.api.del("users/:login/follows/channels/" + channel_id) - .done(check_following); - - return false; - }); - - btn.addEventListener('mousedown', function(e) { - if ( e.button !== 1 ) - return; - - e.preventDefault(); - window.open(Twitch.uri.profile(channel_id)); - }); - - noti.addEventListener('click', function() { - var sw = f._build_following_popup(noti_c, channel_id, notifications); - if ( sw ) - sw.addEventListener('click', function() { - var notice = ! notifications; - sw.classList.toggle('active', notice); - do_follow(notice); - return false; - }); - return false; - }); - - - display_name = FFZ.get_capitalization(channel_id, on_name); - update(); - - setTimeout(check_following, Math.random()*5000); - - container.appendChild(btn_c); - container.appendChild(noti_c); -} - - -FFZ.prototype._build_following_popup = function(container, channel_id, notifications) { - var popup = this._popup, out = '', - pos = container.offsetLeft + container.offsetWidth; - - - if ( popup ) { - popup.parentElement.removeChild(popup); - delete this._popup; - this._popup_kill && this._popup_kill(); - delete this._popup_kill; - - if ( popup.id == "ffz-following-popup" && popup.getAttribute('data-channel') === channel_id ) - return null; - } - - popup = this._popup = document.createElement('div'); - popup.id = 'ffz-following-popup'; - popup.setAttribute('data-channel', channel_id); - - popup.className = (pos >= 300 ? 'right' : 'left') + ' dropmenu notify-menu js-notify'; - - out = '
You are following ' + FFZ.get_capitalization(channel_id) + '
'; - out += '

'; - out += ''; - out += 'Notify me when the broadcaster goes live'; - out += '

'; - - popup.innerHTML = out; - container.appendChild(popup); - return popup.querySelector('a.switch'); -} -},{"../utils":35}],27:[function(require,module,exports){ -var FFZ = window.FrankerFaceZ, - constants = require('../constants'), - utils = require('../utils'), - - TWITCH_BASE = "http://static-cdn.jtvnw.net/emoticons/v1/", - - fix_menu_position = function(container) { - var swapped = document.body.classList.contains('ffz-sidebar-swap'); - - var bounds = container.getBoundingClientRect(), - left = parseInt(container.style.left || '0'), - right = bounds.left + container.scrollWidth, - moved = !!container.style.left; - - if ( swapped ) { - if ( bounds.left < 20 ) { - container.style.left = ''; - moved = false; - } else if ( right > document.body.clientWidth ) - container.style.left = (left - (right - document.body.clientWidth)) + 'px'; - - } else { - if ( bounds.left < 0 ) - container.style.left = (left - bounds.left) + 'px'; - else if ( right > (document.body.clientWidth - 20) ) { - container.style.left = ''; - moved = false; - } - } - - container.classList.toggle('ui-moved', moved); - }; - - -// -------------------- -// Initializer -// -------------------- - -FFZ.prototype.setup_menu = function() { - this.log("Installing mouse-up event to auto-close menus."); - var f = this; - - jQuery(document).mouseup(function(e) { - var popup = f._popup, parent; - if ( ! popup ) return; - if ( popup.id === 'ffz-chat-menu' && popup.style && popup.style.left ) - return; - - popup = jQuery(popup); - parent = popup.parent(); - - if ( ! parent.is(e.target) && parent.has(e.target).length === 0 ) { - popup.remove(); - delete f._popup; - f._popup_kill && f._popup_kill(); - delete f._popup_kill; - } - }); - - document.body.classList.toggle("ffz-menu-replace", this.settings.replace_twitch_menu); - - // Add FFZ to the chat settings menu. - - this.log("Hooking the Ember Chat Settings view."); - - var Settings = window.App && App.__container__.resolve('view:settings'); - - if ( ! Settings ) - return; - - Settings.reopen({ - didInsertElement: function() { - this._super(); - - try { - this.ffzInit(); - } catch(err) { - f.error("ChatSettings didInsertElement: " + err); - } - }, - - willClearRender: function() { - try { - this.ffzTeardown(); - } catch(err) { - f.error("ChatSettings willClearRender: " + err); - } - this._super(); - }, - - ffzInit: function() { - var view = this, - el = this.get('element'), - menu = el && el.querySelector('.dropmenu'); - - if ( ! menu ) - return; - - var header = document.createElement('div'), - content = document.createElement('div'), - p, cb, a; - - header.className = 'list-header'; - header.innerHTML = 'FrankerFaceZ'; - - content.className = 'chat-menu-content'; - - // Dark Twitch - p = document.createElement('p'); - p.className = 'no-bttv'; - cb = document.createElement('input'); - cb.type = "checkbox"; - cb.className = "ember-checkbox ffz-setting-dark-twitch"; - cb.checked = f.settings.dark_twitch; - p.appendChild(cb); - p.appendChild(document.createTextNode("Dark Twitch")); - content.appendChild(p); - - cb.addEventListener("change", function(e) { - f.settings.set("dark_twitch", this.checked); - }); - - - // Channel Hosting - p = document.createElement('p'); - //p.className = 'no-bttv'; - cb = document.createElement('input'); - cb.type = "checkbox"; - cb.className = "ember-checkbox ffz-setting-hosted-channels"; - cb.checked = f.settings.hosted_channels; - p.appendChild(cb); - p.appendChild(document.createTextNode("Channel Hosting")); - content.appendChild(p); - - cb.addEventListener("change", function(e) { - f.settings.set("hosted_channels", this.checked); - }); - - - // More Settings - p = document.createElement('p'); - a = document.createElement('a'); - a.href = '#'; - a.innerHTML = 'More Settings'; - p.appendChild(a); - content.appendChild(p); - - a.addEventListener('click', function(e) { - view.set('controller.model.hidden', true); - f._last_page = 'settings'; - f.build_ui_popup(f._chatv); - e.stopPropagation(); - return false; - }); - - menu.appendChild(header); - menu.appendChild(content); - }, - - ffzTeardown: function() { - // Nothing~! - } - }); - - // For some reason, this doesn't work unless we create an instance of the - // chat settings view and then destroy it immediately. - try { - Settings.create().destroy(); - } catch(err) { } - - // Modify all existing Chat Settings views. - for(var key in Ember.View.views) { - if ( ! Ember.View.views.hasOwnProperty(key) ) - continue; - - var view = Ember.View.views[key]; - if ( !(view instanceof Settings) ) - continue; - - this.log("Manually updating existing Chat Settings view.", view); - try { - view.ffzInit(); - } catch(err) { - this.error("setup: ChatSettings ffzInit: " + err); - } - } -} - - -FFZ.menu_pages = {}; - - -// -------------------- -// Create Menu -// -------------------- - -FFZ.prototype._fix_menu_position = function() { - var container = document.querySelector('#ffz-chat-menu'); - if ( container ) - fix_menu_position(container); -} - -FFZ.prototype.build_ui_popup = function(view) { - var popup = this._popup; - if ( popup ) { - popup.parentElement.removeChild(popup); - delete this._popup; - this._popup_kill && this._popup_kill(); - delete this._popup_kill; - return; - } - - // Start building the DOM. - var container = document.createElement('div'), - inner = document.createElement('div'), - menu = document.createElement('ul'), - - dark = (this.has_bttv ? BetterTTV.settings.get('darkenedMode') : false); - - container.className = 'emoticon-selector chat-menu ffz-ui-popup'; - container.id = 'ffz-chat-menu'; - inner.className = 'emoticon-selector-box dropmenu'; - container.appendChild(inner); - - container.classList.toggle('dark', dark); - - - // Menu Container - var sub_container = document.createElement('div'); - sub_container.className = 'ffz-ui-menu-page'; - - inner.appendChild(sub_container); - - // Render Menu Tabs - menu.className = 'menu clearfix'; - inner.appendChild(menu); - - var heading = document.createElement('li'); - heading.className = 'title'; - heading.innerHTML = 'Franker' + (constants.DEBUG ? 'Dev' : 'Face') + 'Z'; - - // Close Button - var close_btn = document.createElement('span'), - f = this; - - close_btn.className = 'ffz-handle ffz-close-button'; - heading.insertBefore(close_btn, heading.firstChild); - - var can_close = false; - close_btn.addEventListener('mousedown', function() { - var popup = f._popup; - can_close = popup && popup.id === "ffz-chat-menu" && popup.style.left; - }); - - close_btn.addEventListener('click', function() { - var popup = f._popup; - if ( can_close && popup ) { - popup.parentElement.removeChild(popup); - delete f._popup; - f._popup_kill && f._popup_kill(); - delete f._popup_kill; - } - }); - - menu.appendChild(heading); - - // Draggable - jQuery(container).draggable({ - handle: menu, cancel: 'li.item', axis:"x", - stop: function(e) { fix_menu_position(this); } - }); - - // Get rid of the position: relative that draggable adds. - container.style.position = ''; - - var menu_pages = []; - for(var key in FFZ.menu_pages) { - if ( ! FFZ.menu_pages.hasOwnProperty(key) ) - continue; - - var page = FFZ.menu_pages[key]; - try { - if ( !page || (page.hasOwnProperty("visible") && (!page.visible || (typeof page.visible == "function" && !page.visible.bind(this)(view)))) ) - continue; - } catch(err) { - this.error("menu_pages " + key + " visible: " + err); - continue; - } - - menu_pages.push([page.sort_order || 0, key, page]); - } - - menu_pages.sort(function(a,b) { - if ( a[0] < b[0] ) return 1; - else if ( a[0] > b[0] ) return -1; - - var al = a[1].toLowerCase(), - bl = b[1].toLowerCase(); - - if ( al < bl ) return 1; - if ( al > bl ) return -1; - return 0; - }); - - for(var i=0; i < menu_pages.length; i++) { - var key = menu_pages[i][1], - page = menu_pages[i][2], - el = document.createElement('li'), - link = document.createElement('a'); - - el.className = 'item' + (page.sub_menu ? ' has-sub-menu' : ''); - el.id = "ffz-menu-page-" + key; - link.title = page.name; - link.innerHTML = page.icon; - - jQuery(link).tipsy(); - - link.addEventListener("click", this._ui_change_page.bind(this, view, inner, menu, sub_container, key)); - - el.appendChild(link); - menu.appendChild(el); - } - - // Render Current Page - var page = (this._last_page || "channel").split("_", 1)[0]; - this._ui_change_page(view, inner, menu, sub_container, page); - - // Add the menu to the DOM. - this._popup = container; - sub_container.style.maxHeight = Math.max(200, view.$().height() - 172) + "px"; - view.$('.chat-interface').append(container); -} - - -FFZ.prototype._ui_change_page = function(view, inner, menu, container, page) { - this._last_page = page; - container.innerHTML = ""; - container.setAttribute('data-page', page); - - // Allow settings to be wide. We need to know if chat is stand-alone. - var app = document.querySelector(".app-main") || document.querySelector(".ember-chat-container"); - inner.style.maxWidth = (!FFZ.menu_pages[page].wide || (typeof FFZ.menu_pages[page].wide == "function" && !FFZ.menu_pages[page].wide.bind(this)())) ? "" : (app.offsetWidth < 640 ? (app.offsetWidth-40) : 600) + "px"; - - var els = menu.querySelectorAll('li.active'); - for(var i=0; i < els.length; i++) - els[i].classList.remove('active'); - - var el = menu.querySelector('#ffz-menu-page-' + page); - if ( el ) - el.classList.add('active'); - else - this.log("No matching page: " + page); - - FFZ.menu_pages[page].render.bind(this)(view, container, inner, menu); - - // Re-position if necessary. - var f = this; - setTimeout(function(){f._fix_menu_position();}); -} - - -// -------------------- -// Channel Page -// -------------------- - -FFZ.menu_pages.channel = { - render: function(view, inner) { - // Get the current room. - var room_id = view.get('controller.currentRoom.id'), - room = this.rooms[room_id], - has_product = false, - f = this; - - // Check for a product. - if ( this.settings.replace_twitch_menu ) { - var product = room.room.get("product"); - if ( product && !product.get("error") ) { - // We have a product, and no error~! - has_product = true; - var tickets = App.__container__.resolve('model:ticket').find('user', {channel: room_id}), - is_subscribed = tickets ? tickets.get('content') : false, - is_loaded = tickets ? tickets.get('isLoaded') : false, - icon = room.room.get("badgeSet.subscriber.image"), - - grid = document.createElement("div"), - header = document.createElement("div"), - c = 0; - - // Weird is_subscribed check. Might be more accurate? - is_subscribed = is_subscribed && is_subscribed.length > 0; - - // See if we've loaded. If we haven't loaded the ticket yet - // then try loading it, and then re-render the menu. - if ( tickets && ! is_subscribed && ! is_loaded ) { - tickets.addObserver('isLoaded', function() { - setTimeout(function(){ - if ( inner.getAttribute('data-page') !== 'channel' ) - return; - - inner.innerHTML = ''; - FFZ.menu_pages.channel.render.bind(f)(view, inner); - },0); - - }); - - tickets.load(); - } - - - grid.className = "emoticon-grid"; - header.className = "heading"; - if ( icon ) - header.style.backgroundImage = 'url("' + icon + '")'; - - header.innerHTML = 'TwitchSubscriber Emoticons'; - grid.appendChild(header); - - for(var emotes=product.get("emoticons") || [], i=0; i < emotes.length; i++) { - var emote = emotes[i]; - if ( emote.state !== "active" ) - continue; - - var s = document.createElement('span'), - can_use = is_subscribed || !emote.subscriber_only, - img_set = 'image-set(url("' + TWITCH_BASE + emote.id + '/1.0") 1x, url("' + TWITCH_BASE + emote.id + '/2.0") 2x, url("' + TWITCH_BASE + emote.id + '/3.0") 4x)'; - - s.className = 'emoticon tooltip' + (!can_use ? " locked" : ""); - - s.style.backgroundImage = 'url("' + TWITCH_BASE + emote.id + '/1.0")'; - s.style.backgroundImage = '-webkit-' + img_set; - s.style.backgroundImage = '-moz-' + img_set; - s.style.backgroundImage = '-ms-' + img_set; - s.style.backgroundImage = img_set; - - s.style.width = emote.width + "px"; - s.style.height = emote.height + "px"; - s.title = emote.regex; - - s.addEventListener('click', function(can_use, id, code, e) { - if ( (e.shiftKey || e.shiftLeft) && f.settings.clickable_emoticons ) - window.open("https://twitchemotes.com/emote/" + id); - else if ( can_use ) - this._add_emote(view, code); - else - return; - e.preventDefault(); - }.bind(this, can_use, emote.id, emote.regex)); - - grid.appendChild(s); - c++; - } - - if ( c > 0 ) - inner.appendChild(grid); - - if ( c > 0 && ! is_subscribed ) { - var sub_message = document.createElement("div"), - nonsub_message = document.createElement("div"), - unlock_text = document.createElement("span"), - sub_link = document.createElement("a"); - - sub_message.className = "subscribe-message"; - nonsub_message.className = "non-subscriber-message"; - sub_message.appendChild(nonsub_message); - - unlock_text.className = "unlock-text"; - unlock_text.innerHTML = "Subscribe to unlock Emoticons"; - nonsub_message.appendChild(unlock_text); - - sub_link.className = "action subscribe-button button primary"; - sub_link.href = product.get("product_url"); - sub_link.innerHTML = ''; - nonsub_message.appendChild(sub_link); - - inner.appendChild(sub_message); - } else if ( c > 0 ) { - var last_content = tickets.get("content"); - last_content = last_content.length > 0 ? last_content[last_content.length-1] : undefined; - if ( last_content && last_content.purchase_profile && !last_content.purchase_profile.will_renew ) { - var ends_at = utils.parse_date(last_content.access_end || ""); - sub_message = document.createElement("div"), - nonsub_message = document.createElement("div"), - unlock_text = document.createElement("span"), - end_time = ends_at ? Math.floor((ends_at.getTime() - Date.now()) / 1000) : null; - - sub_message.className = "subscribe-message"; - nonsub_message.className = "non-subscriber-message"; - sub_message.appendChild(nonsub_message); - - unlock_text.className = "unlock-text"; - unlock_text.innerHTML = "Subscription expires in " + utils.time_to_string(end_time, true, true); - nonsub_message.appendChild(unlock_text); - inner.appendChild(sub_message); - } - } - } - } - - // Do we have extra sets? - var extra_sets = room && room.extra_sets || []; - - // Basic Emote Sets - this._emotes_for_sets(inner, view, room && room.set && [room.set] || [], (this.feature_friday || has_product || extra_sets.length ) ? "Channel Emoticons" : null, "http://cdn.frankerfacez.com/script/devicon.png", "FrankerFaceZ"); - - for(var i=0; i < extra_sets.length; i++) { - // Look up the set name. - var set = this.emote_sets[extra_sets[i]], - name = set ? "Featured " + set.title : "Featured Channel"; - - this._emotes_for_sets(inner, view, [extra_sets[i]], name, "http://cdn.frankerfacez.com/script/devicon.png", "FrankerFaceZ"); - } - - // Feature Friday! - this._feature_friday_ui(room_id, inner, view); - }, - - name: "Channel", - icon: constants.ZREKNARF - }; - - -// -------------------- -// Emotes for Sets -// -------------------- - -FFZ.prototype._emotes_for_sets = function(parent, view, sets, header, image, sub_text) { - var grid = document.createElement('div'), c = 0, f = this; - grid.className = 'emoticon-grid'; - - if ( header != null ) { - var el_header = document.createElement('div'); - el_header.className = 'heading'; - - if ( sub_text ) { - var s = document.createElement("span"); - s.className = "right"; - s.appendChild(document.createTextNode(sub_text)); - el_header.appendChild(s); - } - - el_header.appendChild(document.createTextNode(header)); - - if ( image ) - el_header.style.backgroundImage = 'url("' + image + '")'; - - grid.appendChild(el_header); - } - - var emotes = []; - for(var i=0; i < sets.length; i++) { - var set = this.emote_sets[sets[i]]; - if ( ! set || ! set.emoticons ) - continue; - - for(var eid in set.emoticons) { - if ( ! set.emoticons.hasOwnProperty(eid) || set.emoticons[eid].hidden ) - continue; - - emotes.push(set.emoticons[eid]); - } - } - - // Sort the emotes! - emotes.sort(function(a,b) { - var an = a.name.toLowerCase(), - bn = b.name.toLowerCase(); - - if ( an < bn ) return -1; - else if ( an > bn ) return 1; - return 0; - }); - - for(var i=0; i < emotes.length; i++) { - var emote = emotes[i], srcset = null; - - if ( emote.urls[2] || emote.urls[4] ) { - srcset = 'url("' + emote.urls[1] + '") 1x'; - if ( emote.urls[2] ) - srcset += ', url("' + emote.urls[2] + '") 2x'; - if ( emote.urls[4] ) - srcset += ', url("' + emote.urls[4] + '") 4x'; - } - - c++; - var s = document.createElement('span'); - s.className = 'emoticon tooltip'; - s.style.backgroundImage = 'url("' + emote.urls[1] + '")'; - - if ( srcset ) { - var img_set = 'image-set(' + srcset + ')'; - s.style.backgroundImage = '-webkit-' + img_set; - s.style.backgroundImage = '-moz-' + img_set; - s.style.backgroundImage = '-ms-' + img_set; - s.style.backgroundImage = img_set; - } - - s.style.width = emote.width + "px"; - s.style.height = emote.height + "px"; - s.title = this._emote_tooltip(emote); - - s.addEventListener('click', function(id, code, e) { - e.preventDefault(); - if ( (e.shiftKey || e.shiftLeft) && f.settings.clickable_emoticons ) - window.open("https://www.frankerfacez.com/emoticons/" + id); - else - this._add_emote(view, code); - }.bind(this, emote.id, emote.name)); - - grid.appendChild(s); - } - - if ( !c ) { - grid.innerHTML += "This channel has no emoticons."; - grid.className = "emoticon-grid ffz-no-emotes center"; - } - - parent.appendChild(grid); -} - - -FFZ.prototype._add_emote = function(view, emote) { - var input_el, text, room; - - if ( this.has_bttv ) { - input_el = view.get('element').querySelector('textarea'); - text = input_el.value; - - } else { - room = view.get('controller.currentRoom'); - text = room.get('messageToSend') || ''; - } - - text += (text && text.substr(-1) !== " " ? " " : "") + (emote.name || emote); - - if ( input_el ) - input_el.value = text; - else - room.set('messageToSend', text); -} -},{"../constants":5,"../utils":35}],28:[function(require,module,exports){ -var FFZ = window.FrankerFaceZ, - constants = require('../constants'); - -// -------------------- -// Initialization -// -------------------- - -FFZ.prototype.build_ui_link = function(view) { - var link = document.createElement('a'); - link.className = 'ffz-ui-toggle'; - link.innerHTML = constants.CHAT_BUTTON; - - link.addEventListener('click', this.build_ui_popup.bind(this, view)); - - this.update_ui_link(link); - return link; -} - - -FFZ.prototype.update_ui_link = function(link) { - var controller = window.App && App.__container__.lookup('controller:chat'); - link = link || document.querySelector('a.ffz-ui-toggle'); - if ( !link || !controller ) - return; - - var room_id = controller.get('currentRoom.id'), - room = this.rooms[room_id], - has_emotes = false, - - dark = (this.has_bttv ? BetterTTV.settings.get('darkenedMode') : false), - blue = (this.has_bttv ? BetterTTV.settings.get('showBlueButtons') : false), - live = (this.feature_friday && this.feature_friday.live); - - - // Check for emoticons. - if ( room && room.set ) { - var set = this.emote_sets[room.set]; - if ( set && set.count > 0 ) - has_emotes = true; - } - - link.classList.toggle('no-emotes', ! has_emotes); - link.classList.toggle('live', live); - link.classList.toggle('dark', dark); - link.classList.toggle('blue', blue); -} -},{"../constants":5}],29:[function(require,module,exports){ -var FFZ = window.FrankerFaceZ, - constants = require("../constants"), - utils = require("../utils"), - - TWITCH_BASE = "http://static-cdn.jtvnw.net/emoticons/v1/", - BANNED_SETS = {"00000turbo":true}; - - -// ------------------- -// Initialization -// ------------------- - -FFZ.basic_settings.replace_twitch_menu = { - type: "boolean", - - category: "Chat", - - name: "Unified Emoticons Menu", - help: "Completely replace the default Twitch emoticon menu and display global emoticons in the My Emoticons menu.", - - get: function() { - return this.settings.replace_twitch_menu && this.settings.global_emotes_in_menu && this.settings.emoji_in_menu; - }, - - set: function(val) { - this.settings.set('replace_twitch_menu', val); - this.settings.set('global_emotes_in_menu', val); - this.settings.set('emoji_in_menu', val); - } -}; - -FFZ.settings_info.replace_twitch_menu = { - type: "boolean", - value: false, - - category: "Chat Input", - - name: "Replace Twitch Emoticon Menu", - help: "Completely replace the default Twitch emoticon menu.", - - on_update: function(val) { - document.body.classList.toggle("ffz-menu-replace", val); - } - }; - - -FFZ.settings_info.global_emotes_in_menu = { - type: "boolean", - value: false, - - category: "Chat Input", - - name: "Display Global Emotes in My Emotes", - help: "Display the global Twitch emotes in the My Emoticons menu." - }; - - -FFZ.settings_info.emoji_in_menu = { - type: "boolean", - value: false, - - category: "Chat Input", - - name: "Display Emoji in My Emotes", - help: "Display the supported emoji images in the My Emoticons menu." - }; - - -FFZ.settings_info.emote_menu_collapsed = { - value: [], - visible: false -} - - -FFZ.prototype.setup_my_emotes = function() { - this._twitch_badges = {}; - this._twitch_badges["--global--"] = "//cdn.frankerfacez.com/script/twitch_logo.png"; - this._twitch_badges["--turbo-faces--"] = this._twitch_badges["turbo"] = "//cdn.frankerfacez.com/script/turbo_badge.png"; -} - - -// ------------------- -// Menu Page -// ------------------- - -FFZ.menu_pages.myemotes = { - name: "My Emoticons", - icon: constants.EMOTE, - - visible: function(view) { - var user = this.get_user(), - tmi = view.get('controller.currentRoom.tmiSession'), - ffz_sets = user && this.users[user.login] && this.users[user.login].sets || [], - twitch_sets = (tmi && tmi.getEmotes() || {'emoticon_sets': {}})['emoticon_sets']; - - return ffz_sets.length || (twitch_sets && Object.keys(twitch_sets).length); - }, - - render: function(view, container) { - var tmi = view.get('controller.currentRoom.tmiSession'), - twitch_sets = (tmi && tmi.getEmotes() || {'emoticon_sets': {}})['emoticon_sets']; - - // We don't have to do async stuff anymore cause we pre-load data~! - return FFZ.menu_pages.myemotes.draw_menu.bind(this)(view, container, twitch_sets); - }, - - toggle_section: function(heading) { - var menu = heading.parentElement, - set_id = menu.getAttribute('data-set'), - collapsed_list = this.settings.emote_menu_collapsed, - is_collapsed = collapsed_list.indexOf(set_id) !== -1; - - if ( is_collapsed ) - collapsed_list.removeObject(set_id); - else - collapsed_list.push(set_id); - - this.settings.set('emote_menu_collapsed', collapsed_list); - menu.classList.toggle('collapsed', !is_collapsed); - }, - - draw_emoji: function(view) { - var heading = document.createElement('div'), - menu = document.createElement('div'), - f = this; - - heading.className = 'heading'; - heading.innerHTML = 'FrankerFaceZEmoji'; - heading.style.backgroundImage = 'url("' + constants.SERVER + '/emoji/1f4af-1x.png")'; - - menu.className = 'emoticon-grid collapsable'; - menu.appendChild(heading); - - menu.setAttribute('data-set', 'emoji'); - menu.classList.toggle('collapsed', this.settings.emote_menu_collapsed.indexOf('emoji') !== -1); - heading.addEventListener('click', function() { FFZ.menu_pages.myemotes.toggle_section.bind(f)(this); }); - - var set = []; - for(var eid in this.emoji_data) - set.push(this.emoji_data[eid]); - - set.sort(function(a,b) { - var an = a.short_name.toLowerCase(), - bn = b.short_name.toLowerCase(); - - if ( an < bn ) return -1; - else if ( an > bn ) return 1; - if ( a.raw < b.raw ) return -1; - if ( a.raw > b.raw ) return 1; - return 0; - }); - - for(var i=0; i < set.length; i++) { - var emoji = set[i], - em = document.createElement('span'), - img_set = 'image-set(url("' + emoji.src + '") 1x, url("' + constants.SERVER + 'emoji/' + emoji.code + '-2x.png") 2x, url("' + constants.SERVER + 'emoji/' + emoji.code + '-4x.png") 4x)'; - - em.className = 'emoticon tooltip'; - em.title = 'Emoji: ' + emoji.raw + '\nName: :' + emoji.short_name + ':'; - em.addEventListener('click', this._add_emote.bind(this, view, emoji.raw)); - - em.style.backgroundImage = 'url("' + emoji.src + '")'; - em.style.backgroundImage = '-webkit-' + img_set; - em.style.backgroundImage = '-moz-' + img_set; - em.style.backgroundImage = '-ms-' + img_set; - em.style.backgroudnImage = img_set; - - menu.appendChild(em); - } - - return menu; - }, - - draw_twitch_set: function(view, set_id, set) { - var heading = document.createElement('div'), - menu = document.createElement('div'), - f = this, - - channel_id = this._twitch_set_to_channel[set_id], title; - - if ( channel_id === "twitch_unknown" ) - title = "Unknown Channel"; - else if ( channel_id === "--global--" ) - title = "Global Emoticons"; - else if ( channel_id === "turbo" || channel_id === "--turbo-faces--" ) - title = "Twitch Turbo"; - else - title = FFZ.get_capitalization(channel_id, function(name) { - heading.innerHTML = 'Twitch' + utils.sanitize(name); - }); - - heading.className = 'heading'; - heading.innerHTML = 'Twitch' + utils.sanitize(title); - - if ( this._twitch_badges[channel_id] ) - heading.style.backgroundImage = 'url("' + this._twitch_badges[channel_id] + '")'; - else { - var f = this; - Twitch.api.get("chat/" + channel_id + "/badges", null, {version: 3}) - .done(function(data) { - if ( data.subscriber && data.subscriber.image ) { - f._twitch_badges[channel_id] = data.subscriber.image; - localStorage.ffzTwitchBadges = JSON.stringify(f._twitch_badges); - heading.style.backgroundImage = 'url("' + data.subscriber.image + '")'; - } - }); - } - - menu.className = 'emoticon-grid collapsable'; - menu.appendChild(heading); - - menu.setAttribute('data-set', 'twitch-' + set_id); - menu.classList.toggle('collapsed', this.settings.emote_menu_collapsed.indexOf('twitch-' + set_id) !== -1); - heading.addEventListener('click', function() { FFZ.menu_pages.myemotes.toggle_section.bind(f)(this); }); - - set.sort(function(a,b) { - var an = a.code.toLowerCase(), - bn = b.code.toLowerCase(); - - if ( an < bn ) return -1; - else if ( an > bn ) return 1; - if ( a.id < b.id ) return -1; - if ( a.id > b.id ) return 1; - return 0; - }); - - for(var i=0; i < set.length; i++) { - var emote = set[i], - code = constants.KNOWN_CODES[emote.code] || emote.code, - - em = document.createElement('span'), - img_set = 'image-set(url("' + TWITCH_BASE + emote.id + '/1.0") 1x, url("' + TWITCH_BASE + emote.id + '/2.0") 2x, url("' + TWITCH_BASE + emote.id + '/3.0") 4x)'; - - em.className = 'emoticon tooltip'; - - if ( this.settings.replace_bad_emotes && constants.EMOTE_REPLACEMENTS[emote.id] ) { - em.style.backgroundImage = 'url("' + constants.EMOTE_REPLACEMENT_BASE + constants.EMOTE_REPLACEMENTS[emote.id] + '")'; - } else { - em.style.backgroundImage = 'url("' + TWITCH_BASE + emote.id + '/1.0")'; - em.style.backgroundImage = '-webkit-' + img_set; - em.style.backgroundImage = '-moz-' + img_set; - em.style.backgroundImage = '-ms-' + img_set; - em.style.backgroudnImage = img_set; - } - - em.title = code; - em.addEventListener("click", function(id, code, e) { - e.preventDefault(); - if ( (e.shiftKey || e.shiftLeft) && f.settings.clickable_emoticons ) - window.open("https://twitchemotes.com/emote/" + id); - else - this._add_emote(view, code); - }.bind(this, emote.id, emote.code)); - menu.appendChild(em); - } - - return menu; - }, - - draw_ffz_set: function(view, set) { - var heading = document.createElement('div'), - menu = document.createElement('div'), - f = this, - emotes = []; - - heading.className = 'heading'; - heading.innerHTML = 'FrankerFaceZ' + set.title; - heading.style.backgroundImage = 'url("' + (set.icon || '//cdn.frankerfacez.com/script/devicon.png') + '")'; - - menu.className = 'emoticon-grid collapsable'; - menu.appendChild(heading); - - menu.setAttribute('data-set', 'ffz-' + set.id); - menu.classList.toggle('collapsed', this.settings.emote_menu_collapsed.indexOf('ffz-' + set.id) !== -1); - heading.addEventListener('click', function() { FFZ.menu_pages.myemotes.toggle_section.bind(f)(this); }); - - for(var emote_id in set.emoticons) - set.emoticons.hasOwnProperty(emote_id) && ! set.emoticons[emote_id].hidden && emotes.push(set.emoticons[emote_id]); - - emotes.sort(function(a,b) { - var an = a.name.toLowerCase(), - bn = b.name.toLowerCase(); - - if ( an < bn ) return -1; - else if ( an > bn ) return 1; - if ( a.id < b.id ) return -1; - if ( a.id > b.id ) return 1; - return 0; - }); - - for(var i=0; i < emotes.length; i++) { - var emote = emotes[i], - - em = document.createElement('span'), - img_set = 'image-set(url("' + emote.urls[1] + '") 1x'; - - if ( emote.urls[2] ) - img_set += ', url("' + emote.urls[2] + '") 2x'; - - if ( emote.urls[4] ) - img_set += ', url("' + emote.urls[4] + '") 4x'; - - img_set += ')'; - - em.className = 'emoticon tooltip'; - em.style.backgroundImage = 'url("' + emote.urls[1] + '")'; - em.style.backgroundImage = '-webkit-' + img_set; - em.style.backgroundImage = '-moz-' + img_set; - em.style.backgroundImage = '-ms-' + img_set; - em.style.backgroudnImage = img_set; - - if ( emote.height ) - em.style.height = emote.height + "px"; - if ( emote.width ) - em.style.width = emote.width + "px"; - - em.title = this._emote_tooltip(emote); - em.addEventListener("click", function(id, code, e) { - e.preventDefault(); - if ( (e.shiftKey || e.shiftLeft) && f.settings.clickable_emoticons ) - window.open("https://www.frankerfacez.com/emoticons/" + id); - else - this._add_emote(view, code); - }.bind(this, emote.id, emote.name)); - menu.appendChild(em); - } - - return menu; - }, - - draw_menu: function(view, container, twitch_sets) { - // Make sure we're still on the My Emoticons page. Since this is - // asynchronous, the user could've tabbed away. - if ( container.getAttribute('data-page') !== 'myemotes' ) - return; - - container.innerHTML = ""; - try { - var user = this.get_user(), - ffz_sets = this.getEmotes(user && user.login, null), - sets = []; - - // Start with Twitch Sets - for(var set_id in twitch_sets) { - if ( ! twitch_sets.hasOwnProperty(set_id) || ( ! this.settings.global_emotes_in_menu && set_id === '0' ) ) - continue; - - var set = twitch_sets[set_id]; - if ( ! set.length ) - continue; - - sets.push([this._twitch_set_to_channel[set_id], FFZ.menu_pages.myemotes.draw_twitch_set.bind(this)(view, set_id, set)]); - } - - // Emoji~! - if ( this.settings.emoji_in_menu ) - sets.push(["emoji", FFZ.menu_pages.myemotes.draw_emoji.bind(this)(view)]); - - // Now, FFZ! - for(var i=0; i < ffz_sets.length; i++) { - var set_id = ffz_sets[i], - set = this.emote_sets[set_id]; - - if ( ! set || ! set.count || ( ! this.settings.global_emotes_in_menu && this.default_sets.indexOf(set_id) !== -1 ) ) - continue; - - sets.push([set.title.toLowerCase(), FFZ.menu_pages.myemotes.draw_ffz_set.bind(this)(view, set)]); - } - - - // Finally, sort and add them all. - sets.sort(function(a,b) { - var an = a[0], bn = b[0]; - if ( an === "turbo" || an === "--turbo-faces--" ) - an = "zza|" + an; - else if ( an === "global" || an === "global emoticons" || an === "--global--" ) - an = "zzy|" + an; - else if ( an === "emoji" ) - an = "zzz|" + an; - - if ( bn === "turbo" || bn === "--turbo-faces--" ) - bn = "zza|" + bn; - else if ( bn === "global" || bn === "global emoticons" || bn === "--global--" ) - bn = "zzy|" + bn; - else if ( bn === "emoji" ) - bn = "zzz|" + bn; - - if ( an < bn ) return -1; - if ( an > bn ) return 1; - return 0; - }); - - for(var i=0; i < sets.length; i++) - container.appendChild(sets[i][1]); - - } catch(err) { - this.error("myemotes draw_menu: " + err); - container.innerHTML = ""; - - var menu = document.createElement('div'), - heading = document.createElement('div'), - p = document.createElement('p'); - - heading.className = 'heading'; - heading.innerHTML = 'Error Loading Menu'; - menu.appendChild(heading); - - p.className = 'clearfix'; - p.textContent = err; - menu.appendChild(p); - - menu.className = 'chat-menu-content'; - container.appendChild(menu); - } - } -}; -},{"../constants":5,"../utils":35}],30:[function(require,module,exports){ -var FFZ = window.FrankerFaceZ; - - -// --------------------- -// Initialization -// --------------------- - -FFZ.prototype.setup_notifications = function() { - this.log("Adding event handler for window focus."); - window.addEventListener("focus", this.clear_notifications.bind(this)); -} - - -// --------------------- -// Settings -// --------------------- - -FFZ.settings_info.highlight_notifications = { - type: "boolean", - value: false, - - category: "Chat Filtering", - no_bttv: true, - no_mobile: true, - //visible: function() { return ! this.has_bttv }, - - name: "Highlight Notifications", - help: "Display notifications when a highlighted word appears in chat in an unfocused tab. This is automatically disabled on the dashboard.", - - on_update: function(val, direct) { - // Check to see if we have notification permission. If this is - // enabled, at least. - if ( ! val || ! direct ) - return; - - if ( Notification.permission === "denied" ) { - this.log("Notifications have been denied by the user."); - this.settings.set("highlight_notifications", false); - return; - - } else if ( Notification.permission === "granted" ) - return; - - var f = this; - Notification.requestPermission(function(e) { - if ( e === "denied" ) { - f.log("Notifications have been denied by the user."); - f.settings.set("highlight_notifications", false); - } - }); - } - }; - - -FFZ.settings_info.notification_timeout = { - type: "button", - value: 60, - - category: "Chat Filtering", - no_bttv: true, - no_mobile: true, - - name: "Notification Timeout", - help: "Specify how long notifications should be displayed before automatically closing.", - - method: function() { - var old_val = this.settings.notification_timeout, - new_val = prompt("Notification Timeout\n\nPlease enter the time you'd like notifications to be displayed before automatically closing, in seconds.\n\nDefault is: 60", old_val); - - if ( new_val === null || new_val === undefined ) - return; - - var parsed = parseInt(new_val); - if ( parsed === NaN || parsed < 1 ) - parsed = 60; - - this.settings.set("notification_timeout", parsed); - } - }; - - -// --------------------- -// Socket Commands -// --------------------- - -FFZ.ws_commands.message = function(message) { - this.show_message(message); -} - - -// --------------------- -// Notifications -// --------------------- - -FFZ._notifications = {}; -FFZ._last_notification = 0; - -FFZ.prototype.clear_notifications = function() { - for(var k in FFZ._notifications) { - var n = FFZ._notifications[k]; - if ( n ) - try { - n.close(); - } catch(err) { } - } - - FFZ._notifications = {}; - FFZ._last_notification = 0; -} - - -FFZ.prototype.show_notification = function(message, title, tag, timeout, on_click, on_close) { - var perm = Notification.permission; - if ( perm === "denied " ) - return false; - - if ( perm === "granted" ) { - title = title || "FrankerFaceZ"; - timeout = timeout || (this.settings.notification_timeout*1000); - - var options = { - lang: "en-US", - dir: "ltr", - body: message, - tag: tag || "FrankerFaceZ", - icon: "http://cdn.frankerfacez.com/icon32.png" - }; - - var f = this, - n = new Notification(title, options), - nid = FFZ._last_notification++; - - FFZ._notifications[nid] = n; - - n.addEventListener("click", function() { - delete FFZ._notifications[nid]; - if ( on_click ) - on_click.bind(f)(); - }); - - n.addEventListener("close", function() { - delete FFZ._notifications[nid]; - if ( on_close ) - on_close.bind(f)(); - }); - - if ( typeof timeout == "number" ) - n.addEventListener("show", function() { - setTimeout(function() { - delete FFZ._notifications[nid]; - n.close(); - }, timeout); - }); - - return; - } - - var f = this; - Notification.requestPermission(function(e) { - f.show_notification(message, title, tag); - }); -} - - - -// --------------------- -// Noty Notification -// --------------------- - -FFZ.prototype.show_message = function(message) { - window.noty({ - text: message, - theme: "ffzTheme", - layout: "bottomCenter", - closeWith: ["button"] - }).show(); -} -},{}],31:[function(require,module,exports){ -var FFZ = window.FrankerFaceZ, - utils = require('../utils'); - - -// --------------- -// Initialization -// --------------- - -FFZ.prototype.setup_races = function() { - this.log("Initializing race support."); - this.srl_races = {}; -} - - -// --------------- -// Settings -// --------------- - -FFZ.settings_info.srl_races = { - type: "boolean", - value: true, - no_mobile: true, - - category: "Channel Metadata", - name: "SRL Race Information", - help: 'Display information about SpeedRunsLive races under channels.', - on_update: function(val) { - this.rebuild_race_ui(); - } - }; - - -// --------------- -// Socket Handler -// --------------- - -FFZ.ws_on_close.push(function() { - var controller = window.App && App.__container__.lookup('controller:channel'), - current_id = controller && controller.get('id'), - current_host = controller && controller.get('hostModeTarget.id'), - need_update = false; - - if ( ! controller ) - return; - - for(var chan in this.srl_races) { - delete this.srl_races[chan]; - if ( chan === current_id || chan === current_host ) - need_update = true; - } - - if ( need_update ) - this.rebuild_race_ui(); -}); - - -FFZ.ws_commands.srl_race = function(data) { - var controller = App.__container__.lookup('controller:channel'), - current_id = controller && controller.get('id'), - current_host = controller && controller.get('hostModeTarget.id'), - need_update = false; - - this.srl_races = this.srl_races || {}; - - for(var i=0; i < data[0].length; i++) { - var channel_id = data[0][i]; - this.srl_races[channel_id] = data[1]; - if ( channel_id === current_id || channel_id === current_host ) - need_update = true; - } - - if ( data[1] ) { - var race = data[1], - tte = race.twitch_entrants = {}; - - for(var ent in race.entrants) { - if ( ! race.entrants.hasOwnProperty(ent) ) continue; - if ( race.entrants[ent].channel ) - tte[race.entrants[ent].channel] = ent; - race.entrants[ent].name = ent; - } - } - - if ( need_update ) - this.rebuild_race_ui(); -} - - -// --------------- -// Race UI -// --------------- - -FFZ.prototype.rebuild_race_ui = function() { - var controller = App.__container__.lookup('controller:channel'), - channel_id = controller && controller.get('id'), - hosted_id = controller && controller.get('hostModeTarget.id'); - - if ( ! this._cindex ) - return; - - if ( channel_id ) { - var race = this.srl_races && this.srl_races[channel_id], - - el = this._cindex.get('element'), - container = el && el.querySelector('.stats-and-actions .channel-actions'), - race_container = container && container.querySelector('#ffz-ui-race'); - - if ( ! container || ! this.settings.srl_races || ! race ) { - if ( race_container ) - race_container.parentElement.removeChild(race_container); - - } else { - if ( ! race_container ) { - race_container = document.createElement('span'); - race_container.id = 'ffz-ui-race'; - race_container.setAttribute('data-channel', channel_id); - - var btn = document.createElement('span'); - btn.className = 'button drop action'; - btn.title = "SpeedRunsLive Race"; - btn.innerHTML = ''; - - btn.addEventListener('click', this._build_race_popup.bind(this, race_container, channel_id)); - - race_container.appendChild(btn); - container.appendChild(race_container); - } - - this._update_race(race_container, true); - } - } - - if ( hosted_id ) { - var race = this.srl_races && this.srl_races[hosted_id], - - el = this._cindex.get('element'), - container = el && el.querySelector('#hostmode .channel-actions'), - race_container = container && container.querySelector('#ffz-ui-race'); - - if ( ! container || ! this.settings.srl_races || ! race ) { - if ( race_container ) - race_container.parentElement.removeChild(race_container); - - } else { - if ( ! race_container ) { - race_container = document.createElement('span'); - race_container.id = 'ffz-ui-race'; - race_container.setAttribute('data-channel', hosted_id); - - var btn = document.createElement('span'); - btn.className = 'button drop action'; - btn.title = "SpeedRunsLive Race"; - btn.innerHTML = ''; - - btn.addEventListener('click', this._build_race_popup.bind(this, race_container, hosted_id)); - - race_container.appendChild(btn); - container.appendChild(race_container); - } - - this._update_race(race_container, true); - } - } -} - - -// --------------- -// Race Popup -// --------------- - -FFZ.prototype._race_kill = function() { - if ( this._race_timer ) { - clearTimeout(this._race_timer); - delete this._race_timer; - } - - delete this._race_game; - delete this._race_goal; -} - - -FFZ.prototype._build_race_popup = function(container, channel_id) { - var popup = this._popup; - if ( popup ) { - popup.parentElement.removeChild(popup); - delete this._popup; - this._popup_kill && this._popup_kill(); - delete this._popup_kill; - - if ( popup.id === "ffz-race-popup" && popup.getAttribute('data-channel') === channel_id ) - return; - } - - if ( ! container ) - return; - - var el = container.querySelector('.button'), - pos = el.offsetLeft + el.offsetWidth, - race = this.srl_races[channel_id]; - - var popup = document.createElement('div'), out = ''; - popup.id = 'ffz-race-popup'; - popup.setAttribute('data-channel', channel_id); - popup.className = (pos >= 300 ? 'right' : 'left') + ' share dropmenu'; - - this._popup_kill = this._race_kill.bind(this); - this._popup = popup; - - var link = 'http://kadgar.net/live', - has_entrant = false; - for(var ent in race.entrants) { - var state = race.entrants[ent].state; - if ( race.entrants.hasOwnProperty(ent) && race.entrants[ent].channel && (state == "racing" || state == "entered") ) { - link += "/" + race.entrants[ent].channel; - has_entrant = true; - } - } - - var height = document.querySelector('.app-main.theatre') ? document.body.clientHeight - 300 : container.parentElement.offsetTop - 175, - controller = App.__container__.lookup('controller:channel'), - display_name = controller ? controller.get('display_name') : FFZ.get_capitalization(channel_id), - tweet = encodeURIComponent("I'm watching " + display_name + " race " + race.goal + " in " + race.game + " on SpeedRunsLive!"); - - out = '
'; - out += '
Developers
Dan Salvato  
Stendec  
Version ' + FFZ.version_info + 'Logs
'; - out += '
#Entrant Time
'; - out += '
'; - - out += ''; - - out += '

SRL'; - - if ( has_entrant ) - out += '   Multitwitch'; - - out += '

'; - popup.innerHTML = out; - container.appendChild(popup); - - this._update_race(container, true); -} - - -FFZ.prototype._update_race = function(container, not_timer) { - if ( this._race_timer && not_timer ) { - clearTimeout(this._race_timer); - delete this._race_timer; - } - - if ( ! container ) - return; - - var channel_id = container.getAttribute('data-channel'), - race = this.srl_races[channel_id]; - - if ( ! race ) { - // No race. Abort. - container.parentElement.removeChild(container); - if ( this._popup && this._popup.id === 'ffz-race-popup' && this._popup.getAttribute('data-channel') === channel_id ) { - this._popup_kill && this._popup_kill(); - if ( this._popup ) { - delete this._popup; - delete this._popup_kill; - } - } - return; - } - - var entrant_id = race.twitch_entrants[channel_id], - entrant = race.entrants[entrant_id], - - popup = container.querySelector('#ffz-race-popup'), - now = Date.now() / 1000, - elapsed = Math.floor(now - race.time); - - container.querySelector('.logo').innerHTML = utils.placement(entrant); - - if ( popup ) { - var tbody = popup.querySelector('tbody'), - timer = popup.querySelector('.heading span'), - info = popup.querySelector('.heading div'); - - tbody.innerHTML = ''; - var entrants = [], done = true; - for(var ent in race.entrants) { - if ( ! race.entrants.hasOwnProperty(ent) ) continue; - if ( race.entrants[ent].state == "racing" ) - done = false; - entrants.push(race.entrants[ent]); - } - - entrants.sort(function(a,b) { - var a_place = a.place || 9999, - b_place = b.place || 9999, - - a_time = a.time || elapsed, - b_time = b.time || elapsed; - - if ( a.state == "forfeit" || a.state == "dq" ) - a_place = 10000; - - if ( b.state == "forfeit" || b.state == "dq" ) - b_place = 10000; - - if ( a_place < b_place ) return -1; - else if ( a_place > b_place ) return 1; - - else if ( a.name < b.name ) return -1; - else if ( a.name > b.name ) return 1; - - else if ( a_time < b_time ) return -1; - else if ( a_time > b_time ) return 1; - }); - - for(var i=0; i < entrants.length; i++) { - var ent = entrants[i], - name = '' + ent.display_name + '', - twitch_link = ent.channel ? '' : '', - hitbox_link = ent.hitbox ? '' : '', - time = elapsed ? utils.time_to_string(ent.time||elapsed) : "", - place = utils.place_string(ent.place), - comment = ent.comment ? utils.sanitize(ent.comment) : ""; - - tbody.innerHTML += '' + place + '' + name + '' + twitch_link + hitbox_link + '' + (ent.state == "forfeit" ? "Forfeit" : time) + ''; - } - - if ( this._race_game != race.game || this._race_goal != race.goal ) { - this._race_game = race.game; - this._race_goal = race.goal; - - var game = utils.sanitize(race.game), - goal = utils.sanitize(race.goal); - - info.innerHTML = '

' + game + "

Goal: " + goal; - } - - if ( ! elapsed ) - timer.innerHTML = "Entry Open"; - else if ( done ) - timer.innerHTML = "Done"; - else { - timer.innerHTML = utils.time_to_string(elapsed); - this._race_timer = setTimeout(this._update_race.bind(this, container), 1000); - } - } -} -},{"../utils":35}],32:[function(require,module,exports){ -var FFZ = window.FrankerFaceZ, - constants = require('../constants'); - -FFZ.prototype.setup_css = function() { - this.log("Injecting main FrankerFaceZ CSS."); - - var s = this._main_style = document.createElement('link'); - - s.id = "ffz-ui-css"; - s.setAttribute('rel', 'stylesheet'); - s.setAttribute('href', constants.SERVER + "script/style.css?_=" + (constants.DEBUG ? Date.now() : FFZ.version_info)); - document.head.appendChild(s); - - jQuery.noty.themes.ffzTheme = { - name: "ffzTheme", - style: function() { - this.$bar.removeClass().addClass("noty_bar").addClass("ffz-noty").addClass(this.options.type); - }, - callback: { - onShow: function() {}, - onClose: function() {} - } - }; -} -},{"../constants":5}],33:[function(require,module,exports){ -var FFZ = window.FrankerFaceZ, - constants = require('../constants'), - utils = require('../utils'); - - -// ------------------- -// Subscriber Display -// ------------------- - -FFZ.prototype._update_subscribers = function() { - if ( this._update_subscribers_timer ) { - clearTimeout(this._update_subscribers_timer); - delete this._update_subscribers_timer; - } - - // Schedule an update. - this._update_subscribers_timer = setTimeout(this._update_subscribers.bind(this), 60000); - - var user = this.get_user(), f = this, - match = this.is_dashboard ? location.pathname.match(/\/([^\/]+)/) : undefined, - id = this.is_dashboard && match && match[1]; - - if ( this.has_bttv || ! id || id !== user.login ) { - var el = document.querySelector("#ffz-sub-display"); - if ( el ) - el.parentElement.removeChild(el); - return; - } - - // Spend a moment wishing we could just hit the subscribers API from the - // context of the web user. - - // Get the count! - jQuery.ajax({url: "/broadcast/dashboard/partnership"}).done(function(data) { - try { - var html = document.createElement('span'), dash; - - html.innerHTML = data; - dash = html.querySelector("#dash_main"); - - var match = dash && dash.textContent.match(/([\d,\.]+) total active subscribers/), - sub_count = match && match[1]; - - if ( ! sub_count ) { - var el = document.querySelector("#ffz-sub-display"); - if ( el ) - el.parentElement.removeChild(el); - - if ( f._update_subscribers_timer ) { - clearTimeout(f._update_subscribers_timer); - delete f._update_subscribers_timer; - } - - return; - } - - var el = document.querySelector('#ffz-sub-display span'); - if ( ! el ) { - var cont = f.is_dashboard ? document.querySelector("#stats") : document.querySelector("#channel .stats-and-actions .channel-stats"); - if ( ! cont ) - return; - - var stat = document.createElement('span'); - stat.className = 'ffz stat'; - stat.id = 'ffz-sub-display'; - stat.title = 'Active Channel Subscribers'; - - stat.innerHTML = constants.STAR + ' '; - - el = document.createElement('span'); - stat.appendChild(el); - - Twitch.api.get("chat/" + id + "/badges", null, {version: 3}) - .done(function(data) { - if ( data.subscriber && data.subscriber.image ) { - stat.innerHTML = ''; - stat.appendChild(el); - - stat.style.backgroundImage = 'url("' + data.subscriber.image + '")'; - stat.style.backgroundRepeat = 'no-repeat'; - stat.style.paddingLeft = '23px'; - stat.style.backgroundPosition = '0 50%'; - } - }); - - cont.appendChild(stat); - jQuery(stat).tipsy(f.is_dashboard ? {"gravity":"s"} : undefined); - } - - el.innerHTML = sub_count; - - } catch(err) { - f.error("_update_subscribers: " + err); - } - }).fail(function(){ - var el = document.querySelector("#ffz-sub-display"); - if ( el ) - el.parentElement.removeChild(el); - return; - });; -} - -},{"../constants":5,"../utils":35}],34:[function(require,module,exports){ -var FFZ = window.FrankerFaceZ, - constants = require('../constants'), - utils = require('../utils'); - - -// ------------ -// FFZ Viewers -// ------------ - -FFZ.ws_commands.chatters = function(data) { - var channel = data[0], count = data[1]; - - var controller = window.App && App.__container__.lookup('controller:channel'), - match = this.is_dashboard ? location.pathname.match(/\/([^\/]+)/) : undefined, - id = this.is_dashboard ? match && match[1] : controller && controller.get && controller.get('id'); - - if ( ! this.is_dashboard ) { - var room = this.rooms && this.rooms[channel]; - if ( room ) { - room.ffz_chatters = count; - if ( this._cindex ) - this._cindex.ffzUpdateChatters(); - } - return; - } - - this._dash_chatters = count; -} - -FFZ.ws_commands.viewers = function(data) { - var channel = data[0], count = data[1]; - - var controller = window.App && App.__container__.lookup('controller:channel'), - match = this.is_dashboard ? location.pathname.match(/\/([^\/]+)/) : undefined, - id = this.is_dashboard ? match && match[1] : controller && controller.get && controller.get('id'); - - if ( ! this.is_dashboard ) { - var room = this.rooms && this.rooms[channel]; - if ( room ) { - room.ffz_viewers = count; - if ( this._cindex ) - this._cindex.ffzUpdateChatters(); - } - return; - } - - this._dash_viewers = count; - - if ( ! this.settings.chatter_count || id !== channel ) - return; - - var view_count = document.querySelector('#ffz-ffzchatter-display'), - content = constants.ZREKNARF + ' ' + utils.number_commas(count) + (typeof this._dash_chatters === "number" ? ' (' + utils.number_commas(this._dash_chatters) + ')' : ""); - - if ( view_count ) - view_count.innerHTML = content; - else { - var parent = document.querySelector("#stats"); - if ( ! parent ) - return; - - view_count = document.createElement('span'); - view_count.id = "ffz-ffzchatter-display"; - view_count.className = 'ffz stat'; - view_count.title = 'Viewers (In Chat) with FrankerFaceZ'; - view_count.innerHTML = content; - - parent.appendChild(view_count); - jQuery(view_count).tipsy(this.is_dashboard ? {"gravity":"s"} : undefined); - } -} -},{"../constants":5,"../utils":35}],35:[function(require,module,exports){ -var FFZ = window.FrankerFaceZ, - constants = require('./constants'); - - -var sanitize_el = document.createElement('span'), - - sanitize = function(msg) { - sanitize_el.textContent = msg; - return sanitize_el.innerHTML; - }, - - R_QUOTE = /"/g, - R_SQUOTE = /'/g, - R_AMP = /&/g, - R_LT = //g, - - quote_attr = function(msg) { - return msg.replace(R_AMP, "&").replace(R_QUOTE, """).replace(R_SQUOTE, "'").replace(R_LT, "<").replace(R_GT, ">"); - }, - - pluralize = function(value, singular, plural) { - plural = plural || 's'; - singular = singular || ''; - return value === 1 ? singular : plural; - }, - - place_string = function(num) { - if ( num == 1 ) return '1st'; - else if ( num == 2 ) return '2nd'; - else if ( num == 3 ) return '3rd'; - else if ( num == null ) return '---'; - return num + "th"; - }, - - date_regex = /^(\d{4}|\+\d{6})(?:-?(\d{2})(?:-?(\d{2})(?:T(\d{2})(?::?(\d{2})(?::?(\d{2})(?:(?:\.|,)(\d{1,}))?)?)?(Z|([\-+])(\d{2})(?::?(\d{2}))?)?)?)?)?$/, - - parse_date = function(str) { - var parts = str.match(date_regex); - if ( ! parts ) - return null; - - parts[7] = (parts[7] && parts[7].length) ? parts[7].substr(0, 3) : 0; - - var unix = Date.UTC(parts[1], parts[2] - 1, parts[3], parts[4], parts[5], parts[6], parts[7]); - - // Check Offset - if ( parts[9] ) { - var offset = (parts[9] == "-" ? 1 : -1) * 60000 * (60*parts[10] + 1*parts[11]); - unix += offset; - } - - return new Date(unix); - }, - - - // IRC Messages - splitIRCMessage = function(msgString) { - msgString = $.trim(msgString); - var split = {raw: msgString}; - - var tagsEnd = -1; - if ( msgString.charAt(0) === '@' ) { - tagsEnd = msgString.indexOf(' '); - split.tags = msgString.substr(1, tagsEnd - 1); - } - - var prefixStart = tagsEnd + 1, - prefixEnd = -1; - - if ( msgString.charAt(prefixStart) === ':' ) { - prefixEnd = msgString.indexOf(' ', prefixStart); - split.prefix = msgString.substr(prefixStart + 1, prefixEnd - (prefixStart + 1)); - } - - var trailingStart = msgString.indexOf(' :', prefixStart); - if ( trailingStart >= 0 ) { - split.trailing = msgString.substr(trailingStart + 2); - } else { - trailingStart = msgString.length; - } - - var commandAndParams = msgString.substr(prefixEnd + 1, trailingStart - prefixEnd - 1).split(' '); - split.command = commandAndParams[0]; - if ( commandAndParams.length > 1 ) - split.params = commandAndParams.slice(1); - - return split; - }, - - - ESCAPE_CHARS = { - ':': ';', - s: ' ', - r: '\r', - n: '\n', - '\\': '\\' - }, - - unescapeTag = function(tag) { - var result = ''; - for(var i=0; i < tag.length; i++) { - var c = tag.charAt(i); - if ( c === '\\' ) { - if ( i === tag.length - 1 ) - throw 'Improperly escaped tag'; - - c = ESCAPE_CHARS[tag.charAt(i+1)]; - if ( c === undefined ) - throw 'Improperly escaped tag'; - - i++; - } - result += c; - } - - return result; - }, - - parseTag = function(tag, value) { - switch(tag) { - case 'slow': - try { - return parseInt(value); - } catch(err) { return 0; } - case 'subs-only': - case 'r9k': - case 'subscriber': - case 'turbo': - return value === '1'; - default: - try { - return unescapeTag(value); - } catch(err) { return ''; } - } - }, - - parseIRCTags = function(tagsString) { - var tags = {}, - keyValues = tagsString.split(';'); - - for(var i=0; i < keyValues.length; ++i) { - var kv = keyValues[i].split('='); - if ( kv.length === 2 ) - tags[kv[0]] = parseTag(kv[0], kv[1]); - } - - return tags; - }, - - - EMOJI_CODEPOINTS = {}, - emoji_to_codepoint = function(icon, variant) { - if ( EMOJI_CODEPOINTS[icon] && EMOJI_CODEPOINTS[icon][variant] ) - return EMOJI_CODEPOINTS[icon][variant]; - - var ico = variant === '\uFE0F' ? icon.slice(0, -1) : (icon.length === 3 && icon.charAt(1) === '\uFE0F' ? icon.charAt(0) + icon.charAt(2) : icon), - r = [], c = 0, p = 0, i = 0; - - while ( i < ico.length ) { - c = ico.charCodeAt(i++); - if ( p ) { - r.push((0x10000 + ((p - 0xD800) << 10) + (c - 0xDC00)).toString(16)); - p = 0; - } else if ( 0xD800 <= c && c <= 0xDBFF) { - p = c; - } else { - r.push(c.toString(16)); - } - } - - var es = EMOJI_CODEPOINTS[icon] = EMOJI_CODEPOINTS[icon] || {}, - out = es[variant] = r.join("-"); - - return out; - }; - - -module.exports = { - update_css: function(element, id, css) { - var all = element.innerHTML, - start = "/*BEGIN " + id + "*/", - end = "/*END " + id + "*/", - s_ind = all.indexOf(start), - e_ind = all.indexOf(end), - found = s_ind !== -1 && e_ind !== -1 && e_ind > s_ind; - - if ( !found && !css ) - return; - - if ( found ) - all = all.substr(0, s_ind) + all.substr(e_ind + end.length); - - if ( css ) - all += start + css + end; - - element.innerHTML = all; - }, - - - splitIRCMessage: splitIRCMessage, - parseIRCTags: parseIRCTags, - - emoji_to_codepoint: emoji_to_codepoint, - - parse_date: parse_date, - - number_commas: function(x) { - var parts = x.toString().split("."); - parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ","); - return parts.join("."); - }, - - place_string: place_string, - - placement: function(entrant) { - if ( entrant.state == "forfeit" ) return "Forfeit"; - else if ( entrant.state == "dq" ) return "DQed"; - else if ( entrant.place ) return place_string(entrant.place); - return ""; - }, - - sanitize: sanitize, - quote_attr: quote_attr, - - date_string: function(date) { - return date.getFullYear() + "-" + (date.getMonth()+1) + "-" + date.getDate(); - }, - - pluralize: pluralize, - - human_time: function(elapsed, factor) { - factor = factor || 1; - elapsed = Math.floor(elapsed); - - var years = Math.floor((elapsed*factor) / 31536000) / factor; - if ( years >= 1 ) - return years + ' year' + pluralize(years); - - var days = Math.floor((elapsed %= 31536000) / 86400); - if ( days >= 1 ) - return days + ' day' + pluralize(days); - - var hours = Math.floor((elapsed %= 86400) / 3600); - if ( hours >= 1 ) - return hours + ' hour' + pluralize(hours); - - var minutes = Math.floor((elapsed %= 3600) / 60); - if ( minutes >= 1 ) - return minutes + ' minute' + pluralize(minutes); - - var seconds = elapsed % 60; - if ( seconds >= 1 ) - return seconds + ' second' + pluralize(seconds); - - return 'less than a second'; - }, - - time_to_string: function(elapsed, separate_days, days_only, no_hours) { - var seconds = elapsed % 60, - minutes = Math.floor(elapsed / 60), - hours = Math.floor(minutes / 60), - days = ""; - - minutes = minutes % 60; - - if ( separate_days ) { - days = Math.floor(hours / 24); - hours = hours % 24; - if ( days_only && days > 0 ) - return days + " days"; - - days = ( days > 0 ) ? days + " days, " : ""; - } - - return days + ((!no_hours || days || hours) ? ((hours < 10 ? "0" : "") + hours + ':') : '') + (minutes < 10 ? "0" : "") + minutes + ":" + (seconds < 10 ? "0" : "") + seconds; - }, - - format_unread: function(count) { - if ( count < 1 ) - return ""; - - else if ( count >= 99 ) - return "99+"; - - return "" + count; - } -} -},{"./constants":5}]},{},[18]);window.ffz = new FrankerFaceZ()}(window)); \ No newline at end of file diff --git a/script.min.js b/script.min.js deleted file mode 100644 index fc78c7c9..00000000 --- a/script.min.js +++ /dev/null @@ -1,9 +0,0 @@ -!function(t){!function e(t,s,n){function i(a,r){if(!s[a]){if(!t[a]){var d="function"==typeof require&&require;if(!r&&d)return d(a,!0);if(o)return o(a,!0);throw new Error("Cannot find module '"+a+"'")}var u=s[a]={exports:{}};t[a][0].call(u.exports,function(e){var s=t[a][1][e];return i(s?s:e)},u,u.exports,e,t,s,n)}return s[a].exports}for(var o="function"==typeof require&&require,a=0;at&&this._legacy_load_bots(t))})},i.prototype._legacy_load_donors=function(t){jQuery.ajax(o.SERVER+"script/donors.txt",{cache:!1,context:this}).done(function(t){this._legacy_parse_badges(t,1,1)}).fail(function(e){return 404!=e.status?(t=(t||0)+1,10>t?this._legacy_load_donors(t):void 0):void 0})},i.prototype._legacy_parse_badges=function(t,e,s,n){var i=this.badges[s].title,o=0;if(ds=null,n=n||"{}",null!=t)for(var r=t.trim().split(/\W+/),d=0;d1&&(h[e].title=n.replace("{}",u[1])),o+=1)}this.log('Added "'+i+'" badge to '+a.number_commas(o)+" users.")}},{"./constants":5,"./utils":35}],3:[function(e,s,n){var i=t.FrankerFaceZ,o=function(t,e,s){return 0>s&&(s+=1),s>1&&(s-=1),1/6>s?t+6*(e-t)*s:.5>s?e:2/3>s?t+(e-t)*(2/3-s)*6:t};i.settings_info.fix_color={type:"select",options:{"-1":"Disabled",0:"Default Colors",1:"Luv Adjustment",2:"HSL Adjustment (Depreciated)",3:"HSV Adjustment (Depreciated)",4:"RGB Adjustment (Depreciated)"},value:"1",category:"Chat Appearance",no_bttv:!0,name:"Username Colors - Brightness",help:"Ensure that username colors contrast with the background enough to be readable.",process_value:function(t){return t===!1?"0":t===!0?"1":t},on_update:function(t){document.body.classList.toggle("ffz-chat-colors-gray",!this.has_bttv&&"-1"===t),this.has_bttv||"-1"===t||this._rebuild_colors()}},i.settings_info.luv_contrast={type:"button",value:4.5,category:"Chat Appearance",no_bttv:!0,name:"Username Colors - Luv Minimum Contrast",help:"Set the minimum contrast ratio used by Luv Adjustment to ensure colors are readable.",method:function(){var t=this.settings.luv_contrast,e=prompt("Luv Adjustment Minimum Contrast Ratio\n\nPlease enter a new value for the minimum contrast ratio required between username colors and the background. The default is: 4.5",t);if(null!==e&&void 0!==e){var s=parseFloat(e);(s===0/0||1>s)&&(s=4.5),this.settings.set("luv_contrast",s)}},on_update:function(t){this._rebuild_contrast(),this.has_bttv||"1"!=this.settings.fix_color||this._rebuild_colors()}},i.settings_info.color_blind={type:"select",options:{0:"Disabled",protanope:"Protanope",deuteranope:"Deuteranope",tritanope:"Tritanope"},value:"0",category:"Chat Appearance",no_bttv:!0,name:"Username Colors - Color Blindness",help:"Adjust username colors in an attempt to make them more distinct for people with color blindness.",on_update:function(t){this.has_bttv||"-1"===this.settings.fix_color||this._rebuild_colors()}},i.prototype.setup_colors=function(){this._colors={},this._rebuild_contrast(),this._update_colors();var e=t.App&&App.__container__.lookup("controller:layout"),s=t.App&&App.__container__.lookup("controller:settings");e&&e.addObserver("isTheatreMode",this._update_colors.bind(this,!0)),s&&s.addObserver("model.darkMode",this._update_colors.bind(this,!0)),this._color_old_darkness=e&&e.get("isTheatreMode")||s&&s.get("model.darkMode")},i.Color={},i.Color.CVDMatrix={protanope:[0,2.02344,-2.52581,0,1,0,0,0,1],deuteranope:[1,0,0,.494207,0,1.24827,0,0,1],tritanope:[1,0,0,0,1,0,-.395913,.801109,0]};var a=i.Color.RGB=function(t,e,s){this.r=t||0,this.g=e||0,this.b=s||0},r=i.Color.HSV=function(t,e,s){this.h=t||0,this.s=e||0,this.v=s||0},d=i.Color.HSL=function(t,e,s){this.h=t||0,this.s=e||0,this.l=s||0},u=i.Color.XYZ=function(t,e,s){this.x=t||0,this.y=e||0,this.z=s||0},c=i.Color.LUV=function(t,e,s){this.l=t||0,this.u=e||0,this.v=s||0};a.prototype.eq=function(t){return t.r===this.r&&t.g===this.g&&t.b===this.b},a.fromHex=function(t){var e=parseInt("#"===t.charAt(0)?t.substr(1):t,16);return new a(e>>16,e>>8&255,255&e)},a.fromHSV=function(t,e,s){var n,i,o,r=Math.floor(6*t),d=6*t-r,u=s*(1-e),c=s*(1-d*e),l=s*(1-(1-d)*e);switch(r%6){case 0:n=s,i=l,o=u;break;case 1:n=c,i=s,o=u;break;case 2:n=u,i=s,o=l;break;case 3:n=u,i=c,o=s;break;case 4:n=l,i=u,o=s;break;case 5:n=s,i=u,o=c}return new a(Math.round(Math.min(Math.max(0,255*n),255)),Math.round(Math.min(Math.max(0,255*i),255)),Math.round(Math.min(Math.max(0,255*o),255)))},a.fromXYZ=function(t,e,s){var n=3.240479*t-1.53715*e-.498535*s,i=-.969256*t+1.875992*e+.041556*s,o=.055648*t-.204043*e+1.057311*s;return new a(Math.max(0,Math.min(255,255*u.channelConverter(n))),Math.max(0,Math.min(255,255*u.channelConverter(i))),Math.max(0,Math.min(255,255*u.channelConverter(o))))},a.fromHSL=function(t,e,s){if(0===e){var n=Math.round(Math.min(Math.max(0,255*s),255));return new a(n,n,n)}var i=.5>s?s*(1+e):s+e-s*e,r=2*s-i;return new a(Math.round(Math.min(Math.max(0,255*o(r,i,t+1/3)),255)),Math.round(Math.min(Math.max(0,255*o(r,i,t)),255)),Math.round(Math.min(Math.max(0,255*o(r,i,t-1/3)),255)))},a.prototype.toHSV=function(){return r.fromRGB(this.r,this.g,this.b)},a.prototype.toHSL=function(){return d.fromRGB(this.r,this.g,this.b)},a.prototype.toCSS=function(){return"rgb("+Math.round(this.r)+","+Math.round(this.g)+","+Math.round(this.b)+")"},a.prototype.toXYZ=function(){return u.fromRGB(this.r,this.g,this.b)},a.prototype.toLUV=function(){return this.toXYZ().toLUV()},a.prototype.toHex=function(){var t=this.b|this.g<<8|this.r<<16;return"#"+(16777216+t).toString(16).slice(1)},a.prototype.luminance=function(){for(var t=[this.r/255,this.g/255,this.b/255],e=0,s=t.length;s>e;e++)t[e]=t[e]<=.03928?t[e]/12.92:Math.pow((t[e]+.055)/1.055,2.4);return.2126*t[0]+.7152*t[1]+.0722*t[2]},a.prototype.brighten=function(t){return t="number"==typeof t?t:1,t=Math.round(255*(t/100)),new a(Math.max(0,Math.min(255,this.r+t)),Math.max(0,Math.min(255,this.g+t)),Math.max(0,Math.min(255,this.b+t)))},a.prototype.daltonize=function(t,e){e="number"==typeof e?e:1;var s;if("string"==typeof t){if(!i.Color.CVDMatrix.hasOwnProperty(t))throw"Invalid CVD matrix.";s=i.Color.CVDMatrix[t]}else s=t;var n,o,r,d,u,c,l,h,f,m,_,p,g=s[0],v=s[1],b=s[2],y=s[3],w=s[4],z=s[5],k=s[6],C=s[7],E=s[8];return n=17.8824*this.r+43.5161*this.g+4.11935*this.b,o=3.45565*this.r+27.1554*this.g+3.86714*this.b,r=.0299566*this.r+.184309*this.g+1.46709*this.b,d=g*n+v*o+b*r,u=y*n+w*o+z*r,c=k*n+C*o+E*r,l=.0809444479*d+-.130504409*u+.116721066*c,h=-.0102485335*d+.0540193266*u+-.113614708*c,f=-.000365296938*d+-.00412161469*u+.693511405*c,l=this.r-l,h=this.g-h,f=this.b-f,m=0*l+0*h+0*f,_=.7*l+1*h+0*f,p=.7*l+0*h+1*f,l=Math.min(Math.max(0,m+this.r),255),h=Math.min(Math.max(0,_+this.g),255),f=Math.min(Math.max(0,p+this.b),255),new a(l,h,f)},a.prototype._r=function(t){return new a(t,this.g,this.b)},a.prototype._g=function(t){return new a(this.r,t,this.b)},a.prototype._b=function(t){return new a(this.r,this.g,t)},d.prototype.eq=function(t){return t.h===this.h&&t.s===this.s&&t.l===this.l},d.fromRGB=function(t,e,s){t/=255,e/=255,s/=255;var n,i,o=Math.max(t,e,s),a=Math.min(t,e,s),r=Math.min(Math.max(0,(o+a)/2),1),u=Math.min(Math.max(0,o-a),1);if(0===u)n=i=0;else{switch(i=r>.5?u/(2-o-a):u/(o+a),o){case t:n=(e-s)/u+(s>e?6:0);break;case e:n=(s-t)/u+2;break;case s:n=(t-e)/u+4}n/=6}return new d(n,i,r)},d.prototype.toRGB=function(){return a.fromHSL(this.h,this.s,this.l)},d.prototype.toCSS=function(){return"hsl("+Math.round(360*this.h)+","+Math.round(100*this.s)+"%,"+Math.round(100*this.l)+"%)"},d.prototype.toHex=function(){return a.fromHSL(this.h,this.s,this.l).toHex()},d.prototype.toHSV=function(){return a.fromHSL(this.h,this.s,this.l).toHSV()},d.prototype.toXYZ=function(){return a.fromHSL(this.h,this.s,this.l).toXYZ()},d.prototype.toLUV=function(){return a.fromHSL(this.h,this.s,this.l).toLUV()},d.prototype._h=function(t){return new d(t,this.s,this.l)},d.prototype._s=function(t){return new d(this.h,t,this.l)},d.prototype._l=function(t){return new d(this.h,this.s,t)},r.prototype.eq=function(t){return t.h===this.h&&t.s===this.s&&t.v===this.v},r.fromRGB=function(t,e,s){t/=255,e/=255,s/=255;var n,i=Math.max(t,e,s),o=Math.min(t,e,s),a=Math.min(Math.max(0,i-o),1),d=0===i?0:a/i,u=i;if(0===a)n=0;else{switch(i){case t:n=(e-s)/a+(s>e?6:0);break;case e:n=(s-t)/a+2;break;case s:n=(t-e)/a+4}n/=6}return new r(n,d,u)},r.prototype.toRGB=function(){return a.fromHSV(this.h,this.s,this.v)},r.prototype.toHSL=function(){return a.fromHSV(this.h,this.s,this.v).toHSL()},r.prototype.toXYZ=function(){return a.fromHSV(this.h,this.s,this.v).toXYZ()},r.prototype.toLUV=function(){return a.fromHSV(this.h,this.s,this.v).toLUV()},r.prototype._h=function(t){return new r(t,this.s,this.v)},r.prototype._s=function(t){return new r(this.h,t,this.v)},r.prototype._v=function(t){return new r(this.h,this.s,t)},a.channelConverter=function(t){return Math.pow(t,2.2)},u.channelConverter=function(t){return Math.pow(t,1/2.2)},u.prototype.eq=function(t){return t.x===this.x&&t.y===this.y&&t.z===this.z},u.fromRGB=function(t,e,s){var n=a.channelConverter(t/255),i=a.channelConverter(e/255),o=a.channelConverter(s/255);return new u(.412453*n+.35758*i+.180423*o,.212671*n+.71516*i+.072169*o,.019334*n+.119193*i+.950227*o)},u.fromLUV=function(t,e,s){var n=1/(u.WHITE.x+15*u.WHITE.y+3*u.WHITE.z),i=4*u.WHITE.x*n,o=9*u.WHITE.y*n,a=t>8?Math.pow((t+16)/116,3):t/u.KAPPA,r=1/3*(52*t/(e+13*t*i)-1),d=-5*a,c=-1/3,l=a*(39*t/(s+13*t*o)-5),h=(l-d)/(r-c),f=h*r+d;return new u(h,a,f)},u.prototype.toRGB=function(){return a.fromXYZ(this.x,this.y,this.z)},u.prototype.toLUV=function(){return c.fromXYZ(this.x,this.y,this.z)},u.prototype.toHSL=function(){return a.fromXYZ(this.x,this.y,this.z).toHSL()},u.prototype.toHSV=function(){return a.fromXYZ(this.x,this.y,this.z).toHSV()},u.prototype._x=function(t){return new u(t,this.y,this.z)},u.prototype._y=function(t){return new u(this.x,t,this.z)},u.prototype._z=function(t){return new u(this.x,this.y,t)},u.EPSILON=Math.pow(6/29,3),u.KAPPA=Math.pow(29/3,3),u.WHITE=new a(255,255,255).toXYZ(),c.prototype.eq=function(t){return t.l===this.l&&t.u===this.u&&t.v===this.v},c.fromXYZ=function(t,e,s){var n=1/(u.WHITE.x+15*u.WHITE.y+3*u.WHITE.z),i=4*u.WHITE.x*n,o=9*u.WHITE.y*n,a=e/u.WHITE.y,r=t+15*e+3*s;0===r&&(r=1);var d=1/r,l=4*t*d,h=9*e*d,f=a>u.EPSILON?116*Math.pow(a,1/3)-16:u.KAPPA*a,m=13*f*(l-i),_=13*f*(h-o);return new c(f,m,_)},c.prototype.toXYZ=function(){return u.fromLUV(this.l,this.u,this.v)},c.prototype.toRGB=function(){return u.fromLUV(this.l,this.u,this.v).toRGB()},c.prototype.toHSL=function(){return u.fromLUV(this.l,this.u,this.v).toHSL()},c.prototype.toHSV=function(){return u.fromLUV(this.l,this.u,this.v).toHSV()},c.prototype._l=function(t){return new c(t,this.u,this.v)},c.prototype._u=function(t){return new c(this.l,t,this.v)},c.prototype._v=function(t){return new c(this.l,this.u,t)},i.prototype._rebuild_contrast=function(){this._luv_required_bright=new u(0,this.settings.luv_contrast*(new a(35,35,35).toXYZ().y+.05)-.05,0).toLUV().l,this._luv_required_dark=new u(0,(new a(217,217,217).toXYZ().y+.05)/this.settings.luv_contrast-.05,0).toLUV().l},i.prototype._rebuild_colors=function(){this.has_bttv||(this._colors={},this._update_colors())},i.prototype._update_colors=function(e){var s=t.App&&App.__container__.lookup("controller:layout"),n=t.App&&App.__container__.lookup("controller:settings"),i=s&&s.get("isTheatreMode")||n&&n.get("model.darkMode");if(!e||this._color_old_darkness!==i){this._color_old_darkness=i;for(var o=document.querySelectorAll(".chat-line .has-color"),a=0,r=o.length;r>a;a++){var d=o[a],u=d.getAttribute("data-color"),c=u&&this._handle_color(u);c&&(d.style.color=i?c[1]:c[0])}}},i.prototype._handle_color=function(t){if(!t||this._colors.hasOwnProperty(t))return this._colors[t];var e=a.fromHex(t),s=t,n=t;if("0"!==this.settings.color_blind){var i=e.daltonize(this.settings.color_blind);e.eq(i)||(e=i,s=n=e.toHex())}if("4"===this.settings.fix_color){var o=e.luminance();if(o>.3){for(var r=127,d=e;r--&&(d=d.brighten(-1),!(d.luminance()<=.3)););s=d.toHex()}if(.15>o){for(var r=127,d=e;r--&&(d=d.brighten(),!(d.luminance()>=.15)););n=d.toHex()}}if("2"===this.settings.fix_color){var u=e.toHSL();s=u._l(Math.min(Math.max(0,.7*u.l),1)).toHex(),n=u._l(Math.min(Math.max(0,.3+.7*u.l),1)).toHex()}if("3"===this.settings.fix_color){var c=e.toHSV();0===c.s?(s=c._v(Math.min(Math.max(.5,.5*c.v),1)).toRGB().toHex(),n=c._v(Math.min(Math.max(.5,.5+.5*c.v),1)).toRGB().toHex()):(s=a.fromHSV(c.h,Math.min(Math.max(.7,.7+.3*c.s),1),Math.min(.7,c.v)).toHex(),n=a.fromHSV(c.h,Math.min(.7,c.s),Math.min(Math.max(.7,.7+.3*c.v),1)).toHex())}if("1"===this.settings.fix_color){var l=e.toLUV();l.l>this._luv_required_dark&&(s=l._l(this._luv_required_dark).toRGB().toHex()),l.l50)return"Each user you unmod counts as a single message. To avoid being globally banned, please limit yourself to 50 at a time and wait between uses.";for(var n=e.length;e.length;){var i=e.shift();t.room.tmiRoom.sendMessage("/unmod "+i)}return"Sent unmod command for "+n+" users."},i.ffz_commands.massunmod.help="Usage: /ffz massunmod \nBroadcaster only. Unmod all the users in the provided list.",i.ffz_commands.massmod=function(t,e){if(e=e.join(" ").trim(),!e.length)return"You must provide a list of users to mod.";e=e.split(/\W*,\W*/);var s=this.get_user();if(!s||!s.login==t.id)return"You must be the broadcaster to use massmod.";if(e.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.";for(var n=e.length;e.length;){var i=e.shift();t.room.tmiRoom.sendMessage("/mod "+i)}return"Sent mod command for "+n+" users."},i.ffz_commands.massmod.help="Usage: /ffz massmod \nBroadcaster only. Mod all the users in the provided list."},{}],5:[function(t,e,s){var n='',i="true"==localStorage.ffzDebugMode&&document.body.classList.contains("ffz-dev"),o=i?"//localhost:8000/":"//cdn.frankerfacez.com/";e.exports={DEBUG:i,SERVER:o,API_SERVER:"//api.frankerfacez.com/",API_SERVER_2:"//direct-api.frankerfacez.com/",KNOWN_CODES:{"#-?[\\\\/]":"#-/",":-?(?:7|L)":":-7","\\<\\;\\]":"<]","\\:-?(S|s)":":-S","\\:-?\\\\":":-\\","\\:\\>\\;":":>","B-?\\)":"B-)","\\:-?[z|Z|\\|]":":-Z","\\:-?\\)":":-)","\\:-?\\(":":-(","\\:-?(p|P)":":-P","\\;-?(p|P)":";-P","\\<\\;3":"<3","\\:-?[\\\\/]":":-/","\\;-?\\)":";-)","R-?\\)":"R-)","[o|O](_|\\.)[o|O]":"O.o","\\:-?D":":-D","\\:-?(o|O)":":-O","\\>\\;\\(":">(","Gr(a|e)yFace":"GrayFace"},EMOTE_REPLACEMENT_BASE:o+"script/replacements/",EMOTE_REPLACEMENTS:{15:"15-JKanStyle.png",16:"16-OptimizePrime.png",17:"17-StoneLightning.png",18:"18-TheRinger.png",19:"19-PazPazowitz.png",20:"20-EagleEye.png",21:"21-CougarHunt.png",22:"22-RedCoat.png",26:"26-JonCarnage.png",27:"27-PicoMause.png",30:"30-BCWarrior.png",33:"33-DansGame.png",36:"36-PJSalt.png"},EMOJI_REGEX:/((?:\ud83c\udde8\ud83c\uddf3|\ud83c\uddfa\ud83c\uddf8|\ud83c\uddf7\ud83c\uddfa|\ud83c\uddf0\ud83c\uddf7|\ud83c\uddef\ud83c\uddf5|\ud83c\uddee\ud83c\uddf9|\ud83c\uddec\ud83c\udde7|\ud83c\uddeb\ud83c\uddf7|\ud83c\uddea\ud83c\uddf8|\ud83c\udde9\ud83c\uddea|\u0039\ufe0f?\u20e3|\u0038\ufe0f?\u20e3|\u0037\ufe0f?\u20e3|\u0036\ufe0f?\u20e3|\u0035\ufe0f?\u20e3|\u0034\ufe0f?\u20e3|\u0033\ufe0f?\u20e3|\u0032\ufe0f?\u20e3|\u0031\ufe0f?\u20e3|\u0030\ufe0f?\u20e3|\u0023\ufe0f?\u20e3|\ud83d\udeb3|\ud83d\udeb1|\ud83d\udeb0|\ud83d\udeaf|\ud83d\udeae|\ud83d\udea6|\ud83d\udea3|\ud83d\udea1|\ud83d\udea0|\ud83d\ude9f|\ud83d\ude9e|\ud83d\ude9d|\ud83d\ude9c|\ud83d\ude9b|\ud83d\ude98|\ud83d\ude96|\ud83d\ude94|\ud83d\ude90|\ud83d\ude8e|\ud83d\ude8d|\ud83d\ude8b|\ud83d\ude8a|\ud83d\ude88|\ud83d\ude86|\ud83d\ude82|\ud83d\ude81|\ud83d\ude36|\ud83d\ude34|\ud83d\ude2f|\ud83d\ude2e|\ud83d\ude2c|\ud83d\ude27|\ud83d\ude26|\ud83d\ude1f|\ud83d\ude1b|\ud83d\ude19|\ud83d\ude17|\ud83d\ude15|\ud83d\ude11|\ud83d\ude10|\ud83d\ude0e|\ud83d\ude08|\ud83d\ude07|\ud83d\ude00|\ud83d\udd67|\ud83d\udd66|\ud83d\udd65|\ud83d\udd64|\ud83d\udd63|\ud83d\udd62|\ud83d\udd61|\ud83d\udd60|\ud83d\udd5f|\ud83d\udd5e|\ud83d\udd5d|\ud83d\udd5c|\ud83d\udd2d|\ud83d\udd2c|\ud83d\udd15|\ud83d\udd09|\ud83d\udd08|\ud83d\udd07|\ud83d\udd06|\ud83d\udd05|\ud83d\udd04|\ud83d\udd02|\ud83d\udd01|\ud83d\udd00|\ud83d\udcf5|\ud83d\udcef|\ud83d\udced|\ud83d\udcec|\ud83d\udcb7|\ud83d\udcb6|\ud83d\udcad|\ud83d\udc6d|\ud83d\udc6c|\ud83d\udc65|\ud83d\udc2a|\ud83d\udc16|\ud83d\udc15|\ud83d\udc13|\ud83d\udc10|\ud83d\udc0f|\ud83d\udc0b|\ud83d\udc0a|\ud83d\udc09|\ud83d\udc08|\ud83d\udc07|\ud83d\udc06|\ud83d\udc05|\ud83d\udc04|\ud83d\udc03|\ud83d\udc02|\ud83d\udc01|\ud83d\udc00|\ud83c\udfe4|\ud83c\udfc9|\ud83c\udfc7|\ud83c\udf7c|\ud83c\udf50|\ud83c\udf4b|\ud83c\udf33|\ud83c\udf32|\ud83c\udf1e|\ud83c\udf1d|\ud83c\udf1c|\ud83c\udf1a|\ud83c\udf18|\ud83c\udccf|\ud83c\udd70|\ud83c\udd71|\ud83c\udd7e|\ud83c\udd8e|\ud83c\udd91|\ud83c\udd92|\ud83c\udd93|\ud83c\udd94|\ud83c\udd95|\ud83c\udd96|\ud83c\udd97|\ud83c\udd98|\ud83c\udd99|\ud83c\udd9a|\ud83d\udc77|\ud83d\udec5|\ud83d\udec4|\ud83d\udec3|\ud83d\udec2|\ud83d\udec1|\ud83d\udebf|\ud83d\udeb8|\ud83d\udeb7|\ud83d\udeb5|\ud83c\ude01|\ud83c\ude02|\ud83c\ude32|\ud83c\ude33|\ud83c\ude34|\ud83c\ude35|\ud83c\ude36|\ud83c\ude37|\ud83c\ude38|\ud83c\ude39|\ud83c\ude3a|\ud83c\ude50|\ud83c\ude51|\ud83c\udf00|\ud83c\udf01|\ud83c\udf02|\ud83c\udf03|\ud83c\udf04|\ud83c\udf05|\ud83c\udf06|\ud83c\udf07|\ud83c\udf08|\ud83c\udf09|\ud83c\udf0a|\ud83c\udf0b|\ud83c\udf0c|\ud83c\udf0f|\ud83c\udf11|\ud83c\udf13|\ud83c\udf14|\ud83c\udf15|\ud83c\udf19|\ud83c\udf1b|\ud83c\udf1f|\ud83c\udf20|\ud83c\udf30|\ud83c\udf31|\ud83c\udf34|\ud83c\udf35|\ud83c\udf37|\ud83c\udf38|\ud83c\udf39|\ud83c\udf3a|\ud83c\udf3b|\ud83c\udf3c|\ud83c\udf3d|\ud83c\udf3e|\ud83c\udf3f|\ud83c\udf40|\ud83c\udf41|\ud83c\udf42|\ud83c\udf43|\ud83c\udf44|\ud83c\udf45|\ud83c\udf46|\ud83c\udf47|\ud83c\udf48|\ud83c\udf49|\ud83c\udf4a|\ud83c\udf4c|\ud83c\udf4d|\ud83c\udf4e|\ud83c\udf4f|\ud83c\udf51|\ud83c\udf52|\ud83c\udf53|\ud83c\udf54|\ud83c\udf55|\ud83c\udf56|\ud83c\udf57|\ud83c\udf58|\ud83c\udf59|\ud83c\udf5a|\ud83c\udf5b|\ud83c\udf5c|\ud83c\udf5d|\ud83c\udf5e|\ud83c\udf5f|\ud83c\udf60|\ud83c\udf61|\ud83c\udf62|\ud83c\udf63|\ud83c\udf64|\ud83c\udf65|\ud83c\udf66|\ud83c\udf67|\ud83c\udf68|\ud83c\udf69|\ud83c\udf6a|\ud83c\udf6b|\ud83c\udf6c|\ud83c\udf6d|\ud83c\udf6e|\ud83c\udf6f|\ud83c\udf70|\ud83c\udf71|\ud83c\udf72|\ud83c\udf73|\ud83c\udf74|\ud83c\udf75|\ud83c\udf76|\ud83c\udf77|\ud83c\udf78|\ud83c\udf79|\ud83c\udf7a|\ud83c\udf7b|\ud83c\udf80|\ud83c\udf81|\ud83c\udf82|\ud83c\udf83|\ud83c\udf84|\ud83c\udf85|\ud83c\udf86|\ud83c\udf87|\ud83c\udf88|\ud83c\udf89|\ud83c\udf8a|\ud83c\udf8b|\ud83c\udf8c|\ud83c\udf8d|\ud83c\udf8e|\ud83c\udf8f|\ud83c\udf90|\ud83c\udf91|\ud83c\udf92|\ud83c\udf93|\ud83c\udfa0|\ud83c\udfa1|\ud83c\udfa2|\ud83c\udfa3|\ud83c\udfa4|\ud83c\udfa5|\ud83c\udfa6|\ud83c\udfa7|\ud83c\udfa8|\ud83c\udfa9|\ud83c\udfaa|\ud83c\udfab|\ud83c\udfac|\ud83c\udfad|\ud83c\udfae|\ud83c\udfaf|\ud83c\udfb0|\ud83c\udfb1|\ud83c\udfb2|\ud83c\udfb3|\ud83c\udfb4|\ud83c\udfb5|\ud83c\udfb6|\ud83c\udfb7|\ud83c\udfb8|\ud83c\udfb9|\ud83c\udfba|\ud83c\udfbb|\ud83c\udfbc|\ud83c\udfbd|\ud83c\udfbe|\ud83c\udfbf|\ud83c\udfc0|\ud83c\udfc1|\ud83c\udfc2|\ud83c\udfc3|\ud83c\udfc4|\ud83c\udfc6|\ud83c\udfc8|\ud83c\udfca|\ud83c\udfe0|\ud83c\udfe1|\ud83c\udfe2|\ud83c\udfe3|\ud83c\udfe5|\ud83c\udfe6|\ud83c\udfe7|\ud83c\udfe8|\ud83c\udfe9|\ud83c\udfea|\ud83c\udfeb|\ud83c\udfec|\ud83c\udfed|\ud83c\udfee|\ud83c\udfef|\ud83c\udff0|\ud83d\udc0c|\ud83d\udc0d|\ud83d\udc0e|\ud83d\udc11|\ud83d\udc12|\ud83d\udc14|\ud83d\udc17|\ud83d\udc18|\ud83d\udc19|\ud83d\udc1a|\ud83d\udc1b|\ud83d\udc1c|\ud83d\udc1d|\ud83d\udc1e|\ud83d\udc1f|\ud83d\udc20|\ud83d\udc21|\ud83d\udc22|\ud83d\udc23|\ud83d\udc24|\ud83d\udc25|\ud83d\udc26|\ud83d\udc27|\ud83d\udc28|\ud83d\udc29|\ud83d\udc2b|\ud83d\udc2c|\ud83d\udc2d|\ud83d\udc2e|\ud83d\udc2f|\ud83d\udc30|\ud83d\udc31|\ud83d\udc32|\ud83d\udc33|\ud83d\udc34|\ud83d\udc35|\ud83d\udc36|\ud83d\udc37|\ud83d\udc38|\ud83d\udc39|\ud83d\udc3a|\ud83d\udc3b|\ud83d\udc3c|\ud83d\udc3d|\ud83d\udc3e|\ud83d\udc40|\ud83d\udc42|\ud83d\udc43|\ud83d\udc44|\ud83d\udc45|\ud83d\udc46|\ud83d\udc47|\ud83d\udc48|\ud83d\udc49|\ud83d\udc4a|\ud83d\udc4b|\ud83d\udc4c|\ud83d\udc4d|\ud83d\udc4e|\ud83d\udc4f|\ud83d\udc50|\ud83d\udc51|\ud83d\udc52|\ud83d\udc53|\ud83d\udc54|\ud83d\udc55|\ud83d\udc56|\ud83d\udc57|\ud83d\udc58|\ud83d\udc59|\ud83d\udc5a|\ud83d\udc5b|\ud83d\udc5c|\ud83d\udc5d|\ud83d\udc5e|\ud83d\udc5f|\ud83d\udc60|\ud83d\udc61|\ud83d\udc62|\ud83d\udc63|\ud83d\udc64|\ud83d\udc66|\ud83d\udc67|\ud83d\udc68|\ud83d\udc69|\ud83d\udc6a|\ud83d\udc6b|\ud83d\udc6e|\ud83d\udc6f|\ud83d\udc70|\ud83d\udc71|\ud83d\udc72|\ud83d\udc73|\ud83d\udc74|\ud83d\udc75|\ud83d\udc76|\ud83d\udeb4|\ud83d\udc78|\ud83d\udc79|\ud83d\udc7a|\ud83d\udc7b|\ud83d\udc7c|\ud83d\udc7d|\ud83d\udc7e|\ud83d\udc7f|\ud83d\udc80|\ud83d\udc81|\ud83d\udc82|\ud83d\udc83|\ud83d\udc84|\ud83d\udc85|\ud83d\udc86|\ud83d\udc87|\ud83d\udc88|\ud83d\udc89|\ud83d\udc8a|\ud83d\udc8b|\ud83d\udc8c|\ud83d\udc8d|\ud83d\udc8e|\ud83d\udc8f|\ud83d\udc90|\ud83d\udc91|\ud83d\udc92|\ud83d\udc93|\ud83d\udc94|\ud83d\udc95|\ud83d\udc96|\ud83d\udc97|\ud83d\udc98|\ud83d\udc99|\ud83d\udc9a|\ud83d\udc9b|\ud83d\udc9c|\ud83d\udc9d|\ud83d\udc9e|\ud83d\udc9f|\ud83d\udca0|\ud83d\udca1|\ud83d\udca2|\ud83d\udca3|\ud83d\udca4|\ud83d\udca5|\ud83d\udca6|\ud83d\udca7|\ud83d\udca8|\ud83d\udca9|\ud83d\udcaa|\ud83d\udcab|\ud83d\udcac|\ud83d\udcae|\ud83d\udcaf|\ud83d\udcb0|\ud83d\udcb1|\ud83d\udcb2|\ud83d\udcb3|\ud83d\udcb4|\ud83d\udcb5|\ud83d\udcb8|\ud83d\udcb9|\ud83d\udcba|\ud83d\udcbb|\ud83d\udcbc|\ud83d\udcbd|\ud83d\udcbe|\ud83d\udcbf|\ud83d\udcc0|\ud83d\udcc1|\ud83d\udcc2|\ud83d\udcc3|\ud83d\udcc4|\ud83d\udcc5|\ud83d\udcc6|\ud83d\udcc7|\ud83d\udcc8|\ud83d\udcc9|\ud83d\udcca|\ud83d\udccb|\ud83d\udccc|\ud83d\udccd|\ud83d\udcce|\ud83d\udccf|\ud83d\udcd0|\ud83d\udcd1|\ud83d\udcd2|\ud83d\udcd3|\ud83d\udcd4|\ud83d\udcd5|\ud83d\udcd6|\ud83d\udcd7|\ud83d\udcd8|\ud83d\udcd9|\ud83d\udcda|\ud83d\udcdb|\ud83d\udcdc|\ud83d\udcdd|\ud83d\udcde|\ud83d\udcdf|\ud83d\udce0|\ud83d\udce1|\ud83d\udce2|\ud83d\udce3|\ud83d\udce4|\ud83d\udce5|\ud83d\udce6|\ud83d\udce7|\ud83d\udce8|\ud83d\udce9|\ud83d\udcea|\ud83d\udceb|\ud83d\udcee|\ud83d\udcf0|\ud83d\udcf1|\ud83d\udcf2|\ud83d\udcf3|\ud83d\udcf4|\ud83d\udcf6|\ud83d\udcf7|\ud83d\udcf9|\ud83d\udcfa|\ud83d\udcfb|\ud83d\udcfc|\ud83d\udd03|\ud83d\udd0a|\ud83d\udd0b|\ud83d\udd0c|\ud83d\udd0d|\ud83d\udd0e|\ud83d\udd0f|\ud83d\udd10|\ud83d\udd11|\ud83d\udd12|\ud83d\udd13|\ud83d\udd14|\ud83d\udd16|\ud83d\udd17|\ud83d\udd18|\ud83d\udd19|\ud83d\udd1a|\ud83d\udd1b|\ud83d\udd1c|\ud83d\udd1d|\ud83d\udd1e|\ud83d\udd1f|\ud83d\udd20|\ud83d\udd21|\ud83d\udd22|\ud83d\udd23|\ud83d\udd24|\ud83d\udd25|\ud83d\udd26|\ud83d\udd27|\ud83d\udd28|\ud83d\udd29|\ud83d\udd2a|\ud83d\udd2b|\ud83d\udd2e|\ud83d\udd2f|\ud83d\udd30|\ud83d\udd31|\ud83d\udd32|\ud83d\udd33|\ud83d\udd34|\ud83d\udd35|\ud83d\udd36|\ud83d\udd37|\ud83d\udd38|\ud83d\udd39|\ud83d\udd3a|\ud83d\udd3b|\ud83d\udd3c|\ud83d\udd3d|\ud83d\udd50|\ud83d\udd51|\ud83d\udd52|\ud83d\udd53|\ud83d\udd54|\ud83d\udd55|\ud83d\udd56|\ud83d\udd57|\ud83d\udd58|\ud83d\udd59|\ud83d\udd5a|\ud83d\udd5b|\ud83d\uddfb|\ud83d\uddfc|\ud83d\uddfd|\ud83d\uddfe|\ud83d\uddff|\ud83d\ude01|\ud83d\ude02|\ud83d\ude03|\ud83d\ude04|\ud83d\ude05|\ud83d\ude06|\ud83d\ude09|\ud83d\ude0a|\ud83d\ude0b|\ud83d\ude0c|\ud83d\ude0d|\ud83d\ude0f|\ud83d\ude12|\ud83d\ude13|\ud83d\ude14|\ud83d\ude16|\ud83d\ude18|\ud83d\ude1a|\ud83d\ude1c|\ud83d\ude1d|\ud83d\ude1e|\ud83d\ude20|\ud83d\ude21|\ud83d\ude22|\ud83d\ude23|\ud83d\ude24|\ud83d\ude25|\ud83d\ude28|\ud83d\ude29|\ud83d\ude2a|\ud83d\ude2b|\ud83d\ude2d|\ud83d\ude30|\ud83d\ude31|\ud83d\ude32|\ud83d\ude33|\ud83d\ude35|\ud83d\ude37|\ud83d\ude38|\ud83d\ude39|\ud83d\ude3a|\ud83d\ude3b|\ud83d\ude3c|\ud83d\ude3d|\ud83d\ude3e|\ud83d\ude3f|\ud83d\ude40|\ud83d\ude45|\ud83d\ude46|\ud83d\ude47|\ud83d\ude48|\ud83d\ude49|\ud83d\ude4a|\ud83d\ude4b|\ud83d\ude4c|\ud83d\ude4d|\ud83d\ude4e|\ud83d\ude4f|\ud83d\ude80|\ud83d\ude83|\ud83d\ude84|\ud83d\ude85|\ud83d\ude87|\ud83d\ude89|\ud83d\ude8c|\ud83d\ude8f|\ud83d\ude91|\ud83d\ude92|\ud83d\ude93|\ud83d\ude95|\ud83d\ude97|\ud83d\ude99|\ud83d\ude9a|\ud83d\udea2|\ud83d\udea4|\ud83d\udea5|\ud83d\udea7|\ud83d\udea8|\ud83d\udea9|\ud83d\udeaa|\ud83d\udeab|\ud83d\udeac|\ud83d\udead|\ud83d\udeb2|\ud83d\udeb6|\ud83d\udeb9|\ud83d\udeba|\ud83d\udebb|\ud83d\udebc|\ud83d\udebd|\ud83d\udebe|\ud83d\udec0|\ud83c\udde6|\ud83c\udde7|\ud83c\udde8|\ud83c\udde9|\ud83c\uddea|\ud83c\uddeb|\ud83c\uddec|\ud83c\udded|\ud83c\uddee|\ud83c\uddef|\ud83c\uddf0|\ud83c\uddf1|\ud83c\uddf2|\ud83c\uddf3|\ud83c\uddf4|\ud83c\uddf5|\ud83c\uddf6|\ud83c\uddf7|\ud83c\uddf8|\ud83c\uddf9|\ud83c\uddfa|\ud83c\uddfb|\ud83c\uddfc|\ud83c\uddfd|\ud83c\uddfe|\ud83c\uddff|\ud83c\udf0d|\ud83c\udf0e|\ud83c\udf10|\ud83c\udf12|\ud83c\udf16|\ud83c\udf17|\ue50a|\u3030|\u27b0|\u2797|\u2796|\u2795|\u2755|\u2754|\u2753|\u274e|\u274c|\u2728|\u270b|\u270a|\u2705|\u26ce|\u23f3|\u23f0|\u23ec|\u23eb|\u23ea|\u23e9|\u2122|\u27bf|\u00a9|\u00ae)|(?:(?:\ud83c\udc04|\ud83c\udd7f|\ud83c\ude1a|\ud83c\ude2f|\u3299|\u303d|\u2b55|\u2b50|\u2b1c|\u2b1b|\u2b07|\u2b06|\u2b05|\u2935|\u2934|\u27a1|\u2764|\u2757|\u2747|\u2744|\u2734|\u2733|\u2716|\u2714|\u2712|\u270f|\u270c|\u2709|\u2708|\u2702|\u26fd|\u26fa|\u26f5|\u26f3|\u26f2|\u26ea|\u26d4|\u26c5|\u26c4|\u26be|\u26bd|\u26ab|\u26aa|\u26a1|\u26a0|\u2693|\u267f|\u267b|\u3297|\u2666|\u2665|\u2663|\u2660|\u2653|\u2652|\u2651|\u2650|\u264f|\u264e|\u264d|\u264c|\u264b|\u264a|\u2649|\u2648|\u263a|\u261d|\u2615|\u2614|\u2611|\u260e|\u2601|\u2600|\u25fe|\u25fd|\u25fc|\u25fb|\u25c0|\u25b6|\u25ab|\u25aa|\u24c2|\u231b|\u231a|\u21aa|\u21a9|\u2199|\u2198|\u2197|\u2196|\u2195|\u2194|\u2139|\u2049|\u203c|\u2668)([\uFE0E\uFE0F]?)))/g, -SVGPATH:n,ZREKNARF:''+n+"",CHAT_BUTTON:''+n+"",ROOMS:'',CAMERA:'',INVITE:'',LIVE:'',EYE:'',CLOCK:'',GEAR:'',HEART:'',EMOTE:'',STAR:'',CLOSE:'',EDIT:'',GRAPH:''}},{}],6:[function(e,s,n){var i=t.FrankerFaceZ;i.settings_info.developer_mode={type:"boolean",value:!1,storage_key:"ffzDebugMode",visible:function(){return this.settings.developer_mode||Date.now()-parseInt(localStorage.ffzLastDevMode||"0")<6048e5},category:"Debugging",name:"Developer Mode",help:"Load FrankerFaceZ from the local development server instead of the CDN. Please refresh after changing this setting.",on_update:function(){localStorage.ffzLastDevMode=Date.now()}},i.ffz_commands.developer_mode=function(t,e){var s,e=e&&e.length?e[0].toLowerCase():null;return"y"==e||"yes"==e||"true"==e||"on"==e?s=!0:("n"==e||"no"==e||"false"==e||"off"==e)&&(s=!1),void 0===s?"Developer Mode is currently "+(this.settings.developer_mode?"enabled.":"disabled."):(this.settings.set("developer_mode",s),"Developer Mode is now "+(s?"enabled":"disabled")+". Please refresh your browser.")},i.ffz_commands.developer_mode.help="Usage: /ffz developer_mode \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."},{}],7:[function(e,s,n){var i=t.FrankerFaceZ,o=e("../utils"),a=e("../constants");i.prototype.setup_channel=function(){this.log("Creating channel style element.");var t=this._channel_style=document.createElement("style");t.id="ffz-channel-css",document.head.appendChild(t),document.body.classList.toggle("ffz-hide-view-count",!this.settings.channel_views),this.log("Creating channel style element.");var t=this._channel_style=document.createElement("style");t.id="ffz-channel-css",document.head.appendChild(t),this.log("Hooking the Ember Channel Index view.");var e=App.__container__.resolve("view:channel/index"),s=this;if(e){this._modify_cindex(e);try{e.create().destroy()}catch(n){}for(var o in Ember.View.views)if(Ember.View.views.hasOwnProperty(o)){var a=Ember.View.views[o];a instanceof e&&(this.log("Manually updating Channel Index view.",a),this._modify_cindex(a),a.ffzInit())}this.log("Hooking the Ember Channel model."),e=App.__container__.resolve("model:channel"),e&&(e.reopen({ffz_host_target:void 0,setHostMode:function(t){return s.settings.hosted_channels?(this.set("ffz_host_target",t.target),this._super(t)):(this.set("ffz_host_target",void 0),this._super({target:void 0,delay:0}))}}),this.log("Hooking the Ember Channel controller."),e=App.__container__.lookup("controller:channel"),e&&(e.reopen({ffzUpdateUptime:function(){s._cindex&&s._cindex.ffzUpdateUptime()}.observes("isLive","content.id"),ffzUpdateInfo:function(){this._ffz_update_timer&&clearTimeout(this._ffz_update_timer),this.get("content.id")&&(this._ffz_update_timer=setTimeout(this.ffzCheckUpdate.bind(this),6e4))}.observes("content.id"),ffzCheckUpdate:function(){var t=this,e=t.get("content.id");e&&Twitch.api&&Twitch.api.get("streams/"+e,{},{version:3}).done(function(e){if(!e||!e.stream)return t.set("stream.created_at",null),void t.set("stream.viewers",0);t.set("stream.created_at",e.stream.created_at||null),t.set("stream.viewers",e.stream.viewers||0);var s=e.stream.game||e.stream.channel&&e.stream.channel.game;s&&(t.set("game",s),t.set("rollbackData.game",s)),e.stream.channel&&(e.stream.channel.status&&t.set("status",e.stream.channel.status),e.stream.channel.views&&t.set("views",e.stream.channel.views),e.stream.channel.followers&&t.get("content.followers.isLoaded")&&t.set("content.followers.total",e.stream.channel.followers))}).always(function(e){t.ffzUpdateInfo()})},ffzUpdateTitle:function(){var t=this.get("content.name"),e=this.get("content.display_name");e&&(i.capitalization[t]=[e,Date.now()]),s._cindex&&s._cindex.ffzFixTitle()}.observes("content.status","content.id"),ffzHostTarget:function(){var t=this.get("content.hostModeTarget"),e=t&&t.get("name"),n=t&&t.get("id"),o=t&&t.get("display_name");n!==s.__old_host_target&&(s.__old_host_target&&s.ws_send("unsub_channel",s.__old_host_target),n?(s.ws_send("sub_channel",n),s.__old_host_target=n):delete s.__old_host_target),o&&(i.capitalization[e]=[o,Date.now()]),s.settings.group_tabs&&s._chatv&&s._chatv.ffzRebuildTabs(),s.settings.follow_buttons&&s.rebuild_following_ui(),s.settings.srl_races&&s.rebuild_race_ui()}.observes("content.hostModeTarget")}),e.ffzUpdateInfo()))}},i.prototype._modify_cindex=function(t){var e=this;t.reopen({didInsertElement:function(){this._super();try{this.ffzInit()}catch(t){e.error("CIndex didInsertElement: "+t)}},willClearRender:function(){try{this.ffzTeardown()}catch(t){e.error("CIndex willClearRender: "+t)}return this._super()},ffzInit:function(){var t=this.get("controller.id"),s=this.get("element");e._cindex=this,e.ws_send("sub_channel",t),s.setAttribute("data-channel",t),s.classList.add("ffz-channel"),this.$(".theatre-button a").attr("title","Theater Mode (Alt+T)"),this.ffzFixTitle(),this.ffzUpdateUptime(),this.ffzUpdateChatters(),this.ffzUpdateHostButton(),this.ffzUpdatePlayerStats();var n=this.get("element").querySelector(".svg-glyph_views:not(.ffz-svg)");if(n&&n.parentNode.classList.add("twitch-channel-views"),e.settings.follow_buttons&&e.rebuild_following_ui(),e.settings.srl_races&&e.rebuild_race_ui(),e.settings.auto_theater){var i=App.__container__.lookup("controller:layout");i&&i.set("isTheatreMode",!0)}},ffzFixTitle:function(){if(!e.has_bttv&&e.settings.stream_title){var t=this.get("controller.status"),s=this.get("controller.id");t=e.render_tokens(e.tokenize_line(s,s,t,!0)),this.$(".title span").each(function(e,s){var n=s.querySelectorAll("script");s.innerHTML=n.length?n[0].outerHTML+t+n[1].outerHTML:t})}},ffzUpdateHostButton:function(){var t=this.get("controller.id"),s=this.get("controller.hostModeTarget.id"),n=e.get_user(),a=n&&e.rooms&&e.rooms[n.login]&&e.rooms[n.login].room,r=a&&a.ffz_host_target,d=a&&a.ffz_hosts_left,u=this.get("element");if(this.set("ffz_host_updating",!1),t){var c=u&&u.querySelector(".stats-and-actions .channel-actions"),l=c&&c.querySelector("#ffz-ui-host-button");if(c&&e.settings.stream_host_button&&n&&n.login!==t){if(!l){l=document.createElement("span"),l.id="ffz-ui-host-button",l.className="button action tooltip",l.addEventListener("click",this.ffzClickHost.bind(l,this,!1));var h;try{h=c.querySelector(":scope > .theatre-button")}catch(f){h=void 0}h?c.insertBefore(l,h):c.appendChild(l)}l.classList.remove("disabled"),l.innerHTML=t===r?"Unhost":"Host",l.title=r?"You are now hosting "+o.sanitize(i.get_capitalization(r))+".":"You are not hosting any channel.","number"==typeof d&&(l.title+=" You have "+d+" host command"+o.pluralize(d)+" remaining this half hour.")}else l&&l.parentElement.removeChild(l)}if(s){var c=u&&u.querySelector("#hostmode .channel-actions"),l=c&&c.querySelector("#ffz-ui-host-button");if(c&&e.settings.stream_host_button&&n&&n.login!==s){if(!l){l=document.createElement("span"),l.id="ffz-ui-host-button",l.className="button action tooltip",l.addEventListener("click",this.ffzClickHost.bind(l,this,!0));var h;try{h=c.querySelector(":scope > .theatre-button")}catch(f){h=void 0}h?c.insertBefore(l,h):c.appendChild(l)}l.classList.remove("disabled"),l.innerHTML=s===r?"Unhost":"Host",l.title=r?"You are currently hosting "+o.sanitize(i.get_capitalization(r))+". Click to "+(s===r?"unhost":"host")+" this channel.":"You are not currently hosting any channel. Click to host this channel.","number"==typeof d&&(l.title+=" You have "+d+" host command"+o.pluralize(d)+" remaining this half hour.")}else l&&l.parentElement.removeChild(l)}},ffzClickHost:function(t,s){var n=t.get(s?"controller.hostModeTarget.id":"controller.id"),i=e.get_user(),o=i&&e.rooms&&e.rooms[i.login]&&e.rooms[i.login].room,a=o&&o.ffz_host_target;o&&!t.get("ffz_host_updating")&&(this.classList.add("disabled"),this.title="Updating...",t.set("ffz_host_updating",!0),o.send(a===n?"/unhost":"/host "+n))},ffzUpdateChatters:function(){var t=this.get("controller.id"),s=e.rooms&&e.rooms[t];if(!s||!e.settings.chatter_count){var n=this.get("element").querySelector("#ffz-chatter-display");return n&&n.parentElement.removeChild(n),n=this.get("element").querySelector("#ffz-ffzchatter-display"),void(n&&n.parentElement.removeChild(n))}var i=Object.keys(s.room.get("ffz_chatters")||{}).length,r=s.ffz_chatters||0,d=s.ffz_viewers||0,n=this.get("element").querySelector("#ffz-chatter-display span");if(!n){var u=this.get("element").querySelector(".stats-and-actions .channel-stats");if(!u)return;var c=document.createElement("span");c.className="ffz stat",c.id="ffz-chatter-display",c.title="Currently in Chat",c.innerHTML=a.ROOMS+" ",n=document.createElement("span"),c.appendChild(n);var l=u.querySelector("#ffz-ffzchatter-display");l?u.insertBefore(c,l):u.appendChild(c),jQuery(c).tipsy()}if(n.innerHTML=o.number_commas(i),!r&&!d)return n=this.get("element").querySelector("#ffz-ffzchatter-display"),void(n&&n.parentNode.removeChild(n));if(n=this.get("element").querySelector("#ffz-ffzchatter-display span"),!n){var u=this.get("element").querySelector(".stats-and-actions .channel-stats");if(!u)return;var c=document.createElement("span");c.className="ffz stat",c.id="ffz-ffzchatter-display",c.title="Viewers (In Chat) with FrankerFaceZ",c.innerHTML=a.ZREKNARF+" ",n=document.createElement("span"),c.appendChild(n);var l=u.querySelector("#ffz-chatter-display");l?u.insertBefore(c,l.nextSibling):u.appendChild(c),jQuery(c).tipsy()}n.innerHTML=o.number_commas(d)+" ("+o.number_commas(r)+")"},ffzUpdatePlayerStats:function(){var t=this.get("controller.id"),s=this.get("controller.hostModeTarget.id"),n=this.get("element");if(t){var i=n&&n.querySelector(".stats-and-actions .channel-stats"),o=i&&i.querySelector("#ffz-ui-player-stats"),n=o&&o.querySelector("span"),r=e.players&&e.players[t],d=r&&r.player,u=d&&d.stats;if(i&&e.settings.player_stats&&u&&"NaN"!==u.hlsLatencyBroadcaster&&u.hlsLatencyBroadcaster!==0/0){if(!o){o=document.createElement("span"),o.id="ffz-ui-player-stats",o.className="ffz stat tooltip",o.innerHTML=a.GRAPH+" ",n=document.createElement("span"),o.appendChild(n);var c=i.querySelector("#ffz-uptime-display");c?i.insertBefore(o,c.nextSibling):i.appendChild(o)}o.title="Stream Latency\nFPS: "+u.fps+"\nPlayback Rate: "+u.playbackRate+" Kbps",n.textContent=u.hlsLatencyBroadcaster+"s"}else o&&o.parentElement.removeChild(o)}if(s){var i=n&&n.querySelector("#hostmode .channel-stats"),o=i&&i.querySelector("#ffz-ui-player-stats"),n=o&&o.querySelector("span"),r=e.players&&e.players[s],d=r&&r.player,u=d&&d.stats;if(i&&e.settings.player_stats&&u&&"NaN"!==u.hlsLatencyBroadcaster&&u.hlsLatencyBroadcaster!==0/0){if(!o){o=document.createElement("span"),o.id="ffz-ui-player-stats",o.className="ffz stat tooltip",o.innerHTML=a.GRAPH+" ",n=document.createElement("span"),o.appendChild(n);var c=i.querySelector("#ffz-uptime-display");c?i.insertBefore(o,c.nextSibling):i.appendChild(o)}o.title="Stream Latency\nFPS: "+u.fps+"\nPlayback Rate: "+u.playbackRate+" Kbps",n.textContent=u.hlsLatencyBroadcaster+"s"}else o&&o.parentElement.removeChild(o)}},ffzUpdateUptime:function(){if(this._ffz_update_uptime&&(clearTimeout(this._ffz_update_uptime),delete this._ffz_update_uptime),!e.settings.stream_uptime||!this.get("controller.isLiveAccordingToKraken")){var t=this.get("element").querySelector("#ffz-uptime-display");return void(t&&t.parentElement.removeChild(t))}this._ffz_update_uptime=setTimeout(this.ffzUpdateUptime.bind(this),1e3);var s=this.get("controller.content.stream.created_at");s=s&&o.parse_date(s);var n=s&&Math.floor((Date.now()-s.getTime())/1e3)||-1;if(0>n){var t=this.get("element").querySelector("#ffz-uptime-display");return void(t&&t.parentElement.removeChild(t))}var t=this.get("element").querySelector("#ffz-uptime-display span");if(!t){var i=this.get("element").querySelector(".stats-and-actions .channel-stats");if(!i)return;var r=document.createElement("span");r.className="ffz stat",r.id="ffz-uptime-display",r.title="Stream Uptime (since "+s.toLocaleString()+")",r.innerHTML=a.CLOCK+" ",t=document.createElement("span"),r.appendChild(t);var d=i.querySelector(".live-count");if(d)i.insertBefore(r,d.nextSibling);else try{d=i.querySelector("script:nth-child(0n+2)"),i.insertBefore(r,d.nextSibling)}catch(u){i.insertBefore(r,i.childNodes[0])}jQuery(r).tipsy({html:!0})}t.innerHTML=o.time_to_string(n)},ffzTeardown:function(){var t=this.get("controller.id");t&&e.ws_send("unsub_channel",t),this.get("element").setAttribute("data-channel",""),e._cindex=void 0,this._ffz_update_uptime&&clearTimeout(this._ffz_update_uptime),o.update_css(e._channel_style,t,null)}})},i.settings_info.auto_theater={type:"boolean",value:!1,category:"Appearance",no_mobile:!0,no_bttv:!0,name:"Automatic Theater Mode",help:"Automatically enter theater mode when opening a channel."},i.settings_info.chatter_count={type:"boolean",value:!1,no_mobile:!0,category:"Channel Metadata",name:"Chatter Count",help:"Display the current number of users connected to chat beneath the channel.",on_update:function(t){if(this._cindex&&this._cindex.ffzUpdateChatters(),t&&this.rooms)for(var e in this.rooms)this.rooms.hasOwnProperty(e)&&this.rooms[e].room&&this.rooms[e].room.ffzInitChatterCount()}},i.settings_info.channel_views={type:"boolean",value:!0,no_mobile:!0,category:"Channel Metadata",name:"Channel Views",help:"Display the number of times the channel has been viewed beneath the stream.",on_update:function(t){document.body.classList.toggle("ffz-hide-view-count",!t)}},i.settings_info.hosted_channels={type:"boolean",value:!0,no_mobile:!0,category:"Channel Metadata",name:"Channel Hosting",help:"Display other channels that have been featured by the current channel.",on_update:function(t){var e=document.querySelector("input.ffz-setting-hosted-channels");if(e&&(e.checked=t),this._cindex){var s=this._cindex.get("controller.model"),n=s&&this.rooms&&this.rooms[s.get("id")],i=n&&n.room&&n.room.get("ffz_host_target");s&&n&&s.setHostMode({target:i,delay:0})}}},i.settings_info.stream_host_button={type:"boolean",value:!0,no_mobile:!0,category:"Channel Metadata",name:"Host This Channel Button",help:"Display a button underneath streams that make it easy to host them with your own channel.",on_update:function(t){this._cindex&&this._cindex.ffzUpdateHostButton()}},i.settings_info.stream_uptime={type:"boolean",value:!1,no_mobile:!0,category:"Channel Metadata",name:"Stream Uptime",help:"Display the stream uptime under a channel by the viewer count.",on_update:function(t){this._cindex&&this._cindex.ffzUpdateUptime()}},i.settings_info.stream_title={type:"boolean",value:!0,no_bttv:!0,no_mobile:!0,category:"Channel Metadata",name:"Title Links",help:"Make links in stream titles clickable.",on_update:function(t){this._cindex&&this._cindex.ffzFixTitle()}}},{"../constants":5,"../utils":35}],8:[function(e,s,n){var i=t.FrankerFaceZ,o=e("../utils"),a=(e("../constants"),-1!==navigator.userAgent.indexOf("Android")),r={BACKSPACE:8,TAB:9,ENTER:13,ESC:27,SPACE:32,LEFT:37,UP:38,RIGHT:39,DOWN:40,TWO:50,COLON:59,FAKE_COLON:186},d=function(t){if("number"==typeof t.selectionStart)return t.selectionStart;if(!t.createTextRange)return-1;var e=document.selection.createRange(),s=t.createTextRange();return s.moveToBookmark(e.getBookmark()),s.moveStart("character",-t.value.length),s.text.length},u=function(t,e){if(t.setSelectionRange)t.setSelectionRange(e,e);else if(t.createTextRange){var s=t.createTextRange();s.move("character",-t.value.length),s.move("character",e),s.select()}};i.settings_info.input_quick_reply={type:"boolean",value:!0,category:"Chat Input",no_bttv:!0,name:"Reply to Whispers with /r",help:"Automatically replace /r at the start of the line with the command to whisper to the person you've whispered with most recently."},i.settings_info.input_mru={type:"boolean",value:!0,category:"Chat Input",no_bttv:!0,name:"Chat Input History",help:"Use the Up and Down arrows in chat to select previously sent chat messages."},i.settings_info.input_emoji={type:"boolean",value:!1,category:"Chat Input",no_bttv:!0,name:"Enter Emoji By Name",help:"Replace emoji that you type by name with the character. :+1: becomes 👍."},i.prototype.setup_chat_input=function(){this.log("Hooking the Ember Chat Input controller.");var t=App.__container__.resolve("component:twitch-chat-input");if(t&&(this._modify_chat_input(t),this._roomv))for(var e=0;e500&&this.ffzResizeInput()}}.observes("textareaValue"),ffzResizeInput:function(){this._ffz_last_resize=Date.now();var t=this.get("element"),e=t&&t.querySelector("textarea");if(e&&s._chat_style&&s.settings.minimal_chat){this._ffz_minimal_style.innerHTML="body.ffz-minimal-chat .ember-chat .chat-interface .textarea-contain textarea { height: auto !important; }";var n=Math.max(32,Math.min(128,e.scrollHeight));this._ffz_minimal_style.innerHTML="body.ffz-minimal-chat .ember-chat .chat-interface .textarea-contain textarea { height: "+n+"px !important; }",n!==this._ffz_last_height&&(o.update_css(s._chat_style,"input_height","body.ffz-minimal-chat .ember-chat .chat-interface { height: "+n+"px !important; }body.ffz-minimal-chat .ember-chat .chat-messages, body.ffz-minimal-chat .ember-chat .chat-interface .emoticon-selector { bottom: "+n+"px !important; }"),s._roomv&&s._roomv.get("stuckToBottom")&&s._roomv._scrollToBottom()),this._ffz_last_height=n}},_ffzKeyDown:function(e){var n=e||t.event,i=n.charCode||n.keyCode;switch(i){case r.UP:case r.DOWN:if(n.shiftKey||n.shiftLeft||n.ctrlKey||n.metaKey)return;if(this.get("isShowingSuggestions"))n.preventDefault();else{if(!s.settings.input_mru)return this._onKeyDown(e);Ember.run.next(this.ffzCycleMRU.bind(this,i,d(this.get("chatTextArea"))))}break;case r.SPACE:if(!s.settings.input_quick_reply||2!==d(this.get("chatTextArea"))||"/r"!==this.get("textareaValue").substring(0,2))return this._onKeyDown(e);var o=this;Ember.run.next(function(){var t=o.get("uniqueWhisperSuggestions.0");if(t){var e="/w "+t+o.get("textareaValue").substr(2);o.set("_currentWhisperTarget",0),o.set("textareaValue",e),Ember.run.next(function(){u(o.get("chatTextArea"),4+t.length)})}});break;case r.COLON:case r.FAKE_COLON:if(s.settings.input_emoji&&(n.shiftKey||n.shiftLeft)){var o=this,a=d(this.get("chatTextArea"));return void(a>0&&Ember.run.next(function(){var t=o.get("textareaValue"),e=t.lastIndexOf(":",a-1);if(-1!==e&&-1!==a&&":"===t.charAt(a)){var n=t.substr(e+1,a-e-1),i=s.emoji_names[n],r=s.emoji_data[i];if(r){var d=t.substr(0,e)+r.raw;o.set("textareaValue",d+t.substr(a+1)),Ember.run.next(function(){u(o.get("chatTextArea"),d.length)})}}}))}return this._onKeyDown(e);case r.ENTER:n.shiftKey||n.shiftLeft||this.set("ffz_mru_index",-1);default:return this._onKeyDown(e)}},ffzCycleMRU:function(t,e){var s=d(this.get("chatTextArea"));if(e===s){var n=this.get("ffz_mru_index"),i=this._parentView.get("context.model.mru_list")||[];n=t===r.UP?(n+1)%(i.length+1):(n+i.length)%(i.length+1);var o=this.get("ffz_old_mru");(void 0===o||null===o)&&(o=this.get("textareaValue"),this.set("ffz_old_mru",o));var a=i[n];void 0===a&&(this.set("ffz_old_mru",void 0),a=o),this.set("ffz_mru_index",n),this.set("textareaValue",a)}},completeSuggestion:function(t){var e,s,n=this,o=this.get("textareaValue"),a=this.get("partialNameStartIndex");e=o.substring(0,a)+("/"===o.charAt(0)?t:i.get_capitalization(t)),s=o.substring(a+this.get("partialName").length),s||(e+=" "),this.set("textareaValue",e+s),this.set("isShowingSuggestions",!1),this.set("partialName",""),this.trackSuggestionsCompleted(),Ember.run.next(function(){u(n.get("chatTextArea"),e.length)})}})}},{"../constants":5,"../utils":35}],9:[function(e,s,n){var i=t.FrankerFaceZ,o=e("../utils"),a=e("../constants");i.basic_settings.cure_cancer={type:"boolean",category:"Chat",name:"Cure Cancer",help:"Destroys all cancerous chat messages before they can even be seen.",get:function(){return this.settings.remove_deleted&&this.settings.remove_bot_ban_notices&&+this.settings.chat_delay},set:function(t){this.settings.set("remove_deleted",t),this.settings.set("remove_bot_ban_notices",t),this.settings.set("chat_delay",t?""+(+this.settings.chat_delay||300):"0")}},i.settings_info.minimal_chat={type:"boolean",value:!1,category:"Chat Appearance",name:"Minimalistic Chat",help:"Hide all of the chat user interface, only showing messages and an input box.",on_update:function(t){if(document.body.classList.toggle("ffz-minimal-chat",t),this.settings.group_tabs&&this._chatv&&this._chatv._ffz_tabs){var e=this;setTimeout(function(){e._chatv&&e._chatv.$(".chat-room").css("top",e._chatv._ffz_tabs.offsetHeight+"px"),e._roomv&&e._roomv.get("stuckToBottom")&&e._roomv._scrollToBottom()},0)}this._chatv&&this._chatv.get("controller.showList")&&this._chatv.set("controller.showList",!1),!t&&this._chat_style?(this._inputv&&(this._inputv._ffz_minimal_style&&(this._inputv._ffz_minimal_style.innerHTML=""),this._inputv._ffz_last_height=void 0),o.update_css(this._chat_style,"input_height",""),this._roomv&&this._roomv.get("stuckToBottom")&&this._roomv._scrollToBottom()):this._inputv&&this._inputv.ffzResizeInput()}},i.settings_info.chat_delay={type:"select",options:{0:"No Delay",300:"Wait for bot auto-bans (300ms)",1200:"Wait for human mods (1200ms)",5e3:"ESPORTS (5000ms)"},value:0,category:"Chat Appearance",name:"Artificial Chat Delay",help:"Delay messages allowing moderators to ban them before you see them.",on_update:function(t){var e=document.querySelector("#ffz-stat-delay");e.title=o.number_commas(+t||300)+"ms of artifical chat delay added.",e.classList.toggle("hidden",!+t)}},i.settings_info.remove_deleted={type:"boolean",value:!1,no_bttv:!0,category:"Chat Filtering",name:"Remove Deleted Messages",help:"Remove deleted messages from chat entirely rather than leaving behind a clickable <deleted message>.",on_update:function(t){if(!this.has_bttv&&this.rooms&&t)for(var e in this.rooms){var s=this.rooms[e],n=s&&s.room;if(n)for(var i,o=n.get("messages"),a=o.get("length"),r=a;r--;){var d=o.get(r);d.ffz_deleted||d.deleted?(void 0===i&&(i=d.ffz_alternate),o.removeAt(r)):void 0===i?i=d.ffz_alternate:(i=!i,n.set("messages."+r+".ffz_alternate",i))}}}},i.settings_info.remove_bot_ban_notices={type:"boolean",value:!1,category:"Chat Filtering",name:"Remove Bot Ban Notices",help:"Remove messages from bots announcing who was banned for what reason and for how long."},i.settings_info.prevent_clear={type:"boolean",value:!1,no_bttv:!0,category:"Chat Filtering",name:"Show Deleted Messages",help:"Fade deleted messages instead of replacing them, and prevent chat from being cleared.",on_update:function(t){if(!this.has_bttv&&this.rooms)for(var e in this.rooms){var s=this.rooms[e],n=s&&s.room;n&&n.get("messages").forEach(function(e,s){t&&!e.ffz_deleted&&e.deleted?n.set("messages."+s+".deleted",!1):!e.ffz_deleted||t||e.deleted||n.set("messages."+s+".deleted",!0)})}}},i.settings_info.chat_history={type:"boolean",value:!0,visible:!1,category:"Chat Appearance",name:"Chat History Alpha",help:"Load previous chat messages when loading a chat room so you can see what people have been talking about. This currently only works in a handful of channels due to server capacity."},i.settings_info.group_tabs={type:"boolean",value:!1,no_bttv:!0,category:"Chat Moderation",name:"Chat Room Tabs Beta",help:"Enhanced UI for switching the current chat room and noticing new messages.",on_update:function(t){var e=!this.has_bttv&&t;this._chatv&&e!==this._group_tabs_state&&(e?this._chatv.ffzEnableTabs():this._chatv.ffzDisableTabs())}},i.settings_info.pinned_rooms={value:[],visible:!1},i.settings_info.visible_rooms={value:[],visible:!1},i.prototype.setup_chatview=function(){document.body.classList.toggle("ffz-minimal-chat",this.settings.minimal_chat),this.log("Hooking the Ember Chat controller.");var t=App.__container__.lookup("controller:chat"),e=this;t&&t.reopen({ffzUpdateChannels:function(){e._chatv&&(e._chatv.ffzRebuildMenu(),e.settings.group_tabs&&e._chatv.ffzRebuildTabs())}.observes("currentChannelRoom","connectedPrivateGroupRooms"),removeCurrentChannelRoom:function(){if(!e.settings.group_tabs||e.has_bttv)return this._super();var t=this.get("currentChannelRoom"),s=t&&t.get("id"),n=e.get_user();e.settings.pinned_rooms&&-1!==e.settings.pinned_rooms.indexOf(s)||(t===this.get("currentRoom")&&this.blurRoom(),t&&n&&n.login===s&&t.destroy()),this.set("currentChannelRoom",void 0)}}),this.log("Hooking the Ember Chat view.");var t=App.__container__.resolve("view:chat");this._modify_cview(t);try{t.create().destroy()}catch(s){}for(var n in Ember.View.views)if(Ember.View.views.hasOwnProperty(n)){var i=Ember.View.views[n];if(i instanceof t){this.log("Manually updating existing Chat view.",i);try{i.ffzInit()}catch(s){this.error("setup: build_ui_link: "+s)}}}this.log("Hooking the Ember 'Right Column' controller. Seriously...");var o=App.__container__.lookup("controller:right-column");o&&o.reopen({ffzFixTabs:function(){e.settings.group_tabs&&e._chatv&&e._chatv._ffz_tabs&&setTimeout(function(){e._chatv&&e._chatv.$(".chat-room").css("top",e._chatv._ffz_tabs.offsetHeight+"px")},0)}.observes("firstTabSelected")})},i.prototype._modify_cview=function(t){var e=this;t.reopen({didInsertElement:function(){this._super();try{this.ffzInit()}catch(t){e.error("ChatView didInsertElement: "+t)}},willClearRender:function(){try{this.ffzTeardown()}catch(t){e.error("ChatView willClearRender: "+t)}this._super()},ffzInit:function(){e._chatv=this,this.$(".textarea-contain").append(e.build_ui_link(this)),this.$(".chat-messages").find(".html-tooltip").tipsy({live:!0,html:!0,gravity:jQuery.fn.tipsy.autoNS}),!e.has_bttv&&e.settings.group_tabs&&this.ffzEnableTabs(),this.ffzRebuildMenu(),setTimeout(function(){e.settings.group_tabs&&e._chatv&&e._chatv._ffz_tabs&&e._chatv.$(".chat-room").css("top",e._chatv._ffz_tabs.offsetHeight+"px");var t=e._chatv.get("controller");t&&t.set("showList",!1)},1e3)},ffzTeardown:function(){e._chatv===this&&(e._chatv=null),this.$(".textarea-contain .ffz-ui-toggle").remove(),e.settings.group_tabs&&this.ffzDisableTabs()},ffzChangeRoom:Ember.observer("controller.currentRoom",function(){e.update_ui_link();var t,s=this.get("controller.currentRoom");if(s&&s.resetUnreadCount(),this._ffz_chan_table&&(t=jQuery(this._ffz_chan_table), -t.children(".ffz-room-row").removeClass("active"),s&&t.children('.ffz-room-row[data-room="'+s.get("id")+'"]').addClass("active").children("span").text("")),this._ffz_group_table&&(t=jQuery(this._ffz_group_table),t.children(".ffz-room-row").removeClass("active"),s&&t.children('.ffz-room-row[data-room="'+s.get("id")+'"]').addClass("active").children("span").text("")),!e.has_bttv&&e.settings.group_tabs&&this._ffz_tabs){var n=jQuery(this._ffz_tabs);if(n.children(".ffz-chat-tab").removeClass("active"),s&&s._ffz_tab){s._ffz_tab.classList.remove("tab-mentioned"),s._ffz_tab.classList.remove("hidden"),s._ffz_tab.classList.add("active");var i=s._ffz_tab.querySelector("span");i&&(i.innerHTML="")}var o=s&&s.get("canInvite");this._ffz_invite&&this._ffz_invite.classList.toggle("hidden",!o),this.set("controller.showInviteUser",o&&this.get("controller.showInviteUser")),this.$(".chat-room").css("top",this._ffz_tabs.offsetHeight+"px")}}),ffzRebuildMenu:function(){return},ffzBuildRow:function(t,s,n,r){var d,u=document.createElement("tr"),c=document.createElement("td"),l=document.createElement("td"),h=document.createElement("td"),f=document.createElement("td"),m=s.get("isGroupRoom"),_=s===t.get("controller.currentRoom"),p=s.get("tmiRoom.displayName")||(m?s.get("tmiRoom.name"):i.get_capitalization(s.get("id"),function(t){e.log("Name for Row: "+t),l.innerHTML=o.sanitize(t)}));return l.className="ffz-room",l.innerHTML=o.sanitize(p),n?(c.innerHTML=a.CAMERA,c.title=l.title="Current Channel",c.className=l.className="tooltip"):r&&(c.innerHTML=a.EYE,c.title=l.title="Hosted Channel",c.className=l.className="tooltip"),h.className=f.className="ffz-row-switch",h.innerHTML='',f.innerHTML='',u.setAttribute("data-room",s.get("id")),u.className="ffz-room-row",u.classList.toggle("current-channel",n),u.classList.toggle("host-channel",r),u.classList.toggle("group-chat",m),u.classList.toggle("active",_),u.appendChild(c),u.appendChild(l),m?(d=document.createElement("a"),d.className="leave-chat tooltip",d.innerHTML=a.CLOSE,d.title="Leave Group",l.appendChild(d),d.addEventListener("click",function(t){t.preventDefault(),t.stopPropagation&&t.stopPropagation(),confirm('Are you sure you want to leave the group room "'+p+'"?')&&s.get("isGroupRoom")&&s.del()})):(u.appendChild(h),d=h.querySelector("a.switch"),d.addEventListener("click",function(t){t.preventDefault(),t.stopPropagation&&t.stopPropagation();var n=s.get("id"),i=-1!==e.settings.pinned_rooms.indexOf(n);i?e._leave_room(n):e._join_room(n),this.classList.toggle("active",!i)})),u.appendChild(f),d=f.querySelector("a.switch"),d.addEventListener("click",function(n){n.preventDefault(),n.stopPropagation&&n.stopPropagation();var i=s.get("id"),o=e.settings.visible_rooms,a=-1!==o.indexOf(i);a?o.removeObject(i):o.push(i),e.settings.set("visible_rooms",o),this.classList.toggle("active",!a),t.ffzRebuildTabs()}),u.addEventListener("click",function(){var e=t.get("controller");e.focusRoom(s),e.set("showList",!1)}),u},ffzEnableTabs:function(){if(!e.has_bttv&&e.settings.group_tabs){this.$(".chat-header").addClass("hidden");var t=this._ffz_tabs=document.createElement("div");t.id="ffz-group-tabs",this.$(".chat-header").after(t),this.ffzRebuildTabs()}},ffzRebuildTabs:function(){if(!e.has_bttv&&e.settings.group_tabs){var t=this._ffz_tabs||this.get("element").querySelector("#ffz-group-tabs");if(t){t.innerHTML="";var s=document.createElement("a"),n=this;s.className="button glyph-only tooltip",s.title="Chat Room Management",s.innerHTML=a.ROOMS,s.addEventListener("click",function(){var t=n.get("controller");t&&t.set("showList",!t.get("showList"))}),t.appendChild(s),s=document.createElement("a"),s.className="button glyph-only tooltip invite",s.title="Invite a User",s.innerHTML=a.INVITE,s.addEventListener("click",function(){var t=n.get("controller");t&&t.set("showInviteUser",t.get("currentRoom.canInvite")&&!t.get("showInviteUser"))}),s.classList.toggle("hidden",!this.get("controller.currentRoom.canInvite")),n._ffz_invite=s,t.appendChild(s);var i,o=this.get("controller.currentChannelRoom");o&&(i=this.ffzBuildTab(n,o,!0),i&&t.appendChild(i));var r=App.__container__.lookup("controller:channel"),d=App.__container__.resolve("model:room");if(target=r&&r.get("hostModeTarget"),target&&d){var u=target.get("id");this._ffz_host!==u&&(-1===e.settings.pinned_rooms.indexOf(this._ffz_host)&&this._ffz_host_room&&(this.get("controller.currentRoom")===this._ffz_host_room&&this.get("controller").blurRoom(),this._ffz_host_room.destroy()),this._ffz_host=u,this._ffz_host_room=d.findOne(u))}else this._ffz_host&&(-1===e.settings.pinned_rooms.indexOf(this._ffz_host)&&this._ffz_host_room&&(this.get("controller.currentRoom")===this._ffz_host_room&&this.get("controller").blurRoom(),this._ffz_host_room.destroy()),delete this._ffz_host,delete this._ffz_host_room);this._ffz_host_room&&(i=n.ffzBuildTab(n,this._ffz_host_room,!1,!0),i&&t.appendChild(i));for(var c=0;c"+u+"
"})),n?(l=a.CAMERA,c.title="Current Channel"):r?(l=a.EYE,c.title="Hosted Channel"):c.title=f?"Group Chat":"Pinned Channel",c.innerHTML=l+o.sanitize(d)+""+u+"",c.addEventListener("click",function(){var e=t.get("controller");e.focusRoom(s),e.set("showList",!1)}),s._ffz_tab=c,c},ffzDisableTabs:function(){this._ffz_tabs&&(this._ffz_tabs.parentElement.removeChild(this._ffz_tabs),delete this._ffz_tabs,delete this._ffz_invite),this._ffz_host&&(-1===e.settings.pinned_rooms.indexOf(this._ffz_host)&&this._ffz_host_room&&(this.get("controller.currentRoom")===this._ffz_host_room&&this.get("controller").blurRoom(),this._ffz_host_room.destroy()),delete this._ffz_host,delete this._ffz_host_room),this.$(".chat-room").css("top",""),this.$(".chat-header").removeClass("hidden")}})},i.prototype.connect_extra_chat=function(){var t=this.get_user();if(t&&t.login&&(!this.rooms[t.login]||this.rooms[t.login].room)){var e=App.__container__.resolve("model:room");e&&e.findOne(t.login)}if(!this.has_bttv){for(var s=0;s1)return"Join Usage: /join ";var s=e[0].toLowerCase();return"#"===s.charAt(0)&&(s=s.substr(1)),this._join_room(s)?"Joining "+s+". You will always connect to this channel's chat unless you later /part from it.":"You have already joined "+s+'. Please use "/part '+s+'" to leave it.'},i.chat_commands.part=function(t,e){if(!e||!e.length||e.length>1)return"Part Usage: /part ";var s=e[0].toLowerCase();return"#"===s.charAt(0)&&(s=s.substr(1)),this._leave_room(s)?"Leaving "+s+".":this.rooms[s]?"You do not have "+s+" pinned and you cannot leave the current channel or hosted channels via /part.":"You are not in "+s+"."}},{"../constants":5,"../utils":35}],10:[function(e,s,n){var i=t.FrankerFaceZ;i.settings_info.swap_sidebars={type:"boolean",value:!1,category:"Appearance",no_mobile:!0,no_bttv:!0,name:"Swap Sidebar Positions",help:"Swap the positions of the left and right sidebars, placing chat on the left.",on_update:function(t){this.has_bttv||(document.body.classList.toggle("ffz-sidebar-swap",t),this._fix_menu_position())}},i.settings_info.right_column_width={type:"button",value:340,category:"Appearance",no_mobile:!0,no_bttv:!0,name:"Right Sidebar Width",help:"Set the width of the right sidebar for chat.",method:function(){var t=this.settings.right_column_width||340,e=prompt("Right Sidebar Width\n\nPlease enter a new width for the right sidebar, in pixels. Minimum: 250, Default: 340",t);if(null!==e&&void 0!==e){var s=parseInt(e);s&&s!==0/0||(s=340),this.settings.set("right_column_width",Math.max(250,s))}},on_update:function(t){if(!this.has_bttv){var e=App.__container__.lookup("controller:layout");e&&(e.set("rightColumnWidth",t),Ember.propertyDidChange(e,"contentWidth"))}}},i.prototype.setup_layout=function(){if(!this.has_bttv){document.body.classList.toggle("ffz-sidebar-swap",this.settings.swap_sidebars),this.log("Creating layout style element.");var t=this._layout_style=document.createElement("style");t.id="ffz-layout-css",document.head.appendChild(t),this.log("Hooking the Ember Layout controller.");var e=App.__container__.lookup("controller:layout"),s=this;e&&(e.reopen({rightColumnWidth:340,isTooSmallForRightColumn:function(){return this.get("windowWidth")<1090-this.get("rightColumnWidth")}.property("windowWidth","rightColumnWidth"),contentWidth:function(){var t=this.get("isLeftColumnClosed")?50:240,e=this.get("isRightColumnClosed")?0:this.get("rightColumnWidth");return this.get("windowWidth")-t-e-60}.property("windowWidth","isRightColumnClosed","isLeftColumnClosed","rightColumnWidth"),ffzUpdateCss:function(){var t=this.get("rightColumnWidth");s._layout_style.innerHTML="#main_col.expandRight #right_close { left: none !important; } #right_col { width: "+t+"px; } body:not(.ffz-sidebar-swap) #main_col:not(.expandRight) { margin-right: "+t+"px; } body.ffz-sidebar-swap #main_col:not(.expandRight) { margin-left: "+t+"px; }"}.observes("rightColumnWidth"),ffzFixTabs:function(){s.settings.group_tabs&&s._chatv&&s._chatv._ffz_tabs&&setTimeout(function(){s._chatv&&s._chatv.$(".chat-room").css("top",s._chatv._ffz_tabs.offsetHeight+"px")},0)}.observes("isRightColumnClosed","rightColumnWidth")}),e.set("rightColumnWidth",this.settings.right_column_width),Ember.propertyDidChange(e,"contentWidth"))}}},{}],11:[function(e,s,n){var i=t.FrankerFaceZ,o=e("../utils"),a=(e("../constants"),"[\\s`~<>!-#%-\\x2A,-/:;\\x3F@\\x5B-\\x5D_\\x7B}\\u00A1\\u00A7\\u00AB\\u00B6\\u00B7\\u00BB\\u00BF\\u037E\\u0387\\u055A-\\u055F\\u0589\\u058A\\u05BE\\u05C0\\u05C3\\u05C6\\u05F3\\u05F4\\u0609\\u060A\\u060C\\u060D\\u061B\\u061E\\u061F\\u066A-\\u066D\\u06D4\\u0700-\\u070D\\u07F7-\\u07F9\\u0830-\\u083E\\u085E\\u0964\\u0965\\u0970\\u0AF0\\u0DF4\\u0E4F\\u0E5A\\u0E5B\\u0F04-\\u0F12\\u0F14\\u0F3A-\\u0F3D\\u0F85\\u0FD0-\\u0FD4\\u0FD9\\u0FDA\\u104A-\\u104F\\u10FB\\u1360-\\u1368\\u1400\\u166D\\u166E\\u169B\\u169C\\u16EB-\\u16ED\\u1735\\u1736\\u17D4-\\u17D6\\u17D8-\\u17DA\\u1800-\\u180A\\u1944\\u1945\\u1A1E\\u1A1F\\u1AA0-\\u1AA6\\u1AA8-\\u1AAD\\u1B5A-\\u1B60\\u1BFC-\\u1BFF\\u1C3B-\\u1C3F\\u1C7E\\u1C7F\\u1CC0-\\u1CC7\\u1CD3\\u2010-\\u2027\\u2030-\\u2043\\u2045-\\u2051\\u2053-\\u205E\\u207D\\u207E\\u208D\\u208E\\u2329\\u232A\\u2768-\\u2775\\u27C5\\u27C6\\u27E6-\\u27EF\\u2983-\\u2998\\u29D8-\\u29DB\\u29FC\\u29FD\\u2CF9-\\u2CFC\\u2CFE\\u2CFF\\u2D70\\u2E00-\\u2E2E\\u2E30-\\u2E3B\\u3001-\\u3003\\u3008-\\u3011\\u3014-\\u301F\\u3030\\u303D\\u30A0\\u30FB\\uA4FE\\uA4FF\\uA60D-\\uA60F\\uA673\\uA67E\\uA6F2-\\uA6F7\\uA874-\\uA877\\uA8CE\\uA8CF\\uA8F8-\\uA8FA\\uA92E\\uA92F\\uA95F\\uA9C1-\\uA9CD\\uA9DE\\uA9DF\\uAA5C-\\uAA5F\\uAADE\\uAADF\\uAAF0\\uAAF1\\uABEB\\uFD3E\\uFD3F\\uFE10-\\uFE19\\uFE30-\\uFE52\\uFE54-\\uFE61\\uFE63\\uFE68\\uFE6A\\uFE6B\\uFF01-\\uFF03\\uFF05-\\uFF0A\\uFF0C-\\uFF0F\\uFF1A\\uFF1B\\uFF1F\\uFF20\\uFF3B-\\uFF3D\\uFF3F\\uFF5B\\uFF5D\\uFF5F-\\uFF65]"),r=new RegExp(a+"*,"+a+"*");i.settings_info.room_status={type:"boolean",value:!0,category:"Chat Appearance",no_bttv:!0,name:"Room Status Indicators",help:"Display the current room state (slow mode, sub mode, and r9k mode) next to the Chat button.",on_update:function(){this._roomv&&this._roomv.ffzUpdateStatus()}},i.settings_info.line_purge_icon={type:"boolean",value:!1,no_bttv:!0,category:"Chat Moderation",name:"Purge Icon in Mod Icons",help:"Display a Purge Icon in chat line Mod Icons for quickly purging users.",on_update:function(t){this.has_bttv||document.body.classList.toggle("ffz-chat-purge-icon",t)}},i.settings_info.replace_bad_emotes={type:"boolean",value:!0,category:"Chat Appearance",no_bttv:!0,name:"Fix Low Quality Twitch Global Emoticons",help:"Replace emoticons such as DansGame and RedCoat with cleaned up versions that don't have pixels around the edges or white backgrounds for nicer display on dark chat."},i.settings_info.parse_emoji={type:"boolean",value:!0,category:"Chat Appearance",name:"Replace Emoji with Images",help:"Replace emoji in chat messages with nicer looking images from the open-source Twitter Emoji project."},i.settings_info.room_status={type:"boolean",value:!0,category:"Chat Appearance",no_bttv:!0,name:"Room Status Indicators",help:"Display the current room state (slow mode, sub mode, and r9k mode) next to the Chat button.",on_update:function(){this._roomv&&this._roomv.ffzUpdateStatus()}},i.settings_info.scrollback_length={type:"button",value:150,category:"Chat Appearance",no_bttv:!0,name:"Scrollback Length",help:"Set the maximum number of lines to keep in chat.",method:function(){var t=prompt("Scrollback Length\n\nPlease enter a new maximum length for the chat scrollback. Default: 150\n\nNote: Making this too large will cause your browser to lag.",this.settings.scrollback_length);if(null!==t&&void 0!==t&&(t=parseInt(t),t!==0/0)){10>t&&(t=10),this.settings.set("scrollback_length",t);var e=App.__container__.lookup("controller:chat"),s=e&&e.get("currentRoom.id");for(var n in this.rooms){var i=this.rooms[n];i.room.set("messageBufferSize",t+(this._roomv&&!this._roomv.get("stuckToBottom")&&s===n?150:0))}}}},i.settings_info.hosted_sub_notices={type:"boolean",value:!0,category:"Chat Filtering",no_bttv:!0,name:"Show Hosted Channel Subscriber Notices",help:"Display notices in chat when someone subscribes to the hosted channel."},i.settings_info.banned_words={type:"button",value:[],category:"Chat Filtering",no_bttv:!0,name:"Banned Words",help:"Set a list of words that will be locally removed from chat messages.",method:function(){var t=this.settings.banned_words.join(", "),e=prompt("Banned Words\n\nPlease enter a comma-separated list of words that you would like to be removed from chat messages.",t);if(null!==e&&void 0!==e){e=e.trim().split(r);for(var s=[],n=0;nBeta",help:"Check links against known bad websites, unshorten URLs, and show YouTube info."},i.settings_info.link_image_hover={type:"boolean",value:!1,category:"Chat Tooltips",no_bttv:!0,no_mobile:!0,name:"Image Preview",help:"Display image thumbnails for links to Imgur and YouTube."},i.settings_info.image_hover_all_domains={type:"boolean",value:!1,category:"Chat Tooltips",no_bttv:!0,no_mobile:!0,name:"Image Preview - All Domains",help:"Requires Image Preview. Attempt to show an image preview for any URL ending in the appropriate extension. Warning: This may be used to leak your IP address to malicious users."},i.settings_info.legacy_badges={type:"boolean",value:!1,category:"Chat Appearance",name:"Legacy Badges",help:"Display the old, pre-vector chat badges from Twitch.",on_update:function(t){document.body.classList.toggle("ffz-legacy-badges",t)}},i.settings_info.chat_rows={type:"boolean",value:!1,category:"Chat Appearance",no_bttv:!0,name:"Chat Line Backgrounds",help:"Display alternating background colors for lines in chat.",on_update:function(t){document.body.classList.toggle("ffz-chat-background",!this.has_bttv&&t)}},i.settings_info.chat_separators={type:"select",options:{0:"Disabled",1:"Basic Line (1px solid)",2:"3D Line (2px groove)"},value:"0",category:"Chat Appearance",no_bttv:!0,process_value:function(t){return t===!1?"0":t===!0?"1":t},name:"Chat Line Separators",help:"Display thin lines between chat messages for further visual separation.",on_update:function(t){document.body.classList.toggle("ffz-chat-separator",!this.has_bttv&&"0"!==t),document.body.classList.toggle("ffz-chat-separator-3d",!this.has_bttv&&"2"===t)}},i.settings_info.chat_padding={type:"boolean",value:!1,category:"Chat Appearance",no_bttv:!0,name:"Reduced Chat Line Padding",help:"Reduce the amount of padding around chat messages to fit more on-screen at once.",on_update:function(t){document.body.classList.toggle("ffz-chat-padding",!this.has_bttv&&t)}},i.settings_info.high_contrast_chat={type:"select",options:{222:"Disabled",212:"Bold",221:"Text",211:"Text + Bold",122:"Background",121:"Background + Text",112:"Background + Bold",111:"All"},value:"222",category:"Chat Appearance",no_bttv:!0,name:"High Contrast",help:"Display chat using white and black for maximum contrast. This is suitable for capturing and chroma keying chat to display on stream.",process_value:function(t){return t===!1?"222":t===!0?"111":t},on_update:function(t){document.body.classList.toggle("ffz-high-contrast-chat-text",!this.has_bttv&&"1"===t[2]),document.body.classList.toggle("ffz-high-contrast-chat-bold",!this.has_bttv&&"1"===t[1]),document.body.classList.toggle("ffz-high-contrast-chat-bg",!this.has_bttv&&"1"===t[0])}},i.settings_info.chat_font_size={type:"button",value:12,category:"Chat Appearance",no_bttv:!0,name:"Font Size",help:"Make the chat font bigger or smaller.",method:function(){var t=this.settings.chat_font_size,e=prompt("Chat Font Size\n\nPlease enter a new size for the chat font. The default is 12.",t);if(null!==e&&void 0!==e){var s=parseInt(e);(!s||s===0/0||1>s)&&(s=12),this.settings.set("chat_font_size",s)}},on_update:function(t){if(!this.has_bttv&&this._chat_style){var e;if(12!==t&&t){var s=Math.max(20,Math.round(20/12*t)),n=Math.floor((s-20)/2);e=".ember-chat .chat-messages .chat-line { font-size: "+t+"px !important; line-height: "+s+"px !important; }",n&&(e+=".ember-chat .chat-messages .chat-line .mod-icons, .ember-chat .chat-messages .chat-line .badges { padding-top: "+n+"px; }")}else e="";o.update_css(this._chat_style,"chat_font_size",e),i.settings_info.chat_ts_size.on_update.bind(this)(this.settings.chat_ts_size)}}},i.settings_info.chat_ts_size={type:"button",value:null,category:"Chat Appearance",no_bttv:!0,name:"Timestamp Font Size",help:"Make the chat timestamp font bigger or smaller.",method:function(){var t=this.settings.chat_ts_size;t||(t=this.settings.chat_font_size);var e=prompt("Chat Timestamp Font Size\n\nPlease enter a new size for the chat timestamp font. The default is to match the regular chat font size.",t);if(null!==e&&void 0!==e){var s=parseInt(e);(!s||s===0/0||1>s)&&(s=null),this.settings.set("chat_ts_size",s)}},on_update:function(t){if(!this.has_bttv&&this._chat_style){var e;if(null===t)e="";else{var s=Math.max(20,Math.round(20/12*t),Math.round(20/12*this.settings.chat_font_size));e=".ember-chat .chat-messages .timestamp { font-size: "+t+"px !important; line-height: "+s+"px !important; }"}o.update_css(this._chat_style,"chat_ts_font_size",e)}}},i.prototype.setup_line=function(){jQuery(document.body).on("mouseleave",".tipsy",function(){this.parentElement.removeChild(this)});try{this.aliases=JSON.parse(localStorage.ffz_aliases||"{}")}catch(t){this.log("Error Loading Aliases: "+t),this.aliases={}}var e=this._chat_style=document.createElement("style");e.id="ffz-style-chat",e.type="text/css",document.head.appendChild(e),i.settings_info.chat_font_size.on_update.bind(this)(this.settings.chat_font_size),document.body.classList.toggle("ffz-chat-colors",!this.has_bttv&&"-1"!==this.settings.fix_color),document.body.classList.toggle("ffz-chat-colors-gray",!this.has_bttv&&"-1"===this.settings.fix_color),document.body.classList.toggle("ffz-legacy-badges",this.settings.legacy_badges),document.body.classList.toggle("ffz-chat-background",!this.has_bttv&&this.settings.chat_rows),document.body.classList.toggle("ffz-chat-separator",!this.has_bttv&&"0"!==this.settings.chat_separators),document.body.classList.toggle("ffz-chat-separator-3d",!this.has_bttv&&"2"===this.settings.chat_separators),document.body.classList.toggle("ffz-chat-padding",!this.has_bttv&&this.settings.chat_padding),document.body.classList.toggle("ffz-chat-purge-icon",!this.has_bttv&&this.settings.line_purge_icon),document.body.classList.toggle("ffz-high-contrast-chat-text",!this.has_bttv&&"1"===this.settings.high_contrast_chat[2]),document.body.classList.toggle("ffz-high-contrast-chat-bold",!this.has_bttv&&"1"===this.settings.high_contrast_chat[1]),document.body.classList.toggle("ffz-high-contrast-chat-bg",!this.has_bttv&&"1"===this.settings.high_contrast_chat[0]),this._last_row={},this.log("Hooking the Ember Whisper Line component.");var s=App.__container__.resolve("component:whisper-line");s&&this._modify_line(s),this.log("Hooking the Ember Message Line component.");var n=App.__container__.resolve("component:message-line");n&&this._modify_line(n);var o=this.get_user();o&&o.name&&(i.capitalization[o.login]=[o.name,Date.now()])},i.prototype.save_aliases=function(){this.log("Saving "+Object.keys(this.aliases).length+" aliases to local storage."),localStorage.ffz_aliases=JSON.stringify(this.aliases)},i.prototype._modify_line=function(e){var s=this,n=App.__container__.lookup("controller:layout"),a=App.__container__.lookup("controller:settings");e.reopen({tokenizedMessage:function(){var t=this.get("msgObject.cachedTokens");if(t)return t;t=this._super();var e=performance.now(),n=s.get_user(),o=n&&this.get("msgObject.from")===n.login;t=s._remove_banned(t),t=s._emoticonize(this,t),s.settings.parse_emoji&&(t=s.tokenize_emoji(t));var a=this.get("msgObject.tags.display-name");a&&a.length&&(i.capitalization[this.get("msgObject.from")]=[a.trim(),Date.now()]),o||(t=s.tokenize_mentions(t));for(var r=0;r5&&s.log("Tokenizing Message Took Too Long - "+(u-e)+"ms",t,!1,!0),this.set("msgObject.cachedTokens",t),t}.property("msgObject.message","isChannelLinksDisabled","currentUserNick","msgObject.from","msgObject.tags.emotes"),ffzUpdated:Ember.observer("msgObject.ffz_deleted","msgObject.ffz_old_messages",function(){this.rerender()}),click:function(e){if(e.target&&e.target.classList.contains("ffz-old-messages"))return s._show_deleted(this.get("msgObject.room"));if(e.target&&e.target.classList.contains("deleted-link"))return s._deleted_link_click.bind(e.target)(e);if(e.target&&e.target.classList.contains("mod-icon")&&(jQuery(e.target).trigger("mouseout"),e.target.classList.contains("purge"))){var n=this.get("msgObject.from"),i=this.get("msgObject.room"),o=i&&s.rooms[i]&&s.rooms[i].room;return void(o&&(o.send("/timeout "+n+" 1"),o.clearMessages(n)))}if((e.shiftKey||e.shiftLeft)&&s.settings.clickable_emoticons&&e.target&&e.target.classList.contains("emoticon")){var a=e.target.getAttribute("data-emote");a?t.open("https://twitchemotes.com/emote/"+a):(a=e.target.getAttribute("data-ffz-emote"),t.open("https://www.frankerfacez.com/emoticons/"+a))}return this._super(e)},ffzUserLevel:function(){return this.get("isStaff")?5:this.get("isAdmin")?4:this.get("isBroadcaster")?3:this.get("isGlobalModerator")?2:this.get("isModerator")?1:0}.property("msgObject.labels.[]"),render:function(t){var e=this.get("msgObject.deleted"),i={},r=this.get("msgObject.from"),d=this.get("msgObject.room"),u=s.rooms&&s.rooms[d],c=this.get("msgObject.to"),l=c&&c.length,h=this.get("ffzUserLevel"),f=u&&u.room&&u.room.get("ffzUserLevel")||0,m=this.get("msgObject.ffz_alternate"),_=this.get("msgObject.color"),p=_&&s._handle_color(_),g=n&&n.get("isTheatreMode")||a&&a.get("model.darkMode");void 0===m&&(m=s._last_row[d]=s._last_row.hasOwnProperty(d)?!s._last_row[d]:!1,this.set("msgObject.ffz_alternate",m)),t.push('
'),t.push(''+this.get("timestamp")+" "),!l&&f>h&&(t.push(''),t.push(e?'Unban':'Ban'),t.push('Timeout'),t.push('Purge'),t.push("")),!l&&this.get("isBroadcaster")?i[0]={klass:"broadcaster",title:"Broadcaster"}:this.get("isStaff")?i[0]={klass:"staff",title:"Staff"}:this.get("isAdmin")?i[0]={klass:"admin",title:"Admin"}:this.get("isGlobalMod")?i[0]={klass:"global-moderator",title:"Global Moderator"}:!l&&this.get("isModerator")&&(i[0]={klass:"moderator",title:"Moderator"}),!l&&this.get("isSubscriber")&&(i[10]={klass:"subscriber",title:"Subscriber"}),this.get("hasTurbo")&&(i[15]={klass:"turbo",title:"Turbo"}),i=s.render_badges(this,i),t.push('');for(var v in i){var b=i[v],y=b.image?"background-image:url(""+b.image+"");":"";b.color&&(y+="background-color:"+b.color+";"),b.extra_css&&(y+=b.extra_css),t.push('
')}t.push("
");var w=s.aliases[r],z=this.get("msgObject.tags.display-name")||r&&r.capitalize()||"unknown user",k=p&&"color:"+(g?p[1]:p[0]),C=k?" has-color":"";if(t.push(w?''+o.sanitize(w)+"":''+o.sanitize(z)+""),l){var E=s.aliases[c],x=this.get("msgObject.tags.recipient-display-name")||c&&c.capitalize()||"unknown user",T=this.get("msgObject.toColor"),L=T&&s._handle_color(T),M=T&&"color:"+(g?L[1]:L[0]),S=M?" has-color":"";this._renderWhisperArrow(t),t.push(E?''+o.sanitize(E)+"":''+o.sanitize(x)+"")}if(t.push(': '),"action"!==this.get("msgObject.style")&&(k="",C=""),e)t.push('<message deleted>');else{t.push(''),t.push(s.render_tokens(this.get("tokenizedMessage"),!0));var A=this.get("msgObject.ffz_old_messages");A&&A.length&&t.push('
Show '+o.number_commas(A.length)+" Old
"),t.push("
")}},classNameBindings:["msgObject.ffz_alternate:ffz-alternate","msgObject.ffz_has_mention:ffz-mentioned","ffzWasDeleted:ffz-deleted","ffzHasOldMessages:clearfix","ffzHasOldMessages:ffz-has-deleted"],ffzWasDeleted:function(){return s.settings.prevent_clear&&this.get("msgObject.ffz_deleted")}.property("msgObject.ffz_deleted"),ffzHasOldMessages:function(){var t=this.get("msgObject.ffz_old_messages");return t&&t.length}.property("msgObject.ffz_old_messages"),didInsertElement:function(){this._super();var t=this.get("element");t.setAttribute("data-room",this.get("msgObject.room")),t.setAttribute("data-sender",this.get("msgObject.from")),t.setAttribute("data-deleted",this.get("msgObject.deleted")||!1)}})},i.capitalization={},i._cap_fetching=0,i.get_capitalization=function(t,e){if(!t)return t;if(t=t.toLowerCase(),"jtv"==t||"twitchnotify"==t)return t;var s=i.capitalization[t];return s&&Date.now()-s[1]<36e5?s[0]:(i._cap_fetching<25&&(i._cap_fetching++,i.get().ws_send("get_display_name",t,function(s,n){var o=s?n:t;i.capitalization[t]=[o,Date.now()],i._cap_fetching--,"function"==typeof e&&e(o)})),s?s[0]:t)},i.prototype._remove_banned=function(t){var e=this.settings.banned_words,s=["j.mp","bit.ly"],n=e&&e.length;if(!(n||s&&s.length))return t;"string"==typeof t&&(t=[t]);for(var o=i._words_to_regex(e),a=i._words_to_regex(s),r=[],d=0;d',c='',l={},h=function(t){ -if(1===t)return"Purge";if(l[t])return l[t];var e,s,n,i,o;e=Math.floor(t/604800),o=t%604800,s=Math.floor(o/86400),o%=86400,n=Math.floor(o/3600),o%=3600,i=Math.floor(o/60),o%=60;var a=l[t]=(e?e+"w":"")+(s||e&&(n||i||o)?s+"d":"")+(n||(e||s)&&(i||o)?n+"h":"")+(i||(e||s||n)&&o?i+"m":"")+(o?o+"s":"");return a};try{i=t.require&&t.require("ember-twitch-chat/helpers/chat-line-helpers")}catch(f){}o.basic_settings.enhanced_moderation_cards={type:"boolean",no_bttv:!0,category:"Chat",name:"Enhanced Moderation Cards",help:"Improve moderation cards with hotkeys, additional buttons, chat history, and other information to make moderating easier.",get:function(){return this.settings.mod_card_hotkeys&&this.settings.mod_card_info&&this.settings.mod_card_history},set:function(t){this.settings.set("mod_card_hotkeys",t),this.settings.set("mod_card_info",t),this.settings.set("mod_card_history",t)}},o.basic_settings.chat_hover_pause={type:"boolean",no_bttv:!0,category:"Chat",name:"Pause Chat Scrolling on Mouse Hover",help:"Automatically prevent the chat from scrolling when moving the mouse over it to prevent moderation mistakes and link misclicks.",get:"chat_hover_pause",set:"chat_hover_pause"},o.settings_info.chat_hover_pause={type:"boolean",value:!1,no_bttv:!0,category:"Chat Moderation",name:"Pause Chat Scrolling on Mouse Hover",help:"Automatically prevent the chat from scrolling when moving the mouse over it to prevent moderation mistakes and link misclicks.",on_update:function(t){this._roomv&&(t?this._roomv.ffzEnableFreeze():this._roomv.ffzDisableFreeze())}},o.settings_info.short_commands={type:"boolean",value:!0,no_bttv:!0,category:"Chat Moderation",name:"Short Moderation Commands",help:"Use /t, /b, and /u in chat in place of /timeout, /ban, /unban for quicker moderation, and use /p for 1 second timeouts."},o.settings_info.mod_card_hotkeys={type:"boolean",value:!1,no_bttv:!0,category:"Chat Moderation",name:"Moderation Card Hotkeys",help:"With a moderation card selected, press B to ban the user, T to time them out for 10 minutes, P to time them out for 1 second, or U to unban them. ESC closes the card."},o.settings_info.mod_card_info={type:"boolean",value:!0,no_bttv:!0,category:"Chat Moderation",name:"Moderation Card Additional Information",help:"Display a channel's follower count, view count, and account age on moderation cards."},o.settings_info.mod_card_history={type:"boolean",value:!1,no_bttv:!0,category:"Chat Moderation",name:"Moderation Card History",help:"Display a few of the user's previously sent messages on moderation cards.",on_update:function(t){if(!t&&this.rooms)for(var e in this.rooms){var s=this.rooms[e];s&&(s.user_history=void 0)}}},o.settings_info.mod_card_buttons={type:"button",value:[],category:"Chat Moderation",no_bttv:!0,name:"Moderation Card Additional Buttons",help:"Add additional buttons to moderation cards for running chat commands on those users.",method:function(){for(var t="",e=0;e0&&s.push(i)}this.settings.set("mod_card_durations",s)}}},o.prototype.setup_mod_card=function(){this.log("Modifying Mousetrap stopCallback so we can catch ESC.");var e=Mousetrap.stopCallback;Mousetrap.stopCallback=function(t,s,n){return s.classList.contains("no-mousetrap")?!0:e(t,s,n)},Mousetrap.bind("up up down down left right left right b a enter",function(){var t=document.querySelector(".app-main")||document.querySelector(".ember-chat-container");t&&t.classList.toggle("ffz-flip")}),this.log("Hooking the Ember Moderation Card view.");var s=App.__container__.resolve("component:moderation-card"),n=this;s.reopen({ffzForceRedraw:function(){this.rerender()}.observes("cardInfo.isModeratorOrHigher","cardInfo.user"),ffzRebuildInfo:function(){var t=this.get("element"),e=t&&t.querySelector(".info");if(e){var s=''+r.EYE+" "+a.number_commas(this.get("cardInfo.user.views")||0)+"",n=a.parse_date(this.get("cardInfo.user.created_at")||""),i=this.get("cardInfo.user.ffz_followers");if("number"==typeof i)s+=''+r.HEART+" "+a.number_commas(i||0)+"";else if(void 0===i){var o=this;this.set("cardInfo.user.ffz_followers",!1),Twitch.api.get("channels/"+this.get("cardInfo.user.id")+"/follows",{limit:1}).done(function(t){o.set("cardInfo.user.ffz_followers",t._total),o.ffzRebuildInfo()}).fail(function(t){o.set("cardInfo.user.ffz_followers",void 0)})}if(n){var d=Math.floor((Date.now()-n.getTime())/1e3);d>0&&(s+=''+r.CLOCK+" "+a.human_time(d,10)+"")}e.innerHTML=s}}.observes("cardInfo.user.views"),userName:Ember.computed("cardInfo.user.id","cardInfo.user.display_name",function(){var t=this.get("cardInfo.user.id"),e=n.aliases[t];return e||this.get("cardInfo.user.display_name")||t.capitalize()}),didInsertElement:function(){this._super(),t._card=this;try{if(n.has_bttv)return;var e,s=this.get("element"),o=this.get("controller"),l=o.get("cardInfo.user.id"),f=n.aliases[l];if(f){var m=s.querySelector("h3.name"),p=m&&m.querySelector("a");p&&(m=p),m&&(m.classList.add("ffz-alias"),m.title=a.sanitize(o.get("cardInfo.user.display_name")||l.capitalize()),jQuery(m).tipsy())}if(s.classList.add("ffz-moderation-card"),n.settings.mod_card_info){var g=document.createElement("div"),v=s.querySelector("h3.name");v&&(s.classList.add("ffz-has-info"),g.className="info channel-stats",v.parentElement.insertBefore(g,v.nextSibling),this.ffzRebuildInfo())}if(n.settings.mod_card_buttons&&n.settings.mod_card_buttons.length){e=document.createElement("div"),e.className="extra-interface interface clearfix";for(var b={},y=function(t){var e=o.get("cardInfo.user.id"),s=App.__container__.lookup("controller:chat"),n=s&&s.get("currentRoom");n&&n.send(t.replace(/{user}/g,e))},w=function(t){var e=document.createElement("button"),s=t.split(" ",1)[0],n=b[s]>1?t.split(" ",b[s]):[s];return/^[!~./]/.test(n[0])&&(n[0]=n[0].substr(1)),n=_.map(n,function(t){return t.capitalize()}).join(" "),e.className="button",e.innerHTML=a.sanitize(n),e.title=a.sanitize(t.replace(/{user}/g,o.get("cardInfo.user.id")||"{user}")),jQuery(e).tipsy(),e.addEventListener("click",y.bind(this,t)),e},b={},z=0;z button.message-button");if(R){R.innerHTML="W",R.classList.add("glyph-only"),R.classList.add("message"),R.title="Whisper User",jQuery(R).tipsy();var H=document.createElement("button");H.className="message-button button glyph-only message tooltip",H.innerHTML=u,H.title="Message User",H.addEventListener("click",function(){t.open("http://www.twitch.tv/message/compose?to="+o.get("cardInfo.user.id"))}),R.parentElement.insertBefore(H,R.nextSibling)}var O=document.createElement("button");if(O.className="alias button glyph-only tooltip",O.innerHTML=r.EDIT,O.title="Set Alias",O.addEventListener("click",function(){var t=o.get("cardInfo.user.id"),e=n.aliases[t],i=prompt("Alias for User: "+t+"\n\nPlease enter an alias for the user. Leave it blank to remove the alias.",e);if(null!==i&&void 0!==i){i=i.trim(),i||(i=void 0),n.aliases[t]=i,n.save_aliases(),n._update_alias(t),Ember.propertyDidChange(o,"userName");var a=s.querySelector("h3.name"),r=a&&a.querySelector("a");r&&(a=r),a&&a.classList.toggle("ffz-alias",i)}}),R)R.parentElement.insertBefore(O,R);else{var I=s.querySelector(".interface > .follow-button");I&&I.parentElement.insertBefore(O,I.nextSibling)}if(n.settings.mod_card_history){var B=App.__container__.lookup("controller:chat"),D=B&&B.get("currentRoom"),N=D&&n.rooms&&n.rooms[D.get("id")],j=N&&N.user_history&&N.user_history[o.get("cardInfo.user.id")];if(j&&j.length){var P=document.createElement("ul"),U=!1;P.className="interface clearfix chat-history";for(var z=0;z'+i.getTime(e.date)+" ":"")+''+("action"===e.style?"*"+e.from+" ":"")+n.render_tokens(e.cachedTokens)+"";for(var V=q.querySelectorAll("a.deleted-link"),W=0;WG.bottom){var Y=Z.bottom-G.bottom;Z.top-Y>G.top&&(s.style.top=Z.top-Y+"px")}this.$().draggable({start:function(){s.focus()}}),s.focus()}catch($){try{n.error("ModerationCardView didInsertElement: "+$)}catch($){}}}})},o.prototype._update_alias=function(t){var e=this.aliases&&this.aliases[t],s=o.get_capitalization(t),n=e||s,i=this._roomv&&this._roomv.get("element"),a=i&&i.querySelectorAll('.chat-line[data-sender="'+t+'"]');if(a)for(var r=0,d=a.length;d>r;r++){var u=a[r],c=u.querySelector(".from");c.classList.toggle("ffz-alias",e),c.textContent=n,c.title=e?s:""}},o.chat_commands.purge=function(t,e){if(!e||!e.length)return"Purge Usage: /p username [more usernames separated by spaces]";if(e.length>10)return"Please only purge up to 10 users at once.";for(var s=0;s10)return"Please only ban up to 10 users at once.";for(var s=0;s10)return"Please only unban up to 10 users at once.";for(var s=0;s750&&this.ffzUnfreeze()}},ffzUnfreeze:function(){this.ffz_frozen=!1,this._ffz_last_move=0,this.ffzUnwarnPaused(),this.get("stuckToBottom")&&this._scrollToBottom()},ffzMouseDown:function(t){var s=this._$chatMessagesScroller;if(s&&s[0]&&(!this.ffz_frozen&&"mousedown"===t.type||"mousewheel"===t.type||d&&"scroll"===t.type)){"mousedown"===t.type&&e.log("Freezing from mouse down!",t);var n=s[0].scrollHeight-s[0].scrollTop-s[0].offsetHeight;this._setStuckToBottom(10>=n)}},ffzMouseOut:function(t){this._ffz_outside=!0;var e=this;setTimeout(function(){e._ffz_outside&&e.ffzUnfreeze()},25)},ffzMouseMove:function(t){this._ffz_last_move=Date.now(),this._ffz_outside=!1,(t.screenX!==this._ffz_last_screenx||t.screenY!==this._ffz_last_screeny)&&(this._ffz_last_screenx=t.screenX,this._ffz_last_screeny=t.screenY,this.ffz_frozen||(this.ffz_frozen=!0,this.get("stuckToBottom")&&(this.set("controller.model.messageBufferSize",e.settings.scrollback_length+150),this.ffzWarnPaused())))},_scrollToBottom:_.throttle(function(){var t=this,e=this._$chatMessagesScroller;Ember.run.next(function(){setTimeout(function(){!t.ffz_frozen&&e&&e.length&&(e[0].scrollTop=e[0].scrollHeight,t._setStuckToBottom(!0))})})},200),_setStuckToBottom:function(t){this.set("stuckToBottom",t),this.get("controller.model")&&this.set("controller.model.messageBufferSize",e.settings.scrollback_length+(t?0:150)),t||this.ffzUnfreeze()},ffzWarnPaused:function(){var t=this.get("element"),e=t&&t.querySelector(".chat-interface .more-messages-indicator.ffz-freeze-indicator");if(t){if(!e){e=document.createElement("div"),e.className="more-messages-indicator ffz-freeze-indicator",e.innerHTML="(Chat Paused Due to Mouse Movement)";var s=t.querySelector(".chat-interface");if(!s)return;s.insertBefore(e,s.childNodes[0])}e.classList.remove("hidden")}},ffzUnwarnPaused:function(){var t=this.get("element"),e=t&&t.querySelector(".chat-interface .more-messages-indicator.ffz-freeze-indicator");e&&e.classList.add("hidden")}})},i.chat_commands={},i.ffz_commands={},i.prototype.room_message=function(t,e){var s=e.split("\n");if(this.has_bttv)for(var n=0;n300,f=e.length,m=n.get("messages.0.ffz_alternate")||!1;h&&(m=!m);for(var f=e.length;f--;){var _=e[f];if("string"==typeof _.date&&(_.date=r.parse_date(_.date)),_.ffz_alternate=m=!m,_.room||(_.room=t),_.color||(_.color=_.tags&&_.tags.color?_.tags.color:o&&_.from?o.getColor(_.from.toLowerCase()):"#755000"),!_.labels||!_.labels.length){var p=_.labels=[];if(_.tags)if(_.tags.turbo&&p.push("turbo"),_.tags.subscriber&&p.push("subscriber"),_.from===t)p.push("owner");else{var g=_.tags["user-type"];("mod"===g||"staff"===g||"admin"===g||"global_mod"===g)&&p.push(g)}}if(_.style||("jtv"===_.from?_.style="admin":"twitchnotify"===_.from&&(_.style="notification")),_.cachedTokens&&_.cachedTokens.length||this.tokenize_chat_line(_,!0,n.get("roomProperties.hide_chat_links")),n.shouldShowMessage(_)){if(!(i.lengthv&&(_.ffz_old_messages=_.ffz_old_messages.slice(_.ffz_old_messages.length-v))}i.unshiftObject(_),a+=1}}if(h){var _={ffz_alternate:!m,color:"#755000",date:new Date,from:"frankerfacez_admin",style:"admin",message:"(Last message is "+r.human_time(l)+" old.)",room:t};if(this.tokenize_chat_line(_,!0,n.get("roomProperties.hide_chat_links")),n.shouldShowMessage(_))for(i.insertAt(a,_);i.length>n.get("messageBufferSize");)i.removeAt(0)}}},i.prototype.load_room=function(t,e,s){var n=this;jQuery.getJSON(((s||0)%2===0?a.API_SERVER:a.API_SERVER_2)+"v1/room/"+t).done(function(s){if(s.sets)for(var i in s.sets)s.sets.hasOwnProperty(i)&&n._load_set_json(i,void 0,s.sets[i]);n._load_room_json(t,e,s)}).fail(function(i){return 404==i.status?"function"==typeof e&&e(!1):(s=(s||0)+1,10>s?n.load_room(t,e,s):"function"==typeof e&&e(!1))})},i.prototype._load_room_json=function(t,e,s){if(!s||!s.room)return"function"==typeof e&&e(!1);s=s.room,this.rooms[t]&&(s.room=this.rooms[t].room);for(var n in this.rooms[t])"room"!==n&&this.rooms[t].hasOwnProperty(n)&&!s.hasOwnProperty(n)&&(s[n]=this.rooms[t][n]);s.needs_history=this.rooms[t]&&this.rooms[t].needs_history||!1,this.rooms[t]=s,(s.css||s.moderator_badge)&&r.update_css(this._room_style,t,u(s)+(s.css||"")),this.emote_sets.hasOwnProperty(s.set)?-1===this.emote_sets[s.set].users.indexOf(t)&&this.emote_sets[s.set].users.push(t):this.load_set(s.set,function(e,s){-1===s.users.indexOf(t)&&s.users.push(t)}),this.update_ui_link(),e&&e(!0,s)},i.prototype._modify_room=function(e){var s=this;e.reopen({slowWaiting:!1,slow:0,mru_list:[],updateWait:function(t,e){var n=this.get("slowWait")||0;this.set("slowWait",t),1>n&&t>0?(this._ffz_wait_timer&&clearTimeout(this._ffz_wait_timer),this._ffz_wait_timer=setTimeout(this.ffzUpdateWait.bind(this),1e3),s._roomv&&s._roomv.ffzUpdateStatus()):(n>0&&1>t||e)&&(this.set("ffz_banned",!1),s._roomv&&s._roomv.ffzUpdateStatus())},ffzUpdateWait:function(){this._ffz_wait_timer=void 0;var t=this.get("slowWait")||0;1>t||(this.set("slowWait",--t),t>0?this._ffz_wait_timer=setTimeout(this.ffzUpdateWait.bind(this),1e3):(this.set("ffz_banned",!1),s._roomv&&s._roomv.ffzUpdateStatus()))},ffzUpdateStatus:function(){s._roomv&&s._roomv.ffzUpdateStatus()}.observes("r9k","subsOnly","slow","ffz_banned"),ffzUserLevel:function(){return this.get("isStaff")?5:this.get("isAdmin")?4:this.get("isBroadcaster")?3:this.get("isGlobalModerator")?2:this.get("isModerator")?1:0}.property("id","chatLabels.[]"),init:function(){this._super();try{s.add_room(this.id,this),this.set("ffz_chatters",{})}catch(t){s.error("add_room: "+t)}},willDestroy:function(){this._super();try{s.remove_room(this.id)}catch(t){s.error("remove_room: "+t)}},clearMessages:function(t){var e=this;if(t){for(this.ffzRecentlyBanned||(this.ffzRecentlyBanned=[]),this.ffzRecentlyBanned.push(t);this.ffzRecentlyBanned.length>100;)this.ffzRecentlyBanned.shift();for(var n,i=e.get("messages"),o=i.get("length"),a=o;a--;){var d=i.get(a);if(d.from===t){if(s.settings.remove_deleted){void 0===n&&(n=d.ffz_alternate),i.removeAt(a);continue}e.set("messages."+a+".ffz_deleted",!0),s.settings.prevent_clear||e.set("messages."+a+".deleted",!0)}void 0===n?n=d.ffz_alternate:(n=!n,e.set("messages."+a+".ffz_alternate",n))}if(e.ffzPending)for(i=e.ffzPending,a=i.length;a--;){var d=i.get(a);d.from===t&&(d.ffz_deleted=!0,d.deleted=!s.settings.prevent_clear,d.removed=s.settings.remove_deleted)}if(s.settings.mod_card_history){var u=s.rooms&&s.rooms[e.get("id")],c=u&&u.user_history&&u.user_history[t];if(null!==c&&void 0!==c){var l=!1,h=c.length>0?c[c.length-1]:null;if(l=null!==h&&h.is_delete)h.cachedTokens=["User has been timed out "+r.number_commas(++h.deleted_times)+" times."];else for(c.push({from:"jtv",is_delete:!0,style:"admin",cachedTokens:["User has been timed out."],deleted_times:1,date:new Date});c.length>20;)c.shift()}}}else if(s.settings.prevent_clear)this.addTmiMessage("A moderator's attempt to clear chat was ignored.");else{var i=e.get("messages");e.set("messages",[]),e.addMessage({style:"admin",message:i18n("Chat was cleared by a moderator"),ffz_old_messages:i})}},trimMessages:function(){var t=this.get("messages"),e=t.get("length"),s=this.get("messageBufferSize");e>s&&t.removeAt(0,e-s)},pushMessage:function(t){+s.settings.chat_delay?(this.ffzPending||(this.ffzPending=[]),this.ffzPending.length||setTimeout(this.ffzPendingFlush.bind(this),100),t.time=Date.now(),this.ffzPending.push(t)):this.ffzActualPushMessage(t)},ffzActualPushMessage:function(t){this.shouldShowMessage(t)&&this.ffzShouldShowMessage(t)&&(this.get("messages").pushObject(t), -this.trimMessages(),"admin"===t.style||"whisper"===t.style&&!this.ffz_whisper_room||this.incrementProperty("unreadCount",1))},ffzPendingFlush:function(){for(var t=Date.now(),e=0,n=this.ffzPending.length;n>e;e++){var i=this.ffzPending[e];if(!i.removed){if(+s.settings.chat_delay+i.time>t)break;this.ffzActualPushMessage(i)}}this.ffzPending=this.ffzPending.slice(e),this.ffzPending.length&&setTimeout(this.ffzPendingFlush.bind(this),100)},ffzShouldShowMessage:function(t){if(s.settings.remove_bot_ban_notices&&this.ffzRecentlyBanned){var e="("+this.ffzRecentlyBanned.join("|")+")",n={nightbot:"^"+e,moobot:"\\("+e+"\\)",xanbot:"^"+e};if(t.from in n&&new RegExp(n[t.from]).test(t.message))return!1}return!0},addMessage:function(t){if(t){if(!s.settings.hosted_sub_notices&&"notification"===t.style&&o.test(t.message))return;var e="whisper"===t.style;if(s.settings.group_tabs&&s.settings.whisper_room&&(e&&!this.ffz_whisper_room||!e&&this.ffz_whisper_room))return;if(e||(t.room=this.get("id")),s.tokenize_chat_line(t,!1,this.get("roomProperties.hide_chat_links")),!e&&t.from&&"jtv"!==t.from&&"twitchnotify"!==t.from&&s.settings.mod_card_history){var n=s.rooms&&s.rooms[t.room];if(n){var i=(n.user_history=n.user_history||{},n.user_history[t.from]=n.user_history[t.from]||[]);i.push({from:t.tags&&t.tags["display-name"]||t.from,cachedTokens:t.cachedTokens,style:t.style,date:t.date}),i.length>20&&i.shift()}}if(!e){var a=s.get_user();if(a&&a.login===t.from){var r=this.get("ffz_banned");this.set("ffz_banned",!1),this.get("isSubscriber")||this.get("isModeratorOrHigher")||!this.get("slowMode")?this.updateWait(0,r):this.get("slowMode")&&this.updateWait(this.get("slow"))}}e||!this.chatters||this.chatters[t.from]||"twitchnotify"===t.from||"jtv"===t.from||this.ffzUpdateChatters(t.from)}var d=this._super(t);return t.color&&s._handle_color(t.color),d},setHostMode:function(t){this.set("ffz_host_target",t&&t.hostTarget||null);var e=s.get_user();e&&s._cindex&&this.get("id")===e.login&&s._cindex.ffzUpdateHostButton();var n=App.__container__.lookup("controller:chat");if(n&&n.get("currentChannelRoom")===this)return this._super(t)},send:function(t){if(!(s.settings.group_tabs&&s.settings.whisper_room&&this.ffz_whisper_room)){try{if(t){var e=this.get("mru_list"),n=e.indexOf(t);-1!==n?e.splice(n,1):e.length>20&&e.pop(),e.unshift(t)}var i=t.split(" ",1)[0].toLowerCase();if("/ffz"===i)return this.set("messageToSend",""),void s.run_ffz_command(t.substr(5),this.get("id"));if("/"===i.charAt(0)&&s.run_command(t,this.get("id")))return void this.set("messageToSend","")}catch(o){s.error("send: "+o)}return this._super(t)}},ffzUpdateUnread:function(){if(s.settings.group_tabs){var t=App.__container__.lookup("controller:chat");t&&t.get("currentRoom")===this?this.resetUnreadCount():s._chatv&&s._chatv.ffzTabUnread(this.get("id"))}}.observes("unreadCount"),ffzInitChatterCount:function(){if(this.tmiRoom){this._ffz_chatter_timer&&(clearTimeout(this._ffz_chatter_timer),this._ffz_chatter_timer=void 0);var t=this;this.tmiRoom.list().done(function(e){var s={};e=e.data.chatters;for(var n=0;n0}.property("slow"),onSlowOff:function(){this.get("slowMode")||this.updateWait(0)}.observes("slowMode")})}},{"../constants":5,"../utils":35}],14:[function(e,s,n){var i=t.FrankerFaceZ;i.prototype.setup_viewers=function(){this.log("Hooking the Ember Viewers controller.");var t=App.__container__.resolve("controller:viewers");this._modify_viewers(t)},i.prototype._modify_viewers=function(t){var e=this;t.reopen({lines:function(){var t=this._super();try{var s=[],n={},o=null,a=App.__container__.lookup("controller:channel"),r=this.get("parentController.model.id"),d=a&&a.get("id");if(d){var u=a.get("display_name");u&&(i.capitalization[d]=[u,Date.now()])}r!=d&&(d=null);for(var c=0;ce?String.fromCharCode(e):(e-=65536,String.fromCharCode(55296+(e>>10),56320+(1023&e)))};i.prototype.setup_emoticons=function(){this.log("Preparing emoticon system."),this.emoji_data={},this.emoji_names={},this.emote_sets={},this.global_sets=[],this.default_sets=[],this._last_emote_id=0,this.emote_usage={},this.log("Creating emoticon style element.");var t=this._emote_style=document.createElement("style");t.id="ffz-emoticon-css",document.head.appendChild(t),this.log("Loading global emote sets."),this.load_global_sets(),this.log("Loading emoji data."),this.load_emoji_data(),this.log("Watching Twitch emoticon parser to ensure it loads."),this._twitch_emote_check=setTimeout(this.check_twitch_emotes.bind(this),1e4)},i.prototype.add_usage=function(t,e,s){var n=this.emote_usage[e]=this.emote_usage[e]||{};n[t]=(n[t]||0)+(s||1),this._emote_report_scheduled||(this._emote_report_scheduled=setTimeout(this._report_emotes.bind(this),3e4))},i.prototype._report_emotes=function(){this._emote_report_scheduled&&delete this._emote_report_scheduled;var t=this.emote_usage;this.emote_usage={},this.ws_send("emoticon_uses",[t],function(){},!0)},i.prototype.check_twitch_emotes=function(){this._twitch_emote_check&&(clearTimeout(this._twitch_emote_check),delete this._twitch_emote_check);var t;if(this.rooms)for(var e in this.rooms)if(this.rooms.hasOwnProperty(e)){t=this.rooms[e];break}if(!t||!t.room||!t.room.tmiSession)return void(this._twitch_emote_check=setTimeout(this.check_twitch_emotes.bind(this),1e4));var s=t.room.tmiSession._emotesParser,n=Object.keys(s.emoticonRegexToIds).length;if(!(n>0)){var i=s.emoticonSetIds;s.emoticonSetIds="",s.updateEmoticons(i),this._twitch_emote_check=setTimeout(this.check_twitch_emotes.bind(this),1e4)}},i.prototype.getEmotes=function(t,e){var s=this.users&&this.users[t],n=this.rooms&&this.rooms[e];return _.union(s&&s.sets||[],n&&n.set&&[n.set]||[],n&&n.extra_sets||[],this.default_sets)},i.ws_commands.reload_set=function(t){this.emote_sets.hasOwnProperty(t)&&this.load_set(t)},i.ws_commands.load_set=function(t){this.load_set(t)},i.prototype._emote_tooltip=function(t){if(!t)return null;if(t._tooltip)return t._tooltip;var e=this.emote_sets[t.set_id],s=t.owner,n=e&&e.title||"Global";return t._tooltip="Emoticon: "+(t.hidden?"???":t.name)+"\nFFZ "+n+(s?"\nBy: "+s.display_name:""),t._tooltip},i.prototype.load_emoji_data=function(t,e){var s=this;jQuery.getJSON(o.SERVER+"emoji/emoji.json").done(function(e){var n={},i={};for(var a in e){var r=e[a];a=a.toLowerCase(),r.code=a,n[a]=r,i[r.short_name]=a,r.raw=_.map(r.code.split("-"),c).join(""),r.src=o.SERVER+"emoji/"+a+"-1x.png",r.srcSet=r.src+" 1x, "+o.SERVER+"emoji/"+a+"-2x.png 2x, "+o.SERVER+"emoji/"+a+"-4x.png 4x",r.token={srcSet:r.srcSet,emoticonSrc:r.src,ffzEmoji:a,altText:r.raw}}s.emoji_data=n,s.emoji_names=i,s.log("Loaded data on "+Object.keys(n).length+" emoji."),"function"==typeof t&&t(!0,e)}).fail(function(n){return 404===n.status?"function"==typeof t&&t(!1):(e=(e||0)+1,50>e?s.load_emoji(t,e):"function"==typeof t&&t(!1))})},i.prototype.load_global_sets=function(t,e){var s=this;jQuery.getJSON(((e||0)%2===0?o.API_SERVER:o.API_SERVER_2)+"v1/set/global").done(function(t){s.default_sets=t.default_sets;var e=s.global_sets=[],n=t.sets||{};s.feature_friday&&s.feature_friday.set&&(-1===s.global_sets.indexOf(s.feature_friday.set)&&s.global_sets.push(s.feature_friday.set),-1===s.default_sets.indexOf(s.feature_friday.set)&&s.default_sets.push(s.feature_friday.set));for(var i in n)if(n.hasOwnProperty(i)){var o=n[i];e.push(i),s._load_set_json(i,void 0,o)}}).fail(function(n){return 404==n.status?"function"==typeof t&&t(!1):(e=e||0,e++,50>e?s.load_global_sets(t,e):"function"==typeof t&&t(!1))})},i.prototype.load_set=function(t,e,s){var n=this;jQuery.getJSON(((s||0)%2===0?o.API_SERVER:o.API_SERVER_2)+"v1/set/"+t).done(function(s){n._load_set_json(t,e,s&&s.set)}).fail(function(i){return 404==i.status?"function"==typeof e&&e(!1):(s=s||0,s++,10>s?n.load_set(t,e,s):"function"==typeof e&&e(!1))})},i.prototype.unload_set=function(t){var e=this.emote_sets[t];e&&(this.log("Unloading emoticons for set: "+t),a.update_css(this._emote_style,t,null),delete this.emote_sets[t])},i.prototype._load_set_json=function(t,e,s){if(!s)return"function"==typeof e&&e(!1);var n=this.emote_sets[t]&&this.emote_sets[t].users||[];this.emote_sets[t]=s,s.users=n,s.count=0;var i="",o=s.emoticons;s.emoticons={};for(var r=0;r=6e4?this.log("BetterTTV was not detected after 60 seconds."):setTimeout(this.find_bttv.bind(this,e,(s||0)+e),e))},i.prototype.setup_bttv=function(t){this.log("BetterTTV was detected after "+t+"ms. Hooking."),this.has_bttv=!0,document.body.classList.remove("ffz-dark"),this._dark_style&&(this._dark_style.parentElement.removeChild(this._dark_style),this._dark_style=void 0),this._layout_style&&(this._layout_style.parentElement.removeChild(this._layout_style),this._layout_style=void 0),this._chat_style&&(o.update_css(this._chat_style,"chat_font_size",""),o.update_css(this._chat_style,"chat_ts_font_size","")),this.settings.group_tabs&&this._chatv&&this._chatv.ffzDisableTabs(),this._roomv&&(this.settings.chat_hover_pause&&this._roomv.ffzDisableFreeze(),this.settings.room_status&&this._roomv.ffzUpdateStatus()),document.body.classList.remove("ffz-chat-colors"),document.body.classList.remove("ffz-chat-colors-gray"),document.body.classList.remove("ffz-chat-background"),document.body.classList.remove("ffz-chat-padding"),document.body.classList.remove("ffz-chat-separator"),document.body.classList.remove("ffz-chat-separator-3d"),document.body.classList.remove("ffz-sidebar-swap"),document.body.classList.remove("ffz-transparent-badges"),document.body.classList.remove("ffz-high-contrast-chat-text"),document.body.classList.remove("ffz-high-contrast-chat-bg"),document.body.classList.remove("ffz-high-contrast-chat-bold"),this.settings.following_count&&(this._schedule_following_count(),this._draw_following_count(),this._draw_following_channels()),this.is_dashboard&&this._update_subscribers(),document.body.classList.add("ffz-bttv");var e=BetterTTV.chat.helpers.sendMessage,s=this;BetterTTV.chat.helpers.sendMessage=function(t){var n=t.split(" ",1)[0].toLowerCase();return"/ffz"!==n?e(t):void s.run_ffz_command(t.substr(5),BetterTTV.chat.store.currentRoom)};var n,i=BetterTTV.chat.handlers.onPrivmsg;BetterTTV.chat.handlers.onPrivmsg=function(t,e){n=t;var s=i(t,e);return n=null,s};var a=BetterTTV.chat.templates.privmsg;BetterTTV.chat.templates.privmsg=function(t,e,i,o,r){try{return s.bttv_badges(r),'
'+BetterTTV.chat.templates.timestamp(r.time)+" "+(o?BetterTTV.chat.templates.modicons():"")+" "+BetterTTV.chat.templates.badges(r.badges)+BetterTTV.chat.templates.from(r.nickname,r.color)+BetterTTV.chat.templates.message(r.sender,r.message,r.emotes,e?r.color:!1)+"
"}catch(d){return s.log("Error: ",d),a(t,e,i,o,r)}};var r=BetterTTV.chat.templates.whisper;BetterTTV.chat.templates.whisper=function(t){try{return s.bttv_badges(t),'
'+BetterTTV.chat.templates.timestamp(t.time)+" "+(t.badges&&t.badges.length?BetterTTV.chat.templates.badges(t.badges):"")+BetterTTV.chat.templates.whisperName(t.sender,t.receiver,t.from,t.to,t.fromColor,t.toColor)+BetterTTV.chat.templates.message(t.sender,t.message,t.emotes,!1)+"
"}catch(e){return s.log("Error: ",e),r(t)}};var d,u=BetterTTV.chat.templates.message;BetterTTV.chat.templates.message=function(t,e,n,i){try{i=i||!1;var o=encodeURIComponent(e);if("jtv"!==t){d=t;var a=BetterTTV.chat.templates.emoticonize(e,n);d=null;for(var r=0;r'+e+""}catch(c){return s.log("Error: ",c),u(t,e,n,i)}};var c=BetterTTV.chat.templates.emoticonize;BetterTTV.chat.templates.emoticonize=function(t,e){var i=c(t,e),o=n||BetterTTV.getChannel(),a=o&&o.toLowerCase(),r=d&&d.toLowerCase(),u=s.getEmotes(r,a),e=[],l=s.get_user(),h=l&&l.login===r;return _.each(u,function(t){var n=s.emote_sets[t];n&&_.each(n.emoticons,function(t){_.any(i,function(e){return _.isString(e)&&e.match(t.regex)})&&e.push(t)})}),e.length&&_.each(e,function(t){var e=s._emote_tooltip(t),n=[''],o=i;i=[];for(var r=0;r=6e4?this.log("Emote Menu for Twitch was not detected after 60 seconds."):setTimeout(this.find_emote_menu.bind(this,e,(s||0)+e),e))},i.prototype.setup_emote_menu=function(t){this.log("Emote Menu for Twitch was detected after "+t+"ms. Registering emote enumerator."),emoteMenu.registerEmoteGetter("FrankerFaceZ",this._emote_menu_enumerator.bind(this))},i.prototype._emote_menu_enumerator=function(){for(var t=this.get_user(),e=t?t.login:null,s=App.__container__.lookup("controller:chat"),n=s?s.get("currentRoom.id"):null,i=this.getEmotes(e,n),o=[],a=0;a=6e4?this.log('Twitch application not detected in "'+location.toString()+'". Aborting.'):setTimeout(this.initialize.bind(this,e,(s||0)+e),e)))}},i.prototype.init_player=function(e){var s=t.performance&&performance.now?performance.now():Date.now();this.log("Found Twitch Player after "+(e||0)+' ms in "'+location+'". Initializing FrankerFaceZ version '+i.version_info),this.users={},this.is_dashboard=!1;try{this.embed_in_dash=t.top!==t&&/\/[^\/]+\/dashboard/.test(t.top.location.pathname)&&!/bookmarks$/.test(t.top.location.pathname)}catch(n){this.embed_in_dash=!1}this.load_settings(),this.setup_dark();var o=t.performance&&performance.now?performance.now():Date.now(),a=o-s;this.log("Initialization complete in "+a+"ms")},i.prototype.init_normal=function(e,s){var n=t.performance&&performance.now?performance.now():Date.now();this.log("Found non-Ember Twitch after "+(e||0)+' ms in "'+location+'". Initializing FrankerFaceZ version '+i.version_info),this.users={},this.is_dashboard=!1;try{this.embed_in_dash=t.top!==t&&/\/[^\/]+\/dashboard/.test(t.top.location.pathname)&&!/bookmarks$/.test(t.top.location.pathname)}catch(o){this.embed_in_dash=!1}this.load_settings(),this.setup_dark(),s||this.ws_create(),this.setup_colors(),this.setup_emoticons(),this.setup_badges(),this.setup_notifications(),this.setup_following_count(!1),this.setup_css(),this.setup_menu(),this.find_bttv(10);var a=t.performance&&performance.now?performance.now():Date.now(),r=a-n;this.log("Initialization complete in "+r+"ms")},i.prototype.is_dashboard=!1,i.prototype.init_dashboard=function(e){var s=t.performance&&performance.now?performance.now():Date.now();this.log("Found Twitch Dashboard after "+(e||0)+' ms in "'+location+'". Initializing FrankerFaceZ version '+i.version_info),this.users={},this.is_dashboard=!0,this.embed_in_dash=!1,this.load_settings(),this.setup_dark(),this.ws_create(),this.setup_colors(),this.setup_emoticons(),this.setup_badges(),this.setup_tokenization(),this.setup_notifications(),this.setup_css(),this._update_subscribers(),this.setup_message_event(),this.find_bttv(10);var n=t.performance&&performance.now?performance.now():Date.now(),o=n-s;this.log("Initialization complete in "+o+"ms")},i.prototype.init_ember=function(e){var s=t.performance&&performance.now?performance.now():Date.now();this.log("Found Twitch application after "+(e||0)+' ms in "'+location+'". Initializing FrankerFaceZ version '+i.version_info),this.users={},this.is_dashboard=!1;try{this.embed_in_dash=t.top!==t&&/\/[^\/]+\/dashboard/.test(t.top.location.pathname)&&!/bookmarks$/.test(t.top.location.pathname)}catch(n){this.embed_in_dash=!1}this.load_settings(),this.setup_dark(),this.ws_create(),this.setup_emoticons(),this.setup_badges(),this.setup_colors(),this.setup_tokenization(),this.setup_channel(),this.setup_room(),this.setup_line(),this.setup_layout(),this.setup_chatview(),this.setup_viewers(),this.setup_mod_card(),this.setup_chat_input(),this.setup_notifications(),this.setup_css(),this.setup_menu(),this.setup_my_emotes(),this.setup_following(),this.setup_following_count(!0),this.setup_races(),this.connect_extra_chat(),this.find_bttv(10),this.find_emote_menu(10),this.check_ff();var o=t.performance&&performance.now?performance.now():Date.now(),a=o-s;this.log("Initialization complete in "+a+"ms")},i.prototype.setup_message_event=function(){this.log("Listening for Window Messages."),t.addEventListener("message",this._on_window_message.bind(this),!1)},i.prototype._on_window_message=function(t){if(t.data&&t.data.from_ffz){t.data}}},{"./badges":2,"./colors":3,"./commands":4,"./debug":6,"./ember/channel":7,"./ember/chat-input":8,"./ember/chatview":9,"./ember/layout":10,"./ember/line":11,"./ember/moderation-card":12,"./ember/room":13,"./ember/viewers":14,"./emoticons":15,"./ext/betterttv":16,"./ext/emote_menu":17,"./featurefriday":19,"./settings":20,"./socket":21,"./tokenize":22,"./ui/about_page":23,"./ui/dark":24,"./ui/following":26,"./ui/following-count":25,"./ui/menu":27,"./ui/menu_button":28,"./ui/my_emotes":29,"./ui/notifications":30,"./ui/races":31,"./ui/styles":32,"./ui/sub_count":33,"./ui/viewer_count":34}],19:[function(e,s,n){var i=t.FrankerFaceZ,o=e("./constants");i.prototype.feature_friday=null,i.prototype.check_ff=function(t){t||this.log("Checking for Feature Friday data..."),jQuery.ajax(o.SERVER+"script/event.json",{cache:!1,dataType:"json",context:this}).done(function(t){return this._load_ff(t)}).fail(function(e){return 404==e.status?this._load_ff(null):(t=t||0,t++,10>t?setTimeout(this.check_ff.bind(this,t),250):this._load_ff(null))})},i.ws_commands.reload_ff=function(){this.check_ff()},i.prototype._feature_friday_ui=function(t,e,s){if(this.feature_friday&&this.feature_friday.channel!=t){this._emotes_for_sets(e,s,[this.feature_friday.set],this.feature_friday.title,this.feature_friday.icon,"FrankerFaceZ");var n=App.__container__.lookup("controller:channel");if(!n||n.get("id")!=this.feature_friday.channel){var i=this.feature_friday,o=document.createElement("div"),a=document.createElement("a");o.className="chat-menu-content",o.style.textAlign="center";var r=i.display_name+(i.live?" is live now!":"");a.className="button primary",a.classList.toggle("live",i.live),a.classList.toggle("blue",this.has_bttv&&BetterTTV.settings.get("showBlueButtons")),a.href="http://www.twitch.tv/"+i.channel,a.title=r,a.target="_new",a.innerHTML=""+r+"",o.appendChild(a),e.appendChild(o)}}},i.prototype._load_ff=function(t){this.feature_friday&&(this.global_sets.removeObject(this.feature_friday.set),this.default_sets.removeObject(this.feature_friday.set),this.feature_friday=null,this.update_ui_link()),t&&t.set&&t.channel&&(this.feature_friday={set:t.set,channel:t.channel,live:!1,title:t.title||"Feature Friday",display_name:i.get_capitalization(t.channel,this._update_ff_name.bind(this))},this.global_sets.push(t.set),this.default_sets.push(t.set),this.load_set(t.set),this._update_ff_live())},i.prototype._update_ff_live=function(){if(this.feature_friday){var t=this;Twitch.api.get("streams/"+this.feature_friday.channel).done(function(e){t.feature_friday.live=null!=e.stream,t.update_ui_link()}).always(function(){t.feature_friday.timer=setTimeout(t._update_ff_live.bind(t),12e4)})}},i.prototype._update_ff_name=function(t){this.feature_friday&&(this.feature_friday.display_name=t)}},{"./constants":5}],20:[function(e,s,n){var i=t.FrankerFaceZ,o=e("./constants"),a=e("./FileSaver");make_ls=function(t){return"ffz_setting_"+t},toggle_setting=function(t,e){var s=!this.settings.get(e);this.settings.set(e,s),t.classList.toggle("active",s)},option_setting=function(t,e){this.settings.set(e,JSON.parse(t.options[t.selectedIndex].value))},toggle_basic_setting=function(t,e){var s=i.basic_settings[e].get,n=!("function"==typeof s?s.bind(this)():this.settings.get(s)),o=i.basic_settings[e].set;"function"==typeof o?o.bind(this)(n):this.settings.set(o,n),t.classList.toggle("active",n)},option_basic_setting=function(t,e){i.basic_settings[e].set.bind(this)(JSON.parse(t.options[t.selectedIndex].value))},i.settings_info={advanced_settings:{value:!1,visible:!1}},i.basic_settings={},i.prototype.load_settings=function(){this.log("Loading settings."),this.settings={};for(var e in i.settings_info)if(i.settings_info.hasOwnProperty(e)){var s=i.settings_info[e],n=s.storage_key||make_ls(e),o=s.hasOwnProperty("value")?s.value:void 0;if(localStorage.hasOwnProperty(n))try{o=JSON.parse(localStorage.getItem(n))}catch(a){this.log('Error loading value for "'+e+'": '+a)}s.process_value&&(o=s.process_value.bind(this)(o)),this.settings[e]=o}this.settings.get=this._setting_get.bind(this),this.settings.set=this._setting_set.bind(this),this.settings.del=this._setting_del.bind(this),t.addEventListener("storage",this._setting_update.bind(this),!1)},i.prototype.save_settings_file=function(){var t={version:1,script_version:i.version_info+"",aliases:this.aliases,settings:{}};for(var e in i.settings_info)if(i.settings_info.hasOwnProperty(e)){var s=i.settings_info[e],n=s.storage_key||make_ls(e);localStorage.hasOwnProperty(n)&&(t.settings[e]=this.settings[e])}var o=new Blob([JSON.stringify(t,null,4)],{type:"application/json;charset=utf-8"});a.saveAs(o,"ffz-settings.json")},i.prototype.load_settings_file=function(t){if("string"==typeof t)this._load_settings_file(t);else{var e=new FileReader,s=this;e.onload=function(t){s._load_settings_file(t.target.result)},e.readAsText(t)}},i.prototype._load_settings_file=function(t){try{t=JSON.parse(t)}catch(e){return this.error("Error Loading Settings: "+e),alert("There was an error attempting to read the provided settings data.")}this.log("Loading Settings Data",t);var s=[],n=[];if(t.settings)for(var o in t.settings)if(i.settings_info.hasOwnProperty(o)){var a=i.settings_info[o],r=t.settings[o];a.process_value&&(r=a.process_value.bind(this)(r)),r!==this.settings.get(o)&&this.settings.set(o,r),n.push(o)}else s.push(o);setTimeout(function(){alert("Successfully loaded "+n.length+" settings and skipped "+s.length+" settings.")})},i.menu_pages.settings={render:function(t,e){var s=document.createElement("ul"),n=document.createElement("div"),o=document.createElement("li"),a=document.createElement("a"),r=document.createElement("li"),d=document.createElement("a"),u=document.createElement("li"),c=document.createElement("a"),l=parseInt(e.style.maxHeight||"0");l||(l=Math.max(200,t.$().height()-172)),l&&l!==0/0&&(l-=37,n.style.maxHeight=l+"px"),n.className="ffz-ui-sub-menu-page",s.className="menu sub-menu clearfix",o.className="item",o.id="ffz-settings-page-basic",a.innerHTML="Basic",o.appendChild(a),r.className="item",r.id="ffz-settings-page-advanced",d.innerHTML="Advanced",r.appendChild(d),u.className="item",u.id="ffz-settings-page-save",c.textContent="Backup & Restore",u.appendChild(c),s.appendChild(o),s.appendChild(r),s.appendChild(u);var h=i.menu_pages.settings.change_page;a.addEventListener("click",h.bind(this,t,e,s,n,"basic")),d.addEventListener("click",h.bind(this,t,e,s,n,"advanced")),c.addEventListener("click",h.bind(this,t,e,s,n,"save")),this.settings.advanced_settings?d.click():a.click(),e.appendChild(n),e.appendChild(s)},change_page:function(t,e,s,n,o){n.innerHTML="",n.setAttribute("data-page",o);for(var a=s.querySelectorAll("li.active"),r=0,d=a.length;d>r;r++)a[r].classList.remove("active");var u=s.querySelector("#ffz-settings-page-"+o);u&&u.classList.add("active"),i.menu_pages.settings["render_"+o].bind(this)(t,n),"advanced"===o?this.settings.set("advanced_settings",!0):"basic"===o&&this.settings.set("advanced_settings",!1)},render_save:function(t,e){var s=document.createElement("div"),n=document.createElement("div"),i=document.createElement("div"),o=document.createElement("div"),a=document.createElement("p"),r=document.createElement("a"),d=document.createElement("span"),u=document.createElement("p"),c=document.createElement("input"),l=document.createElement("a"),h=document.createElement("span"),f=this;i.className="chat-menu-content",s.className="heading",s.innerHTML="Backup Settings",i.appendChild(s),a.className="clearfix option",r.href="#",r.innerHTML="Save to File",r.addEventListener("click",this.save_settings_file.bind(this)),d.className="help",d.innerHTML="This generates a JSON file containing all of your settings and prompts you to save it.",a.appendChild(r),a.appendChild(d),i.appendChild(a),o.className="chat-menu-content", -n.className="heading",n.innerHTML="Restore Settings",o.appendChild(n),u.className="clearfix option",c.type="file",c.addEventListener("change",function(){f.load_settings_file(this.files[0])}),l.href="#",l.innerHTML="Restore from File",l.addEventListener("click",function(t){t.preventDefault(),c.click()}),h.className="help",h.innerHTML="This loads settings from a previously generated JSON file.",u.appendChild(l),u.appendChild(h),o.appendChild(u),e.appendChild(i),e.appendChild(o)},render_basic:function(t,e){var s={},n=[],o=-1!==navigator.userAgent.indexOf("Android");for(var a in i.basic_settings)if(i.basic_settings.hasOwnProperty(a)){var r=i.basic_settings[a],d=r.category||"Miscellaneous",u=s[d];if(void 0!==r.visible&&null!==r.visible){var c=r.visible;if("function"==typeof r.visible&&(c=r.visible.bind(this)()),!c)continue}o&&r.no_mobile||(u||(n.push(d),u=s[d]=[]),u.push([a,r]))}n.sort(function(t,e){var t=t.toLowerCase(),e=e.toLowerCase();return"debugging"===t&&(t="zzz"+t),"debugging"===e&&(e="zzz"+e),e>t?-1:t>e?1:0});for(var l=(this._ffz_basic_settings_page||n[0],0);ls?-1:s>n?1:o>i?-1:i>o?1:0});for(var p=0;p",b.className="switch-label",b.innerHTML=r.name,g.appendChild(w),g.appendChild(b),w.addEventListener("click",toggle_basic_setting.bind(this,w,a))}else if("select"===r.type){var z=document.createElement("select"),b=document.createElement("span");b.className="option-label",b.innerHTML=r.name;for(var k in r.options){var C=document.createElement("option");C.value=JSON.stringify(k),v===k&&C.setAttribute("selected",!0),C.innerHTML=r.options[k],z.appendChild(C)}z.addEventListener("change",option_basic_setting.bind(this,z,a)),g.appendChild(b),g.appendChild(z)}else{g.classList.add("option");var E=document.createElement("a");E.innerHTML=r.name,E.href="#",g.appendChild(E),E.addEventListener("click",r.method.bind(this))}if(r.help){var y=document.createElement("span");y.className="help",y.innerHTML=r.help,g.appendChild(y)}}m.appendChild(g)}e.appendChild(m)}},render_advanced:function(t,e){var s={},n=[],o=-1!==navigator.userAgent.indexOf("Android");for(var a in i.settings_info)if(i.settings_info.hasOwnProperty(a)){var r=i.settings_info[a],d=r.category||"Miscellaneous",u=s[d];if(void 0!==r.visible&&null!==r.visible){var c=r.visible;if("function"==typeof r.visible&&(c=r.visible.bind(this)()),!c)continue}o&&r.no_mobile||(u||(n.push(d),u=s[d]=[]),u.push([a,r]))}n.sort(function(t,e){var t=t.toLowerCase(),e=e.toLowerCase();return"debugging"===t&&(t="zzz"+t),"debugging"===e&&(e="zzz"+e),e>t?-1:t>e?1:0});for(var l=this,h=this._ffz_settings_page||n[0],f=0;fs?-1:s>n?1:o>i?-1:i>o?1:0});for(var v=0;v<_.length;v++){var a=_[v][0],r=_[v][1],b=document.createElement("p"),y=this.settings.get(a);if(b.className="clearfix",this.has_bttv&&r.no_bttv){var w=document.createElement("span"),z=document.createElement("span");w.className="switch-label",w.innerHTML=r.name,z=document.createElement("span"),z.className="help",z.innerHTML="Disabled due to incompatibility with BetterTTV.",b.classList.add("disabled"),b.appendChild(w),b.appendChild(z)}else{if("boolean"==r.type){var k=document.createElement("a"),w=document.createElement("span");k.className="switch",k.classList.toggle("active",y),k.innerHTML="",w.className="switch-label",w.innerHTML=r.name,b.appendChild(k),b.appendChild(w),k.addEventListener("click",toggle_setting.bind(this,k,a))}else if("select"===r.type){var C=document.createElement("select"),w=document.createElement("span");w.className="option-label",w.innerHTML=r.name;for(var E in r.options){var x=document.createElement("option");x.value=JSON.stringify(E),y===E&&x.setAttribute("selected",!0),x.innerHTML=r.options[E],C.appendChild(x)}C.addEventListener("change",option_setting.bind(this,C,a)),b.appendChild(w),b.appendChild(C)}else{b.classList.add("option");var T=document.createElement("a");T.innerHTML=r.name,T.href="#",b.appendChild(T),T.addEventListener("click",r.method.bind(this))}if(r.help){var z=document.createElement("span");z.className="help",z.innerHTML=r.help,b.appendChild(z)}}p.appendChild(b)}e.appendChild(p)}},name:"Settings",icon:o.GEAR,sort_order:99999,wide:!0,sub_menu:!0},i.prototype._setting_update=function(e){if(e||(e=t.event),e.key&&"ffz_setting_"===e.key.substr(0,12)){var s=e.key,n=s.substr(12),o=void 0,a=i.settings_info[n];if(!a){for(n in i.settings_info)if(i.settings_info.hasOwnProperty(n)&&(a=i.settings_info[n],a.storage_key==s))break;if(a.storage_key!=s)return}this.log("Updated Setting: "+n);try{o=JSON.parse(e.newValue)}catch(r){this.log('Error loading new value for "'+n+'": '+r),o=a.value||void 0}if(this.settings[n]=o,a.on_update)try{a.on_update.bind(this)(o,!1)}catch(r){this.log('Error running updater for setting "'+n+'": '+r)}}},i.prototype._setting_get=function(t){return this.settings[t]},i.prototype._setting_set=function(t,e){var s=i.settings_info[t],n=s.storage_key||make_ls(t),o=JSON.stringify(e);if(this.settings[t]=e,localStorage.setItem(n,o),this.log('Changed Setting "'+t+'" to: '+o),s.on_update)try{s.on_update.bind(this)(e,!0)}catch(a){this.log('Error running updater for setting "'+t+'": '+a)}},i.prototype._setting_del=function(t){var e=i.settings_info[t],s=e.storage_key||make_ls(t),n=void 0;if(localStorage.hasOwnProperty(s)&&localStorage.removeItem(s),delete this.settings[t],e&&(n=this.settings[t]=e.hasOwnProperty("value")?e.value:void 0),e.on_update)try{e.on_update.bind(this)(n,!0)}catch(o){this.log('Error running updater for setting "'+t+'": '+o)}}},{"./FileSaver":1,"./constants":5}],21:[function(e,s,n){var i=t.FrankerFaceZ;i.prototype._ws_open=!1,i.prototype._ws_delay=0,i.prototype._ws_last_iframe=0,i.ws_commands={},i.ws_on_close=[],i.prototype.ws_iframe=function(){this._ws_last_iframe=Date.now();var t=document.createElement("iframe"),e=this;t.src="http://catbag.frankerfacez.com",t.style.visibility="hidden",document.body.appendChild(t),setTimeout(function(){document.body.removeChild(t),e._ws_open||e.ws_create()},2e3)},i.prototype.ws_create=function(){var e,s=this;this._ws_last_req=0,this._ws_callbacks={},this._ws_pending=this._ws_pending||[];try{e=this._ws_sock=new WebSocket("ws://catbag.frankerfacez.com/")}catch(n){return this._ws_exists=!1,this.log("Error Creating WebSocket: "+n)}this._ws_exists=!0,e.onopen=function(e){s._ws_open=!0,s._ws_delay=0,s._ws_last_iframe=Date.now(),s.log("Socket connected.");var n=t.RequestFileSystem||t.webkitRequestFileSystem;n?n(t.TEMPORARY,100,s.ws_send.bind(s,"hello",["ffz_"+i.version_info,localStorage.ffzClientId],s._ws_on_hello.bind(s)),s.log.bind(s,"Operating in Incognito Mode.")):s.ws_send("hello",["ffz_"+i.version_info,localStorage.ffzClientId],s._ws_on_hello.bind(s));var o=s.get_user();if(o&&s.ws_send("setuser",o.login),s.is_dashboard){var a=location.pathname.match(/\/([^\/]+)/);a&&(s.ws_send("sub",a[1]),s.ws_send("sub_channel",a[1]))}for(var r in s.rooms)s.rooms.hasOwnProperty(r)&&s.rooms[r]&&(s.ws_send("sub",r),s.rooms[r].needs_history&&(s.rooms[r].needs_history=!1,!s.has_bttv&&s.settings.chat_history&&s.ws_send("chat_history",[r,25],s._load_history.bind(s,r))));if(s._cindex){var d=s._cindex.get("controller.id"),u=s._cindex.get("controller.hostModeTarget.id");d&&s.ws_send("sub_channel",d),u&&s.ws_send("sub_channel",u)}var c=s._ws_pending;s._ws_pending=[];for(var l=0;l1e4){var o=navigator.userAgent.toLowerCase();if(Date.now()-s._ws_last_iframe>18e5&&(-1!==o.indexOf("chrome")||-1===o.indexOf("safari")))return s.ws_iframe()}s._ws_delay<6e4?s._ws_delay+=1e3*(Math.floor(10*Math.random())+5):s._ws_delay=1e3*(Math.floor(60*Math.random())+30),setTimeout(s.ws_create.bind(s),s._ws_delay)},e.onmessage=function(t){var e,n,o=t.data.indexOf(" "),a=t.data.substr(o+1),r=parseInt(t.data.slice(0,o));if(o=a.indexOf(" "),-1===o&&(o=a.length),e=a.slice(0,o),a=a.substr(o+1),a&&(n=JSON.parse(a)),-1===r){var d=i.ws_commands[e];d?d.bind(s)(n):s.log("Invalid command: "+e,n,!1,!0)}else{var u="True"===e,c="function"==typeof s._ws_callbacks[r];if(c){try{s._ws_callbacks[r](u,n)}catch(l){s.error("Callback for "+r+": "+l)}s._ws_callbacks[r]=void 0}else s.log("Socket Reply to "+r+" - "+(u?"SUCCESS":"FAIL"),n,!1,!0)}}},i.prototype.ws_send=function(t,e,s,n){if(!this._ws_open){if(n){var i=this._ws_pending=this._ws_pending||[];return i.push([t,e,s]),!0}return!1}var o=++this._ws_last_req;e=void 0!==e?" "+JSON.stringify(e):"",s&&(this._ws_callbacks[o]=s);try{this._ws_sock.send(o+" "+t+e)}catch(a){return this.log("Socket Send Error: "+a),!1}return o},i.prototype._ws_on_hello=function(t,e){if(!t)return this.log("Error Saying Hello: "+e);localStorage.ffzClientId=e,this.log("Client ID: "+e);var s={},n=s.settings={};for(var o in i.settings_info)n[o]=this.settings[o];n.keywords=this.settings.keywords.length,n.banned_words=this.settings.banned_words.length,s.bttv=this.has_bttv||!!document.head.querySelector('script[src*="betterttv"]'),s["user-agent"]=navigator.userAgent,s.screen=[screen.width,screen.height],s.language=navigator.language,s.platform=navigator.platform,this.ws_send("survey",[s])},i.ws_commands.do_authorize=function(t){var e;for(var s in this.rooms)if(this.rooms.hasOwnProperty(s)){var n=this.rooms[s];if(n&&n.room&&!n.room.get("roomProperties.eventchat")&&!n.room.get("isGroupRoom")&&n.room.tmiRoom){var o=n.room.tmiRoom._getConnection();if(o.isConnected){e=o;break}}}e?e._send("PRIVMSG #frankerfacezauthorizer :AUTH "+t):setTimeout(i.ws_commands.do_authorize.bind(this,t),5e3)}},{}],22:[function(e,s,n){var i,o=t.FrankerFaceZ,a=e("./utils"),r=e("./constants"),d="http://static-cdn.jtvnw.net/emoticons/v1/",u={};build_srcset=function(t){if(u[t])return u[t];var e=u[t]=d+t+"/1.0 1x, "+d+t+"/2.0 2x, "+d+t+"/3.0 4x";return e},data_to_tooltip=function(t){var e=t.set,s=t.set_type,n=t.owner;return void 0===s&&(s="Channel"),e?("--global--"===e?(e="Twitch Global",s=null):("--twitch-turbo--"==e||"turbo"==e||"--turbo-faces--"==e)&&(e="Twitch Turbo",s=null),"Emoticon: "+t.code+"\n"+(s?s+": ":"")+e+(n?"\nBy: "+n.display_name:"")):t.code},build_tooltip=function(t){{var e=this._twitch_emotes[t];e?e.set:null}return e?"string"==typeof e?e:e.tooltip?e.tooltip:e.tooltip=data_to_tooltip(e):"???"},load_emote_data=function(t,e,s,n){if(!s)return e;e&&(n.code=e),this._twitch_emotes[t]=n;for(var i=build_tooltip.bind(this)(t),o=document.querySelectorAll('img[data-emote="'+t+'"]'),a=0;a!-#%-\\x2A,-/:;\\x3F@\\x5B-\\x5D_\\x7B}\\u00A1\\u00A7\\u00AB\\u00B6\\u00B7\\u00BB\\u00BF\\u037E\\u0387\\u055A-\\u055F\\u0589\\u058A\\u05BE\\u05C0\\u05C3\\u05C6\\u05F3\\u05F4\\u0609\\u060A\\u060C\\u060D\\u061B\\u061E\\u061F\\u066A-\\u066D\\u06D4\\u0700-\\u070D\\u07F7-\\u07F9\\u0830-\\u083E\\u085E\\u0964\\u0965\\u0970\\u0AF0\\u0DF4\\u0E4F\\u0E5A\\u0E5B\\u0F04-\\u0F12\\u0F14\\u0F3A-\\u0F3D\\u0F85\\u0FD0-\\u0FD4\\u0FD9\\u0FDA\\u104A-\\u104F\\u10FB\\u1360-\\u1368\\u1400\\u166D\\u166E\\u169B\\u169C\\u16EB-\\u16ED\\u1735\\u1736\\u17D4-\\u17D6\\u17D8-\\u17DA\\u1800-\\u180A\\u1944\\u1945\\u1A1E\\u1A1F\\u1AA0-\\u1AA6\\u1AA8-\\u1AAD\\u1B5A-\\u1B60\\u1BFC-\\u1BFF\\u1C3B-\\u1C3F\\u1C7E\\u1C7F\\u1CC0-\\u1CC7\\u1CD3\\u2010-\\u2027\\u2030-\\u2043\\u2045-\\u2051\\u2053-\\u205E\\u207D\\u207E\\u208D\\u208E\\u2329\\u232A\\u2768-\\u2775\\u27C5\\u27C6\\u27E6-\\u27EF\\u2983-\\u2998\\u29D8-\\u29DB\\u29FC\\u29FD\\u2CF9-\\u2CFC\\u2CFE\\u2CFF\\u2D70\\u2E00-\\u2E2E\\u2E30-\\u2E3B\\u3001-\\u3003\\u3008-\\u3011\\u3014-\\u301F\\u3030\\u303D\\u30A0\\u30FB\\uA4FE\\uA4FF\\uA60D-\\uA60F\\uA673\\uA67E\\uA6F2-\\uA6F7\\uA874-\\uA877\\uA8CE\\uA8CF\\uA8F8-\\uA8FA\\uA92E\\uA92F\\uA95F\\uA9C1-\\uA9CD\\uA9DE\\uA9DF\\uAA5C-\\uAA5F\\uAADE\\uAADF\\uAAF0\\uAAF1\\uABEB\\uFD3E\\uFD3F\\uFE10-\\uFE19\\uFE30-\\uFE52\\uFE54-\\uFE61\\uFE63\\uFE68\\uFE6A\\uFE6B\\uFF01-\\uFF03\\uFF05-\\uFF0A\\uFF0C-\\uFF0F\\uFF1A\\uFF1B\\uFF1F\\uFF20\\uFF3B-\\uFF3D\\uFF3F\\uFF5B\\uFF5D\\uFF5F-\\uFF65]",SPLITTER=new RegExp(SEPARATORS+"*,"+SEPARATORS+"*"),LINK_SPLIT=/^(?:(https?):\/\/)?(?:(.*?)@)?([^\/:]+)(?::(\d+))?(.*?)(?:\?(.*?))?(?:\#(.*?))?$/,YOUTUBE_CHECK=/^(?:https?:\/\/)?(?:m\.|www\.)?youtu(?:be\.com|\.be)\/(?:v\/|watch\/|.*?(?:embed|watch).*?v=)?([a-zA-Z0-9\-_]+)$/,IMGUR_PATH=/^\/(?:gallery\/)?[A-Za-z0-9]+(?:\.(?:png|jpg|jpeg|gif|gifv|bmp))?$/,IMAGE_EXT=/\.(?:png|jpg|jpeg|gif|bmp)$/i,IMAGE_DOMAINS=[],is_image=function(t,e){var s=t.match(LINK_SPLIT);if(s){var n=s[3].toLowerCase(),i=s[4],o=s[5];return i&&"80"!==i&&"443"!==i?!1:"i.imgur.com"===n||"imgur.com"===n||"www.imgur.com"===n||"m.imgur.com"===n?IMGUR_PATH.test(o):e?IMAGE_EXT.test(o):-1!==IMAGE_DOMAINS.indexOf(n)}},image_iframe=function(t,e){return''},build_link_tooltip=function(t){var e,s=this._link_data[t];if(!s)return"";if(s.tooltip)return s.tooltip;if("youtube"==s.type)e=this.settings.link_image_hover?image_iframe(s.full||t,"ffz-yt-thumb"):"",e+="YouTube: "+a.sanitize(s.title)+"
",e+="Channel: "+a.sanitize(s.channel)+" | "+a.time_to_string(s.duration)+"
",e+=a.number_commas(s.views||0)+" Views | 👍 "+a.number_commas(s.likes||0)+" 👎 "+a.number_commas(s.dislikes||0);else if("strawpoll"==s.type){e="Strawpoll: "+a.sanitize(s.title)+"
";for(var n in s.items){{var i=s.items[n];Math.floor(i/s.total*100)}e+='"}e+="
'+a.sanitize(n)+''+a.number_commas(i)+"

Total: "+a.number_commas(s.total);var o=a.parse_date(s.fetched);if(o){var r=Math.floor((o.getTime()-Date.now())/1e3);r>60&&(e+="
Data was cached "+a.time_to_string(r)+" ago.")}}else if("twitch"==s.type){e="Twitch: "+a.sanitize(s.display_name)+"
";var d=a.parse_date(s.since);d&&(e+="Member Since: "+a.date_string(d)+"
"),e+="Views: "+a.number_commas(s.views)+" | Followers: "+a.number_commas(s.followers)+""}else if("twitch_vod"==s.type)e="Twitch "+("highlight"==s.broadcast_type?"Highlight":"Broadcast")+": "+a.sanitize(s.title)+"
",e+="By: "+a.sanitize(s.display_name)+(s.game?" | Playing: "+a.sanitize(s.game):" | Not Playing")+"
",e+="Views: "+a.number_commas(s.views)+" | "+a.time_to_string(s.length);else if("twitter"==s.type)e="Tweet By: "+a.sanitize(s.user)+"
",e+=a.sanitize(s.tweet);else if("reputation"==s.type){if(e=this.settings.link_image_hover&&is_image(s.full||t,this.settings.image_hover_all_domains)?image_iframe(s.full||t):"",e+=''+a.sanitize(s.full.toLowerCase())+"",s.trust<50||s.safety<50||s.tags&&s.tags.length>0){e+="
";var u=!1;(s.trust<50||s.safety<50)&&(s.unsafe=!0,e+="Potentially Unsafe Link
",e+="Trust: "+s.trust+"% | Child Safety: "+s.safety+"%",u=!0),s.tags&&s.tags.length>0&&(e+=(u?"
":"")+"Tags: "+s.tags.join(", ")),e+="
Data Source: WOT"}}else s.full&&(e=this.settings.link_image_hover&&is_image(s.full||t,this.settings.image_hover_all_domains)?image_iframe(s.full||t):"",e+=''+a.sanitize(s.full.toLowerCase())+"");return e||(e=''+a.sanitize(t.toLowerCase())+""),s.tooltip=e,e},load_link_data=function(t,e,s){if(e){this._link_data[t]=s,s.unsafe=!1;var n,i=build_link_tooltip.bind(this)(t),o="/"==t.charAt(t.length-1)?t.substr(0,t.length-1):null;if(n=document.querySelectorAll(o?'span.message a[href="'+t+'"], span.message a[href="'+o+'"], span.message a[data-url="'+t+'"], span.message a[data-url="'+o+'"]':'span.message a[href="'+t+'"], span.message a[data-url="'+t+'"]'),this.settings.link_info)for(var a=0;a12&&!e.settings.twenty_four_timestamps?s-=12:0!==s||e.settings.twenty_four_timestamps||(s=12),s+":"+(10>n?"0":"")+n},i.linkifyMessage=function(t,s){var n=e.settings.show_deleted_links;return _.chain(t).map(function(t){if(!_.isString(t))return t;var e=t.match(LINK);return e&&e.length?_.zip(t.split(LINK),_.map(e,function(t){var e=t.length>255;return n||!s&&!e?{isLink:!0,href:t}:{isLink:!0,isDeleted:!0,isLong:e,href:t}})):[t]}).flatten().compact().value()}},o.prototype.load_twitch_emote_data=function(t){jQuery.ajax(r.SERVER+"script/twitch_emotes.json",{cache:!1,context:this}).done(function(t){for(var e in t){var s=t[e];if(s){this._twitch_set_to_channel[e]=s.name;for(var n=0,i=s.emotes.length;i>n;n++)this._twitch_emote_to_set[s.emotes[n]]=e}}this._twitch_set_to_channel[0]="--global--",this._twitch_set_to_channel[33]="--turbo-faces--",this._twitch_set_to_channel[42]="--turbo-faces--"}).fail(function(e){404!==e.status&&(t=(t||0)+1,10>t&&setTimeout(this.load_twitch_emote_data.bind(this,t),1e3))})},o.prototype.tokenize_chat_line=function(e,s,n){if(e.cachedTokens)return e.cachedTokens;var a=e.message,r=this.get_user(),d=e.room,u=r&&e.from===r.login,c=e.tags&&e.tags.emotes,l=[a];if(i&&i.linkifyMessage){var h=a.labels||[],f=-1!==h.indexOf("owner")||-1!==h.indexOf("staff")||-1!==h.indexOf("admin")||-1!==h.indexOf("global_mod")||-1!==h.indexOf("mod")||"admin"===a.style;l=i.linkifyMessage(l,n&&!f)}r&&r.login&&i&&i.mentionizeMessage&&(l=i.mentionizeMessage(l,r.login,u)),i&&i.emoticonizeMessage&&(l=i.emoticonizeMessage(l,c)),this.settings.replace_bad_emotes&&(l=this.tokenize_replace_emotes(l)),l=this._remove_banned(l),l=this.tokenize_emotes(e.from,d,l,u),this.settings.parse_emoji&&(l=this.tokenize_emoji(l));var m=e.tags&&e.tags["display-name"];if(m&&m.length&&(o.capitalization[e.from]=[m.trim(),Date.now()]),!u){l=this.tokenize_mentions(l);for(var p=0;p'}if(t.isLink){var g=t.title||t.isLong&&""||t.isDeleted&&""||t.href;if(!e&&void 0!==e)return a.sanitize(g);var n,v=t.href,b="",y=v.indexOf("@"),w=v.indexOf("/");if(-1!==y&&(-1===w||w>y))b="email-link",s.settings.link_info&&(b+=" tooltip",n="E-Mail "+v),v="mailto:"+v;else if(v.match(/^https?:\/\//)||(v="http://"+v),s.settings.link_info){b="html-tooltip";var m=s._link_data&&s._link_data[v];m?(n=m.tooltip,m.unsafe&&(b+=" unsafe-link")):(s._link_data=s._link_data||{},s._link_data[v]=!0,s.ws_send("get_link",v,load_link_data.bind(s,v)),s.settings.link_image_hover&&is_image(v,s.settings.image_hover_all_domains)&&(n=image_iframe(v)))}else s.settings.link_image_hover&&(b="html-tooltip",is_image(v,s.settings.image_hover_all_domains)&&(n=image_iframe(v)));var z=v;return t.isDeleted&&(b="deleted-link "+b,n=a.sanitize(t.censoredHref||t.href),v="#"),''+a.sanitize(g)+""}return t.mentionedUser?''+a.sanitize(t.mentionedUser)+"":a.sanitize(t.deletedLink?t.text:t)}).join("")},o.prototype.tokenize_replace_emotes=function(t){_.isString(t)&&(t=[t]);for(var e=0;e-1&&(-1===e.indexOf("/")||e.indexOf("@")0&&(a=!0)}var u=document.createElement("div"),c="";c+="

FrankerFaceZ

",c+='
new ways to woof
',u.className="chat-menu-content center",u.innerHTML=c,e.appendChild(u);var l=0,h=u.querySelector("h1");h&&h.addEventListener("click",function(){if(h.style.cursor="pointer",l++,l>=3){l=0;var t=document.querySelector(".app-main")||document.querySelector(".ember-chat-container");t&&t.classList.toggle("ffz-flip")}setTimeout(function(){l=0,h.style.cursor=""},2e3)});var f=document.createElement("div"),m=document.createElement("a"),_="To use custom emoticons in "+(a?"this channel":"tons of channels")+", get FrankerFaceZ from http://www.frankerfacez.com";m.className="button primary",m.innerHTML="Advertise in Chat",m.addEventListener("click",this._add_emote.bind(this,t,_)),f.appendChild(m);var p=document.createElement("a");p.className="button ffz-donate",p.href="https://www.frankerfacez.com/donate",p.target="_new",p.innerHTML="Donate",f.appendChild(p),f.className="chat-menu-content center",e.appendChild(f);var g=document.createElement("div");c='',c+='',c+='',c+='',c+='',g.className="chat-menu-content center",g.innerHTML=c,g.querySelector("#ffz-changelog").addEventListener("click",function(){r._ui_change_page(t,s,n,e,"about_changelog")});var v=!1;g.querySelector("#ffz-debug-logs").addEventListener("click",function(){ -v||(v=!0,r._pastebin(r._log_data.join("\n"),function(t){v=!1,t?prompt("Your FrankerFaceZ logs have been uploaded to the URL:",t):alert("There was an error uploading the FrankerFaceZ logs.")}))}),e.appendChild(g)}}},{"../constants":5}],24:[function(e,s,n){var i=t.FrankerFaceZ,o=e("../constants");i.basic_settings.dark_twitch={type:"boolean",no_bttv:!0,category:"General",name:"Dark Twitch",help:"Apply a dark background to channels and other related pages for easier viewing.",get:function(){return this.settings.dark_twitch},set:function(t){this.settings.set("dark_twitch",t),this.settings.set("dark_no_blue",t)}},i.basic_settings.separated_chat={type:"boolean",no_bttv:!0,category:"Chat",name:"Separated Lines",help:"Use alternating rows and thin lines to visually separate chat messages for easier reading.",get:function(){return this.settings.chat_rows&&"0"!==this.settings.chat_separators},set:function(t){this.settings.set("chat_rows",t),this.settings.set("chat_separators",t?"2":"0")}},i.basic_settings.minimalistic_chat={type:"boolean",category:"Chat",name:"Minimalistic UI",help:"Hide all of chat except messages and the input box and reduce chat margins.",get:function(){return this.settings.minimal_chat&&this.settings.chat_padding},set:function(t){this.settings.set("minimal_chat",t),this.settings.set("chat_padding",t)}},i.basic_settings.high_contrast={type:"boolean",category:"Chat",no_bttv:!0,name:"High Contrast",help:"Display chat using white and black for maximum contrast. This is suitable for capturing and chroma keying chat to display on stream.",get:function(){return"222"!==this.settings.high_contrast_chat},set:function(t){this.settings.set("high_contrast_chat",t?"111":"222")}},i.basic_settings.keywords={type:"button",category:"Chat",no_bttv:!0,name:"Highlight Keywords",help:"Set additional keywords that will be highlighted in chat.",method:function(){i.settings_info.keywords.method.bind(this)()}},i.basic_settings.banned_words={type:"button",category:"Chat",no_bttv:!0,name:"Banned Keywords",help:"Set a list of words that will be removed from chat messages, locally.",method:function(){i.settings_info.banned_words.method.bind(this)()}},i.settings_info.twitch_chat_dark={type:"boolean",value:!1,visible:!1},i.settings_info.dark_twitch={type:"boolean",value:!1,no_bttv:!0,category:"Appearance",name:"Dark Twitch",help:"Apply a dark background to channels and other related pages for easier viewing.",on_update:function(e){var s=document.querySelector("input.ffz-setting-dark-twitch");if(s&&(s.checked=e),!this.has_bttv){document.body.classList.toggle("ffz-dark",e);var n=t.App?App.__container__.lookup("controller:settings").get("model"):void 0;e?(this._load_dark_css(),n&&this.settings.set("twitch_chat_dark",n.get("darkMode")),n&&n.set("darkMode",!0)):n&&n.set("darkMode",this.settings.twitch_chat_dark)}}},i.settings_info.dark_no_blue={type:"boolean",value:!1,category:"Appearance",name:"Gray Chat (no blue)",help:"Make the dark theme for chat and a few other places on Twitch a bit darker and not at all blue.",on_update:function(t){document.body.classList.toggle("ffz-no-blue",t)}},i.settings_info.hide_recent_past_broadcast={type:"boolean",value:!1,no_mobile:!0,category:"Channel Metadata",name:'Hide "Watch Last Broadcast"',help:'Hide the "Watch Last Broadcast" banner at the top of offline Twitch channels.',on_update:function(t){document.body.classList.toggle("ffz-hide-recent-past-broadcast",t)}},i.prototype.setup_dark=function(){document.body.classList.toggle("ffz-hide-recent-past-broadcast",this.settings.hide_recent_past_broadcast),document.body.classList.toggle("ffz-no-blue",this.settings.dark_no_blue),this.has_bttv||(document.body.classList.toggle("ffz-dark",this.settings.dark_twitch),this.settings.dark_twitch&&(t.App&&App.__container__.lookup("controller:settings").set("model.darkMode",!0),this._load_dark_css()))},i.prototype._load_dark_css=function(){if(!this._dark_style){this.log("Injecting FrankerFaceZ Dark Twitch CSS.");var t=this._dark_style=document.createElement("link");t.id="ffz-dark-css",t.setAttribute("rel","stylesheet"),t.setAttribute("href",o.SERVER+"script/dark.css?_="+(o.DEBUG?Date.now():i.version_info)),document.head.appendChild(t)}}},{"../constants":5}],25:[function(e,s,n){var i=t.FrankerFaceZ,o=e("../utils"),a=e("../constants"),r=function(t,e){return(t.settings.following_count&&"following"===e.parentElement.getAttribute("data-name")?"n":"")+(t.settings.swap_sidebars?"e":"w")},d=function(t,e){return!t.settings.following_count||"header_following"!==e.id&&"following"!==e.parentElement.getAttribute("data-name")?"":"ffz-wide-tip"};i.settings_info.following_count={type:"boolean",value:!0,no_mobile:!0,category:"Appearance",name:"Sidebar Following Data",help:"Display the number of live channels you're following on the sidebar, and list the channels in a tooltip.",on_update:function(e){this._schedule_following_count();var s=t.App&&App.__container__.resolve("model:stream"),n=s&&s.find("live");if(n){var i=n.get("total")||0;this._draw_following_count(i),this._draw_following_channels(n.get("content"),i)}else this._update_following_count(),this._draw_following_channels()}},i.prototype.setup_following_count=function(e){if(this.settings.following_count&&this._schedule_following_count(),this._install_following_tooltips(),!e)return this._update_following_count();this.log("Connecting to Live Streams model.");var s=t.App&&App.__container__.resolve("model:stream");if(!s)return this.log("Unable to find Stream model.");var n=s.find("live"),i=this;if(!n)return this.log("Unable to find Live Streams collection.");n.addObserver("total",function(){i._draw_following_count(this.get("total"))}),n.addObserver("content.length",function(){i._draw_following_channels(this.get("content"),this.get("total"))}),n.load();var o=n.get("total"),a=n.get("content");"number"==typeof o&&(this._draw_following_count(o),a&&a.length&&this._draw_following_channels(a,o))},i.prototype._schedule_following_count=function(){return this.settings.following_count?void(this._following_count_timer||(this._following_count_timer=setTimeout(this._update_following_count.bind(this),55e3+1e4*Math.random()))):void(this._following_count_timer&&(clearTimeout(this._following_count_timer),this._following_count_timer=void 0))},i.prototype._update_following_count=function(){if(!this.settings.following_count)return void(this._following_count_timer&&(clearTimeout(this._following_count_timer),this._following_count_timer=void 0));this._following_count_timer=setTimeout(this._update_following_count.bind(this),55e3+1e4*Math.random());var e=t.App&&App.__container__.resolve("model:stream"),s=e&&e.find("live"),n=this;s?s.load():Twitch.api&&Twitch.api.get("streams/followed",{limit:5,offset:0},{version:3}).done(function(t){n._draw_following_count(t._total),n._draw_following_channels(t.streams,t._total)}).fail(function(){n._draw_following_count(),n._draw_following_channels()})},i.prototype._build_following_tooltip=function(t){if("header_following"!==t.id&&"following"!==t.parentElement.getAttribute("data-name"))return t.getAttribute("original-title");if(!this.settings.following_count)return"Following";var e=(this.has_bttv?'FrankerFaceZ':"")+"Following",s=t.getBoundingClientRect(),n=document.body.clientHeight-(s.bottom+54),i=Math.max(Math.floor(n/36)-1,2),r=this._tooltip_streams,d=this._tooltip_total||r&&r.length||0;if(r&&r.length)for(var u=0,c=0,l=r.length;l>c;c++){var h=r[c];if(h&&h.channel){if(u+=1,u>i){e+="
And "+o.number_commas(d-i)+" more...";break}var f=this.settings.stream_uptime&&h.created_at&&o.parse_date(h.created_at),m=f&&Math.floor((Date.now()-f.getTime())/1e3)||0,_=Math.floor(m/60)%60,p=Math.floor(m/3600);e+=(0===c?"
":"")+(m>0?''+a.CLOCK+" "+(p>0?p+"h":"")+_+"m":"")+''+a.LIVE+" "+o.number_commas(h.viewers)+""+o.sanitize(h.channel.display_name||h.channel.name)+'
'+(h.channel.game?"Playing "+o.sanitize(h.channel.game):"Not Playing")+""}}else e+="
No one you're following is online.";return setTimeout(function(){var t=document.querySelector(".tipsy"),e=t.getBoundingClientRect(),s=parseInt(t.style.left||"0"),n=e.left+t.scrollWidth;e.left<5?t.style.left=s-e.left+5+"px":n>document.body.clientWidth-5&&(t.style.left=s-(5+n-document.body.clientWidth)+"px")}),e},i.prototype._install_following_tooltips=function(){var t=this,e={html:!0,className:function(){return d(t,this)},title:function(){return t._build_following_tooltip(this)}},s=jQuery('#small_nav ul.game_filters li[data-name="following"] a');if(s&&s.length){var n=s.data("tipsy");n&&n.options?(n.options=_.extend(n.options,e),n.options.gravity=function(){return r(t,this)}):s.tipsy(_.extend({gravity:function(){return r(t,this)}},e))}var i=jQuery('#large_nav #nav_personal li[data-name="following"] a');if(i&&i.length){var n=i.data("tipsy");n&&n.options?n.options=_.extend(n.options,e):i.tipsy(e)}var o=jQuery("#header_actions #header_following");if(o&&o.length){var n=o.data("tipsy");n&&n.options?n.options=_.extend(n.options,e):o.tipsy(e)}},i.prototype._draw_following_channels=function(t,e){this._tooltip_streams=t,this._tooltip_total=e},i.prototype._draw_following_count=function(t){var e=document.querySelector('#small_nav ul.game_filters li[data-name="following"] a');if(e){var s=e.querySelector(".ffz-follow-count");this.has_bttv||!this.settings.following_count?s&&s.parentElement.removeChild(s):(s||(s=document.createElement("span"),s.className="ffz-follow-count",e.appendChild(s)),s.innerHTML=t?o.format_unread(t):"")}var n=document.querySelector('#large_nav #nav_personal li[data-name="following"] a');if(n){var s=n.querySelector(".ffz-follow-count");this.has_bttv||!this.settings.following_count?s&&s.parentElement.removeChild(s):(s||(s=document.createElement("span"),s.className="ffz-follow-count",n.appendChild(s)),s.innerHTML=t?o.format_unread(t):"")}var i=document.querySelector("#header_actions #header_following");if(i){var s=i.querySelector(".ffz-follow-count");this.has_bttv||!this.settings.following_count?s&&s.parentElement.removeChild(s):(s||(s=document.createElement("span"),s.className="ffz-follow-count",i.appendChild(s)),s.innerHTML=t?o.format_unread(t):"")}}},{"../constants":5,"../utils":35}],26:[function(e,s,n){var i=t.FrankerFaceZ,o=e("../utils"),a=/^[A-Za-z0-9_]+$/,r=/^(?:https?:\/\/)?(?:www\.)?twitch\.tv\/([A-Za-z0-9_]+)/i;i.prototype.setup_following=function(){this.log("Initializing following support."),this.follow_data={},this.follow_sets={}},i.settings_info.follow_buttons={type:"boolean",value:!0,no_mobile:!0,category:"Channel Metadata",name:"Relevant Follow Buttons",help:"Display additional Follow buttons for channels relevant to the stream, such as people participating in co-operative gameplay.",on_update:function(t){this.rebuild_following_ui()}},i.ffz_commands.following=function(t,e){e=e.join(" ").trim().toLowerCase().split(/[ ,]+/);for(var s=[],n=0,i=e.length;i>n;n++){var o=e[n],a=o.match(r);a&&(o=a[1]),""!==o&&-1===s.indexOf(o)&&s.push(o)}var d=this.get_user(),u=this;return!d||d.login!==t.id&&"sirstendec"!==d.login&&"dansalvato"!==d.login?"You must be logged in as the broadcaster to use this command.":this.ws_send("update_follow_buttons",[t.id,s],function(e,s){return e?void(s?u.room_message(t,"The following buttons have been updated."):u.room_message(t,"The following buttons have been disabled.")):void u.room_message(t,"There was an error updating the following buttons.")})?void 0:"There was an error communicating with the server."},i.ws_on_close.push(function(){var e=t.App&&App.__container__.lookup("controller:channel"),s=e&&e.get("id"),n=e&&e.get("hostModeTarget.id"),i=!1;if(this.follow_sets={},e){for(var o in this.follow_data)if(delete this.follow_data[o],(o===s||o===n)&&(i=!0),this.rooms&&this.rooms[o]&&this.rooms[o].extra_sets){var a=this.rooms[o].extra_sets;delete this.rooms[o].extra_sets;for(var r=0;r span")}catch(d){r=void 0}r?o.insertBefore(a,r):o.appendChild(a)}for(var u=[e],c=0;cc;c++){var l=n[c];-1===u.indexOf(l)&&(this._build_following_button(a,l),u.push(l))}}else a&&a.parentElement.removeChild(a)}if(s){var n=this.follow_data&&this.follow_data[s],i=this._cindex.get("element"),o=i&&i.querySelector("#hostmode .channel-actions"),a=o&&o.querySelector("#ffz-ui-following");if(o&&this.settings.follow_buttons&&n&&n.length){if(a)a.innerHTML="";else{a=document.createElement("span"),a.id="ffz-ui-following";var r;try{r=o.querySelector(":scope > span")}catch(d){r=void 0}r?o.insertBefore(a,r):o.appendChild(a)}for(var u=[s],c=0;cc;c++){var l=n[c];-1===u.indexOf(l)&&(this._build_following_button(a,l),u.push(l))}}else a&&a.parentElement.removeChild(a)}}},i.prototype._build_following_button=function(e,s){if(!a.test(s))return this.log("Ignoring Invalid Channel: "+o.sanitize(s));var n,r=document.createElement("a"),d=this,u=document.createElement("div"),c=document.createElement("a"),l=document.createElement("div"),h=!1,f=!1,m=function(){u.classList.toggle("is-following",h),r.title=(h?"Unf":"F")+"ollow "+o.sanitize(n),r.innerHTML=(h?"":"Follow ")+o.sanitize(n),l.classList.toggle("hidden",!h)},_=function(){var t=d.get_user();return t&&t.login?void Twitch.api.get("users/"+t.login+"/follows/channels/"+s).done(function(t){h=!0,f=t.notifications,u.classList.add("is-initialized"),m()}).fail(function(t){h=!1,f=!1,u.classList.add("is-initialized"),m()}):(h=!1,notification=!1,u.classList.add("is-initialized"),m())},p=function(t){t!==!1&&(t=!0);var e=d.get_user();return e&&e.login?(f=t,Twitch.api.put("users/:login/follows/channels/"+s,{notifications:f}).fail(_)):null},g=function(t){n=t||s,m()};u.className="ember-follow follow-button",u.appendChild(r),c.className="toggle-notification-menu js-toggle-notification-menu",c.href="#",l.className="notification-controls v2 hidden",l.appendChild(c),r.addEventListener("click",function(t){var e=d.get_user();return e&&e.login?(h=!h,m(),d.ws_send("track_follow",[s,h]),h?p():Twitch.api.del("users/:login/follows/channels/"+s).done(_),!1):Ember.$.login({mpSourceAction:"follow-button",follow:s})}),r.addEventListener("mousedown",function(e){1===e.button&&(e.preventDefault(),t.open(Twitch.uri.profile(s)))}),c.addEventListener("click",function(){var t=d._build_following_popup(l,s,f);return t&&t.addEventListener("click",function(){var e=!f;return t.classList.toggle("active",e),p(e),!1}),!1}),n=i.get_capitalization(s,g),m(),setTimeout(_,5e3*Math.random()),e.appendChild(u),e.appendChild(l)},i.prototype._build_following_popup=function(t,e,s){var n=this._popup,o="",a=t.offsetLeft+t.offsetWidth;return n&&(n.parentElement.removeChild(n),delete this._popup,this._popup_kill&&this._popup_kill(),delete this._popup_kill,"ffz-following-popup"==n.id&&n.getAttribute("data-channel")===e)?null:(n=this._popup=document.createElement("div"),n.id="ffz-following-popup",n.setAttribute("data-channel",e),n.className=(a>=300?"right":"left")+" dropmenu notify-menu js-notify",o='
You are following '+i.get_capitalization(e)+"
",o+='

',o+='',o+='Notify me when the broadcaster goes live',o+="

",n.innerHTML=o,t.appendChild(n),n.querySelector("a.switch"))}},{"../utils":35}],27:[function(e,s,n){var i=t.FrankerFaceZ,o=e("../constants"),a=e("../utils"),r="http://static-cdn.jtvnw.net/emoticons/v1/",d=function(t){var e=document.body.classList.contains("ffz-sidebar-swap"),s=t.getBoundingClientRect(),n=parseInt(t.style.left||"0"),i=s.left+t.scrollWidth,o=!!t.style.left;e?s.left<20?(t.style.left="",o=!1):i>document.body.clientWidth&&(t.style.left=n-(i-document.body.clientWidth)+"px"):s.left<0?t.style.left=n-s.left+"px":i>document.body.clientWidth-20&&(t.style.left="",o=!1),t.classList.toggle("ui-moved",o)};i.prototype.setup_menu=function(){this.log("Installing mouse-up event to auto-close menus.");var e=this;jQuery(document).mouseup(function(t){var s,n=e._popup;n&&("ffz-chat-menu"===n.id&&n.style&&n.style.left||(n=jQuery(n),s=n.parent(),s.is(t.target)||0!==s.has(t.target).length||(n.remove(),delete e._popup,e._popup_kill&&e._popup_kill(),delete e._popup_kill)))}),document.body.classList.toggle("ffz-menu-replace",this.settings.replace_twitch_menu),this.log("Hooking the Ember Chat Settings view.");var s=t.App&&App.__container__.resolve("view:settings");if(s){s.reopen({didInsertElement:function(){this._super();try{this.ffzInit()}catch(t){e.error("ChatSettings didInsertElement: "+t)}},willClearRender:function(){try{this.ffzTeardown()}catch(t){e.error("ChatSettings willClearRender: "+t)}this._super()},ffzInit:function(){var t=this,s=this.get("element"),n=s&&s.querySelector(".dropmenu");if(n){var i,o,a,r=document.createElement("div"),d=document.createElement("div");r.className="list-header",r.innerHTML="FrankerFaceZ",d.className="chat-menu-content",i=document.createElement("p"),i.className="no-bttv",o=document.createElement("input"),o.type="checkbox",o.className="ember-checkbox ffz-setting-dark-twitch",o.checked=e.settings.dark_twitch,i.appendChild(o),i.appendChild(document.createTextNode("Dark Twitch")),d.appendChild(i),o.addEventListener("change",function(t){e.settings.set("dark_twitch",this.checked)}),i=document.createElement("p"),o=document.createElement("input"),o.type="checkbox",o.className="ember-checkbox ffz-setting-hosted-channels",o.checked=e.settings.hosted_channels,i.appendChild(o),i.appendChild(document.createTextNode("Channel Hosting")),d.appendChild(i),o.addEventListener("change",function(t){e.settings.set("hosted_channels",this.checked)}),i=document.createElement("p"),a=document.createElement("a"),a.href="#",a.innerHTML="More Settings",i.appendChild(a),d.appendChild(i),a.addEventListener("click",function(s){return t.set("controller.model.hidden",!0),e._last_page="settings",e.build_ui_popup(e._chatv),s.stopPropagation(),!1}),n.appendChild(r),n.appendChild(d)}},ffzTeardown:function(){}});try{s.create().destroy()}catch(n){}for(var i in Ember.View.views)if(Ember.View.views.hasOwnProperty(i)){var o=Ember.View.views[i];if(o instanceof s){this.log("Manually updating existing Chat Settings view.",o);try{o.ffzInit()}catch(n){this.error("setup: ChatSettings ffzInit: "+n)}}}}},i.menu_pages={},i.prototype._fix_menu_position=function(){var t=document.querySelector("#ffz-chat-menu");t&&d(t)},i.prototype.build_ui_popup=function(t){var e=this._popup;if(e)return e.parentElement.removeChild(e),delete this._popup,this._popup_kill&&this._popup_kill(),void delete this._popup_kill;var s=document.createElement("div"),n=document.createElement("div"),a=document.createElement("ul"),r=this.has_bttv?BetterTTV.settings.get("darkenedMode"):!1;s.className="emoticon-selector chat-menu ffz-ui-popup",s.id="ffz-chat-menu",n.className="emoticon-selector-box dropmenu",s.appendChild(n),s.classList.toggle("dark",r);var u=document.createElement("div");u.className="ffz-ui-menu-page",n.appendChild(u),a.className="menu clearfix",n.appendChild(a);var c=document.createElement("li");c.className="title",c.innerHTML='Franker'+(o.DEBUG?"Dev":"Face")+"Z";var l=document.createElement("span"),h=this;l.className="ffz-handle ffz-close-button",c.insertBefore(l,c.firstChild);var f=!1;l.addEventListener("mousedown",function(){var t=h._popup;f=t&&"ffz-chat-menu"===t.id&&t.style.left}),l.addEventListener("click",function(){var t=h._popup;f&&t&&(t.parentElement.removeChild(t),delete h._popup,h._popup_kill&&h._popup_kill(),delete h._popup_kill)}),a.appendChild(c),jQuery(s).draggable({handle:a,cancel:"li.item",axis:"x",stop:function(t){d(this)}}),s.style.position="";var m=[];for(var _ in i.menu_pages)if(i.menu_pages.hasOwnProperty(_)){var p=i.menu_pages[_];try{if(!p||p.hasOwnProperty("visible")&&(!p.visible||"function"==typeof p.visible&&!p.visible.bind(this)(t)))continue}catch(g){this.error("menu_pages "+_+" visible: "+g);continue}m.push([p.sort_order||0,_,p])}m.sort(function(t,e){if(t[0]e[0])return-1;var s=t[1].toLowerCase(),n=e[1].toLowerCase();return n>s?1:s>n?-1:0});for(var v=0;v0,!l||h||f||(l.addObserver("isLoaded",function(){setTimeout(function(){"channel"===s.getAttribute("data-page")&&(s.innerHTML="",i.menu_pages.channel.render.bind(u)(e,s))},0)}),l.load()),_.className="emoticon-grid",p.className="heading",m&&(p.style.backgroundImage='url("'+m+'")'),p.innerHTML='TwitchSubscriber Emoticons',_.appendChild(p);for(var v=c.get("emoticons")||[],b=0;b0&&s.appendChild(_),g>0&&!h){var C=document.createElement("div"),E=document.createElement("div"),x=document.createElement("span"),T=document.createElement("a");C.className="subscribe-message",E.className="non-subscriber-message",C.appendChild(E),x.className="unlock-text",x.innerHTML="Subscribe to unlock Emoticons",E.appendChild(x),T.className="action subscribe-button button primary",T.href=c.get("product_url"),T.innerHTML='",E.appendChild(T),s.appendChild(C)}else if(g>0){var L=l.get("content");if(L=L.length>0?L[L.length-1]:void 0,L&&L.purchase_profile&&!L.purchase_profile.will_renew){var M=a.parse_date(L.access_end||"");C=document.createElement("div"),E=document.createElement("div"),x=document.createElement("span"),end_time=M?Math.floor((M.getTime()-Date.now())/1e3):null,C.className="subscribe-message",E.className="non-subscriber-message",C.appendChild(E),x.className="unlock-text",x.innerHTML="Subscription expires in "+a.time_to_string(end_time,!0,!0),E.appendChild(x),s.appendChild(C)}}}}var S=o&&o.extra_sets||[];this._emotes_for_sets(s,e,o&&o.set&&[o.set]||[],this.feature_friday||d||S.length?"Channel Emoticons":null,"http://cdn.frankerfacez.com/script/devicon.png","FrankerFaceZ");for(var b=0;bs?-1:s>n?1:0});for(var f=0;f0&&(o=!0)}e.classList.toggle("no-emotes",!o),e.classList.toggle("live",d),e.classList.toggle("dark",a),e.classList.toggle("blue",r)}}},{"../constants":5}],29:[function(e,s,n){var i=t.FrankerFaceZ,o=e("../constants"),a=e("../utils"),r="http://static-cdn.jtvnw.net/emoticons/v1/";i.basic_settings.replace_twitch_menu={type:"boolean",category:"Chat",name:"Unified Emoticons Menu",help:"Completely replace the default Twitch emoticon menu and display global emoticons in the My Emoticons menu.",get:function(){return this.settings.replace_twitch_menu&&this.settings.global_emotes_in_menu&&this.settings.emoji_in_menu},set:function(t){this.settings.set("replace_twitch_menu",t),this.settings.set("global_emotes_in_menu",t),this.settings.set("emoji_in_menu",t)}},i.settings_info.replace_twitch_menu={type:"boolean",value:!1,category:"Chat Input",name:"Replace Twitch Emoticon Menu",help:"Completely replace the default Twitch emoticon menu.",on_update:function(t){document.body.classList.toggle("ffz-menu-replace",t)}},i.settings_info.global_emotes_in_menu={type:"boolean",value:!1,category:"Chat Input",name:"Display Global Emotes in My Emotes",help:"Display the global Twitch emotes in the My Emoticons menu."},i.settings_info.emoji_in_menu={type:"boolean",value:!1,category:"Chat Input",name:"Display Emoji in My Emotes",help:"Display the supported emoji images in the My Emoticons menu."},i.settings_info.emote_menu_collapsed={value:[],visible:!1},i.prototype.setup_my_emotes=function(){this._twitch_badges={},this._twitch_badges["--global--"]="//cdn.frankerfacez.com/script/twitch_logo.png",this._twitch_badges["--turbo-faces--"]=this._twitch_badges.turbo="//cdn.frankerfacez.com/script/turbo_badge.png"},i.menu_pages.myemotes={name:"My Emoticons",icon:o.EMOTE,visible:function(t){var e=this.get_user(),s=t.get("controller.currentRoom.tmiSession"),n=e&&this.users[e.login]&&this.users[e.login].sets||[],i=(s&&s.getEmotes()||{emoticon_sets:{}}).emoticon_sets;return n.length||i&&Object.keys(i).length},render:function(t,e){var s=t.get("controller.currentRoom.tmiSession"),n=(s&&s.getEmotes()||{emoticon_sets:{}}).emoticon_sets;return i.menu_pages.myemotes.draw_menu.bind(this)(t,e,n)},toggle_section:function(t){var e=t.parentElement,s=e.getAttribute("data-set"),n=this.settings.emote_menu_collapsed,i=-1!==n.indexOf(s);i?n.removeObject(s):n.push(s),this.settings.set("emote_menu_collapsed",n),e.classList.toggle("collapsed",!i)},draw_emoji:function(t){var e=document.createElement("div"),s=document.createElement("div"),n=this;e.className="heading",e.innerHTML='FrankerFaceZEmoji',e.style.backgroundImage='url("'+o.SERVER+'/emoji/1f4af-1x.png")',s.className="emoticon-grid collapsable",s.appendChild(e),s.setAttribute("data-set","emoji"),s.classList.toggle("collapsed",-1!==this.settings.emote_menu_collapsed.indexOf("emoji")),e.addEventListener("click",function(){i.menu_pages.myemotes.toggle_section.bind(n)(this)});var a=[];for(var r in this.emoji_data)a.push(this.emoji_data[r]);a.sort(function(t,e){var s=t.short_name.toLowerCase(),n=e.short_name.toLowerCase();return n>s?-1:s>n?1:t.rawe.raw?1:0});for(var d=0;ds?-1:s>n?1:t.ide.id?1:0});for(var f=0;fFrankerFaceZ'+s.title,n.style.backgroundImage='url("'+(s.icon||"//cdn.frankerfacez.com/script/devicon.png")+'")',o.className="emoticon-grid collapsable",o.appendChild(n),o.setAttribute("data-set","ffz-"+s.id),o.classList.toggle("collapsed",-1!==this.settings.emote_menu_collapsed.indexOf("ffz-"+s.id)),n.addEventListener("click",function(){i.menu_pages.myemotes.toggle_section.bind(a)(this)});for(var d in s.emoticons)s.emoticons.hasOwnProperty(d)&&!s.emoticons[d].hidden&&r.push(s.emoticons[d]);r.sort(function(t,e){var s=t.name.toLowerCase(),n=e.name.toLowerCase();return n>s?-1:s>n?1:t.ide.id?1:0});for(var u=0;us?-1:s>n?1:0});for(var u=0;us)&&(s=60),this.settings.set("notification_timeout",s)}}},i.ws_commands.message=function(t){this.show_message(t)},i._notifications={},i._last_notification=0,i.prototype.clear_notifications=function(){for(var t in i._notifications){var e=i._notifications[t];if(e)try{e.close()}catch(s){}}i._notifications={},i._last_notification=0},i.prototype.show_notification=function(t,e,s,n,o,a){var r=Notification.permission;if("denied "===r)return!1;if("granted"===r){e=e||"FrankerFaceZ",n=n||1e3*this.settings.notification_timeout;var d={lang:"en-US",dir:"ltr",body:t,tag:s||"FrankerFaceZ",icon:"http://cdn.frankerfacez.com/icon32.png"},u=this,c=new Notification(e,d),l=i._last_notification++;return i._notifications[l]=c,c.addEventListener("click",function(){delete i._notifications[l],o&&o.bind(u)()}),c.addEventListener("close",function(){delete i._notifications[l],a&&a.bind(u)()}),void("number"==typeof n&&c.addEventListener("show",function(){setTimeout(function(){delete i._notifications[l],c.close()},n)}))}var u=this;Notification.requestPermission(function(n){u.show_notification(t,e,s)})},i.prototype.show_message=function(e){t.noty({text:e,theme:"ffzTheme",layout:"bottomCenter",closeWith:["button"]}).show()}},{}],31:[function(e,s,n){var i=t.FrankerFaceZ,o=e("../utils");i.prototype.setup_races=function(){this.log("Initializing race support."),this.srl_races={}},i.settings_info.srl_races={type:"boolean",value:!0,no_mobile:!0,category:"Channel Metadata",name:"SRL Race Information",help:'Display information about SpeedRunsLive races under channels.',on_update:function(t){this.rebuild_race_ui()}},i.ws_on_close.push(function(){var e=t.App&&App.__container__.lookup("controller:channel"),s=e&&e.get("id"),n=e&&e.get("hostModeTarget.id"),i=!1;if(e){for(var o in this.srl_races)delete this.srl_races[o],(o===s||o===n)&&(i=!0);i&&this.rebuild_race_ui()}}),i.ws_commands.srl_race=function(t){var e=App.__container__.lookup("controller:channel"),s=e&&e.get("id"),n=e&&e.get("hostModeTarget.id"),i=!1;this.srl_races=this.srl_races||{};for(var o=0;o=300?"right":"left")+" share dropmenu",this._popup_kill=this._race_kill.bind(this),this._popup=s;var d="http://kadgar.net/live",u=!1;for(var c in a.entrants){var l=a.entrants[c].state;a.entrants.hasOwnProperty(c)&&a.entrants[c].channel&&("racing"==l||"entered"==l)&&(d+="/"+a.entrants[c].channel,u=!0)}var h=document.querySelector(".app-main.theatre")?document.body.clientHeight-300:t.parentElement.offsetTop-175,f=App.__container__.lookup("controller:channel"),m=f?f.get("display_name"):i.get_capitalization(e),_=encodeURIComponent("I'm watching "+m+" race "+a.goal+" in "+a.game+" on SpeedRunsLive!");r='
',r+='
Developers
Dan Salvato  
Stendec  
Version '+i.version_info+'Logs
',r+="
#Entrant Time
",r+='
',r+='',r+='

SRL',u&&(r+='   Multitwitch'),r+="

",s.innerHTML=r,t.appendChild(s),this._update_race(t,!0)}},i.prototype._update_race=function(t,e){if(this._race_timer&&e&&(clearTimeout(this._race_timer),delete this._race_timer),t){var s=t.getAttribute("data-channel"),n=this.srl_races[s];if(!n)return t.parentElement.removeChild(t),void(this._popup&&"ffz-race-popup"===this._popup.id&&this._popup.getAttribute("data-channel")===s&&(this._popup_kill&&this._popup_kill(),this._popup&&(delete this._popup,delete this._popup_kill)));var i=n.twitch_entrants[s],a=n.entrants[i],r=t.querySelector("#ffz-race-popup"),d=Date.now()/1e3,u=Math.floor(d-n.time);if(t.querySelector(".logo").innerHTML=o.placement(a),r){var c=r.querySelector("tbody"),l=r.querySelector(".heading span"),h=r.querySelector(".heading div");c.innerHTML="";var f=[],m=!0;for(var _ in n.entrants)n.entrants.hasOwnProperty(_)&&("racing"==n.entrants[_].state&&(m=!1),f.push(n.entrants[_]));f.sort(function(t,e){var s=t.place||9999,n=e.place||9999,i=t.time||u,o=e.time||u;return("forfeit"==t.state||"dq"==t.state)&&(s=1e4),("forfeit"==e.state||"dq"==e.state)&&(n=1e4),n>s?-1:s>n?1:t.namee.name?1:o>i?-1:i>o?1:void 0});for(var p=0;p'+_.display_name+"",v=_.channel?'':"",b=_.hitbox?'':"",y=u?o.time_to_string(_.time||u):"",w=o.place_string(_.place),z=_.comment?o.sanitize(_.comment):"";c.innerHTML+="'+w+""+g+""+v+b+''+("forfeit"==_.state?"Forfeit":y)+""}if(this._race_game!=n.game||this._race_goal!=n.goal){this._race_game=n.game,this._race_goal=n.goal;var k=o.sanitize(n.game),C=o.sanitize(n.goal);h.innerHTML='

'+k+"

Goal: "+C}u?m?l.innerHTML="Done":(l.innerHTML=o.time_to_string(u),this._race_timer=setTimeout(this._update_race.bind(this,t),1e3)):l.innerHTML="Entry Open"}}}},{"../utils":35}],32:[function(e,s,n){var i=t.FrankerFaceZ,o=e("../constants");i.prototype.setup_css=function(){this.log("Injecting main FrankerFaceZ CSS.");var t=this._main_style=document.createElement("link");t.id="ffz-ui-css",t.setAttribute("rel","stylesheet"),t.setAttribute("href",o.SERVER+"script/style.css?_="+(o.DEBUG?Date.now():i.version_info)),document.head.appendChild(t),jQuery.noty.themes.ffzTheme={name:"ffzTheme",style:function(){this.$bar.removeClass().addClass("noty_bar").addClass("ffz-noty").addClass(this.options.type)},callback:{onShow:function(){},onClose:function(){}}}}},{"../constants":5}],33:[function(e,s,n){{var i=t.FrankerFaceZ,o=e("../constants");e("../utils")}i.prototype._update_subscribers=function(){this._update_subscribers_timer&&(clearTimeout(this._update_subscribers_timer),delete this._update_subscribers_timer),this._update_subscribers_timer=setTimeout(this._update_subscribers.bind(this),6e4);var t=this.get_user(),e=this,s=this.is_dashboard?location.pathname.match(/\/([^\/]+)/):void 0,n=this.is_dashboard&&s&&s[1];if(this.has_bttv||!n||n!==t.login){var i=document.querySelector("#ffz-sub-display");return void(i&&i.parentElement.removeChild(i))}jQuery.ajax({url:"/broadcast/dashboard/partnership"}).done(function(t){try{var s,i=document.createElement("span");i.innerHTML=t,s=i.querySelector("#dash_main");var a=s&&s.textContent.match(/([\d,\.]+) total active subscribers/),r=a&&a[1];if(!r){var d=document.querySelector("#ffz-sub-display");return d&&d.parentElement.removeChild(d),void(e._update_subscribers_timer&&(clearTimeout(e._update_subscribers_timer),delete e._update_subscribers_timer))}var d=document.querySelector("#ffz-sub-display span");if(!d){var u=document.querySelector(e.is_dashboard?"#stats":"#channel .stats-and-actions .channel-stats");if(!u)return;var c=document.createElement("span");c.className="ffz stat",c.id="ffz-sub-display",c.title="Active Channel Subscribers",c.innerHTML=o.STAR+" ",d=document.createElement("span"),c.appendChild(d),Twitch.api.get("chat/"+n+"/badges",null,{version:3}).done(function(t){t.subscriber&&t.subscriber.image&&(c.innerHTML="",c.appendChild(d),c.style.backgroundImage='url("'+t.subscriber.image+'")',c.style.backgroundRepeat="no-repeat",c.style.paddingLeft="23px",c.style.backgroundPosition="0 50%")}),u.appendChild(c),jQuery(c).tipsy(e.is_dashboard?{gravity:"s"}:void 0)}d.innerHTML=r}catch(l){e.error("_update_subscribers: "+l)}}).fail(function(){var t=document.querySelector("#ffz-sub-display");t&&t.parentElement.removeChild(t)})}},{"../constants":5,"../utils":35}],34:[function(e,s,n){var i=t.FrankerFaceZ,o=e("../constants"),a=e("../utils");i.ws_commands.chatters=function(e){{var s=e[0],n=e[1],i=t.App&&App.__container__.lookup("controller:channel"),o=this.is_dashboard?location.pathname.match(/\/([^\/]+)/):void 0;this.is_dashboard?o&&o[1]:i&&i.get&&i.get("id")}if(!this.is_dashboard){var a=this.rooms&&this.rooms[s];return void(a&&(a.ffz_chatters=n,this._cindex&&this._cindex.ffzUpdateChatters()))}this._dash_chatters=n},i.ws_commands.viewers=function(e){var s=e[0],n=e[1],i=t.App&&App.__container__.lookup("controller:channel"),r=this.is_dashboard?location.pathname.match(/\/([^\/]+)/):void 0,d=this.is_dashboard?r&&r[1]:i&&i.get&&i.get("id");if(!this.is_dashboard){var u=this.rooms&&this.rooms[s];return void(u&&(u.ffz_viewers=n,this._cindex&&this._cindex.ffzUpdateChatters()))}if(this._dash_viewers=n,this.settings.chatter_count&&d===s){var c=document.querySelector("#ffz-ffzchatter-display"),l=o.ZREKNARF+" "+a.number_commas(n)+("number"==typeof this._dash_chatters?" ("+a.number_commas(this._dash_chatters)+")":"");if(c)c.innerHTML=l;else{var h=document.querySelector("#stats");if(!h)return;c=document.createElement("span"),c.id="ffz-ffzchatter-display",c.className="ffz stat",c.title="Viewers (In Chat) with FrankerFaceZ",c.innerHTML=l,h.appendChild(c),jQuery(c).tipsy(this.is_dashboard?{gravity:"s"}:void 0)}}}},{"../constants":5,"../utils":35}],35:[function(e,s,n){var i=(t.FrankerFaceZ,e("./constants"),document.createElement("span")),o=function(t){return i.textContent=t,i.innerHTML},a=/"/g,r=/'/g,d=/&/g,u=//g,l=function(t){return t.replace(d,"&").replace(a,""").replace(r,"'").replace(u,"<").replace(c,">")},h=function(t,e,s){return s=s||"s",e=e||"",1===t?e:s},f=function(t){return 1==t?"1st":2==t?"2nd":3==t?"3rd":null==t?"---":t+"th"},m=/^(\d{4}|\+\d{6})(?:-?(\d{2})(?:-?(\d{2})(?:T(\d{2})(?::?(\d{2})(?::?(\d{2})(?:(?:\.|,)(\d{1,}))?)?)?(Z|([\-+])(\d{2})(?::?(\d{2}))?)?)?)?)?$/,_=function(t){var e=t.match(m);if(!e)return null;e[7]=e[7]&&e[7].length?e[7].substr(0,3):0;var s=Date.UTC(e[1],e[2]-1,e[3],e[4],e[5],e[6],e[7]);if(e[9]){var n=6e4*("-"==e[9]?1:-1)*(60*e[10]+1*e[11]);s+=n}return new Date(s)},p=function(t){t=$.trim(t);var e={raw:t},s=-1;"@"===t.charAt(0)&&(s=t.indexOf(" "),e.tags=t.substr(1,s-1));var n=s+1,i=-1;":"===t.charAt(n)&&(i=t.indexOf(" ",n),e.prefix=t.substr(n+1,i-(n+1)));var o=t.indexOf(" :",n);o>=0?e.trailing=t.substr(o+2):o=t.length;var a=t.substr(i+1,o-i-1).split(" ");return e.command=a[0],a.length>1&&(e.params=a.slice(1)),e},g={":":";",s:" ",r:"\r",n:"\n","\\":"\\"},v=function(t){for(var e="",s=0;s=55296&&56319>=i?o=i:n.push(i.toString(16));var r=w[t]=w[t]||{},d=r[e]=n.join("-");return d};s.exports={update_css:function(t,e,s){var n=t.innerHTML,i="/*BEGIN "+e+"*/",o="/*END "+e+"*/",a=n.indexOf(i),r=n.indexOf(o),d=-1!==a&&-1!==r&&r>a;(d||s)&&(d&&(n=n.substr(0,a)+n.substr(r+o.length)),s&&(n+=i+s+o),t.innerHTML=n)},splitIRCMessage:p,parseIRCTags:y,emoji_to_codepoint:z,parse_date:_,number_commas:function(t){var e=t.toString().split(".");return e[0]=e[0].replace(/\B(?=(\d{3})+(?!\d))/g,","),e.join(".")},place_string:f,placement:function(t){return"forfeit"==t.state?"Forfeit":"dq"==t.state?"DQed":t.place?f(t.place):""},sanitize:o,quote_attr:l,date_string:function(t){return t.getFullYear()+"-"+(t.getMonth()+1)+"-"+t.getDate()},pluralize:h,human_time:function(t,e){e=e||1,t=Math.floor(t);var s=Math.floor(t*e/31536e3)/e;if(s>=1)return s+" year"+h(s);var n=Math.floor((t%=31536e3)/86400);if(n>=1)return n+" day"+h(n);var i=Math.floor((t%=86400)/3600);if(i>=1)return i+" hour"+h(i);var o=Math.floor((t%=3600)/60);if(o>=1)return o+" minute"+h(o);var a=t%60;return a>=1?a+" second"+h(a):"less than a second"},time_to_string:function(t,e,s,n){var i=t%60,o=Math.floor(t/60),a=Math.floor(o/60),r="";if(o%=60,e){if(r=Math.floor(a/24),a%=24,s&&r>0)return r+" days";r=r>0?r+" days, ":""}return r+(!n||r||a?(10>a?"0":"")+a+":":"")+(10>o?"0":"")+o+":"+(10>i?"0":"")+i},format_unread:function(t){return 1>t?"":t>=99?"99+":""+t}}},{"./constants":5}]},{},[18]),t.ffz=new FrankerFaceZ}(window); \ No newline at end of file