diff --git a/dark.css b/dark.css index 9d1f4d5a..891cc5b6 100644 --- a/dark.css +++ b/dark.css @@ -1,107 +1,131 @@ /* host mode */ .ffz-dark #hostmode,.hostmode{ - background-color:rgb(16,16,16)!important; + background-color:rgb(16,16,16)!important; } .ffz-dark div#channel > .target-frame.active{ - background-color:rgb(16,16,16)!important; + background-color:rgb(16,16,16)!important; } .ffz-dark .hostmode-title-container{ - background-color:rgb(16,16,16)!important; - box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.4) inset; + background-color:rgb(16,16,16)!important; + box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.4) inset; } .ffz-dark div.hostmode-title.clearfix{ - background-color:rgb(24,24,24)!important; - box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.4) inset; + background-color:rgb(24,24,24)!important; + box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.4) inset; } .ffz-dark .close-hostmode{ - background-color:rgb(16,16,16)!important; - border-top: 1px solid rgba(255, 255, 255, 0.05); + background-color:rgb(16,16,16)!important; + border-top: 1px solid rgba(255, 255, 255, 0.05); } /* hidden chat */ .ffz-dark .ember-chat .chat-hidden-overlay{ - background-color:rgb(24,24,24)!important; + background-color:rgb(24,24,24)!important; } /* edit icon */ .ffz-dark #channel .player-column #broadcast-meta .info .edit-link svg path{ - fill:rgba(255,255,255,0.5)!important; + fill:rgba(255,255,255,0.5)!important; } /* vod icons */ +.ffz-dark #stats .stat svg:not(.svg-glyph_live) path, .ffz-dark #main_col .content #stats_and_actions #channel_stats .stat svg path{ - fill:rgba(255,255,255,0.5)!important; + fill:rgba(255,255,255,0.5)!important; } /* hover icon for chats menu */ .ffz-dark .ember-chat .chat-menu-button-container:hover svg path{ - fill:#fff!important; + fill:#fff!important; } /* dropdown arrows */ .ffz-dark .button.drop:after{ -border-color: rgba(255, 255, 255, 0.35) transparent transparent!important;; + border-color: rgba(255, 255, 255, 0.35) transparent transparent!important;; } /* hovering buttons */ .ffz-dark .button:not(.primary):not(.ffz-donate):hover{ - color:rgb(222,222,222)!important; + color:rgb(222,222,222)!important; } .ffz-dark .moderation-card .button:not(.glyph-only):hover { color: #fff !important; } +/* moderation card */ + +.ember-chat-container.dark .ember-chat .ffz-moderation-card, +.chat-container.dark .ember-chat .ffz-moderation-card, +.app-main.theatre .ember-chat .ffz-moderation-card { + border-color: #1b1b20; +} + +.ember-chat-container.dark .ember-chat .moderation-card:focus, +.chat-container.dark .ember-chat .moderation-card:focus, +.app-main.theatre .ember-chat .moderation-card:focus { + border-color: #cbcbcb; +} + +.ember-chat-container.dark .ember-chat .moderation-card .interface, +.chat-container.dark .ember-chat .moderation-card .interface, +.app-main.theatre .ember-chat .moderation-card .interface { + background-color: #232329; +} + +.moderation-card h3.name a { color: #fff !important; } + + /* stats */ .ffz-dark #channel .player-column .stats-and-actions .channel-stats .stat svg:not(.svg-glyph_live) path{ - fill:rgba(255,255,255,0.35)!important; + fill:rgba(255,255,255,0.35)!important; } /* Scrollbar */ .ffz-dark .tse-scrollbar .drag-handle { - background: none repeat scroll 0% 0% rgba(255, 255, 255, 0.6)!important; + background: none repeat scroll 0% 0% rgba(255, 255, 255, 0.6)!important; } /* main column */ -.ffz-dark div#main_col.column{ - background:rgb(16,16,16)!important; - color:rgb(195,195,195)!important; - border-right:1px solid rgb(0,0,0)!important; +.ffz-dark div#main_col { + background:rgb(16,16,16)!important; + color:rgb(195,195,195)!important; + border-right:1px solid rgb(0,0,0)!important; } /* stream title */ .ffz-dark span.real_title{ - color:rgb(245,245,245)!important; + color:rgb(245,245,245)!important; } /* name playing x on x */ .ffz-dark span.playing,span#team_membership,.ffz-dark #channel .player-column #broadcast-meta .info .channel { - color:rgb(195,195,195)!important; + color:rgb(195,195,195)!important; } .ffz-dark div.title > span.real, .ffz-dark div.title > span.over{ - color:rgb(222,222,222)!important; - background-color:rgb(16,16,16)!important; + color:rgb(222,222,222)!important; + background-color:rgb(16,16,16)!important; } .ffz-dark div.title > span.over:hover, .ffz-dark div.title > span.real:hover { - background-color:rgb(16,16,16)!important; - color:rgb(255,255,255)!important; + background-color:rgb(16,16,16)!important; + color:rgb(255,255,255)!important; } /* Right Column */ @@ -114,8 +138,10 @@ border-color: rgba(255, 255, 255, 0.35) transparent transparent!important;; /* Popups */ +.ffz-dark .ember-chat .chat-interface .ffz-ui-popup.emoticon-selector .emoticon-selector-box, .ffz-dark .card, .ffz-dark #flyout .content, +.ffz-dark .ui-menu, .ffz-dark .dropmenu, .ffz-dark .top-dropdown, .ffz-dark form.js-new_panel_form, @@ -127,6 +153,10 @@ border-color: rgba(255, 255, 255, 0.35) transparent transparent!important;; border-color: #32323e; } +.ffz-dark .js-new_panel_btn:hover { + background-color: rgb(24,24,24); +} + .ffz-dark #flyout .point::before { border-right-color: rgb(16,16,16); /*rgb(25,25,31);*/ } @@ -153,54 +183,79 @@ border-color: rgba(255, 255, 255, 0.35) transparent transparent!important;; /* Other stuff */ +.ffz-dark #channel .player-column #broadcast-meta .info .edit-link span { + color: inherit; +} + +.ffz-dark .no-login-contain li { + background-color: rgb(25,25,31); +} + +.ffz-dark .no-login-contain h3 { + color: #ccc; +} + .ffz-dark .panel-formatting .panel { - color: #8c8c9c; + color: #8c8c9c; } .ffz-dark .manager .videos-grid .video:hover .meta .actions li a, .ffz-dark .ember-chat .chat-room-list .room:not(:hover) p.room-title, .ffz-dark .dropmenu_action:not(:hover) span, -.ffz-dark a:not(.switch):not(.follow) { +.ffz-dark a:not(.button):not(.switch):not(.follow):not(.fb_button) { color: #a68ed2; } +.ffz-dark a.dropmenu_action:hover { + color: #fff !important; +} + .ffz-dark .panel-formatting .panel h3 { - color: inherit; + color: inherit; } .ffz-dark .follow-button .notify:before, .ffz-dark .button.drop:after, .ffz-dark .follow-button .drop.follow:after { - border: 5px solid rgba(255,255,255,0.35); - border-left-color: transparent; - border-right-color: transparent; - border-bottom-color: transparent; + border: 5px solid rgba(255,255,255,0.35); + border-left-color: transparent; + border-right-color: transparent; + border-bottom-color: transparent; } .ffz-dark .follow-button .notify { - background-color: #25252a; + background-color: #25252a; } .ffz-dark .button:not(.primary) { - color: #a68ed2; + color: #a68ed2; } .ffz-dark .button.glyph-only svg path { - fill: #a68ed2; + fill: #a68ed2; } .ffz-dark .button.glyph-only:hover svg path { - fill: #fff; + fill: #fff; } .ffz-dark .button.primary.subscribe-button { - color: #fff; + color: #fff; } .ffz-dark .ember-chat .moderation-card img.channel_logo { border-color: rgb(16,16,16); } +/* Upsell banner */ +.ffz-dark .upsell-banner { + background-color: rgb(25,25,31); +} + +.ffz-dark .upsell-banner .message .title { + color: #ccc; +} + /* Video Manager */ .ffz-dark .manager .videos-grid .video .meta .actions { @@ -257,6 +312,16 @@ border-color: rgba(255, 255, 255, 0.35) transparent transparent!important;; /* Profile page fixes */ +.ffz-dark pre { + background-color: rgb(8,8,8); + box-shadow: inset 0 0 0 1px #333; + border-left-color: #6441a5; +} + +.ffz-dark code { + color: #999; +} + .ffz-dark .streams .stream .content .thumb .boxart, .ffz-dark .videos .video .content .thumb .boxart { border-color: rgb(16,16,16); @@ -269,11 +334,11 @@ border-color: rgba(255, 255, 255, 0.35) transparent transparent!important;; .ffz-dark .items-grid .meta .title, .ffz-dark .items-grid .meta p a { - color: #9c9c9c; + color: #9c9c9c !important; } .ffz-dark .items-grid .meta .info { - color: #6c6c6c; + color: #6c6c6c; } .ffz-dark ul.tabs li > a, @@ -316,6 +381,15 @@ border-color: rgba(255, 255, 255, 0.35) transparent transparent!important;; box-shadow: inset 0 0 0 1px #32323e; } + +/* Autocomplete Suggestions */ + +.ffz-dark .ember-chat .chat-interface .suggestions { + background-color: rgb(16,16,16); + border-color: rgba(255,255,255,0.2); +} + + /* FrankerFaceZ Menu */ .ffz-dark .ember-chat .chat-menu .list-header { @@ -350,4 +424,284 @@ border-color: rgba(255, 255, 255, 0.35) transparent transparent!important;; .ffz-dark #new-user-prompt .message .title { color: #fff; +} + +/* Messages Table */ + +.ffz-dark .messages #send_message_form #bottom_buttons, +.messages #reply_message_form #bottom_buttons { + border-color: rgba(255,255,255,0.2); + box-shadow: none; +} + +.ffz-dark .messages #send_message_form .button_group, +.ffz-dark .messages #reply_message_form .button_group, +.ffz-dark .messages #send_message_form, +.ffz-dark .messages #reply_message_form { + background: transparent; +} + +.ffz-dark .messages #send_message_form, +.ffz-dark .messages #reply_message_form +{ padding: 10px 0 0 0; } + +.ffz-dark .messages #message_actions { + background-color: rgb(48,48,48); + border-bottom-color: rgba(255,255,255,0.2); +} + +.ffz-dark .messages #send_message_form, +.ffz-dark .messages .divider { + border-color: rgba(255,255,255,0.2); +} + +.ffz-dark .messages .message_action.delete { + display: block; + width: 16px; + height: 16px; + background: url(trash_button.png) no-repeat; +} + +.ffz-dark .messages .message_action.delete img { display: none; } + +.ffz-dark .messages .message_action.block { + background-image: url(block_button.png); + color: #c1c1c1 !important; +} + +.ffz-dark .messages div.message { + border-bottom-color: rgba(255,255,255,0.2); +} + +.ffz-dark .messages div.message:last-of-type { border-bottom-color: transparent; } + +.ffz-dark .messages div.preview { + background-color: rgb(16,16,16); + border-bottom-color: rgba(255,255,255,0.2); + border-left-color: transparent; +} + +.ffz-dark .messages div.preview.unread { + background-color: rgb(32,32,32); + border-left-color: #6441a5; +} + +.ffz-dark .messages div.preview:hover { + background-color: rgb(8,8,8); +} + +.ffz-dark .messages #messages_column { + background-color: transparent; +} + +.ffz-dark .page_links em { + background-color: #EEE; + color: #000; +} + +.ffz-dark .page_links a.disabled.previous_page b, +.ffz-dark .page_links span.disabled.previous_page b { border-right-color: transparent; } + +.ffz-dark .page_links a.disabled.next_page b, +.ffz-dark .page_links span.disabled.next_page b { border-left-color: transparent; } + +/* Tshirts are weird */ + +.ffz-dark .teespring-panel-progress { + background-color: #393939; +} + +.ffz-dark .teespring-panel-image a { + cursor: pointer; +} + +.ffz-dark .teespring-panel-image img { + filter: drop-shadow(0px 0px 10px white); + -moz-filter: drop-shadow(0px 0px 10px white); + -webkit-filter: drop-shadow(0px 0px 10px white); +} + +.ffz-dark .teespring-panel { + cursor: inherit; + background-color: rgb(8,8,8); + border-color: rgba(255,255,255,0.2); +} + +/* Settings */ + +.ffz-dark .app_title { color: #ccc; } + +.ffz-dark .connect_items .connect-item-info .details-toggle, +.ffz-dark .connect_items .connect-item-info, +.ffz-dark ul.manage_simple span.obj { + background-color: rgb(8,8,8); +} + +.ffz-dark .connect_items .connect-item-info .details-toggle { + border-color: rgba(255,255,255,0.2); +} + +.ffz-dark .connect_items .connect-item-info .details-toggle:hover { + border-color: rgba(255,255,255,0.4); +} + +.ffz-dark .connect_items .connect-item-info .status, +.ffz-dark #settings #followers_content .hdr, +.ffz-dark table.simple_table th { + background-color: rgb(32,32,32); + border-color: transparent; +} + +.ffz-dark .multi_select li form:hover, +.ffz-dark .multi_select li .ms-int:hover, +.ffz-dark .multi_select li label:hover { + background-color: rgb(8,8,8); +} + +.ffz-dark #turbo_chat_color, +.ffz-dark #turbo_emote_set { + background-color: rgba(255,255,255,0.05); +} + +.ffz-dark .ui-slider-horizontal { + background-color: rgba(255,255,255,0.2); +} + +.ffz-dark .cl-container .section-header, +.ffz-dark .cl-container .cl-section .cl-subheader { + background-color: rgb(32,32,32); +} + +.ffz-dark .cl-container .cl-section { + background-color: rgb(16,16,16); +} + +.ffz-dark table.simple_table td, +.ffz-dark table.simple_table th, +.ffz-dark .cl-container .section-header, +.ffz-dark .cl-container fieldset { + border-bottom-color: rgba(255,255,255,0.2); +} + +.ffz-dark .cl-container fieldset .label-wrapper label { + color: #ccc; +} + +.ffz-dark #emoticons #emote_switch .set-button, +.ffz-dark #example .line { + background-color: #19191f; + color: #acacbf; +} + +/* Dashboard */ + +.ffz-dark .js-show-stream-key, +.ffz-dark .js-reset-stream-key { + display: block; + margin: 15px auto; + padding: 0; + text-align: center; + width: 100px; + color: #fff !important; +} + +.ffz-dark .js-show-stream-key { background-color: #804400; } +.ffz-dark .js-reset-stream-key { r-color: rgb(128,0,0); } + +.ffz-dark .js-show-stream-key:hover, +.ffz-dark .js-show-stream-key:focus, +.ffz-dark .js-reset-stream-key:hover, +.ffz-dark .js-reset-stream-key:focus { + padding: 5px; + margin: 10px auto; +} + +.ffz-dark .js-show-stream-key:hover, +.ffz-dark .js-show-stream-key:focus { background-color: rgb(192,102,0); } + +.ffz-dark .js-reset-stream-key:hover, +.ffz-dark .js-reset-stream-key:focus { background-color: rgb(192,0,0); } + +.ffz-dark .statchart_aggregator .statchart_first_column { + background-color: rgb(32,32,32); + color: #ccc; + text-shadow: none; +} + +.ffz-dark .statchart_aggregator { + margin-top: 10px; +} + +.ffz-dark .statchart_aggregator td { + border-color: #3f3f42; +} + +.ffz-dark .statchart_aggregator .statchart_first_column[style="color: #6441A5"] { + color: #a68ed2 !important; +} + +.ffz-dark .whatisthis { + background-color: #333; + box-shadow: 0 0 0 1px rgba(255,255,255,0.2); +} + +.ffz-dark #chart_container svg rect[fill="#FFFFFF"] { + fill: rgb(32,32,32) !important; +} + +.ffz-dark #chart_container svg rect[fill="rgb(255,255,255)"] { + fill: rgb(8,8,8) !important; +} + +.ffz-dark #chart_container svg text[x="5"] { + color: #fff !important; + fill: #fff !important; +} + +.ffz-dark #chart_container svg text.highcharts-title { + fill: #a68ed2 !important; +} + +.ffz-dark #chart_container svg text { + fill: #fff; +} + +.ffz-dark ul.subtabs li.selected { + background-color: #333; + border-radius: 4px; +} + +.ffz-dark .dropmenu .menu-divider { + border-bottom-color: rgba(255,255,255,0.2); +} + +.ffz-dark #header_logo svg path { + fill: #fff; +} + +.ffz-dark #dash_main .action .content .data, +.ffz-dark #site_header { + background-color: rgb(32,32,32); +} + +.ffz-dark .header_divider { + border-right-color: rgba(255,255,255,0.2); +} + +.ffz-dark .dash-hostmode-list-contain { + border-top-color: rgba(255,255,255,0.2); +} + +.ffz-dark #dash_main #controls_column .section_header { + color: #ccc; +} + +.ffz-dark .main, +.ffz-dark .fullwidth_main { + color: #ccc; + background-color: transparent; +} + +.ffz-dark .dash-chat-column { + border-color: rgba(255,255,255,0.2); } \ No newline at end of file diff --git a/script.js b/script.js index 0f04fe05..56771da2 100644 --- a/script.js +++ b/script.js @@ -324,7 +324,7 @@ FFZ.prototype._legacy_parse_badges = function(data, slot, badge_id) { this.log('Added "' + title + '" badge to ' + utils.number_commas(count) + " users."); } -},{"./constants":3,"./utils":27}],2:[function(require,module,exports){ +},{"./constants":3,"./utils":28}],2:[function(require,module,exports){ var FFZ = window.FrankerFaceZ; @@ -410,6 +410,7 @@ module.exports = { ZREKNARF: '' + SVGPATH + '', CHAT_BUTTON: '' + SVGPATH + '', + CLOCK: '', GEAR: '', HEART: '', EMOTE: '' @@ -455,6 +456,178 @@ FFZ.ffz_commands.developer_mode = function(room, args) { 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."; },{}],5:[function(require,module,exports){ +var FFZ = window.FrankerFaceZ, + utils = require('../utils'), + constants = require('../constants'); + + +// -------------------- +// Initialization +// -------------------- + +FFZ.prototype.setup_channel = function() { + this.channels = {}; + + 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 view."); + + var Channel = App.__container__.lookup('controller:channel'), + f = this; + + if ( ! Channel ) + return; + + Channel.reopen({ + ffzUpdateUptime: function() { + f.update_uptime(); + }.observes("isLive", "content.id").on("init") + + /*ffzUpdateInfo: function() { + f.log("Updated! ID: " + this.get("content.id")); + f.update_stream_info(true); + }.observes("content.id").on("init")*/ + }); + + // Do uptime the first time. + this.update_uptime(); + //this.update_stream_info(true); +} + + +// --------------- +// Settings +// --------------- + +FFZ.settings_info.stream_uptime = { + type: "boolean", + value: false, + + category: "Channel Metadata", + name: "Stream Uptime", + help: 'Display the stream uptime under a channel by the viewer count.', + on_update: function(val) { + this.update_uptime(); + } + }; + + +// -------------------- +// Stream Data Update +// -------------------- + +/*FFZ.prototype.update_stream_info = function(just_schedule) { + if ( this._stream_info_update ) { + clearTimeout(this._stream_info_update); + delete this._stream_info_update; + } + + this._stream_info_update = setTimeout(this.update_stream_info.bind(this), 90000); + + if ( just_schedule ) + return; + + var Channel = App.__container__.lookup('controller:channel'), + channel_id = Channel ? Channel.get('content.id') : undefined, + f = this; + if ( ! channel_id ) + return; + + Twitch.api.get("streams/" + channel_id, {}, {version: 3}) + .done(function(data) { + var channel_id = Channel.get('content.id'), d = data.stream; + if ( ! data.stream || d.channel.name != channel_id ) + return; + + // Override the data in Twitch. We can't just .load() the stream + // because that resets the whole channel layout, resetting the + // video player. Twitch pls fix + var old_created = Channel.get('content.stream.created_at'); + + Channel.set('content.stream.created_at', d.created_at); + Channel.set('content.stream.average_fps', d.average_fps); + Channel.set('content.stream.viewers', d.viewers); + Channel.set('content.stream.video_height', d.video_height); + Channel.set('content.stream.csGoSkill', Twitch.uri.csGoSkillImg(("0" + d.skill).slice(-2))); + + Channel.set('content.stream.game', d.game); + Channel.set('content.stream.gameUrl', Twitch.uri.game(d.game)); + Channel.set('content.stream.gameBoxart', Twitch.uri.gameBoxArtJpg(d.game)); + + + // Update the uptime display. + if ( f.settings.stream_uptime && old_created != d.created_at ) + f.update_uptime(true) && f.update_uptime(); + }); +}*/ + + +// -------------------- +// Uptime Display +// -------------------- + +FFZ.prototype.update_uptime = function(destroy) { + if ( this._uptime_update ) { + clearTimeout(this._uptime_update); + delete this._uptime_update; + } + + var Channel = App.__container__.lookup('controller:channel'); + if ( destroy || ! this.settings.stream_uptime || ! Channel || ! Channel.get('isLiveAccordingToKraken') ) { + var el = document.querySelector("#ffz-uptime-display"); + if ( el ) + el.parentElement.removeChild(el); + return; + } + + // Schedule an update. + this._update_uptime = setTimeout(this.update_uptime.bind(this), 1000); + + // Determine when the channel last went live. + var online = Channel.get('content.stream.created_at'); + if ( ! online ) return; + + online = utils.parse_date(online); + if ( ! online ) return; + + var uptime = Math.floor((Date.now() - online.getTime()) / 1000); + if ( uptime < 0 ) return; + + var el = document.querySelector("#ffz-uptime-display span"); + if ( ! el ) { + var cont = document.querySelector("#channel .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); +} +},{"../constants":3,"../utils":28}],6:[function(require,module,exports){ var FFZ = window.FrankerFaceZ; @@ -529,7 +702,7 @@ FFZ.prototype._modify_cview = function(view) { }) }); } -},{}],6:[function(require,module,exports){ +},{}],7:[function(require,module,exports){ var FFZ = window.FrankerFaceZ, utils = require("../utils"), @@ -603,6 +776,106 @@ var FFZ = window.FrankerFaceZ, var images = document.querySelectorAll('img[emote-id="' + id + '"]'); for(var x=0; x < images.length; x++) images[x].title = tooltip; + }, + + 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 = "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 == "twitter" ) { + tooltip = "Tweet By: " + utils.sanitize(link_data.user) + "
"; + tooltip += utils.sanitize(link_data.tweet); + + + } else if ( link_data.type == "reputation" ) { + 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 = '' + 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; + } }; @@ -610,24 +883,13 @@ var FFZ = window.FrankerFaceZ, // Settings // --------------------- -FFZ.settings_info.capitalize = { - type: "boolean", - value: true, - - category: "Chat", - visible: function() { return ! this.has_bttv }, - - name: "Username Capitalization", - help: "Display names in chat with proper capitalization." - }; - - FFZ.settings_info.banned_words = { type: "button", value: [], category: "Chat", - visible: function() { return ! this.has_bttv }, + 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.", @@ -658,7 +920,8 @@ FFZ.settings_info.keywords = { value: [], category: "Chat", - visible: function() { return ! this.has_bttv }, + no_bttv: true, + //visible: function() { return ! this.has_bttv }, name: "Highlight Keywords", help: "Set additional keywords that will be highlighted in chat.", @@ -690,7 +953,8 @@ FFZ.settings_info.fix_color = { value: true, category: "Chat", - visible: function() { return ! this.has_bttv }, + no_bttv: true, + //visible: function() { return ! this.has_bttv }, name: "Adjust Username Colors", help: "Ensure that username colors contrast with the background enough to be readable.", @@ -704,12 +968,26 @@ FFZ.settings_info.fix_color = { }; +FFZ.settings_info.link_info = { + type: "boolean", + value: true, + + category: "Chat", + no_bttv: true, + //visible: function() { return ! this.has_bttv }, + + name: "Link Tooltips Beta", + help: "Check links against known bad websites, unshorten URLs, and show YouTube info." + }; + + FFZ.settings_info.chat_rows = { type: "boolean", value: false, category: "Chat", - visible: function() { return ! this.has_bttv }, + no_bttv: true, + //visible: function() { return ! this.has_bttv }, name: "Chat Line Backgrounds", help: "Display alternating background colors for lines in chat.", @@ -743,6 +1021,7 @@ FFZ.prototype.setup_line = function() { // Emoticon Data this._twitch_emotes = {}; + this._link_data = {}; this.log("Hooking the Ember Line controller."); @@ -764,6 +1043,11 @@ FFZ.prototype.setup_line = function() { if ( ! user || this.get("model.from") != user.login ) tokens = f._mentionize(this, tokens); + // Store the capitalization. + var display = this.get("model.tags.display-name"); + if ( display && display.length ) + FFZ.capitalization[this.get("model.from")] = [display.trim(), Date.now()]; + var end = performance.now(); if ( end - start > 5 ) f.log("Tokenizing Message Took Too Long - " + (end-start) + "ms", tokens, false, true); @@ -797,7 +1081,6 @@ FFZ.prototype.setup_line = function() { row_type = controller.get('model.ffz_alternate'); - // Color Processing if ( color ) f._handle_color(color); @@ -822,11 +1105,6 @@ FFZ.prototype.setup_line = function() { f.render_badge(this); - // Capitalization - if ( f.settings.capitalize ) - f.capitalize(this, user); - - // Mention Highlighting var mentioned = el.querySelector('span.mentioned'); if ( mentioned ) { @@ -889,12 +1167,52 @@ FFZ.prototype.setup_line = function() { 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(); }); // Also add a nice tooltip. - jQuery(link).tipsy(); + jQuery(link).tipsy({html:true}); + } + + + // Link Tooltips + if ( f.settings.link_info ) { + var links = el.querySelectorAll("span.message a"); + for(var i=0; i < links.length; i++) { + var link = links[i], + href = link.href, + deleted = false; + + if ( link.classList.contains("deleted-link") ) { + href = link.getAttribute("data-url"); + deleted = true; + } + + // Check the cache. + var link_data = f._link_data[href]; + if ( link_data ) { + if ( !deleted && typeof link_data != "boolean" ) + link.title = link_data.tooltip; + + if ( link_data.unsafe ) + link.classList.add('unsafe-link'); + + } else if ( ! /^mailto:/.test(href) ) { + f._link_data[href] = true; + f.ws_send("get_link", href, load_link_data.bind(f, href)); + } + } + + jQuery(links).tipsy({html:true}); } @@ -1068,16 +1386,6 @@ FFZ.get_capitalization = function(name, callback) { } -FFZ.prototype.capitalize = function(view, user) { - var name = FFZ.get_capitalization(user, this.capitalize.bind(this, view)); - if ( !name || !view ) - return; - - var from = view.$('.from'); - from && from.text(name); -} - - // --------------------- // Extra Mentions // --------------------- @@ -1242,7 +1550,7 @@ FFZ.prototype._emoticonize = function(controller, tokens) { return tokens; } -},{"../utils":27}],7:[function(require,module,exports){ +},{"../utils":28}],8:[function(require,module,exports){ var FFZ = window.FrankerFaceZ, utils = require("../utils"), @@ -1250,7 +1558,8 @@ var FFZ = window.FrankerFaceZ, ESC: 27, P: 80, B: 66, - T: 84 + T: 84, + U: 85 }, btns = [ @@ -1272,7 +1581,8 @@ FFZ.settings_info.enhanced_moderation = { type: "boolean", value: false, - visible: function() { return ! this.has_bttv }, + no_bttv: true, + //visible: function() { return ! this.has_bttv }, category: "Chat", name: "Enhanced Moderation", @@ -1286,32 +1596,33 @@ FFZ.settings_info.enhanced_moderation = { FFZ.prototype.setup_mod_card = function() { this.log("Hooking the Ember Moderation Card view."); - var Card = App.__container__.resolve('view:moderation-card'), + var Card = App.__container__.resolve('component:moderation-card'), f = this; Card.reopen({ didInsertElement: function() { this._super(); + window._card = this; try { if ( f.has_bttv || ! f.settings.enhanced_moderation ) return; var el = this.get('element'), - controller = this.get('context'); + controller = this.get('controller'); // Style it! el.classList.add('ffz-moderation-card'); // Only do the big stuff if we're mod. - if ( controller.get('parentController.model.isModeratorOrHigher') ) { + if ( controller.get('cardInfo.isModeratorOrHigher') ) { el.classList.add('ffz-is-mod'); el.setAttribute('tabindex', 1); // Key Handling el.addEventListener('keyup', function(e) { var key = e.keyCode || e.which, - user_id = controller.get('model.user.id'), - room = controller.get('parentController.model'); + 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"); @@ -1322,6 +1633,9 @@ FFZ.prototype.setup_mod_card = function() { 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; @@ -1334,8 +1648,8 @@ FFZ.prototype.setup_mod_card = function() { line.className = 'interface clearfix'; var btn_click = function(timeout) { - var user_id = controller.get('model.user.id'), - room = controller.get('parentController.model'); + var user_id = controller.get('cardInfo.user.id'), + room = App.__container__.lookup('controller:chat').get('currentRoom'); if ( timeout === -1 ) room.send("/unban " + user_id); @@ -1396,8 +1710,9 @@ FFZ.prototype.setup_mod_card = function() { // More Fixing Other Buttons var op_btn = el.querySelector('button.mod'); if ( op_btn ) { - var model = controller.get('parentController.model'), - can_op = model.get('isBroadcaster') || model.get('isStaff') || model.get('isAdmin'); + 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); @@ -1405,7 +1720,7 @@ FFZ.prototype.setup_mod_card = function() { var msg_btn = el.querySelector(".interface > button"); - if ( msg_btn && msg_btn.className == "button" ) { + if ( msg_btn && msg_btn.classList.contains("message-button") ) { msg_btn.innerHTML = MESSAGE; msg_btn.classList.add('glyph-only'); msg_btn.classList.add('message'); @@ -1494,7 +1809,7 @@ FFZ.chat_commands.u = function(room, args) { } FFZ.chat_commands.u.enabled = function() { return this.settings.enhanced_moderation; } -},{"../utils":27}],8:[function(require,module,exports){ +},{"../utils":28}],9:[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*['"]([^'"]+)['"][^}]+(?:}|$)/, @@ -1525,6 +1840,16 @@ FFZ.prototype.setup_room = function() { this.log("Hooking the Ember Room model."); + // Responsive ban button. + var RC = App.__container__.lookup('controller:room'); + if ( RC ) { + var orig_action = RC._actions.banUser; + RC._actions.banUser = function(e) { + orig_action.bind(this)(e); + this.get("model").clearMessages(e.user); + } + } + var Room = App.__container__.resolve('model:room'); this._modify_room(Room); @@ -1784,8 +2109,7 @@ FFZ.prototype._modify_room = function(room) { var suggestions = this._super(); try { - if ( f.settings.capitalize ) - suggestions = _.map(suggestions, FFZ.get_capitalization); + suggestions = _.map(suggestions, FFZ.get_capitalization); } catch(err) { f.error("get_suggestions: " + err); } @@ -1862,7 +2186,7 @@ FFZ.prototype._legacy_load_room_css = function(room_id, callback, data) { output.css = data || null; return this._load_room_json(room_id, callback, output); } -},{"../constants":3,"../utils":27}],9:[function(require,module,exports){ +},{"../constants":3,"../utils":28}],10:[function(require,module,exports){ var FFZ = window.FrankerFaceZ; @@ -1960,7 +2284,7 @@ FFZ.prototype._modify_viewers = function(controller) { }.property("content.chatters") }); } -},{}],10:[function(require,module,exports){ +},{}],11:[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*['"]([^'"]+)['"][^}]+(?:}|$)/, @@ -2165,7 +2489,7 @@ FFZ.prototype._legacy_load_css = function(set_id, callback, data) { this._load_set_json(set_id, callback, output); } -},{"./constants":3,"./utils":27}],11:[function(require,module,exports){ +},{"./constants":3,"./utils":28}],12:[function(require,module,exports){ var FFZ = window.FrankerFaceZ, SENDER_REGEX = /(\sdata-sender="[^"]*"(?=>))/; @@ -2342,7 +2666,7 @@ FFZ.prototype.setup_bttv = function(delay) { this.update_ui_link(); } -},{}],12:[function(require,module,exports){ +},{}],13:[function(require,module,exports){ var FFZ = window.FrankerFaceZ; @@ -2418,7 +2742,7 @@ FFZ.prototype._emote_menu_enumerator = function() { return emotes; } -},{}],13:[function(require,module,exports){ +},{}],14:[function(require,module,exports){ // Modify Array and others. require('./shims'); @@ -2443,7 +2767,7 @@ FFZ.get = function() { return FFZ.instance; } // Version var VER = FFZ.version_info = { - major: 3, minor: 2, revision: 5, + major: 3, minor: 3, revision: 1, toString: function() { return [VER.major, VER.minor, VER.revision].join(".") + (VER.extra || ""); } @@ -2514,7 +2838,7 @@ FFZ.prototype.get_user = function() { if ( window.PP && PP.login ) { return PP; } else if ( window.App ) { - var nc = App.__container__.lookup("controller:navigation"); + var nc = App.__container__.lookup("controller:login"); return nc ? nc.get("userData") : undefined; } } @@ -2536,6 +2860,7 @@ require('./emoticons'); require('./badges'); // Analytics: require('./ember/router'); +require('./ember/channel'); require('./ember/room'); require('./ember/line'); require('./ember/chatview'); @@ -2575,7 +2900,17 @@ FFZ.prototype.initialize = function(increment, delay) { // Make sure that FrankerFaceZ doesn't start setting itself up until the // Twitch ember application is ready. - // TODO: Special Dashboard check. + // Check for special non-ember pages. + if ( /\/(?:settings|messages?\/)/.test(location.pathname) ) { + this.setup_normal(delay); + return; + } + + // Check for the dashboard. + if ( /\/[A-Za-z_-]+\/dashboard/.test(location.pathname) && !/bookmarks$/.test(location.pathname) ) { + this.setup_dashboard(delay); + return; + } var loaded = window.App != undefined && App.__container__ != undefined && @@ -2595,6 +2930,65 @@ FFZ.prototype.initialize = function(increment, delay) { } +FFZ.prototype.setup_normal = function(delay) { + 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 = {}; + + // 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_notifications(); + this.setup_css(); + + 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.setup_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; + + // 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_notifications(); + this.setup_css(); + + 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.setup_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); @@ -2614,6 +3008,7 @@ FFZ.prototype.setup_ember = function(delay) { //this.setup_piwik(); //this.setup_router(); + this.setup_channel(); this.setup_room(); this.setup_line(); this.setup_chatview(); @@ -2639,7 +3034,7 @@ FFZ.prototype.setup_ember = function(delay) { this.log("Initialization complete in " + duration + "ms"); } -},{"./badges":1,"./commands":2,"./debug":4,"./ember/chatview":5,"./ember/line":6,"./ember/moderation-card":7,"./ember/room":8,"./ember/viewers":9,"./emoticons":10,"./ext/betterttv":11,"./ext/emote_menu":12,"./featurefriday":14,"./settings":15,"./shims":16,"./socket":17,"./ui/about_page":18,"./ui/dark":19,"./ui/menu":20,"./ui/menu_button":21,"./ui/my_emotes":22,"./ui/notifications":23,"./ui/races":24,"./ui/styles":25,"./ui/viewer_count":26}],14:[function(require,module,exports){ +},{"./badges":1,"./commands":2,"./debug":4,"./ember/channel":5,"./ember/chatview":6,"./ember/line":7,"./ember/moderation-card":8,"./ember/room":9,"./ember/viewers":10,"./emoticons":11,"./ext/betterttv":12,"./ext/emote_menu":13,"./featurefriday":15,"./settings":16,"./shims":17,"./socket":18,"./ui/about_page":19,"./ui/dark":20,"./ui/menu":21,"./ui/menu_button":22,"./ui/my_emotes":23,"./ui/notifications":24,"./ui/races":25,"./ui/styles":26,"./ui/viewer_count":27}],15:[function(require,module,exports){ var FFZ = window.FrankerFaceZ, constants = require('./constants'); @@ -2689,7 +3084,7 @@ 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._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. @@ -2787,7 +3182,7 @@ FFZ.prototype._update_ff_name = function(name) { if ( this.feature_friday ) this.feature_friday.display_name = name; } -},{"./constants":3}],15:[function(require,module,exports){ +},{"./constants":3}],16:[function(require,module,exports){ var FFZ = window.FrankerFaceZ, constants = require("./constants"); @@ -2848,6 +3243,19 @@ FFZ.prototype.load_settings = function() { // Menu Page // -------------------- +FFZ.settings_info.replace_twitch_menu = { + type: "boolean", + value: false, + + name: "Replace Twitch Emoticon Menu Beta", + help: "Completely replace the default Twitch emoticon menu.", + + on_update: function(val) { + document.body.classList.toggle("ffz-menu-replace", val); + } + }; + + FFZ.menu_pages.settings = { render: function(view, container) { var settings = {}, @@ -2931,37 +3339,53 @@ FFZ.menu_pages.settings = { el.className = 'clearfix'; - if ( info.type == "boolean" ) { - var swit = document.createElement('a'), - label = document.createElement('span'); - - swit.className = 'switch'; - swit.classList.toggle('active', val); - swit.innerHTML = ""; - + if ( this.has_bttv && info.no_bttv ) { + var label = document.createElement('span'), + help = document.createElement('span'); label.className = 'switch-label'; label.innerHTML = info.name; - el.appendChild(swit); - el.appendChild(label); + help = document.createElement('span'); + help.className = 'help'; + help.innerHTML = 'Disabled due to incompatibility with BetterTTV.'; - swit.addEventListener("click", toggle_setting.bind(this, swit, key)); + el.classList.add('disabled'); + el.appendChild(label); + el.appendChild(help); } else { - el.classList.add("option"); - var link = document.createElement('a'); - link.innerHTML = info.name; - link.href = "#"; - el.appendChild(link); + if ( info.type == "boolean" ) { + var swit = document.createElement('a'), + label = document.createElement('span'); - link.addEventListener("click", info.method.bind(this)); - } + swit.className = 'switch'; + swit.classList.toggle('active', val); + swit.innerHTML = ""; - if ( info.help ) { - var help = document.createElement('span'); - help.className = 'help'; - help.innerHTML = info.help; - el.appendChild(help); + label.className = 'switch-label'; + label.innerHTML = info.name; + + el.appendChild(swit); + el.appendChild(label); + + swit.addEventListener("click", toggle_setting.bind(this, swit, key)); + + } 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); @@ -2973,7 +3397,8 @@ FFZ.menu_pages.settings = { name: "Settings", icon: constants.GEAR, - sort_order: 99999 + sort_order: 99999, + wide: true }; @@ -2985,8 +3410,6 @@ FFZ.prototype._setting_update = function(e) { if ( ! e ) e = window.event; - this.log("Storage Event", e); - if ( ! e.key || e.key.substr(0, 12) !== "ffz_setting_" ) return; @@ -3079,7 +3502,7 @@ FFZ.prototype._setting_del = function(key) { this.log('Error running updater for setting "' + key + '": ' + err); } } -},{"./constants":3}],16:[function(require,module,exports){ +},{"./constants":3}],17:[function(require,module,exports){ Array.prototype.equals = function (array) { // if the other array is a falsy value, return if (!array) @@ -3105,7 +3528,7 @@ Array.prototype.equals = function (array) { } -},{}],17:[function(require,module,exports){ +},{}],18:[function(require,module,exports){ var FFZ = window.FrankerFaceZ; FFZ.prototype._ws_open = false; @@ -3144,6 +3567,13 @@ FFZ.prototype.ws_create = function() { 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]); + } + // Send the current rooms. for(var room_id in f.rooms) f.rooms.hasOwnProperty(room_id) && f.ws_send("sub", room_id); @@ -3267,7 +3697,7 @@ FFZ.ws_commands.do_authorize = function(data) { // Try again shortly. setTimeout(FFZ.ws_commands.do_authorize.bind(this, data), 5000); } -},{}],18:[function(require,module,exports){ +},{}],19:[function(require,module,exports){ var FFZ = window.FrankerFaceZ, constants = require("../constants"); @@ -3307,6 +3737,18 @@ FFZ.menu_pages.about = { 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'), @@ -3363,7 +3805,7 @@ FFZ.menu_pages.about = { container.appendChild(credits); } } -},{"../constants":3}],19:[function(require,module,exports){ +},{"../constants":3}],20:[function(require,module,exports){ var FFZ = window.FrankerFaceZ, constants = require("../constants"); @@ -3383,9 +3825,10 @@ FFZ.settings_info.dark_twitch = { type: "boolean", value: false, - visible: function() { return ! this.has_bttv }, + no_bttv: true, + //visible: function() { return ! this.has_bttv }, - name: "Dark Twitch Beta", + name: "Dark Twitch", help: "Apply a dark background to channels and other related pages for easier viewing.", on_update: function(val) { @@ -3394,14 +3837,14 @@ FFZ.settings_info.dark_twitch = { document.body.classList.toggle("ffz-dark", val); - var model = App.__container__.lookup('controller:settings').get('model'); + var model = window.App ? App.__container__.lookup('controller:settings').get('model') : undefined; if ( val ) { this._load_dark_css(); - this.settings.set('twitch_chat_dark', model.get('darkMode')); - model.set('darkMode', true); + model && this.settings.set('twitch_chat_dark', model.get('darkMode')); + model && model.set('darkMode', true); } else - model.set('darkMode', this.settings.twitch_chat_dark); + model && model.set('darkMode', this.settings.twitch_chat_dark); } }; @@ -3416,7 +3859,7 @@ FFZ.prototype.setup_dark = function() { document.body.classList.toggle("ffz-dark", this.settings.dark_twitch); if ( this.settings.dark_twitch ) - App.__container__.lookup('controller:settings').set('model.darkMode', true); + window.App && App.__container__.lookup('controller:settings').set('model.darkMode', true); if ( this.settings.dark_twitch ) this._load_dark_css(); @@ -3436,9 +3879,12 @@ FFZ.prototype._load_dark_css = function() { s.setAttribute('href', constants.SERVER + "script/dark.css?_=" + Date.now()); document.head.appendChild(s); } -},{"../constants":3}],20:[function(require,module,exports){ +},{"../constants":3}],21:[function(require,module,exports){ var FFZ = window.FrankerFaceZ, - constants = require('../constants'); + constants = require('../constants'), + utils = require('../utils'), + + TWITCH_BASE = "http://static-cdn.jtvnw.net/emoticons/v1/"; // -------------------- @@ -3462,6 +3908,8 @@ FFZ.prototype.setup_menu = function() { delete f._popup_kill; } }); + + document.body.classList.toggle("ffz-menu-replace", this.settings.replace_twitch_menu); } @@ -3547,27 +3995,31 @@ FFZ.prototype.build_ui_popup = function(view) { jQuery(link).tipsy(); - link.addEventListener("click", this._ui_change_page.bind(this, view, menu, sub_container, key)); + link.addEventListener("click", this._ui_change_page.bind(this, view, inner, menu, sub_container, key)); el.appendChild(link); menu.appendChild(el); } // Render Current Page - this._ui_change_page(view, menu, sub_container, this._last_page || "channel"); + this._ui_change_page(view, inner, menu, sub_container, this._last_page || "channel"); // Add the menu to the DOM. this._popup = container; - sub_container.style.maxHeight = Math.max(100, view.$().height() - 162) + "px"; + sub_container.style.maxHeight = Math.max(200, view.$().height() - 172) + "px"; view.$('.chat-interface').append(container); } -FFZ.prototype._ui_change_page = function(view, menu, container, page) { +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'); @@ -3582,55 +4034,6 @@ FFZ.prototype._ui_change_page = function(view, menu, container, page) { } -// -------------------- -// Favorites Page -// -------------------- - -FFZ.prototype._tokenize_message = function(message, room_id) { - var lc = App.__container__.lookup('controller:line'), - rc = App.__container__.lookup('controller:room'), - room = this.rooms[room_id], - user = this.get_user(); - - if ( ! lc || ! rc || ! room ) - return [message]; - - rc.set('model', room.room); - lc.set('parentController', rc); - - var model = { - from: user && user.login || "FrankerFaceZ", - message: message, - tags: { - emotes: room.room.tmiSession._emotesParser.parseEmotesTag(message) - } - }; - - lc.set('model', model); - - var tokens = lc.get('tokenizedMessage'); - - lc.set('model', null); - rc.set('model', null); - lc.set('parentController', null); - - return tokens; -} - - -/*FFZ.menu_pages.favorites = { - render: function(view, container) { - // Get the current room. - var room_id = view.get('controller.currentRoom.id'); - - - }, - - name: "Favorites", - icon: constants.HEART - };*/ - - // -------------------- // Channel Page // -------------------- @@ -3639,10 +4042,107 @@ 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]; + room = this.rooms[room_id], + has_product = false; + + // 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 is_subscribed = room.room.get("channel.isSubscribed.content"), + 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; + + 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; + if ( can_use ) + s.addEventListener('click', this._add_emote.bind(this, view, emote.regex)); + grid.appendChild(s); + c++; + } + + if ( c > 0 ) + inner.appendChild(grid); + + if ( ! 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 = 'Subscribe' + product.get("price") + ''; + nonsub_message.appendChild(sub_link); + + inner.appendChild(sub_message); + } else { + var last_content = room.room.get("channel.isSubscribed.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); + } + } + } + } // Basic Emote Sets - this._emotes_for_sets(inner, view, room && room.menu_sets || []); + this._emotes_for_sets(inner, view, room && room.menu_sets || [], (this.feature_friday || has_product) ? "Channel Emoticons" : null, "http://cdn.frankerfacez.com/channel/global/devicon.png", "FrankerFaceZ"); // Feature Friday! this._feature_friday_ui(room_id, inner, view); @@ -3657,21 +4157,29 @@ FFZ.menu_pages.channel = { // Emotes for Sets // -------------------- -FFZ.prototype._emotes_for_sets = function(parent, view, sets, header, btn) { - if ( header != null ) { - var el_header = document.createElement('div'); - el_header.className = 'list-header'; - el_header.appendChild(document.createTextNode(header)); - - if ( btn ) - el_header.appendChild(btn); - - parent.appendChild(el_header); - } - +FFZ.prototype._emotes_for_sets = function(parent, view, sets, header, image, sub_text) { var grid = document.createElement('div'), c = 0; 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); + } + for(var i=0; i < sets.length; i++) { var set = this.emote_sets[sets[i]]; if ( ! set || ! set.emotes ) @@ -3698,8 +4206,8 @@ FFZ.prototype._emotes_for_sets = function(parent, view, sets, header, btn) { } if ( !c ) { - grid.innerHTML = "This channel has no emoticons."; - grid.className = "chat-menu-content ffz-no-emotes center"; + grid.innerHTML += "This channel has no emoticons."; + grid.className = "emoticon-grid ffz-no-emotes center"; } parent.appendChild(grid); @@ -3725,7 +4233,7 @@ FFZ.prototype._add_emote = function(view, emote) { else room.set('messageToSend', text); } -},{"../constants":3}],21:[function(require,module,exports){ +},{"../constants":3,"../utils":28}],22:[function(require,module,exports){ var FFZ = window.FrankerFaceZ, constants = require('../constants'); @@ -3746,7 +4254,7 @@ FFZ.prototype.build_ui_link = function(view) { FFZ.prototype.update_ui_link = function(link) { - var controller = App.__container__.lookup('controller:chat'); + var controller = window.App && App.__container__.lookup('controller:chat'); link = link || document.querySelector('a.ffz-ui-toggle'); if ( !link || !controller ) return; @@ -3776,7 +4284,7 @@ FFZ.prototype.update_ui_link = function(link) { link.classList.toggle('dark', dark); link.classList.toggle('blue', blue); } -},{"../constants":3}],22:[function(require,module,exports){ +},{"../constants":3}],23:[function(require,module,exports){ var FFZ = window.FrankerFaceZ, constants = require("../constants"), @@ -3784,6 +4292,12 @@ var FFZ = window.FrankerFaceZ, BANNED_SETS = {"00000turbo":true}, KNOWN_CODES = { + "#-?[\\\\/]": "#-/", + ":-?(?:7|L)": ":-7", + "\\<\\;\\]": "<]", + "\\:-?(S|s)": ":-S", + "\\:-?\\\\": ":-\\", + "\\:\\>\\;": ":>", "B-?\\)": "B-)", "\\:-?[z|Z|\\|]": ":-Z", "\\:-?\\)": ":-)", @@ -3791,7 +4305,7 @@ var FFZ = window.FrankerFaceZ, "\\:-?(p|P)": ":-P", "\\;-?(p|P)": ";-P", "\\<\\;3": "<3", - "\\:-?(?:\\/|\\\\)(?!\\/)": ":-/", + "\\:-?[\\\\/]": ":-/", "\\;-?\\)": ";-)", "R-?\\)": "R-)", "[o|O](_|\\.)[o|O]": "O.o", @@ -3812,7 +4326,7 @@ var FFZ = window.FrankerFaceZ, user_sets = user && ffz.users[user.login] && ffz.users[user.login].sets || []; // Remove the 'default' set. - set_ids = set_ids.split(",").removeObject("0") + set_ids = set_ids.split(",").removeObject("0"); if ( ffz.settings.global_emotes_in_menu ) { set_ids.push("0"); @@ -3847,6 +4361,8 @@ FFZ.prototype.setup_my_emotes = function() { } this._twitch_set_to_channel[0] = "twitch_global"; + this._twitch_set_to_channel[33] = "twitch_tfaces"; + this._twitch_set_to_channel[42] = "twitch_tfaces"; } @@ -3930,7 +4446,7 @@ FFZ.menu_pages.my_emotes = { return; } - if ( name == "turbo" ) { + if ( name == "turbo" || name == "twitch_tfaces" ) { set.channel = "Twitch Turbo"; set.badge = "//cdn.frankerfacez.com/script/turbo_badge.png"; return; @@ -3970,7 +4486,7 @@ FFZ.menu_pages.my_emotes = { if ( ! set.channel ) set.channel = name; dn(); - }.bind(this,set,name,dn), 2000); + }.bind(this,set,name,dn), 500); }.bind(this, set, lname, name))); }, handle_promises = function() { @@ -4065,11 +4581,16 @@ FFZ.menu_pages.my_emotes = { var an = a[1].toLowerCase(), bn = b[1].toLowerCase(); - if ( an === "twitch turbo" || an === "global emoticons" ) - an = "zzz" + an; + if ( an === "twitch turbo" || an === "twitch_tfaces" ) + an = "zza|" + an; + + else if ( an === "global emoticons" ) + an = "zzz|" + an; - if ( bn === "twitch turbo" || bn === "global emoticons" ) - bn = "zzz" + bn; + if ( bn === "twitch turbo" || bn === "twitch_tfaces" ) + bn = "zza|" + bn; + else if ( bn === "global emoticons" ) + bn = "zzz|" + bn; if ( an < bn ) return -1; else if ( an > bn ) return 1; @@ -4104,7 +4625,7 @@ FFZ.menu_pages.my_emotes = { } else { ems = set.emotes; - title = FFZ.get_capitalization(set.channel); + title = set.channel == "Twitch Turbo" ? set.channel : FFZ.get_capitalization(set.channel); badge = set.badge; } @@ -4181,7 +4702,7 @@ FFZ.menu_pages.my_emotes = { } }; -},{"../constants":3}],23:[function(require,module,exports){ +},{"../constants":3}],24:[function(require,module,exports){ var FFZ = window.FrankerFaceZ; @@ -4204,7 +4725,8 @@ FFZ.settings_info.highlight_notifications = { value: false, category: "Chat", - visible: function() { return ! this.has_bttv }, + no_bttv: true, + //visible: function() { return ! this.has_bttv }, name: "Highlight Notifications", help: "Display notifications when a highlighted word appears in chat in an unfocused tab.", @@ -4330,7 +4852,7 @@ FFZ.prototype.show_message = function(message) { closeWith: ["button"] }).show(); } -},{}],24:[function(require,module,exports){ +},{}],25:[function(require,module,exports){ var FFZ = window.FrankerFaceZ, utils = require('../utils'); @@ -4520,14 +5042,14 @@ FFZ.prototype.build_race_popup = function() { out += '
'; out += '
#Entrant Time
'; out += '
'; - - out += ''; - + + out += ''; + out += '

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

'; popup.innerHTML = out; container.appendChild(popup); @@ -4617,7 +5139,7 @@ FFZ.prototype._update_race = function(not_timer) { 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; @@ -4638,7 +5160,7 @@ FFZ.prototype._update_race = function(not_timer) { } } } -},{"../utils":27}],25:[function(require,module,exports){ +},{"../utils":28}],26:[function(require,module,exports){ var FFZ = window.FrankerFaceZ, constants = require('../constants'); @@ -4663,7 +5185,7 @@ FFZ.prototype.setup_css = function() { } }; } -},{"../constants":3}],26:[function(require,module,exports){ +},{"../constants":3}],27:[function(require,module,exports){ var FFZ = window.FrankerFaceZ, constants = require('../constants'), utils = require('../utils'); @@ -4675,32 +5197,34 @@ var FFZ = window.FrankerFaceZ, FFZ.ws_commands.viewers = function(data) { var channel = data[0], count = data[1]; - var controller = App.__container__.lookup('controller:channel'), - id = controller && controller.get && controller.get('id'); + 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 ( id !== channel ) return; - var view_count = document.querySelector('.channel-stats .ffz.stat'), + var view_count = document.querySelector('#ffz-viewer-display'), content = constants.ZREKNARF + ' ' + utils.number_commas(count); if ( view_count ) view_count.innerHTML = content; else { - var parent = document.querySelector('.channel-stats'); + var parent = document.querySelector(this.is_dashboard ? "#stats" : '.stats-and-actions .channel-stats'); if ( ! parent ) return; view_count = document.createElement('span'); + view_count.id = "ffz-viewer-display"; view_count.className = 'ffz stat'; - view_count.title = 'Viewers with FrankerFaceZ'; + view_count.title = 'Chatters with FrankerFaceZ'; view_count.innerHTML = content; parent.appendChild(view_count); - jQuery(view_count).tipsy(); + jQuery(view_count).tipsy(this.is_dashboard ? {"gravity":"s"} : undefined); } } -},{"../constants":3,"../utils":27}],27:[function(require,module,exports){ +},{"../constants":3,"../utils":28}],28:[function(require,module,exports){ var FFZ = window.FrankerFaceZ, constants = require('./constants'); @@ -4820,14 +5344,28 @@ module.exports = { return m; }, - time_to_string: function(elapsed) { + date_string: function(date) { + return date.getFullYear() + "-" + (date.getMonth()+1) + "-" + date.getDate(); + }, + + time_to_string: function(elapsed, separate_days, days_only) { var seconds = elapsed % 60, minutes = Math.floor(elapsed / 60), - hours = Math.floor(minutes / 60); + hours = Math.floor(minutes / 60), + days = ""; minutes = minutes % 60; - return (hours < 10 ? "0" : "") + hours + ":" + (minutes < 10 ? "0" : "") + minutes + ":" + (seconds < 10 ? "0" : "") + seconds; + 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 + (hours < 10 ? "0" : "") + hours + ":" + (minutes < 10 ? "0" : "") + minutes + ":" + (seconds < 10 ? "0" : "") + seconds; } } -},{"./constants":3}]},{},[13]);window.ffz = new FrankerFaceZ()}(window)); \ No newline at end of file +},{"./constants":3}]},{},[14]);window.ffz = new FrankerFaceZ()}(window)); \ No newline at end of file diff --git a/script.min.js b/script.min.js index a189f759..e6bd3440 100644 --- a/script.min.js +++ b/script.min.js @@ -1,3 +1,3 @@ -!function(e){!function t(e,n,o){function i(s,r){if(!n[s]){if(!e[s]){var l="function"==typeof require&&require;if(!r&&l)return l(s,!0);if(a)return a(s,!0);throw new Error("Cannot find module '"+s+"'")}var c=n[s]={exports:{}};e[s][0].call(c.exports,function(t){var n=e[s][1][t];return i(n?n:t)},c,c.exports,t,e,n,o)}return n[s].exports}for(var a="function"==typeof require&&require,s=0;se&&this._legacy_load_bots(e))})},n.prototype._legacy_load_donors=function(e){jQuery.ajax(o.SERVER+"script/donors.txt",{cache:!1,context:this}).done(function(e){this._legacy_parse_badges(e,1,1)}).fail(function(t){return 404!=t.status?(e=(e||0)+1,10>e?this._legacy_load_donors(e):void 0):void 0})},n.prototype._legacy_parse_badges=function(e,t,n){var o=this.badges[n].title,a=0;if(ds=1==n?".donor":"",null!=e)for(var s=e.trim().split(/\W+/),r=0;r50)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 o=t.length;t.length;){var i=t.shift();e.room.tmiRoom.sendMessage("/unmod "+i)}return"Sent unmod command for "+o+" users."},t.ffz_commands.massunmod.help="Usage: /ffz massunmod \nBroadcaster only. Unmod all the users in the provided list.",t.ffz_commands.massmod=function(e,t){if(t=t.join(" ").trim(),!t.length)return"You must provide a list of users to mod.";t=t.split(/\W*,\W*/);var n=this.get_user();if(!n||!n.login==e.id)return"You must be the broadcaster to use massmod.";if(t.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 o=t.length;t.length;){var i=t.shift();e.room.tmiRoom.sendMessage("/mod "+i)}return"Sent mod command for "+o+" users."},t.ffz_commands.massmod.help="Usage: /ffz massmod \nBroadcaster only. Mod all the users in the provided list."},{}],3:[function(e,t){var n='',o="true"==localStorage.ffzDebugMode&&document.body.classList.contains("ffz-dev");t.exports={DEBUG:o,SERVER:o?"//localhost:8000/":"//cdn.frankerfacez.com/",SVGPATH:n,ZREKNARF:''+n+"",CHAT_BUTTON:''+n+"",GEAR:'',HEART:'',EMOTE:''}},{}],4:[function(){var t=e.FrankerFaceZ;t.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()}},t.ffz_commands.developer_mode=function(e,t){var n,t=t&&t.length?t[0].toLowerCase():null;return"y"==t||"yes"==t||"true"==t||"on"==t?n=!0:("n"==t||"no"==t||"false"==t||"off"==t)&&(n=!1),void 0===n?"Developer Mode is currently "+(this.settings.developer_mode?"enabled.":"disabled."):(this.settings.set("developer_mode",n),"Developer Mode is now "+(n?"enabled":"disabled")+". Please refresh your browser.")},t.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."},{}],5:[function(){var t=e.FrankerFaceZ;t.prototype.setup_chatview=function(){this.log("Hooking the Ember Chat view.");var e=App.__container__.resolve("view:chat");this._modify_cview(e);try{e.create().destroy()}catch(t){}for(var n in Ember.View.views)if(Ember.View.views.hasOwnProperty(n)){var o=Ember.View.views[n];if(o instanceof e){this.log("Adding UI link manually to Chat view.",o);try{o.$(".textarea-contain").append(this.build_ui_link(o))}catch(t){this.error("setup: build_ui_link: "+t)}}}},t.prototype._modify_cview=function(e){var t=this;e.reopen({didInsertElement:function(){this._super();try{this.$()&&this.$(".textarea-contain").append(t.build_ui_link(this))}catch(e){t.error("didInsertElement: build_ui_link: "+e)}},willClearRender:function(){this._super();try{this.$(".ffz-ui-toggle").remove()}catch(e){t.error("willClearRender: remove ui link: "+e)}},ffzUpdateLink:Ember.observer("controller.currentRoom",function(){try{t.update_ui_link()}catch(e){t.error("ffzUpdateLink: update_ui_link: "+e)}})})}},{}],6:[function(t){var n=e.FrankerFaceZ,o=t("../utils"),i=function(e){return e.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")},a="[\\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]",s=new RegExp(a+"*,"+a+"*"),r=function(e){return(e+"").replace(/&/g,"&").replace(/'/g,"'").replace(/"/g,""").replace(//g,">")},l="http://static-cdn.jtvnw.net/emoticons/v1/",c=function(e){return l+e+"/1.0 1x, "+l+e+"/2.0 2x, "+l+e+"/3.0 4x"},u=function(e){var t=e.set,n=e.set_type;return void 0===n&&(n="Channel"),t?(("00000turbo"==t||"turbo"==t)&&(t="Twitch Turbo",n=null),"Emoticon: "+e.code+"\n"+(n?n+": ":"")+t):e.code},d=function(e){{var t=this._twitch_emotes[e];t?t.set:null}return t?"string"==typeof t?t:t.tooltip?t.tooltip:t.tooltip=u(t):"???"},h=function(e,t,n,o){if(n){t&&(o.code=t),this._twitch_emotes[e]=o;for(var i=d.bind(this)(e),a=document.querySelectorAll('img[emote-id="'+e+'"]'),s=0;s5&&i.log("Tokenizing Message Took Too Long - "+(o-t)+"ms",e,!1,!0)}catch(a){try{i.error("LineController tokenizedMessage: "+a)}catch(a){}}return e}.property("model.message","isModeratorOrHigher")}),this.log("Hooking the Ember Line view.");var o=App.__container__.resolve("view:line");o.reopen({didInsertElement:function(){this._super();try{var t=performance.now(),o=this.get("element"),a=this.get("context"),s=a.get("model.from"),r=a.get("parentController.content.id"),l=a.get("model.color"),d=a.get("model.ffz_alternate");l&&i._handle_color(l),void 0===d&&(d=i._last_row[r]=i._last_row.hasOwnProperty(r)?!i._last_row[r]:!1,this.set("context.model.ffz_alternate",d)),o.classList.toggle("ffz-alternate",d),o.setAttribute("data-room",r),o.setAttribute("data-sender",s),o.setAttribute("data-deleted",a.get("model.deleted")),i.render_badge(this),i.settings.capitalize&&i.capitalize(this,s);var m=o.querySelector("span.mentioned");if(m&&(o.classList.add("ffz-mentioned"),i.settings.highlight_notifications&&!document.hasFocus()&&!this.get("context.model.ffz_notified"))){var _=n.get_capitalization(r),p=n.get_capitalization(s),f=_,g=this.get("context.model.message");this.get("context.parentController.content.isGroupRoom")&&(f=this.get("context.parentController.content.tmiRoom.displayName")),g="action"==this.get("context.model.style")?"* "+p+" "+g:p+": "+g,i.show_notification(g,"Twitch Chat Mention in "+f,_,6e4,e.focus.bind(e))}this.set("context.model.ffz_notified",!0);for(var v=o.querySelectorAll("a.deleted-link"),b=0;b-1&&(-1===t.indexOf("/")||t.indexOf("@")5&&i.log("Line Took Too Long - "+D+"ms",o.innerHTML,!1,!0)}catch(B){try{i.error("LineView didInsertElement: "+B)}catch(B){}}}});var a=this.get_user();a&&a.name&&(n.capitalization[a.login]=[a.name,Date.now()])},n.prototype._handle_color=function(e){if(e&&!this._colors[e]){this._colors[e]=!0;var t=parseInt(e.substr(1),16),n=[t>>16,t>>8&255,255&t],i=o.get_luminance(n),a="",s='span[style="color:'+e+'"]',r=!1;if(i>.3){r=!0;for(var l=127,c=n;l--&&(c=o.darken(c),!(o.get_luminance(c)<=.3)););a+=".ffz-chat-colors .ember-chat-container:not(.dark) .chat-line "+s+", .ffz-chat-colors .chat-container:not(.dark) .chat-line "+s+" { color: "+o.rgb_to_css(c)+" !important; }\n"}else a+=".ffz-chat-colors .ember-chat-container:not(.dark) .chat-line "+s+", .ffz-chat-colors .chat-container:not(.dark) .chat-line "+s+" { color: "+e+" !important; }\n";if(.15>i){r=!0;for(var l=127,c=n;l--&&(c=o.brighten(c),!(o.get_luminance(c)>=.15)););a+=".ffz-chat-colors .theatre .chat-container .chat-line "+s+", .ffz-chat-colors .chat-container.dark .chat-line "+s+", .ffz-chat-colors .ember-chat-container.dark .chat-line "+s+" { color: "+o.rgb_to_css(c)+" !important; }\n"}else a+=".ffz-chat-colors .theatre .chat-container .chat-line "+s+", .ffz-chat-colors .chat-container.dark .chat-line "+s+", .ffz-chat-colors .ember-chat-container.dark .chat-line "+s+" { color: "+e+" !important; }\n";r&&(this._fix_color_style.innerHTML+=a)}},n.capitalization={},n._cap_fetching=0,n.get_capitalization=function(t,o){if(e.BetterTTV&&BetterTTV.chat&&BetterTTV.chat.helpers.lookupDisplayName)return BetterTTV.chat.helpers.lookupDisplayName(t);if(!t)return t;if(t=t.toLowerCase(),"jtv"==t||"twitchnotify"==t)return t;var i=n.capitalization[t];return i&&Date.now()-i[1]<36e5?i[0]:(n._cap_fetching<25&&(n._cap_fetching++,n.get().ws_send("get_display_name",t,function(e,i){var a=e?i:t;n.capitalization[t]=[a,Date.now()],n._cap_fetching--,"function"==typeof o&&o(a)})),i?i[0]:t)},n.prototype.capitalize=function(e,t){var o=n.get_capitalization(t,this.capitalize.bind(this,e));if(o&&e){var i=e.$(".from");i&&i.text(o)}},n._regex_cache={},n._get_regex=function(e){return n._regex_cache[e]=n._regex_cache[e]||RegExp("\\b"+i(e)+"\\b","ig")},n._words_to_regex=function(e){var t=n._regex_cache[e];if(!t){for(var o="",s=0;s<banned link>',own:!0}:s)}return i},n.prototype._emoticonize=function(e,t){var n=e.get("parentController.model.id"),o=e.get("model.from"),i=this,a=this.getEmotes(o,n),s=[];return _.each(a,function(e){var n=i.emote_sets[e];n&&_.each(n.emotes,function(e){_.any(t,function(t){return _.isString(t)&&t.match(e.regex)})&&s.push(e)})}),s.length?("string"==typeof t&&(t=[t]),_.each(s,function(e){var n={isEmoticon:!0,cls:e.klass,srcSet:e.url+" 1x",emoticonSrc:e.url+'" data-ffz-emote="'+encodeURIComponent(JSON.stringify([e.id,e.set_id])),altText:e.hidden?"???":e.name};t=_.compact(_.flatten(_.map(t,function(t){if(_.isObject(t))return t;var o=t.split(e.regex),i=[];return o.forEach(function(e,t){i.push(e),t!==o.length-1&&i.push(n)}),i})))}),t):t}},{"../utils":27}],7:[function(t){var n=e.FrankerFaceZ,o=t("../utils"),i={ESC:27,P:80,B:66,T:84},a=[["5m",300],["10m",600],["1hr",3600],["12hr",43200],["24hr",86400]],s='',r='';n.settings_info.enhanced_moderation={type:"boolean",value:!1,visible:function(){return!this.has_bttv},category:"Chat",name:"Enhanced Moderation",help:"Use /p, /t, /u and /b in chat to moderate chat, or use hotkeys with moderation cards."},n.prototype.setup_mod_card=function(){this.log("Hooking the Ember Moderation Card view.");var e=App.__container__.resolve("view:moderation-card"),t=this;e.reopen({didInsertElement:function(){this._super();try{if(t.has_bttv||!t.settings.enhanced_moderation)return;var e=this.get("element"),n=this.get("context");if(e.classList.add("ffz-moderation-card"),n.get("parentController.model.isModeratorOrHigher")){e.classList.add("ffz-is-mod"),e.setAttribute("tabindex",1),e.addEventListener("keyup",function(e){var t=e.keyCode||e.which,o=n.get("model.user.id"),a=n.get("parentController.model");if(t==i.P)a.send("/timeout "+o+" 1");else if(t==i.B)a.send("/ban "+o);else if(t==i.T)a.send("/timeout "+o+" 600");else if(t!=i.ESC)return;n.send("hideModOverlay")});var l=document.createElement("div");l.className="interface clearfix";var c=function(e){var t=n.get("model.user.id"),o=n.get("parentController.model");o.send(-1===e?"/unban "+t:"/timeout "+t+" "+e)},u=function(e,t){var n=document.createElement("button");return n.className="button",n.innerHTML=e,n.title="Timeout User for "+o.number_commas(t)+" Second"+(1!=t?"s":""),600===t?n.title="(T)"+n.title.substr(1):1===t&&(n.title="(P)urge - "+n.title),jQuery(n).tipsy(),n.addEventListener("click",c.bind(this,t)),n};l.appendChild(u("Purge",1));var d=document.createElement("span");d.className="right",l.appendChild(d);for(var h=0;h button");v&&"button"==v.className&&(v.innerHTML=s,v.classList.add("glyph-only"),v.classList.add("message"),v.title="Message User",jQuery(v).tipsy()),this.$().draggable({start:function(){e.focus()}}),e.focus()}catch(b){try{t.error("ModerationCardView didInsertElement: "+b)}catch(b){}}}})},n.chat_commands.purge=n.chat_commands.p=function(e,t){if(!t||!t.length)return"Purge Usage: /p username [more usernames separated by spaces]";if(t.length>10)return"Please only purge up to 10 users at once.";for(var n=0;n10)return"Please only ban up to 10 users at once.";for(var n=0;n10)return"Please only unban up to 10 users at once.";for(var n=0;nn?this._legacy_add_room(e,t,n):void 0)})},n.prototype._legacy_load_room_css=function(e,t,n){var s=e,r=s.match(a);r&&r[1]&&(s=r[1]);var l={id:e,menu_sets:[s],sets:[s],moderator_badge:null,css:null};return n&&(n=n.replace(o,"").trim()),n&&(n=n.replace(i,function(e,t){return l.moderator_badge||"modicon.png"!==t.substr(-11)?e:(l.moderator_badge=t,"")})),l.css=n||null,this._load_room_json(e,t,l)}},{"../constants":3,"../utils":27}],9:[function(){var t=e.FrankerFaceZ;t.prototype.setup_viewers=function(){this.log("Hooking the Ember Viewers controller.");var e=App.__container__.resolve("controller:viewers");this._modify_viewers(e)},t.prototype._modify_viewers=function(e){var n=this;e.reopen({lines:function(){var e=this._super();try{var o=[],i={},a=null,s=App.__container__.lookup("controller:channel"),r=this.get("parentController.model.id"),l=s&&s.get("id");if(l){var c=s.get("display_name");c&&(t.capitalization[l]=[c,Date.now()])}r!=l&&(l=null);for(var u=0;un?this._legacy_load_set(e,t,n):"function"==typeof t&&t(!1))})},n.prototype._legacy_load_css=function(e,t,n){var a={},s={id:e,emotes:a,extra_css:null},r=this;"global"==e?s.title="Global":"globalevent"==e?s.title="Global Event":".donor"==e&&(s.title="Donor"),n=n.replace(o,function(t,n,o,i,s,c,u,d){s=parseInt(s),c=parseInt(c),u=l(u,s);var h="."===i.substr(i.lastIndexOf("/")+1,1),m=++r._last_emote_id,_={id:m,set_id:e,hidden:h,name:o,height:s,width:c,url:i,margins:u,extra_css:d};return a[m]=_,""}).trim(),n&&n.replace(i,function(e,t){s.icon||"modicon.png"!==t.substr(-11)||(s.icon=t)}),this._load_set_json(e,t,s)}},{"./constants":3,"./utils":27}],11:[function(){var t=e.FrankerFaceZ;t.prototype.find_bttv=function(t,n){return this.has_bttv=!1,e.BTTVLOADED?this.setup_bttv(n||0):void(n>=6e4?this.log("BetterTTV was not detected after 60 seconds."):setTimeout(this.find_bttv.bind(this,t,(n||0)+t),t))},t.prototype.setup_bttv=function(e){this.log("BetterTTV was detected after "+e+"ms. Hooking."),this.has_bttv=!0,this.log("WOO"),document.body.classList.remove("ffz-dark"),this._dark_style&&(this._dark_style.parentElement.removeChild(this._dark_style),delete this._dark_style),document.body.classList.remove("ffz-chat-colors"),document.body.classList.remove("ffz-chat-background");var t=BetterTTV.chat.helpers.sendMessage,n=this;BetterTTV.chat.helpers.sendMessage=function(e){var o=e.split(" ",1)[0].toLowerCase();return"/ffz"!==o?t(e):void n.run_ffz_command(e.substr(5),BetterTTV.chat.store.currentRoom)};var o,i=BetterTTV.chat.handlers.onPrivmsg;BetterTTV.chat.handlers.onPrivmsg=function(e,t){o=e;var n=i(e,t);return o=null,n};var a=BetterTTV.chat.templates.privmsg;BetterTTV.chat.templates.privmsg=function(e,t,i,s,r){try{return n.bttv_badges(r),'
'+BetterTTV.chat.templates.timestamp(r.time)+" "+(s?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,t?r.color:!1)+"
"}catch(l){return n.log("Error: ",l),a(e,t,i,s,r)}};var s,r=BetterTTV.chat.templates.message;BetterTTV.chat.templates.message=function(e,t,o,i){try{i=i||!1;var a=encodeURIComponent(t);if("jtv"!==e){s=e;var l=BetterTTV.chat.templates.emoticonize(t,o);s=null;for(var c=0;c'+t+"
"}catch(u){return n.log("Error: ",u),r(e,t,o,i)}};var l=BetterTTV.chat.templates.emoticonize;BetterTTV.chat.templates.emoticonize=function(e,t){var i=l(e,t),a=n.getEmotes(s,o),t=[];return _.each(a,function(e){var o=n.emote_sets[e];o&&_.each(o.emotes,function(e){_.any(i,function(t){return _.isString(t)&&t.match(e.regex)})&&t.push(e)})}),t.length?(_.each(t,function(e){var t=[''+e.name+''],n=i;if(i=[],!n||!n.length)return i;for(var o=0;o=6e4?this.log("Emote Menu for Twitch was not detected after 60 seconds."):setTimeout(this.find_emote_menu.bind(this,t,(n||0)+t),t))},t.prototype.setup_emote_menu=function(e){this.log("Emote Menu for Twitch was detected after "+e+"ms. Registering emote enumerator."),emoteMenu.registerEmoteGetter("FrankerFaceZ",this._emote_menu_enumerator.bind(this))},t.prototype._emote_menu_enumerator=function(){for(var e=this.get_user(),n=e?e.login:null,o=App.__container__.lookup("controller:chat"),i=o?o.get("currentRoom.id"):null,a=this.getEmotes(n,i),s=[],r=0;r=6e4?this.log('Twitch application not detected in "'+location.toString()+'". Aborting.'):setTimeout(this.initialize.bind(this,t,(n||0)+t),t)))},n.prototype.setup_ember=function(t){var o=e.performance&&performance.now?performance.now():Date.now();this.log("Found Twitch application after "+(t||0)+' ms in "'+location+'". Initializing FrankerFaceZ version '+n.version_info),this.users={},this.load_settings(),this.setup_dark(),this.ws_create(),this.setup_emoticons(),this.setup_badges(),this.setup_room(),this.setup_line(),this.setup_chatview(),this.setup_viewers(),this.setup_mod_card(),this.setup_notifications(),this.setup_css(),this.setup_menu(),this.setup_my_emotes(),this.setup_races(),this.find_bttv(10),this.find_emote_menu(10),this.check_ff();var i=e.performance&&performance.now?performance.now():Date.now(),a=i-o;this.log("Initialization complete in "+a+"ms")}},{"./badges":1,"./commands":2,"./debug":4,"./ember/chatview":5,"./ember/line":6,"./ember/moderation-card":7,"./ember/room":8,"./ember/viewers":9,"./emoticons":10,"./ext/betterttv":11,"./ext/emote_menu":12,"./featurefriday":14,"./settings":15,"./shims":16,"./socket":17,"./ui/about_page":18,"./ui/dark":19,"./ui/menu":20,"./ui/menu_button":21,"./ui/my_emotes":22,"./ui/notifications":23,"./ui/races":24,"./ui/styles":25,"./ui/viewer_count":26}],14:[function(t){var n=e.FrankerFaceZ,o=t("./constants");n.prototype.feature_friday=null,n.prototype.check_ff=function(e){e||this.log("Checking for Feature Friday data..."),jQuery.ajax(o.SERVER+"script/event.json",{cache:!1,dataType:"json",context:this}).done(function(e){return this._load_ff(e)}).fail(function(t){return 404==t.status?this._load_ff(null):(e=e||0,e++,10>e?setTimeout(this.check_ff.bind(this,e),250):this._load_ff(null))})},n.ws_commands.reload_ff=function(){this.check_ff()},n.prototype._feature_friday_ui=function(e,t,n){if(this.feature_friday&&this.feature_friday.channel!=e){this._emotes_for_sets(t,n,[this.feature_friday.set],this.feature_friday.title);var o=App.__container__.lookup("controller:channel");if(!o||o.get("id")!=this.feature_friday.channel){var i=this.feature_friday,a=document.createElement("div"),s=document.createElement("a");a.className="chat-menu-content",a.style.textAlign="center";var r=i.display_name+(i.live?" is live now!":"");s.className="button primary",s.classList.toggle("live",i.live),s.classList.toggle("blue",this.has_bttv&&BetterTTV.settings.get("showBlueButtons")),s.href="http://www.twitch.tv/"+i.channel,s.title=r,s.target="_new",s.innerHTML=""+r+"",a.appendChild(s),t.appendChild(a)}}},n.prototype._load_ff=function(e){if(this.feature_friday){this.global_sets.removeObject(this.feature_friday.set);var t=this.emote_sets[this.feature_friday.set];t&&(t.global=!1),this.feature_friday=null,this.update_ui_link()}e&&e.set&&e.channel&&(this.feature_friday={set:e.set,channel:e.channel,live:!1,title:e.title||"Feature Friday",display_name:n.get_capitalization(e.channel,this._update_ff_name.bind(this))},this.global_sets.push(e.set),this.load_set(e.set,this._update_ff_set.bind(this)),this._update_ff_live())},n.prototype._update_ff_live=function(){if(this.feature_friday){var e=this;Twitch.api.get("streams/"+this.feature_friday.channel).done(function(t){e.feature_friday.live=null!=t.stream,e.update_ui_link()}).always(function(){e.feature_friday.timer=setTimeout(e._update_ff_live.bind(e),12e4)})}},n.prototype._update_ff_set=function(e,t){t&&(t.global=!0)},n.prototype._update_ff_name=function(e){this.feature_friday&&(this.feature_friday.display_name=e)}},{"./constants":3}],15:[function(t){var n=e.FrankerFaceZ,o=t("./constants");make_ls=function(e){return"ffz_setting_"+e},toggle_setting=function(e,t){var n=!this.settings.get(t);this.settings.set(t,n),e.classList.toggle("active",n)},n.settings_info={},n.prototype.load_settings=function(){this.log("Loading settings."),this.settings={};for(var t in n.settings_info)if(n.settings_info.hasOwnProperty(t)){var o=n.settings_info[t],i=o.storage_key||make_ls(t),a=o.hasOwnProperty("value")?o.value:void 0;if(localStorage.hasOwnProperty(i))try{a=JSON.parse(localStorage.getItem(i))}catch(s){this.log('Error loading value for "'+t+'": '+s)}this.settings[t]=a}this.settings.get=this._setting_get.bind(this),this.settings.set=this._setting_set.bind(this),this.settings.del=this._setting_del.bind(this),e.addEventListener("storage",this._setting_update.bind(this),!1)},n.menu_pages.settings={render:function(e,t){var o={},i=[];for(var a in n.settings_info)if(n.settings_info.hasOwnProperty(a)){var s=n.settings_info[a],r=s.category||"Miscellaneous",l=o[r];if(void 0!==s.visible&&null!==s.visible){var c=s.visible;if("function"==typeof s.visible&&(c=s.visible.bind(this)()),!c)continue}l||(i.push(r),l=o[r]=[]),l.push([a,s])}i.sort(function(e,t){var e=e.toLowerCase(),t=t.toLowerCase();return"debugging"===e&&(e="zzz"+e),"debugging"===t&&(t="zzz"+t),t>e?-1:e>t?1:0});for(var u=0;un?-1:n>o?1:a>i?-1:i>a?1:0});for(var p=0;p",b.className="switch-label",b.innerHTML=s.name,f.appendChild(v),f.appendChild(b),v.addEventListener("click",toggle_setting.bind(this,v,a))}else{f.classList.add("option");var y=document.createElement("a");y.innerHTML=s.name,y.href="#",f.appendChild(y),y.addEventListener("click",s.method.bind(this))}if(s.help){var w=document.createElement("span");w.className="help",w.innerHTML=s.help,f.appendChild(w)}m.appendChild(f)}t.appendChild(m)}},name:"Settings",icon:o.GEAR,sort_order:99999},n.prototype._setting_update=function(t){if(t||(t=e.event),this.log("Storage Event",t),t.key&&"ffz_setting_"===t.key.substr(0,12)){var o=t.key,i=o.substr(12),a=void 0,s=n.settings_info[i];if(!s){for(i in n.settings_info)if(n.settings_info.hasOwnProperty(i)&&(s=n.settings_info[i],s.storage_key==o))break;if(s.storage_key!=o)return}this.log("Updated Setting: "+i);try{a=JSON.parse(t.newValue)}catch(r){this.log('Error loading new value for "'+i+'": '+r),a=s.value||void 0}if(this.settings[i]=a,s.on_update)try{s.on_update.bind(this)(a,!1)}catch(r){this.log('Error running updater for setting "'+i+'": '+r)}}},n.prototype._setting_get=function(e){return this.settings[e]},n.prototype._setting_set=function(e,t){var o=n.settings_info[e],i=o.storage_key||make_ls(e),a=JSON.stringify(t);if(this.settings[e]=t,localStorage.setItem(i,a),this.log('Changed Setting "'+e+'" to: '+a),o.on_update)try{o.on_update.bind(this)(t,!0)}catch(s){this.log('Error running updater for setting "'+e+'": '+s)}},n.prototype._setting_del=function(e){var t=n.settings_info[e],o=t.storage_key||make_ls(e),i=void 0;if(localStorage.hasOwnProperty(o)&&localStorage.removeItem(o),delete this.settings[e],t&&(i=this.settings[e]=t.hasOwnProperty("value")?t.value:void 0),t.on_update)try{t.on_update.bind(this)(i,!0)}catch(a){this.log('Error running updater for setting "'+e+'": '+a)}}},{"./constants":3}],16:[function(){Array.prototype.equals=function(e){if(!e)return!1;if(this.length!=e.length)return!1;for(var t=0,n=this.length;n>t;t++)if(this[t]instanceof Array&&e[t]instanceof Array){if(!this[t].equals(e[t]))return!1}else if(this[t]!=e[t])return!1;return!0}},{}],17:[function(){var t=e.FrankerFaceZ;t.prototype._ws_open=!1,t.prototype._ws_delay=0,t.ws_commands={},t.ws_on_close=[],t.prototype.ws_create=function(){var e,n=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(o){return this._ws_exists=!1,this.log("Error Creating WebSocket: "+o)}this._ws_exists=!0,e.onopen=function(){n._ws_open=!0,n._ws_delay=0,n.log("Socket connected.");var e=n.get_user();e&&n.ws_send("setuser",e.login);for(var t in n.rooms)n.rooms.hasOwnProperty(t)&&n.ws_send("sub",t);var o=n._ws_pending;n._ws_pending=[];for(var i=0;i0){i=!0;break}}var l=document.createElement("div"),c="";c+="

FrankerFaceZ

",c+='
new ways to woof
',l.className="chat-menu-content center",l.innerHTML=c,t.appendChild(l);var u=document.createElement("div"),d=document.createElement("a"),h="To use custom emoticons in "+(i?"this channel":"tons of channels")+", get FrankerFaceZ from http://www.frankerfacez.com";d.className="button primary",d.innerHTML="Advertise in Chat",d.addEventListener("click",this._add_emote.bind(this,e,h)),u.appendChild(d);var m=document.createElement("a");m.className="button ffz-donate",m.href="http://www.frankerfacez.com/donate.html",m.target="_new",m.innerHTML="Donate",u.appendChild(m),u.className="chat-menu-content center",t.appendChild(u);var _=document.createElement("div");c='',c+='',c+='',c+='',c+='',_.className="chat-menu-content center",_.innerHTML=c;var p=!1;_.querySelector("#ffz-debug-logs").addEventListener("click",function(){p||(p=!0,a._pastebin(a._log_data.join("\n"),function(e){p=!1,e?prompt("Your FrankerFaceZ logs have been uploaded to the URL:",e):alert("There was an error uploading the FrankerFaceZ logs.")}))}),t.appendChild(_)}}},{"../constants":3}],19:[function(t){var n=e.FrankerFaceZ,o=t("../constants");n.settings_info.twitch_chat_dark={type:"boolean",value:!1,visible:!1},n.settings_info.dark_twitch={type:"boolean",value:!1,visible:function(){return!this.has_bttv},name:"Dark Twitch Beta",help:"Apply a dark background to channels and other related pages for easier viewing.",on_update:function(e){if(!this.has_bttv){document.body.classList.toggle("ffz-dark",e);var t=App.__container__.lookup("controller:settings").get("model");e?(this._load_dark_css(),this.settings.set("twitch_chat_dark",t.get("darkMode")),t.set("darkMode",!0)):t.set("darkMode",this.settings.twitch_chat_dark)}}},n.prototype.setup_dark=function(){this.has_bttv||(document.body.classList.toggle("ffz-dark",this.settings.dark_twitch),this.settings.dark_twitch&&App.__container__.lookup("controller:settings").set("model.darkMode",!0),this.settings.dark_twitch&&this._load_dark_css())},n.prototype._load_dark_css=function(){if(!this._dark_style){this.log("Injecting FrankerFaceZ Dark Twitch CSS.");var e=this._dark_style=document.createElement("link");e.id="ffz-dark-css",e.setAttribute("rel","stylesheet"),e.setAttribute("href",o.SERVER+"script/dark.css?_="+Date.now()),document.head.appendChild(e)}}},{"../constants":3}],20:[function(t){var n=e.FrankerFaceZ,o=t("../constants");n.prototype.setup_menu=function(){this.log("Installing mouse-up event to auto-close menus.");var e=this;jQuery(document).mouseup(function(t){var n,o=e._popup;o&&(o=jQuery(o),n=o.parent(),n.is(t.target)||0!==n.has(t.target).length||(o.remove(),delete e._popup,e._popup_kill&&e._popup_kill(),delete e._popup_kill))})},n.menu_pages={},n.prototype.build_ui_popup=function(e){var t=this._popup;if(t)return t.parentElement.removeChild(t),delete this._popup,this._popup_kill&&this._popup_kill(),void delete this._popup_kill;var i=document.createElement("div"),a=document.createElement("div"),s=document.createElement("ul"),r=this.has_bttv?BetterTTV.settings.get("darkenedMode"):!1;i.className="emoticon-selector chat-menu ffz-ui-popup",a.className="emoticon-selector-box dropmenu",i.appendChild(a),i.classList.toggle("dark",r);var l=document.createElement("div");l.className="ffz-ui-menu-page",a.appendChild(l),s.className="menu clearfix",a.appendChild(s);var c=document.createElement("li");c.className="title",c.innerHTML=""+(o.DEBUG?"[DEV] ":"")+"FrankerFaceZ",s.appendChild(c);var u=[];for(var d in n.menu_pages)if(n.menu_pages.hasOwnProperty(d)){var h=n.menu_pages[d];h&&(!h.hasOwnProperty("visible")||h.visible&&("function"!=typeof h.visible||h.visible.bind(this)()))&&u.push([h.sort_order||0,d,h])}u.sort(function(e,t){if(e[0]t[0])return-1;var n=e[1].toLowerCase(),o=t[1].toLowerCase();return o>n?1:n>o?-1:0});for(var m=0;m0){i=!0;break}}e.classList.toggle("no-emotes",!i),e.classList.toggle("live",r),e.classList.toggle("dark",a),e.classList.toggle("blue",s)}}},{"../constants":3}],22:[function(t){var n=e.FrankerFaceZ,o=t("../constants"),i="http://static-cdn.jtvnw.net/emoticons/v1/",a={"00000turbo":!0},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"},r=function(e){var t=App.__container__.lookup("controller:chat"),n=t.get("currentRoom.id"),o=e.rooms[n],i=o?o.room.tmiSession:null,a=i&&i._emotesParser&&i._emotesParser.emoticonSetIds||"0",s=e.get_user(),r=s&&e.users[s.login]&&e.users[s.login].sets||[]; -return a=a.split(",").removeObject("0"),e.settings.global_emotes_in_menu&&(a.push("0"),r=_.union(r,e.global_sets)),[a,r]};n.settings_info.global_emotes_in_menu={type:"boolean",value:!1,name:"Display Global Emotes in My Emotes",help:"Display the global Twitch emotes in the My Emoticons menu."},n.prototype.setup_my_emotes=function(){if(this._twitch_emote_sets={},this._twitch_set_to_channel={},localStorage.ffzTwitchSets)try{this._twitch_set_to_channel=JSON.parse(localStorage.ffzTwitchSets)}catch(e){}this._twitch_set_to_channel[0]="twitch_global"},n.menu_pages.my_emotes={name:"My Emoticons",icon:o.EMOTE,visible:function(){var e=r(this);return e[0].length>0||e[1].length>0},render:function(e,t){var o=r(this),l=this;new RSVP.Promise(function(e){for(var t=[],i=0;ie?-1:e>t?1:0}),o.emotes=i,o.source="Twitch"}e()}).fail(function(){e()}):e()}),new RSVP.Promise(function(e){if(!t.length)return e();var o=[],i=t,s=function(e,t){var i=l._twitch_emote_sets[e]=l._twitch_emote_sets[e]||{};if(t&&!a[t]){if("twitch_global"==t)return n.capitalization["global emoticons"]=["Global Emoticons",Date.now()],i.channel="Global Emoticons",void(i.badge="//cdn.frankerfacez.com/channel/global/twitch_logo.png");if("turbo"==t)return i.channel="Twitch Turbo",void(i.badge="//cdn.frankerfacez.com/script/turbo_badge.png");o.push(new RSVP.Promise(function(e,t,n){Twitch.api.get("chat/"+t+"/badges",null,{version:3}).done(function(t){t.subscriber&&t.subscriber.image&&(e.badge=t.subscriber.image),n()}).fail(n)}.bind(this,i,t)));var s=t.toLowerCase(),r=n.capitalization[s];return r&&Date.now()-r[1]<36e5?void(i.channel=r[0]):void o.push(new RSVP.Promise(function(e,t,o,i){l.ws_send("get_display_name",t,function(a,s){var r=a?s:o;n.capitalization[t]=[r,Date.now()],e.channel=r,i()})||(e.channel=o,i()),setTimeout(function(e,t,n){e.channel||(e.channel=t),n()}.bind(this,e,o,i),2e3)}.bind(this,i,s,t)))}},r=function(){o.length?RSVP.all(o).then(e,e):e()};t=[];for(var c=0;c0?(l.ws_send("twitch_sets",t,function(e,n){if(t=[],e){for(var o in n)n.hasOwnProperty(o)&&(l._twitch_set_to_channel[o]=n[o],s(o,n[o]));localStorage.ffzTwitchSets=JSON.stringify(l._twitch_set_to_channel)}r()}),setTimeout(function(){t.length&&r()},2e3)):r()})]).then(function(){for(var t={},n=0;n0&&c.push([2,d.id,d])}c.sort(function(e,t){if(e[0]t[0])return 1;var n=e[1].toLowerCase(),o=t[1].toLowerCase();return("twitch turbo"===n||"global emoticons"===n)&&(n="zzz"+n),("twitch turbo"===o||"global emoticons"===o)&&(o="zzz"+o),o>n?-1:n>o?1:0});for(var h=0;h'+b+""+_,m&&(g.style.backgroundImage='url("'+m+'")'),v.className="emoticon-grid",v.appendChild(g);for(var k=0;kSpeedRunsLive races under channels.',on_update:function(){this.rebuild_race_ui()}},n.ws_on_close.push(function(){var t=e.App&&App.__container__.lookup("controller:channel"),n=t&&t.get("id"),o=!1;if(t){for(var i in this.srl_races)delete this.srl_races[i],i==n&&(o=!0);o&&this.rebuild_race_ui()}}),n.ws_commands.srl_race=function(e){for(var t=App.__container__.lookup("controller:channel"),n=t.get("id"),o=!1,i=0;i=300?"right":"left")+" share dropmenu",this._popup_kill=this._race_kill.bind(this),this._popup=e;var l="http://kadgar.net/live",c=!1;for(var u in s.entrants){var d=s.entrants[u].state;s.entrants.hasOwnProperty(u)&&s.entrants[u].channel&&("racing"==d||"entered"==d)&&(l+="/"+s.entrants[u].channel,c=!0)}var h=document.querySelector(".app-main.theatre")?document.body.clientHeight-300:t.parentElement.offsetTop-175,m=App.__container__.lookup("controller:channel"),_=m?m.get("display_name"):n.get_capitalization(a),p=encodeURIComponent("I'm watching "+_+" race "+s.goal+" in "+s.game+" on SpeedRunsLive!");r='
',r+='
Developers
Dan Salvato  
Stendec  
Version '+n.version_info+'Logs
',r+="
#Entrant Time
",r+='
',r+='',r+='

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

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

'+F+"

Goal: "+E}c?_?d.innerHTML="Done":(d.innerHTML=o.time_to_string(c),this._race_timer=setTimeout(this._update_race.bind(this),1e3)):d.innerHTML="Entry Open"}}}},{"../utils":27}],25:[function(t){var n=e.FrankerFaceZ,o=t("../constants");n.prototype.setup_css=function(){this.log("Injecting main FrankerFaceZ CSS.");var e=this._main_style=document.createElement("link");e.id="ffz-ui-css",e.setAttribute("rel","stylesheet"),e.setAttribute("href",o.SERVER+"script/style.css?_="+Date.now()),document.head.appendChild(e),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":3}],26:[function(t){var n=e.FrankerFaceZ,o=t("../constants"),i=t("../utils");n.ws_commands.viewers=function(e){var t=e[0],n=e[1],a=App.__container__.lookup("controller:channel"),s=a&&a.get&&a.get("id");if(s===t){var r=document.querySelector(".channel-stats .ffz.stat"),l=o.ZREKNARF+" "+i.number_commas(n);if(r)r.innerHTML=l;else{var c=document.querySelector(".channel-stats");if(!c)return;r=document.createElement("span"),r.className="ffz stat",r.title="Viewers with FrankerFaceZ",r.innerHTML=l,c.appendChild(r),jQuery(r).tipsy()}}}},{"../constants":3,"../utils":27}],27:[function(t,n){var o=(e.FrankerFaceZ,t("./constants"),{}),i=document.createElement("span"),a=function(e){return 1==e?"1st":2==e?"2nd":3==e?"3rd":null==e?"---":e+"th"},s=function(e,t){t=0===t?0:t||1,t=Math.round(255*-(t/100));var n=Math.max(0,Math.min(255,e[0]-t)),o=Math.max(0,Math.min(255,e[1]-t)),i=Math.max(0,Math.min(255,e[2]-t));return[n,o,i]},r=function(e){return"rgb("+e[0]+", "+e[1]+", "+e[2]+")"},l=function(e,t){return t=0===t?0:t||1,s(e,-t)},c=function(e){e=[e[0]/255,e[1]/255,e[2]/255];for(var t=0;ts;(l||n)&&(l&&(o=o.substr(0,s)+o.substr(r+a.length)),n&&(o+=i+n+a),e.innerHTML=o)},get_luminance:c,brighten:s,darken:l,rgb_to_css:r,parse_date:d,number_commas:function(e){var t=e.toString().split(".");return t[0]=t[0].replace(/\B(?=(\d{3})+(?!\d))/g,","),t.join(".")},place_string:a,placement:function(e){return"forfeit"==e.state?"Forfeit":"dq"==e.state?"DQed":e.place?a(e.place):""},sanitize:function(e){var t=o[e];return t||(i.textContent=e,t=o[e]=i.innerHTML,i.innerHTML=""),t},time_to_string:function(e){var t=e%60,n=Math.floor(e/60),o=Math.floor(n/60);return n%=60,(10>o?"0":"")+o+":"+(10>n?"0":"")+n+":"+(10>t?"0":"")+t}}},{"./constants":3}]},{},[13]),e.ffz=new FrankerFaceZ}(window); \ No newline at end of file +!function(e){!function t(e,n,a){function o(s,r){if(!n[s]){if(!e[s]){var l="function"==typeof require&&require;if(!r&&l)return l(s,!0);if(i)return i(s,!0);throw new Error("Cannot find module '"+s+"'")}var c=n[s]={exports:{}};e[s][0].call(c.exports,function(t){var n=e[s][1][t];return o(n?n:t)},c,c.exports,t,e,n,a)}return n[s].exports}for(var i="function"==typeof require&&require,s=0;se&&this._legacy_load_bots(e))})},n.prototype._legacy_load_donors=function(e){jQuery.ajax(a.SERVER+"script/donors.txt",{cache:!1,context:this}).done(function(e){this._legacy_parse_badges(e,1,1)}).fail(function(t){return 404!=t.status?(e=(e||0)+1,10>e?this._legacy_load_donors(e):void 0):void 0})},n.prototype._legacy_parse_badges=function(e,t,n){var a=this.badges[n].title,i=0;if(ds=1==n?".donor":"",null!=e)for(var s=e.trim().split(/\W+/),r=0;r50)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 a=t.length;t.length;){var o=t.shift();e.room.tmiRoom.sendMessage("/unmod "+o)}return"Sent unmod command for "+a+" users."},t.ffz_commands.massunmod.help="Usage: /ffz massunmod \nBroadcaster only. Unmod all the users in the provided list.",t.ffz_commands.massmod=function(e,t){if(t=t.join(" ").trim(),!t.length)return"You must provide a list of users to mod.";t=t.split(/\W*,\W*/);var n=this.get_user();if(!n||!n.login==e.id)return"You must be the broadcaster to use massmod.";if(t.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 a=t.length;t.length;){var o=t.shift();e.room.tmiRoom.sendMessage("/mod "+o)}return"Sent mod command for "+a+" users."},t.ffz_commands.massmod.help="Usage: /ffz massmod \nBroadcaster only. Mod all the users in the provided list."},{}],3:[function(e,t){var n='',a="true"==localStorage.ffzDebugMode&&document.body.classList.contains("ffz-dev");t.exports={DEBUG:a,SERVER:a?"//localhost:8000/":"//cdn.frankerfacez.com/",SVGPATH:n,ZREKNARF:''+n+"",CHAT_BUTTON:''+n+"",CLOCK:'',GEAR:'',HEART:'',EMOTE:''}},{}],4:[function(){var t=e.FrankerFaceZ;t.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()}},t.ffz_commands.developer_mode=function(e,t){var n,t=t&&t.length?t[0].toLowerCase():null;return"y"==t||"yes"==t||"true"==t||"on"==t?n=!0:("n"==t||"no"==t||"false"==t||"off"==t)&&(n=!1),void 0===n?"Developer Mode is currently "+(this.settings.developer_mode?"enabled.":"disabled."):(this.settings.set("developer_mode",n),"Developer Mode is now "+(n?"enabled":"disabled")+". Please refresh your browser.")},t.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."},{}],5:[function(t){var n=e.FrankerFaceZ,a=t("../utils"),o=t("../constants");n.prototype.setup_channel=function(){this.channels={},this.log("Creating channel style element.");var e=this._channel_style=document.createElement("style");e.id="ffz-channel-css",document.head.appendChild(e),this.log("Hooking the Ember Channel view.");var t=App.__container__.lookup("controller:channel"),n=this;t&&(t.reopen({ffzUpdateUptime:function(){n.update_uptime()}.observes("isLive","content.id").on("init")}),this.update_uptime())},n.settings_info.stream_uptime={type:"boolean",value:!1,category:"Channel Metadata",name:"Stream Uptime",help:"Display the stream uptime under a channel by the viewer count.",on_update:function(){this.update_uptime()}},n.prototype.update_uptime=function(e){this._uptime_update&&(clearTimeout(this._uptime_update),delete this._uptime_update);var t=App.__container__.lookup("controller:channel");if(e||!this.settings.stream_uptime||!t||!t.get("isLiveAccordingToKraken")){var n=document.querySelector("#ffz-uptime-display");return void(n&&n.parentElement.removeChild(n))}this._update_uptime=setTimeout(this.update_uptime.bind(this),1e3);var i=t.get("content.stream.created_at");if(i&&(i=a.parse_date(i))){var s=Math.floor((Date.now()-i.getTime())/1e3);if(!(0>s)){var n=document.querySelector("#ffz-uptime-display span");if(!n){var r=document.querySelector("#channel .stats-and-actions .channel-stats");if(!r)return;var l=document.createElement("span");l.className="ffz stat",l.id="ffz-uptime-display",l.title="Stream Uptime (since "+i.toLocaleString()+")",l.innerHTML=o.CLOCK+" ",n=document.createElement("span"),l.appendChild(n);var c=r.querySelector(".live-count");if(c)r.insertBefore(l,c.nextSibling);else try{c=r.querySelector("script:nth-child(0n+2)"),r.insertBefore(l,c.nextSibling)}catch(u){r.insertBefore(l,r.childNodes[0])}jQuery(l).tipsy({html:!0})}n.innerHTML=a.time_to_string(s)}}}},{"../constants":3,"../utils":28}],6:[function(){var t=e.FrankerFaceZ;t.prototype.setup_chatview=function(){this.log("Hooking the Ember Chat view.");var e=App.__container__.resolve("view:chat");this._modify_cview(e);try{e.create().destroy()}catch(t){}for(var n in Ember.View.views)if(Ember.View.views.hasOwnProperty(n)){var a=Ember.View.views[n];if(a instanceof e){this.log("Adding UI link manually to Chat view.",a);try{a.$(".textarea-contain").append(this.build_ui_link(a))}catch(t){this.error("setup: build_ui_link: "+t)}}}},t.prototype._modify_cview=function(e){var t=this;e.reopen({didInsertElement:function(){this._super();try{this.$()&&this.$(".textarea-contain").append(t.build_ui_link(this))}catch(e){t.error("didInsertElement: build_ui_link: "+e)}},willClearRender:function(){this._super();try{this.$(".ffz-ui-toggle").remove()}catch(e){t.error("willClearRender: remove ui link: "+e)}},ffzUpdateLink:Ember.observer("controller.currentRoom",function(){try{t.update_ui_link()}catch(e){t.error("ffzUpdateLink: update_ui_link: "+e)}})})}},{}],7:[function(t){var n=e.FrankerFaceZ,a=t("../utils"),o=function(e){return e.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")},i="[\\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]",s=new RegExp(i+"*,"+i+"*"),r=function(e){return(e+"").replace(/&/g,"&").replace(/'/g,"'").replace(/"/g,""").replace(//g,">")},l="http://static-cdn.jtvnw.net/emoticons/v1/",c=function(e){return l+e+"/1.0 1x, "+l+e+"/2.0 2x, "+l+e+"/3.0 4x"},u=function(e){var t=e.set,n=e.set_type;return void 0===n&&(n="Channel"),t?(("00000turbo"==t||"turbo"==t)&&(t="Twitch Turbo",n=null),"Emoticon: "+e.code+"\n"+(n?n+": ":"")+t):e.code},d=function(e){{var t=this._twitch_emotes[e];t?t.set:null}return t?"string"==typeof t?t:t.tooltip?t.tooltip:t.tooltip=u(t):"???"},h=function(e,t,n,a){if(n){t&&(a.code=t),this._twitch_emotes[e]=a;for(var o=d.bind(this)(e),i=document.querySelectorAll('img[emote-id="'+e+'"]'),s=0;sYouTube: "+a.sanitize(n.title)+"
",t+="Channel: "+a.sanitize(n.channel)+" | "+a.time_to_string(n.duration)+"
",t+=a.number_commas(n.views||0)+" Views | 👍 "+a.number_commas(n.likes||0)+" 👎 "+a.number_commas(n.dislikes||0);else if("strawpoll"==n.type){t="Strawpoll: "+a.sanitize(n.title)+"
";for(var o in n.items){{var i=n.items[o];Math.floor(i/n.total*100)}t+='"}t+="
'+a.sanitize(o)+''+a.number_commas(i)+"

Total: "+a.number_commas(n.total);var s=a.parse_date(n.fetched);if(s){var r=Math.floor((s.getTime()-Date.now())/1e3);r>60&&(t+="
Data was cached "+a.time_to_string(r)+" ago.")}}else if("twitch"==n.type){t="Twitch: "+a.sanitize(n.display_name)+"
";var l=a.parse_date(n.since);l&&(t+="Member Since: "+a.date_string(l)+"
"),t+="Views: "+a.number_commas(n.views)+" | Followers: "+a.number_commas(n.followers)+""}else if("twitter"==n.type)t="Tweet By: "+a.sanitize(n.user)+"
",t+=a.sanitize(n.tweet);else if("reputation"==n.type){if(t=''+a.sanitize(n.full.toLowerCase())+"",n.trust<50||n.safety<50||n.tags&&n.tags.length>0){t+="
";var c=!1;(n.trust<50||n.safety<50)&&(n.unsafe=!0,t+="Potentially Unsafe Link
",t+="Trust: "+n.trust+"% | Child Safety: "+n.safety+"%",c=!0),n.tags&&n.tags.length>0&&(t+=(c?"
":"")+"Tags: "+n.tags.join(", ")),t+="
Data Source: WOT"}}else n.full&&(t=''+a.sanitize(n.full.toLowerCase())+"");return t||(t=''+a.sanitize(e.toLowerCase())+""),n.tooltip=t,t},m=function(e,t,n){if(t){this._link_data[e]=n,n.unsafe=!1;var a,o=p.bind(this)(e),i="/"==e.charAt(e.length-1)?e.substr(0,e.length-1):null;if(a=document.querySelectorAll(i?'span.message a[href="'+e+'"], span.message a[href="'+i+'"], span.message a[data-url="'+e+'"], span.message a[data-url="'+i+'"]':'span.message a[href="'+e+'"], span.message a[data-url="'+e+'"]'),this.settings.link_info)for(var s=0;sBeta",help:"Check links against known bad websites, unshorten URLs, and show YouTube info."},n.settings_info.chat_rows={type:"boolean",value:!1,category:"Chat",no_bttv:!0,name:"Chat Line Backgrounds",help:"Display alternating background colors for lines in chat.",on_update:function(e){this.has_bttv||document.body.classList.toggle("ffz-chat-background",e)}},n.prototype.setup_line=function(){document.body.classList.toggle("ffz-chat-colors",!this.has_bttv&&this.settings.fix_color),document.body.classList.toggle("ffz-chat-background",!this.has_bttv&&this.settings.chat_rows),this._colors={},this._last_row={};var t=this._fix_color_style=document.createElement("style");t.id="ffz-style-username-colors",t.type="text/css",document.head.appendChild(t),this._twitch_emotes={},this._link_data={},this.log("Hooking the Ember Line controller.");var a=App.__container__.resolve("controller:line"),o=this;a.reopen({tokenizedMessage:function(){var e=this._super();try{var t=performance.now();e=o._remove_banned(e),e=o._emoticonize(this,e);var a=o.get_user();a&&this.get("model.from")==a.login||(e=o._mentionize(this,e));var i=this.get("model.tags.display-name");i&&i.length&&(n.capitalization[this.get("model.from")]=[i.trim(),Date.now()]);var s=performance.now();s-t>5&&o.log("Tokenizing Message Took Too Long - "+(s-t)+"ms",e,!1,!0)}catch(r){try{o.error("LineController tokenizedMessage: "+r)}catch(r){}}return e}.property("model.message","isModeratorOrHigher")}),this.log("Hooking the Ember Line view.");var a=App.__container__.resolve("view:line");a.reopen({didInsertElement:function(){this._super();try{var t=performance.now(),a=this.get("element"),i=this.get("context"),s=i.get("model.from"),r=i.get("parentController.content.id"),l=i.get("model.color"),d=i.get("model.ffz_alternate");l&&o._handle_color(l),void 0===d&&(d=o._last_row[r]=o._last_row.hasOwnProperty(r)?!o._last_row[r]:!1,this.set("context.model.ffz_alternate",d)),a.classList.toggle("ffz-alternate",d),a.setAttribute("data-room",r),a.setAttribute("data-sender",s),a.setAttribute("data-deleted",i.get("model.deleted")),o.render_badge(this);var p=a.querySelector("span.mentioned");if(p&&(a.classList.add("ffz-mentioned"),o.settings.highlight_notifications&&!document.hasFocus()&&!this.get("context.model.ffz_notified"))){var _=n.get_capitalization(r),f=n.get_capitalization(s),g=_,v=this.get("context.model.message");this.get("context.parentController.content.isGroupRoom")&&(g=this.get("context.parentController.content.tmiRoom.displayName")),v="action"==this.get("context.model.style")?"* "+f+" "+v:f+": "+v,o.show_notification(v,"Twitch Chat Mention in "+g,_,6e4,e.focus.bind(e))}this.set("context.model.ffz_notified",!0);for(var b=a.querySelectorAll("a.deleted-link"),y=0;y-1&&(-1===t.indexOf("/")||t.indexOf("@")5&&o.log("Line Took Too Long - "+P+"ms",a.innerHTML,!1,!0)}catch(H){try{o.error("LineView didInsertElement: "+H)}catch(H){}}}});var i=this.get_user();i&&i.name&&(n.capitalization[i.login]=[i.name,Date.now()])},n.prototype._handle_color=function(e){if(e&&!this._colors[e]){this._colors[e]=!0;var t=parseInt(e.substr(1),16),n=[t>>16,t>>8&255,255&t],o=a.get_luminance(n),i="",s='span[style="color:'+e+'"]',r=!1;if(o>.3){r=!0;for(var l=127,c=n;l--&&(c=a.darken(c),!(a.get_luminance(c)<=.3)););i+=".ffz-chat-colors .ember-chat-container:not(.dark) .chat-line "+s+", .ffz-chat-colors .chat-container:not(.dark) .chat-line "+s+" { color: "+a.rgb_to_css(c)+" !important; }\n"}else i+=".ffz-chat-colors .ember-chat-container:not(.dark) .chat-line "+s+", .ffz-chat-colors .chat-container:not(.dark) .chat-line "+s+" { color: "+e+" !important; }\n";if(.15>o){r=!0;for(var l=127,c=n;l--&&(c=a.brighten(c),!(a.get_luminance(c)>=.15)););i+=".ffz-chat-colors .theatre .chat-container .chat-line "+s+", .ffz-chat-colors .chat-container.dark .chat-line "+s+", .ffz-chat-colors .ember-chat-container.dark .chat-line "+s+" { color: "+a.rgb_to_css(c)+" !important; }\n"}else i+=".ffz-chat-colors .theatre .chat-container .chat-line "+s+", .ffz-chat-colors .chat-container.dark .chat-line "+s+", .ffz-chat-colors .ember-chat-container.dark .chat-line "+s+" { color: "+e+" !important; }\n";r&&(this._fix_color_style.innerHTML+=i)}},n.capitalization={},n._cap_fetching=0,n.get_capitalization=function(t,a){if(e.BetterTTV&&BetterTTV.chat&&BetterTTV.chat.helpers.lookupDisplayName)return BetterTTV.chat.helpers.lookupDisplayName(t);if(!t)return t;if(t=t.toLowerCase(),"jtv"==t||"twitchnotify"==t)return t;var o=n.capitalization[t];return o&&Date.now()-o[1]<36e5?o[0]:(n._cap_fetching<25&&(n._cap_fetching++,n.get().ws_send("get_display_name",t,function(e,o){var i=e?o:t;n.capitalization[t]=[i,Date.now()],n._cap_fetching--,"function"==typeof a&&a(i)})),o?o[0]:t)},n._regex_cache={},n._get_regex=function(e){return n._regex_cache[e]=n._regex_cache[e]||RegExp("\\b"+o(e)+"\\b","ig")},n._words_to_regex=function(e){var t=n._regex_cache[e];if(!t){for(var a="",s=0;s<banned link>',own:!0}:s)}return o},n.prototype._emoticonize=function(e,t){var n=e.get("parentController.model.id"),a=e.get("model.from"),o=this,i=this.getEmotes(a,n),s=[];return _.each(i,function(e){var n=o.emote_sets[e];n&&_.each(n.emotes,function(e){_.any(t,function(t){return _.isString(t)&&t.match(e.regex)})&&s.push(e)})}),s.length?("string"==typeof t&&(t=[t]),_.each(s,function(e){var n={isEmoticon:!0,cls:e.klass,srcSet:e.url+" 1x",emoticonSrc:e.url+'" data-ffz-emote="'+encodeURIComponent(JSON.stringify([e.id,e.set_id])),altText:e.hidden?"???":e.name};t=_.compact(_.flatten(_.map(t,function(t){if(_.isObject(t))return t;var a=t.split(e.regex),o=[];return a.forEach(function(e,t){o.push(e),t!==a.length-1&&o.push(n)}),o})))}),t):t}},{"../utils":28}],8:[function(t){var n=e.FrankerFaceZ,a=t("../utils"),o={ESC:27,P:80,B:66,T:84,U:85},i=[["5m",300],["10m",600],["1hr",3600],["12hr",43200],["24hr",86400]],s='',r='';n.settings_info.enhanced_moderation={type:"boolean",value:!1,no_bttv:!0,category:"Chat",name:"Enhanced Moderation",help:"Use /p, /t, /u and /b in chat to moderate chat, or use hotkeys with moderation cards."},n.prototype.setup_mod_card=function(){this.log("Hooking the Ember Moderation Card view.");var t=App.__container__.resolve("component:moderation-card"),n=this;t.reopen({didInsertElement:function(){this._super(),e._card=this;try{if(n.has_bttv||!n.settings.enhanced_moderation)return;var t=this.get("element"),l=this.get("controller");if(t.classList.add("ffz-moderation-card"),l.get("cardInfo.isModeratorOrHigher")){t.classList.add("ffz-is-mod"),t.setAttribute("tabindex",1),t.addEventListener("keyup",function(e){var t=e.keyCode||e.which,n=l.get("cardInfo.user.id"),a=App.__container__.lookup("controller:chat").get("currentRoom");if(t==o.P)a.send("/timeout "+n+" 1");else if(t==o.B)a.send("/ban "+n);else if(t==o.T)a.send("/timeout "+n+" 600");else if(t==o.U)a.send("/unban "+n);else if(t!=o.ESC)return;l.send("hideModOverlay")});var c=document.createElement("div");c.className="interface clearfix"; +var u=function(e){var t=l.get("cardInfo.user.id"),n=App.__container__.lookup("controller:chat").get("currentRoom");n.send(-1===e?"/unban "+t:"/timeout "+t+" "+e)},d=function(e,t){var n=document.createElement("button");return n.className="button",n.innerHTML=e,n.title="Timeout User for "+a.number_commas(t)+" Second"+(1!=t?"s":""),600===t?n.title="(T)"+n.title.substr(1):1===t&&(n.title="(P)urge - "+n.title),jQuery(n).tipsy(),n.addEventListener("click",u.bind(this,t)),n};c.appendChild(d("Purge",1));var h=document.createElement("span");h.className="right",c.appendChild(h);for(var p=0;p button");b&&b.classList.contains("message-button")&&(b.innerHTML=s,b.classList.add("glyph-only"),b.classList.add("message"),b.title="Message User",jQuery(b).tipsy()),this.$().draggable({start:function(){t.focus()}}),t.focus()}catch(y){try{n.error("ModerationCardView didInsertElement: "+y)}catch(y){}}}})},n.chat_commands.purge=n.chat_commands.p=function(e,t){if(!t||!t.length)return"Purge Usage: /p username [more usernames separated by spaces]";if(t.length>10)return"Please only purge up to 10 users at once.";for(var n=0;n10)return"Please only ban up to 10 users at once.";for(var n=0;n10)return"Please only unban up to 10 users at once.";for(var n=0;nn?this._legacy_add_room(e,t,n):void 0)})},n.prototype._legacy_load_room_css=function(e,t,n){var s=e,r=s.match(i);r&&r[1]&&(s=r[1]);var l={id:e,menu_sets:[s],sets:[s],moderator_badge:null,css:null};return n&&(n=n.replace(a,"").trim()),n&&(n=n.replace(o,function(e,t){return l.moderator_badge||"modicon.png"!==t.substr(-11)?e:(l.moderator_badge=t,"")})),l.css=n||null,this._load_room_json(e,t,l)}},{"../constants":3,"../utils":28}],10:[function(){var t=e.FrankerFaceZ;t.prototype.setup_viewers=function(){this.log("Hooking the Ember Viewers controller.");var e=App.__container__.resolve("controller:viewers");this._modify_viewers(e)},t.prototype._modify_viewers=function(e){var n=this;e.reopen({lines:function(){var e=this._super();try{var a=[],o={},i=null,s=App.__container__.lookup("controller:channel"),r=this.get("parentController.model.id"),l=s&&s.get("id");if(l){var c=s.get("display_name");c&&(t.capitalization[l]=[c,Date.now()])}r!=l&&(l=null);for(var u=0;un?this._legacy_load_set(e,t,n):"function"==typeof t&&t(!1))})},n.prototype._legacy_load_css=function(e,t,n){var i={},s={id:e,emotes:i,extra_css:null},r=this;"global"==e?s.title="Global":"globalevent"==e?s.title="Global Event":".donor"==e&&(s.title="Donor"),n=n.replace(a,function(t,n,a,o,s,c,u,d){s=parseInt(s),c=parseInt(c),u=l(u,s);var h="."===o.substr(o.lastIndexOf("/")+1,1),p=++r._last_emote_id,m={id:p,set_id:e,hidden:h,name:a,height:s,width:c,url:o,margins:u,extra_css:d};return i[p]=m,""}).trim(),n&&n.replace(o,function(e,t){s.icon||"modicon.png"!==t.substr(-11)||(s.icon=t)}),this._load_set_json(e,t,s)}},{"./constants":3,"./utils":28}],12:[function(){var t=e.FrankerFaceZ;t.prototype.find_bttv=function(t,n){return this.has_bttv=!1,e.BTTVLOADED?this.setup_bttv(n||0):void(n>=6e4?this.log("BetterTTV was not detected after 60 seconds."):setTimeout(this.find_bttv.bind(this,t,(n||0)+t),t))},t.prototype.setup_bttv=function(e){this.log("BetterTTV was detected after "+e+"ms. Hooking."),this.has_bttv=!0,this.log("WOO"),document.body.classList.remove("ffz-dark"),this._dark_style&&(this._dark_style.parentElement.removeChild(this._dark_style),delete this._dark_style),document.body.classList.remove("ffz-chat-colors"),document.body.classList.remove("ffz-chat-background");var t=BetterTTV.chat.helpers.sendMessage,n=this;BetterTTV.chat.helpers.sendMessage=function(e){var a=e.split(" ",1)[0].toLowerCase();return"/ffz"!==a?t(e):void n.run_ffz_command(e.substr(5),BetterTTV.chat.store.currentRoom)};var a,o=BetterTTV.chat.handlers.onPrivmsg;BetterTTV.chat.handlers.onPrivmsg=function(e,t){a=e;var n=o(e,t);return a=null,n};var i=BetterTTV.chat.templates.privmsg;BetterTTV.chat.templates.privmsg=function(e,t,o,s,r){try{return n.bttv_badges(r),'
'+BetterTTV.chat.templates.timestamp(r.time)+" "+(s?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,t?r.color:!1)+"
"}catch(l){return n.log("Error: ",l),i(e,t,o,s,r)}};var s,r=BetterTTV.chat.templates.message;BetterTTV.chat.templates.message=function(e,t,a,o){try{o=o||!1;var i=encodeURIComponent(t);if("jtv"!==e){s=e;var l=BetterTTV.chat.templates.emoticonize(t,a);s=null;for(var c=0;c'+t+"
"}catch(u){return n.log("Error: ",u),r(e,t,a,o)}};var l=BetterTTV.chat.templates.emoticonize;BetterTTV.chat.templates.emoticonize=function(e,t){var o=l(e,t),i=n.getEmotes(s,a),t=[];return _.each(i,function(e){var a=n.emote_sets[e];a&&_.each(a.emotes,function(e){_.any(o,function(t){return _.isString(t)&&t.match(e.regex)})&&t.push(e)})}),t.length?(_.each(t,function(e){var t=[''+e.name+''],n=o;if(o=[],!n||!n.length)return o;for(var a=0;a=6e4?this.log("Emote Menu for Twitch was not detected after 60 seconds."):setTimeout(this.find_emote_menu.bind(this,t,(n||0)+t),t))},t.prototype.setup_emote_menu=function(e){this.log("Emote Menu for Twitch was detected after "+e+"ms. Registering emote enumerator."),emoteMenu.registerEmoteGetter("FrankerFaceZ",this._emote_menu_enumerator.bind(this))},t.prototype._emote_menu_enumerator=function(){for(var e=this.get_user(),n=e?e.login:null,a=App.__container__.lookup("controller:chat"),o=a?a.get("currentRoom.id"):null,i=this.getEmotes(n,o),s=[],r=0;r=6e4?this.log('Twitch application not detected in "'+location.toString()+'". Aborting.'):setTimeout(this.initialize.bind(this,t,(n||0)+t),t)))},n.prototype.setup_normal=function(t){var a=e.performance&&performance.now?performance.now():Date.now();this.log("Found non-Ember Twitch after "+(t||0)+' ms in "'+location+'". Initializing FrankerFaceZ version '+n.version_info),this.users={},this.load_settings(),this.setup_dark(),this.ws_create(),this.setup_emoticons(),this.setup_badges(),this.setup_notifications(),this.setup_css(),this.find_bttv(10);var o=e.performance&&performance.now?performance.now():Date.now(),i=o-a;this.log("Initialization complete in "+i+"ms")},n.prototype.is_dashboard=!1,n.prototype.setup_dashboard=function(t){var a=e.performance&&performance.now?performance.now():Date.now();this.log("Found Twitch Dashboard after "+(t||0)+' ms in "'+location+'". Initializing FrankerFaceZ version '+n.version_info),this.users={},this.is_dashboard=!0,this.load_settings(),this.setup_dark(),this.ws_create(),this.setup_emoticons(),this.setup_badges(),this.setup_notifications(),this.setup_css(),this.find_bttv(10);var o=e.performance&&performance.now?performance.now():Date.now(),i=o-a;this.log("Initialization complete in "+i+"ms")},n.prototype.setup_ember=function(t){var a=e.performance&&performance.now?performance.now():Date.now();this.log("Found Twitch application after "+(t||0)+' ms in "'+location+'". Initializing FrankerFaceZ version '+n.version_info),this.users={},this.load_settings(),this.setup_dark(),this.ws_create(),this.setup_emoticons(),this.setup_badges(),this.setup_channel(),this.setup_room(),this.setup_line(),this.setup_chatview(),this.setup_viewers(),this.setup_mod_card(),this.setup_notifications(),this.setup_css(),this.setup_menu(),this.setup_my_emotes(),this.setup_races(),this.find_bttv(10),this.find_emote_menu(10),this.check_ff();var o=e.performance&&performance.now?performance.now():Date.now(),i=o-a;this.log("Initialization complete in "+i+"ms")}},{"./badges":1,"./commands":2,"./debug":4,"./ember/channel":5,"./ember/chatview":6,"./ember/line":7,"./ember/moderation-card":8,"./ember/room":9,"./ember/viewers":10,"./emoticons":11,"./ext/betterttv":12,"./ext/emote_menu":13,"./featurefriday":15,"./settings":16,"./shims":17,"./socket":18,"./ui/about_page":19,"./ui/dark":20,"./ui/menu":21,"./ui/menu_button":22,"./ui/my_emotes":23,"./ui/notifications":24,"./ui/races":25,"./ui/styles":26,"./ui/viewer_count":27}],15:[function(t){var n=e.FrankerFaceZ,a=t("./constants");n.prototype.feature_friday=null,n.prototype.check_ff=function(e){e||this.log("Checking for Feature Friday data..."),jQuery.ajax(a.SERVER+"script/event.json",{cache:!1,dataType:"json",context:this}).done(function(e){return this._load_ff(e)}).fail(function(t){return 404==t.status?this._load_ff(null):(e=e||0,e++,10>e?setTimeout(this.check_ff.bind(this,e),250):this._load_ff(null))})},n.ws_commands.reload_ff=function(){this.check_ff()},n.prototype._feature_friday_ui=function(e,t,n){if(this.feature_friday&&this.feature_friday.channel!=e){this._emotes_for_sets(t,n,[this.feature_friday.set],this.feature_friday.title,this.feature_friday.icon,"FrankerFaceZ");var a=App.__container__.lookup("controller:channel");if(!a||a.get("id")!=this.feature_friday.channel){var o=this.feature_friday,i=document.createElement("div"),s=document.createElement("a");i.className="chat-menu-content",i.style.textAlign="center";var r=o.display_name+(o.live?" is live now!":"");s.className="button primary",s.classList.toggle("live",o.live),s.classList.toggle("blue",this.has_bttv&&BetterTTV.settings.get("showBlueButtons")),s.href="http://www.twitch.tv/"+o.channel,s.title=r,s.target="_new",s.innerHTML=""+r+"",i.appendChild(s),t.appendChild(i)}}},n.prototype._load_ff=function(e){if(this.feature_friday){this.global_sets.removeObject(this.feature_friday.set);var t=this.emote_sets[this.feature_friday.set];t&&(t.global=!1),this.feature_friday=null,this.update_ui_link()}e&&e.set&&e.channel&&(this.feature_friday={set:e.set,channel:e.channel,live:!1,title:e.title||"Feature Friday",display_name:n.get_capitalization(e.channel,this._update_ff_name.bind(this))},this.global_sets.push(e.set),this.load_set(e.set,this._update_ff_set.bind(this)),this._update_ff_live())},n.prototype._update_ff_live=function(){if(this.feature_friday){var e=this;Twitch.api.get("streams/"+this.feature_friday.channel).done(function(t){e.feature_friday.live=null!=t.stream,e.update_ui_link()}).always(function(){e.feature_friday.timer=setTimeout(e._update_ff_live.bind(e),12e4)})}},n.prototype._update_ff_set=function(e,t){t&&(t.global=!0)},n.prototype._update_ff_name=function(e){this.feature_friday&&(this.feature_friday.display_name=e)}},{"./constants":3}],16:[function(t){var n=e.FrankerFaceZ,a=t("./constants");make_ls=function(e){return"ffz_setting_"+e},toggle_setting=function(e,t){var n=!this.settings.get(t);this.settings.set(t,n),e.classList.toggle("active",n)},n.settings_info={},n.prototype.load_settings=function(){this.log("Loading settings."),this.settings={};for(var t in n.settings_info)if(n.settings_info.hasOwnProperty(t)){var a=n.settings_info[t],o=a.storage_key||make_ls(t),i=a.hasOwnProperty("value")?a.value:void 0;if(localStorage.hasOwnProperty(o))try{i=JSON.parse(localStorage.getItem(o))}catch(s){this.log('Error loading value for "'+t+'": '+s)}this.settings[t]=i}this.settings.get=this._setting_get.bind(this),this.settings.set=this._setting_set.bind(this),this.settings.del=this._setting_del.bind(this),e.addEventListener("storage",this._setting_update.bind(this),!1)},n.settings_info.replace_twitch_menu={type:"boolean",value:!1,name:"Replace Twitch Emoticon Menu Beta",help:"Completely replace the default Twitch emoticon menu.",on_update:function(e){document.body.classList.toggle("ffz-menu-replace",e)}},n.menu_pages.settings={render:function(e,t){var a={},o=[];for(var i in n.settings_info)if(n.settings_info.hasOwnProperty(i)){var s=n.settings_info[i],r=s.category||"Miscellaneous",l=a[r];if(void 0!==s.visible&&null!==s.visible){var c=s.visible;if("function"==typeof s.visible&&(c=s.visible.bind(this)()),!c)continue}l||(o.push(r),l=a[r]=[]),l.push([i,s])}o.sort(function(e,t){var e=e.toLowerCase(),t=t.toLowerCase();return"debugging"===e&&(e="zzz"+e),"debugging"===t&&(t="zzz"+t),t>e?-1:e>t?1:0});for(var u=0;un?-1:n>a?1:i>o?-1:o>i?1:0});for(var _=0;_",v.className="switch-label",v.innerHTML=s.name,f.appendChild(y),f.appendChild(v),y.addEventListener("click",toggle_setting.bind(this,y,i))}else{f.classList.add("option");var w=document.createElement("a");w.innerHTML=s.name,w.href="#",f.appendChild(w),w.addEventListener("click",s.method.bind(this))}if(s.help){var b=document.createElement("span");b.className="help",b.innerHTML=s.help,f.appendChild(b)}}p.appendChild(f)}t.appendChild(p)}},name:"Settings",icon:a.GEAR,sort_order:99999,wide:!0},n.prototype._setting_update=function(t){if(t||(t=e.event),t.key&&"ffz_setting_"===t.key.substr(0,12)){var a=t.key,o=a.substr(12),i=void 0,s=n.settings_info[o];if(!s){for(o in n.settings_info)if(n.settings_info.hasOwnProperty(o)&&(s=n.settings_info[o],s.storage_key==a))break;if(s.storage_key!=a)return}this.log("Updated Setting: "+o);try{i=JSON.parse(t.newValue)}catch(r){this.log('Error loading new value for "'+o+'": '+r),i=s.value||void 0}if(this.settings[o]=i,s.on_update)try{s.on_update.bind(this)(i,!1)}catch(r){this.log('Error running updater for setting "'+o+'": '+r)}}},n.prototype._setting_get=function(e){return this.settings[e]},n.prototype._setting_set=function(e,t){var a=n.settings_info[e],o=a.storage_key||make_ls(e),i=JSON.stringify(t);if(this.settings[e]=t,localStorage.setItem(o,i),this.log('Changed Setting "'+e+'" to: '+i),a.on_update)try{a.on_update.bind(this)(t,!0)}catch(s){this.log('Error running updater for setting "'+e+'": '+s)}},n.prototype._setting_del=function(e){var t=n.settings_info[e],a=t.storage_key||make_ls(e),o=void 0;if(localStorage.hasOwnProperty(a)&&localStorage.removeItem(a),delete this.settings[e],t&&(o=this.settings[e]=t.hasOwnProperty("value")?t.value:void 0),t.on_update)try{t.on_update.bind(this)(o,!0)}catch(i){this.log('Error running updater for setting "'+e+'": '+i)}}},{"./constants":3}],17:[function(){Array.prototype.equals=function(e){if(!e)return!1;if(this.length!=e.length)return!1;for(var t=0,n=this.length;n>t;t++)if(this[t]instanceof Array&&e[t]instanceof Array){if(!this[t].equals(e[t]))return!1}else if(this[t]!=e[t])return!1;return!0}},{}],18:[function(){var t=e.FrankerFaceZ;t.prototype._ws_open=!1,t.prototype._ws_delay=0,t.ws_commands={},t.ws_on_close=[],t.prototype.ws_create=function(){var e,n=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(a){return this._ws_exists=!1,this.log("Error Creating WebSocket: "+a)}this._ws_exists=!0,e.onopen=function(){n._ws_open=!0,n._ws_delay=0,n.log("Socket connected.");var e=n.get_user();if(e&&n.ws_send("setuser",e.login),n.is_dashboard){var t=location.pathname.match(/\/([^\/]+)/);t&&n.ws_send("sub",t[1])}for(var a in n.rooms)n.rooms.hasOwnProperty(a)&&n.ws_send("sub",a);var o=n._ws_pending;n._ws_pending=[];for(var i=0;i0){o=!0;break}}var l=document.createElement("div"),c="";c+="

FrankerFaceZ

",c+='
new ways to woof
',l.className="chat-menu-content center",l.innerHTML=c,t.appendChild(l);var u=0,d=l.querySelector("h1");d&&d.addEventListener("click",function(){if(d.style.cursor="pointer",u++,u>=3){u=0;var e=document.querySelector(".app-main")||document.querySelector(".ember-chat-container");e&&e.classList.toggle("ffz-flip")}setTimeout(function(){u=0,d.style.cursor=""},2e3)});var h=document.createElement("div"),p=document.createElement("a"),m="To use custom emoticons in "+(o?"this channel":"tons of channels")+", get FrankerFaceZ from http://www.frankerfacez.com";p.className="button primary",p.innerHTML="Advertise in Chat",p.addEventListener("click",this._add_emote.bind(this,e,m)),h.appendChild(p);var _=document.createElement("a");_.className="button ffz-donate",_.href="http://www.frankerfacez.com/donate.html",_.target="_new",_.innerHTML="Donate",h.appendChild(_),h.className="chat-menu-content center",t.appendChild(h);var f=document.createElement("div");c='',c+='',c+='',c+='',c+='',f.className="chat-menu-content center",f.innerHTML=c; +var g=!1;f.querySelector("#ffz-debug-logs").addEventListener("click",function(){g||(g=!0,i._pastebin(i._log_data.join("\n"),function(e){g=!1,e?prompt("Your FrankerFaceZ logs have been uploaded to the URL:",e):alert("There was an error uploading the FrankerFaceZ logs.")}))}),t.appendChild(f)}}},{"../constants":3}],20:[function(t){var n=e.FrankerFaceZ,a=t("../constants");n.settings_info.twitch_chat_dark={type:"boolean",value:!1,visible:!1},n.settings_info.dark_twitch={type:"boolean",value:!1,no_bttv:!0,name:"Dark Twitch",help:"Apply a dark background to channels and other related pages for easier viewing.",on_update:function(t){if(!this.has_bttv){document.body.classList.toggle("ffz-dark",t);var n=e.App?App.__container__.lookup("controller:settings").get("model"):void 0;t?(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)}}},n.prototype.setup_dark=function(){this.has_bttv||(document.body.classList.toggle("ffz-dark",this.settings.dark_twitch),this.settings.dark_twitch&&e.App&&App.__container__.lookup("controller:settings").set("model.darkMode",!0),this.settings.dark_twitch&&this._load_dark_css())},n.prototype._load_dark_css=function(){if(!this._dark_style){this.log("Injecting FrankerFaceZ Dark Twitch CSS.");var e=this._dark_style=document.createElement("link");e.id="ffz-dark-css",e.setAttribute("rel","stylesheet"),e.setAttribute("href",a.SERVER+"script/dark.css?_="+Date.now()),document.head.appendChild(e)}}},{"../constants":3}],21:[function(t){var n=e.FrankerFaceZ,a=t("../constants"),o=t("../utils"),i="http://static-cdn.jtvnw.net/emoticons/v1/";n.prototype.setup_menu=function(){this.log("Installing mouse-up event to auto-close menus.");var e=this;jQuery(document).mouseup(function(t){var n,a=e._popup;a&&(a=jQuery(a),n=a.parent(),n.is(t.target)||0!==n.has(t.target).length||(a.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)},n.menu_pages={},n.prototype.build_ui_popup=function(e){var t=this._popup;if(t)return t.parentElement.removeChild(t),delete this._popup,this._popup_kill&&this._popup_kill(),void delete this._popup_kill;var o=document.createElement("div"),i=document.createElement("div"),s=document.createElement("ul"),r=this.has_bttv?BetterTTV.settings.get("darkenedMode"):!1;o.className="emoticon-selector chat-menu ffz-ui-popup",i.className="emoticon-selector-box dropmenu",o.appendChild(i),o.classList.toggle("dark",r);var l=document.createElement("div");l.className="ffz-ui-menu-page",i.appendChild(l),s.className="menu clearfix",i.appendChild(s);var c=document.createElement("li");c.className="title",c.innerHTML=""+(a.DEBUG?"[DEV] ":"")+"FrankerFaceZ",s.appendChild(c);var u=[];for(var d in n.menu_pages)if(n.menu_pages.hasOwnProperty(d)){var h=n.menu_pages[d];h&&(!h.hasOwnProperty("visible")||h.visible&&("function"!=typeof h.visible||h.visible.bind(this)()))&&u.push([h.sort_order||0,d,h])}u.sort(function(e,t){if(e[0]t[0])return-1;var n=e[1].toLowerCase(),a=t[1].toLowerCase();return a>n?1:n>a?-1:0});for(var p=0;p0,u.className="emoticon-grid",d.className="heading",c&&(d.style.backgroundImage='url("'+c+'")'),d.innerHTML='TwitchSubscriber Emoticons',u.appendChild(d);for(var p=r.get("emoticons"),m=0;m0&&t.appendChild(u),l){var b=a.room.get("channel.isSubscribed.content");if(b=b.length>0?b[b.length-1]:void 0,b&&b.purchase_profile&&!b.purchase_profile.will_renew){var y=o.parse_date(b.access_end||"");w=document.createElement("div"),k=document.createElement("div"),F=document.createElement("span"),end_time=y?Math.floor((y.getTime()-Date.now())/1e3):null,w.className="subscribe-message",k.className="non-subscriber-message",w.appendChild(k),F.className="unlock-text",F.innerHTML="Subscription expires in "+o.time_to_string(end_time,!0,!0),k.appendChild(F),t.appendChild(w)}}else{var w=document.createElement("div"),k=document.createElement("div"),F=document.createElement("span"),E=document.createElement("a");w.className="subscribe-message",k.className="non-subscriber-message",w.appendChild(k),F.className="unlock-text",F.innerHTML="Subscribe to unlock Emoticons",k.appendChild(F),E.className="action subscribe-button button primary",E.href=r.get("product_url"),E.innerHTML='",k.appendChild(E),t.appendChild(w)}}}this._emotes_for_sets(t,e,a&&a.menu_sets||[],this.feature_friday||s?"Channel Emoticons":null,"http://cdn.frankerfacez.com/channel/global/devicon.png","FrankerFaceZ"),this._feature_friday_ui(n,t,e)},name:"Channel",icon:a.ZREKNARF},n.prototype._emotes_for_sets=function(e,t,n,a,o,i){var s=document.createElement("div"),r=0;if(s.className="emoticon-grid",null!=a){var l=document.createElement("div");if(l.className="heading",i){var c=document.createElement("span");c.className="right",c.appendChild(document.createTextNode(i)),l.appendChild(c)}l.appendChild(document.createTextNode(a)),o&&(l.style.backgroundImage='url("'+o+'")'),s.appendChild(l)}for(var u=0;u0){i=!0;break}}t.classList.toggle("no-emotes",!i),t.classList.toggle("live",l),t.classList.toggle("dark",s),t.classList.toggle("blue",r)}}},{"../constants":3}],23:[function(t){var n=e.FrankerFaceZ,a=t("../constants"),o="http://static-cdn.jtvnw.net/emoticons/v1/",i={"00000turbo":!0},s={"#-?[\\\\/]":"#-/",":-?(?: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"},r=function(e){var t=App.__container__.lookup("controller:chat"),n=t.get("currentRoom.id"),a=e.rooms[n],o=a?a.room.tmiSession:null,i=o&&o._emotesParser&&o._emotesParser.emoticonSetIds||"0",s=e.get_user(),r=s&&e.users[s.login]&&e.users[s.login].sets||[];return i=i.split(",").removeObject("0"),e.settings.global_emotes_in_menu&&(i.push("0"),r=_.union(r,e.global_sets)),[i,r]};n.settings_info.global_emotes_in_menu={type:"boolean",value:!1,name:"Display Global Emotes in My Emotes",help:"Display the global Twitch emotes in the My Emoticons menu."},n.prototype.setup_my_emotes=function(){if(this._twitch_emote_sets={},this._twitch_set_to_channel={},localStorage.ffzTwitchSets)try{this._twitch_set_to_channel=JSON.parse(localStorage.ffzTwitchSets)}catch(e){}this._twitch_set_to_channel[0]="twitch_global",this._twitch_set_to_channel[33]="twitch_tfaces",this._twitch_set_to_channel[42]="twitch_tfaces"},n.menu_pages.my_emotes={name:"My Emoticons",icon:a.EMOTE,visible:function(){var e=r(this);return e[0].length>0||e[1].length>0},render:function(e,t){var a=r(this),l=this;new RSVP.Promise(function(e){for(var t=[],o=0;oe?-1:e>t?1:0}),a.emotes=o,a.source="Twitch"}e()}).fail(function(){e()}):e()}),new RSVP.Promise(function(e){if(!t.length)return e();var a=[],o=t,s=function(e,t){var o=l._twitch_emote_sets[e]=l._twitch_emote_sets[e]||{};if(t&&!i[t]){if("twitch_global"==t)return n.capitalization["global emoticons"]=["Global Emoticons",Date.now()],o.channel="Global Emoticons",void(o.badge="//cdn.frankerfacez.com/channel/global/twitch_logo.png");if("turbo"==t||"twitch_tfaces"==t)return o.channel="Twitch Turbo",void(o.badge="//cdn.frankerfacez.com/script/turbo_badge.png");a.push(new RSVP.Promise(function(e,t,n){Twitch.api.get("chat/"+t+"/badges",null,{version:3}).done(function(t){t.subscriber&&t.subscriber.image&&(e.badge=t.subscriber.image),n()}).fail(n)}.bind(this,o,t)));var s=t.toLowerCase(),r=n.capitalization[s];return r&&Date.now()-r[1]<36e5?void(o.channel=r[0]):void a.push(new RSVP.Promise(function(e,t,a,o){l.ws_send("get_display_name",t,function(i,s){var r=i?s:a;n.capitalization[t]=[r,Date.now()],e.channel=r,o()})||(e.channel=a,o()),setTimeout(function(e,t,n){e.channel||(e.channel=t),n()}.bind(this,e,a,o),500)}.bind(this,o,s,t)))}},r=function(){a.length?RSVP.all(a).then(e,e):e()};t=[];for(var c=0;c0?(l.ws_send("twitch_sets",t,function(e,n){if(t=[],e){for(var a in n)n.hasOwnProperty(a)&&(l._twitch_set_to_channel[a]=n[a],s(a,n[a]));localStorage.ffzTwitchSets=JSON.stringify(l._twitch_set_to_channel)}r()}),setTimeout(function(){t.length&&r()},2e3)):r()})]).then(function(){for(var t={},n=0;n0&&c.push([2,d.id,d])}c.sort(function(e,t){if(e[0]t[0])return 1;var n=e[1].toLowerCase(),a=t[1].toLowerCase();return"twitch turbo"===n||"twitch_tfaces"===n?n="zza|"+n:"global emoticons"===n&&(n="zzz|"+n),"twitch turbo"===a||"twitch_tfaces"===a?a="zza|"+a:"global emoticons"===a&&(a="zzz|"+a),a>n?-1:n>a?1:0});for(var h=0;h'+b+""+m,p&&(g.style.backgroundImage='url("'+p+'")'),v.className="emoticon-grid",v.appendChild(g);for(var k=0;k<_.length;k++){var w=_[k],F=s[w.code]||w.code,E=document.createElement("span");if(E.className="emoticon tooltip",E.style.backgroundImage='url("'+(w.url?w.url:o+w.id+"/1.0")+'")',w.height&&(E.style.height=w.height+"px"),w.width&&(E.style.width=w.width+"px"),!w.url){var z='image-set(url("'+o+w.id+'/1.0") 1x, url("'+o+w.id+'/2.0") 2x, url("'+o+w.id+'/3.0") 4x)';E.style.backgroundImage="-webkit-"+z,E.style.backgroundImage="-moz-"+z,E.style.backgroundImage="-ms-"+z,E.style.backgroundImage=z}E.title=F,E.addEventListener("click",l._add_emote.bind(l,e,F)),v.appendChild(E)}t.appendChild(v)}}if(!c.length){var v=document.createElement("div");v.className="chat-menu-content center",v.innerHTML="Error Loading Subscriptions",t.appendChild(v)}}catch(T){l.log("My Emotes Menu Error",T),t.innerHTML="";var v=document.createElement("div"),g=document.createElement("div"),C=document.createElement("p");g.className="heading",g.innerHTML="Error Loading Menu",v.appendChild(g),C.className="clearfix",C.textContent=T,v.appendChild(C),v.className="chat-menu-content",t.appendChild(v)}})}}},{"../constants":3}],24:[function(){var t=e.FrankerFaceZ;t.prototype.setup_notifications=function(){this.log("Adding event handler for window focus."),e.addEventListener("focus",this.clear_notifications.bind(this))},t.settings_info.highlight_notifications={type:"boolean",value:!1,category:"Chat",no_bttv:!0,name:"Highlight Notifications",help:"Display notifications when a highlighted word appears in chat in an unfocused tab.",on_update:function(e,t){if(e&&t){if("denied"===Notification.permission)return this.log("Notifications have been denied by the user."),void this.settings.set("highlight_notifications",!1);if("granted"!==Notification.permission){var n=this;Notification.requestPermission(function(e){"denied"===e&&(n.log("Notifications have been denied by the user."),n.settings.set("highlight_notifications",!1))})}}}},t.ws_commands.message=function(e){this.show_message(e)},t._notifications={},t._last_notification=0,t.prototype.clear_notifications=function(){for(var e in t._notifications){var n=t._notifications[e];if(n)try{n.close()}catch(a){}}t._notifications={},t._last_notification=0},t.prototype.show_notification=function(e,n,a,o,i,s){var r=Notification.permission;if("denied "===r)return!1;if("granted"===r){n=n||"FrankerFaceZ",o=o||1e4;var l={lang:"en-US",dir:"ltr",body:e,tag:a||"FrankerFaceZ",icon:"http://cdn.frankerfacez.com/icon32.png"},c=this,u=new Notification(n,l),d=t._last_notification++;return t._notifications[d]=u,u.addEventListener("click",function(){delete t._notifications[d],i&&i.bind(c)()}),u.addEventListener("close",function(){delete t._notifications[d],s&&s.bind(c)()}),void("number"==typeof o&&u.addEventListener("show",function(){setTimeout(function(){delete t._notifications[d],u.close()},o)}))}var c=this;Notification.requestPermission(function(){c.show_notification(e,n,a)})},t.prototype.show_message=function(t){e.noty({text:t,theme:"ffzTheme",layout:"bottomCenter",closeWith:["button"]}).show()}},{}],25:[function(t){var n=e.FrankerFaceZ,a=t("../utils");n.prototype.setup_races=function(){this.log("Initializing race support."),this.srl_races={}},n.settings_info.srl_races={type:"boolean",value:!0,category:"Channel Metadata",name:"SRL Race Information",help:'Display information about SpeedRunsLive races under channels.',on_update:function(){this.rebuild_race_ui()}},n.ws_on_close.push(function(){var t=e.App&&App.__container__.lookup("controller:channel"),n=t&&t.get("id"),a=!1;if(t){for(var o in this.srl_races)delete this.srl_races[o],o==n&&(a=!0);a&&this.rebuild_race_ui()}}),n.ws_commands.srl_race=function(e){for(var t=App.__container__.lookup("controller:channel"),n=t.get("id"),a=!1,o=0;o=300?"right":"left")+" share dropmenu",this._popup_kill=this._race_kill.bind(this),this._popup=e;var l="http://kadgar.net/live",c=!1;for(var u in s.entrants){var d=s.entrants[u].state;s.entrants.hasOwnProperty(u)&&s.entrants[u].channel&&("racing"==d||"entered"==d)&&(l+="/"+s.entrants[u].channel,c=!0)}var h=document.querySelector(".app-main.theatre")?document.body.clientHeight-300:t.parentElement.offsetTop-175,p=App.__container__.lookup("controller:channel"),m=p?p.get("display_name"):n.get_capitalization(i),_=encodeURIComponent("I'm watching "+m+" race "+s.goal+" in "+s.game+" on SpeedRunsLive!");r='
',r+='
Developers
Dan Salvato  
Stendec  
Version '+n.version_info+'Logs
',r+="
#Entrant Time
",r+='
',r+='',r+='

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

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

'+F+"

Goal: "+E}c?m?d.innerHTML="Done":(d.innerHTML=a.time_to_string(c),this._race_timer=setTimeout(this._update_race.bind(this),1e3)):d.innerHTML="Entry Open"}}}},{"../utils":28}],26:[function(t){var n=e.FrankerFaceZ,a=t("../constants");n.prototype.setup_css=function(){this.log("Injecting main FrankerFaceZ CSS.");var e=this._main_style=document.createElement("link");e.id="ffz-ui-css",e.setAttribute("rel","stylesheet"),e.setAttribute("href",a.SERVER+"script/style.css?_="+Date.now()),document.head.appendChild(e),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":3}],27:[function(t){var n=e.FrankerFaceZ,a=t("../constants"),o=t("../utils");n.ws_commands.viewers=function(t){var n=t[0],i=t[1],s=e.App&&App.__container__.lookup("controller:channel"),r=this.is_dashboard?location.pathname.match(/\/([^\/]+)/):void 0,l=this.is_dashboard?r&&r[1]:s&&s.get&&s.get("id");if(l===n){var c=document.querySelector("#ffz-viewer-display"),u=a.ZREKNARF+" "+o.number_commas(i);if(c)c.innerHTML=u;else{var d=document.querySelector(this.is_dashboard?"#stats":".stats-and-actions .channel-stats");if(!d)return;c=document.createElement("span"),c.id="ffz-viewer-display",c.className="ffz stat",c.title="Chatters with FrankerFaceZ",c.innerHTML=u,d.appendChild(c),jQuery(c).tipsy(this.is_dashboard?{gravity:"s"}:void 0)}}}},{"../constants":3,"../utils":28}],28:[function(t,n){var a=(e.FrankerFaceZ,t("./constants"),{}),o=document.createElement("span"),i=function(e){return 1==e?"1st":2==e?"2nd":3==e?"3rd":null==e?"---":e+"th"},s=function(e,t){t=0===t?0:t||1,t=Math.round(255*-(t/100));var n=Math.max(0,Math.min(255,e[0]-t)),a=Math.max(0,Math.min(255,e[1]-t)),o=Math.max(0,Math.min(255,e[2]-t));return[n,a,o]},r=function(e){return"rgb("+e[0]+", "+e[1]+", "+e[2]+")"},l=function(e,t){return t=0===t?0:t||1,s(e,-t)},c=function(e){e=[e[0]/255,e[1]/255,e[2]/255];for(var t=0;ts;(l||n)&&(l&&(a=a.substr(0,s)+a.substr(r+i.length)),n&&(a+=o+n+i),e.innerHTML=a)},get_luminance:c,brighten:s,darken:l,rgb_to_css:r,parse_date:d,number_commas:function(e){var t=e.toString().split(".");return t[0]=t[0].replace(/\B(?=(\d{3})+(?!\d))/g,","),t.join(".")},place_string:i,placement:function(e){return"forfeit"==e.state?"Forfeit":"dq"==e.state?"DQed":e.place?i(e.place):""},sanitize:function(e){var t=a[e];return t||(o.textContent=e,t=a[e]=o.innerHTML,o.innerHTML=""),t},date_string:function(e){return e.getFullYear()+"-"+(e.getMonth()+1)+"-"+e.getDate()},time_to_string:function(e,t,n){var a=e%60,o=Math.floor(e/60),i=Math.floor(o/60),s="";if(o%=60,t){if(s=Math.floor(i/24),i%=24,n&&s>0)return s+" days";s=s>0?s+" days, ":""}return s+(10>i?"0":"")+i+":"+(10>o?"0":"")+o+":"+(10>a?"0":"")+a}}},{"./constants":3}]},{},[14]),e.ffz=new FrankerFaceZ}(window); \ No newline at end of file diff --git a/src/constants.js b/src/constants.js index 5dbf3598..0352a5f2 100644 --- a/src/constants.js +++ b/src/constants.js @@ -9,6 +9,7 @@ module.exports = { ZREKNARF: '' + SVGPATH + '', CHAT_BUTTON: '' + SVGPATH + '', + CLOCK: '', GEAR: '', HEART: '', EMOTE: '' diff --git a/src/ember/channel.js b/src/ember/channel.js new file mode 100644 index 00000000..7c153873 --- /dev/null +++ b/src/ember/channel.js @@ -0,0 +1,171 @@ +var FFZ = window.FrankerFaceZ, + utils = require('../utils'), + constants = require('../constants'); + + +// -------------------- +// Initialization +// -------------------- + +FFZ.prototype.setup_channel = function() { + this.channels = {}; + + 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 view."); + + var Channel = App.__container__.lookup('controller:channel'), + f = this; + + if ( ! Channel ) + return; + + Channel.reopen({ + ffzUpdateUptime: function() { + f.update_uptime(); + }.observes("isLive", "content.id").on("init") + + /*ffzUpdateInfo: function() { + f.log("Updated! ID: " + this.get("content.id")); + f.update_stream_info(true); + }.observes("content.id").on("init")*/ + }); + + // Do uptime the first time. + this.update_uptime(); + //this.update_stream_info(true); +} + + +// --------------- +// Settings +// --------------- + +FFZ.settings_info.stream_uptime = { + type: "boolean", + value: false, + + category: "Channel Metadata", + name: "Stream Uptime", + help: 'Display the stream uptime under a channel by the viewer count.', + on_update: function(val) { + this.update_uptime(); + } + }; + + +// -------------------- +// Stream Data Update +// -------------------- + +/*FFZ.prototype.update_stream_info = function(just_schedule) { + if ( this._stream_info_update ) { + clearTimeout(this._stream_info_update); + delete this._stream_info_update; + } + + this._stream_info_update = setTimeout(this.update_stream_info.bind(this), 90000); + + if ( just_schedule ) + return; + + var Channel = App.__container__.lookup('controller:channel'), + channel_id = Channel ? Channel.get('content.id') : undefined, + f = this; + if ( ! channel_id ) + return; + + Twitch.api.get("streams/" + channel_id, {}, {version: 3}) + .done(function(data) { + var channel_id = Channel.get('content.id'), d = data.stream; + if ( ! data.stream || d.channel.name != channel_id ) + return; + + // Override the data in Twitch. We can't just .load() the stream + // because that resets the whole channel layout, resetting the + // video player. Twitch pls fix + var old_created = Channel.get('content.stream.created_at'); + + Channel.set('content.stream.created_at', d.created_at); + Channel.set('content.stream.average_fps', d.average_fps); + Channel.set('content.stream.viewers', d.viewers); + Channel.set('content.stream.video_height', d.video_height); + Channel.set('content.stream.csGoSkill', Twitch.uri.csGoSkillImg(("0" + d.skill).slice(-2))); + + Channel.set('content.stream.game', d.game); + Channel.set('content.stream.gameUrl', Twitch.uri.game(d.game)); + Channel.set('content.stream.gameBoxart', Twitch.uri.gameBoxArtJpg(d.game)); + + + // Update the uptime display. + if ( f.settings.stream_uptime && old_created != d.created_at ) + f.update_uptime(true) && f.update_uptime(); + }); +}*/ + + +// -------------------- +// Uptime Display +// -------------------- + +FFZ.prototype.update_uptime = function(destroy) { + if ( this._uptime_update ) { + clearTimeout(this._uptime_update); + delete this._uptime_update; + } + + var Channel = App.__container__.lookup('controller:channel'); + if ( destroy || ! this.settings.stream_uptime || ! Channel || ! Channel.get('isLiveAccordingToKraken') ) { + var el = document.querySelector("#ffz-uptime-display"); + if ( el ) + el.parentElement.removeChild(el); + return; + } + + // Schedule an update. + this._update_uptime = setTimeout(this.update_uptime.bind(this), 1000); + + // Determine when the channel last went live. + var online = Channel.get('content.stream.created_at'); + if ( ! online ) return; + + online = utils.parse_date(online); + if ( ! online ) return; + + var uptime = Math.floor((Date.now() - online.getTime()) / 1000); + if ( uptime < 0 ) return; + + var el = document.querySelector("#ffz-uptime-display span"); + if ( ! el ) { + var cont = document.querySelector("#channel .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); +} \ No newline at end of file diff --git a/src/ember/line.js b/src/ember/line.js index 3ec71f63..b849b281 100644 --- a/src/ember/line.js +++ b/src/ember/line.js @@ -1,4 +1,4 @@ -var FFZ = window.FrankerFaceZ, +var FFZ = window.FrankerFaceZ, utils = require("../utils"), reg_escape = function(str) { @@ -71,6 +71,106 @@ var FFZ = window.FrankerFaceZ, var images = document.querySelectorAll('img[emote-id="' + id + '"]'); for(var x=0; x < images.length; x++) images[x].title = tooltip; + }, + + 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 = "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 == "twitter" ) { + tooltip = "Tweet By: " + utils.sanitize(link_data.user) + "
"; + tooltip += utils.sanitize(link_data.tweet); + + + } else if ( link_data.type == "reputation" ) { + 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 = '' + 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; + } }; @@ -78,24 +178,13 @@ var FFZ = window.FrankerFaceZ, // Settings // --------------------- -FFZ.settings_info.capitalize = { - type: "boolean", - value: true, - - category: "Chat", - visible: function() { return ! this.has_bttv }, - - name: "Username Capitalization", - help: "Display names in chat with proper capitalization." - }; - - FFZ.settings_info.banned_words = { type: "button", value: [], category: "Chat", - visible: function() { return ! this.has_bttv }, + 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.", @@ -126,7 +215,8 @@ FFZ.settings_info.keywords = { value: [], category: "Chat", - visible: function() { return ! this.has_bttv }, + no_bttv: true, + //visible: function() { return ! this.has_bttv }, name: "Highlight Keywords", help: "Set additional keywords that will be highlighted in chat.", @@ -158,7 +248,8 @@ FFZ.settings_info.fix_color = { value: true, category: "Chat", - visible: function() { return ! this.has_bttv }, + no_bttv: true, + //visible: function() { return ! this.has_bttv }, name: "Adjust Username Colors", help: "Ensure that username colors contrast with the background enough to be readable.", @@ -172,12 +263,26 @@ FFZ.settings_info.fix_color = { }; +FFZ.settings_info.link_info = { + type: "boolean", + value: true, + + category: "Chat", + no_bttv: true, + //visible: function() { return ! this.has_bttv }, + + name: "Link Tooltips Beta", + help: "Check links against known bad websites, unshorten URLs, and show YouTube info." + }; + + FFZ.settings_info.chat_rows = { type: "boolean", value: false, category: "Chat", - visible: function() { return ! this.has_bttv }, + no_bttv: true, + //visible: function() { return ! this.has_bttv }, name: "Chat Line Backgrounds", help: "Display alternating background colors for lines in chat.", @@ -211,6 +316,7 @@ FFZ.prototype.setup_line = function() { // Emoticon Data this._twitch_emotes = {}; + this._link_data = {}; this.log("Hooking the Ember Line controller."); @@ -232,6 +338,11 @@ FFZ.prototype.setup_line = function() { if ( ! user || this.get("model.from") != user.login ) tokens = f._mentionize(this, tokens); + // Store the capitalization. + var display = this.get("model.tags.display-name"); + if ( display && display.length ) + FFZ.capitalization[this.get("model.from")] = [display.trim(), Date.now()]; + var end = performance.now(); if ( end - start > 5 ) f.log("Tokenizing Message Took Too Long - " + (end-start) + "ms", tokens, false, true); @@ -265,7 +376,6 @@ FFZ.prototype.setup_line = function() { row_type = controller.get('model.ffz_alternate'); - // Color Processing if ( color ) f._handle_color(color); @@ -290,11 +400,6 @@ FFZ.prototype.setup_line = function() { f.render_badge(this); - // Capitalization - if ( f.settings.capitalize ) - f.capitalize(this, user); - - // Mention Highlighting var mentioned = el.querySelector('span.mentioned'); if ( mentioned ) { @@ -357,12 +462,52 @@ FFZ.prototype.setup_line = function() { 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(); }); // Also add a nice tooltip. - jQuery(link).tipsy(); + jQuery(link).tipsy({html:true}); + } + + + // Link Tooltips + if ( f.settings.link_info ) { + var links = el.querySelectorAll("span.message a"); + for(var i=0; i < links.length; i++) { + var link = links[i], + href = link.href, + deleted = false; + + if ( link.classList.contains("deleted-link") ) { + href = link.getAttribute("data-url"); + deleted = true; + } + + // Check the cache. + var link_data = f._link_data[href]; + if ( link_data ) { + if ( !deleted && typeof link_data != "boolean" ) + link.title = link_data.tooltip; + + if ( link_data.unsafe ) + link.classList.add('unsafe-link'); + + } else if ( ! /^mailto:/.test(href) ) { + f._link_data[href] = true; + f.ws_send("get_link", href, load_link_data.bind(f, href)); + } + } + + jQuery(links).tipsy({html:true}); } @@ -536,16 +681,6 @@ FFZ.get_capitalization = function(name, callback) { } -FFZ.prototype.capitalize = function(view, user) { - var name = FFZ.get_capitalization(user, this.capitalize.bind(this, view)); - if ( !name || !view ) - return; - - var from = view.$('.from'); - from && from.text(name); -} - - // --------------------- // Extra Mentions // --------------------- diff --git a/src/ember/moderation-card.js b/src/ember/moderation-card.js index cb5d6412..d7c5faa5 100644 --- a/src/ember/moderation-card.js +++ b/src/ember/moderation-card.js @@ -5,7 +5,8 @@ var FFZ = window.FrankerFaceZ, ESC: 27, P: 80, B: 66, - T: 84 + T: 84, + U: 85 }, btns = [ @@ -27,7 +28,8 @@ FFZ.settings_info.enhanced_moderation = { type: "boolean", value: false, - visible: function() { return ! this.has_bttv }, + no_bttv: true, + //visible: function() { return ! this.has_bttv }, category: "Chat", name: "Enhanced Moderation", @@ -41,32 +43,33 @@ FFZ.settings_info.enhanced_moderation = { FFZ.prototype.setup_mod_card = function() { this.log("Hooking the Ember Moderation Card view."); - var Card = App.__container__.resolve('view:moderation-card'), + var Card = App.__container__.resolve('component:moderation-card'), f = this; Card.reopen({ didInsertElement: function() { this._super(); + window._card = this; try { if ( f.has_bttv || ! f.settings.enhanced_moderation ) return; var el = this.get('element'), - controller = this.get('context'); + controller = this.get('controller'); // Style it! el.classList.add('ffz-moderation-card'); // Only do the big stuff if we're mod. - if ( controller.get('parentController.model.isModeratorOrHigher') ) { + if ( controller.get('cardInfo.isModeratorOrHigher') ) { el.classList.add('ffz-is-mod'); el.setAttribute('tabindex', 1); // Key Handling el.addEventListener('keyup', function(e) { var key = e.keyCode || e.which, - user_id = controller.get('model.user.id'), - room = controller.get('parentController.model'); + 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"); @@ -77,6 +80,9 @@ FFZ.prototype.setup_mod_card = function() { 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; @@ -89,8 +95,8 @@ FFZ.prototype.setup_mod_card = function() { line.className = 'interface clearfix'; var btn_click = function(timeout) { - var user_id = controller.get('model.user.id'), - room = controller.get('parentController.model'); + var user_id = controller.get('cardInfo.user.id'), + room = App.__container__.lookup('controller:chat').get('currentRoom'); if ( timeout === -1 ) room.send("/unban " + user_id); @@ -151,8 +157,9 @@ FFZ.prototype.setup_mod_card = function() { // More Fixing Other Buttons var op_btn = el.querySelector('button.mod'); if ( op_btn ) { - var model = controller.get('parentController.model'), - can_op = model.get('isBroadcaster') || model.get('isStaff') || model.get('isAdmin'); + 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); @@ -160,7 +167,7 @@ FFZ.prototype.setup_mod_card = function() { var msg_btn = el.querySelector(".interface > button"); - if ( msg_btn && msg_btn.className == "button" ) { + if ( msg_btn && msg_btn.classList.contains("message-button") ) { msg_btn.innerHTML = MESSAGE; msg_btn.classList.add('glyph-only'); msg_btn.classList.add('message'); diff --git a/src/ember/room.js b/src/ember/room.js index b8f39a68..66272401 100644 --- a/src/ember/room.js +++ b/src/ember/room.js @@ -28,6 +28,16 @@ FFZ.prototype.setup_room = function() { this.log("Hooking the Ember Room model."); + // Responsive ban button. + var RC = App.__container__.lookup('controller:room'); + if ( RC ) { + var orig_action = RC._actions.banUser; + RC._actions.banUser = function(e) { + orig_action.bind(this)(e); + this.get("model").clearMessages(e.user); + } + } + var Room = App.__container__.resolve('model:room'); this._modify_room(Room); @@ -287,8 +297,7 @@ FFZ.prototype._modify_room = function(room) { var suggestions = this._super(); try { - if ( f.settings.capitalize ) - suggestions = _.map(suggestions, FFZ.get_capitalization); + suggestions = _.map(suggestions, FFZ.get_capitalization); } catch(err) { f.error("get_suggestions: " + err); } diff --git a/src/featurefriday.js b/src/featurefriday.js index ef3239a7..002027c5 100644 --- a/src/featurefriday.js +++ b/src/featurefriday.js @@ -47,7 +47,7 @@ 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._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. diff --git a/src/main.js b/src/main.js index 8c7a485e..3b54d6e6 100644 --- a/src/main.js +++ b/src/main.js @@ -22,7 +22,7 @@ FFZ.get = function() { return FFZ.instance; } // Version var VER = FFZ.version_info = { - major: 3, minor: 2, revision: 5, + major: 3, minor: 3, revision: 1, toString: function() { return [VER.major, VER.minor, VER.revision].join(".") + (VER.extra || ""); } @@ -93,7 +93,7 @@ FFZ.prototype.get_user = function() { if ( window.PP && PP.login ) { return PP; } else if ( window.App ) { - var nc = App.__container__.lookup("controller:navigation"); + var nc = App.__container__.lookup("controller:login"); return nc ? nc.get("userData") : undefined; } } @@ -115,6 +115,7 @@ require('./emoticons'); require('./badges'); // Analytics: require('./ember/router'); +require('./ember/channel'); require('./ember/room'); require('./ember/line'); require('./ember/chatview'); @@ -154,7 +155,17 @@ FFZ.prototype.initialize = function(increment, delay) { // Make sure that FrankerFaceZ doesn't start setting itself up until the // Twitch ember application is ready. - // TODO: Special Dashboard check. + // Check for special non-ember pages. + if ( /\/(?:settings|messages?\/)/.test(location.pathname) ) { + this.setup_normal(delay); + return; + } + + // Check for the dashboard. + if ( /\/[A-Za-z_-]+\/dashboard/.test(location.pathname) && !/bookmarks$/.test(location.pathname) ) { + this.setup_dashboard(delay); + return; + } var loaded = window.App != undefined && App.__container__ != undefined && @@ -174,6 +185,65 @@ FFZ.prototype.initialize = function(increment, delay) { } +FFZ.prototype.setup_normal = function(delay) { + 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 = {}; + + // 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_notifications(); + this.setup_css(); + + 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.setup_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; + + // 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_notifications(); + this.setup_css(); + + 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.setup_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); @@ -193,6 +263,7 @@ FFZ.prototype.setup_ember = function(delay) { //this.setup_piwik(); //this.setup_router(); + this.setup_channel(); this.setup_room(); this.setup_line(); this.setup_chatview(); diff --git a/src/settings.js b/src/settings.js index 8f5bb5ca..44e0c8d6 100644 --- a/src/settings.js +++ b/src/settings.js @@ -58,6 +58,19 @@ FFZ.prototype.load_settings = function() { // Menu Page // -------------------- +FFZ.settings_info.replace_twitch_menu = { + type: "boolean", + value: false, + + name: "Replace Twitch Emoticon Menu Beta", + help: "Completely replace the default Twitch emoticon menu.", + + on_update: function(val) { + document.body.classList.toggle("ffz-menu-replace", val); + } + }; + + FFZ.menu_pages.settings = { render: function(view, container) { var settings = {}, @@ -141,37 +154,53 @@ FFZ.menu_pages.settings = { el.className = 'clearfix'; - if ( info.type == "boolean" ) { - var swit = document.createElement('a'), - label = document.createElement('span'); - - swit.className = 'switch'; - swit.classList.toggle('active', val); - swit.innerHTML = ""; - + if ( this.has_bttv && info.no_bttv ) { + var label = document.createElement('span'), + help = document.createElement('span'); label.className = 'switch-label'; label.innerHTML = info.name; - el.appendChild(swit); - el.appendChild(label); + help = document.createElement('span'); + help.className = 'help'; + help.innerHTML = 'Disabled due to incompatibility with BetterTTV.'; - swit.addEventListener("click", toggle_setting.bind(this, swit, key)); + el.classList.add('disabled'); + el.appendChild(label); + el.appendChild(help); } else { - el.classList.add("option"); - var link = document.createElement('a'); - link.innerHTML = info.name; - link.href = "#"; - el.appendChild(link); + if ( info.type == "boolean" ) { + var swit = document.createElement('a'), + label = document.createElement('span'); - link.addEventListener("click", info.method.bind(this)); - } + swit.className = 'switch'; + swit.classList.toggle('active', val); + swit.innerHTML = ""; - if ( info.help ) { - var help = document.createElement('span'); - help.className = 'help'; - help.innerHTML = info.help; - el.appendChild(help); + label.className = 'switch-label'; + label.innerHTML = info.name; + + el.appendChild(swit); + el.appendChild(label); + + swit.addEventListener("click", toggle_setting.bind(this, swit, key)); + + } 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); @@ -183,7 +212,8 @@ FFZ.menu_pages.settings = { name: "Settings", icon: constants.GEAR, - sort_order: 99999 + sort_order: 99999, + wide: true }; @@ -195,8 +225,6 @@ FFZ.prototype._setting_update = function(e) { if ( ! e ) e = window.event; - this.log("Storage Event", e); - if ( ! e.key || e.key.substr(0, 12) !== "ffz_setting_" ) return; diff --git a/src/socket.js b/src/socket.js index 4638bce5..cc7c9dde 100644 --- a/src/socket.js +++ b/src/socket.js @@ -36,6 +36,13 @@ FFZ.prototype.ws_create = function() { 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]); + } + // Send the current rooms. for(var room_id in f.rooms) f.rooms.hasOwnProperty(room_id) && f.ws_send("sub", room_id); diff --git a/src/ui/about_page.js b/src/ui/about_page.js index 03987a54..db730f62 100644 --- a/src/ui/about_page.js +++ b/src/ui/about_page.js @@ -37,6 +37,18 @@ FFZ.menu_pages.about = { 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'), diff --git a/src/ui/dark.js b/src/ui/dark.js index 80f226f9..db8c0655 100644 --- a/src/ui/dark.js +++ b/src/ui/dark.js @@ -17,9 +17,10 @@ FFZ.settings_info.dark_twitch = { type: "boolean", value: false, - visible: function() { return ! this.has_bttv }, + no_bttv: true, + //visible: function() { return ! this.has_bttv }, - name: "Dark Twitch Beta", + name: "Dark Twitch", help: "Apply a dark background to channels and other related pages for easier viewing.", on_update: function(val) { @@ -28,14 +29,14 @@ FFZ.settings_info.dark_twitch = { document.body.classList.toggle("ffz-dark", val); - var model = App.__container__.lookup('controller:settings').get('model'); + var model = window.App ? App.__container__.lookup('controller:settings').get('model') : undefined; if ( val ) { this._load_dark_css(); - this.settings.set('twitch_chat_dark', model.get('darkMode')); - model.set('darkMode', true); + model && this.settings.set('twitch_chat_dark', model.get('darkMode')); + model && model.set('darkMode', true); } else - model.set('darkMode', this.settings.twitch_chat_dark); + model && model.set('darkMode', this.settings.twitch_chat_dark); } }; @@ -50,7 +51,7 @@ FFZ.prototype.setup_dark = function() { document.body.classList.toggle("ffz-dark", this.settings.dark_twitch); if ( this.settings.dark_twitch ) - App.__container__.lookup('controller:settings').set('model.darkMode', true); + window.App && App.__container__.lookup('controller:settings').set('model.darkMode', true); if ( this.settings.dark_twitch ) this._load_dark_css(); diff --git a/src/ui/menu.js b/src/ui/menu.js index 0798533d..43879a13 100644 --- a/src/ui/menu.js +++ b/src/ui/menu.js @@ -1,5 +1,8 @@ var FFZ = window.FrankerFaceZ, - constants = require('../constants'); + constants = require('../constants'), + utils = require('../utils'), + + TWITCH_BASE = "http://static-cdn.jtvnw.net/emoticons/v1/"; // -------------------- @@ -23,6 +26,8 @@ FFZ.prototype.setup_menu = function() { delete f._popup_kill; } }); + + document.body.classList.toggle("ffz-menu-replace", this.settings.replace_twitch_menu); } @@ -108,27 +113,31 @@ FFZ.prototype.build_ui_popup = function(view) { jQuery(link).tipsy(); - link.addEventListener("click", this._ui_change_page.bind(this, view, menu, sub_container, key)); + link.addEventListener("click", this._ui_change_page.bind(this, view, inner, menu, sub_container, key)); el.appendChild(link); menu.appendChild(el); } // Render Current Page - this._ui_change_page(view, menu, sub_container, this._last_page || "channel"); + this._ui_change_page(view, inner, menu, sub_container, this._last_page || "channel"); // Add the menu to the DOM. this._popup = container; - sub_container.style.maxHeight = Math.max(100, view.$().height() - 162) + "px"; + sub_container.style.maxHeight = Math.max(200, view.$().height() - 172) + "px"; view.$('.chat-interface').append(container); } -FFZ.prototype._ui_change_page = function(view, menu, container, page) { +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'); @@ -143,55 +152,6 @@ FFZ.prototype._ui_change_page = function(view, menu, container, page) { } -// -------------------- -// Favorites Page -// -------------------- - -FFZ.prototype._tokenize_message = function(message, room_id) { - var lc = App.__container__.lookup('controller:line'), - rc = App.__container__.lookup('controller:room'), - room = this.rooms[room_id], - user = this.get_user(); - - if ( ! lc || ! rc || ! room ) - return [message]; - - rc.set('model', room.room); - lc.set('parentController', rc); - - var model = { - from: user && user.login || "FrankerFaceZ", - message: message, - tags: { - emotes: room.room.tmiSession._emotesParser.parseEmotesTag(message) - } - }; - - lc.set('model', model); - - var tokens = lc.get('tokenizedMessage'); - - lc.set('model', null); - rc.set('model', null); - lc.set('parentController', null); - - return tokens; -} - - -/*FFZ.menu_pages.favorites = { - render: function(view, container) { - // Get the current room. - var room_id = view.get('controller.currentRoom.id'); - - - }, - - name: "Favorites", - icon: constants.HEART - };*/ - - // -------------------- // Channel Page // -------------------- @@ -200,10 +160,107 @@ 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]; + room = this.rooms[room_id], + has_product = false; + + // 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 is_subscribed = room.room.get("channel.isSubscribed.content"), + 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; + + 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; + if ( can_use ) + s.addEventListener('click', this._add_emote.bind(this, view, emote.regex)); + grid.appendChild(s); + c++; + } + + if ( c > 0 ) + inner.appendChild(grid); + + if ( ! 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 { + var last_content = room.room.get("channel.isSubscribed.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); + } + } + } + } // Basic Emote Sets - this._emotes_for_sets(inner, view, room && room.menu_sets || []); + this._emotes_for_sets(inner, view, room && room.menu_sets || [], (this.feature_friday || has_product) ? "Channel Emoticons" : null, "http://cdn.frankerfacez.com/channel/global/devicon.png", "FrankerFaceZ"); // Feature Friday! this._feature_friday_ui(room_id, inner, view); @@ -218,21 +275,29 @@ FFZ.menu_pages.channel = { // Emotes for Sets // -------------------- -FFZ.prototype._emotes_for_sets = function(parent, view, sets, header, btn) { - if ( header != null ) { - var el_header = document.createElement('div'); - el_header.className = 'list-header'; - el_header.appendChild(document.createTextNode(header)); - - if ( btn ) - el_header.appendChild(btn); - - parent.appendChild(el_header); - } - +FFZ.prototype._emotes_for_sets = function(parent, view, sets, header, image, sub_text) { var grid = document.createElement('div'), c = 0; 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); + } + for(var i=0; i < sets.length; i++) { var set = this.emote_sets[sets[i]]; if ( ! set || ! set.emotes ) @@ -259,8 +324,8 @@ FFZ.prototype._emotes_for_sets = function(parent, view, sets, header, btn) { } if ( !c ) { - grid.innerHTML = "This channel has no emoticons."; - grid.className = "chat-menu-content ffz-no-emotes center"; + grid.innerHTML += "This channel has no emoticons."; + grid.className = "emoticon-grid ffz-no-emotes center"; } parent.appendChild(grid); diff --git a/src/ui/menu_button.js b/src/ui/menu_button.js index 1aa67c31..b1aa3d7e 100644 --- a/src/ui/menu_button.js +++ b/src/ui/menu_button.js @@ -18,7 +18,7 @@ FFZ.prototype.build_ui_link = function(view) { FFZ.prototype.update_ui_link = function(link) { - var controller = App.__container__.lookup('controller:chat'); + var controller = window.App && App.__container__.lookup('controller:chat'); link = link || document.querySelector('a.ffz-ui-toggle'); if ( !link || !controller ) return; diff --git a/src/ui/my_emotes.js b/src/ui/my_emotes.js index a346e45d..d83fc540 100644 --- a/src/ui/my_emotes.js +++ b/src/ui/my_emotes.js @@ -5,6 +5,12 @@ var FFZ = window.FrankerFaceZ, BANNED_SETS = {"00000turbo":true}, KNOWN_CODES = { + "#-?[\\\\/]": "#-/", + ":-?(?:7|L)": ":-7", + "\\<\\;\\]": "<]", + "\\:-?(S|s)": ":-S", + "\\:-?\\\\": ":-\\", + "\\:\\>\\;": ":>", "B-?\\)": "B-)", "\\:-?[z|Z|\\|]": ":-Z", "\\:-?\\)": ":-)", @@ -12,7 +18,7 @@ var FFZ = window.FrankerFaceZ, "\\:-?(p|P)": ":-P", "\\;-?(p|P)": ";-P", "\\<\\;3": "<3", - "\\:-?(?:\\/|\\\\)(?!\\/)": ":-/", + "\\:-?[\\\\/]": ":-/", "\\;-?\\)": ";-)", "R-?\\)": "R-)", "[o|O](_|\\.)[o|O]": "O.o", @@ -33,7 +39,7 @@ var FFZ = window.FrankerFaceZ, user_sets = user && ffz.users[user.login] && ffz.users[user.login].sets || []; // Remove the 'default' set. - set_ids = set_ids.split(",").removeObject("0") + set_ids = set_ids.split(",").removeObject("0"); if ( ffz.settings.global_emotes_in_menu ) { set_ids.push("0"); @@ -68,6 +74,8 @@ FFZ.prototype.setup_my_emotes = function() { } this._twitch_set_to_channel[0] = "twitch_global"; + this._twitch_set_to_channel[33] = "twitch_tfaces"; + this._twitch_set_to_channel[42] = "twitch_tfaces"; } @@ -151,7 +159,7 @@ FFZ.menu_pages.my_emotes = { return; } - if ( name == "turbo" ) { + if ( name == "turbo" || name == "twitch_tfaces" ) { set.channel = "Twitch Turbo"; set.badge = "//cdn.frankerfacez.com/script/turbo_badge.png"; return; @@ -191,7 +199,7 @@ FFZ.menu_pages.my_emotes = { if ( ! set.channel ) set.channel = name; dn(); - }.bind(this,set,name,dn), 2000); + }.bind(this,set,name,dn), 500); }.bind(this, set, lname, name))); }, handle_promises = function() { @@ -286,11 +294,16 @@ FFZ.menu_pages.my_emotes = { var an = a[1].toLowerCase(), bn = b[1].toLowerCase(); - if ( an === "twitch turbo" || an === "global emoticons" ) - an = "zzz" + an; + if ( an === "twitch turbo" || an === "twitch_tfaces" ) + an = "zza|" + an; + + else if ( an === "global emoticons" ) + an = "zzz|" + an; - if ( bn === "twitch turbo" || bn === "global emoticons" ) - bn = "zzz" + bn; + if ( bn === "twitch turbo" || bn === "twitch_tfaces" ) + bn = "zza|" + bn; + else if ( bn === "global emoticons" ) + bn = "zzz|" + bn; if ( an < bn ) return -1; else if ( an > bn ) return 1; @@ -325,7 +338,7 @@ FFZ.menu_pages.my_emotes = { } else { ems = set.emotes; - title = FFZ.get_capitalization(set.channel); + title = set.channel == "Twitch Turbo" ? set.channel : FFZ.get_capitalization(set.channel); badge = set.badge; } diff --git a/src/ui/notifications.js b/src/ui/notifications.js index 0ef99fbf..bd61547a 100644 --- a/src/ui/notifications.js +++ b/src/ui/notifications.js @@ -20,7 +20,8 @@ FFZ.settings_info.highlight_notifications = { value: false, category: "Chat", - visible: function() { return ! this.has_bttv }, + no_bttv: true, + //visible: function() { return ! this.has_bttv }, name: "Highlight Notifications", help: "Display notifications when a highlighted word appears in chat in an unfocused tab.", diff --git a/src/ui/races.js b/src/ui/races.js index 87b9a6bb..4081e2b5 100644 --- a/src/ui/races.js +++ b/src/ui/races.js @@ -187,14 +187,14 @@ FFZ.prototype.build_race_popup = function() { out += '
'; out += '
#Entrant Time
'; out += '
'; - - out += ''; - + + out += ''; + out += '

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

'; popup.innerHTML = out; container.appendChild(popup); @@ -284,7 +284,7 @@ FFZ.prototype._update_race = function(not_timer) { 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; diff --git a/src/ui/viewer_count.js b/src/ui/viewer_count.js index c4e624dc..8bfb7fcc 100644 --- a/src/ui/viewer_count.js +++ b/src/ui/viewer_count.js @@ -9,28 +9,30 @@ var FFZ = window.FrankerFaceZ, FFZ.ws_commands.viewers = function(data) { var channel = data[0], count = data[1]; - var controller = App.__container__.lookup('controller:channel'), - id = controller && controller.get && controller.get('id'); + 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 ( id !== channel ) return; - var view_count = document.querySelector('.channel-stats .ffz.stat'), + var view_count = document.querySelector('#ffz-viewer-display'), content = constants.ZREKNARF + ' ' + utils.number_commas(count); if ( view_count ) view_count.innerHTML = content; else { - var parent = document.querySelector('.channel-stats'); + var parent = document.querySelector(this.is_dashboard ? "#stats" : '.stats-and-actions .channel-stats'); if ( ! parent ) return; view_count = document.createElement('span'); + view_count.id = "ffz-viewer-display"; view_count.className = 'ffz stat'; - view_count.title = 'Viewers with FrankerFaceZ'; + view_count.title = 'Chatters with FrankerFaceZ'; view_count.innerHTML = content; parent.appendChild(view_count); - jQuery(view_count).tipsy(); + jQuery(view_count).tipsy(this.is_dashboard ? {"gravity":"s"} : undefined); } } \ No newline at end of file diff --git a/src/utils.js b/src/utils.js index fe793ac6..0f2ee76d 100644 --- a/src/utils.js +++ b/src/utils.js @@ -117,13 +117,27 @@ module.exports = { return m; }, - time_to_string: function(elapsed) { + date_string: function(date) { + return date.getFullYear() + "-" + (date.getMonth()+1) + "-" + date.getDate(); + }, + + time_to_string: function(elapsed, separate_days, days_only) { var seconds = elapsed % 60, minutes = Math.floor(elapsed / 60), - hours = Math.floor(minutes / 60); + hours = Math.floor(minutes / 60), + days = ""; minutes = minutes % 60; - return (hours < 10 ? "0" : "") + hours + ":" + (minutes < 10 ? "0" : "") + minutes + ":" + (seconds < 10 ? "0" : "") + seconds; + 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 + (hours < 10 ? "0" : "") + hours + ":" + (minutes < 10 ? "0" : "") + minutes + ":" + (seconds < 10 ? "0" : "") + seconds; } } \ No newline at end of file diff --git a/style.css b/style.css index 14b166ab..f6cd1f69 100644 --- a/style.css +++ b/style.css @@ -1,3 +1,9 @@ +.ffz-flip { + -ms-transform: rotate(180deg); + -webkit-transform: rotate(180deg); + transform: rotate(180deg); +} + .ffz-ui-toggle { display: block; position: absolute; @@ -6,9 +12,13 @@ cursor: pointer; } -.emoticon-selector-toggle + script + .ffz-ui-toggle svg { height: 14px; width: 18px; } +.ffz-menu-replace .emoticon-selector-toggle { + display: none !important; +} -.emoticon-selector-toggle + script + .ffz-ui-toggle { +body:not(.ffz-menu-replace) .emoticon-selector-toggle + script + .ffz-ui-toggle svg { height: 14px; width: 18px; } + +body:not(.ffz-menu-replace) .emoticon-selector-toggle + script + .ffz-ui-toggle { height: 14px; width: 18px; top: 28px; } @@ -170,6 +180,32 @@ /* Theater Mode hover bar */ +.app-main.theatre #channel .player-column:focus #broadcast-meta, +.app-main.theatre #channel .player-column:hover #broadcast-meta { + background-color: #19191f; + color: #aaa; + + position: absolute; + top: -25px; + left: 120px; + right: 10px; + z-index: 7; + opacity: 0.95; + height: 20px; +} + +.app-main.theatre #channel .player-column #broadcast-meta .info { padding-left: 5px; } +.app-main.theatre #channel .player-column #broadcast-meta .info .title { + font-size: 12px; + line-height: 20px; +} + +.app-main.theatre #channel .player-column #broadcast-meta .info .channel, +.app-main.theatre #channel .player-column #broadcast-meta .info .edit-link, +.app-main.theatre #broadcast-meta .profile-link { + display: none; +} + .app-main.theatre .player-column:focus .stats-and-actions, .app-main.theatre .player-column:hover .stats-and-actions { background-color: #19191f; @@ -342,23 +378,31 @@ } } +.ffz-ui-menu-page { overflow-y: auto; } -.ffz-ui-menu-page { - overflow-y: auto; - padding: 0 20px; - margin: -20px -20px 0; -} +.ffz-ui-menu-page[data-page="about"], +.ffz-ui-menu-page .chat-menu-content p { padding: 0 20px; } .chat-menu.ffz-ui-popup .ffz-ui-menu-page .chat-menu-content .heading, .chat-menu.ffz-ui-popup .ffz-ui-menu-page .emoticon-grid .heading { - margin-bottom: 5px; - border-bottom: 1px solid rgba(0,0,0,0.2); + padding: 10px 20px; + border-top: 1px solid rgba(0,0,0,0.2); text-align: left; } .chat-menu.ffz-ui-popup .ffz-ui-menu-page .emoticon-grid .heading { - padding-left: 23px; + padding-left: 43px; background-repeat: no-repeat; + background-position: 20px 10px; +} + +.chat-menu.ffz-ui-popup .ffz-ui-menu-page .emoticon-grid + .emoticon-grid { padding-top: 0; } + +.chat-menu.ffz-ui-popup .ffz-ui-menu-page .chat-menu-content:first-of-type .heading, +.chat-menu.ffz-ui-popup .ffz-ui-menu-page .emoticon-grid:first-of-type .heading { + border-top: none; + padding-top: 0; + background-position-y: 0; } .chat-menu.ffz-ui-popup .ffz-ui-menu-page .chat-menu-content { @@ -375,17 +419,27 @@ opacity: 0.75; } -.ffz-ui-menu-page span.help, .ffz-ui-menu-page p.option a { +.ffz-ui-menu-page p.disabled span.switch-label, +.ffz-ui-menu-page span.help, +.ffz-ui-menu-page p.option a { margin-left: 50px; } .ffz-ui-popup ul.menu { list-style-type: none; - margin: 0 -20px; border-top: 1px solid rgba(0,0,0,0.2); background-color: #eee; } +.ffz-ui-popup .emoticon-selector-box { + width: 10000px !important; /* Max-width has our back */ + max-width: 300px; +} + +.ember-chat .chat-interface .ffz-ui-popup.emoticon-selector .emoticon-selector-box .emoticon-grid { background-color: transparent; } + +.app-main.theatre .ffz-ui-popup ul.menu, +.chat-container.dark .ffz-ui-popup ul.menu, .ffz-ui-popup.dark ul.menu { background-color: #282828; } @@ -448,6 +502,40 @@ vertical-align: top; } +/* BTTV Menu Fixes */ + +.ffz-ui-popup.dark .emoticon-grid .heading, +.ffz-ui-popup.dark li.title { color: #fff; } +.ffz-ui-popup.dark .ffz-ui-menu-page { background-color: #1e1e1e; } + + +/* Menu Scrollbar */ + +#ffz-race-popup .table::-webkit-scrollbar, +.emoticon-selector-box .all-emotes::-webkit-scrollbar, +.ffz-ui-menu-page::-webkit-scrollbar { + width: 6px; +} + +#ffz-race-popup .table::-webkit-scrollbar-thumb, +.emoticon-selector-box .all-emotes::-webkit-scrollbar-thumb, +.ffz-ui-menu-page::-webkit-scrollbar-thumb { + border-radius: 7px; + background: rgba(0,0,0,0.7); + box-shadow: 0 0 1px 1px rgba(255,255,255,0.25); +} + +.ffz-dark .table::-webkit-scrollbar-thumb, +.ember-chat-container.dark .emoticon-selector-box .all-emotes::-webkit-scrollbar-thumb, +.chat-container.dark .emoticon-selector-box .all-emotes::-webkit-scrollbar-thumb, +.app-main.theatre .emoticon-selector-box .all-emotes::-webkit-scrollbar-thumb, +.ember-chat-container.dark .ffz-ui-menu-page::-webkit-scrollbar-thumb, +.chat-container.dark .ffz-ui-menu-page::-webkit-scrollbar-thumb, +.app-main.theatre .ffz-ui-menu-page::-webkit-scrollbar-thumb { + background: rgba(255,255,255,0.6); + box-shadow: 0 0 1px 1px rgba(0,0,0,0.25); +} + /* Chat Mentions */ .ember-chat .mentioned:empty, @@ -478,7 +566,8 @@ /* Fix Moderation Cards */ .ember-chat .ffz-moderation-card { - box-shadow: #808080 0 0 5px; + border: 2px solid #cbcbcb; + /*box-shadow: #808080 0 0 5px;*/ } .ember-chat .ffz-moderation-card button { @@ -503,19 +592,8 @@ .ember-chat .ffz-moderation-card:focus { outline: none; - box-shadow: #000 0 0 5px; -} - -.ember-chat-container.dark .ember-chat .ffz-moderation-card:focus, -.chat-container.dark .ember-chat .ffz-moderation-card:focus, -.app-main.theatre .ember-chat .ffz-moderation-card:focus { - box-shadow: #fff 0 0 5px; -} - -.ember-chat-container.dark .ember-chat .ffz-moderation-card .interface, -.chat-container.dark .ember-chat .ffz-moderation-card .interface, -.app-main.theatre .ember-chat .ffz-moderation-card .interface { - background-color: #232329; + border-color: #444; + /*box-shadow: #000 0 0 5px;*/ } .ember-chat .ffz-moderation-card .interface:not(:last-of-type) { @@ -659,4 +737,20 @@ .button.ffz-donate:not(.disabled):hover { background: #08c43d; +} + +/* Dumb Fixes */ + +.dropmenu, .ui-menu, .ui-multiselect-menu { + margin-bottom: 0; +} + +/* Unsafe Links */ + +a.unsafe-link { + color: #a64141 !important; +} + +.chat-container.dark .chat-line a.unsafe-link { + color: #d28e8e !important; } \ No newline at end of file