diff --git a/dark.css b/dark.css index 7c5c99f2..e538228b 100644 --- a/dark.css +++ b/dark.css @@ -83,6 +83,12 @@ background-color: #232329; } +.ffz-no-blue .ember-chat-container.dark .ember-chat .moderation-card .interface, +.ffz-no-blue .chat-container.dark .ember-chat .moderation-card .interface, +.ffz-no-blue .app-main.theatre .ember-chat .moderation-card .interface { + background-color: #232323; +} + .moderation-card h3.name a { color: #fff !important; } @@ -141,6 +147,14 @@ color: #fff; } +.ffz-dark.ffz-no-blue #right_col, +.ffz-dark.ffz-no-blue .no-login-contain li, +.ffz-dark.ffz-no-blue .following-col .following-list .load-more span, +.ffz-dark.ffz-no-blue .viewall a, +.ffz-dark.ffz-no-blue #new-user-prompt { + background-color: rgb(25,25,25); +} + /* Popups */ @@ -224,6 +238,8 @@ color: #a68ed2; } +.ffz-dark .exit-theatre > a { color: #000 !important; } + .ffz-dark .follow-button a, .ffz-dark a.dropmenu_action:hover { color: #fff !important; @@ -272,6 +288,11 @@ background-color: rgba(25,25,31, 0.7); } +.ffz-dark.ffz-no-blue .bookmark-meta, +.ffz-dark.ffz-no-blue .upsell-banner { + background-color: rgba(25,25,25, 0.7); +} + .ffz-dark .bookmark-meta .bookmark-title, .ffz-dark .upsell-banner .message .title { color: #ccc; @@ -375,6 +396,11 @@ background-color: rgb(35,35,41); } +.ffz-dark.ffz-no-blue .following-col .following-list .load-more:hover span, +.ffz-dark.ffz-no-blue .viewall a:hover { + background-color: rgb(35,35,35); +} + .ffz-dark .viewall a .text span:first-child { color: #a68ed2; } diff --git a/script.js b/script.js index 13415730..254642d3 100644 --- a/script.js +++ b/script.js @@ -12,7 +12,7 @@ FFZ.settings_info.show_badges = { type: "boolean", value: true, - category: "Chat", + category: "Chat Appearance", name: "Additional Badges", help: "Show additional badges for bots, FrankerFaceZ donors, and other special users." }; @@ -22,7 +22,7 @@ FFZ.settings_info.transparent_badges = { type: "boolean", value: false, - category: "Chat", + category: "Chat Appearance", no_bttv: true, name: "Transparent Badges", @@ -368,7 +368,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":31}],2:[function(require,module,exports){ +},{"./constants":3,"./utils":32}],2:[function(require,module,exports){ var FFZ = window.FrankerFaceZ; @@ -442,6 +442,14 @@ FFZ.ffz_commands.massmod = function(room, args) { } FFZ.ffz_commands.massmod.help = "Usage: /ffz massmod \nBroadcaster only. Mod all the users in the provided list."; + + +/*FFZ.ffz_commands.massunban = function(room, args) { + args = args.join(" ").trim(); + + + +}*/ },{}],3:[function(require,module,exports){ var SVGPATH = '', DEBUG = localStorage.ffzDebugMode == "true" && document.body.classList.contains('ffz-dev'), @@ -451,6 +459,7 @@ module.exports = { DEBUG: DEBUG, SERVER: SERVER, API_SERVER: "//api.frankerfacez.com/", + API_SERVER_2: "//direct-api.frankerfacez.com/", KNOWN_CODES: { "#-?[\\\\/]": "#-/", @@ -508,7 +517,8 @@ module.exports = { GEAR: '', HEART: '', EMOTE: '', - STAR: '' + STAR: '', + CLOSE: '' } },{}],4:[function(require,module,exports){ var FFZ = window.FrankerFaceZ; @@ -603,6 +613,26 @@ FFZ.prototype.setup_channel = function() { view.ffzInit(); }; + + this.log("Hooking the Ember Channel model."); + Channel = App.__container__.resolve('model:channel'); + if ( ! Channel ) + return; + + Channel.reopen({ + ffz_host_target: undefined, + + setHostMode: function(e) { + if ( f.settings.hosted_channels ) { + this.set('ffz_host_target', e.target); + return this._super(e); + } else { + this.set('ffz_host_target', undefined); + return this._super({target: void 0, delay: 0}); + } + } + }); + this.log("Hooking the Ember Channel controller."); @@ -700,6 +730,7 @@ FFZ.prototype._modify_cindex = function(view) { this.ffzFixTitle(); this.ffzUpdateUptime(); this.ffzUpdateChatters(); + this.ffzUpdateHostButton(); var views = this.get('element').querySelector('.svg-glyph_views:not(.ffz-svg)') if ( views ) @@ -731,6 +762,109 @@ FFZ.prototype._modify_cindex = function(view) { }, + ffzUpdateHostButton: function() { + var channel_id = this.get('controller.id'), + hosted_id = this.get('controller.hostModeTarget.id'), + + user = f.get_user(), + room = user && f.rooms && f.rooms[user.login] && f.rooms[user.login].room, + now_hosting = room && room.ffz_host_target, + hosts_left = room && room.ffz_hosts_left, + + el = this.get('element'); + + this.set('ffz_host_updating', false); + + if ( channel_id ) { + var container = el && el.querySelector('.stats-and-actions .channel-actions'), + btn = container && container.querySelector('#ffz-ui-host-button'); + + if ( ! container || ! f.settings.stream_host_button || ! user || user.login === channel_id ) { + if ( btn ) + btn.parentElement.removeChild(btn); + } else { + if ( ! btn ) { + btn = document.createElement('span'); + btn.id = 'ffz-ui-host-button'; + btn.className = 'button action tooltip'; + + btn.addEventListener('click', this.ffzClickHost.bind(btn, this, false)); + + var before = container.querySelector(':scope > .theatre-button'); + if ( before ) + container.insertBefore(btn, before); + else + container.appendChild(btn); + } + + btn.classList.remove('disabled'); + btn.innerHTML = channel_id === now_hosting ? 'Unhost' : 'Host'; + if ( now_hosting ) + btn.title = 'You are now hosting ' + utils.sanitize(FFZ.get_capitalization(now_hosting)) + '.'; + else + btn.title = 'You are not hosting any channel.'; + + if ( typeof hosts_left === "number" ) + btn.title += ' You have ' + hosts_left + ' host command' + utils.pluralize(hosts_left) + ' remaining this half hour.'; + } + } + + + if ( hosted_id ) { + var container = el && el.querySelector('#hostmode .channel-actions'), + btn = container && container.querySelector('#ffz-ui-host-button'); + + if ( ! container || ! f.settings.stream_host_button || ! user || user.login === hosted_id ) { + if ( btn ) + btn.parentElement.removeChild(btn); + } else { + if ( ! btn ) { + btn = document.createElement('span'); + btn.id = 'ffz-ui-host-button'; + btn.className = 'button action tooltip'; + + btn.addEventListener('click', this.ffzClickHost.bind(btn, this, true)); + + var before = container.querySelector(':scope > .theatre-button'); + if ( before ) + container.insertBefore(btn, before); + else + container.appendChild(btn); + } + + btn.classList.remove('disabled'); + btn.innerHTML = hosted_id === now_hosting ? 'Unhost' : 'Host'; + if ( now_hosting ) + btn.title = 'You are currently hosting ' + utils.sanitize(FFZ.get_capitalization(now_hosting)) + '. Click to ' + (hosted_id === now_hosting ? 'unhost' : 'host') + ' this channel.'; + else + btn.title = 'You are not currently hosting any channel. Click to host this channel.'; + + if ( typeof hosts_left === "number" ) + btn.title += ' You have ' + hosts_left + ' host command' + utils.pluralize(hosts_left) + ' remaining this half hour.'; + } + } + }, + + ffzClickHost: function(controller, is_host) { + var target = controller.get(is_host ? 'controller.hostModeTarget.id' : 'controller.id'), + user = f.get_user(), + room = user && f.rooms && f.rooms[user.login] && f.rooms[user.login].room, + now_hosting = room && room.ffz_host_target; + + if ( ! room || controller.get('ffz_host_updating') ) + return; + + this.classList.add('disabled'); + this.title = 'Updating...'; + + controller.set('ffz_host_updating', true); + if ( now_hosting === target ) + room.send("/unhost"); + else + room.send("/host " + target); + }, + + ffzUpdateChatters: function() { // Get the counts. var room_id = this.get('controller.id'), @@ -926,6 +1060,46 @@ FFZ.settings_info.channel_views = { }; +FFZ.settings_info.hosted_channels = { + type: "boolean", + value: true, + + category: "Channel Metadata", + name: "Channel Hosting", + help: "Display other channels that have been featured by the current channel.", + on_update: function(val) { + var cb = document.querySelector('input.ffz-setting-hosted-channels'); + if ( cb ) + cb.checked = val; + + if ( ! this._cindex ) + return; + + var chan = this._cindex.get('controller.model'), + room = chan && this.rooms && this.rooms[chan.get('id')], + target = room && room.room && room.room.get('ffz_host_target'); + if ( ! chan || ! room ) + return; + + chan.setHostMode({target: target, delay: 0}); + } + }; + + +FFZ.settings_info.stream_host_button = { + type: "boolean", + value: true, + + category: "Channel Metadata", + name: "Host This Channel Button", + help: "Display a button underneath streams that make it easy to host them with your own channel.", + on_update: function(val) { + if ( this._cindex ) + this._cindex.ffzUpdateHostButton(); + } + }; + + FFZ.settings_info.stream_uptime = { type: "boolean", value: false, @@ -953,7 +1127,7 @@ FFZ.settings_info.stream_title = { this._cindex.ffzFixTitle(); } }; -},{"../constants":3,"../utils":31}],6:[function(require,module,exports){ +},{"../constants":3,"../utils":32}],6:[function(require,module,exports){ var FFZ = window.FrankerFaceZ, utils = require("../utils"), constants = require("../constants"), @@ -969,7 +1143,8 @@ var FFZ = window.FrankerFaceZ, RIGHT: 39, DOWN: 40, TWO: 50, - COLON: 186 + COLON: 59, + FAKE_COLON: 186 }, selection_start = function(e) { @@ -1030,7 +1205,7 @@ FFZ.settings_info.input_emoji = { value: false, category: "Chat Input", - visible: false, + //visible: false, no_bttv: true, name: "Enter Emoji By Name", @@ -1121,10 +1296,12 @@ FFZ.prototype._modify_chat_input = function(component) { switch(key) { case KEYCODES.UP: case KEYCODES.DOWN: - if ( this.get("isShowingSuggestions") ) + if ( e.shiftKey || e.shiftLeft || e.ctrlKey || e.metaKey ) + return; + else if ( this.get("isShowingSuggestions") ) e.preventDefault(); else if ( f.settings.input_mru ) - Ember.run.next(this.ffzCycleMRU.bind(this, key)); + Ember.run.next(this.ffzCycleMRU.bind(this, key, selection_start(this.get("chatTextArea")))); else return this._onKeyDown(event); break; @@ -1149,14 +1326,15 @@ FFZ.prototype._modify_chat_input = function(component) { break; case KEYCODES.COLON: - if ( false && f.settings.input_emoji && (e.shiftKey || e.shiftLeft) ) { + case KEYCODES.FAKE_COLON: + if ( f.settings.input_emoji && (e.shiftKey || e.shiftLeft) ) { var t = this, ind = selection_start(this.get("chatTextArea")); ind > 0 && Ember.run.next(function() { var text = t.get("textareaValue"), emoji_start = text.lastIndexOf(":", ind - 1); - + if ( emoji_start !== -1 && ind !== -1 && text.charAt(ind) === ":" ) { var match = text.substr(emoji_start + 1, ind-emoji_start - 1), emoji_id = f.emoji_names[match], @@ -1184,7 +1362,12 @@ FFZ.prototype._modify_chat_input = function(component) { } }, - ffzCycleMRU: function(key) { + ffzCycleMRU: function(key, start_ind) { + // We don't want to do this if the keys were just moving the cursor around. + var cur_pos = selection_start(this.get("chatTextArea")); + if ( start_ind !== cur_pos ) + return; + var ind = this.get('ffz_mru_index'), mru = this._parentView.get('context.model.mru_list') || []; @@ -1194,8 +1377,10 @@ FFZ.prototype._modify_chat_input = function(component) { ind = (ind + mru.length) % (mru.length + 1); var old_val = this.get('ffz_old_mru'); - if ( old_val === undefined ) - this.set('ffz_old_mru', this.get('textareaValue')); + if ( old_val === undefined || old_val === null ) { + old_val = this.get('textareaValue'); + this.set('ffz_old_mru', old_val); + } var new_val = mru[ind]; if ( new_val === undefined ) { @@ -1287,20 +1472,10 @@ FFZ.prototype._modify_chat_input = function(component) { }.property("ffz_emoticons", "ffz_chatters", "isSuggestionsTriggeredWithTab")*/ }); } -},{"../constants":3,"../utils":31}],7:[function(require,module,exports){ +},{"../constants":3,"../utils":32}],7:[function(require,module,exports){ var FFZ = window.FrankerFaceZ, utils = require('../utils'), - constants = require('../constants'), - - format_unread = function(count) { - if ( count < 1 ) - return ""; - - else if ( count >= 99 ) - return "99+"; - - return "" + count; - }; + constants = require('../constants'); // -------------------- @@ -1312,7 +1487,7 @@ FFZ.settings_info.swap_sidebars = { type: "boolean", value: false, - category: "Miscellaneous", + category: "Appearance", no_bttv: true, name: "Swap Sidebar Positions", @@ -1329,7 +1504,7 @@ FFZ.settings_info.minimal_chat = { type: "boolean", value: false, - category: "Chat", + category: "Chat Appearance", name: "Minimalistic Chat", help: "Hide all of the chat user interface, only showing messages and an input box.", @@ -1342,6 +1517,9 @@ FFZ.settings_info.minimal_chat = { f._roomv && f._roomv.get('stuckToBottom') && f._roomv._scrollToBottom(); },0); } + + if ( this._chatv && this._chatv.get('controller.showList') ) + this._chatv.set('controller.showList', false); } }; @@ -1352,7 +1530,7 @@ FFZ.settings_info.prevent_clear = { no_bttv: true, - category: "Chat Moderation", + category: "Chat Filtering", name: "Show Deleted Messages", help: "Fade deleted messages instead of replacing them, and prevent chat from being cleared.", @@ -1382,7 +1560,7 @@ FFZ.settings_info.chat_history = { value: true, visible: false, - category: "Chat", + category: "Chat Appearance", name: "Chat History Alpha", help: "Load previous chat messages when loading a chat room so you can see what people have been talking about. This currently only works in a handful of channels due to server capacity.", }; @@ -1393,7 +1571,7 @@ FFZ.settings_info.group_tabs = { no_bttv: true, - category: "Chat", + category: "Chat Moderation", name: "Chat Room Tabs Beta", help: "Enhanced UI for switching the current chat room and noticing new messages.", @@ -1411,14 +1589,13 @@ FFZ.settings_info.group_tabs = { FFZ.settings_info.pinned_rooms = { - type: "button", value: [], - - category: "Chat", visible: false, + }; - name: "Pinned Chat Rooms", - help: "Set a list of channels that should always be available in chat." +FFZ.settings_info.visible_rooms = { + value: [], + visible: false, }; @@ -1441,8 +1618,13 @@ FFZ.prototype.setup_chatview = function() { if ( Chat ) { Chat.reopen({ ffzUpdateChannels: function() { - if ( f.settings.group_tabs && f._chatv ) + if ( ! f._chatv ) + return; + + f._chatv.ffzRebuildMenu(); + if ( f.settings.group_tabs ) f._chatv.ffzRebuildTabs(); + }.observes("currentChannelRoom", "connectedPrivateGroupRooms"), removeCurrentChannelRoom: function() { @@ -1450,14 +1632,15 @@ FFZ.prototype.setup_chatview = function() { return this._super(); var room = this.get("currentChannelRoom"), - room_id = room && room.get('id'); + room_id = room && room.get('id'), + user = f.get_user(); if ( ! f.settings.pinned_rooms || f.settings.pinned_rooms.indexOf(room_id) === -1 ) { - // We can actually destroy it. if ( room === this.get("currentRoom") ) this.blurRoom(); - - if ( room ) + + // Don't destroy it if it's the user's room. + if ( room && user && user.login === room_id ) room.destroy(); } @@ -1562,8 +1745,10 @@ FFZ.prototype._modify_cview = function(view) { if ( !f.has_bttv && f.settings.group_tabs ) this.ffzEnableTabs(); + this.ffzRebuildMenu(); + setTimeout(function() { - if ( f.settings.group_tabs && f._chatv._ffz_tabs ) + if ( f.settings.group_tabs && f._chatv && f._chatv._ffz_tabs ) f._chatv.$('.chat-room').css('top', f._chatv._ffz_tabs.offsetHeight + "px"); var controller = f._chatv.get('controller'); @@ -1585,14 +1770,28 @@ FFZ.prototype._modify_cview = function(view) { try { f.update_ui_link(); - if ( !f.has_bttv && f.settings.group_tabs && this._ffz_tabs ) { - var room = this.get('controller.currentRoom'); - room && room.resetUnreadCount(); + var room = this.get('controller.currentRoom'), rows; + room && room.resetUnreadCount(); + if ( this._ffz_chan_table ) { + rows = jQuery(this._ffz_chan_table); + rows.children('.ffz-room-row').removeClass('active'); + if ( room ) + rows.children('.ffz-room-row[data-room="' + room.get('id') + '"]').addClass('active').children('span').text(''); + } + + if ( this._ffz_group_table ) { + rows = jQuery(this._ffz_group_table); + rows.children('.ffz-room-row').removeClass('active'); + if ( room ) + rows.children('.ffz-room-row[data-room="' + room.get('id') + '"]').addClass('active').children('span').text(''); + } + + if ( !f.has_bttv && f.settings.group_tabs && this._ffz_tabs ) { var tabs = jQuery(this._ffz_tabs); tabs.children('.ffz-chat-tab').removeClass('active'); if ( room ) - tabs.children('.ffz-chat-tab[data-room="' + room.get('id') + '"]').removeClass('tab-mentioned').addClass('active').children('span').text(''); + tabs.children('.ffz-chat-tab[data-room="' + room.get('id') + '"]').removeClass('tab-mentioned').removeClass('hidden').addClass('active').children('span').text(''); // Invite Link var can_invite = room && room.get('canInvite'); @@ -1608,6 +1807,214 @@ FFZ.prototype._modify_cview = function(view) { } }), + // Better Menu + + ffzRebuildMenu: function() { + return; + + var el = this.get('element'), + room_list = el && el.querySelector('.chat-rooms .tse-content'); + + if ( ! room_list ) + return; + + if ( ! room_list.classList.contains('ffz-room-list') ) { + room_list.classList.add('ffz-room-list'); + + // Find the Pending Invitations + var headers = room_list.querySelectorAll('.list-header'), + hdr = headers.length ? headers[headers.length-1] : undefined; + + if ( hdr ) { + hdr.classList.add('ffz'); + if ( hdr.nextSibling && hdr.nextSibling.classList ) + hdr.nextSibling.classList.add('ffz'); + } + } + + + // Channel Table + var t = this, + chan_table = this._ffz_chan_table || room_list.querySelector('#ffz-channel-table tbody'); + + if ( ! chan_table ) { + var tbl = document.createElement('table'); + tbl.setAttribute('cellspacing', 0); + tbl.id = 'ffz-channel-table'; + tbl.className = 'ffz'; + tbl.innerHTML = 'ChannelsJoinPin'; + room_list.insertBefore(tbl, room_list.firstChild); + + chan_table = this._ffz_chan_table = tbl.querySelector('tbody'); + } + + chan_table.innerHTML = ''; + + // Current Channel + var room = this.get('controller.currentChannelRoom'), row; + if ( room ) { + row = this.ffzBuildRow(this, room, true); + row && chan_table.appendChild(row); + } + + // Host Target + if ( this._ffz_host_room ) { + row = this.ffzBuildRow(this, this._ffz_host_room, false, true); + row && chan_table.appendChild(row); + } + + // Pinned Rooms + for(var i=0; i < f.settings.pinned_rooms.length; i++) { + var room_id = f.settings.pinned_rooms[i]; + if ( room && room.get('id') !== room_id && this._ffz_host !== room_id && f.rooms[room_id] && f.rooms[room_id].room ) { + row = this.ffzBuildRow(this, f.rooms[room_id].room); + row && chan_table.appendChild(row); + } + } + + + // Group Chat Table + var group_table = this._ffz_group_table || room_list.querySelector('#ffz-group-table tbody'); + if ( ! group_table ) { + var tbl = document.createElement('table'); + tbl.setAttribute('cellspacing', 0); + tbl.id = 'ffz-group-table'; + tbl.className = 'ffz'; + tbl.innerHTML = 'Group ChatsPin'; + + var before = room_list.querySelector('#ffz-channel-table'); + room_list.insertBefore(tbl, before.nextSibling); + + group_table = this._ffz_group_table = tbl.querySelector('tbody'); + } + + group_table.innerHTML = ''; + + _.each(this.get('controller.connectedPrivateGroupRooms'), function(room) { + var row = t.ffzBuildRow(t, room); + row && group_table && group_table.appendChild(row); + }); + + + // Change Create Tooltip + var create_btn = el.querySelector('.button.create'); + if ( create_btn ) + create_btn.title = 'Create a Group Room'; + }, + + ffzBuildRow: function(view, room, current_channel, host_channel) { + var row = document.createElement('tr'), + icon = document.createElement('td'), + name_el = document.createElement('td'), + + btn, + toggle_pinned = document.createElement('td'), + toggle_visible = document.createElement('td'), + + group = room.get('isGroupRoom'), + current = room === view.get('controller.currentRoom'), + //unread = format_unread(current ? 0 : room.get('unreadCount')), + + name = room.get('tmiRoom.displayName') || (group ? room.get('tmiRoom.name') : FFZ.get_capitalization(room.get('id'), function(name) { + f.log("Name for Row: " + name); + //unread = format_unread(current ? 0 : room.get('unreadCount')); + name_el.innerHTML = utils.sanitize(name); + })); + + name_el.className = 'ffz-room'; + name_el.innerHTML = utils.sanitize(name); + + if ( current_channel ) { + icon.innerHTML = constants.CAMERA; + icon.title = name_el.title = "Current Channel"; + icon.className = name_el.className = 'tooltip'; + } else if ( host_channel ) { + icon.innerHTML = constants.EYE; + icon.title = name_el.title = "Hosted Channel"; + icon.className = name_el.className = 'tooltip'; + } + + toggle_pinned.className = toggle_visible.className = 'ffz-row-switch'; + + toggle_pinned.innerHTML = ''; + toggle_visible.innerHTML = ''; + + row.setAttribute('data-room', room.get('id')); + + row.className = 'ffz-room-row'; + row.classList.toggle('current-channel', current_channel); + row.classList.toggle('host-channel', host_channel); + row.classList.toggle('group-chat', group); + row.classList.toggle('active', current); + + row.appendChild(icon); + row.appendChild(name_el); + + if ( ! group ) { + row.appendChild(toggle_pinned); + btn = toggle_pinned.querySelector('a.switch'); + btn.addEventListener('click', function(e) { + e.preventDefault(); + e.stopPropagation && e.stopPropagation(); + + var room_id = room.get('id'), + is_pinned = f.settings.pinned_rooms.indexOf(room_id) !== -1; + + if ( is_pinned ) + f._leave_room(room_id); + else + f._join_room(room_id); + + this.classList.toggle('active', !is_pinned); + }); + } else { + btn = document.createElement('a'); + btn.className = 'leave-chat tooltip'; + btn.innerHTML = constants.CLOSE; + btn.title = 'Leave Group'; + + name_el.appendChild(btn); + + btn.addEventListener('click', function(e) { + e.preventDefault(); + e.stopPropagation && e.stopPropagation(); + + if ( ! confirm('Are you sure you want to leave the group room "' + name + '"?') ) + return; + + room.get('isGroupRoom') && room.del(); + }); + } + + row.appendChild(toggle_visible); + btn = toggle_visible.querySelector('a.switch'); + btn.addEventListener('click', function(e) { + e.preventDefault(); + e.stopPropagation && e.stopPropagation(); + + var room_id = room.get('id'), + visible_rooms = f.settings.visible_rooms, + is_visible = visible_rooms.indexOf(room_id) !== -1; + + if ( is_visible ) + visible_rooms.removeObject(room_id); + else + visible_rooms.push(room_id); + + f.settings.set('visible_rooms', visible_rooms); + this.classList.toggle('active', !is_visible); + view.ffzRebuildTabs(); + }); + + row.addEventListener('click', function() { + var controller = view.get('controller'); + controller.focusRoom(room); + controller.set('showList', false); + }); + + return row; + }, + // Group Tabs~! ffzEnableTabs: function() { @@ -1723,6 +2130,8 @@ FFZ.prototype._modify_cview = function(view) { }, ffzTabUnread: function(room_id) { + // TODO: Update menu. + if ( f.has_bttv || ! f.settings.group_tabs ) return; @@ -1736,7 +2145,7 @@ FFZ.prototype._modify_cview = function(view) { room = f.rooms && f.rooms[room_id]; if ( tab && room ) { - var unread = format_unread(room_id === current_id ? 0 : room.room.get('unreadCount')); + var unread = utils.format_unread(room_id === current_id ? 0 : room.room.get('unreadCount')); tab.querySelector('span').innerHTML = unread; } @@ -1753,7 +2162,7 @@ FFZ.prototype._modify_cview = function(view) { if ( ! room ) continue; - var unread = format_unread(room_id === current_id ? 0 : room.room.get('unreadCount')); + var unread = utils.format_unread(room_id === current_id ? 0 : room.room.get('unreadCount')); tab.querySelector('span').innerHTML = unread; } @@ -1763,21 +2172,24 @@ FFZ.prototype._modify_cview = function(view) { ffzBuildTab: function(view, room, current_channel, host_channel) { var tab = document.createElement('span'), name, unread, icon = '', + room_id = room.get('id'), group = room.get('isGroupRoom'), - current = room === view.get('controller.currentRoom'); + current = room === view.get('controller.currentRoom'), + visible = current || f.settings.visible_rooms.indexOf(room_id) !== -1; tab.setAttribute('data-room', room.id); tab.className = 'ffz-chat-tab tooltip'; + //tab.classList.toggle('hidden', ! visible); tab.classList.toggle('current-channel', current_channel); tab.classList.toggle('host-channel', host_channel); tab.classList.toggle('group-chat', group); tab.classList.toggle('active', current); - unread = format_unread(current ? 0 : room.get('unreadCount')); + unread = utils.format_unread(current ? 0 : room.get('unreadCount')); name = room.get('tmiRoom.displayName') || (group ? room.get('tmiRoom.name') : FFZ.get_capitalization(room.get('id'), function(name) { - unread = format_unread(current ? 0 : room.get('unreadCount')); + unread = utils.format_unread(current ? 0 : room.get('unreadCount')); tab.innerHTML = icon + utils.sanitize(name) + '' + unread + ''; })); @@ -1795,7 +2207,9 @@ FFZ.prototype._modify_cview = function(view) { tab.innerHTML = icon + utils.sanitize(name) + '' + unread + ''; tab.addEventListener('click', function() { - view.get('controller').focusRoom(room); + var controller = view.get('controller'); + controller.focusRoom(room); + controller.set('showList', false); }); return tab; @@ -1832,14 +2246,28 @@ FFZ.prototype._modify_cview = function(view) { // ---------------------- FFZ.prototype.connect_extra_chat = function() { + var user = this.get_user(); + if ( user && user.login ) { + // Make sure we're in the user's room. + if ( ! this.rooms[user.login] || this.rooms[user.login].room ) { + var Room = App.__container__.resolve('model:room'), + r = Room && Room.findOne(user.login); + } + } + if ( this.has_bttv ) return; for(var i=0; i < this.settings.pinned_rooms.length; i++) this._join_room(this.settings.pinned_rooms[i], true); - if ( ! this.has_bttv && this._chatv && this.settings.group_tabs ) + if ( ! this._chatv ) + return; + + if ( ! this.has_bttv && this.settings.group_tabs ) this._chatv.ffzRebuildTabs(); + + this._chatv.ffzRebuildMenu(); } @@ -1852,8 +2280,11 @@ FFZ.prototype._join_room = function(room_id, no_rebuild) { } // Make sure we're not already there. - if ( this.rooms[room_id] && this.rooms[room_id].room ) + if ( this.rooms[room_id] && this.rooms[room_id].room ) { + if ( did_join && ! no_rebuild && ! this.has_bttv && this._chatv && this.settings.group_tabs ) + this._chatv.ffzRebuildTabs(); return did_join; + } // Okay, fine. Get it. var Room = App.__container__.resolve('model:room'), @@ -1863,6 +2294,9 @@ FFZ.prototype._join_room = function(room_id, no_rebuild) { if ( ! no_rebuild && ! this.has_bttv && this._chatv && this.settings.group_tabs ) this._chatv.ffzRebuildTabs(); + if ( ! no_rebuild && this._chatv ) + this._chatv.ffzRebuildMenu(); + return did_join; } @@ -1879,7 +2313,8 @@ FFZ.prototype._leave_room = function(room_id, no_rebuild) { return did_leave; var Chat = App.__container__.lookup('controller:chat'), - r = this.rooms[room_id].room; + r = this.rooms[room_id].room, + user = this.get_user(); if ( ! Chat || Chat.get('currentChannelRoom.id') === room_id || (this._chatv && this._chatv._ffz_host === room_id) ) return did_leave; @@ -1887,11 +2322,16 @@ FFZ.prototype._leave_room = function(room_id, no_rebuild) { if ( Chat.get('currentRoom.id') === room_id ) Chat.blurRoom(); - r.destroy(); + // Don't leave the user's room, but update the UI. + if ( ! user || user.login !== room_id ) + r.destroy(); if ( ! no_rebuild && ! this.has_bttv && this._chatv && this.settings.group_tabs ) this._chatv.ffzRebuildTabs(); + if ( ! no_rebuild && this._chatv ) + this._chatv.ffzRebuildMenu(); + return did_leave; } @@ -1930,9 +2370,10 @@ FFZ.chat_commands.part = function(room, args) { else return "You are not in " + room_id + "."; } -},{"../constants":3,"../utils":31}],8:[function(require,module,exports){ +},{"../constants":3,"../utils":32}],8:[function(require,module,exports){ var FFZ = window.FrankerFaceZ, utils = require("../utils"), + constants = require("../constants"), SEPARATORS = "[\\s`~<>!-#%-\\x2A,-/:;\\x3F@\\x5B-\\x5D_\\x7B}\\u00A1\\u00A7\\u00AB\\u00B6\\u00B7\\u00BB\\u00BF\\u037E\\u0387\\u055A-\\u055F\\u0589\\u058A\\u05BE\\u05C0\\u05C3\\u05C6\\u05F3\\u05F4\\u0609\\u060A\\u060C\\u060D\\u061B\\u061E\\u061F\\u066A-\\u066D\\u06D4\\u0700-\\u070D\\u07F7-\\u07F9\\u0830-\\u083E\\u085E\\u0964\\u0965\\u0970\\u0AF0\\u0DF4\\u0E4F\\u0E5A\\u0E5B\\u0F04-\\u0F12\\u0F14\\u0F3A-\\u0F3D\\u0F85\\u0FD0-\\u0FD4\\u0FD9\\u0FDA\\u104A-\\u104F\\u10FB\\u1360-\\u1368\\u1400\\u166D\\u166E\\u169B\\u169C\\u16EB-\\u16ED\\u1735\\u1736\\u17D4-\\u17D6\\u17D8-\\u17DA\\u1800-\\u180A\\u1944\\u1945\\u1A1E\\u1A1F\\u1AA0-\\u1AA6\\u1AA8-\\u1AAD\\u1B5A-\\u1B60\\u1BFC-\\u1BFF\\u1C3B-\\u1C3F\\u1C7E\\u1C7F\\u1CC0-\\u1CC7\\u1CD3\\u2010-\\u2027\\u2030-\\u2043\\u2045-\\u2051\\u2053-\\u205E\\u207D\\u207E\\u208D\\u208E\\u2329\\u232A\\u2768-\\u2775\\u27C5\\u27C6\\u27E6-\\u27EF\\u2983-\\u2998\\u29D8-\\u29DB\\u29FC\\u29FD\\u2CF9-\\u2CFC\\u2CFE\\u2CFF\\u2D70\\u2E00-\\u2E2E\\u2E30-\\u2E3B\\u3001-\\u3003\\u3008-\\u3011\\u3014-\\u301F\\u3030\\u303D\\u30A0\\u30FB\\uA4FE\\uA4FF\\uA60D-\\uA60F\\uA673\\uA67E\\uA6F2-\\uA6F7\\uA874-\\uA877\\uA8CE\\uA8CF\\uA8F8-\\uA8FA\\uA92E\\uA92F\\uA95F\\uA9C1-\\uA9CD\\uA9DE\\uA9DF\\uAA5C-\\uAA5F\\uAADE\\uAADF\\uAAF0\\uAAF1\\uABEB\\uFD3E\\uFD3F\\uFE10-\\uFE19\\uFE30-\\uFE52\\uFE54-\\uFE61\\uFE63\\uFE68\\uFE6A\\uFE6B\\uFF01-\\uFF03\\uFF05-\\uFF0A\\uFF0C-\\uFF0F\\uFF1A\\uFF1B\\uFF1F\\uFF20\\uFF3B-\\uFF3D\\uFF3F\\uFF5B\\uFF5D\\uFF5F-\\uFF65]", SPLITTER = new RegExp(SEPARATORS + "*," + SEPARATORS + "*"), @@ -2122,7 +2563,7 @@ FFZ.settings_info.room_status = { type: "boolean", value: true, - category: "Chat", + category: "Chat Appearance", no_bttv: true, name: "Room Status Indicators", @@ -2135,11 +2576,32 @@ FFZ.settings_info.room_status = { }; +FFZ.settings_info.line_purge_icon = { + type: "boolean", + value: false, + + no_bttv: true, + category: "Chat Moderation", + + name: "Purge Icon in Mod Icons", + help: "Display a Purge Icon in chat line Mod Icons for quickly purging users.", + + on_update: function(val) { + if ( this.has_bttv ) + return; + + document.body.classList.toggle("ffz-chat-purge-icon", val); + } + }; + + FFZ.settings_info.replace_bad_emotes = { type: "boolean", value: true, - category: "Chat", + category: "Chat Appearance", + no_bttv: true, + name: "Fix Low Quality Twitch Global Emoticons", help: "Replace emoticons such as DansGame and RedCoat with cleaned up versions that don't have pixels around the edges or white backgrounds for nicer display on dark chat." } @@ -2148,7 +2610,7 @@ FFZ.settings_info.parse_emoji = { type: "boolean", value: true, - category: "Chat", + category: "Chat Appearance", name: "Replace Emoji with Images", help: "Replace emoji in chat messages with nicer looking images from the open-source Twitter Emoji project." @@ -2159,7 +2621,7 @@ FFZ.settings_info.room_status = { type: "boolean", value: true, - category: "Chat", + category: "Chat Appearance", no_bttv: true, name: "Room Status Indicators", @@ -2176,7 +2638,7 @@ FFZ.settings_info.scrollback_length = { type: "button", value: 150, - category: "Chat", + category: "Chat Appearance", no_bttv: true, name: "Scrollback Length", @@ -2211,7 +2673,7 @@ FFZ.settings_info.banned_words = { type: "button", value: [], - category: "Chat", + category: "Chat Filtering", no_bttv: true, //visible: function() { return ! this.has_bttv }, @@ -2243,7 +2705,7 @@ FFZ.settings_info.keywords = { type: "button", value: [], - category: "Chat", + category: "Chat Filtering", no_bttv: true, //visible: function() { return ! this.has_bttv }, @@ -2276,19 +2738,13 @@ FFZ.settings_info.fix_color = { type: "boolean", value: true, - category: "Chat", + category: "Chat Appearance", 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.", - on_update: function(val) { - if ( this.has_bttv ) - return; - - document.body.classList.toggle("ffz-chat-colors", val); - } + on_update: function(val) { document.body.classList.toggle("ffz-chat-colors", !this.has_bttv && val); } }; @@ -2296,31 +2752,122 @@ FFZ.settings_info.link_info = { type: "boolean", value: true, - category: "Chat", + category: "Chat Appearance", 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.legacy_badges = { + type: "boolean", + value: false, + + category: "Chat Appearance", + + name: "Legacy Badges", + help: "Display the old, pre-vector chat badges from Twitch.", + + on_update: function(val) { document.body.classList.toggle("ffz-legacy-badges", val); } + }; + + FFZ.settings_info.chat_rows = { type: "boolean", value: false, - category: "Chat", + category: "Chat Appearance", no_bttv: true, - //visible: function() { return ! this.has_bttv }, name: "Chat Line Backgrounds", help: "Display alternating background colors for lines in chat.", - on_update: function(val) { - if ( this.has_bttv ) + on_update: function(val) { document.body.classList.toggle("ffz-chat-background", !this.has_bttv && val); } + }; + + +FFZ.settings_info.chat_separators = { + type: "boolean", + value: false, + + category: "Chat Appearance", + no_bttv: true, + + name: "Chat Line Separators", + help: "Display thin lines between chat messages for further visual separation.", + + on_update: function(val) { document.body.classList.toggle("ffz-chat-separator", !this.has_bttv && val); } + }; + +FFZ.settings_info.chat_padding = { + type: "boolean", + value: false, + + category: "Chat Appearance", + no_bttv: true, + + name: "Reduced Chat Line Padding", + help: "Reduce the amount of padding around chat messages to fit more on-screen at once.", + + on_update: function(val) { document.body.classList.toggle("ffz-chat-padding", !this.has_bttv && val); } + }; + + +FFZ.settings_info.high_contrast_chat = { + type: "boolean", + value: false, + + category: "Chat Appearance", + no_bttv: true, + + name: "High Contrast", + help: "Display chat using white and black for maximum contrast. This is suitable for capturing and chroma keying chat to display on stream.", + + on_update: function(val) { document.body.classList.toggle("ffz-high-contrast-chat", !this.has_bttv && val); } + }; + + +FFZ.settings_info.chat_font_size = { + type: "button", + value: 12, + + category: "Chat Appearance", + no_bttv: true, + + name: "Font Size", + help: "Make the chat font bigger or smaller.", + + method: function() { + var old_val = this.settings.chat_font_size, + new_val = prompt("Chat Font Size\n\nPlease enter a new size for the chat font. The default is 12.", old_val); + + if ( new_val === null || new_val === undefined ) return; - document.body.classList.toggle("ffz-chat-background", val); + var parsed = parseInt(new_val); + if ( parsed === NaN || parsed < 1 ) + parsed = 12; + + this.settings.set("chat_font_size", parsed); + }, + + on_update: function(val) { + if ( this.has_bttv || ! this._chat_style ) + return; + + var css; + if ( val === 12 ) + css = ""; + else { + var lh = Math.max(20, Math.round((20/12)*val)), + pd = Math.floor((lh - 20) / 2); + css = ".ember-chat .chat-messages .chat-line { font-size: " + val + "px !important; line-height: " + lh + "px !important; }"; + if ( pd ) + css += ".ember-chat .chat-messages .chat-line .mod-icons, .ember-chat .chat-messages .chat-line .badges { padding-top: " + pd + "px; }"; + } + + utils.update_css(this._chat_style, "chat_font_size", css); } }; @@ -2330,14 +2877,35 @@ FFZ.settings_info.chat_rows = { // --------------------- FFZ.prototype.setup_line = function() { + // Tipsy Handler + jQuery(document.body).on("mouseleave", ".tipsy", function() { + this.parentElement.removeChild(this); + }); + + + // Chat Style + var s = this._chat_style = document.createElement('style'); + s.id = "ffz-style-chat"; + s.type = 'text/css'; + document.head.appendChild(s); + + // Initial calculation. + FFZ.settings_info.chat_font_size.on_update.bind(this)(this.settings.chat_font_size); + + // Chat Enhancements document.body.classList.toggle("ffz-chat-colors", !this.has_bttv && this.settings.fix_color); + document.body.classList.toggle("ffz-legacy-badges", this.settings.legacy_badges); document.body.classList.toggle('ffz-chat-background', !this.has_bttv && this.settings.chat_rows); + document.body.classList.toggle("ffz-chat-separator", !this.has_bttv && this.settings.chat_separators); + document.body.classList.toggle("ffz-chat-padding", !this.has_bttv && this.settings.chat_padding); + document.body.classList.toggle("ffz-chat-purge-icon", !this.has_bttv && this.settings.line_purge_icon); + document.body.classList.toggle("ffz-high-contrast-chat", !this.has_bttv && this.settings.high_contrast_chat); this._colors = {}; this._last_row = {}; - var s = this._fix_color_style = document.createElement('style'); + s = this._fix_color_style = document.createElement('style'); s.id = "ffz-style-username-colors"; s.type = 'text/css'; document.head.appendChild(s); @@ -2347,13 +2915,13 @@ FFZ.prototype.setup_line = function() { this._twitch_emotes = {}; this._link_data = {}; - this.log("Hooking the Ember Whisper controller."); + this.log("Hooking the Ember Whisper Line component."); var Whisper = App.__container__.resolve('component:whisper-line'); if ( Whisper ) this._modify_line(Whisper); - this.log("Hooking the Ember Line controller."); + this.log("Hooking the Ember Message Line component."); var Line = App.__container__.resolve('component:message-line'); @@ -2369,6 +2937,7 @@ FFZ.prototype.setup_line = function() { FFZ.prototype._modify_line = function(component) { var f = this; + component.reopen({ tokenizedMessage: function() { // Add our own step to the tokenization procedure. @@ -2425,16 +2994,33 @@ FFZ.prototype._modify_line = function(component) { }), willClearRender: function() { - // This is here to prevent tipsy tooltips from hanging around. try { - this.$('a.mod-icon').tipsy('disable'); - jQuery('body > .tipsy:last').remove(); } catch(err) { f.error("LineView willClearRender: " + err); } this._super(); }, + + click: function(e) { + if ( e.target && e.target.classList.contains('mod-icon') ) { + jQuery(e.target).trigger('mouseout'); + + if ( e.target.classList.contains('purge') ) { + var i = this.get('msgObject.from'), + room_id = this.get('msgObject.room'), + room = room_id && f.rooms[room_id] && f.rooms[room_id].room; + + if ( room ) { + room.send("/timeout " + i + " 1"); + room.clearMessages(i); + } + return; + } + } + + return this._super(e); + }, didInsertElement: function() { this._super(); @@ -2481,6 +3067,27 @@ FFZ.prototype._modify_line = function(component) { this.$('.message').append(btn); } + + + // Hide Mod Buttons + if ( (this.get('isBroadcaster') && !(this.get('controller.parentController.model.isStaff') || this.get('controller.parentController.model.isAdmin'))) || (this.get('isModeratorOrHigher') && !(this.get('controller.parentController.model.isBroadcaster') || this.get('controller.parentController.model.isStaff') || this.get('controller.parentController.model.isAdmin'))) ) { + var mod_icons = el.querySelector('span.mod-icons'); + mod_icons && mod_icons.classList.add('hidden'); + } + + + // Purge Button + var timeout_btn = el.querySelector('span.mod-icons a.mod-icon.timeout'); + if ( timeout_btn ) { + var purge_btn = document.createElement('a'); + purge_btn.className = 'mod-icon float-left tooltip purge'; + purge_btn.innerHTML = 'Purge'; + purge_btn.title = 'Purge User (Timeout 1s)'; + purge_btn.href = '#'; + + timeout_btn.title = 'Timeout User (10m)'; + timeout_btn.parentElement.insertBefore(purge_btn, timeout_btn.nextSibling); + } // Badge @@ -2539,7 +3146,9 @@ FFZ.prototype._modify_line = function(component) { if ( id !== null ) { // High-DPI Images - img.setAttribute('srcset', build_srcset(id)); + if ( ! constants.EMOTE_REPLACEMENTS[id] ) + img.setAttribute('srcset', build_srcset(id)); + img.setAttribute('emote-id', id); // Source Lookup @@ -2741,9 +3350,10 @@ FFZ.prototype._emoticonize = function(component, tokens) { return this.tokenize_emotes(user_id, room_id, tokens); } -},{"../utils":31}],9:[function(require,module,exports){ +},{"../constants":3,"../utils":32}],9:[function(require,module,exports){ var FFZ = window.FrankerFaceZ, utils = require("../utils"), + constants = require("../constants"), helpers, keycodes = { @@ -2839,6 +3449,18 @@ FFZ.settings_info.mod_card_hotkeys = { }; +FFZ.settings_info.mod_card_info = { + type: "boolean", + value: false, + + no_bttv: true, + category: "Chat Moderation", + + name: "Moderation Card Additional Information", + help: "Display a channel's follower count, view count, and account age on moderation cards." + }; + + FFZ.settings_info.mod_card_history = { type: "boolean", value: false, @@ -2863,6 +3485,69 @@ FFZ.settings_info.mod_card_history = { }; +FFZ.settings_info.mod_card_buttons = { + type: "button", + value: [], + + category: "Chat Moderation", + no_bttv: true, + + name: "Moderation Card Additional Buttons", + help: "Add additional buttons to moderation cards for running chat commands on those users.", + + method: function() { + var old_val = ""; + for(var i=0; i < this.settings.mod_card_buttons.length; i++) { + var cmd = this.settings.mod_card_buttons[i]; + if ( cmd.indexOf(' ') !== -1 ) + old_val += ' "' + cmd + '"'; + else + old_val += ' ' + cmd; + } + + var new_val = prompt("Moderation Card Additional Buttons\n\nPlease enter a list of additional commands to display buttons for on moderation cards. Commands are separated by spaces. To include spaces in a command, surround the command with double quotes (\"). Use \"{user}\" to insert the user's username into the command, otherwise it will be appended to the end.\n\nExample: !permit \"!reg add {user}\"", old_val); + + if ( new_val === null || new_val === undefined ) + return; + + var vals = []; + new_val = new_val.trim(); + + while(new_val) { + if ( new_val.charAt(0) === '"' ) { + var end = new_val.indexOf('"', 1); + if ( end === -1 ) + end = new_val.length; + + var segment = new_val.substr(1, end - 1); + if ( segment ) + vals.push(segment); + + new_val = new_val.substr(end + 1); + + } else { + var ind = new_val.indexOf(' '); + if ( ind === -1 ) { + if ( new_val ) + vals.push(new_val); + + new_val = ''; + + } else { + var segment = new_val.substr(0, ind); + if ( segment ) + vals.push(segment); + + new_val = new_val.substr(ind + 1); + } + } + } + + this.settings.set("mod_card_buttons", vals); + } + }; + + FFZ.settings_info.mod_card_durations = { type: "button", value: [300, 600, 3600, 43200, 86400, 604800], @@ -2930,6 +3615,40 @@ FFZ.prototype.setup_mod_card = function() { this.rerender(); }.observes("cardInfo.isModeratorOrHigher", "cardInfo.user"), + ffzRebuildInfo: function() { + var el = this.get('element'), + info = el && el.querySelector('.info'); + if ( ! info ) + return; + + var out = '' + constants.EYE + ' ' + utils.number_commas(this.get('cardInfo.user.views') || 0) + '', + since = utils.parse_date(this.get('cardInfo.user.created_at') || ''), + followers = this.get('cardInfo.user.ffz_followers'); + + if ( typeof followers === "number" ) { + out += '' + constants.HEART + ' ' + utils.number_commas(followers || 0) + ''; + + } else if ( followers === undefined ) { + var t = this; + this.set('cardInfo.user.ffz_followers', false); + Twitch.api.get("channels/" + this.get('cardInfo.user.id') + '/follows', {limit:1}).done(function(data) { + t.set('cardInfo.user.ffz_followers', data._total); + t.ffzRebuildInfo(); + }).fail(function(data) { + t.set('cardInfo.user.ffz_followers', undefined); + }); + } + + if ( since ) { + var age = Math.floor((Date.now() - since.getTime()) / 1000); + if ( age > 0 ) { + out += '' + constants.CLOCK + ' ' + utils.human_time(age, 10) + ''; + } + } + + info.innerHTML = out; + }.observes("cardInfo.user.views"), + didInsertElement: function() { this._super(); window._card = this; @@ -2938,17 +3657,110 @@ FFZ.prototype.setup_mod_card = function() { return; var el = this.get('element'), - controller = this.get('controller'); + controller = this.get('controller'), + line; // Style it! - if ( f.settings.mod_card_hotkeys || (f.settings.mod_card_durations && f.settings.mod_card_durations.length) ) - el.classList.add('ffz-moderation-card'); + el.classList.add('ffz-moderation-card'); + + // Info-tize it! + if ( f.settings.mod_card_info ) { + var info = document.createElement('div'), + after = el.querySelector('h3.name'); + if ( after ) { + el.classList.add('ffz-has-info'); + info.className = 'info channel-stats'; + after.parentElement.insertBefore(info, after.nextSibling); + this.ffzRebuildInfo(); + } + } + + // Additional Buttons + if ( f.settings.mod_card_buttons && f.settings.mod_card_buttons.length ) { + line = document.createElement('div'); + line.className = 'extra-interface interface clearfix'; + + var cmds = {}, + add_btn_click = function(cmd) { + var user_id = controller.get('cardInfo.user.id'), + cont = App.__container__.lookup('controller:chat'), + room = cont && cont.get('currentRoom'); + + room && room.send(cmd.replace(/{user}/g, user_id)); + }, + + add_btn_make = function(cmd) { + var btn = document.createElement('button'), + segment = cmd.split(' ', 1)[0], + title = cmds[segment] > 1 ? cmd.split(' ', cmds[segment]) : [segment]; + + if ( /^[!~./]/.test(title[0]) ) + title[0] = title[0].substr(1); + + title = _.map(title, function(s){ return s.capitalize() }).join(' '); + + btn.className = 'button'; + btn.innerHTML = utils.sanitize(title); + btn.title = utils.sanitize(cmd.replace(/{user}/g, controller.get('cardInfo.user.id') || '{user}')); + + jQuery(btn).tipsy(); + btn.addEventListener('click', add_btn_click.bind(this, cmd)); + return btn; + }; + + var cmds = {}; + for(var i=0; i < f.settings.mod_card_buttons.length; i++) + cmds[f.settings.mod_card_buttons[i].split(' ',1)[0]] = (cmds[f.settings.mod_card_buttons[i].split(' ',1)[0]] || 0) + 1; + + for(var i=0; i < f.settings.mod_card_buttons.length; i++) { + var cmd = f.settings.mod_card_buttons[i], + ind = cmd.indexOf('{user}'); + + if ( ind === -1 ) + cmd += ' {user}'; + + line.appendChild(add_btn_make(cmd)) + } + + el.appendChild(line); + } + + + // Key Handling + el.setAttribute('tabindex', 1); + if ( f.settings.mod_card_hotkeys ) { + el.classList.add('no-mousetrap'); + + el.addEventListener('keyup', function(e) { + var key = e.keyCode || e.which, + user_id = controller.get('cardInfo.user.id'), + is_mod = controller.get('cardInfo.isModeratorOrHigher'), + room = App.__container__.lookup('controller:chat').get('currentRoom'); + + if ( is_mod && key == keycodes.P ) + room.send("/timeout " + user_id + " 1"); + + else if ( is_mod && key == keycodes.B ) + room.send("/ban " + user_id); + + else if ( is_mod && key == keycodes.T ) + room.send("/timeout " + user_id + " 600"); + + else if ( is_mod && key == keycodes.U ) + room.send("/unban " + user_id); + + else if ( key != keycodes.ESC ) + return; + + controller.send('close'); + }); + } + // Only do the big stuff if we're mod. if ( controller.get('cardInfo.isModeratorOrHigher') ) { el.classList.add('ffz-is-mod'); - el.setAttribute('tabindex', 1); - + // Key Handling if ( f.settings.mod_card_hotkeys ) { el.classList.add('no-mousetrap'); @@ -3006,8 +3818,8 @@ FFZ.prototype.setup_mod_card = function() { if ( f.settings.mod_card_durations && f.settings.mod_card_durations.length ) { // Extra Moderation - var line = document.createElement('div'); - line.className = 'interface clearfix'; + line = document.createElement('div'); + line.className = 'extra-interface interface clearfix'; line.appendChild(btn_make(1)); @@ -3053,14 +3865,27 @@ FFZ.prototype.setup_mod_card = function() { } - var msg_btn = el.querySelector(".interface > button"); - if ( msg_btn && msg_btn.classList.contains("message-button") ) { - msg_btn.innerHTML = MESSAGE; + var msg_btn = el.querySelector(".interface > button.message-button"); + if ( msg_btn ) { + msg_btn.innerHTML = 'W'; msg_btn.classList.add('glyph-only'); msg_btn.classList.add('message'); msg_btn.title = "Whisper User"; jQuery(msg_btn).tipsy(); + + + var real_msg = document.createElement('button'); + real_msg.className = 'message-button button glyph-only message'; + real_msg.innerHTML = MESSAGE; + real_msg.title = "Message User"; + jQuery(real_msg).tipsy(); + + real_msg.addEventListener('click', function() { + window.open('http://www.twitch.tv/message/compose?to=' + controller.get('cardInfo.user.id')); + }) + + msg_btn.parentElement.insertBefore(real_msg, msg_btn.nextSibling); } @@ -3198,7 +4023,7 @@ FFZ.chat_commands.u = function(room, args) { } FFZ.chat_commands.u.enabled = function() { return this.settings.short_commands; } -},{"../utils":31}],10:[function(require,module,exports){ +},{"../constants":3,"../utils":32}],10:[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*['"]([^'"]+)['"][^}]+(?:}|$)/, @@ -3372,7 +4197,7 @@ FFZ.prototype._modify_rview = function(view) { cont = el && el.querySelector('.chat-buttons-container'); if ( ! cont ) - return f.log("no container"); + return; var r9k_badge = cont.querySelector('#ffz-stat-r9k'), sub_badge = cont.querySelector('#ffz-stat-sub'), @@ -3455,8 +4280,13 @@ FFZ.prototype._modify_rview = function(view) { this._ffz_interval = setInterval(this.ffzPulse.bind(this), 200); this._ffz_messages = messages; + this._ffz_mouse_move = this.ffzMouseMove.bind(this); this._ffz_mouse_out = this.ffzMouseOut.bind(this); + this._ffz_mouse_down = this.ffzMouseDown.bind(this); + + this._$chatMessagesScroller.unbind('mousedown'); + this._$chatMessagesScroller.bind('mousedown', this._ffz_mouse_down); messages.addEventListener('mousemove', this._ffz_mouse_move); messages.addEventListener('mouseout', this._ffz_mouse_out); @@ -3505,6 +4335,14 @@ FFZ.prototype._modify_rview = function(view) { this._scrollToBottom(); }, + ffzMouseDown: function(event) { + var t = this._$chatMessagesScroller; + if ( ! this.ffz_frozen && t && t[0] && (event.which > 0 || "mousedown" === event.type || "mousewheel" === event.type) ) { + var r = t[0].scrollHeight - t[0].scrollTop - t[0].offsetHeight; + this._setStuckToBottom(10 >= r); + } + }, + ffzMouseOut: function(event) { this._ffz_outside = true; var e = this; @@ -3527,10 +4365,6 @@ FFZ.prototype._modify_rview = function(view) { if ( this.ffz_frozen ) return; - // Don't do it if we're over the bar itself. - if ( event.clientY >= (this._ffz_messages.getBoundingClientRect().bottom - 21) ) - return; - this.ffz_frozen = true; if ( this.get('stuckToBottom') ) { this.set('controller.model.messageBufferSize', f.settings.scrollback_length + 150); @@ -3552,6 +4386,8 @@ FFZ.prototype._modify_rview = function(view) { _setStuckToBottom: function(val) { this.set("stuckToBottom", val); this.get("controller.model") && this.set("controller.model.messageBufferSize", f.settings.scrollback_length + (val ? 0 : 150)); + if ( ! val ) + this.ffzUnfreeze(); }, // Warnings~! @@ -3937,7 +4773,7 @@ FFZ.prototype._insert_history = function(room_id, data) { FFZ.prototype.load_room = function(room_id, callback, tries) { var f = this; - jQuery.getJSON(constants.API_SERVER + "v1/room/" + room_id) + jQuery.getJSON(((tries||0)%2 === 0 ? constants.API_SERVER : constants.API_SERVER_2) + "v1/room/" + room_id) .done(function(data) { if ( data.sets ) { for(var key in data.sets) @@ -4015,7 +4851,9 @@ FFZ.prototype._modify_room = function(room) { var wait = this.get('slowWait') || 0; this.set('slowWait', value); if ( wait < 1 && value > 0 ) { - setTimeout(this.ffzUpdateWait.bind(this), 1000); + if ( this._ffz_wait_timer ) + clearTimeout(this._ffz_wait_timer); + this._ffz_wait_timer = setTimeout(this.ffzUpdateWait.bind(this), 1000); f._roomv && f._roomv.ffzUpdateStatus(); } else if ( (wait > 0 && value < 1) || was_banned ) { this.set('ffz_banned', false); @@ -4024,13 +4862,14 @@ FFZ.prototype._modify_room = function(room) { }, ffzUpdateWait: function() { + this._ffz_wait_timer = undefined; var wait = this.get('slowWait') || 0; if ( wait < 1 ) return; this.set('slowWait', --wait); if ( wait > 0 ) - setTimeout(this.ffzUpdateWait.bind(this), 1000); + this._ffz_wait_timer = setTimeout(this.ffzUpdateWait.bind(this), 1000); else { this.set('ffz_banned', false); f._roomv && f._roomv.ffzUpdateStatus(); @@ -4169,6 +5008,11 @@ FFZ.prototype._modify_room = function(room) { }, setHostMode: function(e) { + this.set('ffz_host_target', e && e.hostTarget || null); + var user = f.get_user(); + if ( user && f._cindex && this.get('id') === user.login ) + f._cindex.ffzUpdateHostButton(); + var Chat = App.__container__.lookup('controller:chat'); if ( ! Chat || Chat.get('currentChannelRoom') !== this ) return; @@ -4312,6 +5156,14 @@ FFZ.prototype._modify_room = function(room) { room.set('ffz_banned', true); f._roomv && f._roomv.ffzUpdateStatus(); } + + if ( msg.msgId === 'hosts_remaining' ) { + var match = /(\d+) host command/.exec(msg.message); + if ( match ) { + room.set('ffz_hosts_left', parseInt(match[1] || 0)); + f._cindex && f._cindex.ffzUpdateHostButton(); + } + } }); @@ -4350,6 +5202,9 @@ FFZ.prototype._modify_room = function(room) { // IT IS GLORIOUS! tmi.on('roomstate', function(state) { + if ( ! room ) + return; + if ( state.hasOwnProperty('slow') ) { room.set('slowMode', state.slow > 0); room.set('slowValue', state.slow); @@ -4401,7 +5256,7 @@ FFZ.prototype._modify_room = function(room) { }.observes('tmiRoom') }); } -},{"../constants":3,"../utils":31}],11:[function(require,module,exports){ +},{"../constants":3,"../utils":32}],11:[function(require,module,exports){ var FFZ = window.FrankerFaceZ; @@ -4772,12 +5627,19 @@ FFZ.prototype.load_emoji_data = function(callback, tries) { FFZ.prototype.load_global_sets = function(callback, tries) { var f = this; - jQuery.getJSON(constants.API_SERVER + "v1/set/global") + jQuery.getJSON(((tries||0)%2 === 0 ? constants.API_SERVER : constants.API_SERVER_2) + "v1/set/global") .done(function(data) { f.default_sets = data.default_sets; var gs = f.global_sets = [], sets = data.sets || {}; + if ( f.feature_friday && f.feature_friday.set ) { + if ( f.global_sets.indexOf(f.feature_friday.set) === -1 ) + f.global_sets.push(f.feature_friday.set); + if ( f.default_sets.indexOf(f.feature_friday.set) === -1 ) + f.default_sets.push(f.feature_friday.set); + } + for(var key in sets) { if ( ! sets.hasOwnProperty(key) ) continue; @@ -4802,7 +5664,7 @@ FFZ.prototype.load_global_sets = function(callback, tries) { FFZ.prototype.load_set = function(set_id, callback, tries) { var f = this; - jQuery.getJSON(constants.API_SERVER + "v1/set/" + set_id) + jQuery.getJSON(((tries||0)%2 === 0 ? constants.API_SERVER : constants.API_SERVER_2) + "v1/set/" + set_id) .done(function(data) { f._load_set_json(set_id, callback, data && data.set); @@ -4884,7 +5746,7 @@ FFZ.prototype._load_set_json = function(set_id, callback, data) { if ( callback ) callback(true, data); } -},{"./constants":3,"./utils":31}],13:[function(require,module,exports){ +},{"./constants":3,"./utils":32}],13:[function(require,module,exports){ var FFZ = window.FrankerFaceZ, constants = require('../constants'), utils = require('../utils'), @@ -4918,7 +5780,12 @@ FFZ.prototype.setup_bttv = function(delay) { document.body.classList.remove("ffz-dark"); if ( this._dark_style ) { this._dark_style.parentElement.removeChild(this._dark_style); - delete this._dark_style; + this._dark_style = undefined; + } + + if ( this._chat_style ) { + this._chat_style.parentElement.removeChild(this._chat_style); + this._chat_style = undefined; } // Disable Chat Tabs @@ -4939,8 +5806,17 @@ FFZ.prototype.setup_bttv = function(delay) { // Disable other features too. document.body.classList.remove("ffz-chat-colors"); document.body.classList.remove("ffz-chat-background"); + document.body.classList.remove("ffz-chat-padding"); + document.body.classList.remove("ffz-chat-separator"); document.body.classList.remove("ffz-sidebar-swap"); document.body.classList.remove("ffz-transparent-badges"); + document.body.classList.remove("ffz-high-contrast-chat"); + + // Remove Following Count + if ( this.settings.following_count ) { + this._schedule_following_count(); + this._draw_following_count(); + } // Remove Sub Count if ( this.is_dashboard ) @@ -5152,7 +6028,7 @@ FFZ.prototype.setup_bttv = function(delay) { this.update_ui_link(); } -},{"../constants":3,"../utils":31}],14:[function(require,module,exports){ +},{"../constants":3,"../utils":32}],14:[function(require,module,exports){ var FFZ = window.FrankerFaceZ; @@ -5252,7 +6128,7 @@ FFZ.get = function() { return FFZ.instance; } // Version var VER = FFZ.version_info = { - major: 3, minor: 4, revision: 11, + major: 3, minor: 4, revision: 19, toString: function() { return [VER.major, VER.minor, VER.revision].join(".") + (VER.extra || ""); } @@ -5371,6 +6247,7 @@ require('./ui/sub_count'); require('./ui/menu_button'); require('./ui/following'); +require('./ui/following-count'); require('./ui/races'); require('./ui/my_emotes'); require('./ui/about_page'); @@ -5391,6 +6268,12 @@ FFZ.prototype.initialize = function(increment, delay) { this.setup_normal(delay); return; } + + if ( location.hostname === 'passport' && /^\/(?:authorize)/.test(location.pathname) ) { + this.log("Running on passport!"); + this.setup_normal(delay, true); + return; + } // Check for the dashboard. if ( /\/[^\/]+\/dashboard/.test(location.pathname) && !/bookmarks$/.test(location.pathname) ) { @@ -5416,7 +6299,7 @@ FFZ.prototype.initialize = function(increment, delay) { } -FFZ.prototype.setup_normal = function(delay) { +FFZ.prototype.setup_normal = function(delay, no_socket) { var start = (window.performance && performance.now) ? performance.now() : Date.now(); this.log("Found non-Ember Twitch after " + (delay||0) + " ms in \"" + location + "\". Initializing FrankerFaceZ version " + FFZ.version_info); @@ -5432,11 +6315,14 @@ FFZ.prototype.setup_normal = function(delay) { // Start this early, for quick loading. this.setup_dark(); - this.ws_create(); + if ( ! no_socket ) + this.ws_create(); + this.setup_emoticons(); this.setup_badges(); this.setup_notifications(); + this.setup_following_count(false); this.setup_css(); this.setup_menu(); @@ -5524,6 +6410,7 @@ FFZ.prototype.setup_ember = function(delay) { this.setup_menu(); this.setup_my_emotes(); this.setup_following(); + this.setup_following_count(true); this.setup_races(); this.connect_extra_chat(); @@ -5556,7 +6443,7 @@ FFZ.prototype._on_window_message = function(e) { var msg = e.data; } -},{"./badges":1,"./commands":2,"./debug":4,"./ember/channel":5,"./ember/chat-input":6,"./ember/chatview":7,"./ember/line":8,"./ember/moderation-card":9,"./ember/room":10,"./ember/viewers":11,"./emoticons":12,"./ext/betterttv":13,"./ext/emote_menu":14,"./featurefriday":16,"./settings":17,"./socket":18,"./tokenize":19,"./ui/about_page":20,"./ui/dark":21,"./ui/following":22,"./ui/menu":23,"./ui/menu_button":24,"./ui/my_emotes":25,"./ui/notifications":26,"./ui/races":27,"./ui/styles":28,"./ui/sub_count":29,"./ui/viewer_count":30}],16:[function(require,module,exports){ +},{"./badges":1,"./commands":2,"./debug":4,"./ember/channel":5,"./ember/chat-input":6,"./ember/chatview":7,"./ember/line":8,"./ember/moderation-card":9,"./ember/room":10,"./ember/viewers":11,"./emoticons":12,"./ext/betterttv":13,"./ext/emote_menu":14,"./featurefriday":16,"./settings":17,"./socket":18,"./tokenize":19,"./ui/about_page":20,"./ui/dark":21,"./ui/following":23,"./ui/following-count":22,"./ui/menu":24,"./ui/menu_button":25,"./ui/my_emotes":26,"./ui/notifications":27,"./ui/races":28,"./ui/styles":29,"./ui/sub_count":30,"./ui/viewer_count":31}],16:[function(require,module,exports){ var FFZ = window.FrankerFaceZ, constants = require('./constants'); @@ -5756,19 +6643,6 @@ FFZ.prototype.load_settings = function() { // Menu Page // -------------------- -FFZ.settings_info.replace_twitch_menu = { - type: "boolean", - value: false, - - name: "Replace Twitch Emoticon Menu", - help: "Completely replace the default Twitch emoticon menu.", - - on_update: function(val) { - document.body.classList.toggle("ffz-menu-replace", val); - } - }; - - FFZ.menu_pages.settings = { render: function(view, container) { var settings = {}, @@ -5813,6 +6687,9 @@ FFZ.menu_pages.settings = { return 0; }); + var f = this, + current_page = this._ffz_settings_page || categories[0]; + for(var ci=0; ci < categories.length; ci++) { var category = categories[ci], cset = settings[category], @@ -5821,10 +6698,28 @@ FFZ.menu_pages.settings = { heading = document.createElement('div'); heading.className = 'heading'; - menu.className = 'chat-menu-content'; + menu.className = 'chat-menu-content collapsable'; + + menu.setAttribute('data-category', category); + menu.classList.toggle('collapsed', current_page !== category); + heading.innerHTML = category; menu.appendChild(heading); + menu.addEventListener('click', function() { + if ( ! this.classList.contains('collapsed') ) + return; + + var t = this, + old_selection = container.querySelectorAll('.chat-menu-content:not(.collapsed)'); + for(var i=0; i < old_selection.length; i++) + old_selection[i].classList.add('collapsed'); + + f._ffz_settings_page = t.getAttribute('data-category'); + t.classList.remove('collapsed'); + setTimeout(function(){t.scrollIntoViewIfNeeded()}); + }); + cset.sort(function(a,b) { var a = a[1], b = b[1], @@ -6192,7 +7087,13 @@ FFZ.prototype.ws_send = function(func, data, callback, can_wait) { if ( callback ) this._ws_callbacks[request] = callback; - this._ws_sock.send(request + " " + func + data); + try { + this._ws_sock.send(request + " " + func + data); + } catch(err) { + this.log("Socket Send Error: " + err); + return false; + } + return request; } @@ -6296,6 +7197,38 @@ FFZ.src_to_id = function(src) { }; +// --------------------- +// Time Format +// --------------------- + +var ts = new Date(0).toLocaleTimeString().toUpperCase(); + +FFZ.settings_info.twenty_four_timestamps = { + type: "boolean", + value: ts.lastIndexOf('PM') === -1 && ts.lastIndexOf('AM') === -1, + + category: "Chat Appearance", + no_bttv: true, + + name: "24hr Timestamps", + help: "Display timestamps in chat in the 24 hour format rather than 12 hour." + }; + + +if ( helpers ) + helpers.getTime = function(e) { + var hours = e.getHours(), + minutes = e.getMinutes(); + + if ( hours > 12 && ! FFZ.get().settings.twenty_four_timestamps ) + hours -= 12; + else if ( hours === 0 && ! FFZ.get().settings.twenty_four_timestamps ) + hours = 12; + + return hours + ':' + (minutes < 10 ? '0' : '') + minutes; + }; + + // --------------------- // Tokenization // --------------------- @@ -6360,41 +7293,44 @@ FFZ.prototype.tokenize_chat_line = function(msgObject, prevent_notification) { var room = this.rooms[room_id] && this.rooms[room_id].room, room_name; - if ( room && room.get('isGroupRoom') ) - room_name = room.get('tmiRoom.displayName'); - else - room_name = FFZ.get_capitalization(room_id); - - display = display || Twitch.display.capitalize(msgObject.from); - - if ( msgObject.style === 'action' ) - msg = '* ' + display + ' ' + msg; - else - msg = display + ': ' + msg; - - var f = this; - if ( msgObject.style === 'whisper' ) - this.show_notification( - msg, - "Twitch Chat Whisper", - "ffz_whisper_notice", - 60000, - function() { - window.focus(); - } + // Make sure we have UI for this channel. + if ( (this.settings.group_tabs && (this.settings.pinned_rooms.indexOf(room_id) !== -1 || this._chatv._ffz_host )) || room.get('isGroupRoom') || room === this._chatv.get('controller.currentChannelRoom') ) { + if ( room && room.get('isGroupRoom') ) + room_name = room.get('tmiRoom.displayName'); + else + room_name = FFZ.get_capitalization(room_id); + + display = display || Twitch.display.capitalize(msgObject.from); + + if ( msgObject.style === 'action' ) + msg = '* ' + display + ' ' + msg; + else + msg = display + ': ' + msg; + + var f = this; + if ( msgObject.style === 'whisper' ) + this.show_notification( + msg, + "Twitch Chat Whisper", + "ffz_whisper_notice", + 60000, + function() { + window.focus(); + } ); - else - this.show_notification( - msg, - "Twitch Chat Mention in " + room_name, - room_id, - 60000, - function() { - window.focus(); - var cont = App.__container__.lookup('controller:chat'); - room && cont && cont.focusRoom(room); - } + else + this.show_notification( + msg, + "Twitch Chat Mention in " + room_name, + room_id, + 60000, + function() { + window.focus(); + var cont = App.__container__.lookup('controller:chat'); + room && cont && cont.focusRoom(room); + } ); + } } break; @@ -6816,7 +7752,7 @@ FFZ.prototype._deleted_link_click = function(e) { // Stop from Navigating e.preventDefault(); } -},{"./constants":3,"./utils":31}],20:[function(require,module,exports){ +},{"./constants":3,"./utils":32}],20:[function(require,module,exports){ var FFZ = window.FrankerFaceZ, constants = require("../constants"); @@ -6943,10 +7879,15 @@ FFZ.settings_info.dark_twitch = { no_bttv: true, //visible: function() { return ! this.has_bttv }, + category: "Appearance", name: "Dark Twitch", help: "Apply a dark background to channels and other related pages for easier viewing.", on_update: function(val) { + var cb = document.querySelector('input.ffz-setting-dark-twitch'); + if ( cb ) + cb.checked = val; + if ( this.has_bttv ) return; @@ -6964,11 +7905,46 @@ FFZ.settings_info.dark_twitch = { }; +FFZ.settings_info.dark_no_blue = { + type: "boolean", + value: false, + + //no_bttv: true, + + category: "Appearance", + name: "Gray Chat (no blue)", + help: "Make the dark theme for chat and a few other places on Twitch a bit darker and not at all blue.", + + on_update: function(val) { + document.body.classList.toggle("ffz-no-blue", val); + } + }; + + +FFZ.settings_info.hide_recent_past_broadcast = { + type: "boolean", + value: false, + + //no_bttv: true, + + category: "Channel Metadata", + name: "Hide \"Watch Last Broadcast\"", + help: "Hide the \"Watch Last Broadcast\" banner at the top of offline Twitch channels.", + + on_update: function(val) { + document.body.classList.toggle("ffz-hide-recent-past-broadcast", val); + } + }; + + // --------------------- // Initialization // --------------------- FFZ.prototype.setup_dark = function() { + document.body.classList.toggle("ffz-hide-recent-past-broadcast", this.settings.hide_recent_past_broadcast); + document.body.classList.toggle("ffz-no-blue", this.settings.dark_no_blue); + if ( this.has_bttv ) return; @@ -6996,14 +7972,164 @@ FFZ.prototype._load_dark_css = function() { } },{"../constants":3}],22:[function(require,module,exports){ var FFZ = window.FrankerFaceZ, - utils = require('../utils'), + utils = require('../utils'); - EMOTE_CHANNELS = { - sirstendec: true, - europeanspeedsterassembly: true, - esamarathon2: true + + +FFZ.settings_info.following_count = { + type: "boolean", + value: true, + + no_bttv: true, + + category: "Appearance", + name: "Sidebar Following Count", + help: "Display the number of live channels you're following on the sidebar.", + + on_update: function(val) { + this._schedule_following_count(); + + var Stream = window.App && App.__container__.resolve('model:stream'), + Live = Stream && Stream.find("live"); + + if ( Live ) + this._draw_following_count(Live.get('total') || 0); + else + this._update_following_count(); + } }; +// --------------- +// Initialization +// --------------- + +FFZ.prototype.setup_following_count = function(has_ember) { + // Start it updating. + if ( this.settings.following_count ) + this._schedule_following_count(); + + // If we don't have Ember, no point in trying this stuff. + if ( ! has_ember ) + return this._update_following_count(); + + this.log("Connecting to Live Streams model."); + var Stream = window.App && App.__container__.resolve('model:stream'); + if ( ! Stream ) + return this.log("Unable to find Stream model."); + + var Live = Stream.find("live"), + f = this; + + if ( ! Live ) + return this.log("Unable to find Live Streams collection."); + + Live.addObserver('total', function() { f._draw_following_count(this.get('total')); }); + Live.load(); + + var total = Live.get('total'); + if ( typeof total === "number" ) + this._draw_following_count(total); +} + + +FFZ.prototype._schedule_following_count = function() { + if ( this.has_bttv || ! this.settings.following_count ) { + if ( this._following_count_timer ) { + clearTimeout(this._following_count_timer); + this._following_count_timer = undefined; + } + return; + } + + if ( ! this._following_count_timer ) + this._following_count_timer = setTimeout(this._update_following_count.bind(this), 60000); +} + + +FFZ.prototype._update_following_count = function() { + if ( ! this.settings.following_count ) { + if ( this._following_count_timer ) { + clearTimeout(this._following_count_timer); + this._following_count_timer = undefined; + } + return; + } + + this._following_count_timer = setTimeout(this._update_following_count.bind(this), 60000); + + var Stream = window.App && App.__container__.resolve('model:stream'), + Live = Stream && Stream.find("live"), + f = this; + + if ( Live ) + Live.load(); + else + Twitch.api.get("streams/followed", {limit:1, offset:0}, {version:3}) + .done(function(data) { + f._draw_following_count(data._total); + }).fail(function() { + f._draw_following_count(); + }) +} + + +FFZ.prototype._draw_following_count = function(count) { + // Small + var small_following = document.querySelector('#small_nav ul.game_filters li[data-name="following"] a'); + if ( small_following ) { + var badge = small_following.querySelector('.ffz-follow-count'); + if ( this.has_bttv || ! this.settings.following_count ) { + if ( badge ) + badge.parentElement.removeChild(badge); + } else { + if ( ! badge ) { + badge = document.createElement('span'); + badge.className = 'ffz-follow-count'; + small_following.appendChild(badge); + } + badge.innerHTML = count ? utils.format_unread(count) : ''; + } + } + + + // Large + var large_following = document.querySelector('#large_nav #nav_personal li[data-name="following"] a'); + if ( large_following ) { + var badge = large_following.querySelector('.ffz-follow-count'); + if ( this.has_bttv || ! this.settings.following_count ) { + if ( badge ) + badge.parentElement.removeChild(badge); + } else { + if ( ! badge ) { + badge = document.createElement('span'); + badge.className = 'ffz-follow-count'; + large_following.appendChild(badge); + } + badge.innerHTML = count ? utils.format_unread(count) : ''; + } + } + + // Heading + var head_following = document.querySelector('#header_actions #header_following'); + if ( head_following ) { + var badge = head_following.querySelector('.ffz-follow-count'); + if ( this.has_bttv || ! this.settings.following_count ) { + if ( badge ) + badge.parentElement.removeChild(badge); + } else { + if ( ! badge ) { + badge = document.createElement('span'); + badge.className = 'ffz-follow-count'; + head_following.appendChild(badge); + } + badge.innerHTML = count ? utils.format_unread(count) : ''; + } + } +} +},{"../utils":32}],23:[function(require,module,exports){ +var FFZ = window.FrankerFaceZ, + utils = require('../utils'); + // --------------- // Initialization @@ -7399,7 +8525,7 @@ FFZ.prototype._build_following_popup = function(container, channel_id, notificat container.appendChild(popup); return popup.querySelector('a.switch'); } -},{"../utils":31}],23:[function(require,module,exports){ +},{"../utils":32}],24:[function(require,module,exports){ var FFZ = window.FrankerFaceZ, constants = require('../constants'), utils = require('../utils'), @@ -7435,7 +8561,7 @@ FFZ.prototype.setup_menu = function() { this.log("Hooking the Ember Chat Settings view."); - var Settings = App.__container__.resolve('view:settings'); + var Settings = window.App && App.__container__.resolve('view:settings'); if ( ! Settings ) return; @@ -7493,23 +8619,20 @@ FFZ.prototype.setup_menu = function() { }); - // Minimal Chat - /* + // Channel Hosting p = document.createElement('p'); //p.className = 'no-bttv'; cb = document.createElement('input'); cb.type = "checkbox"; - cb.className = "ember-checkbox ffz-setting-minimal-twitch"; - cb.checked = f.settings.minimal_chat; + cb.className = "ember-checkbox ffz-setting-hosted-channels"; + cb.checked = f.settings.hosted_channels; p.appendChild(cb); - p.appendChild(document.createTextNode("Minimalistic Chat")); + p.appendChild(document.createTextNode("Channel Hosting")); content.appendChild(p); cb.addEventListener("change", function(e) { - f.settings.set("minimal_chat", this.checked); - if ( this.checked ) - view.set('controller.model.hidden', true); - });*/ + f.settings.set("hosted_channels", this.checked); + }); // More Settings @@ -7950,7 +9073,7 @@ FFZ.prototype._add_emote = function(view, emote) { else room.set('messageToSend', text); } -},{"../constants":3,"../utils":31}],24:[function(require,module,exports){ +},{"../constants":3,"../utils":32}],25:[function(require,module,exports){ var FFZ = window.FrankerFaceZ, constants = require('../constants'); @@ -7997,7 +9120,7 @@ FFZ.prototype.update_ui_link = function(link) { link.classList.toggle('dark', dark); link.classList.toggle('blue', blue); } -},{"../constants":3}],25:[function(require,module,exports){ +},{"../constants":3}],26:[function(require,module,exports){ var FFZ = window.FrankerFaceZ, constants = require("../constants"), utils = require("../utils"), @@ -8010,10 +9133,27 @@ var FFZ = window.FrankerFaceZ, // Initialization // ------------------- +FFZ.settings_info.replace_twitch_menu = { + type: "boolean", + value: false, + + category: "Chat Input", + + name: "Replace Twitch Emoticon Menu", + help: "Completely replace the default Twitch emoticon menu.", + + on_update: function(val) { + document.body.classList.toggle("ffz-menu-replace", val); + } + }; + + FFZ.settings_info.global_emotes_in_menu = { type: "boolean", value: false, + category: "Chat Input", + name: "Display Global Emotes in My Emotes", help: "Display the global Twitch emotes in the My Emoticons menu." }; @@ -8023,11 +9163,19 @@ FFZ.settings_info.emoji_in_menu = { type: "boolean", value: false, + category: "Chat Input", + name: "Display Emoji in My Emotes", help: "Display the supported emoji images in the My Emoticons menu." }; +FFZ.settings_info.emote_menu_collapsed = { + value: [], + visible: false +} + + FFZ.prototype.setup_my_emotes = function() { this._twitch_set_to_channel = {}; this._twitch_badges = {}; @@ -8114,16 +9262,36 @@ FFZ.menu_pages.my_emotes = { setTimeout(fail, 2000); }, + toggle_section: function(heading) { + var menu = heading.parentElement, + set_id = menu.getAttribute('data-set'), + collapsed_list = this.settings.emote_menu_collapsed, + is_collapsed = collapsed_list.indexOf(set_id) !== -1; + + if ( is_collapsed ) + collapsed_list.removeObject(set_id); + else + collapsed_list.push(set_id); + + this.settings.set('emote_menu_collapsed', collapsed_list); + menu.classList.toggle('collapsed', !is_collapsed); + }, + draw_emoji: function(view) { var heading = document.createElement('div'), - menu = document.createElement('div'); + menu = document.createElement('div'), + f = this; heading.className = 'heading'; heading.innerHTML = 'FrankerFaceZEmoji'; - menu.className = 'emoticon-grid'; + menu.className = 'emoticon-grid collapsable'; menu.appendChild(heading); + menu.setAttribute('data-set', 'emoji'); + menu.classList.toggle('collapsed', this.settings.emote_menu_collapsed.indexOf('emoji') !== -1); + heading.addEventListener('click', function() { FFZ.menu_pages.my_emotes.toggle_section.bind(f)(this); }); + var set = []; for(var eid in this.emoji_data) set.push(this.emoji_data[eid]); @@ -8163,6 +9331,7 @@ FFZ.menu_pages.my_emotes = { draw_twitch_set: function(view, set_id, set) { var heading = document.createElement('div'), menu = document.createElement('div'), + f = this, channel_id = this._twitch_set_to_channel[set_id], title; @@ -8192,8 +9361,12 @@ FFZ.menu_pages.my_emotes = { }); } - menu.className = 'emoticon-grid'; + menu.className = 'emoticon-grid collapsable'; menu.appendChild(heading); + + menu.setAttribute('data-set', 'twitch-' + set_id); + menu.classList.toggle('collapsed', this.settings.emote_menu_collapsed.indexOf('twitch-' + set_id) !== -1); + heading.addEventListener('click', function() { FFZ.menu_pages.my_emotes.toggle_section.bind(f)(this); }); set.sort(function(a,b) { var an = a.code.toLowerCase(), @@ -8236,14 +9409,19 @@ FFZ.menu_pages.my_emotes = { draw_ffz_set: function(view, set) { var heading = document.createElement('div'), menu = document.createElement('div'), + f = this, emotes = []; heading.className = 'heading'; heading.innerHTML = 'FrankerFaceZ' + set.title; heading.style.backgroundImage = 'url("' + (set.icon || '//cdn.frankerfacez.com/script/devicon.png') + '")'; - menu.className = 'emoticon-grid'; + menu.className = 'emoticon-grid collapsable'; menu.appendChild(heading); + + menu.setAttribute('data-set', 'ffz-' + set.id); + menu.classList.toggle('collapsed', this.settings.emote_menu_collapsed.indexOf('ffz-' + set.id) !== -1); + heading.addEventListener('click', function() { FFZ.menu_pages.my_emotes.toggle_section.bind(f)(this); }); for(var emote_id in set.emoticons) set.emoticons.hasOwnProperty(emote_id) && ! set.emoticons[emote_id].hidden && emotes.push(set.emoticons[emote_id]); @@ -8339,11 +9517,15 @@ FFZ.menu_pages.my_emotes = { if ( an === "turbo" || an === "turbo_faces" ) an = "zza|" + an; else if ( an === "global" || an === "global emoticons" ) + an = "zzy|" + an; + else if ( an === "emoji" ) an = "zzz|" + an; if ( bn === "turbo" || bn === "turbo_faces" ) bn = "zza|" + bn; else if ( bn === "global" || bn === "global emoticons" ) + bn = "zzy|" + bn; + else if ( bn === "emoji" ) bn = "zzz|" + bn; if ( an < bn ) return -1; @@ -8375,7 +9557,7 @@ FFZ.menu_pages.my_emotes = { } } }; -},{"../constants":3,"../utils":31}],26:[function(require,module,exports){ +},{"../constants":3,"../utils":32}],27:[function(require,module,exports){ var FFZ = window.FrankerFaceZ; @@ -8397,7 +9579,7 @@ FFZ.settings_info.highlight_notifications = { type: "boolean", value: false, - category: "Chat", + category: "Chat Filtering", no_bttv: true, //visible: function() { return ! this.has_bttv }, @@ -8525,7 +9707,7 @@ FFZ.prototype.show_message = function(message) { closeWith: ["button"] }).show(); } -},{}],27:[function(require,module,exports){ +},{}],28:[function(require,module,exports){ var FFZ = window.FrankerFaceZ, utils = require('../utils'); @@ -8871,7 +10053,7 @@ FFZ.prototype._update_race = function(container, not_timer) { } } } -},{"../utils":31}],28:[function(require,module,exports){ +},{"../utils":32}],29:[function(require,module,exports){ var FFZ = window.FrankerFaceZ, constants = require('../constants'); @@ -8896,7 +10078,7 @@ FFZ.prototype.setup_css = function() { } }; } -},{"../constants":3}],29:[function(require,module,exports){ +},{"../constants":3}],30:[function(require,module,exports){ var FFZ = window.FrankerFaceZ, constants = require('../constants'), utils = require('../utils'); @@ -8999,7 +10181,7 @@ FFZ.prototype._update_subscribers = function() { });; } -},{"../constants":3,"../utils":31}],30:[function(require,module,exports){ +},{"../constants":3,"../utils":32}],31:[function(require,module,exports){ var FFZ = window.FrankerFaceZ, constants = require('../constants'), utils = require('../utils'); @@ -9071,7 +10253,7 @@ FFZ.ws_commands.viewers = function(data) { jQuery(view_count).tipsy(this.is_dashboard ? {"gravity":"s"} : undefined); } } -},{"../constants":3,"../utils":31}],31:[function(require,module,exports){ +},{"../constants":3,"../utils":32}],32:[function(require,module,exports){ var FFZ = window.FrankerFaceZ, constants = require('./constants'); @@ -9333,27 +10515,28 @@ module.exports = { pluralize: pluralize, - human_time: function(elapsed) { + human_time: function(elapsed, factor) { + factor = factor || 1; elapsed = Math.floor(elapsed); - var years = Math.floor(elapsed / 31536000); - if ( years ) + var years = Math.floor((elapsed*factor) / 31536000) / factor; + if ( years >= 1 ) return years + ' year' + pluralize(years); var days = Math.floor((elapsed %= 31536000) / 86400); - if ( days ) + if ( days >= 1 ) return days + ' day' + pluralize(days); var hours = Math.floor((elapsed %= 86400) / 3600); - if ( hours ) + if ( hours >= 1 ) return hours + ' hour' + pluralize(hours); var minutes = Math.floor((elapsed %= 3600) / 60); - if ( minutes ) + if ( minutes >= 1 ) return minutes + ' minute' + pluralize(minutes); var seconds = elapsed % 60; - if ( seconds ) + if ( seconds >= 1 ) return seconds + ' second' + pluralize(seconds); return 'less than a second'; @@ -9377,6 +10560,16 @@ module.exports = { } return days + ((!no_hours || days || hours) ? ((hours < 10 ? "0" : "") + hours + ':') : '') + (minutes < 10 ? "0" : "") + minutes + ":" + (seconds < 10 ? "0" : "") + seconds; + }, + + format_unread: function(count) { + if ( count < 1 ) + return ""; + + else if ( count >= 99 ) + return "99+"; + + return "" + count; } } },{"./constants":3}]},{},[15]);window.ffz = new FrankerFaceZ()}(window)); \ No newline at end of file diff --git a/script.min.js b/script.min.js index fdcb0fdc..74e2e06f 100644 --- a/script.min.js +++ b/script.min.js @@ -1,6 +1,7 @@ -!function(e){!function t(e,s,o){function n(a,r){if(!s[a]){if(!e[a]){var d="function"==typeof require&&require;if(!r&&d)return d(a,!0);if(i)return i(a,!0);throw new Error("Cannot find module '"+a+"'")}var u=s[a]={exports:{}};e[a][0].call(u.exports,function(t){var s=e[a][1][t];return n(s?s:t)},u,u.exports,t,e,s,o)}return s[a].exports}for(var i="function"==typeof require&&require,a=0;ae&&this._legacy_load_bots(e))})},s.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})},s.prototype._legacy_parse_badges=function(e,t,s){var o=this.badges[s].title,i=0;if(ds=null,null!=e)for(var a=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 n=t.shift();e.room.tmiRoom.sendMessage("/unmod "+n)}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 s=this.get_user();if(!s||!s.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 n=t.shift();e.room.tmiRoom.sendMessage("/mod "+n)}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 s='',o="true"==localStorage.ffzDebugMode&&document.body.classList.contains("ffz-dev"),n=o?"//localhost:8000/":"//cdn.frankerfacez.com/";t.exports={DEBUG:o,SERVER:n,API_SERVER:"//api.frankerfacez.com/",KNOWN_CODES:{"#-?[\\\\/]":"#-/",":-?(?:7|L)":":-7","\\<\\;\\]":"<]","\\:-?(S|s)":":-S","\\:-?\\\\":":-\\","\\:\\>\\;":":>","B-?\\)":"B-)","\\:-?[z|Z|\\|]":":-Z","\\:-?\\)":":-)","\\:-?\\(":":-(","\\:-?(p|P)":":-P","\\;-?(p|P)":";-P","\\<\\;3":"<3","\\:-?[\\\\/]":":-/","\\;-?\\)":";-)","R-?\\)":"R-)","[o|O](_|\\.)[o|O]":"O.o","\\:-?D":":-D","\\:-?(o|O)":":-O","\\>\\;\\(":">(","Gr(a|e)yFace":"GrayFace"},EMOTE_REPLACEMENT_BASE:n+"script/replacements/",EMOTE_REPLACEMENTS:{15:"15-JKanStyle.png",16:"16-OptimizePrime.png",17:"17-StoneLightning.png",18:"18-TheRinger.png",19:"19-PazPazowitz.png",20:"20-EagleEye.png",21:"21-CougarHunt.png",22:"22-RedCoat.png",26:"26-JonCarnage.png",27:"27-PicoMause.png",30:"30-BCWarrior.png",33:"33-DansGame.png",36:"36-PJSalt.png"},EMOJI_REGEX:/((?:\ud83c\udde8\ud83c\uddf3|\ud83c\uddfa\ud83c\uddf8|\ud83c\uddf7\ud83c\uddfa|\ud83c\uddf0\ud83c\uddf7|\ud83c\uddef\ud83c\uddf5|\ud83c\uddee\ud83c\uddf9|\ud83c\uddec\ud83c\udde7|\ud83c\uddeb\ud83c\uddf7|\ud83c\uddea\ud83c\uddf8|\ud83c\udde9\ud83c\uddea|\u0039\ufe0f?\u20e3|\u0038\ufe0f?\u20e3|\u0037\ufe0f?\u20e3|\u0036\ufe0f?\u20e3|\u0035\ufe0f?\u20e3|\u0034\ufe0f?\u20e3|\u0033\ufe0f?\u20e3|\u0032\ufe0f?\u20e3|\u0031\ufe0f?\u20e3|\u0030\ufe0f?\u20e3|\u0023\ufe0f?\u20e3|\ud83d\udeb3|\ud83d\udeb1|\ud83d\udeb0|\ud83d\udeaf|\ud83d\udeae|\ud83d\udea6|\ud83d\udea3|\ud83d\udea1|\ud83d\udea0|\ud83d\ude9f|\ud83d\ude9e|\ud83d\ude9d|\ud83d\ude9c|\ud83d\ude9b|\ud83d\ude98|\ud83d\ude96|\ud83d\ude94|\ud83d\ude90|\ud83d\ude8e|\ud83d\ude8d|\ud83d\ude8b|\ud83d\ude8a|\ud83d\ude88|\ud83d\ude86|\ud83d\ude82|\ud83d\ude81|\ud83d\ude36|\ud83d\ude34|\ud83d\ude2f|\ud83d\ude2e|\ud83d\ude2c|\ud83d\ude27|\ud83d\ude26|\ud83d\ude1f|\ud83d\ude1b|\ud83d\ude19|\ud83d\ude17|\ud83d\ude15|\ud83d\ude11|\ud83d\ude10|\ud83d\ude0e|\ud83d\ude08|\ud83d\ude07|\ud83d\ude00|\ud83d\udd67|\ud83d\udd66|\ud83d\udd65|\ud83d\udd64|\ud83d\udd63|\ud83d\udd62|\ud83d\udd61|\ud83d\udd60|\ud83d\udd5f|\ud83d\udd5e|\ud83d\udd5d|\ud83d\udd5c|\ud83d\udd2d|\ud83d\udd2c|\ud83d\udd15|\ud83d\udd09|\ud83d\udd08|\ud83d\udd07|\ud83d\udd06|\ud83d\udd05|\ud83d\udd04|\ud83d\udd02|\ud83d\udd01|\ud83d\udd00|\ud83d\udcf5|\ud83d\udcef|\ud83d\udced|\ud83d\udcec|\ud83d\udcb7|\ud83d\udcb6|\ud83d\udcad|\ud83d\udc6d|\ud83d\udc6c|\ud83d\udc65|\ud83d\udc2a|\ud83d\udc16|\ud83d\udc15|\ud83d\udc13|\ud83d\udc10|\ud83d\udc0f|\ud83d\udc0b|\ud83d\udc0a|\ud83d\udc09|\ud83d\udc08|\ud83d\udc07|\ud83d\udc06|\ud83d\udc05|\ud83d\udc04|\ud83d\udc03|\ud83d\udc02|\ud83d\udc01|\ud83d\udc00|\ud83c\udfe4|\ud83c\udfc9|\ud83c\udfc7|\ud83c\udf7c|\ud83c\udf50|\ud83c\udf4b|\ud83c\udf33|\ud83c\udf32|\ud83c\udf1e|\ud83c\udf1d|\ud83c\udf1c|\ud83c\udf1a|\ud83c\udf18|\ud83c\udccf|\ud83c\udd70|\ud83c\udd71|\ud83c\udd7e|\ud83c\udd8e|\ud83c\udd91|\ud83c\udd92|\ud83c\udd93|\ud83c\udd94|\ud83c\udd95|\ud83c\udd96|\ud83c\udd97|\ud83c\udd98|\ud83c\udd99|\ud83c\udd9a|\ud83d\udc77|\ud83d\udec5|\ud83d\udec4|\ud83d\udec3|\ud83d\udec2|\ud83d\udec1|\ud83d\udebf|\ud83d\udeb8|\ud83d\udeb7|\ud83d\udeb5|\ud83c\ude01|\ud83c\ude02|\ud83c\ude32|\ud83c\ude33|\ud83c\ude34|\ud83c\ude35|\ud83c\ude36|\ud83c\ude37|\ud83c\ude38|\ud83c\ude39|\ud83c\ude3a|\ud83c\ude50|\ud83c\ude51|\ud83c\udf00|\ud83c\udf01|\ud83c\udf02|\ud83c\udf03|\ud83c\udf04|\ud83c\udf05|\ud83c\udf06|\ud83c\udf07|\ud83c\udf08|\ud83c\udf09|\ud83c\udf0a|\ud83c\udf0b|\ud83c\udf0c|\ud83c\udf0f|\ud83c\udf11|\ud83c\udf13|\ud83c\udf14|\ud83c\udf15|\ud83c\udf19|\ud83c\udf1b|\ud83c\udf1f|\ud83c\udf20|\ud83c\udf30|\ud83c\udf31|\ud83c\udf34|\ud83c\udf35|\ud83c\udf37|\ud83c\udf38|\ud83c\udf39|\ud83c\udf3a|\ud83c\udf3b|\ud83c\udf3c|\ud83c\udf3d|\ud83c\udf3e|\ud83c\udf3f|\ud83c\udf40|\ud83c\udf41|\ud83c\udf42|\ud83c\udf43|\ud83c\udf44|\ud83c\udf45|\ud83c\udf46|\ud83c\udf47|\ud83c\udf48|\ud83c\udf49|\ud83c\udf4a|\ud83c\udf4c|\ud83c\udf4d|\ud83c\udf4e|\ud83c\udf4f|\ud83c\udf51|\ud83c\udf52|\ud83c\udf53|\ud83c\udf54|\ud83c\udf55|\ud83c\udf56|\ud83c\udf57|\ud83c\udf58|\ud83c\udf59|\ud83c\udf5a|\ud83c\udf5b|\ud83c\udf5c|\ud83c\udf5d|\ud83c\udf5e|\ud83c\udf5f|\ud83c\udf60|\ud83c\udf61|\ud83c\udf62|\ud83c\udf63|\ud83c\udf64|\ud83c\udf65|\ud83c\udf66|\ud83c\udf67|\ud83c\udf68|\ud83c\udf69|\ud83c\udf6a|\ud83c\udf6b|\ud83c\udf6c|\ud83c\udf6d|\ud83c\udf6e|\ud83c\udf6f|\ud83c\udf70|\ud83c\udf71|\ud83c\udf72|\ud83c\udf73|\ud83c\udf74|\ud83c\udf75|\ud83c\udf76|\ud83c\udf77|\ud83c\udf78|\ud83c\udf79|\ud83c\udf7a|\ud83c\udf7b|\ud83c\udf80|\ud83c\udf81|\ud83c\udf82|\ud83c\udf83|\ud83c\udf84|\ud83c\udf85|\ud83c\udf86|\ud83c\udf87|\ud83c\udf88|\ud83c\udf89|\ud83c\udf8a|\ud83c\udf8b|\ud83c\udf8c|\ud83c\udf8d|\ud83c\udf8e|\ud83c\udf8f|\ud83c\udf90|\ud83c\udf91|\ud83c\udf92|\ud83c\udf93|\ud83c\udfa0|\ud83c\udfa1|\ud83c\udfa2|\ud83c\udfa3|\ud83c\udfa4|\ud83c\udfa5|\ud83c\udfa6|\ud83c\udfa7|\ud83c\udfa8|\ud83c\udfa9|\ud83c\udfaa|\ud83c\udfab|\ud83c\udfac|\ud83c\udfad|\ud83c\udfae|\ud83c\udfaf|\ud83c\udfb0|\ud83c\udfb1|\ud83c\udfb2|\ud83c\udfb3|\ud83c\udfb4|\ud83c\udfb5|\ud83c\udfb6|\ud83c\udfb7|\ud83c\udfb8|\ud83c\udfb9|\ud83c\udfba|\ud83c\udfbb|\ud83c\udfbc|\ud83c\udfbd|\ud83c\udfbe|\ud83c\udfbf|\ud83c\udfc0|\ud83c\udfc1|\ud83c\udfc2|\ud83c\udfc3|\ud83c\udfc4|\ud83c\udfc6|\ud83c\udfc8|\ud83c\udfca|\ud83c\udfe0|\ud83c\udfe1|\ud83c\udfe2|\ud83c\udfe3|\ud83c\udfe5|\ud83c\udfe6|\ud83c\udfe7|\ud83c\udfe8|\ud83c\udfe9|\ud83c\udfea|\ud83c\udfeb|\ud83c\udfec|\ud83c\udfed|\ud83c\udfee|\ud83c\udfef|\ud83c\udff0|\ud83d\udc0c|\ud83d\udc0d|\ud83d\udc0e|\ud83d\udc11|\ud83d\udc12|\ud83d\udc14|\ud83d\udc17|\ud83d\udc18|\ud83d\udc19|\ud83d\udc1a|\ud83d\udc1b|\ud83d\udc1c|\ud83d\udc1d|\ud83d\udc1e|\ud83d\udc1f|\ud83d\udc20|\ud83d\udc21|\ud83d\udc22|\ud83d\udc23|\ud83d\udc24|\ud83d\udc25|\ud83d\udc26|\ud83d\udc27|\ud83d\udc28|\ud83d\udc29|\ud83d\udc2b|\ud83d\udc2c|\ud83d\udc2d|\ud83d\udc2e|\ud83d\udc2f|\ud83d\udc30|\ud83d\udc31|\ud83d\udc32|\ud83d\udc33|\ud83d\udc34|\ud83d\udc35|\ud83d\udc36|\ud83d\udc37|\ud83d\udc38|\ud83d\udc39|\ud83d\udc3a|\ud83d\udc3b|\ud83d\udc3c|\ud83d\udc3d|\ud83d\udc3e|\ud83d\udc40|\ud83d\udc42|\ud83d\udc43|\ud83d\udc44|\ud83d\udc45|\ud83d\udc46|\ud83d\udc47|\ud83d\udc48|\ud83d\udc49|\ud83d\udc4a|\ud83d\udc4b|\ud83d\udc4c|\ud83d\udc4d|\ud83d\udc4e|\ud83d\udc4f|\ud83d\udc50|\ud83d\udc51|\ud83d\udc52|\ud83d\udc53|\ud83d\udc54|\ud83d\udc55|\ud83d\udc56|\ud83d\udc57|\ud83d\udc58|\ud83d\udc59|\ud83d\udc5a|\ud83d\udc5b|\ud83d\udc5c|\ud83d\udc5d|\ud83d\udc5e|\ud83d\udc5f|\ud83d\udc60|\ud83d\udc61|\ud83d\udc62|\ud83d\udc63|\ud83d\udc64|\ud83d\udc66|\ud83d\udc67|\ud83d\udc68|\ud83d\udc69|\ud83d\udc6a|\ud83d\udc6b|\ud83d\udc6e|\ud83d\udc6f|\ud83d\udc70|\ud83d\udc71|\ud83d\udc72|\ud83d\udc73|\ud83d\udc74|\ud83d\udc75|\ud83d\udc76|\ud83d\udeb4|\ud83d\udc78|\ud83d\udc79|\ud83d\udc7a|\ud83d\udc7b|\ud83d\udc7c|\ud83d\udc7d|\ud83d\udc7e|\ud83d\udc7f|\ud83d\udc80|\ud83d\udc81|\ud83d\udc82|\ud83d\udc83|\ud83d\udc84|\ud83d\udc85|\ud83d\udc86|\ud83d\udc87|\ud83d\udc88|\ud83d\udc89|\ud83d\udc8a|\ud83d\udc8b|\ud83d\udc8c|\ud83d\udc8d|\ud83d\udc8e|\ud83d\udc8f|\ud83d\udc90|\ud83d\udc91|\ud83d\udc92|\ud83d\udc93|\ud83d\udc94|\ud83d\udc95|\ud83d\udc96|\ud83d\udc97|\ud83d\udc98|\ud83d\udc99|\ud83d\udc9a|\ud83d\udc9b|\ud83d\udc9c|\ud83d\udc9d|\ud83d\udc9e|\ud83d\udc9f|\ud83d\udca0|\ud83d\udca1|\ud83d\udca2|\ud83d\udca3|\ud83d\udca4|\ud83d\udca5|\ud83d\udca6|\ud83d\udca7|\ud83d\udca8|\ud83d\udca9|\ud83d\udcaa|\ud83d\udcab|\ud83d\udcac|\ud83d\udcae|\ud83d\udcaf|\ud83d\udcb0|\ud83d\udcb1|\ud83d\udcb2|\ud83d\udcb3|\ud83d\udcb4|\ud83d\udcb5|\ud83d\udcb8|\ud83d\udcb9|\ud83d\udcba|\ud83d\udcbb|\ud83d\udcbc|\ud83d\udcbd|\ud83d\udcbe|\ud83d\udcbf|\ud83d\udcc0|\ud83d\udcc1|\ud83d\udcc2|\ud83d\udcc3|\ud83d\udcc4|\ud83d\udcc5|\ud83d\udcc6|\ud83d\udcc7|\ud83d\udcc8|\ud83d\udcc9|\ud83d\udcca|\ud83d\udccb|\ud83d\udccc|\ud83d\udccd|\ud83d\udcce|\ud83d\udccf|\ud83d\udcd0|\ud83d\udcd1|\ud83d\udcd2|\ud83d\udcd3|\ud83d\udcd4|\ud83d\udcd5|\ud83d\udcd6|\ud83d\udcd7|\ud83d\udcd8|\ud83d\udcd9|\ud83d\udcda|\ud83d\udcdb|\ud83d\udcdc|\ud83d\udcdd|\ud83d\udcde|\ud83d\udcdf|\ud83d\udce0|\ud83d\udce1|\ud83d\udce2|\ud83d\udce3|\ud83d\udce4|\ud83d\udce5|\ud83d\udce6|\ud83d\udce7|\ud83d\udce8|\ud83d\udce9|\ud83d\udcea|\ud83d\udceb|\ud83d\udcee|\ud83d\udcf0|\ud83d\udcf1|\ud83d\udcf2|\ud83d\udcf3|\ud83d\udcf4|\ud83d\udcf6|\ud83d\udcf7|\ud83d\udcf9|\ud83d\udcfa|\ud83d\udcfb|\ud83d\udcfc|\ud83d\udd03|\ud83d\udd0a|\ud83d\udd0b|\ud83d\udd0c|\ud83d\udd0d|\ud83d\udd0e|\ud83d\udd0f|\ud83d\udd10|\ud83d\udd11|\ud83d\udd12|\ud83d\udd13|\ud83d\udd14|\ud83d\udd16|\ud83d\udd17|\ud83d\udd18|\ud83d\udd19|\ud83d\udd1a|\ud83d\udd1b|\ud83d\udd1c|\ud83d\udd1d|\ud83d\udd1e|\ud83d\udd1f|\ud83d\udd20|\ud83d\udd21|\ud83d\udd22|\ud83d\udd23|\ud83d\udd24|\ud83d\udd25|\ud83d\udd26|\ud83d\udd27|\ud83d\udd28|\ud83d\udd29|\ud83d\udd2a|\ud83d\udd2b|\ud83d\udd2e|\ud83d\udd2f|\ud83d\udd30|\ud83d\udd31|\ud83d\udd32|\ud83d\udd33|\ud83d\udd34|\ud83d\udd35|\ud83d\udd36|\ud83d\udd37|\ud83d\udd38|\ud83d\udd39|\ud83d\udd3a|\ud83d\udd3b|\ud83d\udd3c|\ud83d\udd3d|\ud83d\udd50|\ud83d\udd51|\ud83d\udd52|\ud83d\udd53|\ud83d\udd54|\ud83d\udd55|\ud83d\udd56|\ud83d\udd57|\ud83d\udd58|\ud83d\udd59|\ud83d\udd5a|\ud83d\udd5b|\ud83d\uddfb|\ud83d\uddfc|\ud83d\uddfd|\ud83d\uddfe|\ud83d\uddff|\ud83d\ude01|\ud83d\ude02|\ud83d\ude03|\ud83d\ude04|\ud83d\ude05|\ud83d\ude06|\ud83d\ude09|\ud83d\ude0a|\ud83d\ude0b|\ud83d\ude0c|\ud83d\ude0d|\ud83d\ude0f|\ud83d\ude12|\ud83d\ude13|\ud83d\ude14|\ud83d\ude16|\ud83d\ude18|\ud83d\ude1a|\ud83d\ude1c|\ud83d\ude1d|\ud83d\ude1e|\ud83d\ude20|\ud83d\ude21|\ud83d\ude22|\ud83d\ude23|\ud83d\ude24|\ud83d\ude25|\ud83d\ude28|\ud83d\ude29|\ud83d\ude2a|\ud83d\ude2b|\ud83d\ude2d|\ud83d\ude30|\ud83d\ude31|\ud83d\ude32|\ud83d\ude33|\ud83d\ude35|\ud83d\ude37|\ud83d\ude38|\ud83d\ude39|\ud83d\ude3a|\ud83d\ude3b|\ud83d\ude3c|\ud83d\ude3d|\ud83d\ude3e|\ud83d\ude3f|\ud83d\ude40|\ud83d\ude45|\ud83d\ude46|\ud83d\ude47|\ud83d\ude48|\ud83d\ude49|\ud83d\ude4a|\ud83d\ude4b|\ud83d\ude4c|\ud83d\ude4d|\ud83d\ude4e|\ud83d\ude4f|\ud83d\ude80|\ud83d\ude83|\ud83d\ude84|\ud83d\ude85|\ud83d\ude87|\ud83d\ude89|\ud83d\ude8c|\ud83d\ude8f|\ud83d\ude91|\ud83d\ude92|\ud83d\ude93|\ud83d\ude95|\ud83d\ude97|\ud83d\ude99|\ud83d\ude9a|\ud83d\udea2|\ud83d\udea4|\ud83d\udea5|\ud83d\udea7|\ud83d\udea8|\ud83d\udea9|\ud83d\udeaa|\ud83d\udeab|\ud83d\udeac|\ud83d\udead|\ud83d\udeb2|\ud83d\udeb6|\ud83d\udeb9|\ud83d\udeba|\ud83d\udebb|\ud83d\udebc|\ud83d\udebd|\ud83d\udebe|\ud83d\udec0|\ud83c\udde6|\ud83c\udde7|\ud83c\udde8|\ud83c\udde9|\ud83c\uddea|\ud83c\uddeb|\ud83c\uddec|\ud83c\udded|\ud83c\uddee|\ud83c\uddef|\ud83c\uddf0|\ud83c\uddf1|\ud83c\uddf2|\ud83c\uddf3|\ud83c\uddf4|\ud83c\uddf5|\ud83c\uddf6|\ud83c\uddf7|\ud83c\uddf8|\ud83c\uddf9|\ud83c\uddfa|\ud83c\uddfb|\ud83c\uddfc|\ud83c\uddfd|\ud83c\uddfe|\ud83c\uddff|\ud83c\udf0d|\ud83c\udf0e|\ud83c\udf10|\ud83c\udf12|\ud83c\udf16|\ud83c\udf17|\ue50a|\u3030|\u27b0|\u2797|\u2796|\u2795|\u2755|\u2754|\u2753|\u274e|\u274c|\u2728|\u270b|\u270a|\u2705|\u26ce|\u23f3|\u23f0|\u23ec|\u23eb|\u23ea|\u23e9|\u2122|\u27bf|\u00a9|\u00ae)|(?:(?:\ud83c\udc04|\ud83c\udd7f|\ud83c\ude1a|\ud83c\ude2f|\u3299|\u303d|\u2b55|\u2b50|\u2b1c|\u2b1b|\u2b07|\u2b06|\u2b05|\u2935|\u2934|\u27a1|\u2764|\u2757|\u2747|\u2744|\u2734|\u2733|\u2716|\u2714|\u2712|\u270f|\u270c|\u2709|\u2708|\u2702|\u26fd|\u26fa|\u26f5|\u26f3|\u26f2|\u26ea|\u26d4|\u26c5|\u26c4|\u26be|\u26bd|\u26ab|\u26aa|\u26a1|\u26a0|\u2693|\u267f|\u267b|\u3297|\u2666|\u2665|\u2663|\u2660|\u2653|\u2652|\u2651|\u2650|\u264f|\u264e|\u264d|\u264c|\u264b|\u264a|\u2649|\u2648|\u263a|\u261d|\u2615|\u2614|\u2611|\u260e|\u2601|\u2600|\u25fe|\u25fd|\u25fc|\u25fb|\u25c0|\u25b6|\u25ab|\u25aa|\u24c2|\u231b|\u231a|\u21aa|\u21a9|\u2199|\u2198|\u2197|\u2196|\u2195|\u2194|\u2139|\u2049|\u203c|\u2668)([\uFE0E\uFE0F]?)))/g,SVGPATH:s,ZREKNARF:''+s+"",CHAT_BUTTON:''+s+"",ROOMS:'',CAMERA:'',INVITE:'',EYE:'',CLOCK:'',GEAR:'',HEART:'',EMOTE:'',STAR:''}},{}],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 s,t=t&&t.length?t[0].toLowerCase():null;return"y"==t||"yes"==t||"true"==t||"on"==t?s=!0:("n"==t||"no"==t||"false"==t||"off"==t)&&(s=!1),void 0===s?"Developer Mode is currently "+(this.settings.developer_mode?"enabled.":"disabled."):(this.settings.set("developer_mode",s),"Developer Mode is now "+(s?"enabled":"disabled")+". Please refresh your browser.")},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 s=e.FrankerFaceZ,o=t("../utils"),n=t("../constants");s.prototype.setup_channel=function(){this.log("Creating channel style element.");var e=this._channel_style=document.createElement("style");e.id="ffz-channel-css",document.head.appendChild(e),document.body.classList.toggle("ffz-hide-view-count",!this.settings.channel_views),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 Index view.");var t=App.__container__.resolve("view:channel/index"),o=this;if(t){this._modify_cindex(t);try{t.create().destroy()}catch(n){}for(var i in Ember.View.views)if(Ember.View.views.hasOwnProperty(i)){var a=Ember.View.views[i];a instanceof t&&(this.log("Manually updating Channel Index view.",a),this._modify_cindex(a),a.ffzInit())}this.log("Hooking the Ember Channel controller."),t=App.__container__.lookup("controller:channel"),t&&t.reopen({ffzUpdateUptime:function(){o._cindex&&o._cindex.ffzUpdateUptime()}.observes("isLive","content.id"),ffzUpdateTitle:function(){var e=this.get("content.name"),t=this.get("content.display_name");t&&(s.capitalization[e]=[t,Date.now()]),o._cindex&&o._cindex.ffzFixTitle()}.observes("content.status","content.id"),ffzHostTarget:function(){var e=this.get("content.hostModeTarget"),t=e&&e.get("name"),n=e&&e.get("id"),i=e&&e.get("display_name");n!==o.__old_host_target&&(o.__old_host_target&&o.ws_send("unsub_channel",o.__old_host_target),n?(o.ws_send("sub_channel",n),o.__old_host_target=n):delete o.__old_host_target),i&&(s.capitalization[t]=[i,Date.now()]),o.settings.group_tabs&&o._chatv&&o._chatv.ffzRebuildTabs(),o.settings.follow_buttons&&o.rebuild_following_ui(),o.settings.srl_races&&o.rebuild_race_ui()}.observes("content.hostModeTarget")})}},s.prototype._modify_cindex=function(e){var t=this;e.reopen({didInsertElement:function(){this._super();try{this.ffzInit()}catch(e){t.error("CIndex didInsertElement: "+e)}},willClearRender:function(){try{this.ffzTeardown()}catch(e){t.error("CIndex willClearRender: "+e)}return this._super()},ffzInit:function(){var e=this.get("controller.id"),s=this.get("element");t._cindex=this,t.ws_send("sub_channel",e),s.setAttribute("data-channel",e),s.classList.add("ffz-channel"),this.$(".theatre-button a").attr("title","Theater Mode (Alt+T)"),this.ffzFixTitle(),this.ffzUpdateUptime(),this.ffzUpdateChatters();var o=this.get("element").querySelector(".svg-glyph_views:not(.ffz-svg)");o&&o.parentNode.classList.add("twitch-channel-views"),t.settings.follow_buttons&&t.rebuild_following_ui(),t.settings.srl_races&&t.rebuild_race_ui()},ffzFixTitle:function(){if(!t.has_bttv&&t.settings.stream_title){var e=this.get("controller.status"),s=this.get("controller.id");e=t.render_tokens(t.tokenize_line(s,s,e,!0)),this.$(".title span").each(function(t,s){var o=s.querySelectorAll("script");s.innerHTML=o.length?o[0].outerHTML+e+o[1].outerHTML:e})}},ffzUpdateChatters:function(){var e=this.get("controller.id"),s=t.rooms&&t.rooms[e];if(!s||!t.settings.chatter_count){var i=this.get("element").querySelector("#ffz-chatter-display");return i&&i.parentElement.removeChild(i),i=this.get("element").querySelector("#ffz-ffzchatter-display"),void(i&&i.parentElement.removeChild(i))}var a=Object.keys(s.room.get("ffz_chatters")||{}).length,r=s.ffz_chatters||0,d=s.ffz_viewers||0,i=this.get("element").querySelector("#ffz-chatter-display span");if(!i){var u=this.get("element").querySelector(".stats-and-actions .channel-stats");if(!u)return;var c=document.createElement("span");c.className="ffz stat",c.id="ffz-chatter-display",c.title="Currently in Chat",c.innerHTML=n.ROOMS+" ",i=document.createElement("span"),c.appendChild(i);var l=u.querySelector("#ffz-ffzchatter-display");l?u.insertBefore(c,l):u.appendChild(c),jQuery(c).tipsy()}if(i.innerHTML=o.number_commas(a),!r&&!d)return i=this.get("element").querySelector("#ffz-ffzchatter-display"),void(i&&i.parentNode.removeChild(i));if(i=this.get("element").querySelector("#ffz-ffzchatter-display span"),!i){var u=this.get("element").querySelector(".stats-and-actions .channel-stats");if(!u)return;var c=document.createElement("span");c.className="ffz stat",c.id="ffz-ffzchatter-display",c.title="Viewers (In Chat) with FrankerFaceZ",c.innerHTML=n.ZREKNARF+" ",i=document.createElement("span"),c.appendChild(i);var l=u.querySelector("#ffz-chatter-display");l?u.insertBefore(c,l.nextSibling):u.appendChild(c),jQuery(c).tipsy()}i.innerHTML=o.number_commas(d)+" ("+o.number_commas(r)+")"},ffzUpdateUptime:function(){if(this._ffz_update_uptime&&(clearTimeout(this._ffz_update_uptime),delete this._ffz_update_uptime),!t.settings.stream_uptime||!this.get("controller.isLiveAccordingToKraken")){var e=this.get("element").querySelector("#ffz-uptime-display");return void(e&&e.parentElement.removeChild(e))}this._ffz_update_uptime=setTimeout(this.ffzUpdateUptime.bind(this),1e3);var s=this.get("controller.content.stream.created_at");if(s&&(s=o.parse_date(s))){var i=Math.floor((Date.now()-s.getTime())/1e3);if(!(0>i)){var e=this.get("element").querySelector("#ffz-uptime-display span");if(!e){var a=this.get("element").querySelector(".stats-and-actions .channel-stats");if(!a)return;var r=document.createElement("span");r.className="ffz stat",r.id="ffz-uptime-display",r.title="Stream Uptime (since "+s.toLocaleString()+")",r.innerHTML=n.CLOCK+" ",e=document.createElement("span"),r.appendChild(e);var d=a.querySelector(".live-count");if(d)a.insertBefore(r,d.nextSibling); -else try{d=a.querySelector("script:nth-child(0n+2)"),a.insertBefore(r,d.nextSibling)}catch(u){a.insertBefore(r,a.childNodes[0])}jQuery(r).tipsy({html:!0})}e.innerHTML=o.time_to_string(i)}}},ffzTeardown:function(){var e=this.get("controller.id");e&&t.ws_send("unsub_channel",e),this.get("element").setAttribute("data-channel",""),t._cindex=void 0,this._ffz_update_uptime&&clearTimeout(this._ffz_update_uptime),o.update_css(t._channel_style,e,null)}})},s.settings_info.chatter_count={type:"boolean",value:!1,category:"Channel Metadata",name:"Chatter Count",help:"Display the current number of users connected to chat beneath the channel.",on_update:function(e){if(this._cindex&&this._cindex.ffzUpdateChatters(),e&&this.rooms)for(var t in this.rooms)this.rooms.hasOwnProperty(t)&&this.rooms[t].room&&this.rooms[t].room.ffzInitChatterCount()}},s.settings_info.channel_views={type:"boolean",value:!0,category:"Channel Metadata",name:"Channel Views",help:"Display the number of times the channel has been viewed beneath the stream.",on_update:function(e){document.body.classList.toggle("ffz-hide-view-count",!e)}},s.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._cindex&&this._cindex.ffzUpdateUptime()}},s.settings_info.stream_title={type:"boolean",value:!0,no_bttv:!0,category:"Channel Metadata",name:"Title Links",help:"Make links in stream titles clickable.",on_update:function(){this._cindex&&this._cindex.ffzFixTitle()}}},{"../constants":3,"../utils":31}],6:[function(t){var s=e.FrankerFaceZ,o=(t("../utils"),t("../constants"),{BACKSPACE:8,TAB:9,ENTER:13,ESC:27,SPACE:32,LEFT:37,UP:38,RIGHT:39,DOWN:40,TWO:50,COLON:186}),n=function(e){if("number"==typeof e.selectionStart)return e.selectionStart;if(!e.createTextRange)return-1;var t=document.selection.createRange(),s=e.createTextRange();return s.moveToBookmark(t.getBookmark()),s.moveStart("character",-e.value.length),s.text.length},i=function(e,t){if(e.setSelectionRange)e.setSelectionRange(t,t);else if(e.createTextRange){var s=e.createTextRange();s.move("character",-e.value.length),s.move("character",t),s.select()}};s.settings_info.input_quick_reply={type:"boolean",value:!0,category:"Chat Input",no_bttv:!0,name:"Reply to Whispers with /r",help:"Automatically replace /r at the start of the line with the command to whisper to the person you've whispered with most recently."},s.settings_info.input_mru={type:"boolean",value:!0,category:"Chat Input",no_bttv:!0,name:"Chat Input History",help:"Use the Up and Down arrows in chat to select previously sent chat messages."},s.settings_info.input_emoji={type:"boolean",value:!1,category:"Chat Input",visible:!1,no_bttv:!0,name:"Enter Emoji By Name",help:"Replace emoji that you type by name with the character. :+1: becomes 👍."},s.prototype.setup_chat_input=function(){this.log("Hooking the Ember Chat Input controller.");var e=App.__container__.resolve("component:twitch-chat-input");if(e&&(this._modify_chat_input(e),this._roomv))for(var t=0;te?"":e>=99?"99+":""+e};s.settings_info.swap_sidebars={type:"boolean",value:!1,category:"Miscellaneous",no_bttv:!0,name:"Swap Sidebar Positions",help:"Swap the positions of the left and right sidebars, placing chat on the left.",on_update:function(e){this.has_bttv||document.body.classList.toggle("ffz-sidebar-swap",e)}},s.settings_info.minimal_chat={type:"boolean",value:!1,category:"Chat",name:"Minimalistic Chat",help:"Hide all of the chat user interface, only showing messages and an input box.",on_update:function(e){if(document.body.classList.toggle("ffz-minimal-chat",e),this.settings.group_tabs&&this._chatv&&this._chatv._ffz_tabs){var t=this;setTimeout(function(){t._chatv&&t._chatv.$(".chat-room").css("top",t._chatv._ffz_tabs.offsetHeight+"px"),t._roomv&&t._roomv.get("stuckToBottom")&&t._roomv._scrollToBottom()},0)}}},s.settings_info.prevent_clear={type:"boolean",value:!1,no_bttv:!0,category:"Chat Moderation",name:"Show Deleted Messages",help:"Fade deleted messages instead of replacing them, and prevent chat from being cleared.",on_update:function(e){if(!this.has_bttv&&this.rooms)for(var t in this.rooms){var s=this.rooms[t],o=s&&s.room;o&&o.get("messages").forEach(function(t,s){e&&!t.ffz_deleted&&t.deleted?o.set("messages."+s+".deleted",!1):!t.ffz_deleted||e||t.deleted||o.set("messages."+s+".deleted",!0)})}}},s.settings_info.chat_history={type:"boolean",value:!0,visible:!1,category:"Chat",name:"Chat History Alpha",help:"Load previous chat messages when loading a chat room so you can see what people have been talking about. This currently only works in a handful of channels due to server capacity."},s.settings_info.group_tabs={type:"boolean",value:!1,no_bttv:!0,category:"Chat",name:"Chat Room Tabs Beta",help:"Enhanced UI for switching the current chat room and noticing new messages.",on_update:function(e){var t=!this.has_bttv&&e;this._chatv&&t!==this._group_tabs_state&&(t?this._chatv.ffzEnableTabs():this._chatv.ffzDisableTabs())}},s.settings_info.pinned_rooms={type:"button",value:[],category:"Chat",visible:!1,name:"Pinned Chat Rooms",help:"Set a list of channels that should always be available in chat."},s.prototype.setup_chatview=function(){document.body.classList.toggle("ffz-minimal-chat",this.settings.minimal_chat),this.has_bttv||document.body.classList.toggle("ffz-sidebar-swap",this.settings.swap_sidebars),this.log("Hooking the Ember Chat controller.");var e=App.__container__.lookup("controller:chat"),t=this;e&&e.reopen({ffzUpdateChannels:function(){t.settings.group_tabs&&t._chatv&&t._chatv.ffzRebuildTabs()}.observes("currentChannelRoom","connectedPrivateGroupRooms"),removeCurrentChannelRoom:function(){if(!t.settings.group_tabs||t.has_bttv)return this._super();var e=this.get("currentChannelRoom"),s=e&&e.get("id");t.settings.pinned_rooms&&-1!==t.settings.pinned_rooms.indexOf(s)||(e===this.get("currentRoom")&&this.blurRoom(),e&&e.destroy()),this.set("currentChannelRoom",void 0)}}),this.log("Hooking the Ember Chat view.");var e=App.__container__.resolve("view:chat");this._modify_cview(e);try{e.create().destroy()}catch(s){}for(var o in Ember.View.views)if(Ember.View.views.hasOwnProperty(o)){var n=Ember.View.views[o];if(n instanceof e){this.log("Manually updating existing Chat view.",n);try{n.ffzInit()}catch(s){this.error("setup: build_ui_link: "+s)}}}this.log("Hooking the Ember Layout controller.");var i=App.__container__.lookup("controller:layout");if(i){i.reopen({ffzFixTabs:function(){t.settings.group_tabs&&t._chatv&&t._chatv._ffz_tabs&&setTimeout(function(){t._chatv&&t._chatv.$(".chat-room").css("top",t._chatv._ffz_tabs.offsetHeight+"px")},0)}.observes("isRightColumnClosed")}),this.log("Hooking the Ember 'Right Column' controller. Seriously...");var a=App.__container__.lookup("controller:right-column");a&&a.reopen({ffzFixTabs:function(){t.settings.group_tabs&&t._chatv&&t._chatv._ffz_tabs&&setTimeout(function(){t._chatv&&t._chatv.$(".chat-room").css("top",t._chatv._ffz_tabs.offsetHeight+"px")},0)}.observes("firstTabSelected")})}},s.prototype._modify_cview=function(e){var t=this;e.reopen({didInsertElement:function(){this._super();try{this.ffzInit()}catch(e){t.error("ChatView didInsertElement: "+e)}},willClearRender:function(){try{this.ffzTeardown()}catch(e){t.error("ChatView willClearRender: "+e)}this._super()},ffzInit:function(){t._chatv=this,this.$(".textarea-contain").append(t.build_ui_link(this)),!t.has_bttv&&t.settings.group_tabs&&this.ffzEnableTabs(),setTimeout(function(){t.settings.group_tabs&&t._chatv._ffz_tabs&&t._chatv.$(".chat-room").css("top",t._chatv._ffz_tabs.offsetHeight+"px");var e=t._chatv.get("controller");e&&e.set("showList",!1)},1e3)},ffzTeardown:function(){t._chatv===this&&(t._chatv=null),this.$(".textarea-contain .ffz-ui-toggle").remove(),t.settings.group_tabs&&this.ffzDisableTabs()},ffzChangeRoom:Ember.observer("controller.currentRoom",function(){try{if(t.update_ui_link(),!t.has_bttv&&t.settings.group_tabs&&this._ffz_tabs){var e=this.get("controller.currentRoom");e&&e.resetUnreadCount();var s=jQuery(this._ffz_tabs);s.children(".ffz-chat-tab").removeClass("active"),e&&s.children('.ffz-chat-tab[data-room="'+e.get("id")+'"]').removeClass("tab-mentioned").addClass("active").children("span").text("");var o=e&&e.get("canInvite");this._ffz_invite&&this._ffz_invite.classList.toggle("hidden",!o),this.set("controller.showInviteUser",o&&this.get("controller.showInviteUser")),this.$(".chat-room").css("top",this._ffz_tabs.offsetHeight+"px")}}catch(n){t.error("ChatView ffzUpdateLink: "+n)}}),ffzEnableTabs:function(){if(!t.has_bttv&&t.settings.group_tabs){this.$(".chat-header").addClass("hidden");var e=this._ffz_tabs=document.createElement("div");e.id="ffz-group-tabs",this.$(".chat-header").after(e),this.ffzRebuildTabs()}},ffzRebuildTabs:function(){if(!t.has_bttv&&t.settings.group_tabs){var e=this._ffz_tabs||this.get("element").querySelector("#ffz-group-tabs");if(e){e.innerHTML="";var s=document.createElement("a"),o=this;s.className="button glyph-only tooltip",s.title="Chat Room Management",s.innerHTML=n.ROOMS,s.addEventListener("click",function(){var e=o.get("controller");e&&e.set("showList",!e.get("showList"))}),e.appendChild(s),s=document.createElement("a"),s.className="button glyph-only tooltip invite",s.title="Invite a User",s.innerHTML=n.INVITE,s.addEventListener("click",function(){var e=o.get("controller");e&&e.set("showInviteUser",e.get("currentRoom.canInvite")&&!e.get("showInviteUser"))}),s.classList.toggle("hidden",!this.get("controller.currentRoom.canInvite")),o._ffz_invite=s,e.appendChild(s);var i,a=this.get("controller.currentChannelRoom");a&&(i=this.ffzBuildTab(o,a,!0),i&&e.appendChild(i));var r=App.__container__.lookup("controller:channel"),d=App.__container__.resolve("model:room");if(target=r&&r.get("hostModeTarget"),target&&d){var u=target.get("id");this._ffz_host!==u&&(-1===t.settings.pinned_rooms.indexOf(this._ffz_host)&&this._ffz_host_room&&(this.get("controller.currentRoom")===this._ffz_host_room&&this.get("controller").blurRoom(),this._ffz_host_room.destroy()),this._ffz_host=u,this._ffz_host_room=d.findOne(u))}else this._ffz_host&&(-1===t.settings.pinned_rooms.indexOf(this._ffz_host)&&this._ffz_host_room&&(this.get("controller.currentRoom")===this._ffz_host_room&&this.get("controller").blurRoom(),this._ffz_host_room.destroy()),delete this._ffz_host,delete this._ffz_host_room);this._ffz_host_room&&(i=o.ffzBuildTab(o,this._ffz_host_room,!1,!0),i&&e.appendChild(i));for(var c=0;c"+u+""})),a?(l=n.CAMERA,c.title="Current Channel"):r?(l=n.EYE,c.title="Hosted Channel"):c.title=h?"Group Chat":"Pinned Channel",c.innerHTML=l+o.sanitize(d)+""+u+"",c.addEventListener("click",function(){e.get("controller").focusRoom(t)}),c},ffzDisableTabs:function(){this._ffz_tabs&&(this._ffz_tabs.parentElement.removeChild(this._ffz_tabs),delete this._ffz_tabs,delete this._ffz_invite),this._ffz_host&&(-1===t.settings.pinned_rooms.indexOf(this._ffz_host)&&this._ffz_host_room&&(this.get("controller.currentRoom")===this._ffz_host_room&&this.get("controller").blurRoom(),this._ffz_host_room.destroy()),delete this._ffz_host,delete this._ffz_host_room),this.$(".chat-room").css("top",""),this.$(".chat-header").removeClass("hidden")}})},s.prototype.connect_extra_chat=function(){if(!this.has_bttv){for(var e=0;e1)return"Join Usage: /join ";var s=t[0].toLowerCase();return"#"===s.charAt(0)&&(s=s.substr(1)),this._join_room(s)?"Joining "+s+". You will always connect to this channel's chat unless you later /part from it.":"You have already joined "+s+'. Please use "/part '+s+'" to leave it.'},s.chat_commands.part=function(e,t){if(!t||!t.length||t.length>1)return"Part Usage: /part ";var s=t[0].toLowerCase();return"#"===s.charAt(0)&&(s=s.substr(1)),this._leave_room(s)?"Leaving "+s+".":this.rooms[s]?"You do not have "+s+" pinned and you cannot leave the current channel or hosted channels via /part.":"You are not in "+s+"."}},{"../constants":3,"../utils":31}],8:[function(t){var s=e.FrankerFaceZ,o=t("../utils"),n="[\\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]",i=new RegExp(n+"*,"+n+"*"),a=function(e){return(e+"").replace(/&/g,"&").replace(/'/g,"'").replace(/"/g,""").replace(//g,">")},r="http://static-cdn.jtvnw.net/emoticons/v1/",d={};build_srcset=function(e){if(d[e])return d[e];var t=d[e]=r+e+"/1.0 1x, "+r+e+"/2.0 2x, "+r+e+"/3.0 4x";return t},data_to_tooltip=function(e){var t=e.set,s=e.set_type,o=e.owner;return void 0===s&&(s="Channel"),t?(("--twitch-turbo--"==t||"turbo"==t)&&(t="Twitch Turbo",s=null),"Emoticon: "+e.code+"\n"+(s?s+": ":"")+t+(o?"\nBy: "+o.display_name:"")):e.code},build_tooltip=function(e){{var t=this._twitch_emotes[e];t?t.set:null}return t?"string"==typeof t?t:t.tooltip?t.tooltip:t.tooltip=data_to_tooltip(t):"???"},load_emote_data=function(e,t,s,o){if(s){t&&(o.code=t),this._twitch_emotes[e]=o;for(var n=build_tooltip.bind(this)(e),i=document.querySelectorAll('img[emote-id="'+e+'"]'),a=0;aYouTube: "+o.sanitize(s.title)+"
",t+="Channel: "+o.sanitize(s.channel)+" | "+o.time_to_string(s.duration)+"
",t+=o.number_commas(s.views||0)+" Views | 👍 "+o.number_commas(s.likes||0)+" 👎 "+o.number_commas(s.dislikes||0);else if("strawpoll"==s.type){t="Strawpoll: "+o.sanitize(s.title)+"
";for(var n in s.items){{var i=s.items[n];Math.floor(i/s.total*100)}t+='"}t+="
'+o.sanitize(n)+''+o.number_commas(i)+"

Total: "+o.number_commas(s.total);var a=o.parse_date(s.fetched);if(a){var r=Math.floor((a.getTime()-Date.now())/1e3);r>60&&(t+="
Data was cached "+o.time_to_string(r)+" ago.")}}else if("twitch"==s.type){t="Twitch: "+o.sanitize(s.display_name)+"
";var d=o.parse_date(s.since);d&&(t+="Member Since: "+o.date_string(d)+"
"),t+="Views: "+o.number_commas(s.views)+" | Followers: "+o.number_commas(s.followers)+""}else if("twitch_vod"==s.type)t="Twitch "+("highlight"==s.broadcast_type?"Highlight":"Broadcast")+": "+o.sanitize(s.title)+"
",t+="By: "+o.sanitize(s.display_name)+(s.game?" | Playing: "+o.sanitize(s.game):" | Not Playing")+"
",t+="Views: "+o.number_commas(s.views)+" | "+o.time_to_string(s.length);else if("twitter"==s.type)t="Tweet By: "+o.sanitize(s.user)+"
",t+=o.sanitize(s.tweet);else if("reputation"==s.type){if(t=''+o.sanitize(s.full.toLowerCase())+"",s.trust<50||s.safety<50||s.tags&&s.tags.length>0){t+="
";var u=!1;(s.trust<50||s.safety<50)&&(s.unsafe=!0,t+="Potentially Unsafe Link
",t+="Trust: "+s.trust+"% | Child Safety: "+s.safety+"%",u=!0),s.tags&&s.tags.length>0&&(t+=(u?"
":"")+"Tags: "+s.tags.join(", ")),t+="
Data Source: WOT"}}else s.full&&(t=''+o.sanitize(s.full.toLowerCase())+"");return t||(t=''+o.sanitize(e.toLowerCase())+""),s.tooltip=t,t},load_link_data=function(e,t,s){if(t){this._link_data[e]=s,s.unsafe=!1;var o,n=build_link_tooltip.bind(this)(e),i="/"==e.charAt(e.length-1)?e.substr(0,e.length-1):null;if(o=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 a=0;ae&&(e=10),this.settings.set("scrollback_length",e);var t=App.__container__.lookup("controller:chat"),s=t&&t.get("currentRoom.id");for(var o in this.rooms){var n=this.rooms[o];n.room.set("messageBufferSize",e+(this._roomv&&!this._roomv.get("stuckToBottom")&&s===o?150:0))}}}},s.settings_info.banned_words={type:"button",value:[],category:"Chat",no_bttv:!0,name:"Banned Words",help:"Set a list of words that will be locally removed from chat messages.",method:function(){var e=this.settings.banned_words.join(", "),t=prompt("Banned Words\n\nPlease enter a comma-separated list of words that you would like to be removed from chat messages.",e);if(null!==t&&void 0!==t){t=t.trim().split(i);for(var s=[],o=0;oBeta",help:"Check links against known bad websites, unshorten URLs, and show YouTube info."},s.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)}},s.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 e=this._fix_color_style=document.createElement("style");e.id="ffz-style-username-colors",e.type="text/css",document.head.appendChild(e),this._twitch_emotes={},this._link_data={},this.log("Hooking the Ember Whisper controller.");var t=App.__container__.resolve("component:whisper-line");t&&this._modify_line(t),this.log("Hooking the Ember Line controller.");var o=App.__container__.resolve("component:message-line");o&&this._modify_line(o);var n=this.get_user();n&&n.name&&(s.capitalization[n.login]=[n.name,Date.now()])},s.prototype._modify_line=function(e){var t=this;e.reopen({tokenizedMessage:function(){var e=this.get("msgObject.cachedTokens");if(e)return e;e=this._super();try{var o=performance.now(),n=t.get_user(),i=n&&this.get("msgObject.from")===n.login;e=t._remove_banned(e),e=t._emoticonize(this,e),t.settings.parse_emoji&&(e=t.tokenize_emoji(e));var a=this.get("msgObject.tags.display-name");a&&a.length&&(s.capitalization[this.get("msgObject.from")]=[a.trim(),Date.now()]),i||(e=t.tokenize_mentions(e));for(var r=0;r5&&t.log("Tokenizing Message Took Too Long - "+(u-o)+"ms",e,!1,!0)}catch(c){try{t.error("LineController tokenizedMessage: "+c)}catch(c){}}return this.set("msgObject.cachedTokens",e),e}.property("msgObject.message","isChannelLinksDisabled","currentUserNick","msgObject.from","msgObject.tags.emotes"),ffzUpdated:Ember.observer("msgObject.ffz_deleted","msgObject.ffz_old_messages",function(){this.rerender()}),willClearRender:function(){try{this.$("a.mod-icon").tipsy("disable"),jQuery("body > .tipsy:last").remove()}catch(e){t.error("LineView willClearRender: "+e)}this._super()},didInsertElement:function(){this._super();try{var e=performance.now(),n=this.get("element"),i=this.get("msgObject.from"),a=this.get("msgObject.room")||App.__container__.lookup("controller:chat").get("currentRoom.id"),r=this.get("msgObject.color"),d=this.get("msgObject.ffz_alternate");r&&t._handle_color(r),void 0===d&&(d=t._last_row[a]=t._last_row.hasOwnProperty(a)?!t._last_row[a]:!1,this.set("msgObject.ffz_alternate",d)),n.classList.toggle("ffz-alternate",d||!1),n.classList.toggle("ffz-deleted",t.settings.prevent_clear&&this.get("msgObject.ffz_deleted")||!1),n.setAttribute("data-room",a),n.setAttribute("data-sender",i),n.setAttribute("data-deleted",this.get("msgObject.deleted")||!1);var u=this.get("msgObject.ffz_old_messages");if(u&&u.length){var c=document.createElement("div");c.className="button primary float-right",c.innerHTML="Show "+o.number_commas(u.length)+" Old",c.addEventListener("click",t._show_deleted.bind(t,a)),n.classList.add("clearfix"),n.classList.add("ffz-has-deleted"),this.$(".message").append(c)}t.render_badge(this),this.get("msgObject.ffz_has_mention")&&n.classList.add("ffz-mentioned");for(var l=n.querySelectorAll("span.message a.deleted-link"),h=0;h5&&t.log("Line Took Too Long - "+x+"ms",n.innerHTML,!1,!0)}catch(F){try{t.error("LineView didInsertElement: "+F)}catch(F){}}}})},s.prototype._handle_color=function(e){if(e&&!this._colors[e]){this._colors[e]=!0;var t=parseInt(e.substr(1),16),s=[t>>16,t>>8&255,255&t],n=o.get_luminance(s),i="",a='span[style="color:'+e+'"]',r=!1;if(n>.3){r=!0;for(var d=127,u=s;d--&&(u=o.darken(u),!(o.get_luminance(u)<=.3)););i+=".ffz-chat-colors .ember-chat-container:not(.dark) .chat-line "+a+", .ffz-chat-colors .chat-container:not(.dark) .chat-line "+a+" { color: "+o.rgb_to_css(u)+" !important; }\n"}else i+=".ffz-chat-colors .ember-chat-container:not(.dark) .chat-line "+a+", .ffz-chat-colors .chat-container:not(.dark) .chat-line "+a+" { color: "+e+" !important; }\n";if(.15>n){r=!0;for(var d=127,u=s;d--&&(u=o.brighten(u),!(o.get_luminance(u)>=.15)););i+=".ffz-chat-colors .theatre .chat-container .chat-line "+a+", .ffz-chat-colors .chat-container.dark .chat-line "+a+", .ffz-chat-colors .ember-chat-container.dark .chat-line "+a+" { color: "+o.rgb_to_css(u)+" !important; }\n"}else i+=".ffz-chat-colors .theatre .chat-container .chat-line "+a+", .ffz-chat-colors .chat-container.dark .chat-line "+a+", .ffz-chat-colors .ember-chat-container.dark .chat-line "+a+" { color: "+e+" !important; }\n"; -r&&(this._fix_color_style.innerHTML+=i)}},s.capitalization={},s._cap_fetching=0,s.get_capitalization=function(e,t){if(!e)return e;if(e=e.toLowerCase(),"jtv"==e||"twitchnotify"==e)return e;var o=s.capitalization[e];return o&&Date.now()-o[1]<36e5?o[0]:(s._cap_fetching<25&&(s._cap_fetching++,s.get().ws_send("get_display_name",e,function(o,n){var i=o?n:e;s.capitalization[e]=[i,Date.now()],s._cap_fetching--,"function"==typeof t&&t(i)})),o?o[0]:e)},s.prototype._remove_banned=function(e){var t=this.settings.banned_words;if(!t||!t.length)return e;"string"==typeof e&&(e=[e]);for(var o=s._words_to_regex(t),n=[],i=0;i<banned link>',own:!0}:r)}return n},s.prototype._emoticonize=function(e,t){var s=e.get("msgObject.room"),o=e.get("msgObject.from");return this.tokenize_emotes(o,s,t)}},{"../utils":31}],9:[function(t){var s,o=e.FrankerFaceZ,n=t("../utils"),i={ESC:27,P:80,B:66,T:84,U:85},a='',r='',d={},u=function(e){if(1===e)return"Purge";if(d[e])return d[e];var t,s,o,n,i;t=Math.floor(e/604800),i=e%604800,s=Math.floor(i/86400),i%=86400,o=Math.floor(i/3600),i%=3600,n=Math.floor(i/60),i%=60;var a=d[e]=(t?t+"w":"")+(s||t&&(o||n||i)?s+"d":"")+(o||(t||s)&&(n||i)?o+"h":"")+(n||(t||s||o)&&i?n+"m":"")+(i?i+"s":"");return a};try{s=e.require&&e.require("ember-twitch-chat/helpers/chat-line-helpers")}catch(c){}o.settings_info.chat_hover_pause={type:"boolean",value:!1,no_bttv:!0,category:"Chat Moderation",name:"Pause Chat Scrolling on Mouse Hover",help:"Automatically prevent the chat from scrolling when moving the mouse over it to prevent moderation mistakes and link mis-clicks.",on_update:function(e){this._roomv&&(e?this._roomv.ffzEnableFreeze():this._roomv.ffzDisableFreeze())}},o.settings_info.short_commands={type:"boolean",value:!0,no_bttv:!0,category:"Chat Moderation",name:"Short Moderation Commands",help:"Use /t, /b, and /u in chat in place of /timeout, /ban, /unban for quicker moderation, and use /p for 1 second timeouts."},o.settings_info.mod_card_hotkeys={type:"boolean",value:!1,no_bttv:!0,category:"Chat Moderation",name:"Moderation Card Hotkeys",help:"With a moderation card selected, press B to ban the user, T to time them out for 10 minutes, P to time them out for 1 second, or U to unban them. ESC closes the card."},o.settings_info.mod_card_history={type:"boolean",value:!1,no_bttv:!0,category:"Chat Moderation",name:"Moderation Card History",help:"Display a few of the user's previously sent messages on moderation cards.",on_update:function(e){if(!e&&this.rooms)for(var t in this.rooms){var s=this.rooms[t];s&&(s.user_history=void 0)}}},o.settings_info.mod_card_durations={type:"button",value:[300,600,3600,43200,86400,604800],category:"Chat Moderation",no_bttv:!0,name:"Moderation Card Timeout Buttons",help:"Add additional timeout buttons to moderation cards with specific durations.",method:function(){var e=this.settings.mod_card_durations.join(", "),t=prompt('Moderation Card Timeout Buttons\n\nPlease enter a comma-separated list of durations that you would like to have timeout buttons for. Durations must be expressed in seconds.\n\nEnter "reset" without quotes to return to the default value.',e);if(null!==t&&void 0!==t){"reset"===t&&(t=o.settings_info.mod_card_durations.value.join(", ")),t=t.trim().split(/[ ,]+/);for(var s=[],n=0;n0&&s.push(i)}this.settings.set("mod_card_durations",s)}}},o.prototype.setup_mod_card=function(){this.log("Modifying Mousetrap stopCallback so we can catch ESC.");var t=Mousetrap.stopCallback;Mousetrap.stopCallback=function(e,s,o){return s.classList.contains("no-mousetrap")?!0:t(e,s,o)},Mousetrap.bind("up up down down left right left right b a enter",function(){var e=document.querySelector(".app-main")||document.querySelector(".ember-chat-container");e&&e.classList.toggle("ffz-flip")}),this.log("Hooking the Ember Moderation Card view.");var o=App.__container__.resolve("component:moderation-card"),d=this;o.reopen({ffzForceRedraw:function(){this.rerender()}.observes("cardInfo.isModeratorOrHigher","cardInfo.user"),didInsertElement:function(){this._super(),e._card=this;try{if(d.has_bttv)return;var t=this.get("element"),o=this.get("controller");if((d.settings.mod_card_hotkeys||d.settings.mod_card_durations&&d.settings.mod_card_durations.length)&&t.classList.add("ffz-moderation-card"),o.get("cardInfo.isModeratorOrHigher")){t.classList.add("ffz-is-mod"),t.setAttribute("tabindex",1),d.settings.mod_card_hotkeys&&(t.classList.add("no-mousetrap"),t.addEventListener("keyup",function(e){var t=e.keyCode||e.which,s=o.get("cardInfo.user.id"),n=App.__container__.lookup("controller:chat").get("currentRoom");if(t==i.P)n.send("/timeout "+s+" 1");else if(t==i.B)n.send("/ban "+s);else if(t==i.T)n.send("/timeout "+s+" 600");else if(t==i.U)n.send("/unban "+s);else if(t!=i.ESC)return;o.send("close")}));var c=function(e){var t=o.get("cardInfo.user.id"),s=App.__container__.lookup("controller:chat").get("currentRoom");s.send(-1===e?"/unban "+t:"/timeout "+t+" "+e)},l=function(e){var t=document.createElement("button");return t.className="button",t.innerHTML=u(e),t.title="Timeout User for "+n.number_commas(e)+" Second"+(1!=e?"s":""),d.settings.mod_card_hotkeys&&600===e?t.title="(T)"+t.title.substr(1):d.settings.mod_card_hotkeys&&1===e&&(t.title="(P)urge - "+t.title),jQuery(t).tipsy(),t.addEventListener("click",c.bind(this,e)),t};if(d.settings.mod_card_durations&&d.settings.mod_card_durations.length){var h=document.createElement("div");h.className="interface clearfix",h.appendChild(l(1));var f=document.createElement("span");f.className="right",h.appendChild(f);for(var _=0;_ button");if(y&&y.classList.contains("message-button")&&(y.innerHTML=a,y.classList.add("glyph-only"),y.classList.add("message"),y.title="Whisper User",jQuery(y).tipsy()),d.settings.mod_card_history){var w=App.__container__.lookup("controller:chat"),z=w&&w.get("currentRoom"),k=z&&d.rooms&&d.rooms[z.get("id")],E=k&&k.user_history&&k.user_history[o.get("cardInfo.user.id")];if(E&&E.length){var C=document.createElement("ul"),T=!1;C.className="interface clearfix chat-history";for(var _=0;_'+s.getTime(h.date)+" ":"")+''+("action"===h.style?"*"+h.from+" ":"")+d.render_tokens(h.cachedTokens)+"";for(var F=x.querySelectorAll("a.deleted-link"),S=0;SA.bottom){var M=L.bottom-A.bottom;L.top-M>A.top&&(t.style.top=L.top-M+"px")}this.$().draggable({start:function(){t.focus()}}),t.focus()}catch(R){try{d.error("ModerationCardView didInsertElement: "+R)}catch(R){}}}})},o.chat_commands.purge=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 s=0;s10)return"Please only ban up to 10 users at once.";for(var s=0;s10)return"Please only unban up to 10 users at once.";for(var s=0;s750&&this.ffzUnfreeze()}},ffzUnfreeze:function(){this.ffz_frozen=!1,this._ffz_last_move=0,this.ffzUnwarnPaused(),this.get("stuckToBottom")&&this._scrollToBottom()},ffzMouseOut:function(){this._ffz_outside=!0;var e=this;setTimeout(function(){e._ffz_outside&&e.ffzUnfreeze()},25)},ffzMouseMove:function(e){this._ffz_last_move=Date.now(),this._ffz_outside=!1,(e.screenX!==this._ffz_last_screenx||e.screenY!==this._ffz_last_screeny)&&(this._ffz_last_screenx=e.screenX,this._ffz_last_screeny=e.screenY,this.ffz_frozen||e.clientY>=this._ffz_messages.getBoundingClientRect().bottom-21||(this.ffz_frozen=!0,this.get("stuckToBottom")&&(this.set("controller.model.messageBufferSize",t.settings.scrollback_length+150),this.ffzWarnPaused())))},_scrollToBottom:_.throttle(function(){var e=this,t=this._$chatMessagesScroller;Ember.run.next(function(){setTimeout(function(){!e.ffz_frozen&&t&&t.length&&(t.scrollTop(t[0].scrollHeight),e._setStuckToBottom(!0))})})},200),_setStuckToBottom:function(e){this.set("stuckToBottom",e),this.get("controller.model")&&this.set("controller.model.messageBufferSize",t.settings.scrollback_length+(e?0:150))},ffzWarnPaused:function(){var e=this.get("element"),t=e&&e.querySelector(".chat-interface .more-messages-indicator.ffz-freeze-indicator");if(e){if(!t){t=document.createElement("div"),t.className="more-messages-indicator ffz-freeze-indicator",t.innerHTML="(Chat Paused Due to Mouse Movement)";var s=e.querySelector(".chat-interface");if(!s)return;s.insertBefore(t,s.childNodes[0])}t.classList.remove("hidden")}},ffzUnwarnPaused:function(){var e=this.get("element"),t=e&&e.querySelector(".chat-interface .more-messages-indicator.ffz-freeze-indicator");t&&t.classList.add("hidden")}})},s.chat_commands={},s.ffz_commands={},s.prototype.room_message=function(e,t){var s=t.split("\n");if(this.has_bttv)for(var o=0;o300,f=t.length,_=o.get("messages.0.ffz_alternate")||!1;h&&(_=!_);for(var f=t.length;f--;){var m=t[f];if("string"==typeof m.date&&(m.date=n.parse_date(m.date)),m.ffz_alternate=_=!_,m.room||(m.room=e),m.color||(m.color=m.tags&&m.tags.color?m.tags.color:a&&m.from?a.getColor(m.from.toLowerCase()):"#755000"),!m.labels||!m.labels.length){var p=m.labels=[];if(m.tags)if(m.tags.turbo&&p.push("turbo"),m.tags.subscriber&&p.push("subscriber"),m.from===e)p.push("owner");else{var g=m.tags["user-type"];("mod"===g||"staff"===g||"admin"===g||"global_mod"===g)&&p.push(g)}}if(m.style||("jtv"===m.from?m.style="admin":"twitchnotify"===m.from&&(m.style="notification")),m.cachedTokens&&m.cachedTokens.length||this.tokenize_chat_line(m,!0),o.shouldShowMessage(m)){if(!(i.lengthv&&(m.ffz_old_messages=m.ffz_old_messages.slice(m.ffz_old_messages.length-v))}i.unshiftObject(m),r+=1}}if(h){var m={ffz_alternate:!_,color:"#755000",date:new Date,from:"frankerfacez_admin",style:"admin",message:"(Last message is "+n.human_time(l)+" old.)",room:e};if(this.tokenize_chat_line(m),o.shouldShowMessage(m))for(i.insertAt(r,m);i.length>o.get("messageBufferSize");)i.removeAt(0)}}},s.prototype.load_room=function(e,t,s){var n=this;jQuery.getJSON(o.API_SERVER+"v1/room/"+e).done(function(s){if(s.sets)for(var o in s.sets)s.sets.hasOwnProperty(o)&&n._load_set_json(o,void 0,s.sets[o]);n._load_room_json(e,t,s)}).fail(function(o){return 404==o.status?"function"==typeof t&&t(!1):(s=(s||0)+1,10>s?n.load_room(e,t,s):"function"==typeof t&&t(!1))})},s.prototype._load_room_json=function(e,t,s){if(!s||!s.room)return"function"==typeof t&&t(!1);s=s.room,this.rooms[e]&&(s.room=this.rooms[e].room);for(var o in this.rooms[e])"room"!==o&&this.rooms[e].hasOwnProperty(o)&&!s.hasOwnProperty(o)&&(s[o]=this.rooms[e][o]);s.needs_history=this.rooms[e]&&this.rooms[e].needs_history||!1,this.rooms[e]=s,(s.css||s.moderator_badge)&&n.update_css(this._room_style,e,i(s)+(s.css||"")),this.emote_sets.hasOwnProperty(s.set)?-1===this.emote_sets[s.set].users.indexOf(e)&&this.emote_sets[s.set].users.push(e):this.load_set(s.set,function(t,s){-1===s.users.indexOf(e)&&s.users.push(e)}),this.update_ui_link(),t&&t(!0,s)},s.prototype._modify_room=function(t){var s=this;t.reopen({subsOnlyMode:!1,r9kMode:!1,slowWaiting:!1,slowValue:0,mru_list:[],updateWait:function(e,t){var o=this.get("slowWait")||0;this.set("slowWait",e),1>o&&e>0?(setTimeout(this.ffzUpdateWait.bind(this),1e3),s._roomv&&s._roomv.ffzUpdateStatus()):(o>0&&1>e||t)&&(this.set("ffz_banned",!1),s._roomv&&s._roomv.ffzUpdateStatus())},ffzUpdateWait:function(){var e=this.get("slowWait")||0;1>e||(this.set("slowWait",--e),e>0?setTimeout(this.ffzUpdateWait.bind(this),1e3):(this.set("ffz_banned",!1),s._roomv&&s._roomv.ffzUpdateStatus()))},ffzUpdateStatus:function(){s._roomv&&s._roomv.ffzUpdateStatus()}.observes("r9kMode","subsOnlyMode","slowMode","slowValue","ffz_banned"),init:function(){this._super();try{s.add_room(this.id,this),this.set("ffz_chatters",{})}catch(e){s.error("add_room: "+e)}},willDestroy:function(){this._super();try{s.remove_room(this.id)}catch(e){s.error("remove_room: "+e)}},clearMessages:function(e){var t=this;if(e){if(this.get("messages").forEach(function(o,n){o.from===e&&(t.set("messages."+n+".ffz_deleted",!0),s.settings.prevent_clear||t.set("messages."+n+".deleted",!0))}),s.settings.mod_card_history){var o=s.rooms&&s.rooms[t.get("id")],n=o&&o.user_history&&o.user_history[e];if(null!==n&&void 0!==n){var i=!1,a=n.length>0?n[n.length-1]:null;if(i=null!==a&&a.is_delete,!i)for(n.push({from:"jtv",is_delete:!0,style:"admin",cachedTokens:["User has been timed out."],date:new Date});n.length>20;)n.shift()}}}else if(s.settings.prevent_clear)this.addTmiMessage("A moderator's attempt to clear chat was ignored.");else{var r=t.get("messages");t.set("messages",[]),t.addMessage({style:"admin",message:i18n("Chat was cleared by a moderator"),ffz_old_messages:r})}},pushMessage:function(e){if(this.shouldShowMessage(e)){var t,s,o,n=this.get("messageBufferSize");for(this.get("messages").pushObject(e),t=this.get("messages.length"),s=t-n,o=0;s>o;o++)this.get("messages").removeAt(0);"admin"===e.style||"whisper"===e.style&&!this.ffz_whisper_room||this.incrementProperty("unreadCount",1)}},addMessage:function(e){try{if(e){var t="whisper"===e.style;if(s.settings.group_tabs&&s.settings.whisper_room&&(t&&!this.ffz_whisper_room||!t&&this.ffz_whisper_room))return;if(t||(e.room=this.get("id")),s.tokenize_chat_line(e),!t&&e.from&&"jtv"!==e.from&&"twitchnotify"!==e.from&&s.settings.mod_card_history){var o=s.rooms&&s.rooms[e.room];if(o){var n=(o.user_history=o.user_history||{},o.user_history[e.from]=o.user_history[e.from]||[]);for(n.push({from:e.tags&&e.tags["display-name"]||e.from,cachedTokens:e.cachedTokens,style:e.style,date:e.date});n.length>20;)n.shift()}}if(!t){var i=s.get_user();if(i&&i.login===e.from){var a=this.get("ffz_banned");this.set("ffz_banned",!1),this.get("isModeratorOrHigher")||!this.get("slowMode")?this.updateWait(0,a):this.get("slowMode")&&this.updateWait(this.get("slowValue"))}}}}catch(r){s.error("Room addMessage: "+r)}return this._super(e)},setHostMode:function(e){var t=App.__container__.lookup("controller:chat");if(t&&t.get("currentChannelRoom")===this)return this._super(e)},send:function(e){if(!(s.settings.group_tabs&&s.settings.whisper_room&&this.ffz_whisper_room)){try{if(e){var t=this.get("mru_list"),o=t.indexOf(e);-1!==o?t.splice(o,1):t.length>20&&t.pop(),t.unshift(e)}var n=e.split(" ",1)[0].toLowerCase();if("/ffz"===n)return this.set("messageToSend",""),void s.run_ffz_command(e.substr(5),this.get("id"));if("/"===n.charAt(0)&&s.run_command(e,this.get("id")))return void this.set("messageToSend","")}catch(i){s.error("send: "+i)}return this._super(e)}},ffzUpdateUnread:function(){if(s.settings.group_tabs){var e=App.__container__.lookup("controller:chat");e&&e.get("currentRoom")===this?this.resetUnreadCount():s._chatv&&s._chatv.ffzTabUnread(this.get("id"))}}.observes("unreadCount"),ffzInitChatterCount:function(){if(this.tmiRoom){var e=this;this.tmiRoom.list().done(function(t){var s={};t=t.data.chatters;for(var o=0;o0),t.set("slowValue",e.slow),t.get("slowMode")||t.updateWait(0)),e.hasOwnProperty("r9k")&&t.set("r9kMode",e.r9k),e.hasOwnProperty("subs-only")&&t.set("subsOnlyMode",e["subs-only"])}),e._roomConn._connection.off("message",e._roomConn._onIrcMessage,e._roomConn),e._roomConn._onIrcMessage=function(e){if(e.target==this.ircChannel)switch(e.command){case"JOIN":this._session&&this._session.nickname===e.sender?this._onIrcJoin(e):s.settings.chatter_count&&t.ffzUpdateChatters(e.sender);break;case"PART":this._session&&this._session.nickname===e.sender?(this._resetActiveState(),this._connection._exitedRoomConn(),this._trigger("exited")):s.settings.chatter_count&&t.ffzUpdateChatters(null,e.sender)}},e._roomConn._connection.on("message",e._roomConn._onIrcMessage,e._roomConn),this.set("ffz_is_patched",!0)}}.observes("tmiRoom")})}},{"../constants":3,"../utils":31}],11:[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 s=this;e.reopen({lines:function(){var e=this._super();try{var o=[],n={},i=null,a=App.__container__.lookup("controller:channel"),r=this.get("parentController.model.id"),d=a&&a.get("id");if(d){var u=a.get("display_name");u&&(t.capitalization[d]=[u,Date.now()])}r!=d&&(d=null);for(var c=0;ct?String.fromCharCode(t):(t-=65536,String.fromCharCode(55296+(t>>10),56320+(1023&t)))};s.prototype.setup_emoticons=function(){this.log("Preparing emoticon system."),this.emoji_data={},this.emoji_names={},this.emote_sets={},this.global_sets=[],this.default_sets=[],this._last_emote_id=0,this.emote_usage={},this.log("Creating emoticon style element.");var e=this._emote_style=document.createElement("style");e.id="ffz-emoticon-css",document.head.appendChild(e),this.log("Loading global emote sets."),this.load_global_sets(),this.log("Loading emoji data."),this.load_emoji_data(),this.log("Watching Twitch emoticon parser to ensure it loads."),this._twitch_emote_check=setTimeout(this.check_twitch_emotes.bind(this),1e4) -},s.prototype.add_usage=function(e,t,s){var o=this.emote_usage[t]=this.emote_usage[t]||{};o[e]=(o[e]||0)+(s||1),this._emote_report_scheduled||(this._emote_report_scheduled=setTimeout(this._report_emotes.bind(this),3e4))},s.prototype._report_emotes=function(){this._emote_report_scheduled&&delete this._emote_report_scheduled;var e=this.emote_usage;this.emote_usage={},this.ws_send("emoticon_uses",[e],function(){},!0)},s.prototype.check_twitch_emotes=function(){this._twitch_emote_check&&(clearTimeout(this._twitch_emote_check),delete this._twitch_emote_check);var e;if(this.rooms)for(var t in this.rooms)if(this.rooms.hasOwnProperty(t)){e=this.rooms[t];break}if(!e||!e.room||!e.room.tmiSession)return void(this._twitch_emote_check=setTimeout(this.check_twitch_emotes.bind(this),1e4));var s=e.room.tmiSession._emotesParser,o=Object.keys(s.emoticonRegexToIds).length;if(!(o>0)){var n=s.emoticonSetIds;s.emoticonSetIds="",s.updateEmoticons(n),this._twitch_emote_check=setTimeout(this.check_twitch_emotes.bind(this),1e4)}},s.prototype.getEmotes=function(e,t){var s=this.users&&this.users[e],o=this.rooms&&this.rooms[t];return _.union(s&&s.sets||[],o&&o.set&&[o.set]||[],o&&o.extra_sets||[],this.default_sets)},s.ws_commands.reload_set=function(e){this.emote_sets.hasOwnProperty(e)&&this.load_set(e)},s.ws_commands.load_set=function(e){this.load_set(e)},s.prototype._emote_tooltip=function(e){if(!e)return null;if(e._tooltip)return e._tooltip;var t=this.emote_sets[e.set_id],s=e.owner,o=t&&t.title||"Global";return e._tooltip="Emoticon: "+(e.hidden?"???":e.name)+"\nFFZ "+o+(s?"\nBy: "+s.display_name:""),e._tooltip},s.prototype.load_emoji_data=function(e,t){var s=this;jQuery.getJSON(o.SERVER+"emoji/emoji.json").done(function(t){var n={},i={};for(var a in t){var r=t[a];a=a.toLowerCase(),r.code=a,n[a]=r,i[r.short_name]=a,r.raw=_.map(r.code.split("-"),d).join(""),r.src=o.SERVER+"emoji/"+a+"-1x.png",r.srcSet=r.src+" 1x, "+o.SERVER+"emoji/"+a+"-2x.png 2x, "+o.SERVER+"emoji/"+a+"-4x.png 4x",r.token={srcSet:r.srcSet,emoticonSrc:r.src+'" data-ffz-emoji="'+a+'" height="18px',ffzEmoji:a,altText:r.raw}}s.emoji_data=n,s.emoji_names=i,s.log("Loaded data on "+Object.keys(n).length+" emoji."),"function"==typeof e&&e(!0,t)}).fail(function(o){return 404===o.status?"function"==typeof e&&e(!1):(t=(t||0)+1,50>t?s.load_emoji(e,t):"function"==typeof e&&e(!1))})},s.prototype.load_global_sets=function(e,t){var s=this;jQuery.getJSON(o.API_SERVER+"v1/set/global").done(function(e){s.default_sets=e.default_sets;var t=s.global_sets=[],o=e.sets||{};for(var n in o)if(o.hasOwnProperty(n)){var i=o[n];t.push(n),s._load_set_json(n,void 0,i)}}).fail(function(o){return 404==o.status?"function"==typeof e&&e(!1):(t=t||0,t++,50>t?s.load_global_sets(e,t):"function"==typeof e&&e(!1))})},s.prototype.load_set=function(e,t,s){var n=this;jQuery.getJSON(o.API_SERVER+"v1/set/"+e).done(function(s){n._load_set_json(e,t,s&&s.set)}).fail(function(o){return 404==o.status?"function"==typeof t&&t(!1):(s=s||0,s++,10>s?n.load_set(e,t,s):"function"==typeof t&&t(!1))})},s.prototype.unload_set=function(e){var t=this.emote_sets[e];t&&(this.log("Unloading emoticons for set: "+e),n.update_css(this._emote_style,e,null),delete this.emote_sets[e])},s.prototype._load_set_json=function(e,t,s){if(!s)return"function"==typeof t&&t(!1);var o=this.emote_sets[e]&&this.emote_sets[e].users||[];this.emote_sets[e]=s,s.users=o,s.count=0;var i="",a=s.emoticons;s.emoticons={};for(var d=0;d=6e4?this.log("BetterTTV was not detected after 60 seconds."):setTimeout(this.find_bttv.bind(this,t,(s||0)+t),t))},s.prototype.setup_bttv=function(e){this.log("BetterTTV was detected after "+e+"ms. Hooking."),this.has_bttv=!0,document.body.classList.remove("ffz-dark"),this._dark_style&&(this._dark_style.parentElement.removeChild(this._dark_style),delete this._dark_style),this.settings.group_tabs&&this._chatv&&this._chatv.ffzDisableTabs(),this._roomv&&(this.settings.chat_hover_pause&&this._roomv.ffzDisableFreeze(),this.settings.room_status&&this._roomv.ffzUpdateStatus()),document.body.classList.remove("ffz-chat-colors"),document.body.classList.remove("ffz-chat-background"),document.body.classList.remove("ffz-sidebar-swap"),document.body.classList.remove("ffz-transparent-badges"),this.is_dashboard&&this._update_subscribers(),document.body.classList.add("ffz-bttv");var t=BetterTTV.chat.helpers.sendMessage,s=this;BetterTTV.chat.helpers.sendMessage=function(e){var o=e.split(" ",1)[0].toLowerCase();return"/ffz"!==o?t(e):void s.run_ffz_command(e.substr(5),BetterTTV.chat.store.currentRoom)};var i,a=BetterTTV.chat.handlers.onPrivmsg;BetterTTV.chat.handlers.onPrivmsg=function(e,t){i=e;var s=a(e,t);return i=null,s};var r=BetterTTV.chat.templates.privmsg;BetterTTV.chat.templates.privmsg=function(e,t,o,n,a){try{return s.bttv_badges(a),'
'+BetterTTV.chat.templates.timestamp(a.time)+" "+(n?BetterTTV.chat.templates.modicons():"")+" "+BetterTTV.chat.templates.badges(a.badges)+BetterTTV.chat.templates.from(a.nickname,a.color)+BetterTTV.chat.templates.message(a.sender,a.message,a.emotes,t?a.color:!1)+"
"}catch(d){return s.log("Error: ",d),r(e,t,o,n,a)}};var d=BetterTTV.chat.templates.whisper;BetterTTV.chat.templates.whisper=function(e){try{return s.bttv_badges(e),'
'+BetterTTV.chat.templates.timestamp(e.time)+" "+(e.badges&&e.badges.length?BetterTTV.chat.templates.badges(e.badges):"")+BetterTTV.chat.templates.whisperName(e.sender,e.receiver,e.from,e.to,e.fromColor,e.toColor)+BetterTTV.chat.templates.message(e.sender,e.message,e.emotes,!1)+"
"}catch(t){return s.log("Error: ",t),d(e)}};var u,c=BetterTTV.chat.templates.message;BetterTTV.chat.templates.message=function(e,t,o,n){try{n=n||!1;var i=encodeURIComponent(t);if("jtv"!==e){u=e;var a=BetterTTV.chat.templates.emoticonize(t,o);u=null;for(var r=0;r'+t+""}catch(d){return s.log("Error: ",d),c(e,t,o,n)}};var l=BetterTTV.chat.templates.emoticonize;BetterTTV.chat.templates.emoticonize=function(e,t){var a=l(e,t),r=i||BetterTTV.getChannel(),d=r&&r.toLowerCase(),c=u&&u.toLowerCase(),h=s.getEmotes(c,d),t=[],f=s.get_user(),m=f&&f.login===c;if(_.each(h,function(e){var o=s.emote_sets[e];o&&_.each(o.emoticons,function(e){_.any(a,function(t){return _.isString(t)&&t.match(e.regex)})&&t.push(e)})}),t.length&&_.each(t,function(e){var t=s._emote_tooltip(e),o=[''+t+''],n=a;a=[];for(var i=0;i']:w+(z||""))}}}else a.push(v)}}return a},this.update_ui_link()}},{"../constants":3,"../utils":31}],14:[function(){var t=e.FrankerFaceZ;t.prototype.find_emote_menu=function(t,s){return this.has_emote_menu=!1,e.emoteMenu&&emoteMenu.registerEmoteGetter?this.setup_emote_menu(s||0):void(s>=6e4?this.log("Emote Menu for Twitch was not detected after 60 seconds."):setTimeout(this.find_emote_menu.bind(this,t,(s||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(),s=e?e.login:null,o=App.__container__.lookup("controller:chat"),n=o?o.get("currentRoom.id"):null,i=this.getEmotes(s,n),a=[],r=0;r=6e4?this.log('Twitch application not detected in "'+location.toString()+'". Aborting.'):setTimeout(this.initialize.bind(this,t,(s||0)+t),t)))},s.prototype.setup_normal=function(t){var o=e.performance&&performance.now?performance.now():Date.now();this.log("Found non-Ember Twitch after "+(t||0)+' ms in "'+location+'". Initializing FrankerFaceZ version '+s.version_info),this.users={},this.is_dashboard=!1;try{this.embed_in_dash=e.top!==e&&/\/[^\/]+\/dashboard/.test(e.top.location.pathname)&&!/bookmarks$/.test(e.top.location.pathname)}catch(n){this.embed_in_dash=!1}this.load_settings(),this.setup_dark(),this.ws_create(),this.setup_emoticons(),this.setup_badges(),this.setup_notifications(),this.setup_css(),this.setup_menu(),this.find_bttv(10);var i=e.performance&&performance.now?performance.now():Date.now(),a=i-o;this.log("Initialization complete in "+a+"ms")},s.prototype.is_dashboard=!1,s.prototype.setup_dashboard=function(t){var o=e.performance&&performance.now?performance.now():Date.now();this.log("Found Twitch Dashboard after "+(t||0)+' ms in "'+location+'". Initializing FrankerFaceZ version '+s.version_info),this.users={},this.is_dashboard=!0,this.embed_in_dash=!1,this.load_settings(),this.setup_dark(),this.ws_create(),this.setup_emoticons(),this.setup_badges(),this.setup_notifications(),this.setup_css(),this._update_subscribers(),this.setup_message_event(),this.find_bttv(10);var n=e.performance&&performance.now?performance.now():Date.now(),i=n-o;this.log("Initialization complete in "+i+"ms")},s.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 '+s.version_info),this.users={},this.is_dashboard=!1;try{this.embed_in_dash=e.top!==e&&/\/[^\/]+\/dashboard/.test(e.top.location.pathname)&&!/bookmarks$/.test(e.top.location.pathname)}catch(n){this.embed_in_dash=!1}this.load_settings(),this.setup_dark(),this.ws_create(),this.setup_emoticons(),this.setup_badges(),this.setup_channel(),this.setup_room(),this.setup_line(),this.setup_chatview(),this.setup_viewers(),this.setup_mod_card(),this.setup_chat_input(),this.setup_notifications(),this.setup_css(),this.setup_menu(),this.setup_my_emotes(),this.setup_following(),this.setup_races(),this.connect_extra_chat(),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")},s.prototype.setup_message_event=function(){this.log("Listening for Window Messages."),e.addEventListener("message",this._on_window_message.bind(this),!1)},s.prototype._on_window_message=function(e){if(e.data&&e.data.from_ffz){e.data}}},{"./badges":1,"./commands":2,"./debug":4,"./ember/channel":5,"./ember/chat-input":6,"./ember/chatview":7,"./ember/line":8,"./ember/moderation-card":9,"./ember/room":10,"./ember/viewers":11,"./emoticons":12,"./ext/betterttv":13,"./ext/emote_menu":14,"./featurefriday":16,"./settings":17,"./socket":18,"./tokenize":19,"./ui/about_page":20,"./ui/dark":21,"./ui/following":22,"./ui/menu":23,"./ui/menu_button":24,"./ui/my_emotes":25,"./ui/notifications":26,"./ui/races":27,"./ui/styles":28,"./ui/sub_count":29,"./ui/viewer_count":30}],16:[function(t){var s=e.FrankerFaceZ,o=t("./constants");s.prototype.feature_friday=null,s.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))})},s.ws_commands.reload_ff=function(){this.check_ff()},s.prototype._feature_friday_ui=function(e,t,s){if(this.feature_friday&&this.feature_friday.channel!=e){this._emotes_for_sets(t,s,[this.feature_friday.set],this.feature_friday.title,this.feature_friday.icon,"FrankerFaceZ");var o=App.__container__.lookup("controller:channel");if(!o||o.get("id")!=this.feature_friday.channel){var n=this.feature_friday,i=document.createElement("div"),a=document.createElement("a");i.className="chat-menu-content",i.style.textAlign="center";var r=n.display_name+(n.live?" is live now!":"");a.className="button primary",a.classList.toggle("live",n.live),a.classList.toggle("blue",this.has_bttv&&BetterTTV.settings.get("showBlueButtons")),a.href="http://www.twitch.tv/"+n.channel,a.title=r,a.target="_new",a.innerHTML=""+r+"",i.appendChild(a),t.appendChild(i)}}},s.prototype._load_ff=function(e){this.feature_friday&&(this.global_sets.removeObject(this.feature_friday.set),this.default_sets.removeObject(this.feature_friday.set),this.feature_friday=null,this.update_ui_link()),e&&e.set&&e.channel&&(this.feature_friday={set:e.set,channel:e.channel,live:!1,title:e.title||"Feature Friday",display_name:s.get_capitalization(e.channel,this._update_ff_name.bind(this))},this.global_sets.push(e.set),this.default_sets.push(e.set),this.load_set(e.set),this._update_ff_live())},s.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)})}},s.prototype._update_ff_name=function(e){this.feature_friday&&(this.feature_friday.display_name=e)}},{"./constants":3}],17:[function(t){var s=e.FrankerFaceZ,o=t("./constants");make_ls=function(e){return"ffz_setting_"+e},toggle_setting=function(e,t){var s=!this.settings.get(t);this.settings.set(t,s),e.classList.toggle("active",s)},s.settings_info={},s.prototype.load_settings=function(){this.log("Loading settings."),this.settings={};for(var t in s.settings_info)if(s.settings_info.hasOwnProperty(t)){var o=s.settings_info[t],n=o.storage_key||make_ls(t),i=o.hasOwnProperty("value")?o.value:void 0;if(localStorage.hasOwnProperty(n))try{i=JSON.parse(localStorage.getItem(n))}catch(a){this.log('Error loading value for "'+t+'": '+a)}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)},s.settings_info.replace_twitch_menu={type:"boolean",value:!1,name:"Replace Twitch Emoticon Menu",help:"Completely replace the default Twitch emoticon menu.",on_update:function(e){document.body.classList.toggle("ffz-menu-replace",e)}},s.menu_pages.settings={render:function(e,t){var o={},n=[];for(var i in s.settings_info)if(s.settings_info.hasOwnProperty(i)){var a=s.settings_info[i],r=a.category||"Miscellaneous",d=o[r];if(void 0!==a.visible&&null!==a.visible){var u=a.visible;if("function"==typeof a.visible&&(u=a.visible.bind(this)()),!u)continue}d||(n.push(r),d=o[r]=[]),d.push([i,a])}n.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 c=0;cs?-1:s>o?1:i>n?-1:n>i?1:0});for(var m=0;m",v.className="switch-label",v.innerHTML=a.name,p.appendChild(y),p.appendChild(v),y.addEventListener("click",toggle_setting.bind(this,y,i))}else{p.classList.add("option");var w=document.createElement("a");w.innerHTML=a.name,w.href="#",p.appendChild(w),w.addEventListener("click",a.method.bind(this))}if(a.help){var b=document.createElement("span");b.className="help",b.innerHTML=a.help,p.appendChild(b)}}f.appendChild(p)}t.appendChild(f)}},name:"Settings",icon:o.GEAR,sort_order:99999,wide:!0},s.prototype._setting_update=function(t){if(t||(t=e.event),t.key&&"ffz_setting_"===t.key.substr(0,12)){var o=t.key,n=o.substr(12),i=void 0,a=s.settings_info[n];if(!a){for(n in s.settings_info)if(s.settings_info.hasOwnProperty(n)&&(a=s.settings_info[n],a.storage_key==o))break;if(a.storage_key!=o)return}this.log("Updated Setting: "+n);try{i=JSON.parse(t.newValue)}catch(r){this.log('Error loading new value for "'+n+'": '+r),i=a.value||void 0}if(this.settings[n]=i,a.on_update)try{a.on_update.bind(this)(i,!1)}catch(r){this.log('Error running updater for setting "'+n+'": '+r)}}},s.prototype._setting_get=function(e){return this.settings[e]},s.prototype._setting_set=function(e,t){var o=s.settings_info[e],n=o.storage_key||make_ls(e),i=JSON.stringify(t);if(this.settings[e]=t,localStorage.setItem(n,i),this.log('Changed Setting "'+e+'" to: '+i),o.on_update)try{o.on_update.bind(this)(t,!0)}catch(a){this.log('Error running updater for setting "'+e+'": '+a)}},s.prototype._setting_del=function(e){var t=s.settings_info[e],o=t.storage_key||make_ls(e),n=void 0;if(localStorage.hasOwnProperty(o)&&localStorage.removeItem(o),delete this.settings[e],t&&(n=this.settings[e]=t.hasOwnProperty("value")?t.value:void 0),t.on_update)try{t.on_update.bind(this)(n,!0)}catch(i){this.log('Error running updater for setting "'+e+'": '+i)}}},{"./constants":3}],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 s,o=this;this._ws_last_req=0,this._ws_callbacks={},this._ws_pending=this._ws_pending||[];try{s=this._ws_sock=new WebSocket("ws://catbag.frankerfacez.com/")}catch(n){return this._ws_exists=!1,this.log("Error Creating WebSocket: "+n)}this._ws_exists=!0,s.onopen=function(){o._ws_open=!0,o._ws_delay=0,o.log("Socket connected.");var s=e.RequestFileSystem||e.webkitRequestFileSystem;s?s(e.TEMPORARY,100,o.ws_send.bind(o,"hello",["ffz_"+t.version_info,localStorage.ffzClientId],o._ws_on_hello.bind(o)),o.log.bind(o,"Operating in Incognito Mode.")):o.ws_send("hello",["ffz_"+t.version_info,localStorage.ffzClientId],o._ws_on_hello.bind(o));var n=o.get_user();if(n&&o.ws_send("setuser",n.login),o.is_dashboard){var i=location.pathname.match(/\/([^\/]+)/);i&&(o.ws_send("sub",i[1]),o.ws_send("sub_channel",i[1]))}for(var a in o.rooms)o.rooms.hasOwnProperty(a)&&o.rooms[a]&&(o.ws_send("sub",a),o.rooms[a].needs_history&&(o.rooms[a].needs_history=!1,!o.has_bttv&&o.settings.chat_history&&o.ws_send("chat_history",[a,25],o._load_history.bind(o,a))));if(o._cindex){var r=o._cindex.get("controller.id"),d=o._cindex.get("controller.hostModeTarget.id");r&&o.ws_send("sub_channel",r),d&&o.ws_send("sub_channel",d)}var u=o._ws_pending;o._ws_pending=[];for(var c=0;c'}if(e.isLink){if(!t&&void 0!==t)return e.href;var h=e.href;if(h.indexOf("@")>-1&&(-1===h.indexOf("/")||h.indexOf("@")'+h+"";var f=(h.match(/^https?:\/\//)?"":"http://")+h,l=s._link_data&&s._link_data[f]||{};return''+h+""}return e.mentionedUser?''+e.mentionedUser+"":n.sanitize(e.deletedLink?e.text:e)}).join("")},o.prototype.tokenize_replace_emotes=function(e){_.isString(e)&&(e=[e]);for(var t=0;t-1&&(-1===t.indexOf("/")||t.indexOf("@")0&&(n=!0)}var r=document.createElement("div"),d="";d+="

FrankerFaceZ

",d+='
new ways to woof
',r.className="chat-menu-content center",r.innerHTML=d,t.appendChild(r);var u=0,c=r.querySelector("h1");c&&c.addEventListener("click",function(){if(c.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,c.style.cursor=""},2e3)});var l=document.createElement("div"),h=document.createElement("a"),f="To use custom emoticons in "+(n?"this channel":"tons of channels")+", get FrankerFaceZ from http://www.frankerfacez.com";h.className="button primary",h.innerHTML="Advertise in Chat",h.addEventListener("click",this._add_emote.bind(this,e,f)),l.appendChild(h);var _=document.createElement("a");_.className="button ffz-donate",_.href="https://www.frankerfacez.com/donate",_.target="_new",_.innerHTML="Donate",l.appendChild(_),l.className="chat-menu-content center",t.appendChild(l);var m=document.createElement("div");d='',d+='',d+='',d+='',d+='',m.className="chat-menu-content center",m.innerHTML=d;var p=!1;m.querySelector("#ffz-debug-logs").addEventListener("click",function(){p||(p=!0,i._pastebin(i._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(m)}}},{"../constants":3}],21:[function(t){var s=e.FrankerFaceZ,o=t("../constants");s.settings_info.twitch_chat_dark={type:"boolean",value:!1,visible:!1},s.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 s=e.App?App.__container__.lookup("controller:settings").get("model"):void 0;t?(this._load_dark_css(),s&&this.settings.set("twitch_chat_dark",s.get("darkMode")),s&&s.set("darkMode",!0)):s&&s.set("darkMode",this.settings.twitch_chat_dark)}}},s.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._load_dark_css()))},s.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?_="+(o.DEBUG?Date.now():s.version_info)),document.head.appendChild(e)}}},{"../constants":3}],22:[function(t){{var s=e.FrankerFaceZ;t("../utils")}s.prototype.setup_following=function(){this.log("Initializing following support."),this.follow_data={},this.follow_sets={}},s.settings_info.follow_buttons={type:"boolean",value:!0,category:"Channel Metadata",name:"Relevant Follow Buttons",help:"Display additional Follow buttons for channels relevant to the stream, such as people participating in co-operative gameplay.",on_update:function(){this.rebuild_following_ui()}},s.ffz_commands.following=function(e,t){t=t.join(" ").trim().split(/\s*,+\s*/),t.length&&""===t[0]&&t.shift(),t.length&&""===t[t.length-1]&&t.pop();var s=this.get_user(),o=this;return!s||s.login!==e.id&&"sirstendec"!==s.login&&"dansalvato"!==s.login?"You must be logged in as the broadcaster to use this command.":this.ws_send("update_follow_buttons",[e.id,t],function(t,s){return t?void(s?o.room_message(e,"The following buttons have been updated."):o.room_message(e,"The following buttons have been disabled.")):void o.room_message(e,"There was an error updating the following buttons.")})?void 0:"There was an error communicating with the server."},s.ws_on_close.push(function(){var t=e.App&&App.__container__.lookup("controller:channel"),s=t&&t.get("id"),o=t&&t.get("hostModeTarget.id"),n=!1;if(this.follow_sets={},t){for(var i in this.follow_data)if(delete this.follow_data[i],(i===s||i===o)&&(n=!0),this.rooms&&this.rooms[i]&&this.rooms[i].extra_sets){var a=this.rooms[i].extra_sets;delete this.rooms[i].extra_sets;for(var r=0;r span");r?i.insertBefore(a,r):i.appendChild(a)}for(var d=0;d span");r?i.insertBefore(a,r):i.appendChild(a)}for(var d=0;d=300?"right":"left")+" dropmenu notify-menu js-notify",i='
You are following '+s.get_capitalization(t)+"
",i+='

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

",n.innerHTML=i,e.appendChild(n),n.querySelector("a.switch"))}},{"../utils":31}],23:[function(t){var s=e.FrankerFaceZ,o=t("../constants"),n=t("../utils"),i="http://static-cdn.jtvnw.net/emoticons/v1/";s.prototype.setup_menu=function(){this.log("Installing mouse-up event to auto-close menus.");var e=this;jQuery(document).mouseup(function(t){var s,o=e._popup;o&&(o=jQuery(o),s=o.parent(),s.is(t.target)||0!==s.has(t.target).length||(o.remove(),delete e._popup,e._popup_kill&&e._popup_kill(),delete e._popup_kill))}),document.body.classList.toggle("ffz-menu-replace",this.settings.replace_twitch_menu),this.log("Hooking the Ember Chat Settings view.");var t=App.__container__.resolve("view:settings");if(t){t.reopen({didInsertElement:function(){this._super();try{this.ffzInit()}catch(t){e.error("ChatSettings didInsertElement: "+t)}},willClearRender:function(){try{this.ffzTeardown()}catch(t){e.error("ChatSettings willClearRender: "+t)}this._super()},ffzInit:function(){var t=this,s=this.get("element"),o=s&&s.querySelector(".dropmenu");if(o){var n,i,a,r=document.createElement("div"),d=document.createElement("div");r.className="list-header",r.innerHTML="FrankerFaceZ",d.className="chat-menu-content",n=document.createElement("p"),n.className="no-bttv",i=document.createElement("input"),i.type="checkbox",i.className="ember-checkbox ffz-setting-dark-twitch",i.checked=e.settings.dark_twitch,n.appendChild(i),n.appendChild(document.createTextNode("Dark Twitch")),d.appendChild(n),i.addEventListener("change",function(){e.settings.set("dark_twitch",this.checked)}),n=document.createElement("p"),a=document.createElement("a"),a.href="#",a.innerHTML="More Settings",n.appendChild(a),d.appendChild(n),a.addEventListener("click",function(s){return t.set("controller.model.hidden",!0),e._last_page="settings",e.build_ui_popup(e._chatv),s.stopPropagation(),!1}),o.appendChild(r),o.appendChild(d)}},ffzTeardown:function(){}});try{t.create().destroy()}catch(s){}for(var o in Ember.View.views)if(Ember.View.views.hasOwnProperty(o)){var n=Ember.View.views[o];if(n instanceof t){this.log("Manually updating existing Chat Settings view.",n);try{n.ffzInit()}catch(s){this.error("setup: ChatSettings ffzInit: "+s)}}}}},s.menu_pages={},s.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 n=document.createElement("div"),i=document.createElement("div"),a=document.createElement("ul"),r=this.has_bttv?BetterTTV.settings.get("darkenedMode"):!1;n.className="emoticon-selector chat-menu ffz-ui-popup",i.className="emoticon-selector-box dropmenu",n.appendChild(i),n.classList.toggle("dark",r);var d=document.createElement("div");d.className="ffz-ui-menu-page",i.appendChild(d),a.className="menu clearfix",i.appendChild(a);var u=document.createElement("li");u.className="title",u.innerHTML=""+(o.DEBUG?"[DEV] ":"")+"FrankerFaceZ",a.appendChild(u);var c=[];for(var l in s.menu_pages)if(s.menu_pages.hasOwnProperty(l)){var h=s.menu_pages[l];try{if(!h||h.hasOwnProperty("visible")&&(!h.visible||"function"==typeof h.visible&&!h.visible.bind(this)(e)))continue}catch(f){this.error("menu_pages "+l+" visible: "+f);continue}c.push([h.sort_order||0,l,h])}c.sort(function(e,t){if(e[0]t[0])return-1;var s=e[1].toLowerCase(),o=t[1].toLowerCase();return o>s?1:s>o?-1:0});for(var _=0;_0,u&&!c&&!l){var p=this;u.addObserver("isLoaded",function(){setTimeout(function(){"channel"===t.getAttribute("data-page")&&(t.innerHTML="",s.menu_pages.channel.render.bind(p)(e,t))},0)}),u.load()}f.className="emoticon-grid",_.className="heading",h&&(_.style.backgroundImage='url("'+h+'")'),_.innerHTML='TwitchSubscriber Emoticons',f.appendChild(_);for(var g=d.get("emoticons")||[],v=0;v0&&t.appendChild(f),m>0&&!c){var k=document.createElement("div"),E=document.createElement("div"),C=document.createElement("span"),T=document.createElement("a");k.className="subscribe-message",E.className="non-subscriber-message",k.appendChild(E),C.className="unlock-text",C.innerHTML="Subscribe to unlock Emoticons",E.appendChild(C),T.className="action subscribe-button button primary",T.href=d.get("product_url"),T.innerHTML='",E.appendChild(T),t.appendChild(k)}else if(m>0){var x=u.get("content");if(x=x.length>0?x[x.length-1]:void 0,x&&x.purchase_profile&&!x.purchase_profile.will_renew){var F=n.parse_date(x.access_end||"");k=document.createElement("div"),E=document.createElement("div"),C=document.createElement("span"),end_time=F?Math.floor((F.getTime()-Date.now())/1e3):null,k.className="subscribe-message",E.className="non-subscriber-message",k.appendChild(E),C.className="unlock-text",C.innerHTML="Subscription expires in "+n.time_to_string(end_time,!0,!0),E.appendChild(C),t.appendChild(k)}}}}var S=a&&a.extra_sets||[];this._emotes_for_sets(t,e,a&&a.set&&[a.set]||[],this.feature_friday||r||S.length?"Channel Emoticons":null,"http://cdn.frankerfacez.com/script/devicon.png","FrankerFaceZ");for(var v=0;vs?-1:s>o?1:0});for(var l=0;l0&&(i=!0)}t.classList.toggle("no-emotes",!i),t.classList.toggle("live",d),t.classList.toggle("dark",a),t.classList.toggle("blue",r)}}},{"../constants":3}],25:[function(t){var s=e.FrankerFaceZ,o=t("../constants"),n=t("../utils"),i="http://static-cdn.jtvnw.net/emoticons/v1/";s.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."},s.settings_info.emoji_in_menu={type:"boolean",value:!1,name:"Display Emoji in My Emotes",help:"Display the supported emoji images in the My Emoticons menu."},s.prototype.setup_my_emotes=function(){if(this._twitch_set_to_channel={},this._twitch_badges={},localStorage.ffzTwitchSets)try{this._twitch_set_to_channel=JSON.parse(localStorage.ffzTwitchSets),this._twitch_badges=JSON.parse(localStorage.ffzTwitchBadges)}catch(e){}this._twitch_set_to_channel[0]="global",this._twitch_set_to_channel[33]="turbo_faces",this._twitch_set_to_channel[42]="turbo_faces",this._twitch_badges.global="//cdn.frankerfacez.com/script/twitch_logo.png",this._twitch_badges.turbo_faces=this._twitch_badges.turbo="//cdn.frankerfacez.com/script/turbo_badge.png"},s.menu_pages.my_emotes={name:"My Emoticons",icon:o.EMOTE,visible:function(e){var t=this.get_user(),s=e.get("controller.currentRoom.tmiSession"),o=t&&this.users[t.login]&&this.users[t.login].sets||[],n=(s&&s.getEmotes()||{emoticon_sets:{}}).emoticon_sets;return o.length||n&&Object.keys(n).length},render:function(e,t){var o=e.get("controller.currentRoom.tmiSession"),n=(o&&o.getEmotes()||{emoticon_sets:{}}).emoticon_sets,i=[];for(var a in n)n.hasOwnProperty(a)&&!this._twitch_set_to_channel.hasOwnProperty(a)&&i.push(a);if(!i.length)return s.menu_pages.my_emotes.draw_menu.bind(this)(e,t,n);var r=this,d=function(){if(i.length){i=[];var o={};for(var a in n)r._twitch_set_to_channel[a]&&(o[a]=n[a]);return s.menu_pages.my_emotes.draw_menu.bind(r)(e,t,o)}};this.ws_send("twitch_sets",i,function(o,a){if(i.length){if(i=[],o){for(var u in a)a.hasOwnProperty(u)&&(r._twitch_set_to_channel[u]=a[u]);return localStorage.ffzTwitchSets=JSON.stringify(r._twitch_set_to_channel),s.menu_pages.my_emotes.draw_menu.bind(r)(e,t,n)}d()}})?setTimeout(d,2e3):d()},draw_emoji:function(e){var t=document.createElement("div"),s=document.createElement("div");t.className="heading",t.innerHTML='FrankerFaceZEmoji',s.className="emoticon-grid",s.appendChild(t);var n=[];for(var i in this.emoji_data)n.push(this.emoji_data[i]);n.sort(function(e,t){var s=e.short_name.toLowerCase(),o=t.short_name.toLowerCase();return o>s?-1:s>o?1:e.rawt.raw?1:0});for(var a=0;as?-1:s>o?1:e.idt.id?1:0});for(var h=0;hFrankerFaceZ'+t.title,s.style.backgroundImage='url("'+(t.icon||"//cdn.frankerfacez.com/script/devicon.png")+'")',o.className="emoticon-grid",o.appendChild(s);for(var i in t.emoticons)t.emoticons.hasOwnProperty(i)&&!t.emoticons[i].hidden&&n.push(t.emoticons[i]);n.sort(function(e,t){var s=e.name.toLowerCase(),o=t.name.toLowerCase();return o>s?-1:s>o?1:e.idt.id?1:0});for(var a=0;as?-1:s>o?1:0});for(var u=0;uSpeedRunsLive races under channels.',on_update:function(){this.rebuild_race_ui() -}},s.ws_on_close.push(function(){var t=e.App&&App.__container__.lookup("controller:channel"),s=t&&t.get("id"),o=t&&t.get("hostModeTarget.id"),n=!1;if(t){for(var i in this.srl_races)delete this.srl_races[i],(i===s||i===o)&&(n=!0);n&&this.rebuild_race_ui()}}),s.ws_commands.srl_race=function(e){var t=App.__container__.lookup("controller:channel"),s=t&&t.get("id"),o=t&&t.get("hostModeTarget.id"),n=!1;this.srl_races=this.srl_races||{};for(var i=0;i=300?"right":"left")+" share dropmenu",this._popup_kill=this._race_kill.bind(this),this._popup=o;var d="http://kadgar.net/live",u=!1;for(var c in a.entrants){var l=a.entrants[c].state;a.entrants.hasOwnProperty(c)&&a.entrants[c].channel&&("racing"==l||"entered"==l)&&(d+="/"+a.entrants[c].channel,u=!0)}var h=document.querySelector(".app-main.theatre")?document.body.clientHeight-300:e.parentElement.offsetTop-175,f=App.__container__.lookup("controller:channel"),_=f?f.get("display_name"):s.get_capitalization(t),m=encodeURIComponent("I'm watching "+_+" race "+a.goal+" in "+a.game+" on SpeedRunsLive!");r='
',r+='
Developers
Dan Salvato  
Stendec  
Version '+s.version_info+'Logs
',r+="
#Entrant Time
",r+='
',r+='',r+='

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

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

'+k+"

Goal: "+E}u?_?l.innerHTML="Done":(l.innerHTML=o.time_to_string(u),this._race_timer=setTimeout(this._update_race.bind(this,e),1e3)):l.innerHTML="Entry Open"}}}},{"../utils":31}],28:[function(t){var s=e.FrankerFaceZ,o=t("../constants");s.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?_="+(o.DEBUG?Date.now():s.version_info)),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}],29:[function(t){{var s=e.FrankerFaceZ,o=t("../constants");t("../utils")}s.prototype._update_subscribers=function(){this._update_subscribers_timer&&(clearTimeout(this._update_subscribers_timer),delete this._update_subscribers_timer),this._update_subscribers_timer=setTimeout(this._update_subscribers.bind(this),6e4);var e=this.get_user(),t=this,s=this.is_dashboard?location.pathname.match(/\/([^\/]+)/):void 0,n=this.is_dashboard&&s&&s[1];if(this.has_bttv||!n||n!==e.login){var i=document.querySelector("#ffz-sub-display");return void(i&&i.parentElement.removeChild(i))}jQuery.ajax({url:"/broadcast/dashboard/partnership"}).done(function(e){try{var s,i=document.createElement("span");i.innerHTML=e,s=i.querySelector("#dash_main");var a=s&&s.textContent.match(/([\d,\.]+) total active subscribers/),r=a&&a[1];if(!r){var d=document.querySelector("#ffz-sub-display");return d&&d.parentElement.removeChild(d),void(t._update_subscribers_timer&&(clearTimeout(t._update_subscribers_timer),delete t._update_subscribers_timer))}var d=document.querySelector("#ffz-sub-display span");if(!d){var u=document.querySelector(t.is_dashboard?"#stats":"#channel .stats-and-actions .channel-stats");if(!u)return;var c=document.createElement("span");c.className="ffz stat",c.id="ffz-sub-display",c.title="Active Channel Subscribers",c.innerHTML=o.STAR+" ",d=document.createElement("span"),c.appendChild(d),Twitch.api.get("chat/"+n+"/badges",null,{version:3}).done(function(e){e.subscriber&&e.subscriber.image&&(c.innerHTML="",c.appendChild(d),c.style.backgroundImage='url("'+e.subscriber.image+'")',c.style.backgroundRepeat="no-repeat",c.style.paddingLeft="23px",c.style.backgroundPosition="0 50%")}),u.appendChild(c),jQuery(c).tipsy(t.is_dashboard?{gravity:"s"}:void 0)}d.innerHTML=r}catch(l){t.error("_update_subscribers: "+l)}}).fail(function(){var e=document.querySelector("#ffz-sub-display");e&&e.parentElement.removeChild(e)})}},{"../constants":3,"../utils":31}],30:[function(t){var s=e.FrankerFaceZ,o=t("../constants"),n=t("../utils");s.ws_commands.chatters=function(t){{var s=t[0],o=t[1],n=e.App&&App.__container__.lookup("controller:channel"),i=this.is_dashboard?location.pathname.match(/\/([^\/]+)/):void 0;this.is_dashboard?i&&i[1]:n&&n.get&&n.get("id")}if(!this.is_dashboard){var a=this.rooms&&this.rooms[s];return void(a&&(a.ffz_chatters=o,this._cindex&&this._cindex.ffzUpdateChatters()))}this._dash_chatters=o},s.ws_commands.viewers=function(t){var s=t[0],i=t[1],a=e.App&&App.__container__.lookup("controller:channel"),r=this.is_dashboard?location.pathname.match(/\/([^\/]+)/):void 0,d=this.is_dashboard?r&&r[1]:a&&a.get&&a.get("id");if(!this.is_dashboard){var u=this.rooms&&this.rooms[s];return void(u&&(u.ffz_viewers=i,this._cindex&&this._cindex.ffzUpdateChatters()))}if(this._dash_viewers=i,this.settings.chatter_count&&d===s){var c=document.querySelector("#ffz-ffzchatter-display"),l=o.ZREKNARF+" "+n.number_commas(i)+("number"==typeof this._dash_chatters?" ("+n.number_commas(this._dash_chatters)+")":"");if(c)c.innerHTML=l;else{var h=document.querySelector("#stats");if(!h)return;c=document.createElement("span"),c.id="ffz-ffzchatter-display",c.className="ffz stat",c.title="Viewers (In Chat) with FrankerFaceZ",c.innerHTML=l,h.appendChild(c),jQuery(c).tipsy(this.is_dashboard?{gravity:"s"}:void 0)}}}},{"../constants":3,"../utils":31}],31:[function(t,s){var o=(e.FrankerFaceZ,t("./constants"),{}),n=document.createElement("span"),i=function(e,t,s){return s=s||"s",t=t||"",1===e?t:s},a=function(e){return 1==e?"1st":2==e?"2nd":3==e?"3rd":null==e?"---":e+"th"},r=function(e,t){t=0===t?0:t||1,t=Math.round(255*-(t/100));var s=Math.max(0,Math.min(255,e[0]-t)),o=Math.max(0,Math.min(255,e[1]-t)),n=Math.max(0,Math.min(255,e[2]-t));return[s,o,n]},d=function(e){return"rgb("+e[0]+", "+e[1]+", "+e[2]+")"},u=function(e,t){return t=0===t?0:t||1,r(e,-t)},c=function(e){e=[e[0]/255,e[1]/255,e[2]/255];for(var t=0;t=0?t.trailing=e.substr(i+2):i=e.length;var a=e.substr(n+1,i-n-1).split(" ");return t.command=a[0],a.length>1&&(t.params=a.slice(1)),t},_={":":";",s:" ",r:"\r",n:"\n","\\":"\\"},m=function(e){for(var t="",s=0;s=55296&&56319>=n?i=n:o.push(n.toString(16));var r=v[e]=v[e]||{},d=r[t]=o.join("-");return d};s.exports={update_css:function(e,t,s){var o=e.innerHTML,n="/*BEGIN "+t+"*/",i="/*END "+t+"*/",a=o.indexOf(n),r=o.indexOf(i),d=-1!==a&&-1!==r&&r>a;(d||s)&&(d&&(o=o.substr(0,a)+o.substr(r+i.length)),s&&(o+=n+s+i),e.innerHTML=o)},splitIRCMessage:f,parseIRCTags:g,emoji_to_codepoint:b,get_luminance:c,brighten:r,darken:u,rgb_to_css:d,parse_date:h,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||(n.textContent=e,t=o[e]=n.innerHTML,n.innerHTML=""),t},date_string:function(e){return e.getFullYear()+"-"+(e.getMonth()+1)+"-"+e.getDate()},pluralize:i,human_time:function(e){e=Math.floor(e);var t=Math.floor(e/31536e3);if(t)return t+" year"+i(t);var s=Math.floor((e%=31536e3)/86400);if(s)return s+" day"+i(s);var o=Math.floor((e%=86400)/3600);if(o)return o+" hour"+i(o);var n=Math.floor((e%=3600)/60);if(n)return n+" minute"+i(n);var a=e%60;return a?a+" second"+i(a):"less than a second"},time_to_string:function(e,t,s,o){var n=e%60,i=Math.floor(e/60),a=Math.floor(i/60),r="";if(i%=60,t){if(r=Math.floor(a/24),a%=24,s&&r>0)return r+" days";r=r>0?r+" days, ":""}return r+(!o||r||a?(10>a?"0":"")+a+":":"")+(10>i?"0":"")+i+":"+(10>n?"0":"")+n}}},{"./constants":3}]},{},[15]),e.ffz=new FrankerFaceZ}(window); \ No newline at end of file +!function(e){!function t(e,s,o){function n(a,r){if(!s[a]){if(!e[a]){var d="function"==typeof require&&require;if(!r&&d)return d(a,!0);if(i)return i(a,!0);throw new Error("Cannot find module '"+a+"'")}var u=s[a]={exports:{}};e[a][0].call(u.exports,function(t){var s=e[a][1][t];return n(s?s:t)},u,u.exports,t,e,s,o)}return s[a].exports}for(var i="function"==typeof require&&require,a=0;ae&&this._legacy_load_bots(e))})},s.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})},s.prototype._legacy_parse_badges=function(e,t,s){var o=this.badges[s].title,i=0;if(ds=null,null!=e)for(var a=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 n=t.shift();e.room.tmiRoom.sendMessage("/unmod "+n)}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 s=this.get_user();if(!s||!s.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 n=t.shift();e.room.tmiRoom.sendMessage("/mod "+n)}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 s='',o="true"==localStorage.ffzDebugMode&&document.body.classList.contains("ffz-dev"),n=o?"//localhost:8000/":"//cdn.frankerfacez.com/";t.exports={DEBUG:o,SERVER:n,API_SERVER:"//api.frankerfacez.com/",API_SERVER_2:"//direct-api.frankerfacez.com/",KNOWN_CODES:{"#-?[\\\\/]":"#-/",":-?(?:7|L)":":-7","\\<\\;\\]":"<]","\\:-?(S|s)":":-S","\\:-?\\\\":":-\\","\\:\\>\\;":":>","B-?\\)":"B-)","\\:-?[z|Z|\\|]":":-Z","\\:-?\\)":":-)","\\:-?\\(":":-(","\\:-?(p|P)":":-P","\\;-?(p|P)":";-P","\\<\\;3":"<3","\\:-?[\\\\/]":":-/","\\;-?\\)":";-)","R-?\\)":"R-)","[o|O](_|\\.)[o|O]":"O.o","\\:-?D":":-D","\\:-?(o|O)":":-O","\\>\\;\\(":">(","Gr(a|e)yFace":"GrayFace"},EMOTE_REPLACEMENT_BASE:n+"script/replacements/",EMOTE_REPLACEMENTS:{15:"15-JKanStyle.png",16:"16-OptimizePrime.png",17:"17-StoneLightning.png",18:"18-TheRinger.png",19:"19-PazPazowitz.png",20:"20-EagleEye.png",21:"21-CougarHunt.png",22:"22-RedCoat.png",26:"26-JonCarnage.png",27:"27-PicoMause.png",30:"30-BCWarrior.png",33:"33-DansGame.png",36:"36-PJSalt.png"},EMOJI_REGEX:/((?:\ud83c\udde8\ud83c\uddf3|\ud83c\uddfa\ud83c\uddf8|\ud83c\uddf7\ud83c\uddfa|\ud83c\uddf0\ud83c\uddf7|\ud83c\uddef\ud83c\uddf5|\ud83c\uddee\ud83c\uddf9|\ud83c\uddec\ud83c\udde7|\ud83c\uddeb\ud83c\uddf7|\ud83c\uddea\ud83c\uddf8|\ud83c\udde9\ud83c\uddea|\u0039\ufe0f?\u20e3|\u0038\ufe0f?\u20e3|\u0037\ufe0f?\u20e3|\u0036\ufe0f?\u20e3|\u0035\ufe0f?\u20e3|\u0034\ufe0f?\u20e3|\u0033\ufe0f?\u20e3|\u0032\ufe0f?\u20e3|\u0031\ufe0f?\u20e3|\u0030\ufe0f?\u20e3|\u0023\ufe0f?\u20e3|\ud83d\udeb3|\ud83d\udeb1|\ud83d\udeb0|\ud83d\udeaf|\ud83d\udeae|\ud83d\udea6|\ud83d\udea3|\ud83d\udea1|\ud83d\udea0|\ud83d\ude9f|\ud83d\ude9e|\ud83d\ude9d|\ud83d\ude9c|\ud83d\ude9b|\ud83d\ude98|\ud83d\ude96|\ud83d\ude94|\ud83d\ude90|\ud83d\ude8e|\ud83d\ude8d|\ud83d\ude8b|\ud83d\ude8a|\ud83d\ude88|\ud83d\ude86|\ud83d\ude82|\ud83d\ude81|\ud83d\ude36|\ud83d\ude34|\ud83d\ude2f|\ud83d\ude2e|\ud83d\ude2c|\ud83d\ude27|\ud83d\ude26|\ud83d\ude1f|\ud83d\ude1b|\ud83d\ude19|\ud83d\ude17|\ud83d\ude15|\ud83d\ude11|\ud83d\ude10|\ud83d\ude0e|\ud83d\ude08|\ud83d\ude07|\ud83d\ude00|\ud83d\udd67|\ud83d\udd66|\ud83d\udd65|\ud83d\udd64|\ud83d\udd63|\ud83d\udd62|\ud83d\udd61|\ud83d\udd60|\ud83d\udd5f|\ud83d\udd5e|\ud83d\udd5d|\ud83d\udd5c|\ud83d\udd2d|\ud83d\udd2c|\ud83d\udd15|\ud83d\udd09|\ud83d\udd08|\ud83d\udd07|\ud83d\udd06|\ud83d\udd05|\ud83d\udd04|\ud83d\udd02|\ud83d\udd01|\ud83d\udd00|\ud83d\udcf5|\ud83d\udcef|\ud83d\udced|\ud83d\udcec|\ud83d\udcb7|\ud83d\udcb6|\ud83d\udcad|\ud83d\udc6d|\ud83d\udc6c|\ud83d\udc65|\ud83d\udc2a|\ud83d\udc16|\ud83d\udc15|\ud83d\udc13|\ud83d\udc10|\ud83d\udc0f|\ud83d\udc0b|\ud83d\udc0a|\ud83d\udc09|\ud83d\udc08|\ud83d\udc07|\ud83d\udc06|\ud83d\udc05|\ud83d\udc04|\ud83d\udc03|\ud83d\udc02|\ud83d\udc01|\ud83d\udc00|\ud83c\udfe4|\ud83c\udfc9|\ud83c\udfc7|\ud83c\udf7c|\ud83c\udf50|\ud83c\udf4b|\ud83c\udf33|\ud83c\udf32|\ud83c\udf1e|\ud83c\udf1d|\ud83c\udf1c|\ud83c\udf1a|\ud83c\udf18|\ud83c\udccf|\ud83c\udd70|\ud83c\udd71|\ud83c\udd7e|\ud83c\udd8e|\ud83c\udd91|\ud83c\udd92|\ud83c\udd93|\ud83c\udd94|\ud83c\udd95|\ud83c\udd96|\ud83c\udd97|\ud83c\udd98|\ud83c\udd99|\ud83c\udd9a|\ud83d\udc77|\ud83d\udec5|\ud83d\udec4|\ud83d\udec3|\ud83d\udec2|\ud83d\udec1|\ud83d\udebf|\ud83d\udeb8|\ud83d\udeb7|\ud83d\udeb5|\ud83c\ude01|\ud83c\ude02|\ud83c\ude32|\ud83c\ude33|\ud83c\ude34|\ud83c\ude35|\ud83c\ude36|\ud83c\ude37|\ud83c\ude38|\ud83c\ude39|\ud83c\ude3a|\ud83c\ude50|\ud83c\ude51|\ud83c\udf00|\ud83c\udf01|\ud83c\udf02|\ud83c\udf03|\ud83c\udf04|\ud83c\udf05|\ud83c\udf06|\ud83c\udf07|\ud83c\udf08|\ud83c\udf09|\ud83c\udf0a|\ud83c\udf0b|\ud83c\udf0c|\ud83c\udf0f|\ud83c\udf11|\ud83c\udf13|\ud83c\udf14|\ud83c\udf15|\ud83c\udf19|\ud83c\udf1b|\ud83c\udf1f|\ud83c\udf20|\ud83c\udf30|\ud83c\udf31|\ud83c\udf34|\ud83c\udf35|\ud83c\udf37|\ud83c\udf38|\ud83c\udf39|\ud83c\udf3a|\ud83c\udf3b|\ud83c\udf3c|\ud83c\udf3d|\ud83c\udf3e|\ud83c\udf3f|\ud83c\udf40|\ud83c\udf41|\ud83c\udf42|\ud83c\udf43|\ud83c\udf44|\ud83c\udf45|\ud83c\udf46|\ud83c\udf47|\ud83c\udf48|\ud83c\udf49|\ud83c\udf4a|\ud83c\udf4c|\ud83c\udf4d|\ud83c\udf4e|\ud83c\udf4f|\ud83c\udf51|\ud83c\udf52|\ud83c\udf53|\ud83c\udf54|\ud83c\udf55|\ud83c\udf56|\ud83c\udf57|\ud83c\udf58|\ud83c\udf59|\ud83c\udf5a|\ud83c\udf5b|\ud83c\udf5c|\ud83c\udf5d|\ud83c\udf5e|\ud83c\udf5f|\ud83c\udf60|\ud83c\udf61|\ud83c\udf62|\ud83c\udf63|\ud83c\udf64|\ud83c\udf65|\ud83c\udf66|\ud83c\udf67|\ud83c\udf68|\ud83c\udf69|\ud83c\udf6a|\ud83c\udf6b|\ud83c\udf6c|\ud83c\udf6d|\ud83c\udf6e|\ud83c\udf6f|\ud83c\udf70|\ud83c\udf71|\ud83c\udf72|\ud83c\udf73|\ud83c\udf74|\ud83c\udf75|\ud83c\udf76|\ud83c\udf77|\ud83c\udf78|\ud83c\udf79|\ud83c\udf7a|\ud83c\udf7b|\ud83c\udf80|\ud83c\udf81|\ud83c\udf82|\ud83c\udf83|\ud83c\udf84|\ud83c\udf85|\ud83c\udf86|\ud83c\udf87|\ud83c\udf88|\ud83c\udf89|\ud83c\udf8a|\ud83c\udf8b|\ud83c\udf8c|\ud83c\udf8d|\ud83c\udf8e|\ud83c\udf8f|\ud83c\udf90|\ud83c\udf91|\ud83c\udf92|\ud83c\udf93|\ud83c\udfa0|\ud83c\udfa1|\ud83c\udfa2|\ud83c\udfa3|\ud83c\udfa4|\ud83c\udfa5|\ud83c\udfa6|\ud83c\udfa7|\ud83c\udfa8|\ud83c\udfa9|\ud83c\udfaa|\ud83c\udfab|\ud83c\udfac|\ud83c\udfad|\ud83c\udfae|\ud83c\udfaf|\ud83c\udfb0|\ud83c\udfb1|\ud83c\udfb2|\ud83c\udfb3|\ud83c\udfb4|\ud83c\udfb5|\ud83c\udfb6|\ud83c\udfb7|\ud83c\udfb8|\ud83c\udfb9|\ud83c\udfba|\ud83c\udfbb|\ud83c\udfbc|\ud83c\udfbd|\ud83c\udfbe|\ud83c\udfbf|\ud83c\udfc0|\ud83c\udfc1|\ud83c\udfc2|\ud83c\udfc3|\ud83c\udfc4|\ud83c\udfc6|\ud83c\udfc8|\ud83c\udfca|\ud83c\udfe0|\ud83c\udfe1|\ud83c\udfe2|\ud83c\udfe3|\ud83c\udfe5|\ud83c\udfe6|\ud83c\udfe7|\ud83c\udfe8|\ud83c\udfe9|\ud83c\udfea|\ud83c\udfeb|\ud83c\udfec|\ud83c\udfed|\ud83c\udfee|\ud83c\udfef|\ud83c\udff0|\ud83d\udc0c|\ud83d\udc0d|\ud83d\udc0e|\ud83d\udc11|\ud83d\udc12|\ud83d\udc14|\ud83d\udc17|\ud83d\udc18|\ud83d\udc19|\ud83d\udc1a|\ud83d\udc1b|\ud83d\udc1c|\ud83d\udc1d|\ud83d\udc1e|\ud83d\udc1f|\ud83d\udc20|\ud83d\udc21|\ud83d\udc22|\ud83d\udc23|\ud83d\udc24|\ud83d\udc25|\ud83d\udc26|\ud83d\udc27|\ud83d\udc28|\ud83d\udc29|\ud83d\udc2b|\ud83d\udc2c|\ud83d\udc2d|\ud83d\udc2e|\ud83d\udc2f|\ud83d\udc30|\ud83d\udc31|\ud83d\udc32|\ud83d\udc33|\ud83d\udc34|\ud83d\udc35|\ud83d\udc36|\ud83d\udc37|\ud83d\udc38|\ud83d\udc39|\ud83d\udc3a|\ud83d\udc3b|\ud83d\udc3c|\ud83d\udc3d|\ud83d\udc3e|\ud83d\udc40|\ud83d\udc42|\ud83d\udc43|\ud83d\udc44|\ud83d\udc45|\ud83d\udc46|\ud83d\udc47|\ud83d\udc48|\ud83d\udc49|\ud83d\udc4a|\ud83d\udc4b|\ud83d\udc4c|\ud83d\udc4d|\ud83d\udc4e|\ud83d\udc4f|\ud83d\udc50|\ud83d\udc51|\ud83d\udc52|\ud83d\udc53|\ud83d\udc54|\ud83d\udc55|\ud83d\udc56|\ud83d\udc57|\ud83d\udc58|\ud83d\udc59|\ud83d\udc5a|\ud83d\udc5b|\ud83d\udc5c|\ud83d\udc5d|\ud83d\udc5e|\ud83d\udc5f|\ud83d\udc60|\ud83d\udc61|\ud83d\udc62|\ud83d\udc63|\ud83d\udc64|\ud83d\udc66|\ud83d\udc67|\ud83d\udc68|\ud83d\udc69|\ud83d\udc6a|\ud83d\udc6b|\ud83d\udc6e|\ud83d\udc6f|\ud83d\udc70|\ud83d\udc71|\ud83d\udc72|\ud83d\udc73|\ud83d\udc74|\ud83d\udc75|\ud83d\udc76|\ud83d\udeb4|\ud83d\udc78|\ud83d\udc79|\ud83d\udc7a|\ud83d\udc7b|\ud83d\udc7c|\ud83d\udc7d|\ud83d\udc7e|\ud83d\udc7f|\ud83d\udc80|\ud83d\udc81|\ud83d\udc82|\ud83d\udc83|\ud83d\udc84|\ud83d\udc85|\ud83d\udc86|\ud83d\udc87|\ud83d\udc88|\ud83d\udc89|\ud83d\udc8a|\ud83d\udc8b|\ud83d\udc8c|\ud83d\udc8d|\ud83d\udc8e|\ud83d\udc8f|\ud83d\udc90|\ud83d\udc91|\ud83d\udc92|\ud83d\udc93|\ud83d\udc94|\ud83d\udc95|\ud83d\udc96|\ud83d\udc97|\ud83d\udc98|\ud83d\udc99|\ud83d\udc9a|\ud83d\udc9b|\ud83d\udc9c|\ud83d\udc9d|\ud83d\udc9e|\ud83d\udc9f|\ud83d\udca0|\ud83d\udca1|\ud83d\udca2|\ud83d\udca3|\ud83d\udca4|\ud83d\udca5|\ud83d\udca6|\ud83d\udca7|\ud83d\udca8|\ud83d\udca9|\ud83d\udcaa|\ud83d\udcab|\ud83d\udcac|\ud83d\udcae|\ud83d\udcaf|\ud83d\udcb0|\ud83d\udcb1|\ud83d\udcb2|\ud83d\udcb3|\ud83d\udcb4|\ud83d\udcb5|\ud83d\udcb8|\ud83d\udcb9|\ud83d\udcba|\ud83d\udcbb|\ud83d\udcbc|\ud83d\udcbd|\ud83d\udcbe|\ud83d\udcbf|\ud83d\udcc0|\ud83d\udcc1|\ud83d\udcc2|\ud83d\udcc3|\ud83d\udcc4|\ud83d\udcc5|\ud83d\udcc6|\ud83d\udcc7|\ud83d\udcc8|\ud83d\udcc9|\ud83d\udcca|\ud83d\udccb|\ud83d\udccc|\ud83d\udccd|\ud83d\udcce|\ud83d\udccf|\ud83d\udcd0|\ud83d\udcd1|\ud83d\udcd2|\ud83d\udcd3|\ud83d\udcd4|\ud83d\udcd5|\ud83d\udcd6|\ud83d\udcd7|\ud83d\udcd8|\ud83d\udcd9|\ud83d\udcda|\ud83d\udcdb|\ud83d\udcdc|\ud83d\udcdd|\ud83d\udcde|\ud83d\udcdf|\ud83d\udce0|\ud83d\udce1|\ud83d\udce2|\ud83d\udce3|\ud83d\udce4|\ud83d\udce5|\ud83d\udce6|\ud83d\udce7|\ud83d\udce8|\ud83d\udce9|\ud83d\udcea|\ud83d\udceb|\ud83d\udcee|\ud83d\udcf0|\ud83d\udcf1|\ud83d\udcf2|\ud83d\udcf3|\ud83d\udcf4|\ud83d\udcf6|\ud83d\udcf7|\ud83d\udcf9|\ud83d\udcfa|\ud83d\udcfb|\ud83d\udcfc|\ud83d\udd03|\ud83d\udd0a|\ud83d\udd0b|\ud83d\udd0c|\ud83d\udd0d|\ud83d\udd0e|\ud83d\udd0f|\ud83d\udd10|\ud83d\udd11|\ud83d\udd12|\ud83d\udd13|\ud83d\udd14|\ud83d\udd16|\ud83d\udd17|\ud83d\udd18|\ud83d\udd19|\ud83d\udd1a|\ud83d\udd1b|\ud83d\udd1c|\ud83d\udd1d|\ud83d\udd1e|\ud83d\udd1f|\ud83d\udd20|\ud83d\udd21|\ud83d\udd22|\ud83d\udd23|\ud83d\udd24|\ud83d\udd25|\ud83d\udd26|\ud83d\udd27|\ud83d\udd28|\ud83d\udd29|\ud83d\udd2a|\ud83d\udd2b|\ud83d\udd2e|\ud83d\udd2f|\ud83d\udd30|\ud83d\udd31|\ud83d\udd32|\ud83d\udd33|\ud83d\udd34|\ud83d\udd35|\ud83d\udd36|\ud83d\udd37|\ud83d\udd38|\ud83d\udd39|\ud83d\udd3a|\ud83d\udd3b|\ud83d\udd3c|\ud83d\udd3d|\ud83d\udd50|\ud83d\udd51|\ud83d\udd52|\ud83d\udd53|\ud83d\udd54|\ud83d\udd55|\ud83d\udd56|\ud83d\udd57|\ud83d\udd58|\ud83d\udd59|\ud83d\udd5a|\ud83d\udd5b|\ud83d\uddfb|\ud83d\uddfc|\ud83d\uddfd|\ud83d\uddfe|\ud83d\uddff|\ud83d\ude01|\ud83d\ude02|\ud83d\ude03|\ud83d\ude04|\ud83d\ude05|\ud83d\ude06|\ud83d\ude09|\ud83d\ude0a|\ud83d\ude0b|\ud83d\ude0c|\ud83d\ude0d|\ud83d\ude0f|\ud83d\ude12|\ud83d\ude13|\ud83d\ude14|\ud83d\ude16|\ud83d\ude18|\ud83d\ude1a|\ud83d\ude1c|\ud83d\ude1d|\ud83d\ude1e|\ud83d\ude20|\ud83d\ude21|\ud83d\ude22|\ud83d\ude23|\ud83d\ude24|\ud83d\ude25|\ud83d\ude28|\ud83d\ude29|\ud83d\ude2a|\ud83d\ude2b|\ud83d\ude2d|\ud83d\ude30|\ud83d\ude31|\ud83d\ude32|\ud83d\ude33|\ud83d\ude35|\ud83d\ude37|\ud83d\ude38|\ud83d\ude39|\ud83d\ude3a|\ud83d\ude3b|\ud83d\ude3c|\ud83d\ude3d|\ud83d\ude3e|\ud83d\ude3f|\ud83d\ude40|\ud83d\ude45|\ud83d\ude46|\ud83d\ude47|\ud83d\ude48|\ud83d\ude49|\ud83d\ude4a|\ud83d\ude4b|\ud83d\ude4c|\ud83d\ude4d|\ud83d\ude4e|\ud83d\ude4f|\ud83d\ude80|\ud83d\ude83|\ud83d\ude84|\ud83d\ude85|\ud83d\ude87|\ud83d\ude89|\ud83d\ude8c|\ud83d\ude8f|\ud83d\ude91|\ud83d\ude92|\ud83d\ude93|\ud83d\ude95|\ud83d\ude97|\ud83d\ude99|\ud83d\ude9a|\ud83d\udea2|\ud83d\udea4|\ud83d\udea5|\ud83d\udea7|\ud83d\udea8|\ud83d\udea9|\ud83d\udeaa|\ud83d\udeab|\ud83d\udeac|\ud83d\udead|\ud83d\udeb2|\ud83d\udeb6|\ud83d\udeb9|\ud83d\udeba|\ud83d\udebb|\ud83d\udebc|\ud83d\udebd|\ud83d\udebe|\ud83d\udec0|\ud83c\udde6|\ud83c\udde7|\ud83c\udde8|\ud83c\udde9|\ud83c\uddea|\ud83c\uddeb|\ud83c\uddec|\ud83c\udded|\ud83c\uddee|\ud83c\uddef|\ud83c\uddf0|\ud83c\uddf1|\ud83c\uddf2|\ud83c\uddf3|\ud83c\uddf4|\ud83c\uddf5|\ud83c\uddf6|\ud83c\uddf7|\ud83c\uddf8|\ud83c\uddf9|\ud83c\uddfa|\ud83c\uddfb|\ud83c\uddfc|\ud83c\uddfd|\ud83c\uddfe|\ud83c\uddff|\ud83c\udf0d|\ud83c\udf0e|\ud83c\udf10|\ud83c\udf12|\ud83c\udf16|\ud83c\udf17|\ue50a|\u3030|\u27b0|\u2797|\u2796|\u2795|\u2755|\u2754|\u2753|\u274e|\u274c|\u2728|\u270b|\u270a|\u2705|\u26ce|\u23f3|\u23f0|\u23ec|\u23eb|\u23ea|\u23e9|\u2122|\u27bf|\u00a9|\u00ae)|(?:(?:\ud83c\udc04|\ud83c\udd7f|\ud83c\ude1a|\ud83c\ude2f|\u3299|\u303d|\u2b55|\u2b50|\u2b1c|\u2b1b|\u2b07|\u2b06|\u2b05|\u2935|\u2934|\u27a1|\u2764|\u2757|\u2747|\u2744|\u2734|\u2733|\u2716|\u2714|\u2712|\u270f|\u270c|\u2709|\u2708|\u2702|\u26fd|\u26fa|\u26f5|\u26f3|\u26f2|\u26ea|\u26d4|\u26c5|\u26c4|\u26be|\u26bd|\u26ab|\u26aa|\u26a1|\u26a0|\u2693|\u267f|\u267b|\u3297|\u2666|\u2665|\u2663|\u2660|\u2653|\u2652|\u2651|\u2650|\u264f|\u264e|\u264d|\u264c|\u264b|\u264a|\u2649|\u2648|\u263a|\u261d|\u2615|\u2614|\u2611|\u260e|\u2601|\u2600|\u25fe|\u25fd|\u25fc|\u25fb|\u25c0|\u25b6|\u25ab|\u25aa|\u24c2|\u231b|\u231a|\u21aa|\u21a9|\u2199|\u2198|\u2197|\u2196|\u2195|\u2194|\u2139|\u2049|\u203c|\u2668)([\uFE0E\uFE0F]?)))/g,SVGPATH:s,ZREKNARF:''+s+"",CHAT_BUTTON:''+s+"",ROOMS:'',CAMERA:'',INVITE:'',EYE:'',CLOCK:'',GEAR:'',HEART:'',EMOTE:'',STAR:'',CLOSE:''}},{}],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 s,t=t&&t.length?t[0].toLowerCase():null;return"y"==t||"yes"==t||"true"==t||"on"==t?s=!0:("n"==t||"no"==t||"false"==t||"off"==t)&&(s=!1),void 0===s?"Developer Mode is currently "+(this.settings.developer_mode?"enabled.":"disabled."):(this.settings.set("developer_mode",s),"Developer Mode is now "+(s?"enabled":"disabled")+". Please refresh your browser.")},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 s=e.FrankerFaceZ,o=t("../utils"),n=t("../constants");s.prototype.setup_channel=function(){this.log("Creating channel style element.");var e=this._channel_style=document.createElement("style");e.id="ffz-channel-css",document.head.appendChild(e),document.body.classList.toggle("ffz-hide-view-count",!this.settings.channel_views),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 Index view.");var t=App.__container__.resolve("view:channel/index"),o=this;if(t){this._modify_cindex(t);try{t.create().destroy()}catch(n){}for(var i in Ember.View.views)if(Ember.View.views.hasOwnProperty(i)){var a=Ember.View.views[i];a instanceof t&&(this.log("Manually updating Channel Index view.",a),this._modify_cindex(a),a.ffzInit())}this.log("Hooking the Ember Channel model."),t=App.__container__.resolve("model:channel"),t&&(t.reopen({ffz_host_target:void 0,setHostMode:function(e){return o.settings.hosted_channels?(this.set("ffz_host_target",e.target),this._super(e)):(this.set("ffz_host_target",void 0),this._super({target:void 0,delay:0}))}}),this.log("Hooking the Ember Channel controller."),t=App.__container__.lookup("controller:channel"),t&&t.reopen({ffzUpdateUptime:function(){o._cindex&&o._cindex.ffzUpdateUptime()}.observes("isLive","content.id"),ffzUpdateTitle:function(){var e=this.get("content.name"),t=this.get("content.display_name");t&&(s.capitalization[e]=[t,Date.now()]),o._cindex&&o._cindex.ffzFixTitle()}.observes("content.status","content.id"),ffzHostTarget:function(){var e=this.get("content.hostModeTarget"),t=e&&e.get("name"),n=e&&e.get("id"),i=e&&e.get("display_name");n!==o.__old_host_target&&(o.__old_host_target&&o.ws_send("unsub_channel",o.__old_host_target),n?(o.ws_send("sub_channel",n),o.__old_host_target=n):delete o.__old_host_target),i&&(s.capitalization[t]=[i,Date.now()]),o.settings.group_tabs&&o._chatv&&o._chatv.ffzRebuildTabs(),o.settings.follow_buttons&&o.rebuild_following_ui(),o.settings.srl_races&&o.rebuild_race_ui()}.observes("content.hostModeTarget")}))}},s.prototype._modify_cindex=function(e){var t=this;e.reopen({didInsertElement:function(){this._super();try{this.ffzInit()}catch(e){t.error("CIndex didInsertElement: "+e)}},willClearRender:function(){try{this.ffzTeardown()}catch(e){t.error("CIndex willClearRender: "+e)}return this._super()},ffzInit:function(){var e=this.get("controller.id"),s=this.get("element");t._cindex=this,t.ws_send("sub_channel",e),s.setAttribute("data-channel",e),s.classList.add("ffz-channel"),this.$(".theatre-button a").attr("title","Theater Mode (Alt+T)"),this.ffzFixTitle(),this.ffzUpdateUptime(),this.ffzUpdateChatters(),this.ffzUpdateHostButton();var o=this.get("element").querySelector(".svg-glyph_views:not(.ffz-svg)");o&&o.parentNode.classList.add("twitch-channel-views"),t.settings.follow_buttons&&t.rebuild_following_ui(),t.settings.srl_races&&t.rebuild_race_ui()},ffzFixTitle:function(){if(!t.has_bttv&&t.settings.stream_title){var e=this.get("controller.status"),s=this.get("controller.id");e=t.render_tokens(t.tokenize_line(s,s,e,!0)),this.$(".title span").each(function(t,s){var o=s.querySelectorAll("script");s.innerHTML=o.length?o[0].outerHTML+e+o[1].outerHTML:e})}},ffzUpdateHostButton:function(){var e=this.get("controller.id"),n=this.get("controller.hostModeTarget.id"),i=t.get_user(),a=i&&t.rooms&&t.rooms[i.login]&&t.rooms[i.login].room,r=a&&a.ffz_host_target,d=a&&a.ffz_hosts_left,u=this.get("element");if(this.set("ffz_host_updating",!1),e){var c=u&&u.querySelector(".stats-and-actions .channel-actions"),l=c&&c.querySelector("#ffz-ui-host-button");if(c&&t.settings.stream_host_button&&i&&i.login!==e){if(!l){l=document.createElement("span"),l.id="ffz-ui-host-button",l.className="button action tooltip",l.addEventListener("click",this.ffzClickHost.bind(l,this,!1));var h=c.querySelector(":scope > .theatre-button");h?c.insertBefore(l,h):c.appendChild(l)}l.classList.remove("disabled"),l.innerHTML=e===r?"Unhost":"Host",l.title=r?"You are now hosting "+o.sanitize(s.get_capitalization(r))+".":"You are not hosting any channel.","number"==typeof d&&(l.title+=" You have "+d+" host command"+o.pluralize(d)+" remaining this half hour.")}else l&&l.parentElement.removeChild(l)}if(n){var c=u&&u.querySelector("#hostmode .channel-actions"),l=c&&c.querySelector("#ffz-ui-host-button");if(c&&t.settings.stream_host_button&&i&&i.login!==n){if(!l){l=document.createElement("span"),l.id="ffz-ui-host-button",l.className="button action tooltip",l.addEventListener("click",this.ffzClickHost.bind(l,this,!0));var h=c.querySelector(":scope > .theatre-button");h?c.insertBefore(l,h):c.appendChild(l)}l.classList.remove("disabled"),l.innerHTML=n===r?"Unhost":"Host",l.title=r?"You are currently hosting "+o.sanitize(s.get_capitalization(r))+". Click to "+(n===r?"unhost":"host")+" this channel.":"You are not currently hosting any channel. Click to host this channel.","number"==typeof d&&(l.title+=" You have "+d+" host command"+o.pluralize(d)+" remaining this half hour.")}else l&&l.parentElement.removeChild(l) +}},ffzClickHost:function(e,s){var o=e.get(s?"controller.hostModeTarget.id":"controller.id"),n=t.get_user(),i=n&&t.rooms&&t.rooms[n.login]&&t.rooms[n.login].room,a=i&&i.ffz_host_target;i&&!e.get("ffz_host_updating")&&(this.classList.add("disabled"),this.title="Updating...",e.set("ffz_host_updating",!0),i.send(a===o?"/unhost":"/host "+o))},ffzUpdateChatters:function(){var e=this.get("controller.id"),s=t.rooms&&t.rooms[e];if(!s||!t.settings.chatter_count){var i=this.get("element").querySelector("#ffz-chatter-display");return i&&i.parentElement.removeChild(i),i=this.get("element").querySelector("#ffz-ffzchatter-display"),void(i&&i.parentElement.removeChild(i))}var a=Object.keys(s.room.get("ffz_chatters")||{}).length,r=s.ffz_chatters||0,d=s.ffz_viewers||0,i=this.get("element").querySelector("#ffz-chatter-display span");if(!i){var u=this.get("element").querySelector(".stats-and-actions .channel-stats");if(!u)return;var c=document.createElement("span");c.className="ffz stat",c.id="ffz-chatter-display",c.title="Currently in Chat",c.innerHTML=n.ROOMS+" ",i=document.createElement("span"),c.appendChild(i);var l=u.querySelector("#ffz-ffzchatter-display");l?u.insertBefore(c,l):u.appendChild(c),jQuery(c).tipsy()}if(i.innerHTML=o.number_commas(a),!r&&!d)return i=this.get("element").querySelector("#ffz-ffzchatter-display"),void(i&&i.parentNode.removeChild(i));if(i=this.get("element").querySelector("#ffz-ffzchatter-display span"),!i){var u=this.get("element").querySelector(".stats-and-actions .channel-stats");if(!u)return;var c=document.createElement("span");c.className="ffz stat",c.id="ffz-ffzchatter-display",c.title="Viewers (In Chat) with FrankerFaceZ",c.innerHTML=n.ZREKNARF+" ",i=document.createElement("span"),c.appendChild(i);var l=u.querySelector("#ffz-chatter-display");l?u.insertBefore(c,l.nextSibling):u.appendChild(c),jQuery(c).tipsy()}i.innerHTML=o.number_commas(d)+" ("+o.number_commas(r)+")"},ffzUpdateUptime:function(){if(this._ffz_update_uptime&&(clearTimeout(this._ffz_update_uptime),delete this._ffz_update_uptime),!t.settings.stream_uptime||!this.get("controller.isLiveAccordingToKraken")){var e=this.get("element").querySelector("#ffz-uptime-display");return void(e&&e.parentElement.removeChild(e))}this._ffz_update_uptime=setTimeout(this.ffzUpdateUptime.bind(this),1e3);var s=this.get("controller.content.stream.created_at");if(s&&(s=o.parse_date(s))){var i=Math.floor((Date.now()-s.getTime())/1e3);if(!(0>i)){var e=this.get("element").querySelector("#ffz-uptime-display span");if(!e){var a=this.get("element").querySelector(".stats-and-actions .channel-stats");if(!a)return;var r=document.createElement("span");r.className="ffz stat",r.id="ffz-uptime-display",r.title="Stream Uptime (since "+s.toLocaleString()+")",r.innerHTML=n.CLOCK+" ",e=document.createElement("span"),r.appendChild(e);var d=a.querySelector(".live-count");if(d)a.insertBefore(r,d.nextSibling);else try{d=a.querySelector("script:nth-child(0n+2)"),a.insertBefore(r,d.nextSibling)}catch(u){a.insertBefore(r,a.childNodes[0])}jQuery(r).tipsy({html:!0})}e.innerHTML=o.time_to_string(i)}}},ffzTeardown:function(){var e=this.get("controller.id");e&&t.ws_send("unsub_channel",e),this.get("element").setAttribute("data-channel",""),t._cindex=void 0,this._ffz_update_uptime&&clearTimeout(this._ffz_update_uptime),o.update_css(t._channel_style,e,null)}})},s.settings_info.chatter_count={type:"boolean",value:!1,category:"Channel Metadata",name:"Chatter Count",help:"Display the current number of users connected to chat beneath the channel.",on_update:function(e){if(this._cindex&&this._cindex.ffzUpdateChatters(),e&&this.rooms)for(var t in this.rooms)this.rooms.hasOwnProperty(t)&&this.rooms[t].room&&this.rooms[t].room.ffzInitChatterCount()}},s.settings_info.channel_views={type:"boolean",value:!0,category:"Channel Metadata",name:"Channel Views",help:"Display the number of times the channel has been viewed beneath the stream.",on_update:function(e){document.body.classList.toggle("ffz-hide-view-count",!e)}},s.settings_info.hosted_channels={type:"boolean",value:!0,category:"Channel Metadata",name:"Channel Hosting",help:"Display other channels that have been featured by the current channel.",on_update:function(e){var t=document.querySelector("input.ffz-setting-hosted-channels");if(t&&(t.checked=e),this._cindex){var s=this._cindex.get("controller.model"),o=s&&this.rooms&&this.rooms[s.get("id")],n=o&&o.room&&o.room.get("ffz_host_target");s&&o&&s.setHostMode({target:n,delay:0})}}},s.settings_info.stream_host_button={type:"boolean",value:!0,category:"Channel Metadata",name:"Host This Channel Button",help:"Display a button underneath streams that make it easy to host them with your own channel.",on_update:function(){this._cindex&&this._cindex.ffzUpdateHostButton()}},s.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._cindex&&this._cindex.ffzUpdateUptime()}},s.settings_info.stream_title={type:"boolean",value:!0,no_bttv:!0,category:"Channel Metadata",name:"Title Links",help:"Make links in stream titles clickable.",on_update:function(){this._cindex&&this._cindex.ffzFixTitle()}}},{"../constants":3,"../utils":32}],6:[function(t){var s=e.FrankerFaceZ,o=(t("../utils"),t("../constants"),{BACKSPACE:8,TAB:9,ENTER:13,ESC:27,SPACE:32,LEFT:37,UP:38,RIGHT:39,DOWN:40,TWO:50,COLON:59,FAKE_COLON:186}),n=function(e){if("number"==typeof e.selectionStart)return e.selectionStart;if(!e.createTextRange)return-1;var t=document.selection.createRange(),s=e.createTextRange();return s.moveToBookmark(t.getBookmark()),s.moveStart("character",-e.value.length),s.text.length},i=function(e,t){if(e.setSelectionRange)e.setSelectionRange(t,t);else if(e.createTextRange){var s=e.createTextRange();s.move("character",-e.value.length),s.move("character",t),s.select()}};s.settings_info.input_quick_reply={type:"boolean",value:!0,category:"Chat Input",no_bttv:!0,name:"Reply to Whispers with /r",help:"Automatically replace /r at the start of the line with the command to whisper to the person you've whispered with most recently."},s.settings_info.input_mru={type:"boolean",value:!0,category:"Chat Input",no_bttv:!0,name:"Chat Input History",help:"Use the Up and Down arrows in chat to select previously sent chat messages."},s.settings_info.input_emoji={type:"boolean",value:!1,category:"Chat Input",no_bttv:!0,name:"Enter Emoji By Name",help:"Replace emoji that you type by name with the character. :+1: becomes 👍."},s.prototype.setup_chat_input=function(){this.log("Hooking the Ember Chat Input controller.");var e=App.__container__.resolve("component:twitch-chat-input");if(e&&(this._modify_chat_input(e),this._roomv))for(var t=0;t0&&Ember.run.next(function(){var e=d.get("textareaValue"),t=e.lastIndexOf(":",u-1);if(-1!==t&&-1!==u&&":"===e.charAt(u)){var s=e.substr(t+1,u-t-1),o=a.emoji_names[s],n=a.emoji_data[o];if(n){var r=e.substr(0,t)+n.raw;d.set("textareaValue",r+e.substr(u+1)),Ember.run.next(function(){i(d.get("chatTextArea"),r.length)})}}}))}return this._onKeyDown(t);case o.ENTER:s.shiftKey||s.shiftLeft||this.set("ffz_mru_index",-1);default:return this._onKeyDown(t)}},ffzCycleMRU:function(e,t){var s=n(this.get("chatTextArea"));if(t===s){var i=this.get("ffz_mru_index"),a=this._parentView.get("context.model.mru_list")||[];i=e===o.UP?(i+1)%(a.length+1):(i+a.length)%(a.length+1);var r=this.get("ffz_old_mru");(void 0===r||null===r)&&(r=this.get("textareaValue"),this.set("ffz_old_mru",r));var d=a[i];void 0===d&&(this.set("ffz_old_mru",void 0),d=r),this.set("ffz_mru_index",i),this.set("textareaValue",d)}},completeSuggestion:function(e){var t,o,n=this,a=this.get("textareaValue"),r=this.get("partialNameStartIndex");t=a.substring(0,r)+("/"===a.charAt(0)?e:s.get_capitalization(e)),o=a.substring(r+this.get("partialName").length),o||(t+=" "),this.set("textareaValue",t+o),this.set("isShowingSuggestions",!1),this.set("partialName",""),this.trackSuggestionsCompleted(),Ember.run.next(function(){i(n.get("chatTextArea"),t.length)})}})}},{"../constants":3,"../utils":32}],7:[function(t){var s=e.FrankerFaceZ,o=t("../utils"),n=t("../constants");s.settings_info.swap_sidebars={type:"boolean",value:!1,category:"Appearance",no_bttv:!0,name:"Swap Sidebar Positions",help:"Swap the positions of the left and right sidebars, placing chat on the left.",on_update:function(e){this.has_bttv||document.body.classList.toggle("ffz-sidebar-swap",e)}},s.settings_info.minimal_chat={type:"boolean",value:!1,category:"Chat Appearance",name:"Minimalistic Chat",help:"Hide all of the chat user interface, only showing messages and an input box.",on_update:function(e){if(document.body.classList.toggle("ffz-minimal-chat",e),this.settings.group_tabs&&this._chatv&&this._chatv._ffz_tabs){var t=this;setTimeout(function(){t._chatv&&t._chatv.$(".chat-room").css("top",t._chatv._ffz_tabs.offsetHeight+"px"),t._roomv&&t._roomv.get("stuckToBottom")&&t._roomv._scrollToBottom()},0)}this._chatv&&this._chatv.get("controller.showList")&&this._chatv.set("controller.showList",!1)}},s.settings_info.prevent_clear={type:"boolean",value:!1,no_bttv:!0,category:"Chat Filtering",name:"Show Deleted Messages",help:"Fade deleted messages instead of replacing them, and prevent chat from being cleared.",on_update:function(e){if(!this.has_bttv&&this.rooms)for(var t in this.rooms){var s=this.rooms[t],o=s&&s.room;o&&o.get("messages").forEach(function(t,s){e&&!t.ffz_deleted&&t.deleted?o.set("messages."+s+".deleted",!1):!t.ffz_deleted||e||t.deleted||o.set("messages."+s+".deleted",!0)})}}},s.settings_info.chat_history={type:"boolean",value:!0,visible:!1,category:"Chat Appearance",name:"Chat History Alpha",help:"Load previous chat messages when loading a chat room so you can see what people have been talking about. This currently only works in a handful of channels due to server capacity."},s.settings_info.group_tabs={type:"boolean",value:!1,no_bttv:!0,category:"Chat Moderation",name:"Chat Room Tabs Beta",help:"Enhanced UI for switching the current chat room and noticing new messages.",on_update:function(e){var t=!this.has_bttv&&e;this._chatv&&t!==this._group_tabs_state&&(t?this._chatv.ffzEnableTabs():this._chatv.ffzDisableTabs())}},s.settings_info.pinned_rooms={value:[],visible:!1},s.settings_info.visible_rooms={value:[],visible:!1},s.prototype.setup_chatview=function(){document.body.classList.toggle("ffz-minimal-chat",this.settings.minimal_chat),this.has_bttv||document.body.classList.toggle("ffz-sidebar-swap",this.settings.swap_sidebars),this.log("Hooking the Ember Chat controller.");var e=App.__container__.lookup("controller:chat"),t=this;e&&e.reopen({ffzUpdateChannels:function(){t._chatv&&(t._chatv.ffzRebuildMenu(),t.settings.group_tabs&&t._chatv.ffzRebuildTabs())}.observes("currentChannelRoom","connectedPrivateGroupRooms"),removeCurrentChannelRoom:function(){if(!t.settings.group_tabs||t.has_bttv)return this._super();var e=this.get("currentChannelRoom"),s=e&&e.get("id"),o=t.get_user();t.settings.pinned_rooms&&-1!==t.settings.pinned_rooms.indexOf(s)||(e===this.get("currentRoom")&&this.blurRoom(),e&&o&&o.login===s&&e.destroy()),this.set("currentChannelRoom",void 0)}}),this.log("Hooking the Ember Chat view.");var e=App.__container__.resolve("view:chat");this._modify_cview(e);try{e.create().destroy()}catch(s){}for(var o in Ember.View.views)if(Ember.View.views.hasOwnProperty(o)){var n=Ember.View.views[o];if(n instanceof e){this.log("Manually updating existing Chat view.",n);try{n.ffzInit()}catch(s){this.error("setup: build_ui_link: "+s)}}}this.log("Hooking the Ember Layout controller.");var i=App.__container__.lookup("controller:layout");if(i){i.reopen({ffzFixTabs:function(){t.settings.group_tabs&&t._chatv&&t._chatv._ffz_tabs&&setTimeout(function(){t._chatv&&t._chatv.$(".chat-room").css("top",t._chatv._ffz_tabs.offsetHeight+"px")},0)}.observes("isRightColumnClosed")}),this.log("Hooking the Ember 'Right Column' controller. Seriously...");var a=App.__container__.lookup("controller:right-column");a&&a.reopen({ffzFixTabs:function(){t.settings.group_tabs&&t._chatv&&t._chatv._ffz_tabs&&setTimeout(function(){t._chatv&&t._chatv.$(".chat-room").css("top",t._chatv._ffz_tabs.offsetHeight+"px")},0)}.observes("firstTabSelected")})}},s.prototype._modify_cview=function(e){var t=this;e.reopen({didInsertElement:function(){this._super();try{this.ffzInit()}catch(e){t.error("ChatView didInsertElement: "+e)}},willClearRender:function(){try{this.ffzTeardown()}catch(e){t.error("ChatView willClearRender: "+e)}this._super()},ffzInit:function(){t._chatv=this,this.$(".textarea-contain").append(t.build_ui_link(this)),!t.has_bttv&&t.settings.group_tabs&&this.ffzEnableTabs(),this.ffzRebuildMenu(),setTimeout(function(){t.settings.group_tabs&&t._chatv&&t._chatv._ffz_tabs&&t._chatv.$(".chat-room").css("top",t._chatv._ffz_tabs.offsetHeight+"px");var e=t._chatv.get("controller");e&&e.set("showList",!1)},1e3)},ffzTeardown:function(){t._chatv===this&&(t._chatv=null),this.$(".textarea-contain .ffz-ui-toggle").remove(),t.settings.group_tabs&&this.ffzDisableTabs()},ffzChangeRoom:Ember.observer("controller.currentRoom",function(){try{t.update_ui_link();var e,s=this.get("controller.currentRoom");if(s&&s.resetUnreadCount(),this._ffz_chan_table&&(e=jQuery(this._ffz_chan_table),e.children(".ffz-room-row").removeClass("active"),s&&e.children('.ffz-room-row[data-room="'+s.get("id")+'"]').addClass("active").children("span").text("")),this._ffz_group_table&&(e=jQuery(this._ffz_group_table),e.children(".ffz-room-row").removeClass("active"),s&&e.children('.ffz-room-row[data-room="'+s.get("id")+'"]').addClass("active").children("span").text("")),!t.has_bttv&&t.settings.group_tabs&&this._ffz_tabs){var o=jQuery(this._ffz_tabs);o.children(".ffz-chat-tab").removeClass("active"),s&&o.children('.ffz-chat-tab[data-room="'+s.get("id")+'"]').removeClass("tab-mentioned").removeClass("hidden").addClass("active").children("span").text("");var n=s&&s.get("canInvite");this._ffz_invite&&this._ffz_invite.classList.toggle("hidden",!n),this.set("controller.showInviteUser",n&&this.get("controller.showInviteUser")),this.$(".chat-room").css("top",this._ffz_tabs.offsetHeight+"px")}}catch(i){t.error("ChatView ffzUpdateLink: "+i)}}),ffzRebuildMenu:function(){return},ffzBuildRow:function(e,i,a,r){var d,u=document.createElement("tr"),c=document.createElement("td"),l=document.createElement("td"),h=document.createElement("td"),f=document.createElement("td"),_=i.get("isGroupRoom"),m=i===e.get("controller.currentRoom"),p=i.get("tmiRoom.displayName")||(_?i.get("tmiRoom.name"):s.get_capitalization(i.get("id"),function(e){t.log("Name for Row: "+e),l.innerHTML=o.sanitize(e)}));return l.className="ffz-room",l.innerHTML=o.sanitize(p),a?(c.innerHTML=n.CAMERA,c.title=l.title="Current Channel",c.className=l.className="tooltip"):r&&(c.innerHTML=n.EYE,c.title=l.title="Hosted Channel",c.className=l.className="tooltip"),h.className=f.className="ffz-row-switch",h.innerHTML='',f.innerHTML='',u.setAttribute("data-room",i.get("id")),u.className="ffz-room-row",u.classList.toggle("current-channel",a),u.classList.toggle("host-channel",r),u.classList.toggle("group-chat",_),u.classList.toggle("active",m),u.appendChild(c),u.appendChild(l),_?(d=document.createElement("a"),d.className="leave-chat tooltip",d.innerHTML=n.CLOSE,d.title="Leave Group",l.appendChild(d),d.addEventListener("click",function(e){e.preventDefault(),e.stopPropagation&&e.stopPropagation(),confirm('Are you sure you want to leave the group room "'+p+'"?')&&i.get("isGroupRoom")&&i.del()})):(u.appendChild(h),d=h.querySelector("a.switch"),d.addEventListener("click",function(e){e.preventDefault(),e.stopPropagation&&e.stopPropagation();var s=i.get("id"),o=-1!==t.settings.pinned_rooms.indexOf(s);o?t._leave_room(s):t._join_room(s),this.classList.toggle("active",!o)})),u.appendChild(f),d=f.querySelector("a.switch"),d.addEventListener("click",function(s){s.preventDefault(),s.stopPropagation&&s.stopPropagation();var o=i.get("id"),n=t.settings.visible_rooms,a=-1!==n.indexOf(o);a?n.removeObject(o):n.push(o),t.settings.set("visible_rooms",n),this.classList.toggle("active",!a),e.ffzRebuildTabs()}),u.addEventListener("click",function(){var t=e.get("controller");t.focusRoom(i),t.set("showList",!1)}),u},ffzEnableTabs:function(){if(!t.has_bttv&&t.settings.group_tabs){this.$(".chat-header").addClass("hidden");var e=this._ffz_tabs=document.createElement("div");e.id="ffz-group-tabs",this.$(".chat-header").after(e),this.ffzRebuildTabs()}},ffzRebuildTabs:function(){if(!t.has_bttv&&t.settings.group_tabs){var e=this._ffz_tabs||this.get("element").querySelector("#ffz-group-tabs");if(e){e.innerHTML="";var s=document.createElement("a"),o=this;s.className="button glyph-only tooltip",s.title="Chat Room Management",s.innerHTML=n.ROOMS,s.addEventListener("click",function(){var e=o.get("controller");e&&e.set("showList",!e.get("showList"))}),e.appendChild(s),s=document.createElement("a"),s.className="button glyph-only tooltip invite",s.title="Invite a User",s.innerHTML=n.INVITE,s.addEventListener("click",function(){var e=o.get("controller");e&&e.set("showInviteUser",e.get("currentRoom.canInvite")&&!e.get("showInviteUser"))}),s.classList.toggle("hidden",!this.get("controller.currentRoom.canInvite")),o._ffz_invite=s,e.appendChild(s);var i,a=this.get("controller.currentChannelRoom");a&&(i=this.ffzBuildTab(o,a,!0),i&&e.appendChild(i));var r=App.__container__.lookup("controller:channel"),d=App.__container__.resolve("model:room");if(target=r&&r.get("hostModeTarget"),target&&d){var u=target.get("id");this._ffz_host!==u&&(-1===t.settings.pinned_rooms.indexOf(this._ffz_host)&&this._ffz_host_room&&(this.get("controller.currentRoom")===this._ffz_host_room&&this.get("controller").blurRoom(),this._ffz_host_room.destroy()),this._ffz_host=u,this._ffz_host_room=d.findOne(u))}else this._ffz_host&&(-1===t.settings.pinned_rooms.indexOf(this._ffz_host)&&this._ffz_host_room&&(this.get("controller.currentRoom")===this._ffz_host_room&&this.get("controller").blurRoom(),this._ffz_host_room.destroy()),delete this._ffz_host,delete this._ffz_host_room);this._ffz_host_room&&(i=o.ffzBuildTab(o,this._ffz_host_room,!1,!0),i&&e.appendChild(i));for(var c=0;c"+u+""})),a?(l=n.CAMERA,c.title="Current Channel"):r?(l=n.EYE,c.title="Hosted Channel"):c.title=f?"Group Chat":"Pinned Channel",c.innerHTML=l+o.sanitize(d)+""+u+"",c.addEventListener("click",function(){var t=e.get("controller");t.focusRoom(i),t.set("showList",!1)}),c},ffzDisableTabs:function(){this._ffz_tabs&&(this._ffz_tabs.parentElement.removeChild(this._ffz_tabs),delete this._ffz_tabs,delete this._ffz_invite),this._ffz_host&&(-1===t.settings.pinned_rooms.indexOf(this._ffz_host)&&this._ffz_host_room&&(this.get("controller.currentRoom")===this._ffz_host_room&&this.get("controller").blurRoom(),this._ffz_host_room.destroy()),delete this._ffz_host,delete this._ffz_host_room),this.$(".chat-room").css("top",""),this.$(".chat-header").removeClass("hidden")}})},s.prototype.connect_extra_chat=function(){var e=this.get_user();if(e&&e.login&&(!this.rooms[e.login]||this.rooms[e.login].room)){var t=App.__container__.resolve("model:room");t&&t.findOne(e.login)}if(!this.has_bttv){for(var s=0;s1)return"Join Usage: /join ";var s=t[0].toLowerCase();return"#"===s.charAt(0)&&(s=s.substr(1)),this._join_room(s)?"Joining "+s+". You will always connect to this channel's chat unless you later /part from it.":"You have already joined "+s+'. Please use "/part '+s+'" to leave it.'},s.chat_commands.part=function(e,t){if(!t||!t.length||t.length>1)return"Part Usage: /part ";var s=t[0].toLowerCase();return"#"===s.charAt(0)&&(s=s.substr(1)),this._leave_room(s)?"Leaving "+s+".":this.rooms[s]?"You do not have "+s+" pinned and you cannot leave the current channel or hosted channels via /part.":"You are not in "+s+"."}},{"../constants":3,"../utils":32}],8:[function(t){var s=e.FrankerFaceZ,o=t("../utils"),n=t("../constants"),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]",a=new RegExp(i+"*,"+i+"*"),r=function(e){return(e+"").replace(/&/g,"&").replace(/'/g,"'").replace(/"/g,""").replace(//g,">")},d="http://static-cdn.jtvnw.net/emoticons/v1/",u={};build_srcset=function(e){if(u[e])return u[e];var t=u[e]=d+e+"/1.0 1x, "+d+e+"/2.0 2x, "+d+e+"/3.0 4x";return t},data_to_tooltip=function(e){var t=e.set,s=e.set_type,o=e.owner;return void 0===s&&(s="Channel"),t?(("--twitch-turbo--"==t||"turbo"==t)&&(t="Twitch Turbo",s=null),"Emoticon: "+e.code+"\n"+(s?s+": ":"")+t+(o?"\nBy: "+o.display_name:"")):e.code},build_tooltip=function(e){{var t=this._twitch_emotes[e];t?t.set:null}return t?"string"==typeof t?t:t.tooltip?t.tooltip:t.tooltip=data_to_tooltip(t):"???"},load_emote_data=function(e,t,s,o){if(s){t&&(o.code=t),this._twitch_emotes[e]=o;for(var n=build_tooltip.bind(this)(e),i=document.querySelectorAll('img[emote-id="'+e+'"]'),a=0;aYouTube: "+o.sanitize(s.title)+"
",t+="Channel: "+o.sanitize(s.channel)+" | "+o.time_to_string(s.duration)+"
",t+=o.number_commas(s.views||0)+" Views | 👍 "+o.number_commas(s.likes||0)+" 👎 "+o.number_commas(s.dislikes||0);else if("strawpoll"==s.type){t="Strawpoll: "+o.sanitize(s.title)+"
";for(var n in s.items){{var i=s.items[n];Math.floor(i/s.total*100)}t+='"}t+="
'+o.sanitize(n)+''+o.number_commas(i)+"

Total: "+o.number_commas(s.total);var a=o.parse_date(s.fetched);if(a){var r=Math.floor((a.getTime()-Date.now())/1e3);r>60&&(t+="
Data was cached "+o.time_to_string(r)+" ago.")}}else if("twitch"==s.type){t="Twitch: "+o.sanitize(s.display_name)+"
";var d=o.parse_date(s.since);d&&(t+="Member Since: "+o.date_string(d)+"
"),t+="Views: "+o.number_commas(s.views)+" | Followers: "+o.number_commas(s.followers)+""}else if("twitch_vod"==s.type)t="Twitch "+("highlight"==s.broadcast_type?"Highlight":"Broadcast")+": "+o.sanitize(s.title)+"
",t+="By: "+o.sanitize(s.display_name)+(s.game?" | Playing: "+o.sanitize(s.game):" | Not Playing")+"
",t+="Views: "+o.number_commas(s.views)+" | "+o.time_to_string(s.length);else if("twitter"==s.type)t="Tweet By: "+o.sanitize(s.user)+"
",t+=o.sanitize(s.tweet);else if("reputation"==s.type){if(t=''+o.sanitize(s.full.toLowerCase())+"",s.trust<50||s.safety<50||s.tags&&s.tags.length>0){t+="
";var u=!1;(s.trust<50||s.safety<50)&&(s.unsafe=!0,t+="Potentially Unsafe Link
",t+="Trust: "+s.trust+"% | Child Safety: "+s.safety+"%",u=!0),s.tags&&s.tags.length>0&&(t+=(u?"
":"")+"Tags: "+s.tags.join(", ")),t+="
Data Source: WOT"}}else s.full&&(t=''+o.sanitize(s.full.toLowerCase())+"");return t||(t=''+o.sanitize(e.toLowerCase())+""),s.tooltip=t,t},load_link_data=function(e,t,s){if(t){this._link_data[e]=s,s.unsafe=!1;var o,n=build_link_tooltip.bind(this)(e),i="/"==e.charAt(e.length-1)?e.substr(0,e.length-1):null;if(o=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 a=0;ae&&(e=10),this.settings.set("scrollback_length",e); +var t=App.__container__.lookup("controller:chat"),s=t&&t.get("currentRoom.id");for(var o in this.rooms){var n=this.rooms[o];n.room.set("messageBufferSize",e+(this._roomv&&!this._roomv.get("stuckToBottom")&&s===o?150:0))}}}},s.settings_info.banned_words={type:"button",value:[],category:"Chat Filtering",no_bttv:!0,name:"Banned Words",help:"Set a list of words that will be locally removed from chat messages.",method:function(){var e=this.settings.banned_words.join(", "),t=prompt("Banned Words\n\nPlease enter a comma-separated list of words that you would like to be removed from chat messages.",e);if(null!==t&&void 0!==t){t=t.trim().split(a);for(var s=[],o=0;oBeta",help:"Check links against known bad websites, unshorten URLs, and show YouTube info."},s.settings_info.legacy_badges={type:"boolean",value:!1,category:"Chat Appearance",name:"Legacy Badges",help:"Display the old, pre-vector chat badges from Twitch.",on_update:function(e){document.body.classList.toggle("ffz-legacy-badges",e)}},s.settings_info.chat_rows={type:"boolean",value:!1,category:"Chat Appearance",no_bttv:!0,name:"Chat Line Backgrounds",help:"Display alternating background colors for lines in chat.",on_update:function(e){document.body.classList.toggle("ffz-chat-background",!this.has_bttv&&e)}},s.settings_info.chat_separators={type:"boolean",value:!1,category:"Chat Appearance",no_bttv:!0,name:"Chat Line Separators",help:"Display thin lines between chat messages for further visual separation.",on_update:function(e){document.body.classList.toggle("ffz-chat-separator",!this.has_bttv&&e)}},s.settings_info.chat_padding={type:"boolean",value:!1,category:"Chat Appearance",no_bttv:!0,name:"Reduced Chat Line Padding",help:"Reduce the amount of padding around chat messages to fit more on-screen at once.",on_update:function(e){document.body.classList.toggle("ffz-chat-padding",!this.has_bttv&&e)}},s.settings_info.high_contrast_chat={type:"boolean",value:!1,category:"Chat Appearance",no_bttv:!0,name:"High Contrast",help:"Display chat using white and black for maximum contrast. This is suitable for capturing and chroma keying chat to display on stream.",on_update:function(e){document.body.classList.toggle("ffz-high-contrast-chat",!this.has_bttv&&e)}},s.settings_info.chat_font_size={type:"button",value:12,category:"Chat Appearance",no_bttv:!0,name:"Font Size",help:"Make the chat font bigger or smaller.",method:function(){var e=this.settings.chat_font_size,t=prompt("Chat Font Size\n\nPlease enter a new size for the chat font. The default is 12.",e);if(null!==t&&void 0!==t){var s=parseInt(t);(0/0===s||1>s)&&(s=12),this.settings.set("chat_font_size",s)}},on_update:function(e){if(!this.has_bttv&&this._chat_style){var t;if(12===e)t="";else{var s=Math.max(20,Math.round(20/12*e)),n=Math.floor((s-20)/2);t=".ember-chat .chat-messages .chat-line { font-size: "+e+"px !important; line-height: "+s+"px !important; }",n&&(t+=".ember-chat .chat-messages .chat-line .mod-icons, .ember-chat .chat-messages .chat-line .badges { padding-top: "+n+"px; }")}o.update_css(this._chat_style,"chat_font_size",t)}}},s.prototype.setup_line=function(){jQuery(document.body).on("mouseleave",".tipsy",function(){this.parentElement.removeChild(this)});var e=this._chat_style=document.createElement("style");e.id="ffz-style-chat",e.type="text/css",document.head.appendChild(e),s.settings_info.chat_font_size.on_update.bind(this)(this.settings.chat_font_size),document.body.classList.toggle("ffz-chat-colors",!this.has_bttv&&this.settings.fix_color),document.body.classList.toggle("ffz-legacy-badges",this.settings.legacy_badges),document.body.classList.toggle("ffz-chat-background",!this.has_bttv&&this.settings.chat_rows),document.body.classList.toggle("ffz-chat-separator",!this.has_bttv&&this.settings.chat_separators),document.body.classList.toggle("ffz-chat-padding",!this.has_bttv&&this.settings.chat_padding),document.body.classList.toggle("ffz-chat-purge-icon",!this.has_bttv&&this.settings.line_purge_icon),document.body.classList.toggle("ffz-high-contrast-chat",!this.has_bttv&&this.settings.high_contrast_chat),this._colors={},this._last_row={},e=this._fix_color_style=document.createElement("style"),e.id="ffz-style-username-colors",e.type="text/css",document.head.appendChild(e),this._twitch_emotes={},this._link_data={},this.log("Hooking the Ember Whisper Line component.");var t=App.__container__.resolve("component:whisper-line");t&&this._modify_line(t),this.log("Hooking the Ember Message Line component.");var o=App.__container__.resolve("component:message-line");o&&this._modify_line(o);var n=this.get_user();n&&n.name&&(s.capitalization[n.login]=[n.name,Date.now()])},s.prototype._modify_line=function(e){var t=this;e.reopen({tokenizedMessage:function(){var e=this.get("msgObject.cachedTokens");if(e)return e;e=this._super();try{var o=performance.now(),n=t.get_user(),i=n&&this.get("msgObject.from")===n.login;e=t._remove_banned(e),e=t._emoticonize(this,e),t.settings.parse_emoji&&(e=t.tokenize_emoji(e));var a=this.get("msgObject.tags.display-name");a&&a.length&&(s.capitalization[this.get("msgObject.from")]=[a.trim(),Date.now()]),i||(e=t.tokenize_mentions(e));for(var r=0;r5&&t.log("Tokenizing Message Took Too Long - "+(u-o)+"ms",e,!1,!0)}catch(c){try{t.error("LineController tokenizedMessage: "+c)}catch(c){}}return this.set("msgObject.cachedTokens",e),e}.property("msgObject.message","isChannelLinksDisabled","currentUserNick","msgObject.from","msgObject.tags.emotes"),ffzUpdated:Ember.observer("msgObject.ffz_deleted","msgObject.ffz_old_messages",function(){this.rerender()}),willClearRender:function(){try{}catch(e){t.error("LineView willClearRender: "+e)}this._super()},click:function(e){if(e.target&&e.target.classList.contains("mod-icon")&&(jQuery(e.target).trigger("mouseout"),e.target.classList.contains("purge"))){var s=this.get("msgObject.from"),o=this.get("msgObject.room"),n=o&&t.rooms[o]&&t.rooms[o].room;return void(n&&(n.send("/timeout "+s+" 1"),n.clearMessages(s)))}return this._super(e)},didInsertElement:function(){this._super();try{var e=performance.now(),i=this.get("element"),a=this.get("msgObject.from"),r=this.get("msgObject.room")||App.__container__.lookup("controller:chat").get("currentRoom.id"),d=this.get("msgObject.color"),u=this.get("msgObject.ffz_alternate");d&&t._handle_color(d),void 0===u&&(u=t._last_row[r]=t._last_row.hasOwnProperty(r)?!t._last_row[r]:!1,this.set("msgObject.ffz_alternate",u)),i.classList.toggle("ffz-alternate",u||!1),i.classList.toggle("ffz-deleted",t.settings.prevent_clear&&this.get("msgObject.ffz_deleted")||!1),i.setAttribute("data-room",r),i.setAttribute("data-sender",a),i.setAttribute("data-deleted",this.get("msgObject.deleted")||!1);var c=this.get("msgObject.ffz_old_messages");if(c&&c.length){var l=document.createElement("div");l.className="button primary float-right",l.innerHTML="Show "+o.number_commas(c.length)+" Old",l.addEventListener("click",t._show_deleted.bind(t,r)),i.classList.add("clearfix"),i.classList.add("ffz-has-deleted"),this.$(".message").append(l)}if(this.get("isBroadcaster")&&!this.get("controller.parentController.model.isStaff")&&!this.get("controller.parentController.model.isAdmin")||this.get("isModeratorOrHigher")&&!(this.get("controller.parentController.model.isBroadcaster")||this.get("controller.parentController.model.isStaff")||this.get("controller.parentController.model.isAdmin"))){var h=i.querySelector("span.mod-icons");h&&h.classList.add("hidden")}var f=i.querySelector("span.mod-icons a.mod-icon.timeout");if(f){var _=document.createElement("a");_.className="mod-icon float-left tooltip purge",_.innerHTML="Purge",_.title="Purge User (Timeout 1s)",_.href="#",f.title="Timeout User (10m)",f.parentElement.insertBefore(_,f.nextSibling)}t.render_badge(this),this.get("msgObject.ffz_has_mention")&&i.classList.add("ffz-mentioned");for(var m=i.querySelectorAll("span.message a.deleted-link"),p=0;p5&&t.log("Line Took Too Long - "+A+"ms",i.innerHTML,!1,!0)}catch(M){try{t.error("LineView didInsertElement: "+M)}catch(M){}}}})},s.prototype._handle_color=function(e){if(e&&!this._colors[e]){this._colors[e]=!0;var t=parseInt(e.substr(1),16),s=[t>>16,t>>8&255,255&t],n=o.get_luminance(s),i="",a='span[style="color:'+e+'"]',r=!1;if(n>.3){r=!0;for(var d=127,u=s;d--&&(u=o.darken(u),!(o.get_luminance(u)<=.3)););i+=".ffz-chat-colors .ember-chat-container:not(.dark) .chat-line "+a+", .ffz-chat-colors .chat-container:not(.dark) .chat-line "+a+" { color: "+o.rgb_to_css(u)+" !important; }\n"}else i+=".ffz-chat-colors .ember-chat-container:not(.dark) .chat-line "+a+", .ffz-chat-colors .chat-container:not(.dark) .chat-line "+a+" { color: "+e+" !important; }\n";if(.15>n){r=!0;for(var d=127,u=s;d--&&(u=o.brighten(u),!(o.get_luminance(u)>=.15)););i+=".ffz-chat-colors .theatre .chat-container .chat-line "+a+", .ffz-chat-colors .chat-container.dark .chat-line "+a+", .ffz-chat-colors .ember-chat-container.dark .chat-line "+a+" { color: "+o.rgb_to_css(u)+" !important; }\n"}else i+=".ffz-chat-colors .theatre .chat-container .chat-line "+a+", .ffz-chat-colors .chat-container.dark .chat-line "+a+", .ffz-chat-colors .ember-chat-container.dark .chat-line "+a+" { color: "+e+" !important; }\n";r&&(this._fix_color_style.innerHTML+=i)}},s.capitalization={},s._cap_fetching=0,s.get_capitalization=function(e,t){if(!e)return e;if(e=e.toLowerCase(),"jtv"==e||"twitchnotify"==e)return e;var o=s.capitalization[e];return o&&Date.now()-o[1]<36e5?o[0]:(s._cap_fetching<25&&(s._cap_fetching++,s.get().ws_send("get_display_name",e,function(o,n){var i=o?n:e;s.capitalization[e]=[i,Date.now()],s._cap_fetching--,"function"==typeof t&&t(i)})),o?o[0]:e)},s.prototype._remove_banned=function(e){var t=this.settings.banned_words;if(!t||!t.length)return e;"string"==typeof e&&(e=[e]);for(var o=s._words_to_regex(t),n=[],i=0;i<banned link>',own:!0}:a)}return n},s.prototype._emoticonize=function(e,t){var s=e.get("msgObject.room"),o=e.get("msgObject.from");return this.tokenize_emotes(o,s,t)}},{"../constants":3,"../utils":32}],9:[function(t){var s,o=e.FrankerFaceZ,n=t("../utils"),i=t("../constants"),a={ESC:27,P:80,B:66,T:84,U:85},r='',d='',u={},c=function(e){if(1===e)return"Purge";if(u[e])return u[e];var t,s,o,n,i;t=Math.floor(e/604800),i=e%604800,s=Math.floor(i/86400),i%=86400,o=Math.floor(i/3600),i%=3600,n=Math.floor(i/60),i%=60;var a=u[e]=(t?t+"w":"")+(s||t&&(o||n||i)?s+"d":"")+(o||(t||s)&&(n||i)?o+"h":"")+(n||(t||s||o)&&i?n+"m":"")+(i?i+"s":"");return a};try{s=e.require&&e.require("ember-twitch-chat/helpers/chat-line-helpers")}catch(l){}o.settings_info.chat_hover_pause={type:"boolean",value:!1,no_bttv:!0,category:"Chat Moderation",name:"Pause Chat Scrolling on Mouse Hover",help:"Automatically prevent the chat from scrolling when moving the mouse over it to prevent moderation mistakes and link mis-clicks.",on_update:function(e){this._roomv&&(e?this._roomv.ffzEnableFreeze():this._roomv.ffzDisableFreeze())}},o.settings_info.short_commands={type:"boolean",value:!0,no_bttv:!0,category:"Chat Moderation",name:"Short Moderation Commands",help:"Use /t, /b, and /u in chat in place of /timeout, /ban, /unban for quicker moderation, and use /p for 1 second timeouts."},o.settings_info.mod_card_hotkeys={type:"boolean",value:!1,no_bttv:!0,category:"Chat Moderation",name:"Moderation Card Hotkeys",help:"With a moderation card selected, press B to ban the user, T to time them out for 10 minutes, P to time them out for 1 second, or U to unban them. ESC closes the card."},o.settings_info.mod_card_info={type:"boolean",value:!1,no_bttv:!0,category:"Chat Moderation",name:"Moderation Card Additional Information",help:"Display a channel's follower count, view count, and account age on moderation cards."},o.settings_info.mod_card_history={type:"boolean",value:!1,no_bttv:!0,category:"Chat Moderation",name:"Moderation Card History",help:"Display a few of the user's previously sent messages on moderation cards.",on_update:function(e){if(!e&&this.rooms)for(var t in this.rooms){var s=this.rooms[t];s&&(s.user_history=void 0)}}},o.settings_info.mod_card_buttons={type:"button",value:[],category:"Chat Moderation",no_bttv:!0,name:"Moderation Card Additional Buttons",help:"Add additional buttons to moderation cards for running chat commands on those users.",method:function(){for(var e="",t=0;t0&&s.push(i)}this.settings.set("mod_card_durations",s)}}},o.prototype.setup_mod_card=function(){this.log("Modifying Mousetrap stopCallback so we can catch ESC.");var t=Mousetrap.stopCallback;Mousetrap.stopCallback=function(e,s,o){return s.classList.contains("no-mousetrap")?!0:t(e,s,o)},Mousetrap.bind("up up down down left right left right b a enter",function(){var e=document.querySelector(".app-main")||document.querySelector(".ember-chat-container");e&&e.classList.toggle("ffz-flip")}),this.log("Hooking the Ember Moderation Card view.");var o=App.__container__.resolve("component:moderation-card"),u=this;o.reopen({ffzForceRedraw:function(){this.rerender()}.observes("cardInfo.isModeratorOrHigher","cardInfo.user"),ffzRebuildInfo:function(){var e=this.get("element"),t=e&&e.querySelector(".info");if(t){var s=''+i.EYE+" "+n.number_commas(this.get("cardInfo.user.views")||0)+"",o=n.parse_date(this.get("cardInfo.user.created_at")||""),a=this.get("cardInfo.user.ffz_followers");if("number"==typeof a)s+=''+i.HEART+" "+n.number_commas(a||0)+"";else if(void 0===a){var r=this;this.set("cardInfo.user.ffz_followers",!1),Twitch.api.get("channels/"+this.get("cardInfo.user.id")+"/follows",{limit:1}).done(function(e){r.set("cardInfo.user.ffz_followers",e._total),r.ffzRebuildInfo()}).fail(function(){r.set("cardInfo.user.ffz_followers",void 0)})}if(o){var d=Math.floor((Date.now()-o.getTime())/1e3);d>0&&(s+=''+i.CLOCK+" "+n.human_time(d,10)+"")}t.innerHTML=s}}.observes("cardInfo.user.views"),didInsertElement:function(){this._super(),e._card=this;try{if(u.has_bttv)return;var t,o=this.get("element"),i=this.get("controller");if(o.classList.add("ffz-moderation-card"),u.settings.mod_card_info){var l=document.createElement("div"),h=o.querySelector("h3.name");h&&(o.classList.add("ffz-has-info"),l.className="info channel-stats",h.parentElement.insertBefore(l,h.nextSibling),this.ffzRebuildInfo())}if(u.settings.mod_card_buttons&&u.settings.mod_card_buttons.length){t=document.createElement("div"),t.className="extra-interface interface clearfix";for(var f={},m=function(e){var t=i.get("cardInfo.user.id"),s=App.__container__.lookup("controller:chat"),o=s&&s.get("currentRoom");o&&o.send(e.replace(/{user}/g,t))},p=function(e){var t=document.createElement("button"),s=e.split(" ",1)[0],o=f[s]>1?e.split(" ",f[s]):[s];return/^[!~./]/.test(o[0])&&(o[0]=o[0].substr(1)),o=_.map(o,function(e){return e.capitalize()}).join(" "),t.className="button",t.innerHTML=n.sanitize(o),t.title=n.sanitize(e.replace(/{user}/g,i.get("cardInfo.user.id")||"{user}")),jQuery(t).tipsy(),t.addEventListener("click",m.bind(this,e)),t},f={},g=0;g button.message-button");if(L){L.innerHTML="W",L.classList.add("glyph-only"),L.classList.add("message"),L.title="Whisper User",jQuery(L).tipsy();var F=document.createElement("button");F.className="message-button button glyph-only message",F.innerHTML=r,F.title="Message User",jQuery(F).tipsy(),F.addEventListener("click",function(){e.open("http://www.twitch.tv/message/compose?to="+i.get("cardInfo.user.id"))}),L.parentElement.insertBefore(F,L.nextSibling)}if(u.settings.mod_card_history){var S=App.__container__.lookup("controller:chat"),A=S&&S.get("currentRoom"),M=A&&u.rooms&&u.rooms[A.get("id")],R=M&&M.user_history&&M.user_history[i.get("cardInfo.user.id")];if(R&&R.length){var O=document.createElement("ul"),I=!1;O.className="interface clearfix chat-history";for(var g=0;g'+s.getTime(t.date)+" ":"")+''+("action"===t.style?"*"+t.from+" ":"")+u.render_tokens(t.cachedTokens)+"";for(var D=B.querySelectorAll("a.deleted-link"),j=0;jN.bottom){var P=H.bottom-N.bottom;H.top-P>N.top&&(o.style.top=H.top-P+"px")}this.$().draggable({start:function(){o.focus()}}),o.focus()}catch(U){try{u.error("ModerationCardView didInsertElement: "+U)}catch(U){}}}})},o.chat_commands.purge=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 s=0;s10)return"Please only ban up to 10 users at once.";for(var s=0;s10)return"Please only unban up to 10 users at once.";for(var s=0;s750&&this.ffzUnfreeze()}},ffzUnfreeze:function(){this.ffz_frozen=!1,this._ffz_last_move=0,this.ffzUnwarnPaused(),this.get("stuckToBottom")&&this._scrollToBottom()},ffzMouseDown:function(e){var t=this._$chatMessagesScroller;if(!this.ffz_frozen&&t&&t[0]&&(e.which>0||"mousedown"===e.type||"mousewheel"===e.type)){var s=t[0].scrollHeight-t[0].scrollTop-t[0].offsetHeight;this._setStuckToBottom(10>=s) +}},ffzMouseOut:function(){this._ffz_outside=!0;var e=this;setTimeout(function(){e._ffz_outside&&e.ffzUnfreeze()},25)},ffzMouseMove:function(e){this._ffz_last_move=Date.now(),this._ffz_outside=!1,(e.screenX!==this._ffz_last_screenx||e.screenY!==this._ffz_last_screeny)&&(this._ffz_last_screenx=e.screenX,this._ffz_last_screeny=e.screenY,this.ffz_frozen||(this.ffz_frozen=!0,this.get("stuckToBottom")&&(this.set("controller.model.messageBufferSize",t.settings.scrollback_length+150),this.ffzWarnPaused())))},_scrollToBottom:_.throttle(function(){var e=this,t=this._$chatMessagesScroller;Ember.run.next(function(){setTimeout(function(){!e.ffz_frozen&&t&&t.length&&(t.scrollTop(t[0].scrollHeight),e._setStuckToBottom(!0))})})},200),_setStuckToBottom:function(e){this.set("stuckToBottom",e),this.get("controller.model")&&this.set("controller.model.messageBufferSize",t.settings.scrollback_length+(e?0:150)),e||this.ffzUnfreeze()},ffzWarnPaused:function(){var e=this.get("element"),t=e&&e.querySelector(".chat-interface .more-messages-indicator.ffz-freeze-indicator");if(e){if(!t){t=document.createElement("div"),t.className="more-messages-indicator ffz-freeze-indicator",t.innerHTML="(Chat Paused Due to Mouse Movement)";var s=e.querySelector(".chat-interface");if(!s)return;s.insertBefore(t,s.childNodes[0])}t.classList.remove("hidden")}},ffzUnwarnPaused:function(){var e=this.get("element"),t=e&&e.querySelector(".chat-interface .more-messages-indicator.ffz-freeze-indicator");t&&t.classList.add("hidden")}})},s.chat_commands={},s.ffz_commands={},s.prototype.room_message=function(e,t){var s=t.split("\n");if(this.has_bttv)for(var o=0;o300,f=t.length,_=o.get("messages.0.ffz_alternate")||!1;h&&(_=!_);for(var f=t.length;f--;){var m=t[f];if("string"==typeof m.date&&(m.date=n.parse_date(m.date)),m.ffz_alternate=_=!_,m.room||(m.room=e),m.color||(m.color=m.tags&&m.tags.color?m.tags.color:a&&m.from?a.getColor(m.from.toLowerCase()):"#755000"),!m.labels||!m.labels.length){var p=m.labels=[];if(m.tags)if(m.tags.turbo&&p.push("turbo"),m.tags.subscriber&&p.push("subscriber"),m.from===e)p.push("owner");else{var g=m.tags["user-type"];("mod"===g||"staff"===g||"admin"===g||"global_mod"===g)&&p.push(g)}}if(m.style||("jtv"===m.from?m.style="admin":"twitchnotify"===m.from&&(m.style="notification")),m.cachedTokens&&m.cachedTokens.length||this.tokenize_chat_line(m,!0),o.shouldShowMessage(m)){if(!(i.lengthv&&(m.ffz_old_messages=m.ffz_old_messages.slice(m.ffz_old_messages.length-v))}i.unshiftObject(m),r+=1}}if(h){var m={ffz_alternate:!_,color:"#755000",date:new Date,from:"frankerfacez_admin",style:"admin",message:"(Last message is "+n.human_time(l)+" old.)",room:e};if(this.tokenize_chat_line(m),o.shouldShowMessage(m))for(i.insertAt(r,m);i.length>o.get("messageBufferSize");)i.removeAt(0)}}},s.prototype.load_room=function(e,t,s){var n=this;jQuery.getJSON(((s||0)%2===0?o.API_SERVER:o.API_SERVER_2)+"v1/room/"+e).done(function(s){if(s.sets)for(var o in s.sets)s.sets.hasOwnProperty(o)&&n._load_set_json(o,void 0,s.sets[o]);n._load_room_json(e,t,s)}).fail(function(o){return 404==o.status?"function"==typeof t&&t(!1):(s=(s||0)+1,10>s?n.load_room(e,t,s):"function"==typeof t&&t(!1))})},s.prototype._load_room_json=function(e,t,s){if(!s||!s.room)return"function"==typeof t&&t(!1);s=s.room,this.rooms[e]&&(s.room=this.rooms[e].room);for(var o in this.rooms[e])"room"!==o&&this.rooms[e].hasOwnProperty(o)&&!s.hasOwnProperty(o)&&(s[o]=this.rooms[e][o]);s.needs_history=this.rooms[e]&&this.rooms[e].needs_history||!1,this.rooms[e]=s,(s.css||s.moderator_badge)&&n.update_css(this._room_style,e,i(s)+(s.css||"")),this.emote_sets.hasOwnProperty(s.set)?-1===this.emote_sets[s.set].users.indexOf(e)&&this.emote_sets[s.set].users.push(e):this.load_set(s.set,function(t,s){-1===s.users.indexOf(e)&&s.users.push(e)}),this.update_ui_link(),t&&t(!0,s)},s.prototype._modify_room=function(t){var s=this;t.reopen({subsOnlyMode:!1,r9kMode:!1,slowWaiting:!1,slowValue:0,mru_list:[],updateWait:function(e,t){var o=this.get("slowWait")||0;this.set("slowWait",e),1>o&&e>0?(this._ffz_wait_timer&&clearTimeout(this._ffz_wait_timer),this._ffz_wait_timer=setTimeout(this.ffzUpdateWait.bind(this),1e3),s._roomv&&s._roomv.ffzUpdateStatus()):(o>0&&1>e||t)&&(this.set("ffz_banned",!1),s._roomv&&s._roomv.ffzUpdateStatus())},ffzUpdateWait:function(){this._ffz_wait_timer=void 0;var e=this.get("slowWait")||0;1>e||(this.set("slowWait",--e),e>0?this._ffz_wait_timer=setTimeout(this.ffzUpdateWait.bind(this),1e3):(this.set("ffz_banned",!1),s._roomv&&s._roomv.ffzUpdateStatus()))},ffzUpdateStatus:function(){s._roomv&&s._roomv.ffzUpdateStatus()}.observes("r9kMode","subsOnlyMode","slowMode","slowValue","ffz_banned"),init:function(){this._super();try{s.add_room(this.id,this),this.set("ffz_chatters",{})}catch(e){s.error("add_room: "+e)}},willDestroy:function(){this._super();try{s.remove_room(this.id)}catch(e){s.error("remove_room: "+e)}},clearMessages:function(e){var t=this;if(e){if(this.get("messages").forEach(function(o,n){o.from===e&&(t.set("messages."+n+".ffz_deleted",!0),s.settings.prevent_clear||t.set("messages."+n+".deleted",!0))}),s.settings.mod_card_history){var o=s.rooms&&s.rooms[t.get("id")],n=o&&o.user_history&&o.user_history[e];if(null!==n&&void 0!==n){var i=!1,a=n.length>0?n[n.length-1]:null;if(i=null!==a&&a.is_delete,!i)for(n.push({from:"jtv",is_delete:!0,style:"admin",cachedTokens:["User has been timed out."],date:new Date});n.length>20;)n.shift()}}}else if(s.settings.prevent_clear)this.addTmiMessage("A moderator's attempt to clear chat was ignored.");else{var r=t.get("messages");t.set("messages",[]),t.addMessage({style:"admin",message:i18n("Chat was cleared by a moderator"),ffz_old_messages:r})}},pushMessage:function(e){if(this.shouldShowMessage(e)){var t,s,o,n=this.get("messageBufferSize");for(this.get("messages").pushObject(e),t=this.get("messages.length"),s=t-n,o=0;s>o;o++)this.get("messages").removeAt(0);"admin"===e.style||"whisper"===e.style&&!this.ffz_whisper_room||this.incrementProperty("unreadCount",1)}},addMessage:function(e){try{if(e){var t="whisper"===e.style;if(s.settings.group_tabs&&s.settings.whisper_room&&(t&&!this.ffz_whisper_room||!t&&this.ffz_whisper_room))return;if(t||(e.room=this.get("id")),s.tokenize_chat_line(e),!t&&e.from&&"jtv"!==e.from&&"twitchnotify"!==e.from&&s.settings.mod_card_history){var o=s.rooms&&s.rooms[e.room];if(o){var n=(o.user_history=o.user_history||{},o.user_history[e.from]=o.user_history[e.from]||[]);for(n.push({from:e.tags&&e.tags["display-name"]||e.from,cachedTokens:e.cachedTokens,style:e.style,date:e.date});n.length>20;)n.shift()}}if(!t){var i=s.get_user();if(i&&i.login===e.from){var a=this.get("ffz_banned");this.set("ffz_banned",!1),this.get("isModeratorOrHigher")||!this.get("slowMode")?this.updateWait(0,a):this.get("slowMode")&&this.updateWait(this.get("slowValue"))}}}}catch(r){s.error("Room addMessage: "+r)}return this._super(e)},setHostMode:function(e){this.set("ffz_host_target",e&&e.hostTarget||null);var t=s.get_user();t&&s._cindex&&this.get("id")===t.login&&s._cindex.ffzUpdateHostButton();var o=App.__container__.lookup("controller:chat");if(o&&o.get("currentChannelRoom")===this)return this._super(e)},send:function(e){if(!(s.settings.group_tabs&&s.settings.whisper_room&&this.ffz_whisper_room)){try{if(e){var t=this.get("mru_list"),o=t.indexOf(e);-1!==o?t.splice(o,1):t.length>20&&t.pop(),t.unshift(e)}var n=e.split(" ",1)[0].toLowerCase();if("/ffz"===n)return this.set("messageToSend",""),void s.run_ffz_command(e.substr(5),this.get("id"));if("/"===n.charAt(0)&&s.run_command(e,this.get("id")))return void this.set("messageToSend","")}catch(i){s.error("send: "+i)}return this._super(e)}},ffzUpdateUnread:function(){if(s.settings.group_tabs){var e=App.__container__.lookup("controller:chat");e&&e.get("currentRoom")===this?this.resetUnreadCount():s._chatv&&s._chatv.ffzTabUnread(this.get("id"))}}.observes("unreadCount"),ffzInitChatterCount:function(){if(this.tmiRoom){var e=this;this.tmiRoom.list().done(function(t){var s={};t=t.data.chatters;for(var o=0;o0),t.set("slowValue",e.slow),t.get("slowMode")||t.updateWait(0)),e.hasOwnProperty("r9k")&&t.set("r9kMode",e.r9k),e.hasOwnProperty("subs-only")&&t.set("subsOnlyMode",e["subs-only"]))}),e._roomConn._connection.off("message",e._roomConn._onIrcMessage,e._roomConn),e._roomConn._onIrcMessage=function(e){if(e.target==this.ircChannel)switch(e.command){case"JOIN":this._session&&this._session.nickname===e.sender?this._onIrcJoin(e):s.settings.chatter_count&&t.ffzUpdateChatters(e.sender);break;case"PART":this._session&&this._session.nickname===e.sender?(this._resetActiveState(),this._connection._exitedRoomConn(),this._trigger("exited")):s.settings.chatter_count&&t.ffzUpdateChatters(null,e.sender)}},e._roomConn._connection.on("message",e._roomConn._onIrcMessage,e._roomConn),this.set("ffz_is_patched",!0)}}.observes("tmiRoom")})}},{"../constants":3,"../utils":32}],11:[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 s=this;e.reopen({lines:function(){var e=this._super();try{var o=[],n={},i=null,a=App.__container__.lookup("controller:channel"),r=this.get("parentController.model.id"),d=a&&a.get("id");if(d){var u=a.get("display_name");u&&(t.capitalization[d]=[u,Date.now()])}r!=d&&(d=null);for(var c=0;ct?String.fromCharCode(t):(t-=65536,String.fromCharCode(55296+(t>>10),56320+(1023&t)))};s.prototype.setup_emoticons=function(){this.log("Preparing emoticon system."),this.emoji_data={},this.emoji_names={},this.emote_sets={},this.global_sets=[],this.default_sets=[],this._last_emote_id=0,this.emote_usage={},this.log("Creating emoticon style element.");var e=this._emote_style=document.createElement("style");e.id="ffz-emoticon-css",document.head.appendChild(e),this.log("Loading global emote sets."),this.load_global_sets(),this.log("Loading emoji data."),this.load_emoji_data(),this.log("Watching Twitch emoticon parser to ensure it loads."),this._twitch_emote_check=setTimeout(this.check_twitch_emotes.bind(this),1e4)},s.prototype.add_usage=function(e,t,s){var o=this.emote_usage[t]=this.emote_usage[t]||{};o[e]=(o[e]||0)+(s||1),this._emote_report_scheduled||(this._emote_report_scheduled=setTimeout(this._report_emotes.bind(this),3e4))},s.prototype._report_emotes=function(){this._emote_report_scheduled&&delete this._emote_report_scheduled;var e=this.emote_usage;this.emote_usage={},this.ws_send("emoticon_uses",[e],function(){},!0)},s.prototype.check_twitch_emotes=function(){this._twitch_emote_check&&(clearTimeout(this._twitch_emote_check),delete this._twitch_emote_check);var e;if(this.rooms)for(var t in this.rooms)if(this.rooms.hasOwnProperty(t)){e=this.rooms[t];break}if(!e||!e.room||!e.room.tmiSession)return void(this._twitch_emote_check=setTimeout(this.check_twitch_emotes.bind(this),1e4));var s=e.room.tmiSession._emotesParser,o=Object.keys(s.emoticonRegexToIds).length;if(!(o>0)){var n=s.emoticonSetIds;s.emoticonSetIds="",s.updateEmoticons(n),this._twitch_emote_check=setTimeout(this.check_twitch_emotes.bind(this),1e4)}},s.prototype.getEmotes=function(e,t){var s=this.users&&this.users[e],o=this.rooms&&this.rooms[t];return _.union(s&&s.sets||[],o&&o.set&&[o.set]||[],o&&o.extra_sets||[],this.default_sets)},s.ws_commands.reload_set=function(e){this.emote_sets.hasOwnProperty(e)&&this.load_set(e)},s.ws_commands.load_set=function(e){this.load_set(e)},s.prototype._emote_tooltip=function(e){if(!e)return null;if(e._tooltip)return e._tooltip;var t=this.emote_sets[e.set_id],s=e.owner,o=t&&t.title||"Global";return e._tooltip="Emoticon: "+(e.hidden?"???":e.name)+"\nFFZ "+o+(s?"\nBy: "+s.display_name:""),e._tooltip},s.prototype.load_emoji_data=function(e,t){var s=this;jQuery.getJSON(o.SERVER+"emoji/emoji.json").done(function(t){var n={},i={};for(var a in t){var r=t[a];a=a.toLowerCase(),r.code=a,n[a]=r,i[r.short_name]=a,r.raw=_.map(r.code.split("-"),d).join(""),r.src=o.SERVER+"emoji/"+a+"-1x.png",r.srcSet=r.src+" 1x, "+o.SERVER+"emoji/"+a+"-2x.png 2x, "+o.SERVER+"emoji/"+a+"-4x.png 4x",r.token={srcSet:r.srcSet,emoticonSrc:r.src+'" data-ffz-emoji="'+a+'" height="18px',ffzEmoji:a,altText:r.raw}}s.emoji_data=n,s.emoji_names=i,s.log("Loaded data on "+Object.keys(n).length+" emoji."),"function"==typeof e&&e(!0,t)}).fail(function(o){return 404===o.status?"function"==typeof e&&e(!1):(t=(t||0)+1,50>t?s.load_emoji(e,t):"function"==typeof e&&e(!1))})},s.prototype.load_global_sets=function(e,t){var s=this;jQuery.getJSON(((t||0)%2===0?o.API_SERVER:o.API_SERVER_2)+"v1/set/global").done(function(e){s.default_sets=e.default_sets;var t=s.global_sets=[],o=e.sets||{};s.feature_friday&&s.feature_friday.set&&(-1===s.global_sets.indexOf(s.feature_friday.set)&&s.global_sets.push(s.feature_friday.set),-1===s.default_sets.indexOf(s.feature_friday.set)&&s.default_sets.push(s.feature_friday.set));for(var n in o)if(o.hasOwnProperty(n)){var i=o[n];t.push(n),s._load_set_json(n,void 0,i)}}).fail(function(o){return 404==o.status?"function"==typeof e&&e(!1):(t=t||0,t++,50>t?s.load_global_sets(e,t):"function"==typeof e&&e(!1))})},s.prototype.load_set=function(e,t,s){var n=this;jQuery.getJSON(((s||0)%2===0?o.API_SERVER:o.API_SERVER_2)+"v1/set/"+e).done(function(s){n._load_set_json(e,t,s&&s.set)}).fail(function(o){return 404==o.status?"function"==typeof t&&t(!1):(s=s||0,s++,10>s?n.load_set(e,t,s):"function"==typeof t&&t(!1))})},s.prototype.unload_set=function(e){var t=this.emote_sets[e];t&&(this.log("Unloading emoticons for set: "+e),n.update_css(this._emote_style,e,null),delete this.emote_sets[e])},s.prototype._load_set_json=function(e,t,s){if(!s)return"function"==typeof t&&t(!1);var o=this.emote_sets[e]&&this.emote_sets[e].users||[];this.emote_sets[e]=s,s.users=o,s.count=0;var i="",a=s.emoticons;s.emoticons={};for(var d=0;d=6e4?this.log("BetterTTV was not detected after 60 seconds."):setTimeout(this.find_bttv.bind(this,t,(s||0)+t),t))},s.prototype.setup_bttv=function(e){this.log("BetterTTV was detected after "+e+"ms. Hooking."),this.has_bttv=!0,document.body.classList.remove("ffz-dark"),this._dark_style&&(this._dark_style.parentElement.removeChild(this._dark_style),this._dark_style=void 0),this._chat_style&&(this._chat_style.parentElement.removeChild(this._chat_style),this._chat_style=void 0),this.settings.group_tabs&&this._chatv&&this._chatv.ffzDisableTabs(),this._roomv&&(this.settings.chat_hover_pause&&this._roomv.ffzDisableFreeze(),this.settings.room_status&&this._roomv.ffzUpdateStatus()),document.body.classList.remove("ffz-chat-colors"),document.body.classList.remove("ffz-chat-background"),document.body.classList.remove("ffz-chat-padding"),document.body.classList.remove("ffz-chat-separator"),document.body.classList.remove("ffz-sidebar-swap"),document.body.classList.remove("ffz-transparent-badges"),document.body.classList.remove("ffz-high-contrast-chat"),this.settings.following_count&&(this._schedule_following_count(),this._draw_following_count()),this.is_dashboard&&this._update_subscribers(),document.body.classList.add("ffz-bttv");var t=BetterTTV.chat.helpers.sendMessage,s=this;BetterTTV.chat.helpers.sendMessage=function(e){var o=e.split(" ",1)[0].toLowerCase();return"/ffz"!==o?t(e):void s.run_ffz_command(e.substr(5),BetterTTV.chat.store.currentRoom)};var i,a=BetterTTV.chat.handlers.onPrivmsg;BetterTTV.chat.handlers.onPrivmsg=function(e,t){i=e;var s=a(e,t);return i=null,s};var r=BetterTTV.chat.templates.privmsg;BetterTTV.chat.templates.privmsg=function(e,t,o,n,a){try{return s.bttv_badges(a),'
'+BetterTTV.chat.templates.timestamp(a.time)+" "+(n?BetterTTV.chat.templates.modicons():"")+" "+BetterTTV.chat.templates.badges(a.badges)+BetterTTV.chat.templates.from(a.nickname,a.color)+BetterTTV.chat.templates.message(a.sender,a.message,a.emotes,t?a.color:!1)+"
"}catch(d){return s.log("Error: ",d),r(e,t,o,n,a)}};var d=BetterTTV.chat.templates.whisper;BetterTTV.chat.templates.whisper=function(e){try{return s.bttv_badges(e),'
'+BetterTTV.chat.templates.timestamp(e.time)+" "+(e.badges&&e.badges.length?BetterTTV.chat.templates.badges(e.badges):"")+BetterTTV.chat.templates.whisperName(e.sender,e.receiver,e.from,e.to,e.fromColor,e.toColor)+BetterTTV.chat.templates.message(e.sender,e.message,e.emotes,!1)+"
"}catch(t){return s.log("Error: ",t),d(e)}};var u,c=BetterTTV.chat.templates.message;BetterTTV.chat.templates.message=function(e,t,o,n){try{n=n||!1;var i=encodeURIComponent(t);if("jtv"!==e){u=e;var a=BetterTTV.chat.templates.emoticonize(t,o);u=null;for(var r=0;r'+t+""}catch(d){return s.log("Error: ",d),c(e,t,o,n)}};var l=BetterTTV.chat.templates.emoticonize;BetterTTV.chat.templates.emoticonize=function(e,t){var a=l(e,t),r=i||BetterTTV.getChannel(),d=r&&r.toLowerCase(),c=u&&u.toLowerCase(),h=s.getEmotes(c,d),t=[],f=s.get_user(),m=f&&f.login===c;if(_.each(h,function(e){var o=s.emote_sets[e];o&&_.each(o.emoticons,function(e){_.any(a,function(t){return _.isString(t)&&t.match(e.regex)})&&t.push(e)})}),t.length&&_.each(t,function(e){var t=s._emote_tooltip(e),o=[''+t+''],n=a;a=[];for(var i=0;i']:w+(z||""))}}}else a.push(v)}}return a},this.update_ui_link()}},{"../constants":3,"../utils":32}],14:[function(){var t=e.FrankerFaceZ;t.prototype.find_emote_menu=function(t,s){return this.has_emote_menu=!1,e.emoteMenu&&emoteMenu.registerEmoteGetter?this.setup_emote_menu(s||0):void(s>=6e4?this.log("Emote Menu for Twitch was not detected after 60 seconds."):setTimeout(this.find_emote_menu.bind(this,t,(s||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(),s=e?e.login:null,o=App.__container__.lookup("controller:chat"),n=o?o.get("currentRoom.id"):null,i=this.getEmotes(s,n),a=[],r=0;r=6e4?this.log('Twitch application not detected in "'+location.toString()+'". Aborting.'):setTimeout(this.initialize.bind(this,t,(s||0)+t),t)))},s.prototype.setup_normal=function(t,o){var n=e.performance&&performance.now?performance.now():Date.now();this.log("Found non-Ember Twitch after "+(t||0)+' ms in "'+location+'". Initializing FrankerFaceZ version '+s.version_info),this.users={},this.is_dashboard=!1;try{this.embed_in_dash=e.top!==e&&/\/[^\/]+\/dashboard/.test(e.top.location.pathname)&&!/bookmarks$/.test(e.top.location.pathname)}catch(i){this.embed_in_dash=!1}this.load_settings(),this.setup_dark(),o||this.ws_create(),this.setup_emoticons(),this.setup_badges(),this.setup_notifications(),this.setup_following_count(!1),this.setup_css(),this.setup_menu(),this.find_bttv(10);var a=e.performance&&performance.now?performance.now():Date.now(),r=a-n;this.log("Initialization complete in "+r+"ms")},s.prototype.is_dashboard=!1,s.prototype.setup_dashboard=function(t){var o=e.performance&&performance.now?performance.now():Date.now();this.log("Found Twitch Dashboard after "+(t||0)+' ms in "'+location+'". Initializing FrankerFaceZ version '+s.version_info),this.users={},this.is_dashboard=!0,this.embed_in_dash=!1,this.load_settings(),this.setup_dark(),this.ws_create(),this.setup_emoticons(),this.setup_badges(),this.setup_notifications(),this.setup_css(),this._update_subscribers(),this.setup_message_event(),this.find_bttv(10);var n=e.performance&&performance.now?performance.now():Date.now(),i=n-o;this.log("Initialization complete in "+i+"ms")},s.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 '+s.version_info),this.users={},this.is_dashboard=!1; +try{this.embed_in_dash=e.top!==e&&/\/[^\/]+\/dashboard/.test(e.top.location.pathname)&&!/bookmarks$/.test(e.top.location.pathname)}catch(n){this.embed_in_dash=!1}this.load_settings(),this.setup_dark(),this.ws_create(),this.setup_emoticons(),this.setup_badges(),this.setup_channel(),this.setup_room(),this.setup_line(),this.setup_chatview(),this.setup_viewers(),this.setup_mod_card(),this.setup_chat_input(),this.setup_notifications(),this.setup_css(),this.setup_menu(),this.setup_my_emotes(),this.setup_following(),this.setup_following_count(!0),this.setup_races(),this.connect_extra_chat(),this.find_bttv(10),this.find_emote_menu(10),this.check_ff();var i=e.performance&&performance.now?performance.now():Date.now(),a=i-o;this.log("Initialization complete in "+a+"ms")},s.prototype.setup_message_event=function(){this.log("Listening for Window Messages."),e.addEventListener("message",this._on_window_message.bind(this),!1)},s.prototype._on_window_message=function(e){if(e.data&&e.data.from_ffz){e.data}}},{"./badges":1,"./commands":2,"./debug":4,"./ember/channel":5,"./ember/chat-input":6,"./ember/chatview":7,"./ember/line":8,"./ember/moderation-card":9,"./ember/room":10,"./ember/viewers":11,"./emoticons":12,"./ext/betterttv":13,"./ext/emote_menu":14,"./featurefriday":16,"./settings":17,"./socket":18,"./tokenize":19,"./ui/about_page":20,"./ui/dark":21,"./ui/following":23,"./ui/following-count":22,"./ui/menu":24,"./ui/menu_button":25,"./ui/my_emotes":26,"./ui/notifications":27,"./ui/races":28,"./ui/styles":29,"./ui/sub_count":30,"./ui/viewer_count":31}],16:[function(t){var s=e.FrankerFaceZ,o=t("./constants");s.prototype.feature_friday=null,s.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))})},s.ws_commands.reload_ff=function(){this.check_ff()},s.prototype._feature_friday_ui=function(e,t,s){if(this.feature_friday&&this.feature_friday.channel!=e){this._emotes_for_sets(t,s,[this.feature_friday.set],this.feature_friday.title,this.feature_friday.icon,"FrankerFaceZ");var o=App.__container__.lookup("controller:channel");if(!o||o.get("id")!=this.feature_friday.channel){var n=this.feature_friday,i=document.createElement("div"),a=document.createElement("a");i.className="chat-menu-content",i.style.textAlign="center";var r=n.display_name+(n.live?" is live now!":"");a.className="button primary",a.classList.toggle("live",n.live),a.classList.toggle("blue",this.has_bttv&&BetterTTV.settings.get("showBlueButtons")),a.href="http://www.twitch.tv/"+n.channel,a.title=r,a.target="_new",a.innerHTML=""+r+"",i.appendChild(a),t.appendChild(i)}}},s.prototype._load_ff=function(e){this.feature_friday&&(this.global_sets.removeObject(this.feature_friday.set),this.default_sets.removeObject(this.feature_friday.set),this.feature_friday=null,this.update_ui_link()),e&&e.set&&e.channel&&(this.feature_friday={set:e.set,channel:e.channel,live:!1,title:e.title||"Feature Friday",display_name:s.get_capitalization(e.channel,this._update_ff_name.bind(this))},this.global_sets.push(e.set),this.default_sets.push(e.set),this.load_set(e.set),this._update_ff_live())},s.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)})}},s.prototype._update_ff_name=function(e){this.feature_friday&&(this.feature_friday.display_name=e)}},{"./constants":3}],17:[function(t){var s=e.FrankerFaceZ,o=t("./constants");make_ls=function(e){return"ffz_setting_"+e},toggle_setting=function(e,t){var s=!this.settings.get(t);this.settings.set(t,s),e.classList.toggle("active",s)},s.settings_info={},s.prototype.load_settings=function(){this.log("Loading settings."),this.settings={};for(var t in s.settings_info)if(s.settings_info.hasOwnProperty(t)){var o=s.settings_info[t],n=o.storage_key||make_ls(t),i=o.hasOwnProperty("value")?o.value:void 0;if(localStorage.hasOwnProperty(n))try{i=JSON.parse(localStorage.getItem(n))}catch(a){this.log('Error loading value for "'+t+'": '+a)}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)},s.menu_pages.settings={render:function(e,t){var o={},n=[];for(var i in s.settings_info)if(s.settings_info.hasOwnProperty(i)){var a=s.settings_info[i],r=a.category||"Miscellaneous",d=o[r];if(void 0!==a.visible&&null!==a.visible){var u=a.visible;if("function"==typeof a.visible&&(u=a.visible.bind(this)()),!u)continue}d||(n.push(r),d=o[r]=[]),d.push([i,a])}n.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 c=this,l=this._ffz_settings_page||n[0],h=0;hs?-1:s>o?1:i>n?-1:n>i?1:0});for(var g=0;g<_.length;g++){var i=_[g][0],a=_[g][1],v=document.createElement("p"),b=this.settings.get(i);if(v.className="clearfix",this.has_bttv&&a.no_bttv){var y=document.createElement("span"),w=document.createElement("span");y.className="switch-label",y.innerHTML=a.name,w=document.createElement("span"),w.className="help",w.innerHTML="Disabled due to incompatibility with BetterTTV.",v.classList.add("disabled"),v.appendChild(y),v.appendChild(w)}else{if("boolean"==a.type){var z=document.createElement("a"),y=document.createElement("span");z.className="switch",z.classList.toggle("active",b),z.innerHTML="",y.className="switch-label",y.innerHTML=a.name,v.appendChild(z),v.appendChild(y),z.addEventListener("click",toggle_setting.bind(this,z,i))}else{v.classList.add("option");var k=document.createElement("a");k.innerHTML=a.name,k.href="#",v.appendChild(k),k.addEventListener("click",a.method.bind(this))}if(a.help){var w=document.createElement("span");w.className="help",w.innerHTML=a.help,v.appendChild(w)}}m.appendChild(v)}t.appendChild(m)}},name:"Settings",icon:o.GEAR,sort_order:99999,wide:!0},s.prototype._setting_update=function(t){if(t||(t=e.event),t.key&&"ffz_setting_"===t.key.substr(0,12)){var o=t.key,n=o.substr(12),i=void 0,a=s.settings_info[n];if(!a){for(n in s.settings_info)if(s.settings_info.hasOwnProperty(n)&&(a=s.settings_info[n],a.storage_key==o))break;if(a.storage_key!=o)return}this.log("Updated Setting: "+n);try{i=JSON.parse(t.newValue)}catch(r){this.log('Error loading new value for "'+n+'": '+r),i=a.value||void 0}if(this.settings[n]=i,a.on_update)try{a.on_update.bind(this)(i,!1)}catch(r){this.log('Error running updater for setting "'+n+'": '+r)}}},s.prototype._setting_get=function(e){return this.settings[e]},s.prototype._setting_set=function(e,t){var o=s.settings_info[e],n=o.storage_key||make_ls(e),i=JSON.stringify(t);if(this.settings[e]=t,localStorage.setItem(n,i),this.log('Changed Setting "'+e+'" to: '+i),o.on_update)try{o.on_update.bind(this)(t,!0)}catch(a){this.log('Error running updater for setting "'+e+'": '+a)}},s.prototype._setting_del=function(e){var t=s.settings_info[e],o=t.storage_key||make_ls(e),n=void 0;if(localStorage.hasOwnProperty(o)&&localStorage.removeItem(o),delete this.settings[e],t&&(n=this.settings[e]=t.hasOwnProperty("value")?t.value:void 0),t.on_update)try{t.on_update.bind(this)(n,!0)}catch(i){this.log('Error running updater for setting "'+e+'": '+i)}}},{"./constants":3}],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 s,o=this;this._ws_last_req=0,this._ws_callbacks={},this._ws_pending=this._ws_pending||[];try{s=this._ws_sock=new WebSocket("ws://catbag.frankerfacez.com/")}catch(n){return this._ws_exists=!1,this.log("Error Creating WebSocket: "+n)}this._ws_exists=!0,s.onopen=function(){o._ws_open=!0,o._ws_delay=0,o.log("Socket connected.");var s=e.RequestFileSystem||e.webkitRequestFileSystem;s?s(e.TEMPORARY,100,o.ws_send.bind(o,"hello",["ffz_"+t.version_info,localStorage.ffzClientId],o._ws_on_hello.bind(o)),o.log.bind(o,"Operating in Incognito Mode.")):o.ws_send("hello",["ffz_"+t.version_info,localStorage.ffzClientId],o._ws_on_hello.bind(o));var n=o.get_user();if(n&&o.ws_send("setuser",n.login),o.is_dashboard){var i=location.pathname.match(/\/([^\/]+)/);i&&(o.ws_send("sub",i[1]),o.ws_send("sub_channel",i[1]))}for(var a in o.rooms)o.rooms.hasOwnProperty(a)&&o.rooms[a]&&(o.ws_send("sub",a),o.rooms[a].needs_history&&(o.rooms[a].needs_history=!1,!o.has_bttv&&o.settings.chat_history&&o.ws_send("chat_history",[a,25],o._load_history.bind(o,a))));if(o._cindex){var r=o._cindex.get("controller.id"),d=o._cindex.get("controller.hostModeTarget.id");r&&o.ws_send("sub_channel",r),d&&o.ws_send("sub_channel",d)}var u=o._ws_pending;o._ws_pending=[];for(var c=0;c12&&!o.get().settings.twenty_four_timestamps?t-=12:0!==t||o.get().settings.twenty_four_timestamps||(t=12),t+":"+(10>s?"0":"")+s}),o.prototype.tokenize_chat_line=function(t,n){if(t.cachedTokens)return t.cachedTokens;var i=t.message,a=this.get_user(),r=t.room,d=a&&t.from===a.login,u=t.tags&&t.tags.emotes,c=[i];c=s.linkifyMessage(c),a&&a.login&&(c=s.mentionizeMessage(c,a.login,d)),c=s.emoticonizeMessage(c,u),this.settings.replace_bad_emotes&&(c=this.tokenize_replace_emotes(c)),c=this._remove_banned(c),c=this.tokenize_emotes(t.from,r,c,d),this.settings.parse_emoji&&(c=this.tokenize_emoji(c));var l=t.tags&&t.tags["display-name"];if(l&&l.length&&(o.capitalization[t.from]=[l.trim(),Date.now()]),!d){c=this.tokenize_mentions(c);for(var h=0;h'}if(e.isLink){if(!t&&void 0!==t)return e.href;var h=e.href;if(h.indexOf("@")>-1&&(-1===h.indexOf("/")||h.indexOf("@")'+h+"";var f=(h.match(/^https?:\/\//)?"":"http://")+h,l=s._link_data&&s._link_data[f]||{};return''+h+""}return e.mentionedUser?''+e.mentionedUser+"":n.sanitize(e.deletedLink?e.text:e)}).join("")},o.prototype.tokenize_replace_emotes=function(e){_.isString(e)&&(e=[e]);for(var t=0;t-1&&(-1===t.indexOf("/")||t.indexOf("@")0&&(n=!0)}var r=document.createElement("div"),d="";d+="

FrankerFaceZ

",d+='
new ways to woof
',r.className="chat-menu-content center",r.innerHTML=d,t.appendChild(r);var u=0,c=r.querySelector("h1");c&&c.addEventListener("click",function(){if(c.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,c.style.cursor=""},2e3)});var l=document.createElement("div"),h=document.createElement("a"),f="To use custom emoticons in "+(n?"this channel":"tons of channels")+", get FrankerFaceZ from http://www.frankerfacez.com";h.className="button primary",h.innerHTML="Advertise in Chat",h.addEventListener("click",this._add_emote.bind(this,e,f)),l.appendChild(h);var _=document.createElement("a");_.className="button ffz-donate",_.href="https://www.frankerfacez.com/donate",_.target="_new",_.innerHTML="Donate",l.appendChild(_),l.className="chat-menu-content center",t.appendChild(l);var m=document.createElement("div");d='',d+='',d+='',d+='',d+='',m.className="chat-menu-content center",m.innerHTML=d;var p=!1;m.querySelector("#ffz-debug-logs").addEventListener("click",function(){p||(p=!0,i._pastebin(i._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(m)}}},{"../constants":3}],21:[function(t){var s=e.FrankerFaceZ,o=t("../constants");s.settings_info.twitch_chat_dark={type:"boolean",value:!1,visible:!1},s.settings_info.dark_twitch={type:"boolean",value:!1,no_bttv:!0,category:"Appearance",name:"Dark Twitch",help:"Apply a dark background to channels and other related pages for easier viewing.",on_update:function(t){var s=document.querySelector("input.ffz-setting-dark-twitch");if(s&&(s.checked=t),!this.has_bttv){document.body.classList.toggle("ffz-dark",t);var o=e.App?App.__container__.lookup("controller:settings").get("model"):void 0;t?(this._load_dark_css(),o&&this.settings.set("twitch_chat_dark",o.get("darkMode")),o&&o.set("darkMode",!0)):o&&o.set("darkMode",this.settings.twitch_chat_dark)}}},s.settings_info.dark_no_blue={type:"boolean",value:!1,category:"Appearance",name:"Gray Chat (no blue)",help:"Make the dark theme for chat and a few other places on Twitch a bit darker and not at all blue.",on_update:function(e){document.body.classList.toggle("ffz-no-blue",e)}},s.settings_info.hide_recent_past_broadcast={type:"boolean",value:!1,category:"Channel Metadata",name:'Hide "Watch Last Broadcast"',help:'Hide the "Watch Last Broadcast" banner at the top of offline Twitch channels.',on_update:function(e){document.body.classList.toggle("ffz-hide-recent-past-broadcast",e)}},s.prototype.setup_dark=function(){document.body.classList.toggle("ffz-hide-recent-past-broadcast",this.settings.hide_recent_past_broadcast),document.body.classList.toggle("ffz-no-blue",this.settings.dark_no_blue),this.has_bttv||(document.body.classList.toggle("ffz-dark",this.settings.dark_twitch),this.settings.dark_twitch&&(e.App&&App.__container__.lookup("controller:settings").set("model.darkMode",!0),this._load_dark_css()))},s.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?_="+(o.DEBUG?Date.now():s.version_info)),document.head.appendChild(e)}}},{"../constants":3}],22:[function(t){var s=e.FrankerFaceZ,o=t("../utils");s.settings_info.following_count={type:"boolean",value:!0,no_bttv:!0,category:"Appearance",name:"Sidebar Following Count",help:"Display the number of live channels you're following on the sidebar.",on_update:function(){this._schedule_following_count();var t=e.App&&App.__container__.resolve("model:stream"),s=t&&t.find("live");s?this._draw_following_count(s.get("total")||0):this._update_following_count()}},s.prototype.setup_following_count=function(t){if(this.settings.following_count&&this._schedule_following_count(),!t)return this._update_following_count();this.log("Connecting to Live Streams model.");var s=e.App&&App.__container__.resolve("model:stream");if(!s)return this.log("Unable to find Stream model.");var o=s.find("live"),n=this;if(!o)return this.log("Unable to find Live Streams collection.");o.addObserver("total",function(){n._draw_following_count(this.get("total"))}),o.load();var i=o.get("total");"number"==typeof i&&this._draw_following_count(i)},s.prototype._schedule_following_count=function(){return this.has_bttv||!this.settings.following_count?void(this._following_count_timer&&(clearTimeout(this._following_count_timer),this._following_count_timer=void 0)):void(this._following_count_timer||(this._following_count_timer=setTimeout(this._update_following_count.bind(this),6e4)))},s.prototype._update_following_count=function(){if(!this.settings.following_count)return void(this._following_count_timer&&(clearTimeout(this._following_count_timer),this._following_count_timer=void 0));this._following_count_timer=setTimeout(this._update_following_count.bind(this),6e4);var t=e.App&&App.__container__.resolve("model:stream"),s=t&&t.find("live"),o=this;s?s.load():Twitch.api.get("streams/followed",{limit:1,offset:0},{version:3}).done(function(e){o._draw_following_count(e._total)}).fail(function(){o._draw_following_count()})},s.prototype._draw_following_count=function(e){var t=document.querySelector('#small_nav ul.game_filters li[data-name="following"] a');if(t){var s=t.querySelector(".ffz-follow-count");this.has_bttv||!this.settings.following_count?s&&s.parentElement.removeChild(s):(s||(s=document.createElement("span"),s.className="ffz-follow-count",t.appendChild(s)),s.innerHTML=e?o.format_unread(e):"")}var n=document.querySelector('#large_nav #nav_personal li[data-name="following"] a');if(n){var s=n.querySelector(".ffz-follow-count");this.has_bttv||!this.settings.following_count?s&&s.parentElement.removeChild(s):(s||(s=document.createElement("span"),s.className="ffz-follow-count",n.appendChild(s)),s.innerHTML=e?o.format_unread(e):"")}var i=document.querySelector("#header_actions #header_following");if(i){var s=i.querySelector(".ffz-follow-count");this.has_bttv||!this.settings.following_count?s&&s.parentElement.removeChild(s):(s||(s=document.createElement("span"),s.className="ffz-follow-count",i.appendChild(s)),s.innerHTML=e?o.format_unread(e):"")}}},{"../utils":32}],23:[function(t){{var s=e.FrankerFaceZ;t("../utils")}s.prototype.setup_following=function(){this.log("Initializing following support."),this.follow_data={},this.follow_sets={}},s.settings_info.follow_buttons={type:"boolean",value:!0,category:"Channel Metadata",name:"Relevant Follow Buttons",help:"Display additional Follow buttons for channels relevant to the stream, such as people participating in co-operative gameplay.",on_update:function(){this.rebuild_following_ui()}},s.ffz_commands.following=function(e,t){t=t.join(" ").trim().split(/\s*,+\s*/),t.length&&""===t[0]&&t.shift(),t.length&&""===t[t.length-1]&&t.pop();var s=this.get_user(),o=this;return!s||s.login!==e.id&&"sirstendec"!==s.login&&"dansalvato"!==s.login?"You must be logged in as the broadcaster to use this command.":this.ws_send("update_follow_buttons",[e.id,t],function(t,s){return t?void(s?o.room_message(e,"The following buttons have been updated."):o.room_message(e,"The following buttons have been disabled.")):void o.room_message(e,"There was an error updating the following buttons.")})?void 0:"There was an error communicating with the server."},s.ws_on_close.push(function(){var t=e.App&&App.__container__.lookup("controller:channel"),s=t&&t.get("id"),o=t&&t.get("hostModeTarget.id"),n=!1;if(this.follow_sets={},t){for(var i in this.follow_data)if(delete this.follow_data[i],(i===s||i===o)&&(n=!0),this.rooms&&this.rooms[i]&&this.rooms[i].extra_sets){var a=this.rooms[i].extra_sets;delete this.rooms[i].extra_sets;for(var r=0;r span");r?i.insertBefore(a,r):i.appendChild(a)}for(var d=0;d span");r?i.insertBefore(a,r):i.appendChild(a)}for(var d=0;d=300?"right":"left")+" dropmenu notify-menu js-notify",i='
You are following '+s.get_capitalization(t)+"
",i+='

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

",n.innerHTML=i,e.appendChild(n),n.querySelector("a.switch"))}},{"../utils":32}],24:[function(t){var s=e.FrankerFaceZ,o=t("../constants"),n=t("../utils"),i="http://static-cdn.jtvnw.net/emoticons/v1/";s.prototype.setup_menu=function(){this.log("Installing mouse-up event to auto-close menus.");var t=this;jQuery(document).mouseup(function(e){var s,o=t._popup;o&&(o=jQuery(o),s=o.parent(),s.is(e.target)||0!==s.has(e.target).length||(o.remove(),delete t._popup,t._popup_kill&&t._popup_kill(),delete t._popup_kill))}),document.body.classList.toggle("ffz-menu-replace",this.settings.replace_twitch_menu),this.log("Hooking the Ember Chat Settings view.");var s=e.App&&App.__container__.resolve("view:settings");if(s){s.reopen({didInsertElement:function(){this._super();try{this.ffzInit()}catch(e){t.error("ChatSettings didInsertElement: "+e)}},willClearRender:function(){try{this.ffzTeardown()}catch(e){t.error("ChatSettings willClearRender: "+e)}this._super()},ffzInit:function(){var e=this,s=this.get("element"),o=s&&s.querySelector(".dropmenu");if(o){var n,i,a,r=document.createElement("div"),d=document.createElement("div");r.className="list-header",r.innerHTML="FrankerFaceZ",d.className="chat-menu-content",n=document.createElement("p"),n.className="no-bttv",i=document.createElement("input"),i.type="checkbox",i.className="ember-checkbox ffz-setting-dark-twitch",i.checked=t.settings.dark_twitch,n.appendChild(i),n.appendChild(document.createTextNode("Dark Twitch")),d.appendChild(n),i.addEventListener("change",function(){t.settings.set("dark_twitch",this.checked)}),n=document.createElement("p"),i=document.createElement("input"),i.type="checkbox",i.className="ember-checkbox ffz-setting-hosted-channels",i.checked=t.settings.hosted_channels,n.appendChild(i),n.appendChild(document.createTextNode("Channel Hosting")),d.appendChild(n),i.addEventListener("change",function(){t.settings.set("hosted_channels",this.checked)}),n=document.createElement("p"),a=document.createElement("a"),a.href="#",a.innerHTML="More Settings",n.appendChild(a),d.appendChild(n),a.addEventListener("click",function(s){return e.set("controller.model.hidden",!0),t._last_page="settings",t.build_ui_popup(t._chatv),s.stopPropagation(),!1}),o.appendChild(r),o.appendChild(d)}},ffzTeardown:function(){}});try{s.create().destroy()}catch(o){}for(var n in Ember.View.views)if(Ember.View.views.hasOwnProperty(n)){var i=Ember.View.views[n];if(i instanceof s){this.log("Manually updating existing Chat Settings view.",i);try{i.ffzInit()}catch(o){this.error("setup: ChatSettings ffzInit: "+o)}}}}},s.menu_pages={},s.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 n=document.createElement("div"),i=document.createElement("div"),a=document.createElement("ul"),r=this.has_bttv?BetterTTV.settings.get("darkenedMode"):!1;n.className="emoticon-selector chat-menu ffz-ui-popup",i.className="emoticon-selector-box dropmenu",n.appendChild(i),n.classList.toggle("dark",r);var d=document.createElement("div");d.className="ffz-ui-menu-page",i.appendChild(d),a.className="menu clearfix",i.appendChild(a);var u=document.createElement("li");u.className="title",u.innerHTML=""+(o.DEBUG?"[DEV] ":"")+"FrankerFaceZ",a.appendChild(u);var c=[];for(var l in s.menu_pages)if(s.menu_pages.hasOwnProperty(l)){var h=s.menu_pages[l];try{if(!h||h.hasOwnProperty("visible")&&(!h.visible||"function"==typeof h.visible&&!h.visible.bind(this)(e)))continue}catch(f){this.error("menu_pages "+l+" visible: "+f);continue}c.push([h.sort_order||0,l,h])}c.sort(function(e,t){if(e[0]t[0])return-1;var s=e[1].toLowerCase(),o=t[1].toLowerCase();return o>s?1:s>o?-1:0});for(var _=0;_0,u&&!c&&!l){var p=this;u.addObserver("isLoaded",function(){setTimeout(function(){"channel"===t.getAttribute("data-page")&&(t.innerHTML="",s.menu_pages.channel.render.bind(p)(e,t))},0)}),u.load()}f.className="emoticon-grid",_.className="heading",h&&(_.style.backgroundImage='url("'+h+'")'),_.innerHTML='TwitchSubscriber Emoticons',f.appendChild(_);for(var g=d.get("emoticons")||[],v=0;v0&&t.appendChild(f),m>0&&!c){var k=document.createElement("div"),E=document.createElement("div"),C=document.createElement("span"),T=document.createElement("a");k.className="subscribe-message",E.className="non-subscriber-message",k.appendChild(E),C.className="unlock-text",C.innerHTML="Subscribe to unlock Emoticons",E.appendChild(C),T.className="action subscribe-button button primary",T.href=d.get("product_url"),T.innerHTML='",E.appendChild(T),t.appendChild(k)}else if(m>0){var x=u.get("content");if(x=x.length>0?x[x.length-1]:void 0,x&&x.purchase_profile&&!x.purchase_profile.will_renew){var L=n.parse_date(x.access_end||"");k=document.createElement("div"),E=document.createElement("div"),C=document.createElement("span"),end_time=L?Math.floor((L.getTime()-Date.now())/1e3):null,k.className="subscribe-message",E.className="non-subscriber-message",k.appendChild(E),C.className="unlock-text",C.innerHTML="Subscription expires in "+n.time_to_string(end_time,!0,!0),E.appendChild(C),t.appendChild(k)}}}}var F=a&&a.extra_sets||[];this._emotes_for_sets(t,e,a&&a.set&&[a.set]||[],this.feature_friday||r||F.length?"Channel Emoticons":null,"http://cdn.frankerfacez.com/script/devicon.png","FrankerFaceZ");for(var v=0;vs?-1:s>o?1:0});for(var l=0;l0&&(i=!0)}t.classList.toggle("no-emotes",!i),t.classList.toggle("live",d),t.classList.toggle("dark",a),t.classList.toggle("blue",r)}}},{"../constants":3}],26:[function(t){var s=e.FrankerFaceZ,o=t("../constants"),n=t("../utils"),i="http://static-cdn.jtvnw.net/emoticons/v1/";s.settings_info.replace_twitch_menu={type:"boolean",value:!1,category:"Chat Input",name:"Replace Twitch Emoticon Menu",help:"Completely replace the default Twitch emoticon menu.",on_update:function(e){document.body.classList.toggle("ffz-menu-replace",e)}},s.settings_info.global_emotes_in_menu={type:"boolean",value:!1,category:"Chat Input",name:"Display Global Emotes in My Emotes",help:"Display the global Twitch emotes in the My Emoticons menu."},s.settings_info.emoji_in_menu={type:"boolean",value:!1,category:"Chat Input",name:"Display Emoji in My Emotes",help:"Display the supported emoji images in the My Emoticons menu."},s.settings_info.emote_menu_collapsed={value:[],visible:!1},s.prototype.setup_my_emotes=function(){if(this._twitch_set_to_channel={},this._twitch_badges={},localStorage.ffzTwitchSets)try{this._twitch_set_to_channel=JSON.parse(localStorage.ffzTwitchSets),this._twitch_badges=JSON.parse(localStorage.ffzTwitchBadges)}catch(e){}this._twitch_set_to_channel[0]="global",this._twitch_set_to_channel[33]="turbo_faces",this._twitch_set_to_channel[42]="turbo_faces",this._twitch_badges.global="//cdn.frankerfacez.com/script/twitch_logo.png",this._twitch_badges.turbo_faces=this._twitch_badges.turbo="//cdn.frankerfacez.com/script/turbo_badge.png"},s.menu_pages.my_emotes={name:"My Emoticons",icon:o.EMOTE,visible:function(e){var t=this.get_user(),s=e.get("controller.currentRoom.tmiSession"),o=t&&this.users[t.login]&&this.users[t.login].sets||[],n=(s&&s.getEmotes()||{emoticon_sets:{}}).emoticon_sets;return o.length||n&&Object.keys(n).length},render:function(e,t){var o=e.get("controller.currentRoom.tmiSession"),n=(o&&o.getEmotes()||{emoticon_sets:{}}).emoticon_sets,i=[];for(var a in n)n.hasOwnProperty(a)&&!this._twitch_set_to_channel.hasOwnProperty(a)&&i.push(a);if(!i.length)return s.menu_pages.my_emotes.draw_menu.bind(this)(e,t,n);var r=this,d=function(){if(i.length){i=[];var o={};for(var a in n)r._twitch_set_to_channel[a]&&(o[a]=n[a]);return s.menu_pages.my_emotes.draw_menu.bind(r)(e,t,o)}};this.ws_send("twitch_sets",i,function(o,a){if(i.length){if(i=[],o){for(var u in a)a.hasOwnProperty(u)&&(r._twitch_set_to_channel[u]=a[u]);return localStorage.ffzTwitchSets=JSON.stringify(r._twitch_set_to_channel),s.menu_pages.my_emotes.draw_menu.bind(r)(e,t,n)}d()}})?setTimeout(d,2e3):d()},toggle_section:function(e){var t=e.parentElement,s=t.getAttribute("data-set"),o=this.settings.emote_menu_collapsed,n=-1!==o.indexOf(s);n?o.removeObject(s):o.push(s),this.settings.set("emote_menu_collapsed",o),t.classList.toggle("collapsed",!n)},draw_emoji:function(e){var t=document.createElement("div"),n=document.createElement("div"),i=this;t.className="heading",t.innerHTML='FrankerFaceZEmoji',n.className="emoticon-grid collapsable",n.appendChild(t),n.setAttribute("data-set","emoji"),n.classList.toggle("collapsed",-1!==this.settings.emote_menu_collapsed.indexOf("emoji")),t.addEventListener("click",function(){s.menu_pages.my_emotes.toggle_section.bind(i)(this)});var a=[];for(var r in this.emoji_data)a.push(this.emoji_data[r]);a.sort(function(e,t){var s=e.short_name.toLowerCase(),o=t.short_name.toLowerCase();return o>s?-1:s>o?1:e.rawt.raw?1:0});for(var d=0;ds?-1:s>o?1:e.idt.id?1:0});for(var h=0;hFrankerFaceZ'+t.title,o.style.backgroundImage='url("'+(t.icon||"//cdn.frankerfacez.com/script/devicon.png")+'")',n.className="emoticon-grid collapsable",n.appendChild(o),n.setAttribute("data-set","ffz-"+t.id),n.classList.toggle("collapsed",-1!==this.settings.emote_menu_collapsed.indexOf("ffz-"+t.id)),o.addEventListener("click",function(){s.menu_pages.my_emotes.toggle_section.bind(i)(this)});for(var r in t.emoticons)t.emoticons.hasOwnProperty(r)&&!t.emoticons[r].hidden&&a.push(t.emoticons[r]);a.sort(function(e,t){var s=e.name.toLowerCase(),o=t.name.toLowerCase();return o>s?-1:s>o?1:e.idt.id?1:0});for(var d=0;ds?-1:s>o?1:0});for(var u=0;uSpeedRunsLive races under channels.',on_update:function(){this.rebuild_race_ui()}},s.ws_on_close.push(function(){var t=e.App&&App.__container__.lookup("controller:channel"),s=t&&t.get("id"),o=t&&t.get("hostModeTarget.id"),n=!1;if(t){for(var i in this.srl_races)delete this.srl_races[i],(i===s||i===o)&&(n=!0);n&&this.rebuild_race_ui()}}),s.ws_commands.srl_race=function(e){var t=App.__container__.lookup("controller:channel"),s=t&&t.get("id"),o=t&&t.get("hostModeTarget.id"),n=!1;this.srl_races=this.srl_races||{};for(var i=0;i=300?"right":"left")+" share dropmenu",this._popup_kill=this._race_kill.bind(this),this._popup=o;var d="http://kadgar.net/live",u=!1;for(var c in a.entrants){var l=a.entrants[c].state;a.entrants.hasOwnProperty(c)&&a.entrants[c].channel&&("racing"==l||"entered"==l)&&(d+="/"+a.entrants[c].channel,u=!0)}var h=document.querySelector(".app-main.theatre")?document.body.clientHeight-300:e.parentElement.offsetTop-175,f=App.__container__.lookup("controller:channel"),_=f?f.get("display_name"):s.get_capitalization(t),m=encodeURIComponent("I'm watching "+_+" race "+a.goal+" in "+a.game+" on SpeedRunsLive!");r='
',r+='
Developers
Dan Salvato  
Stendec  
Version '+s.version_info+'Logs
',r+="
#Entrant Time
",r+='
',r+='',r+='

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

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

'+k+"

Goal: "+E}u?_?l.innerHTML="Done":(l.innerHTML=o.time_to_string(u),this._race_timer=setTimeout(this._update_race.bind(this,e),1e3)):l.innerHTML="Entry Open"}}}},{"../utils":32}],29:[function(t){var s=e.FrankerFaceZ,o=t("../constants");s.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?_="+(o.DEBUG?Date.now():s.version_info)),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}],30:[function(t){{var s=e.FrankerFaceZ,o=t("../constants");t("../utils")}s.prototype._update_subscribers=function(){this._update_subscribers_timer&&(clearTimeout(this._update_subscribers_timer),delete this._update_subscribers_timer),this._update_subscribers_timer=setTimeout(this._update_subscribers.bind(this),6e4);var e=this.get_user(),t=this,s=this.is_dashboard?location.pathname.match(/\/([^\/]+)/):void 0,n=this.is_dashboard&&s&&s[1]; +if(this.has_bttv||!n||n!==e.login){var i=document.querySelector("#ffz-sub-display");return void(i&&i.parentElement.removeChild(i))}jQuery.ajax({url:"/broadcast/dashboard/partnership"}).done(function(e){try{var s,i=document.createElement("span");i.innerHTML=e,s=i.querySelector("#dash_main");var a=s&&s.textContent.match(/([\d,\.]+) total active subscribers/),r=a&&a[1];if(!r){var d=document.querySelector("#ffz-sub-display");return d&&d.parentElement.removeChild(d),void(t._update_subscribers_timer&&(clearTimeout(t._update_subscribers_timer),delete t._update_subscribers_timer))}var d=document.querySelector("#ffz-sub-display span");if(!d){var u=document.querySelector(t.is_dashboard?"#stats":"#channel .stats-and-actions .channel-stats");if(!u)return;var c=document.createElement("span");c.className="ffz stat",c.id="ffz-sub-display",c.title="Active Channel Subscribers",c.innerHTML=o.STAR+" ",d=document.createElement("span"),c.appendChild(d),Twitch.api.get("chat/"+n+"/badges",null,{version:3}).done(function(e){e.subscriber&&e.subscriber.image&&(c.innerHTML="",c.appendChild(d),c.style.backgroundImage='url("'+e.subscriber.image+'")',c.style.backgroundRepeat="no-repeat",c.style.paddingLeft="23px",c.style.backgroundPosition="0 50%")}),u.appendChild(c),jQuery(c).tipsy(t.is_dashboard?{gravity:"s"}:void 0)}d.innerHTML=r}catch(l){t.error("_update_subscribers: "+l)}}).fail(function(){var e=document.querySelector("#ffz-sub-display");e&&e.parentElement.removeChild(e)})}},{"../constants":3,"../utils":32}],31:[function(t){var s=e.FrankerFaceZ,o=t("../constants"),n=t("../utils");s.ws_commands.chatters=function(t){{var s=t[0],o=t[1],n=e.App&&App.__container__.lookup("controller:channel"),i=this.is_dashboard?location.pathname.match(/\/([^\/]+)/):void 0;this.is_dashboard?i&&i[1]:n&&n.get&&n.get("id")}if(!this.is_dashboard){var a=this.rooms&&this.rooms[s];return void(a&&(a.ffz_chatters=o,this._cindex&&this._cindex.ffzUpdateChatters()))}this._dash_chatters=o},s.ws_commands.viewers=function(t){var s=t[0],i=t[1],a=e.App&&App.__container__.lookup("controller:channel"),r=this.is_dashboard?location.pathname.match(/\/([^\/]+)/):void 0,d=this.is_dashboard?r&&r[1]:a&&a.get&&a.get("id");if(!this.is_dashboard){var u=this.rooms&&this.rooms[s];return void(u&&(u.ffz_viewers=i,this._cindex&&this._cindex.ffzUpdateChatters()))}if(this._dash_viewers=i,this.settings.chatter_count&&d===s){var c=document.querySelector("#ffz-ffzchatter-display"),l=o.ZREKNARF+" "+n.number_commas(i)+("number"==typeof this._dash_chatters?" ("+n.number_commas(this._dash_chatters)+")":"");if(c)c.innerHTML=l;else{var h=document.querySelector("#stats");if(!h)return;c=document.createElement("span"),c.id="ffz-ffzchatter-display",c.className="ffz stat",c.title="Viewers (In Chat) with FrankerFaceZ",c.innerHTML=l,h.appendChild(c),jQuery(c).tipsy(this.is_dashboard?{gravity:"s"}:void 0)}}}},{"../constants":3,"../utils":32}],32:[function(t,s){var o=(e.FrankerFaceZ,t("./constants"),{}),n=document.createElement("span"),i=function(e,t,s){return s=s||"s",t=t||"",1===e?t:s},a=function(e){return 1==e?"1st":2==e?"2nd":3==e?"3rd":null==e?"---":e+"th"},r=function(e,t){t=0===t?0:t||1,t=Math.round(255*-(t/100));var s=Math.max(0,Math.min(255,e[0]-t)),o=Math.max(0,Math.min(255,e[1]-t)),n=Math.max(0,Math.min(255,e[2]-t));return[s,o,n]},d=function(e){return"rgb("+e[0]+", "+e[1]+", "+e[2]+")"},u=function(e,t){return t=0===t?0:t||1,r(e,-t)},c=function(e){e=[e[0]/255,e[1]/255,e[2]/255];for(var t=0;t=0?t.trailing=e.substr(i+2):i=e.length;var a=e.substr(n+1,i-n-1).split(" ");return t.command=a[0],a.length>1&&(t.params=a.slice(1)),t},_={":":";",s:" ",r:"\r",n:"\n","\\":"\\"},m=function(e){for(var t="",s=0;s=55296&&56319>=n?i=n:o.push(n.toString(16));var r=v[e]=v[e]||{},d=r[t]=o.join("-");return d};s.exports={update_css:function(e,t,s){var o=e.innerHTML,n="/*BEGIN "+t+"*/",i="/*END "+t+"*/",a=o.indexOf(n),r=o.indexOf(i),d=-1!==a&&-1!==r&&r>a;(d||s)&&(d&&(o=o.substr(0,a)+o.substr(r+i.length)),s&&(o+=n+s+i),e.innerHTML=o)},splitIRCMessage:f,parseIRCTags:g,emoji_to_codepoint:b,get_luminance:c,brighten:r,darken:u,rgb_to_css:d,parse_date:h,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||(n.textContent=e,t=o[e]=n.innerHTML,n.innerHTML=""),t},date_string:function(e){return e.getFullYear()+"-"+(e.getMonth()+1)+"-"+e.getDate()},pluralize:i,human_time:function(e,t){t=t||1,e=Math.floor(e);var s=Math.floor(e*t/31536e3)/t;if(s>=1)return s+" year"+i(s);var o=Math.floor((e%=31536e3)/86400);if(o>=1)return o+" day"+i(o);var n=Math.floor((e%=86400)/3600);if(n>=1)return n+" hour"+i(n);var a=Math.floor((e%=3600)/60);if(a>=1)return a+" minute"+i(a);var r=e%60;return r>=1?r+" second"+i(r):"less than a second"},time_to_string:function(e,t,s,o){var n=e%60,i=Math.floor(e/60),a=Math.floor(i/60),r="";if(i%=60,t){if(r=Math.floor(a/24),a%=24,s&&r>0)return r+" days";r=r>0?r+" days, ":""}return r+(!o||r||a?(10>a?"0":"")+a+":":"")+(10>i?"0":"")+i+":"+(10>n?"0":"")+n},format_unread:function(e){return 1>e?"":e>=99?"99+":""+e}}},{"./constants":3}]},{},[15]),e.ffz=new FrankerFaceZ}(window); \ No newline at end of file diff --git a/src/badges.js b/src/badges.js index 41da3433..40c48b6f 100644 --- a/src/badges.js +++ b/src/badges.js @@ -11,7 +11,7 @@ FFZ.settings_info.show_badges = { type: "boolean", value: true, - category: "Chat", + category: "Chat Appearance", name: "Additional Badges", help: "Show additional badges for bots, FrankerFaceZ donors, and other special users." }; @@ -21,7 +21,7 @@ FFZ.settings_info.transparent_badges = { type: "boolean", value: false, - category: "Chat", + category: "Chat Appearance", no_bttv: true, name: "Transparent Badges", diff --git a/src/commands.js b/src/commands.js index 60d3ed9a..a4a90133 100644 --- a/src/commands.js +++ b/src/commands.js @@ -70,4 +70,12 @@ FFZ.ffz_commands.massmod = function(room, args) { return "Sent mod command for " + count + " users."; } -FFZ.ffz_commands.massmod.help = "Usage: /ffz massmod \nBroadcaster only. Mod all the users in the provided list."; \ No newline at end of file +FFZ.ffz_commands.massmod.help = "Usage: /ffz massmod \nBroadcaster only. Mod all the users in the provided list."; + + +/*FFZ.ffz_commands.massunban = function(room, args) { + args = args.join(" ").trim(); + + + +}*/ \ No newline at end of file diff --git a/src/constants.js b/src/constants.js index ab38ceaa..fd2c3b02 100644 --- a/src/constants.js +++ b/src/constants.js @@ -6,6 +6,7 @@ module.exports = { DEBUG: DEBUG, SERVER: SERVER, API_SERVER: "//api.frankerfacez.com/", + API_SERVER_2: "//direct-api.frankerfacez.com/", KNOWN_CODES: { "#-?[\\\\/]": "#-/", @@ -63,5 +64,6 @@ module.exports = { GEAR: '', HEART: '', EMOTE: '', - STAR: '' + STAR: '', + CLOSE: '' } \ No newline at end of file diff --git a/src/ember/channel.js b/src/ember/channel.js index 163c4918..2895a8d5 100644 --- a/src/ember/channel.js +++ b/src/ember/channel.js @@ -50,6 +50,26 @@ FFZ.prototype.setup_channel = function() { view.ffzInit(); }; + + this.log("Hooking the Ember Channel model."); + Channel = App.__container__.resolve('model:channel'); + if ( ! Channel ) + return; + + Channel.reopen({ + ffz_host_target: undefined, + + setHostMode: function(e) { + if ( f.settings.hosted_channels ) { + this.set('ffz_host_target', e.target); + return this._super(e); + } else { + this.set('ffz_host_target', undefined); + return this._super({target: void 0, delay: 0}); + } + } + }); + this.log("Hooking the Ember Channel controller."); @@ -147,6 +167,7 @@ FFZ.prototype._modify_cindex = function(view) { this.ffzFixTitle(); this.ffzUpdateUptime(); this.ffzUpdateChatters(); + this.ffzUpdateHostButton(); var views = this.get('element').querySelector('.svg-glyph_views:not(.ffz-svg)') if ( views ) @@ -178,6 +199,109 @@ FFZ.prototype._modify_cindex = function(view) { }, + ffzUpdateHostButton: function() { + var channel_id = this.get('controller.id'), + hosted_id = this.get('controller.hostModeTarget.id'), + + user = f.get_user(), + room = user && f.rooms && f.rooms[user.login] && f.rooms[user.login].room, + now_hosting = room && room.ffz_host_target, + hosts_left = room && room.ffz_hosts_left, + + el = this.get('element'); + + this.set('ffz_host_updating', false); + + if ( channel_id ) { + var container = el && el.querySelector('.stats-and-actions .channel-actions'), + btn = container && container.querySelector('#ffz-ui-host-button'); + + if ( ! container || ! f.settings.stream_host_button || ! user || user.login === channel_id ) { + if ( btn ) + btn.parentElement.removeChild(btn); + } else { + if ( ! btn ) { + btn = document.createElement('span'); + btn.id = 'ffz-ui-host-button'; + btn.className = 'button action tooltip'; + + btn.addEventListener('click', this.ffzClickHost.bind(btn, this, false)); + + var before = container.querySelector(':scope > .theatre-button'); + if ( before ) + container.insertBefore(btn, before); + else + container.appendChild(btn); + } + + btn.classList.remove('disabled'); + btn.innerHTML = channel_id === now_hosting ? 'Unhost' : 'Host'; + if ( now_hosting ) + btn.title = 'You are now hosting ' + utils.sanitize(FFZ.get_capitalization(now_hosting)) + '.'; + else + btn.title = 'You are not hosting any channel.'; + + if ( typeof hosts_left === "number" ) + btn.title += ' You have ' + hosts_left + ' host command' + utils.pluralize(hosts_left) + ' remaining this half hour.'; + } + } + + + if ( hosted_id ) { + var container = el && el.querySelector('#hostmode .channel-actions'), + btn = container && container.querySelector('#ffz-ui-host-button'); + + if ( ! container || ! f.settings.stream_host_button || ! user || user.login === hosted_id ) { + if ( btn ) + btn.parentElement.removeChild(btn); + } else { + if ( ! btn ) { + btn = document.createElement('span'); + btn.id = 'ffz-ui-host-button'; + btn.className = 'button action tooltip'; + + btn.addEventListener('click', this.ffzClickHost.bind(btn, this, true)); + + var before = container.querySelector(':scope > .theatre-button'); + if ( before ) + container.insertBefore(btn, before); + else + container.appendChild(btn); + } + + btn.classList.remove('disabled'); + btn.innerHTML = hosted_id === now_hosting ? 'Unhost' : 'Host'; + if ( now_hosting ) + btn.title = 'You are currently hosting ' + utils.sanitize(FFZ.get_capitalization(now_hosting)) + '. Click to ' + (hosted_id === now_hosting ? 'unhost' : 'host') + ' this channel.'; + else + btn.title = 'You are not currently hosting any channel. Click to host this channel.'; + + if ( typeof hosts_left === "number" ) + btn.title += ' You have ' + hosts_left + ' host command' + utils.pluralize(hosts_left) + ' remaining this half hour.'; + } + } + }, + + ffzClickHost: function(controller, is_host) { + var target = controller.get(is_host ? 'controller.hostModeTarget.id' : 'controller.id'), + user = f.get_user(), + room = user && f.rooms && f.rooms[user.login] && f.rooms[user.login].room, + now_hosting = room && room.ffz_host_target; + + if ( ! room || controller.get('ffz_host_updating') ) + return; + + this.classList.add('disabled'); + this.title = 'Updating...'; + + controller.set('ffz_host_updating', true); + if ( now_hosting === target ) + room.send("/unhost"); + else + room.send("/host " + target); + }, + + ffzUpdateChatters: function() { // Get the counts. var room_id = this.get('controller.id'), @@ -373,6 +497,46 @@ FFZ.settings_info.channel_views = { }; +FFZ.settings_info.hosted_channels = { + type: "boolean", + value: true, + + category: "Channel Metadata", + name: "Channel Hosting", + help: "Display other channels that have been featured by the current channel.", + on_update: function(val) { + var cb = document.querySelector('input.ffz-setting-hosted-channels'); + if ( cb ) + cb.checked = val; + + if ( ! this._cindex ) + return; + + var chan = this._cindex.get('controller.model'), + room = chan && this.rooms && this.rooms[chan.get('id')], + target = room && room.room && room.room.get('ffz_host_target'); + if ( ! chan || ! room ) + return; + + chan.setHostMode({target: target, delay: 0}); + } + }; + + +FFZ.settings_info.stream_host_button = { + type: "boolean", + value: true, + + category: "Channel Metadata", + name: "Host This Channel Button", + help: "Display a button underneath streams that make it easy to host them with your own channel.", + on_update: function(val) { + if ( this._cindex ) + this._cindex.ffzUpdateHostButton(); + } + }; + + FFZ.settings_info.stream_uptime = { type: "boolean", value: false, diff --git a/src/ember/chat-input.js b/src/ember/chat-input.js index e813995c..54812efb 100644 --- a/src/ember/chat-input.js +++ b/src/ember/chat-input.js @@ -13,7 +13,8 @@ var FFZ = window.FrankerFaceZ, RIGHT: 39, DOWN: 40, TWO: 50, - COLON: 186 + COLON: 59, + FAKE_COLON: 186 }, selection_start = function(e) { @@ -74,7 +75,7 @@ FFZ.settings_info.input_emoji = { value: false, category: "Chat Input", - visible: false, + //visible: false, no_bttv: true, name: "Enter Emoji By Name", @@ -165,10 +166,12 @@ FFZ.prototype._modify_chat_input = function(component) { switch(key) { case KEYCODES.UP: case KEYCODES.DOWN: - if ( this.get("isShowingSuggestions") ) + if ( e.shiftKey || e.shiftLeft || e.ctrlKey || e.metaKey ) + return; + else if ( this.get("isShowingSuggestions") ) e.preventDefault(); else if ( f.settings.input_mru ) - Ember.run.next(this.ffzCycleMRU.bind(this, key)); + Ember.run.next(this.ffzCycleMRU.bind(this, key, selection_start(this.get("chatTextArea")))); else return this._onKeyDown(event); break; @@ -193,14 +196,15 @@ FFZ.prototype._modify_chat_input = function(component) { break; case KEYCODES.COLON: - if ( false && f.settings.input_emoji && (e.shiftKey || e.shiftLeft) ) { + case KEYCODES.FAKE_COLON: + if ( f.settings.input_emoji && (e.shiftKey || e.shiftLeft) ) { var t = this, ind = selection_start(this.get("chatTextArea")); ind > 0 && Ember.run.next(function() { var text = t.get("textareaValue"), emoji_start = text.lastIndexOf(":", ind - 1); - + if ( emoji_start !== -1 && ind !== -1 && text.charAt(ind) === ":" ) { var match = text.substr(emoji_start + 1, ind-emoji_start - 1), emoji_id = f.emoji_names[match], @@ -228,7 +232,12 @@ FFZ.prototype._modify_chat_input = function(component) { } }, - ffzCycleMRU: function(key) { + ffzCycleMRU: function(key, start_ind) { + // We don't want to do this if the keys were just moving the cursor around. + var cur_pos = selection_start(this.get("chatTextArea")); + if ( start_ind !== cur_pos ) + return; + var ind = this.get('ffz_mru_index'), mru = this._parentView.get('context.model.mru_list') || []; @@ -238,8 +247,10 @@ FFZ.prototype._modify_chat_input = function(component) { ind = (ind + mru.length) % (mru.length + 1); var old_val = this.get('ffz_old_mru'); - if ( old_val === undefined ) - this.set('ffz_old_mru', this.get('textareaValue')); + if ( old_val === undefined || old_val === null ) { + old_val = this.get('textareaValue'); + this.set('ffz_old_mru', old_val); + } var new_val = mru[ind]; if ( new_val === undefined ) { diff --git a/src/ember/chatview.js b/src/ember/chatview.js index 479edb44..6f4084db 100644 --- a/src/ember/chatview.js +++ b/src/ember/chatview.js @@ -1,16 +1,6 @@ var FFZ = window.FrankerFaceZ, utils = require('../utils'), - constants = require('../constants'), - - format_unread = function(count) { - if ( count < 1 ) - return ""; - - else if ( count >= 99 ) - return "99+"; - - return "" + count; - }; + constants = require('../constants'); // -------------------- @@ -22,7 +12,7 @@ FFZ.settings_info.swap_sidebars = { type: "boolean", value: false, - category: "Miscellaneous", + category: "Appearance", no_bttv: true, name: "Swap Sidebar Positions", @@ -39,7 +29,7 @@ FFZ.settings_info.minimal_chat = { type: "boolean", value: false, - category: "Chat", + category: "Chat Appearance", name: "Minimalistic Chat", help: "Hide all of the chat user interface, only showing messages and an input box.", @@ -52,6 +42,9 @@ FFZ.settings_info.minimal_chat = { f._roomv && f._roomv.get('stuckToBottom') && f._roomv._scrollToBottom(); },0); } + + if ( this._chatv && this._chatv.get('controller.showList') ) + this._chatv.set('controller.showList', false); } }; @@ -62,7 +55,7 @@ FFZ.settings_info.prevent_clear = { no_bttv: true, - category: "Chat Moderation", + category: "Chat Filtering", name: "Show Deleted Messages", help: "Fade deleted messages instead of replacing them, and prevent chat from being cleared.", @@ -92,7 +85,7 @@ FFZ.settings_info.chat_history = { value: true, visible: false, - category: "Chat", + category: "Chat Appearance", name: "Chat History Alpha", help: "Load previous chat messages when loading a chat room so you can see what people have been talking about. This currently only works in a handful of channels due to server capacity.", }; @@ -103,7 +96,7 @@ FFZ.settings_info.group_tabs = { no_bttv: true, - category: "Chat", + category: "Chat Moderation", name: "Chat Room Tabs Beta", help: "Enhanced UI for switching the current chat room and noticing new messages.", @@ -121,14 +114,13 @@ FFZ.settings_info.group_tabs = { FFZ.settings_info.pinned_rooms = { - type: "button", value: [], - - category: "Chat", visible: false, + }; - name: "Pinned Chat Rooms", - help: "Set a list of channels that should always be available in chat." +FFZ.settings_info.visible_rooms = { + value: [], + visible: false, }; @@ -151,8 +143,13 @@ FFZ.prototype.setup_chatview = function() { if ( Chat ) { Chat.reopen({ ffzUpdateChannels: function() { - if ( f.settings.group_tabs && f._chatv ) + if ( ! f._chatv ) + return; + + f._chatv.ffzRebuildMenu(); + if ( f.settings.group_tabs ) f._chatv.ffzRebuildTabs(); + }.observes("currentChannelRoom", "connectedPrivateGroupRooms"), removeCurrentChannelRoom: function() { @@ -160,14 +157,15 @@ FFZ.prototype.setup_chatview = function() { return this._super(); var room = this.get("currentChannelRoom"), - room_id = room && room.get('id'); + room_id = room && room.get('id'), + user = f.get_user(); if ( ! f.settings.pinned_rooms || f.settings.pinned_rooms.indexOf(room_id) === -1 ) { - // We can actually destroy it. if ( room === this.get("currentRoom") ) this.blurRoom(); - - if ( room ) + + // Don't destroy it if it's the user's room. + if ( room && user && user.login === room_id ) room.destroy(); } @@ -272,8 +270,10 @@ FFZ.prototype._modify_cview = function(view) { if ( !f.has_bttv && f.settings.group_tabs ) this.ffzEnableTabs(); + this.ffzRebuildMenu(); + setTimeout(function() { - if ( f.settings.group_tabs && f._chatv._ffz_tabs ) + if ( f.settings.group_tabs && f._chatv && f._chatv._ffz_tabs ) f._chatv.$('.chat-room').css('top', f._chatv._ffz_tabs.offsetHeight + "px"); var controller = f._chatv.get('controller'); @@ -295,14 +295,28 @@ FFZ.prototype._modify_cview = function(view) { try { f.update_ui_link(); - if ( !f.has_bttv && f.settings.group_tabs && this._ffz_tabs ) { - var room = this.get('controller.currentRoom'); - room && room.resetUnreadCount(); + var room = this.get('controller.currentRoom'), rows; + room && room.resetUnreadCount(); + if ( this._ffz_chan_table ) { + rows = jQuery(this._ffz_chan_table); + rows.children('.ffz-room-row').removeClass('active'); + if ( room ) + rows.children('.ffz-room-row[data-room="' + room.get('id') + '"]').addClass('active').children('span').text(''); + } + + if ( this._ffz_group_table ) { + rows = jQuery(this._ffz_group_table); + rows.children('.ffz-room-row').removeClass('active'); + if ( room ) + rows.children('.ffz-room-row[data-room="' + room.get('id') + '"]').addClass('active').children('span').text(''); + } + + if ( !f.has_bttv && f.settings.group_tabs && this._ffz_tabs ) { var tabs = jQuery(this._ffz_tabs); tabs.children('.ffz-chat-tab').removeClass('active'); if ( room ) - tabs.children('.ffz-chat-tab[data-room="' + room.get('id') + '"]').removeClass('tab-mentioned').addClass('active').children('span').text(''); + tabs.children('.ffz-chat-tab[data-room="' + room.get('id') + '"]').removeClass('tab-mentioned').removeClass('hidden').addClass('active').children('span').text(''); // Invite Link var can_invite = room && room.get('canInvite'); @@ -318,6 +332,214 @@ FFZ.prototype._modify_cview = function(view) { } }), + // Better Menu + + ffzRebuildMenu: function() { + return; + + var el = this.get('element'), + room_list = el && el.querySelector('.chat-rooms .tse-content'); + + if ( ! room_list ) + return; + + if ( ! room_list.classList.contains('ffz-room-list') ) { + room_list.classList.add('ffz-room-list'); + + // Find the Pending Invitations + var headers = room_list.querySelectorAll('.list-header'), + hdr = headers.length ? headers[headers.length-1] : undefined; + + if ( hdr ) { + hdr.classList.add('ffz'); + if ( hdr.nextSibling && hdr.nextSibling.classList ) + hdr.nextSibling.classList.add('ffz'); + } + } + + + // Channel Table + var t = this, + chan_table = this._ffz_chan_table || room_list.querySelector('#ffz-channel-table tbody'); + + if ( ! chan_table ) { + var tbl = document.createElement('table'); + tbl.setAttribute('cellspacing', 0); + tbl.id = 'ffz-channel-table'; + tbl.className = 'ffz'; + tbl.innerHTML = 'ChannelsJoinPin'; + room_list.insertBefore(tbl, room_list.firstChild); + + chan_table = this._ffz_chan_table = tbl.querySelector('tbody'); + } + + chan_table.innerHTML = ''; + + // Current Channel + var room = this.get('controller.currentChannelRoom'), row; + if ( room ) { + row = this.ffzBuildRow(this, room, true); + row && chan_table.appendChild(row); + } + + // Host Target + if ( this._ffz_host_room ) { + row = this.ffzBuildRow(this, this._ffz_host_room, false, true); + row && chan_table.appendChild(row); + } + + // Pinned Rooms + for(var i=0; i < f.settings.pinned_rooms.length; i++) { + var room_id = f.settings.pinned_rooms[i]; + if ( room && room.get('id') !== room_id && this._ffz_host !== room_id && f.rooms[room_id] && f.rooms[room_id].room ) { + row = this.ffzBuildRow(this, f.rooms[room_id].room); + row && chan_table.appendChild(row); + } + } + + + // Group Chat Table + var group_table = this._ffz_group_table || room_list.querySelector('#ffz-group-table tbody'); + if ( ! group_table ) { + var tbl = document.createElement('table'); + tbl.setAttribute('cellspacing', 0); + tbl.id = 'ffz-group-table'; + tbl.className = 'ffz'; + tbl.innerHTML = 'Group ChatsPin'; + + var before = room_list.querySelector('#ffz-channel-table'); + room_list.insertBefore(tbl, before.nextSibling); + + group_table = this._ffz_group_table = tbl.querySelector('tbody'); + } + + group_table.innerHTML = ''; + + _.each(this.get('controller.connectedPrivateGroupRooms'), function(room) { + var row = t.ffzBuildRow(t, room); + row && group_table && group_table.appendChild(row); + }); + + + // Change Create Tooltip + var create_btn = el.querySelector('.button.create'); + if ( create_btn ) + create_btn.title = 'Create a Group Room'; + }, + + ffzBuildRow: function(view, room, current_channel, host_channel) { + var row = document.createElement('tr'), + icon = document.createElement('td'), + name_el = document.createElement('td'), + + btn, + toggle_pinned = document.createElement('td'), + toggle_visible = document.createElement('td'), + + group = room.get('isGroupRoom'), + current = room === view.get('controller.currentRoom'), + //unread = format_unread(current ? 0 : room.get('unreadCount')), + + name = room.get('tmiRoom.displayName') || (group ? room.get('tmiRoom.name') : FFZ.get_capitalization(room.get('id'), function(name) { + f.log("Name for Row: " + name); + //unread = format_unread(current ? 0 : room.get('unreadCount')); + name_el.innerHTML = utils.sanitize(name); + })); + + name_el.className = 'ffz-room'; + name_el.innerHTML = utils.sanitize(name); + + if ( current_channel ) { + icon.innerHTML = constants.CAMERA; + icon.title = name_el.title = "Current Channel"; + icon.className = name_el.className = 'tooltip'; + } else if ( host_channel ) { + icon.innerHTML = constants.EYE; + icon.title = name_el.title = "Hosted Channel"; + icon.className = name_el.className = 'tooltip'; + } + + toggle_pinned.className = toggle_visible.className = 'ffz-row-switch'; + + toggle_pinned.innerHTML = ''; + toggle_visible.innerHTML = ''; + + row.setAttribute('data-room', room.get('id')); + + row.className = 'ffz-room-row'; + row.classList.toggle('current-channel', current_channel); + row.classList.toggle('host-channel', host_channel); + row.classList.toggle('group-chat', group); + row.classList.toggle('active', current); + + row.appendChild(icon); + row.appendChild(name_el); + + if ( ! group ) { + row.appendChild(toggle_pinned); + btn = toggle_pinned.querySelector('a.switch'); + btn.addEventListener('click', function(e) { + e.preventDefault(); + e.stopPropagation && e.stopPropagation(); + + var room_id = room.get('id'), + is_pinned = f.settings.pinned_rooms.indexOf(room_id) !== -1; + + if ( is_pinned ) + f._leave_room(room_id); + else + f._join_room(room_id); + + this.classList.toggle('active', !is_pinned); + }); + } else { + btn = document.createElement('a'); + btn.className = 'leave-chat tooltip'; + btn.innerHTML = constants.CLOSE; + btn.title = 'Leave Group'; + + name_el.appendChild(btn); + + btn.addEventListener('click', function(e) { + e.preventDefault(); + e.stopPropagation && e.stopPropagation(); + + if ( ! confirm('Are you sure you want to leave the group room "' + name + '"?') ) + return; + + room.get('isGroupRoom') && room.del(); + }); + } + + row.appendChild(toggle_visible); + btn = toggle_visible.querySelector('a.switch'); + btn.addEventListener('click', function(e) { + e.preventDefault(); + e.stopPropagation && e.stopPropagation(); + + var room_id = room.get('id'), + visible_rooms = f.settings.visible_rooms, + is_visible = visible_rooms.indexOf(room_id) !== -1; + + if ( is_visible ) + visible_rooms.removeObject(room_id); + else + visible_rooms.push(room_id); + + f.settings.set('visible_rooms', visible_rooms); + this.classList.toggle('active', !is_visible); + view.ffzRebuildTabs(); + }); + + row.addEventListener('click', function() { + var controller = view.get('controller'); + controller.focusRoom(room); + controller.set('showList', false); + }); + + return row; + }, + // Group Tabs~! ffzEnableTabs: function() { @@ -433,6 +655,8 @@ FFZ.prototype._modify_cview = function(view) { }, ffzTabUnread: function(room_id) { + // TODO: Update menu. + if ( f.has_bttv || ! f.settings.group_tabs ) return; @@ -446,7 +670,7 @@ FFZ.prototype._modify_cview = function(view) { room = f.rooms && f.rooms[room_id]; if ( tab && room ) { - var unread = format_unread(room_id === current_id ? 0 : room.room.get('unreadCount')); + var unread = utils.format_unread(room_id === current_id ? 0 : room.room.get('unreadCount')); tab.querySelector('span').innerHTML = unread; } @@ -463,7 +687,7 @@ FFZ.prototype._modify_cview = function(view) { if ( ! room ) continue; - var unread = format_unread(room_id === current_id ? 0 : room.room.get('unreadCount')); + var unread = utils.format_unread(room_id === current_id ? 0 : room.room.get('unreadCount')); tab.querySelector('span').innerHTML = unread; } @@ -473,21 +697,24 @@ FFZ.prototype._modify_cview = function(view) { ffzBuildTab: function(view, room, current_channel, host_channel) { var tab = document.createElement('span'), name, unread, icon = '', + room_id = room.get('id'), group = room.get('isGroupRoom'), - current = room === view.get('controller.currentRoom'); + current = room === view.get('controller.currentRoom'), + visible = current || f.settings.visible_rooms.indexOf(room_id) !== -1; tab.setAttribute('data-room', room.id); tab.className = 'ffz-chat-tab tooltip'; + //tab.classList.toggle('hidden', ! visible); tab.classList.toggle('current-channel', current_channel); tab.classList.toggle('host-channel', host_channel); tab.classList.toggle('group-chat', group); tab.classList.toggle('active', current); - unread = format_unread(current ? 0 : room.get('unreadCount')); + unread = utils.format_unread(current ? 0 : room.get('unreadCount')); name = room.get('tmiRoom.displayName') || (group ? room.get('tmiRoom.name') : FFZ.get_capitalization(room.get('id'), function(name) { - unread = format_unread(current ? 0 : room.get('unreadCount')); + unread = utils.format_unread(current ? 0 : room.get('unreadCount')); tab.innerHTML = icon + utils.sanitize(name) + '' + unread + ''; })); @@ -505,7 +732,9 @@ FFZ.prototype._modify_cview = function(view) { tab.innerHTML = icon + utils.sanitize(name) + '' + unread + ''; tab.addEventListener('click', function() { - view.get('controller').focusRoom(room); + var controller = view.get('controller'); + controller.focusRoom(room); + controller.set('showList', false); }); return tab; @@ -542,14 +771,28 @@ FFZ.prototype._modify_cview = function(view) { // ---------------------- FFZ.prototype.connect_extra_chat = function() { + var user = this.get_user(); + if ( user && user.login ) { + // Make sure we're in the user's room. + if ( ! this.rooms[user.login] || this.rooms[user.login].room ) { + var Room = App.__container__.resolve('model:room'), + r = Room && Room.findOne(user.login); + } + } + if ( this.has_bttv ) return; for(var i=0; i < this.settings.pinned_rooms.length; i++) this._join_room(this.settings.pinned_rooms[i], true); - if ( ! this.has_bttv && this._chatv && this.settings.group_tabs ) + if ( ! this._chatv ) + return; + + if ( ! this.has_bttv && this.settings.group_tabs ) this._chatv.ffzRebuildTabs(); + + this._chatv.ffzRebuildMenu(); } @@ -562,8 +805,11 @@ FFZ.prototype._join_room = function(room_id, no_rebuild) { } // Make sure we're not already there. - if ( this.rooms[room_id] && this.rooms[room_id].room ) + if ( this.rooms[room_id] && this.rooms[room_id].room ) { + if ( did_join && ! no_rebuild && ! this.has_bttv && this._chatv && this.settings.group_tabs ) + this._chatv.ffzRebuildTabs(); return did_join; + } // Okay, fine. Get it. var Room = App.__container__.resolve('model:room'), @@ -573,6 +819,9 @@ FFZ.prototype._join_room = function(room_id, no_rebuild) { if ( ! no_rebuild && ! this.has_bttv && this._chatv && this.settings.group_tabs ) this._chatv.ffzRebuildTabs(); + if ( ! no_rebuild && this._chatv ) + this._chatv.ffzRebuildMenu(); + return did_join; } @@ -589,7 +838,8 @@ FFZ.prototype._leave_room = function(room_id, no_rebuild) { return did_leave; var Chat = App.__container__.lookup('controller:chat'), - r = this.rooms[room_id].room; + r = this.rooms[room_id].room, + user = this.get_user(); if ( ! Chat || Chat.get('currentChannelRoom.id') === room_id || (this._chatv && this._chatv._ffz_host === room_id) ) return did_leave; @@ -597,11 +847,16 @@ FFZ.prototype._leave_room = function(room_id, no_rebuild) { if ( Chat.get('currentRoom.id') === room_id ) Chat.blurRoom(); - r.destroy(); + // Don't leave the user's room, but update the UI. + if ( ! user || user.login !== room_id ) + r.destroy(); if ( ! no_rebuild && ! this.has_bttv && this._chatv && this.settings.group_tabs ) this._chatv.ffzRebuildTabs(); + if ( ! no_rebuild && this._chatv ) + this._chatv.ffzRebuildMenu(); + return did_leave; } diff --git a/src/ember/line.js b/src/ember/line.js index 72cc1005..4a11611b 100644 --- a/src/ember/line.js +++ b/src/ember/line.js @@ -1,5 +1,6 @@ var FFZ = window.FrankerFaceZ, utils = require("../utils"), + constants = require("../constants"), SEPARATORS = "[\\s`~<>!-#%-\\x2A,-/:;\\x3F@\\x5B-\\x5D_\\x7B}\\u00A1\\u00A7\\u00AB\\u00B6\\u00B7\\u00BB\\u00BF\\u037E\\u0387\\u055A-\\u055F\\u0589\\u058A\\u05BE\\u05C0\\u05C3\\u05C6\\u05F3\\u05F4\\u0609\\u060A\\u060C\\u060D\\u061B\\u061E\\u061F\\u066A-\\u066D\\u06D4\\u0700-\\u070D\\u07F7-\\u07F9\\u0830-\\u083E\\u085E\\u0964\\u0965\\u0970\\u0AF0\\u0DF4\\u0E4F\\u0E5A\\u0E5B\\u0F04-\\u0F12\\u0F14\\u0F3A-\\u0F3D\\u0F85\\u0FD0-\\u0FD4\\u0FD9\\u0FDA\\u104A-\\u104F\\u10FB\\u1360-\\u1368\\u1400\\u166D\\u166E\\u169B\\u169C\\u16EB-\\u16ED\\u1735\\u1736\\u17D4-\\u17D6\\u17D8-\\u17DA\\u1800-\\u180A\\u1944\\u1945\\u1A1E\\u1A1F\\u1AA0-\\u1AA6\\u1AA8-\\u1AAD\\u1B5A-\\u1B60\\u1BFC-\\u1BFF\\u1C3B-\\u1C3F\\u1C7E\\u1C7F\\u1CC0-\\u1CC7\\u1CD3\\u2010-\\u2027\\u2030-\\u2043\\u2045-\\u2051\\u2053-\\u205E\\u207D\\u207E\\u208D\\u208E\\u2329\\u232A\\u2768-\\u2775\\u27C5\\u27C6\\u27E6-\\u27EF\\u2983-\\u2998\\u29D8-\\u29DB\\u29FC\\u29FD\\u2CF9-\\u2CFC\\u2CFE\\u2CFF\\u2D70\\u2E00-\\u2E2E\\u2E30-\\u2E3B\\u3001-\\u3003\\u3008-\\u3011\\u3014-\\u301F\\u3030\\u303D\\u30A0\\u30FB\\uA4FE\\uA4FF\\uA60D-\\uA60F\\uA673\\uA67E\\uA6F2-\\uA6F7\\uA874-\\uA877\\uA8CE\\uA8CF\\uA8F8-\\uA8FA\\uA92E\\uA92F\\uA95F\\uA9C1-\\uA9CD\\uA9DE\\uA9DF\\uAA5C-\\uAA5F\\uAADE\\uAADF\\uAAF0\\uAAF1\\uABEB\\uFD3E\\uFD3F\\uFE10-\\uFE19\\uFE30-\\uFE52\\uFE54-\\uFE61\\uFE63\\uFE68\\uFE6A\\uFE6B\\uFF01-\\uFF03\\uFF05-\\uFF0A\\uFF0C-\\uFF0F\\uFF1A\\uFF1B\\uFF1F\\uFF20\\uFF3B-\\uFF3D\\uFF3F\\uFF5B\\uFF5D\\uFF5F-\\uFF65]", SPLITTER = new RegExp(SEPARATORS + "*," + SEPARATORS + "*"), @@ -189,7 +190,7 @@ FFZ.settings_info.room_status = { type: "boolean", value: true, - category: "Chat", + category: "Chat Appearance", no_bttv: true, name: "Room Status Indicators", @@ -202,11 +203,32 @@ FFZ.settings_info.room_status = { }; +FFZ.settings_info.line_purge_icon = { + type: "boolean", + value: false, + + no_bttv: true, + category: "Chat Moderation", + + name: "Purge Icon in Mod Icons", + help: "Display a Purge Icon in chat line Mod Icons for quickly purging users.", + + on_update: function(val) { + if ( this.has_bttv ) + return; + + document.body.classList.toggle("ffz-chat-purge-icon", val); + } + }; + + FFZ.settings_info.replace_bad_emotes = { type: "boolean", value: true, - category: "Chat", + category: "Chat Appearance", + no_bttv: true, + name: "Fix Low Quality Twitch Global Emoticons", help: "Replace emoticons such as DansGame and RedCoat with cleaned up versions that don't have pixels around the edges or white backgrounds for nicer display on dark chat." } @@ -215,7 +237,7 @@ FFZ.settings_info.parse_emoji = { type: "boolean", value: true, - category: "Chat", + category: "Chat Appearance", name: "Replace Emoji with Images", help: "Replace emoji in chat messages with nicer looking images from the open-source Twitter Emoji project." @@ -226,7 +248,7 @@ FFZ.settings_info.room_status = { type: "boolean", value: true, - category: "Chat", + category: "Chat Appearance", no_bttv: true, name: "Room Status Indicators", @@ -243,7 +265,7 @@ FFZ.settings_info.scrollback_length = { type: "button", value: 150, - category: "Chat", + category: "Chat Appearance", no_bttv: true, name: "Scrollback Length", @@ -278,7 +300,7 @@ FFZ.settings_info.banned_words = { type: "button", value: [], - category: "Chat", + category: "Chat Filtering", no_bttv: true, //visible: function() { return ! this.has_bttv }, @@ -310,7 +332,7 @@ FFZ.settings_info.keywords = { type: "button", value: [], - category: "Chat", + category: "Chat Filtering", no_bttv: true, //visible: function() { return ! this.has_bttv }, @@ -343,19 +365,13 @@ FFZ.settings_info.fix_color = { type: "boolean", value: true, - category: "Chat", + category: "Chat Appearance", 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.", - on_update: function(val) { - if ( this.has_bttv ) - return; - - document.body.classList.toggle("ffz-chat-colors", val); - } + on_update: function(val) { document.body.classList.toggle("ffz-chat-colors", !this.has_bttv && val); } }; @@ -363,31 +379,122 @@ FFZ.settings_info.link_info = { type: "boolean", value: true, - category: "Chat", + category: "Chat Appearance", 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.legacy_badges = { + type: "boolean", + value: false, + + category: "Chat Appearance", + + name: "Legacy Badges", + help: "Display the old, pre-vector chat badges from Twitch.", + + on_update: function(val) { document.body.classList.toggle("ffz-legacy-badges", val); } + }; + + FFZ.settings_info.chat_rows = { type: "boolean", value: false, - category: "Chat", + category: "Chat Appearance", no_bttv: true, - //visible: function() { return ! this.has_bttv }, name: "Chat Line Backgrounds", help: "Display alternating background colors for lines in chat.", - on_update: function(val) { - if ( this.has_bttv ) + on_update: function(val) { document.body.classList.toggle("ffz-chat-background", !this.has_bttv && val); } + }; + + +FFZ.settings_info.chat_separators = { + type: "boolean", + value: false, + + category: "Chat Appearance", + no_bttv: true, + + name: "Chat Line Separators", + help: "Display thin lines between chat messages for further visual separation.", + + on_update: function(val) { document.body.classList.toggle("ffz-chat-separator", !this.has_bttv && val); } + }; + +FFZ.settings_info.chat_padding = { + type: "boolean", + value: false, + + category: "Chat Appearance", + no_bttv: true, + + name: "Reduced Chat Line Padding", + help: "Reduce the amount of padding around chat messages to fit more on-screen at once.", + + on_update: function(val) { document.body.classList.toggle("ffz-chat-padding", !this.has_bttv && val); } + }; + + +FFZ.settings_info.high_contrast_chat = { + type: "boolean", + value: false, + + category: "Chat Appearance", + no_bttv: true, + + name: "High Contrast", + help: "Display chat using white and black for maximum contrast. This is suitable for capturing and chroma keying chat to display on stream.", + + on_update: function(val) { document.body.classList.toggle("ffz-high-contrast-chat", !this.has_bttv && val); } + }; + + +FFZ.settings_info.chat_font_size = { + type: "button", + value: 12, + + category: "Chat Appearance", + no_bttv: true, + + name: "Font Size", + help: "Make the chat font bigger or smaller.", + + method: function() { + var old_val = this.settings.chat_font_size, + new_val = prompt("Chat Font Size\n\nPlease enter a new size for the chat font. The default is 12.", old_val); + + if ( new_val === null || new_val === undefined ) return; - document.body.classList.toggle("ffz-chat-background", val); + var parsed = parseInt(new_val); + if ( parsed === NaN || parsed < 1 ) + parsed = 12; + + this.settings.set("chat_font_size", parsed); + }, + + on_update: function(val) { + if ( this.has_bttv || ! this._chat_style ) + return; + + var css; + if ( val === 12 ) + css = ""; + else { + var lh = Math.max(20, Math.round((20/12)*val)), + pd = Math.floor((lh - 20) / 2); + css = ".ember-chat .chat-messages .chat-line { font-size: " + val + "px !important; line-height: " + lh + "px !important; }"; + if ( pd ) + css += ".ember-chat .chat-messages .chat-line .mod-icons, .ember-chat .chat-messages .chat-line .badges { padding-top: " + pd + "px; }"; + } + + utils.update_css(this._chat_style, "chat_font_size", css); } }; @@ -397,14 +504,35 @@ FFZ.settings_info.chat_rows = { // --------------------- FFZ.prototype.setup_line = function() { + // Tipsy Handler + jQuery(document.body).on("mouseleave", ".tipsy", function() { + this.parentElement.removeChild(this); + }); + + + // Chat Style + var s = this._chat_style = document.createElement('style'); + s.id = "ffz-style-chat"; + s.type = 'text/css'; + document.head.appendChild(s); + + // Initial calculation. + FFZ.settings_info.chat_font_size.on_update.bind(this)(this.settings.chat_font_size); + + // Chat Enhancements document.body.classList.toggle("ffz-chat-colors", !this.has_bttv && this.settings.fix_color); + document.body.classList.toggle("ffz-legacy-badges", this.settings.legacy_badges); document.body.classList.toggle('ffz-chat-background', !this.has_bttv && this.settings.chat_rows); + document.body.classList.toggle("ffz-chat-separator", !this.has_bttv && this.settings.chat_separators); + document.body.classList.toggle("ffz-chat-padding", !this.has_bttv && this.settings.chat_padding); + document.body.classList.toggle("ffz-chat-purge-icon", !this.has_bttv && this.settings.line_purge_icon); + document.body.classList.toggle("ffz-high-contrast-chat", !this.has_bttv && this.settings.high_contrast_chat); this._colors = {}; this._last_row = {}; - var s = this._fix_color_style = document.createElement('style'); + s = this._fix_color_style = document.createElement('style'); s.id = "ffz-style-username-colors"; s.type = 'text/css'; document.head.appendChild(s); @@ -414,13 +542,13 @@ FFZ.prototype.setup_line = function() { this._twitch_emotes = {}; this._link_data = {}; - this.log("Hooking the Ember Whisper controller."); + this.log("Hooking the Ember Whisper Line component."); var Whisper = App.__container__.resolve('component:whisper-line'); if ( Whisper ) this._modify_line(Whisper); - this.log("Hooking the Ember Line controller."); + this.log("Hooking the Ember Message Line component."); var Line = App.__container__.resolve('component:message-line'); @@ -436,6 +564,7 @@ FFZ.prototype.setup_line = function() { FFZ.prototype._modify_line = function(component) { var f = this; + component.reopen({ tokenizedMessage: function() { // Add our own step to the tokenization procedure. @@ -492,16 +621,33 @@ FFZ.prototype._modify_line = function(component) { }), willClearRender: function() { - // This is here to prevent tipsy tooltips from hanging around. try { - this.$('a.mod-icon').tipsy('disable'); - jQuery('body > .tipsy:last').remove(); } catch(err) { f.error("LineView willClearRender: " + err); } this._super(); }, + + click: function(e) { + if ( e.target && e.target.classList.contains('mod-icon') ) { + jQuery(e.target).trigger('mouseout'); + + if ( e.target.classList.contains('purge') ) { + var i = this.get('msgObject.from'), + room_id = this.get('msgObject.room'), + room = room_id && f.rooms[room_id] && f.rooms[room_id].room; + + if ( room ) { + room.send("/timeout " + i + " 1"); + room.clearMessages(i); + } + return; + } + } + + return this._super(e); + }, didInsertElement: function() { this._super(); @@ -548,6 +694,27 @@ FFZ.prototype._modify_line = function(component) { this.$('.message').append(btn); } + + + // Hide Mod Buttons + if ( (this.get('isBroadcaster') && !(this.get('controller.parentController.model.isStaff') || this.get('controller.parentController.model.isAdmin'))) || (this.get('isModeratorOrHigher') && !(this.get('controller.parentController.model.isBroadcaster') || this.get('controller.parentController.model.isStaff') || this.get('controller.parentController.model.isAdmin'))) ) { + var mod_icons = el.querySelector('span.mod-icons'); + mod_icons && mod_icons.classList.add('hidden'); + } + + + // Purge Button + var timeout_btn = el.querySelector('span.mod-icons a.mod-icon.timeout'); + if ( timeout_btn ) { + var purge_btn = document.createElement('a'); + purge_btn.className = 'mod-icon float-left tooltip purge'; + purge_btn.innerHTML = 'Purge'; + purge_btn.title = 'Purge User (Timeout 1s)'; + purge_btn.href = '#'; + + timeout_btn.title = 'Timeout User (10m)'; + timeout_btn.parentElement.insertBefore(purge_btn, timeout_btn.nextSibling); + } // Badge @@ -606,7 +773,9 @@ FFZ.prototype._modify_line = function(component) { if ( id !== null ) { // High-DPI Images - img.setAttribute('srcset', build_srcset(id)); + if ( ! constants.EMOTE_REPLACEMENTS[id] ) + img.setAttribute('srcset', build_srcset(id)); + img.setAttribute('emote-id', id); // Source Lookup diff --git a/src/ember/moderation-card.js b/src/ember/moderation-card.js index a6983f88..57766435 100644 --- a/src/ember/moderation-card.js +++ b/src/ember/moderation-card.js @@ -1,5 +1,6 @@ var FFZ = window.FrankerFaceZ, utils = require("../utils"), + constants = require("../constants"), helpers, keycodes = { @@ -95,6 +96,18 @@ FFZ.settings_info.mod_card_hotkeys = { }; +FFZ.settings_info.mod_card_info = { + type: "boolean", + value: false, + + no_bttv: true, + category: "Chat Moderation", + + name: "Moderation Card Additional Information", + help: "Display a channel's follower count, view count, and account age on moderation cards." + }; + + FFZ.settings_info.mod_card_history = { type: "boolean", value: false, @@ -119,6 +132,69 @@ FFZ.settings_info.mod_card_history = { }; +FFZ.settings_info.mod_card_buttons = { + type: "button", + value: [], + + category: "Chat Moderation", + no_bttv: true, + + name: "Moderation Card Additional Buttons", + help: "Add additional buttons to moderation cards for running chat commands on those users.", + + method: function() { + var old_val = ""; + for(var i=0; i < this.settings.mod_card_buttons.length; i++) { + var cmd = this.settings.mod_card_buttons[i]; + if ( cmd.indexOf(' ') !== -1 ) + old_val += ' "' + cmd + '"'; + else + old_val += ' ' + cmd; + } + + var new_val = prompt("Moderation Card Additional Buttons\n\nPlease enter a list of additional commands to display buttons for on moderation cards. Commands are separated by spaces. To include spaces in a command, surround the command with double quotes (\"). Use \"{user}\" to insert the user's username into the command, otherwise it will be appended to the end.\n\nExample: !permit \"!reg add {user}\"", old_val); + + if ( new_val === null || new_val === undefined ) + return; + + var vals = []; + new_val = new_val.trim(); + + while(new_val) { + if ( new_val.charAt(0) === '"' ) { + var end = new_val.indexOf('"', 1); + if ( end === -1 ) + end = new_val.length; + + var segment = new_val.substr(1, end - 1); + if ( segment ) + vals.push(segment); + + new_val = new_val.substr(end + 1); + + } else { + var ind = new_val.indexOf(' '); + if ( ind === -1 ) { + if ( new_val ) + vals.push(new_val); + + new_val = ''; + + } else { + var segment = new_val.substr(0, ind); + if ( segment ) + vals.push(segment); + + new_val = new_val.substr(ind + 1); + } + } + } + + this.settings.set("mod_card_buttons", vals); + } + }; + + FFZ.settings_info.mod_card_durations = { type: "button", value: [300, 600, 3600, 43200, 86400, 604800], @@ -186,6 +262,40 @@ FFZ.prototype.setup_mod_card = function() { this.rerender(); }.observes("cardInfo.isModeratorOrHigher", "cardInfo.user"), + ffzRebuildInfo: function() { + var el = this.get('element'), + info = el && el.querySelector('.info'); + if ( ! info ) + return; + + var out = '' + constants.EYE + ' ' + utils.number_commas(this.get('cardInfo.user.views') || 0) + '', + since = utils.parse_date(this.get('cardInfo.user.created_at') || ''), + followers = this.get('cardInfo.user.ffz_followers'); + + if ( typeof followers === "number" ) { + out += '' + constants.HEART + ' ' + utils.number_commas(followers || 0) + ''; + + } else if ( followers === undefined ) { + var t = this; + this.set('cardInfo.user.ffz_followers', false); + Twitch.api.get("channels/" + this.get('cardInfo.user.id') + '/follows', {limit:1}).done(function(data) { + t.set('cardInfo.user.ffz_followers', data._total); + t.ffzRebuildInfo(); + }).fail(function(data) { + t.set('cardInfo.user.ffz_followers', undefined); + }); + } + + if ( since ) { + var age = Math.floor((Date.now() - since.getTime()) / 1000); + if ( age > 0 ) { + out += '' + constants.CLOCK + ' ' + utils.human_time(age, 10) + ''; + } + } + + info.innerHTML = out; + }.observes("cardInfo.user.views"), + didInsertElement: function() { this._super(); window._card = this; @@ -194,17 +304,110 @@ FFZ.prototype.setup_mod_card = function() { return; var el = this.get('element'), - controller = this.get('controller'); + controller = this.get('controller'), + line; // Style it! - if ( f.settings.mod_card_hotkeys || (f.settings.mod_card_durations && f.settings.mod_card_durations.length) ) - el.classList.add('ffz-moderation-card'); + el.classList.add('ffz-moderation-card'); + + // Info-tize it! + if ( f.settings.mod_card_info ) { + var info = document.createElement('div'), + after = el.querySelector('h3.name'); + if ( after ) { + el.classList.add('ffz-has-info'); + info.className = 'info channel-stats'; + after.parentElement.insertBefore(info, after.nextSibling); + this.ffzRebuildInfo(); + } + } + + // Additional Buttons + if ( f.settings.mod_card_buttons && f.settings.mod_card_buttons.length ) { + line = document.createElement('div'); + line.className = 'extra-interface interface clearfix'; + + var cmds = {}, + add_btn_click = function(cmd) { + var user_id = controller.get('cardInfo.user.id'), + cont = App.__container__.lookup('controller:chat'), + room = cont && cont.get('currentRoom'); + + room && room.send(cmd.replace(/{user}/g, user_id)); + }, + + add_btn_make = function(cmd) { + var btn = document.createElement('button'), + segment = cmd.split(' ', 1)[0], + title = cmds[segment] > 1 ? cmd.split(' ', cmds[segment]) : [segment]; + + if ( /^[!~./]/.test(title[0]) ) + title[0] = title[0].substr(1); + + title = _.map(title, function(s){ return s.capitalize() }).join(' '); + + btn.className = 'button'; + btn.innerHTML = utils.sanitize(title); + btn.title = utils.sanitize(cmd.replace(/{user}/g, controller.get('cardInfo.user.id') || '{user}')); + + jQuery(btn).tipsy(); + btn.addEventListener('click', add_btn_click.bind(this, cmd)); + return btn; + }; + + var cmds = {}; + for(var i=0; i < f.settings.mod_card_buttons.length; i++) + cmds[f.settings.mod_card_buttons[i].split(' ',1)[0]] = (cmds[f.settings.mod_card_buttons[i].split(' ',1)[0]] || 0) + 1; + + for(var i=0; i < f.settings.mod_card_buttons.length; i++) { + var cmd = f.settings.mod_card_buttons[i], + ind = cmd.indexOf('{user}'); + + if ( ind === -1 ) + cmd += ' {user}'; + + line.appendChild(add_btn_make(cmd)) + } + + el.appendChild(line); + } + + + // Key Handling + el.setAttribute('tabindex', 1); + if ( f.settings.mod_card_hotkeys ) { + el.classList.add('no-mousetrap'); + + el.addEventListener('keyup', function(e) { + var key = e.keyCode || e.which, + user_id = controller.get('cardInfo.user.id'), + is_mod = controller.get('cardInfo.isModeratorOrHigher'), + room = App.__container__.lookup('controller:chat').get('currentRoom'); + + if ( is_mod && key == keycodes.P ) + room.send("/timeout " + user_id + " 1"); + + else if ( is_mod && key == keycodes.B ) + room.send("/ban " + user_id); + + else if ( is_mod && key == keycodes.T ) + room.send("/timeout " + user_id + " 600"); + + else if ( is_mod && key == keycodes.U ) + room.send("/unban " + user_id); + + else if ( key != keycodes.ESC ) + return; + + controller.send('close'); + }); + } + // Only do the big stuff if we're mod. if ( controller.get('cardInfo.isModeratorOrHigher') ) { el.classList.add('ffz-is-mod'); - el.setAttribute('tabindex', 1); - + // Key Handling if ( f.settings.mod_card_hotkeys ) { el.classList.add('no-mousetrap'); @@ -262,8 +465,8 @@ FFZ.prototype.setup_mod_card = function() { if ( f.settings.mod_card_durations && f.settings.mod_card_durations.length ) { // Extra Moderation - var line = document.createElement('div'); - line.className = 'interface clearfix'; + line = document.createElement('div'); + line.className = 'extra-interface interface clearfix'; line.appendChild(btn_make(1)); @@ -309,14 +512,27 @@ FFZ.prototype.setup_mod_card = function() { } - var msg_btn = el.querySelector(".interface > button"); - if ( msg_btn && msg_btn.classList.contains("message-button") ) { - msg_btn.innerHTML = MESSAGE; + var msg_btn = el.querySelector(".interface > button.message-button"); + if ( msg_btn ) { + msg_btn.innerHTML = 'W'; msg_btn.classList.add('glyph-only'); msg_btn.classList.add('message'); msg_btn.title = "Whisper User"; jQuery(msg_btn).tipsy(); + + + var real_msg = document.createElement('button'); + real_msg.className = 'message-button button glyph-only message'; + real_msg.innerHTML = MESSAGE; + real_msg.title = "Message User"; + jQuery(real_msg).tipsy(); + + real_msg.addEventListener('click', function() { + window.open('http://www.twitch.tv/message/compose?to=' + controller.get('cardInfo.user.id')); + }) + + msg_btn.parentElement.insertBefore(real_msg, msg_btn.nextSibling); } diff --git a/src/ember/room.js b/src/ember/room.js index 5677416c..770b6c11 100644 --- a/src/ember/room.js +++ b/src/ember/room.js @@ -171,7 +171,7 @@ FFZ.prototype._modify_rview = function(view) { cont = el && el.querySelector('.chat-buttons-container'); if ( ! cont ) - return f.log("no container"); + return; var r9k_badge = cont.querySelector('#ffz-stat-r9k'), sub_badge = cont.querySelector('#ffz-stat-sub'), @@ -254,8 +254,13 @@ FFZ.prototype._modify_rview = function(view) { this._ffz_interval = setInterval(this.ffzPulse.bind(this), 200); this._ffz_messages = messages; + this._ffz_mouse_move = this.ffzMouseMove.bind(this); this._ffz_mouse_out = this.ffzMouseOut.bind(this); + this._ffz_mouse_down = this.ffzMouseDown.bind(this); + + this._$chatMessagesScroller.unbind('mousedown'); + this._$chatMessagesScroller.bind('mousedown', this._ffz_mouse_down); messages.addEventListener('mousemove', this._ffz_mouse_move); messages.addEventListener('mouseout', this._ffz_mouse_out); @@ -304,6 +309,14 @@ FFZ.prototype._modify_rview = function(view) { this._scrollToBottom(); }, + ffzMouseDown: function(event) { + var t = this._$chatMessagesScroller; + if ( ! this.ffz_frozen && t && t[0] && (event.which > 0 || "mousedown" === event.type || "mousewheel" === event.type) ) { + var r = t[0].scrollHeight - t[0].scrollTop - t[0].offsetHeight; + this._setStuckToBottom(10 >= r); + } + }, + ffzMouseOut: function(event) { this._ffz_outside = true; var e = this; @@ -326,10 +339,6 @@ FFZ.prototype._modify_rview = function(view) { if ( this.ffz_frozen ) return; - // Don't do it if we're over the bar itself. - if ( event.clientY >= (this._ffz_messages.getBoundingClientRect().bottom - 21) ) - return; - this.ffz_frozen = true; if ( this.get('stuckToBottom') ) { this.set('controller.model.messageBufferSize', f.settings.scrollback_length + 150); @@ -351,6 +360,8 @@ FFZ.prototype._modify_rview = function(view) { _setStuckToBottom: function(val) { this.set("stuckToBottom", val); this.get("controller.model") && this.set("controller.model.messageBufferSize", f.settings.scrollback_length + (val ? 0 : 150)); + if ( ! val ) + this.ffzUnfreeze(); }, // Warnings~! @@ -736,7 +747,7 @@ FFZ.prototype._insert_history = function(room_id, data) { FFZ.prototype.load_room = function(room_id, callback, tries) { var f = this; - jQuery.getJSON(constants.API_SERVER + "v1/room/" + room_id) + jQuery.getJSON(((tries||0)%2 === 0 ? constants.API_SERVER : constants.API_SERVER_2) + "v1/room/" + room_id) .done(function(data) { if ( data.sets ) { for(var key in data.sets) @@ -814,7 +825,9 @@ FFZ.prototype._modify_room = function(room) { var wait = this.get('slowWait') || 0; this.set('slowWait', value); if ( wait < 1 && value > 0 ) { - setTimeout(this.ffzUpdateWait.bind(this), 1000); + if ( this._ffz_wait_timer ) + clearTimeout(this._ffz_wait_timer); + this._ffz_wait_timer = setTimeout(this.ffzUpdateWait.bind(this), 1000); f._roomv && f._roomv.ffzUpdateStatus(); } else if ( (wait > 0 && value < 1) || was_banned ) { this.set('ffz_banned', false); @@ -823,13 +836,14 @@ FFZ.prototype._modify_room = function(room) { }, ffzUpdateWait: function() { + this._ffz_wait_timer = undefined; var wait = this.get('slowWait') || 0; if ( wait < 1 ) return; this.set('slowWait', --wait); if ( wait > 0 ) - setTimeout(this.ffzUpdateWait.bind(this), 1000); + this._ffz_wait_timer = setTimeout(this.ffzUpdateWait.bind(this), 1000); else { this.set('ffz_banned', false); f._roomv && f._roomv.ffzUpdateStatus(); @@ -968,6 +982,11 @@ FFZ.prototype._modify_room = function(room) { }, setHostMode: function(e) { + this.set('ffz_host_target', e && e.hostTarget || null); + var user = f.get_user(); + if ( user && f._cindex && this.get('id') === user.login ) + f._cindex.ffzUpdateHostButton(); + var Chat = App.__container__.lookup('controller:chat'); if ( ! Chat || Chat.get('currentChannelRoom') !== this ) return; @@ -1111,6 +1130,14 @@ FFZ.prototype._modify_room = function(room) { room.set('ffz_banned', true); f._roomv && f._roomv.ffzUpdateStatus(); } + + if ( msg.msgId === 'hosts_remaining' ) { + var match = /(\d+) host command/.exec(msg.message); + if ( match ) { + room.set('ffz_hosts_left', parseInt(match[1] || 0)); + f._cindex && f._cindex.ffzUpdateHostButton(); + } + } }); @@ -1149,6 +1176,9 @@ FFZ.prototype._modify_room = function(room) { // IT IS GLORIOUS! tmi.on('roomstate', function(state) { + if ( ! room ) + return; + if ( state.hasOwnProperty('slow') ) { room.set('slowMode', state.slow > 0); room.set('slowValue', state.slow); diff --git a/src/emoticons.js b/src/emoticons.js index c987081e..877d8567 100644 --- a/src/emoticons.js +++ b/src/emoticons.js @@ -270,12 +270,19 @@ FFZ.prototype.load_emoji_data = function(callback, tries) { FFZ.prototype.load_global_sets = function(callback, tries) { var f = this; - jQuery.getJSON(constants.API_SERVER + "v1/set/global") + jQuery.getJSON(((tries||0)%2 === 0 ? constants.API_SERVER : constants.API_SERVER_2) + "v1/set/global") .done(function(data) { f.default_sets = data.default_sets; var gs = f.global_sets = [], sets = data.sets || {}; + if ( f.feature_friday && f.feature_friday.set ) { + if ( f.global_sets.indexOf(f.feature_friday.set) === -1 ) + f.global_sets.push(f.feature_friday.set); + if ( f.default_sets.indexOf(f.feature_friday.set) === -1 ) + f.default_sets.push(f.feature_friday.set); + } + for(var key in sets) { if ( ! sets.hasOwnProperty(key) ) continue; @@ -300,7 +307,7 @@ FFZ.prototype.load_global_sets = function(callback, tries) { FFZ.prototype.load_set = function(set_id, callback, tries) { var f = this; - jQuery.getJSON(constants.API_SERVER + "v1/set/" + set_id) + jQuery.getJSON(((tries||0)%2 === 0 ? constants.API_SERVER : constants.API_SERVER_2) + "v1/set/" + set_id) .done(function(data) { f._load_set_json(set_id, callback, data && data.set); diff --git a/src/ext/betterttv.js b/src/ext/betterttv.js index 75578011..0015d7c1 100644 --- a/src/ext/betterttv.js +++ b/src/ext/betterttv.js @@ -31,7 +31,12 @@ FFZ.prototype.setup_bttv = function(delay) { document.body.classList.remove("ffz-dark"); if ( this._dark_style ) { this._dark_style.parentElement.removeChild(this._dark_style); - delete this._dark_style; + this._dark_style = undefined; + } + + if ( this._chat_style ) { + this._chat_style.parentElement.removeChild(this._chat_style); + this._chat_style = undefined; } // Disable Chat Tabs @@ -52,8 +57,17 @@ FFZ.prototype.setup_bttv = function(delay) { // Disable other features too. document.body.classList.remove("ffz-chat-colors"); document.body.classList.remove("ffz-chat-background"); + document.body.classList.remove("ffz-chat-padding"); + document.body.classList.remove("ffz-chat-separator"); document.body.classList.remove("ffz-sidebar-swap"); document.body.classList.remove("ffz-transparent-badges"); + document.body.classList.remove("ffz-high-contrast-chat"); + + // Remove Following Count + if ( this.settings.following_count ) { + this._schedule_following_count(); + this._draw_following_count(); + } // Remove Sub Count if ( this.is_dashboard ) diff --git a/src/main.js b/src/main.js index 2592a140..d0719422 100644 --- a/src/main.js +++ b/src/main.js @@ -21,7 +21,7 @@ FFZ.get = function() { return FFZ.instance; } // Version var VER = FFZ.version_info = { - major: 3, minor: 4, revision: 11, + major: 3, minor: 4, revision: 19, toString: function() { return [VER.major, VER.minor, VER.revision].join(".") + (VER.extra || ""); } @@ -140,6 +140,7 @@ require('./ui/sub_count'); require('./ui/menu_button'); require('./ui/following'); +require('./ui/following-count'); require('./ui/races'); require('./ui/my_emotes'); require('./ui/about_page'); @@ -160,6 +161,12 @@ FFZ.prototype.initialize = function(increment, delay) { this.setup_normal(delay); return; } + + if ( location.hostname === 'passport' && /^\/(?:authorize)/.test(location.pathname) ) { + this.log("Running on passport!"); + this.setup_normal(delay, true); + return; + } // Check for the dashboard. if ( /\/[^\/]+\/dashboard/.test(location.pathname) && !/bookmarks$/.test(location.pathname) ) { @@ -185,7 +192,7 @@ FFZ.prototype.initialize = function(increment, delay) { } -FFZ.prototype.setup_normal = function(delay) { +FFZ.prototype.setup_normal = function(delay, no_socket) { var start = (window.performance && performance.now) ? performance.now() : Date.now(); this.log("Found non-Ember Twitch after " + (delay||0) + " ms in \"" + location + "\". Initializing FrankerFaceZ version " + FFZ.version_info); @@ -201,11 +208,14 @@ FFZ.prototype.setup_normal = function(delay) { // Start this early, for quick loading. this.setup_dark(); - this.ws_create(); + if ( ! no_socket ) + this.ws_create(); + this.setup_emoticons(); this.setup_badges(); this.setup_notifications(); + this.setup_following_count(false); this.setup_css(); this.setup_menu(); @@ -293,6 +303,7 @@ FFZ.prototype.setup_ember = function(delay) { this.setup_menu(); this.setup_my_emotes(); this.setup_following(); + this.setup_following_count(true); this.setup_races(); this.connect_extra_chat(); diff --git a/src/settings.js b/src/settings.js index dd44b80b..b46bfa0c 100644 --- a/src/settings.js +++ b/src/settings.js @@ -58,19 +58,6 @@ FFZ.prototype.load_settings = function() { // Menu Page // -------------------- -FFZ.settings_info.replace_twitch_menu = { - type: "boolean", - value: false, - - name: "Replace Twitch Emoticon Menu", - help: "Completely replace the default Twitch emoticon menu.", - - on_update: function(val) { - document.body.classList.toggle("ffz-menu-replace", val); - } - }; - - FFZ.menu_pages.settings = { render: function(view, container) { var settings = {}, @@ -115,6 +102,9 @@ FFZ.menu_pages.settings = { return 0; }); + var f = this, + current_page = this._ffz_settings_page || categories[0]; + for(var ci=0; ci < categories.length; ci++) { var category = categories[ci], cset = settings[category], @@ -123,10 +113,28 @@ FFZ.menu_pages.settings = { heading = document.createElement('div'); heading.className = 'heading'; - menu.className = 'chat-menu-content'; + menu.className = 'chat-menu-content collapsable'; + + menu.setAttribute('data-category', category); + menu.classList.toggle('collapsed', current_page !== category); + heading.innerHTML = category; menu.appendChild(heading); + menu.addEventListener('click', function() { + if ( ! this.classList.contains('collapsed') ) + return; + + var t = this, + old_selection = container.querySelectorAll('.chat-menu-content:not(.collapsed)'); + for(var i=0; i < old_selection.length; i++) + old_selection[i].classList.add('collapsed'); + + f._ffz_settings_page = t.getAttribute('data-category'); + t.classList.remove('collapsed'); + setTimeout(function(){t.scrollIntoViewIfNeeded()}); + }); + cset.sort(function(a,b) { var a = a[1], b = b[1], diff --git a/src/socket.js b/src/socket.js index 9f1c95a8..de38cc6d 100644 --- a/src/socket.js +++ b/src/socket.js @@ -174,7 +174,13 @@ FFZ.prototype.ws_send = function(func, data, callback, can_wait) { if ( callback ) this._ws_callbacks[request] = callback; - this._ws_sock.send(request + " " + func + data); + try { + this._ws_sock.send(request + " " + func + data); + } catch(err) { + this.log("Socket Send Error: " + err); + return false; + } + return request; } diff --git a/src/tokenize.js b/src/tokenize.js index 43e9b1f9..345ac84f 100644 --- a/src/tokenize.js +++ b/src/tokenize.js @@ -33,6 +33,38 @@ FFZ.src_to_id = function(src) { }; +// --------------------- +// Time Format +// --------------------- + +var ts = new Date(0).toLocaleTimeString().toUpperCase(); + +FFZ.settings_info.twenty_four_timestamps = { + type: "boolean", + value: ts.lastIndexOf('PM') === -1 && ts.lastIndexOf('AM') === -1, + + category: "Chat Appearance", + no_bttv: true, + + name: "24hr Timestamps", + help: "Display timestamps in chat in the 24 hour format rather than 12 hour." + }; + + +if ( helpers ) + helpers.getTime = function(e) { + var hours = e.getHours(), + minutes = e.getMinutes(); + + if ( hours > 12 && ! FFZ.get().settings.twenty_four_timestamps ) + hours -= 12; + else if ( hours === 0 && ! FFZ.get().settings.twenty_four_timestamps ) + hours = 12; + + return hours + ':' + (minutes < 10 ? '0' : '') + minutes; + }; + + // --------------------- // Tokenization // --------------------- @@ -97,41 +129,44 @@ FFZ.prototype.tokenize_chat_line = function(msgObject, prevent_notification) { var room = this.rooms[room_id] && this.rooms[room_id].room, room_name; - if ( room && room.get('isGroupRoom') ) - room_name = room.get('tmiRoom.displayName'); - else - room_name = FFZ.get_capitalization(room_id); - - display = display || Twitch.display.capitalize(msgObject.from); - - if ( msgObject.style === 'action' ) - msg = '* ' + display + ' ' + msg; - else - msg = display + ': ' + msg; - - var f = this; - if ( msgObject.style === 'whisper' ) - this.show_notification( - msg, - "Twitch Chat Whisper", - "ffz_whisper_notice", - 60000, - function() { - window.focus(); - } + // Make sure we have UI for this channel. + if ( (this.settings.group_tabs && (this.settings.pinned_rooms.indexOf(room_id) !== -1 || this._chatv._ffz_host )) || room.get('isGroupRoom') || room === this._chatv.get('controller.currentChannelRoom') ) { + if ( room && room.get('isGroupRoom') ) + room_name = room.get('tmiRoom.displayName'); + else + room_name = FFZ.get_capitalization(room_id); + + display = display || Twitch.display.capitalize(msgObject.from); + + if ( msgObject.style === 'action' ) + msg = '* ' + display + ' ' + msg; + else + msg = display + ': ' + msg; + + var f = this; + if ( msgObject.style === 'whisper' ) + this.show_notification( + msg, + "Twitch Chat Whisper", + "ffz_whisper_notice", + 60000, + function() { + window.focus(); + } ); - else - this.show_notification( - msg, - "Twitch Chat Mention in " + room_name, - room_id, - 60000, - function() { - window.focus(); - var cont = App.__container__.lookup('controller:chat'); - room && cont && cont.focusRoom(room); - } + else + this.show_notification( + msg, + "Twitch Chat Mention in " + room_name, + room_id, + 60000, + function() { + window.focus(); + var cont = App.__container__.lookup('controller:chat'); + room && cont && cont.focusRoom(room); + } ); + } } break; diff --git a/src/ui/dark.js b/src/ui/dark.js index 72542ec4..3bc45f11 100644 --- a/src/ui/dark.js +++ b/src/ui/dark.js @@ -20,10 +20,15 @@ FFZ.settings_info.dark_twitch = { no_bttv: true, //visible: function() { return ! this.has_bttv }, + category: "Appearance", name: "Dark Twitch", help: "Apply a dark background to channels and other related pages for easier viewing.", on_update: function(val) { + var cb = document.querySelector('input.ffz-setting-dark-twitch'); + if ( cb ) + cb.checked = val; + if ( this.has_bttv ) return; @@ -41,11 +46,46 @@ FFZ.settings_info.dark_twitch = { }; +FFZ.settings_info.dark_no_blue = { + type: "boolean", + value: false, + + //no_bttv: true, + + category: "Appearance", + name: "Gray Chat (no blue)", + help: "Make the dark theme for chat and a few other places on Twitch a bit darker and not at all blue.", + + on_update: function(val) { + document.body.classList.toggle("ffz-no-blue", val); + } + }; + + +FFZ.settings_info.hide_recent_past_broadcast = { + type: "boolean", + value: false, + + //no_bttv: true, + + category: "Channel Metadata", + name: "Hide \"Watch Last Broadcast\"", + help: "Hide the \"Watch Last Broadcast\" banner at the top of offline Twitch channels.", + + on_update: function(val) { + document.body.classList.toggle("ffz-hide-recent-past-broadcast", val); + } + }; + + // --------------------- // Initialization // --------------------- FFZ.prototype.setup_dark = function() { + document.body.classList.toggle("ffz-hide-recent-past-broadcast", this.settings.hide_recent_past_broadcast); + document.body.classList.toggle("ffz-no-blue", this.settings.dark_no_blue); + if ( this.has_bttv ) return; diff --git a/src/ui/following-count.js b/src/ui/following-count.js new file mode 100644 index 00000000..183aab6d --- /dev/null +++ b/src/ui/following-count.js @@ -0,0 +1,155 @@ +var FFZ = window.FrankerFaceZ, + utils = require('../utils'); + + + +FFZ.settings_info.following_count = { + type: "boolean", + value: true, + + no_bttv: true, + + category: "Appearance", + name: "Sidebar Following Count", + help: "Display the number of live channels you're following on the sidebar.", + + on_update: function(val) { + this._schedule_following_count(); + + var Stream = window.App && App.__container__.resolve('model:stream'), + Live = Stream && Stream.find("live"); + + if ( Live ) + this._draw_following_count(Live.get('total') || 0); + else + this._update_following_count(); + } + }; + +// --------------- +// Initialization +// --------------- + +FFZ.prototype.setup_following_count = function(has_ember) { + // Start it updating. + if ( this.settings.following_count ) + this._schedule_following_count(); + + // If we don't have Ember, no point in trying this stuff. + if ( ! has_ember ) + return this._update_following_count(); + + this.log("Connecting to Live Streams model."); + var Stream = window.App && App.__container__.resolve('model:stream'); + if ( ! Stream ) + return this.log("Unable to find Stream model."); + + var Live = Stream.find("live"), + f = this; + + if ( ! Live ) + return this.log("Unable to find Live Streams collection."); + + Live.addObserver('total', function() { f._draw_following_count(this.get('total')); }); + Live.load(); + + var total = Live.get('total'); + if ( typeof total === "number" ) + this._draw_following_count(total); +} + + +FFZ.prototype._schedule_following_count = function() { + if ( this.has_bttv || ! this.settings.following_count ) { + if ( this._following_count_timer ) { + clearTimeout(this._following_count_timer); + this._following_count_timer = undefined; + } + return; + } + + if ( ! this._following_count_timer ) + this._following_count_timer = setTimeout(this._update_following_count.bind(this), 60000); +} + + +FFZ.prototype._update_following_count = function() { + if ( ! this.settings.following_count ) { + if ( this._following_count_timer ) { + clearTimeout(this._following_count_timer); + this._following_count_timer = undefined; + } + return; + } + + this._following_count_timer = setTimeout(this._update_following_count.bind(this), 60000); + + var Stream = window.App && App.__container__.resolve('model:stream'), + Live = Stream && Stream.find("live"), + f = this; + + if ( Live ) + Live.load(); + else + Twitch.api.get("streams/followed", {limit:1, offset:0}, {version:3}) + .done(function(data) { + f._draw_following_count(data._total); + }).fail(function() { + f._draw_following_count(); + }) +} + + +FFZ.prototype._draw_following_count = function(count) { + // Small + var small_following = document.querySelector('#small_nav ul.game_filters li[data-name="following"] a'); + if ( small_following ) { + var badge = small_following.querySelector('.ffz-follow-count'); + if ( this.has_bttv || ! this.settings.following_count ) { + if ( badge ) + badge.parentElement.removeChild(badge); + } else { + if ( ! badge ) { + badge = document.createElement('span'); + badge.className = 'ffz-follow-count'; + small_following.appendChild(badge); + } + badge.innerHTML = count ? utils.format_unread(count) : ''; + } + } + + + // Large + var large_following = document.querySelector('#large_nav #nav_personal li[data-name="following"] a'); + if ( large_following ) { + var badge = large_following.querySelector('.ffz-follow-count'); + if ( this.has_bttv || ! this.settings.following_count ) { + if ( badge ) + badge.parentElement.removeChild(badge); + } else { + if ( ! badge ) { + badge = document.createElement('span'); + badge.className = 'ffz-follow-count'; + large_following.appendChild(badge); + } + badge.innerHTML = count ? utils.format_unread(count) : ''; + } + } + + // Heading + var head_following = document.querySelector('#header_actions #header_following'); + if ( head_following ) { + var badge = head_following.querySelector('.ffz-follow-count'); + if ( this.has_bttv || ! this.settings.following_count ) { + if ( badge ) + badge.parentElement.removeChild(badge); + } else { + if ( ! badge ) { + badge = document.createElement('span'); + badge.className = 'ffz-follow-count'; + head_following.appendChild(badge); + } + badge.innerHTML = count ? utils.format_unread(count) : ''; + } + } +} \ No newline at end of file diff --git a/src/ui/following.js b/src/ui/following.js index 48208cb2..931ad605 100644 --- a/src/ui/following.js +++ b/src/ui/following.js @@ -1,11 +1,5 @@ var FFZ = window.FrankerFaceZ, - utils = require('../utils'), - - EMOTE_CHANNELS = { - sirstendec: true, - europeanspeedsterassembly: true, - esamarathon2: true - }; + utils = require('../utils'); // --------------- diff --git a/src/ui/menu.js b/src/ui/menu.js index 598f356c..2a8f27e5 100644 --- a/src/ui/menu.js +++ b/src/ui/menu.js @@ -33,7 +33,7 @@ FFZ.prototype.setup_menu = function() { this.log("Hooking the Ember Chat Settings view."); - var Settings = App.__container__.resolve('view:settings'); + var Settings = window.App && App.__container__.resolve('view:settings'); if ( ! Settings ) return; @@ -91,23 +91,20 @@ FFZ.prototype.setup_menu = function() { }); - // Minimal Chat - /* + // Channel Hosting p = document.createElement('p'); //p.className = 'no-bttv'; cb = document.createElement('input'); cb.type = "checkbox"; - cb.className = "ember-checkbox ffz-setting-minimal-twitch"; - cb.checked = f.settings.minimal_chat; + cb.className = "ember-checkbox ffz-setting-hosted-channels"; + cb.checked = f.settings.hosted_channels; p.appendChild(cb); - p.appendChild(document.createTextNode("Minimalistic Chat")); + p.appendChild(document.createTextNode("Channel Hosting")); content.appendChild(p); cb.addEventListener("change", function(e) { - f.settings.set("minimal_chat", this.checked); - if ( this.checked ) - view.set('controller.model.hidden', true); - });*/ + f.settings.set("hosted_channels", this.checked); + }); // More Settings diff --git a/src/ui/my_emotes.js b/src/ui/my_emotes.js index bad78d75..2e1abb96 100644 --- a/src/ui/my_emotes.js +++ b/src/ui/my_emotes.js @@ -10,10 +10,27 @@ var FFZ = window.FrankerFaceZ, // Initialization // ------------------- +FFZ.settings_info.replace_twitch_menu = { + type: "boolean", + value: false, + + category: "Chat Input", + + name: "Replace Twitch Emoticon Menu", + help: "Completely replace the default Twitch emoticon menu.", + + on_update: function(val) { + document.body.classList.toggle("ffz-menu-replace", val); + } + }; + + FFZ.settings_info.global_emotes_in_menu = { type: "boolean", value: false, + category: "Chat Input", + name: "Display Global Emotes in My Emotes", help: "Display the global Twitch emotes in the My Emoticons menu." }; @@ -23,11 +40,19 @@ FFZ.settings_info.emoji_in_menu = { type: "boolean", value: false, + category: "Chat Input", + name: "Display Emoji in My Emotes", help: "Display the supported emoji images in the My Emoticons menu." }; +FFZ.settings_info.emote_menu_collapsed = { + value: [], + visible: false +} + + FFZ.prototype.setup_my_emotes = function() { this._twitch_set_to_channel = {}; this._twitch_badges = {}; @@ -114,16 +139,36 @@ FFZ.menu_pages.my_emotes = { setTimeout(fail, 2000); }, + toggle_section: function(heading) { + var menu = heading.parentElement, + set_id = menu.getAttribute('data-set'), + collapsed_list = this.settings.emote_menu_collapsed, + is_collapsed = collapsed_list.indexOf(set_id) !== -1; + + if ( is_collapsed ) + collapsed_list.removeObject(set_id); + else + collapsed_list.push(set_id); + + this.settings.set('emote_menu_collapsed', collapsed_list); + menu.classList.toggle('collapsed', !is_collapsed); + }, + draw_emoji: function(view) { var heading = document.createElement('div'), - menu = document.createElement('div'); + menu = document.createElement('div'), + f = this; heading.className = 'heading'; heading.innerHTML = 'FrankerFaceZEmoji'; - menu.className = 'emoticon-grid'; + menu.className = 'emoticon-grid collapsable'; menu.appendChild(heading); + menu.setAttribute('data-set', 'emoji'); + menu.classList.toggle('collapsed', this.settings.emote_menu_collapsed.indexOf('emoji') !== -1); + heading.addEventListener('click', function() { FFZ.menu_pages.my_emotes.toggle_section.bind(f)(this); }); + var set = []; for(var eid in this.emoji_data) set.push(this.emoji_data[eid]); @@ -163,6 +208,7 @@ FFZ.menu_pages.my_emotes = { draw_twitch_set: function(view, set_id, set) { var heading = document.createElement('div'), menu = document.createElement('div'), + f = this, channel_id = this._twitch_set_to_channel[set_id], title; @@ -192,8 +238,12 @@ FFZ.menu_pages.my_emotes = { }); } - menu.className = 'emoticon-grid'; + menu.className = 'emoticon-grid collapsable'; menu.appendChild(heading); + + menu.setAttribute('data-set', 'twitch-' + set_id); + menu.classList.toggle('collapsed', this.settings.emote_menu_collapsed.indexOf('twitch-' + set_id) !== -1); + heading.addEventListener('click', function() { FFZ.menu_pages.my_emotes.toggle_section.bind(f)(this); }); set.sort(function(a,b) { var an = a.code.toLowerCase(), @@ -236,14 +286,19 @@ FFZ.menu_pages.my_emotes = { draw_ffz_set: function(view, set) { var heading = document.createElement('div'), menu = document.createElement('div'), + f = this, emotes = []; heading.className = 'heading'; heading.innerHTML = 'FrankerFaceZ' + set.title; heading.style.backgroundImage = 'url("' + (set.icon || '//cdn.frankerfacez.com/script/devicon.png') + '")'; - menu.className = 'emoticon-grid'; + menu.className = 'emoticon-grid collapsable'; menu.appendChild(heading); + + menu.setAttribute('data-set', 'ffz-' + set.id); + menu.classList.toggle('collapsed', this.settings.emote_menu_collapsed.indexOf('ffz-' + set.id) !== -1); + heading.addEventListener('click', function() { FFZ.menu_pages.my_emotes.toggle_section.bind(f)(this); }); for(var emote_id in set.emoticons) set.emoticons.hasOwnProperty(emote_id) && ! set.emoticons[emote_id].hidden && emotes.push(set.emoticons[emote_id]); @@ -339,11 +394,15 @@ FFZ.menu_pages.my_emotes = { if ( an === "turbo" || an === "turbo_faces" ) an = "zza|" + an; else if ( an === "global" || an === "global emoticons" ) + an = "zzy|" + an; + else if ( an === "emoji" ) an = "zzz|" + an; if ( bn === "turbo" || bn === "turbo_faces" ) bn = "zza|" + bn; else if ( bn === "global" || bn === "global emoticons" ) + bn = "zzy|" + bn; + else if ( bn === "emoji" ) bn = "zzz|" + bn; if ( an < bn ) return -1; diff --git a/src/ui/notifications.js b/src/ui/notifications.js index d5e7774b..8f90ed4d 100644 --- a/src/ui/notifications.js +++ b/src/ui/notifications.js @@ -19,7 +19,7 @@ FFZ.settings_info.highlight_notifications = { type: "boolean", value: false, - category: "Chat", + category: "Chat Filtering", no_bttv: true, //visible: function() { return ! this.has_bttv }, diff --git a/src/utils.js b/src/utils.js index 96e77f1d..5255e8e2 100644 --- a/src/utils.js +++ b/src/utils.js @@ -259,27 +259,28 @@ module.exports = { pluralize: pluralize, - human_time: function(elapsed) { + human_time: function(elapsed, factor) { + factor = factor || 1; elapsed = Math.floor(elapsed); - var years = Math.floor(elapsed / 31536000); - if ( years ) + var years = Math.floor((elapsed*factor) / 31536000) / factor; + if ( years >= 1 ) return years + ' year' + pluralize(years); var days = Math.floor((elapsed %= 31536000) / 86400); - if ( days ) + if ( days >= 1 ) return days + ' day' + pluralize(days); var hours = Math.floor((elapsed %= 86400) / 3600); - if ( hours ) + if ( hours >= 1 ) return hours + ' hour' + pluralize(hours); var minutes = Math.floor((elapsed %= 3600) / 60); - if ( minutes ) + if ( minutes >= 1 ) return minutes + ' minute' + pluralize(minutes); var seconds = elapsed % 60; - if ( seconds ) + if ( seconds >= 1 ) return seconds + ' second' + pluralize(seconds); return 'less than a second'; @@ -303,5 +304,15 @@ module.exports = { } return days + ((!no_hours || days || hours) ? ((hours < 10 ? "0" : "") + hours + ':') : '') + (minutes < 10 ? "0" : "") + minutes + ":" + (seconds < 10 ? "0" : "") + seconds; + }, + + format_unread: function(count) { + if ( count < 1 ) + return ""; + + else if ( count >= 99 ) + return "99+"; + + return "" + count; } } \ No newline at end of file diff --git a/style.css b/style.css index 1756c2ef..a4c09217 100644 --- a/style.css +++ b/style.css @@ -12,6 +12,7 @@ cursor: pointer; } +.ffz-hide-recent-past-broadcast .recent-past-broadcast, .ffz-hide-view-count .stat.twitch-channel-views, .ffz-minimal-chat .emoticon-selector-toggle, .ffz-menu-replace .emoticon-selector-toggle { @@ -201,10 +202,21 @@ body:not(.ffz-minimal-chat):not(.ffz-menu-replace) .emoticon-selector-toggle + s height: 20px; } +.ffz-sidebar-swap .app-main.theatre #channel .player-column:focus #broadcast-meta, +.ffz-sidebar-swap .app-main.theatre #channel .player-column:hover #broadcast-meta { + left: 145px; +} + .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; + color: #dedede; +} + +.app-main.theatre #channel .player-column #broadcast-meta .info .title, +.app-main.theatre #channel .player-column #broadcast-meta .info .title .over { + background-color: rgba(16,16,16,0.3); } .app-main.theatre #channel .player-column #broadcast-meta .info .channel, @@ -385,6 +397,54 @@ body:not(.ffz-minimal-chat):not(.ffz-menu-replace) .emoticon-selector-toggle + s /* Menu Options */ +.emoticon-grid.collapsed span, +.chat-menu-content.collapsed p { display: none; } + +.chat-menu.ffz-ui-popup .ffz-ui-menu-page .chat-menu-content.collapsed .heading, +.chat-menu.ffz-ui-popup .ffz-ui-menu-page .emoticon-grid.collapsed .heading { + padding-bottom: 0; +} + +.emoticon-grid.collapsable .heading, +.emoticon-grid.collapsed, +.chat-menu-content.collapsed { + cursor: pointer; +} + +.emoticon-grid.collapsable .heading, +.chat-menu-content.collapsable .heading { + position: relative; +} + +.chat-menu-content.collapsable .heading span.right { + padding-right: 15px; +} + +.emoticon-grid.collapsable .heading:before, +.chat-menu-content.collapsable .heading:before { + content: ""; + border: 5px solid #666; + border-left-color: transparent; + border-right-color: transparent; + + position: absolute; + margin-top: 6px; + right: 20px; +} + +.emoticon-grid.collapsable.collapsed .heading:before, +.chat-menu-content.collapsable.collapsed .heading:before { border-bottom-color: transparent; } + +.emoticon-grid.collapsable:not(.collapsed) .heading:before { + display: none; +} + +.chat-menu-content.collapsable:not(.collapsed) .heading:before { + border-top-color: transparent; + margin-top: 1px; +} + + #small_nav .content ul li#ffz_small_menu .filter_icon svg { margin: 11px 13px; } @@ -621,6 +681,38 @@ body:not(.ffz-minimal-chat):not(.ffz-menu-replace) .emoticon-selector-toggle + s /*box-shadow: #808080 0 0 5px;*/ } +.ember-chat .ffz-moderation-card .extra-interface { + padding-top: 0; +} + +.ember-chat .ffz-moderation-card .extra-interface + .extra-interface { + margin-top: -10px; +} + +.ember-chat .ffz-moderation-card.ffz-has-info h3.name { + margin-top: 0; +} + +.ember-chat .ffz-moderation-card .info { + float: none; + position: relative; + z-index: 4; + margin-left: 50px; + height: 18px; + line-height: 18px; +} + +.ember-chat .ffz-moderation-card .info.channel-stats .stat { + color: #fff; +} + +.ember-chat .ffz-moderation-card .info.channel-stats .stat svg { + margin: 1px 5px 1px 0; + pointer-events: none; +} + +.ember-chat .ffz-moderation-card .info svg path { fill: #fff; } + .ember-chat .ffz-moderation-card button { margin: 0; padding: 0 5px; @@ -649,15 +741,16 @@ body:not(.ffz-minimal-chat):not(.ffz-menu-replace) .emoticon-selector-toggle + s .ember-chat .ffz-moderation-card .interface:not(:last-of-type) { border-bottom: none; - padding-bottom: 0; } .ember-chat .ffz-moderation-card .interface { border-top: none; } +.ember-chat .ffz-moderation-card h3.name { display: inline-block; } + +.ember-chat .ffz-moderation-card .info, .ember-chat .ffz-moderation-card h3.name { - display: inline-block; text-shadow: black 0 0 5px; } @@ -667,6 +760,13 @@ body:not(.ffz-minimal-chat):not(.ffz-menu-replace) .emoticon-selector-toggle + s } +body:not(.ffz-chat-purge-icon) .ember-chat .mod-icons .purge { display: none; } + +.ember-chat .mod-icons .purge { + background-image: url('//cdn.frankerfacez.com/script/PurgeButton.svg'); + background-repeat: no-repeat; +} + /* Chat Rows */ .ember-chat .chat-messages .chat-line.ffz-has-deleted { @@ -674,7 +774,7 @@ body:not(.ffz-minimal-chat):not(.ffz-menu-replace) .emoticon-selector-toggle + s } .chat-line.ffz-deleted > span { - opacity: 0.15; + opacity: 0.5; } .chat-line.ffz-deleted > span.message { @@ -682,7 +782,7 @@ body:not(.ffz-minimal-chat):not(.ffz-menu-replace) .emoticon-selector-toggle + s } .chat-line.ffz-deleted:hover > span { - opacity: 0.5; + opacity: 0.9; } .chat-line.ffz-deleted:hover > span.message { @@ -708,47 +808,88 @@ body:not(.ffz-minimal-chat):not(.ffz-menu-replace) .emoticon-selector-toggle + s margin: 0px 0px; } */ -.ffz-chat-background .chat-history .chat-line.ffz-alternate, -.ffz-chat-background .ember-chat .chat-messages .chat-line.ffz-alternate { +.ffz-chat-separator .chat-line, +.ffz-chat-background .chat-line { + position: relative; + z-index: 1; +} + +.ffz-chat-padding .ember-chat .chat-messages .chat-line, +.ffz-chat-padding .ember-chat .chat-messages .chat-line.admin { + padding: 5px; +} + +.ffz-chat-separator .chat-line:before, +.ffz-chat-background .chat-line:before { + content: ""; + position: absolute; + z-index: -1; + left: 0; right: 0; + top: 2px; bottom: 1px; +} + +.ffz-chat-background .chat-history .chat-line:before { + top: 0; bottom: 0; +} + +.ffz-chat-separator .chat-line:before { + border-bottom: 1px solid #aaa; +} + +.ffz-chat-separator ul.chat-lines div:last-of-type .chat-line:not(.ffz-alternate):before { + border-bottom: none; +} + +.ffz-chat-separator .app-main.theatre .chat-line:before, +.ffz-chat-separator .chat-container.dark .chat-line:before, +.ffz-chat-separator .chat-container.force-dark .chat-line:before, +.ffz-chat-separator .ember-chat-container.dark .chat-line:before, +.ffz-chat-separator .ember-chat-container.force-dark .chat-line:before { + border-bottom-color: #000; +} + + +.ffz-chat-background .chat-history .chat-line.ffz-alternate:before, +.ffz-chat-background .ember-chat .chat-messages .chat-line.ffz-alternate:before { background-color: rgba(0,0,0, 0.1); } -.ffz-chat-background .chat-history .chat-line.ffz-mentioned, -.ffz-chat-background .ember-chat .chat-messages .chat-line.ffz-mentioned { +.ffz-chat-background .chat-history .chat-line.ffz-mentioned:before, +.ffz-chat-background .ember-chat .chat-messages .chat-line.ffz-mentioned:before { background-color: rgba(255,127,127, 0.2); } -.ffz-chat-background .chat-history .chat-line.ffz-mentioned-ffz-alternate, -.ffz-chat-background .ember-chat .chat-messages .chat-line.ffz-mentioned.ffz-alternate { +.ffz-chat-background .chat-history .chat-line.ffz-mentioned-ffz-alternate:before, +.ffz-chat-background .ember-chat .chat-messages .chat-line.ffz-mentioned.ffz-alternate:before { background-color: rgba(255,127,127, 0.4); } -.ffz-chat-background .app-main.theatre .chat-history .chat-line.ffz-alternate, -.ffz-chat-background .chat-container.dark .chat-history .chat-line.ffz-alternate, -.ffz-chat-background .ember-chat-container.dark .chat-history .chat-line.ffz-alternate, -.ffz-chat-background .app-main.theatre .ember-chat .chat-messages .chat-line.ffz-alternate, -.ffz-chat-background .chat-container.dark .ember-chat .chat-messages .chat-line.ffz-alternate, -.ffz-chat-background .ember-chat-container.dark .ember-chat .chat-messages .chat-line.ffz-alternate { +.ffz-chat-background .app-main.theatre .chat-history .chat-line.ffz-alternate:before, +.ffz-chat-background .chat-container.dark .chat-history .chat-line.ffz-alternate:before, +.ffz-chat-background .ember-chat-container.dark .chat-history .chat-line.ffz-alternate:before, +.ffz-chat-background .app-main.theatre .ember-chat .chat-messages .chat-line.ffz-alternate:before, +.ffz-chat-background .chat-container.dark .ember-chat .chat-messages .chat-line.ffz-alternate:before, +.ffz-chat-background .ember-chat-container.dark .ember-chat .chat-messages .chat-line.ffz-alternate:before { background-color: rgba(255,255,255, 0.05); } -.ffz-chat-background .app-main.theatre .chat-history .chat-line.ffz-mentioned, -.ffz-chat-background .chat-container.dark .chat-history .chat-line.ffz-mentioned, -.ffz-chat-background .ember-chat-container.dark .chat-history .chat-line.ffz-mentioned, -.ffz-chat-background .app-main.theatre .ember-chat .chat-messages .chat-line.ffz-mentioned, -.ffz-chat-background .chat-container.dark .ember-chat .chat-messages .chat-line.ffz-mentioned, -.ffz-chat-background .ember-chat-container.dark .ember-chat .chat-messages .chat-line.ffz-mentioned { +.ffz-chat-background .app-main.theatre .chat-history .chat-line.ffz-mentioned:before, +.ffz-chat-background .chat-container.dark .chat-history .chat-line.ffz-mentioned:before, +.ffz-chat-background .ember-chat-container.dark .chat-history .chat-line.ffz-mentioned:before, +.ffz-chat-background .app-main.theatre .ember-chat .chat-messages .chat-line.ffz-mentioned:before, +.ffz-chat-background .chat-container.dark .ember-chat .chat-messages .chat-line.ffz-mentioned:before, +.ffz-chat-background .ember-chat-container.dark .ember-chat .chat-messages .chat-line.ffz-mentioned:before { background-color: rgba(255,0,0, 0.2); } -.ffz-chat-background .app-main.theatre .chat-history .chat-line.ffz-mentioned.ffz-alternate, -.ffz-chat-background .chat-container.dark .chat-history .chat-line.ffz-mentioned.ffz-alternate, -.ffz-chat-background .ember-chat-container.dark .chat-history .chat-line.ffz-mentioned.ffz-alternate, -.ffz-chat-background .app-main.theatre .ember-chat .chat-messages .chat-line.ffz-mentioned.ffz-alternate, -.ffz-chat-background .chat-container.dark .ember-chat .chat-messages .chat-line.ffz-mentioned.ffz-alternate, -.ffz-chat-background .ember-chat-container.dark .ember-chat .chat-messages .chat-line.ffz-mentioned.ffz-alternate { +.ffz-chat-background .app-main.theatre .chat-history .chat-line.ffz-mentioned.ffz-alternate:before, +.ffz-chat-background .chat-container.dark .chat-history .chat-line.ffz-mentioned.ffz-alternate:before, +.ffz-chat-background .ember-chat-container.dark .chat-history .chat-line.ffz-mentioned.ffz-alternate:before, +.ffz-chat-background .app-main.theatre .ember-chat .chat-messages .chat-line.ffz-mentioned.ffz-alternate:before, +.ffz-chat-background .chat-container.dark .ember-chat .chat-messages .chat-line.ffz-mentioned.ffz-alternate:before, +.ffz-chat-background .ember-chat-container.dark .ember-chat .chat-messages .chat-line.ffz-mentioned.ffz-alternate:before { background-color: rgba(255,0,0, 0.3); } @@ -760,25 +901,25 @@ body:not(.ffz-minimal-chat):not(.ffz-menu-replace) .emoticon-selector-toggle + s border-left-width: 4px !important; } -.ffz-chat-background .app-main.theatre .ember-chat .chat-messages .whisper-line.whisper-incoming, -.ffz-chat-background .chat-container.dark .ember-chat .chat-messages .whisper-line.whisper-incoming, -.ffz-chat-background .ember-chat-container.dark .ember-chat .chat-messages .whisper-line.whisper-incoming { +.ffz-chat-background .app-main.theatre .ember-chat .chat-messages .whisper-line.whisper-incoming:before, +.ffz-chat-background .chat-container.dark .ember-chat .chat-messages .whisper-line.whisper-incoming:before, +.ffz-chat-background .ember-chat-container.dark .ember-chat .chat-messages .whisper-line.whisper-incoming:before { /* 675980 */ background-color: rgba(78,51,128, 0.4); } -.ffz-chat-background .app-main.theatre .ember-chat .chat-messages .whisper-line.whisper-incoming.ffz-alternate, -.ffz-chat-background .chat-container.dark .ember-chat .chat-messages .whisper-line.whisper-incoming.ffz-alternate, -.ffz-chat-background .ember-chat-container.dark .ember-chat .chat-messages .whisper-line.whisper-incoming.ffz-alternate { +.ffz-chat-background .app-main.theatre .ember-chat .chat-messages .whisper-line.whisper-incoming.ffz-alternate:before, +.ffz-chat-background .chat-container.dark .ember-chat .chat-messages .whisper-line.whisper-incoming.ffz-alternate:before, +.ffz-chat-background .ember-chat-container.dark .ember-chat .chat-messages .whisper-line.whisper-incoming.ffz-alternate:before { /* 675980 */ background-color: rgba(78,51,128, 0.5); } -.ffz-chat-background .ember-chat .chat-messages .whisper-line.whisper-incoming { +.ffz-chat-background .ember-chat .chat-messages .whisper-line.whisper-incoming:before { background-color: rgba(205,178,255, 0.4); } -.ffz-chat-background .ember-chat .chat-messages .whisper-line.whisper-incoming.ffz-alternate { +.ffz-chat-background .ember-chat .chat-messages .whisper-line.whisper-incoming.ffz-alternate:before { background-color: rgba(205,178,255, 0.6); } @@ -892,7 +1033,101 @@ a.unsafe-link { color: #d28e8e !important; } -/* Group Tabs */ +/* Chat Menu */ + +.ffz-room-list > div.ffz + ul.room-list { display: block !important; } + +.ffz-room-list > div:not(.ffz), +.ffz-room-list > ul:not(.ffz) { + display: none !important; +} + +.ffz-room-list > table { + padding: 15px 0 0; +} + +.ffz-room-list > table + table { + margin-top: 10px; +} + +.ffz-room-list > table th { + padding: 2px 5px; + color: #8c8c8c; + font-weight: normal; + text-transform: uppercase; +} + +.ffz-room-list > table > tbody tr { + line-height: 26px; +} + +.ffz-room-list > table td { + padding: 2px 0; + text-align: center; +} + +.ffz-room-list > table th:first-child, +.ffz-room-list > table td:nth-child(0n+2) { + width: 100%; + text-align: left; +} + +.ffz-room-row { + cursor: pointer; +} + +.ffz-room-list > table th:first-child, +.ffz-room-list > table td:first-child { + padding-left: 18px; +} + +.ffz-room-list > table th:last-child, +.ffz-room-list > table td:last-child { + padding-right: 18px; +} + +.ffz-room-list td svg { + margin: 5px; + float: left; +} + +.ffz-dark .ffz-room-row { color: #a68ed2; } +.ffz-dark .ffz-room-row svg path { fill: #a68ed2; } + +.ffz-room-row { color: #6441a5; } +.ffz-room-row svg path { fill: #6441a5; } + +.ffz-room-row:hover svg path, +.ffz-room-row:focus svg path, +.ffz-room-row.active svg path { fill: #fff; } + +.ffz-room-row:hover td, +.ffz-room-row:focus td, +.ffz-room-row.active td { + background-color: #6441A5; + color: #fff !important; +} + +th.ffz-row-switch { + min-width: 40px; +} + +.ffz-room-row a.leave-chat { + float: right; + margin-right: 12px; +} + +.ffz-row-switch .switch { + float: none; + margin: 5px 0 -4px; +} + +.ffz-row-switch .switch.active { + background-color: #362359; +} + + +/* Chat Tabs */ #ffz-group-tabs { padding: 10px 10px 6px; @@ -1063,8 +1298,7 @@ body.ffz-minimal-chat .ember-chat .chat-interface .textarea-contain textarea { .ember-chat .chat-interface .more-messages-indicator.ffz-freeze-indicator { opacity: 1; cursor: default; - padding: 2px 0; - top: -21px; + top: 0; } /* Chat History */ @@ -1172,6 +1406,20 @@ body.ffz-minimal-chat .ember-chat .chat-interface .textarea-contain textarea { margin-right: 240px; } +.ffz-sidebar-swap .app-main.theatre #main_col { + margin-left: 340px; + margin-right: 0px; +} + +.ffz-sidebar-swap .app-main.theatre #main_col.expandRight { + margin-left: 0px; +} + + +.ffz-sidebar-swap .exit-theatre { + left: 30px; +} + .ffz-sidebar-swap #main_col.expandLeft { margin-right: 50px; } @@ -1224,8 +1472,189 @@ body.ffz-minimal-chat .ember-chat .chat-interface .textarea-contain textarea { background-color: transparent !important; } +.ffz-transparent-badges > .chat-container:not(.dark):not(.force-dark) .ember-chat .badges .badge:not(.ffz-badge-0):not(.subscriber), +.ffz-transparent-badges > .ember-chat-container:not(.dark):not(.force-dark) .ember-chat .badges .badge:not(.ffz-badge-0):not(.subscriber), .ffz-transparent-badges .app-main:not(.theatre) .chat-container:not(.dark):not(.force-dark) .ember-chat .badges .badge:not(.ffz-badge-0):not(.subscriber), .ffz-transparent-badges .app-main:not(.theatre) .ember-chat-container:not(.dark):not(.force-dark) .ember-chat .badges .badge:not(.ffz-badge-0):not(.subscriber) { filter: invert(100%); -webkit-filter: invert(100%); +} + +/* No Blue */ + +.ffz-no-blue #large_nav .content, +.ffz-no-blue #small_nav .content, +.ffz-no-blue .chat-container.dark, +.ffz-no-blue .app-main.theatre .chat-container, +.ffz-no-blue .chat-container.force-dark, +.ffz-no-blue .ember-chat-container.dark, +.ffz-no-blue .app-main.theatre .ember-chat-container.chat-container, +.ffz-no-blue .ember-chat-container.force-dark, +.ffz-no-blue .chat-container.dark .chat-hidden-overlay, +.ffz-no-blue .app-main.theatre .chat-container .chat-hidden-overlay, +.ffz-no-blue .chat-container.force-dark .chat-hidden-overlay, +.ffz-no-blue .ember-chat-container.dark .chat-hidden-overlay, +.ffz-no-blue .app-main.theatre .ember-chat-container.chat-container .chat-hidden-overlay, +.ffz-no-blue .ember-chat-container.force-dark .chat-hidden-overlay, +.ffz-no-blue .chat-container.dark .chatters-view, +.ffz-no-blue .app-main.theatre .chat-container .chatters-view, +.ffz-no-blue .chat-container.force-dark .chatters-view, +.ffz-no-blue .ember-chat-container.dark .chatters-view, +.ffz-no-blue .app-main.theatre .ember-chat-container.chat-container .chatters-view, +.ffz-no-blue .ember-chat-container.force-dark .chatters-view, +.ffz-no-blue .chat-container.dark .emoticon-selector .emoticon-selector-box, +.ffz-no-blue .app-main.theatre .chat-container .emoticon-selector .emoticon-selector-box, +.ffz-no-blue .chat-container.force-dark .emoticon-selector .emoticon-selector-box, +.ffz-no-blue .ember-chat-container.dark .emoticon-selector .emoticon-selector-box, +.ffz-no-blue .app-main.theatre .ember-chat-container.chat-container .emoticon-selector .emoticon-selector-box, +.ffz-no-blue .ember-chat-container.force-dark .emoticon-selector .emoticon-selector-box, +.ffz-no-blue .chat-container.dark .emoticon-selector .emoticon-grid, +.ffz-no-blue .app-main.theatre .chat-container .emoticon-selector .emoticon-grid, +.ffz-no-blue .chat-container.force-dark .emoticon-selector .emoticon-grid, +.ffz-no-blue .ember-chat-container.dark .emoticon-selector .emoticon-grid, +.ffz-no-blue .app-main.theatre .ember-chat-container.chat-container .emoticon-selector .emoticon-grid, +.ffz-no-blue .ember-chat-container.force-dark .emoticon-selector .emoticon-grid, +.ffz-no-blue .chat-container.dark .chat-commands-dropdown, +.ffz-no-blue .app-main.theatre .chat-container .chat-commands-dropdown, +.ffz-no-blue .chat-container.force-dark .chat-commands-dropdown, +.ffz-no-blue .ember-chat-container.dark .chat-commands-dropdown, +.ffz-no-blue .app-main.theatre .ember-chat-container.chat-container .chat-commands-dropdown, +.ffz-no-blue .ember-chat-container.force-dark .chat-commands-dropdown, +.ffz-no-blue .chat-container.dark .chat-commands-dropdown li, +.ffz-no-blue .app-main.theatre .chat-container .chat-commands-dropdown li, +.ffz-no-blue .chat-container.force-dark .chat-commands-dropdown li, +.ffz-no-blue .ember-chat-container.dark .chat-commands-dropdown li, +.ffz-no-blue .app-main.theatre .ember-chat-container.chat-container .chat-commands-dropdown li, +.ffz-no-blue .ember-chat-container.force-dark .chat-commands-dropdown li, +.ffz-no-blue.error_500, +.ffz-no-blue.error_400, +.ffz-no-blue .takeover #carousel, +.ffz-no-blue #carousel_and_background, +.ffz-no-blue #carousel .items .pic img, +.ffz-no-blue #content .turbo_landing { + background-color: #191919; +} + +.ffz-no-blue .chat-container.dark .chat-interface .emoticon-selector .tabs, +.ffz-no-blue .app-main.theatre .chat-container .chat-interface .emoticon-selector .tabs, +.ffz-no-blue .chat-container.force-dark .chat-interface .emoticon-selector .tabs, +.ffz-no-blue .ember-chat-container.dark .chat-interface .emoticon-selector .tabs, +.ffz-no-blue .app-main.theatre .ember-chat-container.chat-container .chat-interface .emoticon-selector .tabs, +.ffz-no-blue .ember-chat-container.force-dark .chat-interface .emoticon-selector .tabs { + background-color: #232323; +} + +/* Following Count */ + +li[data-name="following"] a { + position: relative; +} + +.ffz-follow-count { + display: inline-block; + border-radius: 2px; + text-align: center; + color: #fff; +} + +#header_following .ffz-follow-count { + margin: 0 5px; + padding: 0 5px; + line-height: 20px; + background-color: rgba(25,25,25,0.5); +} + +#large_nav .ffz-follow-count, +.ffz-dark #header_following .ffz-follow-count { + background-color: rgba(127,127,127,0.5); +} + +#large_nav .ffz-follow-count { + position: absolute; + right: 10px; + top: 8px; + + line-height: 14px; + padding: 2px 5px; +} + +#large_nav .game_filter.selected .ffz-follow-count { right: 13px; } + +#small_nav .ffz-follow-count { + position: absolute; + bottom: 2px; + right: 2px; + padding: 0 2px; + font-size: 10px; + background-color: #191919; + color: rgba(255,255,255,0.5); +} + +#small_nav .game_filter.selected a .ffz-follow-count, +#small_nav .content ul li a:hover .ffz-follow-count { + background-color: #101014; +} + +#small_nav .game_filter.selected .ffz-follow-count { right: 5px; } + +/* Legacy Badges */ + +.ffz-legacy-badges .ember-chat .badges .moderator { + background-color: #068c10; + background-image: url('legacy-mod.png'); +} + +.ffz-legacy-badges .ember-chat .badges .staff { + background-color: #6441a5; + background-image: url('legacy-staff.png'); +} + +.ffz-legacy-badges .ember-chat .badges .broadcaster { + background-color: #000; + background-image: url('legacy-broadcaster.png'); +} + +.ffz-legacy-badges .ember-chat .badges .admin { + background-color: #ff0303; + background-image: url('legacy-admin.png'); +} + +.ffz-legacy-badges .ember-chat .badges .turbo { + background-color: #6441a3; + background-image: url('legacy-turbo.png'); +} + +/* High Contrast Chat */ + +.ffz-high-contrast-chat .chat-container, +.ffz-high-contrast-chat .ember-chat-container { + background-color: #fff; + color: #000; +} + +.ffz-high-contrast-chat .ember-chat .chat-messages .chat-line .from, +.ffz-high-contrast-chat .ember-chat .chat-messages .chat-line .colon, +.ffz-high-contrast-chat .ember-chat .chat-messages .chat-line .message { + font-weight: bold; +} + +.ffz-high-contrast-chat .chat-line:before { + background-color: transparent !important; + border: none !important; +} + +.ffz-high-contrast-chat .chat-container.dark, +.ffz-high-contrast-chat .chat-container.force-dark, +.ffz-high-contrast-chat .ember-chat-container.dark, +.ffz-high-contrast-chat .ember-chat-container.force-dark, +.ffz-high-contrast-chat .app-main.theatre .chat-container, +.ffz-high-contrast-chat.ffz-dark .ember-chat-container.dark .chat-line, +.ffz-high-contrast-chat.ffz-dark .chat-container.dark .chat-line { + background-color: #000; + color: #fff; +} + +.ffz-high-contrast-chat .chat-line .mentioned { + color: inherit !important; + background-color: transparent !important; } \ No newline at end of file