diff --git a/script.js b/script.js index d5e8e85b..433c67b7 100644 --- a/script.js +++ b/script.js @@ -55,6 +55,7 @@ var ffz = function() { ffz.prototype.last_set = 0; ffz.prototype.last_emote = 0; ffz.prototype.manger = null; +ffz.prototype.has_bttv = false; ffz.commands = {}; @@ -162,6 +163,9 @@ ffz.prototype.setup = function() { document.addEventListener("DOMContentLoaded", this.listen_dom, false); } + // Detect BetterTTV + this.find_bttv(10); + this.log("Initialization complete."); }; @@ -198,7 +202,10 @@ ffz.prototype.listen_dom = function() { // Commands // ----------------- -var msg = function(room, out) { +ffz.prototype._msg = function(room, out) { + if ( this.has_bttv ) + return BetterTTV.chat.helpers.serverMessage(out.replace(/\n/g, "
")); + out = out.split("\n"); for(var i=0; i < out.length; i++) room.addMessage({style: 'ffz admin', from: 'FFZ', message: out[i]}); @@ -216,7 +223,7 @@ ffz.prototype.run_command = function(room, m) { else out = "No such sub-command."; - if ( out ) msg(room, out); + if ( out ) this._msg(room, out); } ffz.commands['help'] = function(room, args) { @@ -335,11 +342,13 @@ ffz.commands['log'] = function(room, args) { ffz.commands['log'].help = "Usage: /ffz log\nOpen a window with FFZ's debugging output."; ffz.commands['list'] = function(room, args) { - var output = '', filter; + var output = '', filter, html = this.has_bttv; if ( args && args.length > 0 ) filter = args.join(" ").toLowerCase(); + if ( html ) output += ""; + for(var name in this.collections) { if ( ! this.collections.hasOwnProperty(name) ) return; @@ -353,19 +362,30 @@ ffz.commands['list'] = function(room, args) { if ( !include ) continue; + if ( html ) + output += ""; + else + output += name + "\n"; + var em = this.collections[name]; - output += name + "\n"; for(var e in em) { if ( em.hasOwnProperty(e) ) { var emote = em[e], t = emote.text; - t = t[0] + "\u200B" + t.substr(1); - output += " " + t + " = " + emote.text + "\n"; + if ( html ) + output += ""; + else { + t = t[0] + "\u200B" + t.substr(1); + output += " " + t + " = " + emote.text + "\n"; + } } } + if ( html ) output += ""; } + if ( html ) output += "
" + name + "
" + t + "" + emote.image.html + "
"; + // Make sure we actually have output. - if ( output.indexOf('\u200B') === -1 ) + if ( output.indexOf(html ? '' : '\u200B') === -1 ) return "There are no available FFZ channel emoticons. If this is in error, please try the /ffz reload command."; else return "The following emotes are available:\n" + output; @@ -393,7 +413,7 @@ ffz.commands['inject'] = function(room, args) { return "/ffz inject requires exactly 1 argument."; var album = args[0].split('/').pop().split('?').shift().split('#').shift(); - room.addMessage({style: 'ffz admin', message: "Attempting to load test emoticons from imgur album \"" + album + "\"..."}); + this._msg(room, "Attempting to load test emoticons from imgur album \"" + album + "\"..."); // Make sure there's no cache hits. var res = "https://api.imgur.com/3/album/" + album; @@ -404,13 +424,13 @@ ffz.commands['inject'] = function(room, args) { {'Accept': 'application/json', 'Authorization': 'Client-ID ' + IMGUR_KEY}, 5); } -ffz.commands['inject'].help = "Usage: /ffz inject \nLoads emoticons from an imgur album for testing. album-id can simply be the album URL. Ex: /ffz inject http://imgur.com/a/v4aZr"; +ffz.commands['inject'].help = "Usage: /ffz inject [album-id]\nLoads emoticons from an imgur album for testing. album-id can simply be the album URL. Ex: /ffz inject http://imgur.com/a/v4aZr"; ffz.prototype.do_imgur = function(room, album, data) { if ( data === undefined ) - return msg(room, "An error occurred communicating with Imgur."); + return this._msg(room, "An error occurred communicating with Imgur."); else if ( !data ) - return msg(room, "The named album does not exist or is private."); + return this._msg(room, "The named album does not exist or is private."); // Get our data structure. data = JSON.parse(data).data; @@ -438,8 +458,56 @@ ffz.prototype.do_imgur = function(room, album, data) { } var count = this.process_css('imgur-' + album, 'FFZ Global Emotes - Imgur Album: ' + album, css); - msg(room, "Loaded " + count + " emoticons from Imgur."); - msg(room, ffz.commands['list'].bind(this)(room, [album])); + this._msg(room, "Loaded " + count + " emoticons from Imgur."); + this._msg(room, ffz.commands['list'].bind(this)(room, [album])); +} + + +// ----------------- +// BetterTTV Hooks +// ----------------- + +ffz.prototype.find_bttv = function(increment, delay) { + if ( !this.alive ) return; + + if ( window.BTTVLOADED ) + return this.setup_bttv(); + + else if ( delay === undefined ) + this.log("BetterTTV not yet loaded. Waiting..."); + + if ( delay >= 60000 ) + this.log("BetterTTV not detected in \"" + location.toString() + "\". Giving up."); + else + setTimeout(this.find_bttv.bind(this, increment, (delay||0) + increment), + increment); +} + +var donor_badge = {type: 'ffz-donor', name: '', description: 'FFZ Donor'}; + +ffz.prototype.setup_bttv = function() { + this.log("BetterTTV was detected. Installing hook."); + this.has_bttv = true; + + // Add badge handling to BetterTTV chat. + var privmsg = BetterTTV.chat.templates.privmsg, f = this; + BetterTTV.chat.templates.privmsg = function(highlight, action, server, isMod, data) { + if ( f.check_donor(data.sender) ) { + var inserted = false; + for(var i=0; i < data.badges.length; i++) { + var t = data.badges[i].type; + if ( t != 'turbo' && t != 'subscriber' ) + continue; + data.badges.insertAt(i, donor_badge); + inserted = true; + break; + } + if ( ! inserted ) + data.badges.push(donor_badge); + } + + return privmsg(highlight, action, server, isMod, data); + } } @@ -600,8 +668,9 @@ ffz.prototype.add_channel = function(id, room) { // Do we have log messages? if ( this._log2.length > 0 ) { + var func = this.has_bttv ? BetterTTV.chat.helpers.serverMessage : room.addTmiMessage; while ( this._log2.length ) - room.addTmiMessage(this._log2.shift()); + func(this._log2.shift()); } // Load the emotes for this channel. @@ -730,8 +799,9 @@ ffz.prototype.process_css = function(group, channel, data) { this.log("Loaded " + count + " emotes from collection: " + group); // Notify the manager that we've added emotes. - if ( this.manager ) - this.manager.notifyPropertyChange('emoticons'); + // Don't notify the manager for now because of BTTV. + //if ( this.manager && ! this.has_bttv ) + // this.manager.notifyPropertyChange('emoticons'); return count; } @@ -791,8 +861,9 @@ ffz.prototype.unload_emotes = function(group) { this.emoticons = this.emoticons.filter(filt); // Update the emoticons with the manager. - if ( this.manager ) - this.manager.notifyPropertyChange('emoticons'); + // Don't notify the manager for now for BTTV. + //if ( this.manager && ! this.has_bttv ) + // this.manager.notifyPropertyChange('emoticons'); } diff --git a/script.min.js b/script.min.js new file mode 100644 index 00000000..3b0bfcb5 --- /dev/null +++ b/script.min.js @@ -0,0 +1,31 @@ +(function wrapper(l,t){if(t){var p=document.createElement("script");p.textContent="("+wrapper+")(window, false)";document.body.appendChild(p);document.body.removeChild(p)}else{var u=/\.([\w\-_]+) \{content:.*?"([^"]+)";.*?background-image: url\("([^"]+)"\);.*?height:.*?(\d+).+?width:.*?(\d+)[^}]*\}/mg,r=-1!==location.search.indexOf("frankerfacez"),d=function(){this.alive=!0;this.donors={};this.getting={};this.emoticons=[];this.emotesets={};this.channels={};this.collections={};this.globals={};this.global_sets= +[];this.styles={};this.pending_styles=[];this._log=[];this._log2=[];this.init(10)};d.prototype.last_set=0;d.prototype.last_emote=0;d.prototype.manger=null;d.prototype.has_bttv=!1;d.commands={};d.prototype.log=function(b){this._log.push(b);b="FFZ"+(this.alive?": ":" (Dead): ")+b;console.log(b);if(r){var c,a;for(a in this.channels)if(this.channels[a]&&this.channels[a].room){c=this.channels[a];break}c?c.room.addTmiMessage(b):this._log2.push(b)}};d.prototype.init=function(b,c){if(this.alive){if(l.CurrentChat&& +l.CurrentChat.emoticons)return this.log("Detected old chat. Injecting old FFZ."),this.inject_old();if(void 0==l.Ember||void 0==l.App||void 0==App.EmoticonsController||void 0==App.Room)6E4<=c?this.log('Twitch API not detected in "'+location.toString()+'". Aborting.'):setTimeout(this.init.bind(this,b,(c||0)+b),b);else{if(App.hasOwnProperty("useNewChat")&&!App.useNewChat)return this.log("Detected old chat. Injecting old FFZ."),this.inject_old();this.setup()}}};d.prototype.inject_old=function(){this._dom&& +document.removeEventListener("DOMContentLoaded",this._dom,!1);if(document.body){var b=document.createElement("script");b.src="//commondatastorage.googleapis.com/frankerfacez/script/old-frankerfacez.js";document.body.appendChild(b);document.body.removeChild(b)}else this._dom=this.inject_old.bind(this),document.addEventListener("DOMContentLoaded",this._dom,!1)};d.prototype.setup=function(){this.alive&&(this.log("Hooking Ember application."),this.modify_room(),this.modify_viewers(),this.modify_emotes(), +this.modify_lines(),this.log("Loading data."),this.load_donors(),this.load_emotes("global"),document.body||(this.listen_dom=this.listen_dom.bind(this),document.addEventListener("DOMContentLoaded",this.listen_dom,!1)),this.find_bttv(10),this.log("Initialization complete."))};d.prototype.destroy=function(){this.alive&&(alive=!1,l.ffz===this&&(l.ffz=void 0),delete this._log,delete this._log2)};d.prototype.listen_dom=function(){for(document.removeEventListener("DOMContentLoaded",this.listen_dom,!1);this.pending_styles.length;)document.body.appendChild(this.pending_styles.pop())}; +d.prototype._msg=function(b,c){if(this.has_bttv)return BetterTTV.chat.helpers.serverMessage(c.replace(/\n/g,"
"));c=c.split("\n");for(var a=0;a'+f+""):a+(f+"\n"),e=this.collections[f],n;for(n in e)if(e.hasOwnProperty(n)){var k=e[n], +m=k.text;g?a+=''+m+""+k.image.html+"":(m=m[0]+"\u200b"+m.substr(1),a+=" "+m+" = "+k.text+"\n")}g&&(a+="")}}g&&(a+="");return-1===a.indexOf(g?"":"\u200b")?"There are no available FFZ channel emoticons. If this is in error, please try the /ffz reload command.":"The following emotes are available:\n"+a};d.commands.list.help="Usage: /ffz list [global]\nList available FFZ emoticons. Use the global parameter to list ALL FFZ emoticons, or filter for a specific set."; +d.commands.global=function(b,c){return d.commands.list.bind(this)(b,["global"])};d.commands.global.help="Usage: /ffz global\nShorthand for /ffz list global. List ALL FFZ emoticons, including FFZ global emoticons.";d.commands.reload=function(b,c){for(var a in this.channels)this.channels.hasOwnProperty(a)&&this.channels[a]&&this.load_emotes(a,!0);this.load_emotes("global");this.load_donors();return"Attempting to reload FFZ data from the server."};d.commands.reload.help="Usage: /ffz reload\nAttempt to reload FFZ emoticons and donors."; +d.commands.inject=function(b,c){if(!c||1!==c.length)return"/ffz inject requires exactly 1 argument.";var a=c[0].split("/").pop().split("?").shift().split("#").shift();this._msg(b,'Attempting to load test emoticons from imgur album "'+a+'"...');var d="https://api.imgur.com/3/album/"+a;l.localStorage&&localStorage.removeItem("ffz_"+d);this.get(d,this.do_imgur.bind(this,b,a),1,{Accept:"application/json",Authorization:"Client-ID e48d122e3437051"},5)};d.commands.inject.help="Usage: /ffz inject [album-id]\nLoads emoticons from an imgur album for testing. album-id can simply be the album URL. Ex: /ffz inject http://imgur.com/a/v4aZr"; +d.prototype.do_imgur=function(b,c,a){if(void 0===a)return this._msg(b,"An error occurred communicating with Imgur.");if(!a)return this._msg(b,"The named album does not exist or is private.");a=JSON.parse(a).data;a=a.images;for(var h="",g=0;g',id:--k.last_emote};a="!"===e[e.length-1]?RegExp("\\b"+e+"(?=\\W|$)","g"):RegExp("\\b"+e+"\\b","g");e={image:h,images:[h],text:e,channel:c,hidden:!1,regex:a,ffzset:b};m.push(e);k.emoticons.push(e);f.push({isEmoticon:!0,cls:d,regex:a});l++});this.log("Loaded "+l+" emotes from collection: "+ +b);return l}};d.prototype.unload_emotes=function(b){if(this.alive){var c=this.channels[b],a,d,g;if(!1!==c){c?(a=c.set_id,d=c.style,g="FFZ Channel Emotes: "+b,delete c.set,delete c.set_id,delete c.style):(a=this.globals[b],d=this.styles[b],g="FFZ Global Emotes"+("global"!=b?": "+b:""),delete this.globals[b],delete this.styles[b],c=this.global_sets.indexOf(a),-1!==c&&this.global_sets.splice(c,1));this.collections[g]&&delete this.collections[g];if(d)try{d.parentNode.removeChild(d)}catch(f){}delete this.emotesets[a]; +this.manager&&delete this.manager.emoticonSets[a];this.emoticons=this.emoticons.filter(function(a){return a.ffzgroup!==b})}}};d.prototype.check_donor=function(b){return this.donors[b]||!1};d.prototype.load_donors=function(b){this.get("//commondatastorage.googleapis.com/frankerfacez/donors.txt",this.process_donors.bind(this),b?1:108E5)};d.prototype.process_donors=function(b){if(this.alive){this.donors={};var c=0;if(null!=b){b=b.trim().split(/\W+/);for(var a=0;aa?(this.log("Resource expired. Fetching: "+ +b),this.do_get(b,c,0,d,g)):this.getting[b]=!1}};d.prototype.do_get=function(b,c,a,d,g){function f(){var e=(a||0)+1;if(!g||e<=g)return setTimeout(k.do_get.bind(k,b,c,e,d,g),1E3),!0}if(this.alive){var e=new XMLHttpRequest;e.open("GET",b);if(d)for(var n in d)d.hasOwnProperty(n)&&e.setRequestHeader(n,d[n]);var k=this;e.addEventListener("error",function(a){if(!f()){k.getting[b]=!1;try{c(void 0)}catch(d){k.log("Error in callback: "+d)}}},!1);e.addEventListener("load",function(a){if(200===e.status){if(a= +e.responseText,l.localStorage){var d=localStorage.getItem("ffz_last_"+b),g=e.getResponseHeader("Last-Modified");if(d&&d==g){k.log("Resource not modified: "+b);localStorage.setItem("ffz_age_"+b,(new Date).getTime());k.getting[b]=!1;return}localStorage.setItem("ffz_last_"+b,g)}}else{if(304===e.status){k.log("Resource not modified: "+b);l.localStorage&&localStorage.setItem("ffz_age_"+b,(new Date).getTime());k.getting[b]=!1;return}if(404===e.status)a=null;else{if(f())return;a=void 0}}l.localStorage&& +void 0!==a&&(localStorage.setItem("ffz_"+b,JSON.stringify(a)),localStorage.setItem("ffz_age_"+b,(new Date).getTime()));k.getting[b]=!1;try{c(a)}catch(h){k.log("Error in callback: "+h)}},!1);e.send()}else this.getting[b]=!1};l.ffz=new d}})(this.unsafeWindow||window,window.chrome?!0:!1); \ No newline at end of file