Raw Source
sascha03092002gmail.com / YoutubePopoutChat

// ==UserScript==
// @name    YoutubePopoutChat
// @namespace   http://videomatic3.diskstation.me/~dreamstate83/youtubelivechat
// @include http*://*.youtube.com/*
// @author  http://twitter.com/thefrosthaven
// @version 2.0.3
// @run-at  document-end
// @require http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.js
// @updateURL   http://videomatic3.diskstation.me/~dreamstate83/youtubepopoutchat.user.js
// @downloadURL http://videomatic3.diskstation.me/~dreamstate83/youtubepopoutchat.user.js
// @grant GM_log
// @grant GM_getValue
// @grant GM_setValue
// ==/UserScript==

/* CHANGELOG -----------------------------------------------------------------------
------------------------------------------------------------------------------------
REPORT BUGS TO HTTP://TWITTER.COM/THEFROSTHAVEN

11-02-2015 v2.0.3
    - added support for custom mention text and background colors

10-28-2015 v2.0.2
    - adjusted for youtube stylesheet changes
    
9-19-2025 v2.0.1
	- fixed a few typos, added link to video tutorial in config page

9-18-2015 v2.0
    - complete code rewrite to use youtube's built in popout feature that was added
    - popout window now contains a config button in the bottom right
        - your configuration will now save across updates because of this!
    - added support for playing a sound file when donations are received
*/

(function () {
    // API -------------------------------------------------------------------------
    //------------------------------------------------------------------------------
    config = [];

    api = {
        //config ---------------------------------------------
        //----------------------------------------------------
        config: {
            debug: true
        },

        //variable storage -----------------------------------
        //----------------------------------------------------
        url:  false,
        page: false,
        
        //methods --------------------------------------------
        //----------------------------------------------------
        log: function(msg) {
            if (api.config.debug) {
                console.log('[YoutubePopout]: ' + msg);
            }
        },

        setPage: function() {
            var page = false;
            if (api.urlContains("live_chat")) {
                page = "live_chat";
            } else if (api.urlContains("live_event_analytics?")) {
                page = "scheduled_dash";
            } else if (api.urlContains("live_dashboard")) {
                page = "live_dash";
            } else if (api.urlContains('/watch?') || api.urlContains('/live')) {
                //we are on a view page
                if (api.urlContains("gaming.youtube")) {
                    page = 'gyt_view_page';
                } else {
                    page = 'view_page';
                }
            }

            if (page) {
                api.page = page;
            }
            return page;
        },

        waitForChat: function(callback) {
            if (api.page == 'view_page') {
                var retries = 0;
                var chatCheck = setInterval(function() {
                    retries++;
                    if (retries <= 50) {
                        api.log('Looking for chat...');
                        if ($('#comments-view') && $('#hide-live-chat-button').is(':visible')) {
                            api.log('Found Chat!');
                            clearInterval(chatCheck);
                            if ($.isFunction(callback)) {
                                callback();
                            }
                        }
                    } else {
                        clearInterval(chatCheck);   
                    }
                }, 1000);
            } else if (api.page == 'gyt_view_page'){
                var retries = 0;
                var chatCheck = setInterval(function() {
                    retries++;
                    if (retries <= 50) {
                        api.log('Looking for chat...');
                        if ($('.ytg-live-chat-frame').length) {
                            api.log('Found Chat!');
                            clearInterval(chatCheck);
                            if ($.isFunction(callback)) {
                                callback();
                            }
                        }
                    } else {
                        clearInterval(chatCheck);   
                    }
                }, 1000);
            } else {
                callback();
            }
        },

        getPopoutUrl: function(page) {
            var videoId;
            var url = 'http://www.youtube.com/live_chat?v=<VIDEOID>&dark_theme=1&is_popout=1#ytpo-popout-chat';
            if (api.page == "live_dash" || api.page == 'view_page') {
                //set required information
                videoId = $('input[name=video_id]').attr('value');
                url = url.replace("<VIDEOID>", videoId);
            } else if (api.page == 'gyt_view_page') {
                var src;
                $('.ytg-live-chat-frame').each(function(i){
                    if (i == 0) {
                        src = $(this).attr('src');
                    }
                });
                url = src + '#ytpo-popout-chat';
            } else if (api.page == "scheduled_dash") {
                videoId = $('.creator-editor-nav-aside').find('a:first').attr('href').split("=")[1];
                url = url.replace("<VIDEOID>", videoId);
            } else {
                url = false;
            }

            return url;
        },
    
        popoutChat: function(url) {
            var width = 430;
            var height = 600;

            window.open(
                url,
                'Youtube Chat',
                'toolbar=no, location=no, status=no, menubar=no, scrollbars=no, resizable=no, width=' + width + ', height=' + height
            );
        },

        loadChatConfig: function() {
            $('#ytpo-config input').each(function(i, obj) {
                var saveAs = ('ytpo-config-' + $(this).attr('data-saveas'));
                var defaultValue = $(this).attr('data-default');
                var savedValue = GM_getValue(saveAs, defaultValue);
                $(this).val(savedValue);
                $(this).attr('data-undo',savedValue);
                config[$(this).attr('data-saveas')] = $(this).val();
                $(this).trigger('change');
            });
        },

        saveChatConfig: function() {
            $('#ytpo-config input').each(function(i, obj) {
                var saveAs = ('ytpo-config-' + $(this).attr('data-saveas'));
                GM_setValue(saveAs,$(this).val());
                window.location.reload();
            });
        },

        registerConfigEvents: function() {
            $('body').on('click', '#ytpo-config-toggle', function() {
                if ($('#ytpo-config').is(':visible')) {
                    $('#ytpo-config').hide();
                } else {
                    $('#ytpo-config').show();   
                }
            });

            $('body').on('click', '#ytpo-config-save', function() {
                api.saveChatConfig();
            });

            $('body').on('click', '#ytpo-config-cancel', function() {
                $('#ytpo-config input').each(function(i, obj) {
                    var undoValue = $(this).attr('data-undo');
                    $(this).val(undoValue);
                    $(this).trigger('change');
                });
                $('#ytpo-config-toggle').trigger('click');
            });

            $('body').on('click', '.ytpo-test-audio', function() {
                var audio = $(this).next('.ytpo-test-audio-container');
                audio.attr('src', $(this).prev().val());
                audio[0].play();
            });

            $('body').on('click', '.default', function() {
                var input = $(this).prev().prev();
                input.val(input.attr('data-default'));
                input.trigger('change');
            });

            $('body').on('keyup', '.color-input', function() {
                $(this).trigger('change');
            });

            $('body').on('keydown', '.color-input', function() {
                $(this).trigger('change');
            });

            $('body').on('change', '.color-input', function() {
                var swatch = $(this).next(".swatch");
                $(swatch).css('background-color', $(this).val());
            });
        },

        appendUI: function(page) {
            //popout chat buttons
            var popoutUrl = api.getPopoutUrl(page);
            if (popoutUrl) { api.log('Chat Url: ' + popoutUrl); }

            if (api.page == 'live_dash') {
                $('.dashboard-control.player-controls-buttons').append('<button class="yt-uix-button yt-uix-button-size-default yt-uix-button-default" type="button" id="ytpo-popout-chat" data-popout-url="' + popoutUrl + '" style="background:#ff5555; border:1px solid #ff0000; color:#ffffff;"><span class="yt-uix-button-content">Stream Chat</span></button><button class="yt-uix-button yt-uix-button-size-default yt-uix-button-default" id="ytpo-video-toggle" type="button" style="background:#ff5555; border:1px solid #ff0000; color:#ffffff"><span class="yt-uix-button-content">Toggle Video Preview</span></button>');
            } else if (api.page == 'scheduled_dash') {
                $('.creator-editor-nav-aside').prepend('<li><a href="javascript:;" id="ytpo-popout-chat" data-popout-url="' + popoutUrl + '" class="yt-uix-button  yt-uix-button-subnav yt-uix-sessionlink yt-uix-button-default yt-uix-button-size-default" style="background:#ff5555; border:1px solid #ff0000; color:#ffffff;"><span class="yt-uix-button-content">Stream Chat</a></li>');
            } else if (api.page == 'view_page') {
                $('.watch-secondary-actions').prepend('<button class="yt-uix-button yt-uix-button-size-default yt-uix-button-default" type="button" id="ytpo-popout-chat" data-popout-url="' + popoutUrl + '" style="background:#ff5555; border:1px solid #ff0000; color:#ffffff;padding: 2px !important;height: 16px;position:relative; margin-left:10px;"><span class="yt-uix-button-content">Stream Chat</span></button>');
            } else if (api.page == 'gyt_view_page') {
                $('.watch-info').prepend('<button class="yt-uix-button yt-uix-button-size-default yt-uix-button-default" type="button" id="ytpo-popout-chat" data-popout-url="' + popoutUrl + '" style="background:#ff5555; border:1px solid #ff0000; color:#ffffff;padding: 2px !important;height: 16px;position: relative;top: -2px;"><span class="yt-uix-button-content">Stream Chat</span></button>');
            } else if (api.page == 'live_chat') {
                var configPanel = '\
                <div id="ytpo-config" style="display:none; background-color:#333333; color:#ffffff; position:fixed; top:0px; right:0px; left:0px; bottom:0px; z-index:2000; padding:5px; overflow-y:scroll;">\
                    <b style="color:#ff3333;">Youtube Popout Chat Config</b>\
                    <br />\
                    <span style="color:#888888;">Video Tutorial:</span> <a href="http://www.youtube.com/watch?v=qrj1w8ho5lY" target="_blank">Click here to view</a>\
                    <br />\
                    <span style="color:#888888;">You can get hex / rgba color codes at</span> <a href="http://www.hexcolortool.com/" target="_blank">www.hexcolortool.com</a>\
                    <br /><br />\
                    Page BG Color<br />\
                    <input class="color-input" type="text" size="25" data-default="#222222" data-saveas="backgroundcolor" style="background-color:#222222; color:#888888; padding:3px; border:1px solid #444444">\
                    <div class="swatch" style="display:inline-block; background:#ffffff; border:1px solid #444444; width:20px; height:20px; position:relative; top:6px; left:15px;"></div>\
                    <div class="default" style="cursor:pointer; color:#888888; display: inline-block; border: 1px solid rgb(68, 68, 68); height: 20px; position: relative; top:-2px; left: 25px; padding:2px 8px 0px 8px; background: #222222">Default</div>\
                    <br />\
                    Comment BG Color (Odd)<br />\
                    <input class="color-input" type="text" size="25" data-default="#1b1b1b" data-saveas="comment-bg-color-odd" style="background-color:#222222; color:#888888; padding:3px; border:1px solid #444444">\
                    <div class="swatch" style="display:inline-block; background:#ffffff; border:1px solid #444444; width:20px; height:20px; position:relative; top:6px; left:15px;"></div>\
                    <div class="default" style="cursor:pointer; color:#888888; display: inline-block; border: 1px solid rgb(68, 68, 68); height: 20px; position: relative; top:-2px; left: 25px; padding:2px 8px 0px 8px; background: #222222">Default</div>\
                    <br />\
                    Comment BG Color (Even)<br />\
                    <input class="color-input" type="text" size="25" data-default="#222222" data-saveas="comment-bg-color-even" style="background-color:#222222; color:#888888; padding:3px; border:1px solid #444444">\
                    <div class="swatch" style="display:inline-block; background:#ffffff; border:1px solid #444444; width:20px; height:20px; position:relative; top:6px; left:15px;"></div>\
                    <div class="default" style="cursor:pointer; color:#888888; display: inline-block; border: 1px solid rgb(68, 68, 68); height: 20px; position: relative; top:-2px; left: 25px; padding:2px 8px 0px 8px; background: #222222">Default</div>\
                    <br />\
                    Donation BG Color<br />\
                    <input class="color-input" type="text" size="25" data-default="#0f9d58" data-saveas="donation-bg-color" style="background-color:#222222; color:#888888; padding:3px; border:1px solid #444444">\
                    <div class="swatch" style="display:inline-block; background:#ffffff; border:1px solid #444444; width:20px; height:20px; position:relative; top:6px; left:15px;"></div>\
                    <div class="default" style="cursor:pointer; color:#888888; display: inline-block; border: 1px solid rgb(68, 68, 68); height: 20px; position: relative; top:-2px; left: 25px; padding:2px 8px 0px 8px; background: #222222">Default</div>\
                    <br />\
                    Username Color<br />\
                    <input class="color-input" type="text" size="25" data-default="rgba(255,255,255,0.54)" data-saveas="username-color" style="background-color:#222222; color:#888888; padding:3px; border:1px solid #444444">\
                    <div class="swatch" style="display:inline-block; background:#ffffff; border:1px solid #444444; width:20px; height:20px; position:relative; top:6px; left:15px;"></div>\
                    <div class="default" style="cursor:pointer; color:#888888; display: inline-block; border: 1px solid rgb(68, 68, 68); height: 20px; position: relative; top:-2px; left: 25px; padding:2px 8px 0px 8px; background: #222222">Default</div>\
                    <br />\
                    Comment Text Color<br />\
                    <input class="color-input" type="text" size="25" data-default="#ffffff" data-saveas="comment-text-color" style="background-color:#222222; color:#888888; padding:3px; border:1px solid #444444">\
                    <div class="swatch" style="display:inline-block; background:#ffffff; border:1px solid #444444; width:20px; height:20px; position:relative; top:6px; left:15px;"></div>\
                    <div class="default" style="cursor:pointer; color:#888888; display: inline-block; border: 1px solid rgb(68, 68, 68); height: 20px; position: relative; top:-2px; left: 25px; padding:2px 8px 0px 8px; background: #222222">Default</div>\
                    <br />\
                    Mention Text Color<br />\
                    <input class="color-input" type="text" size="25" data-default="#ffffff" data-saveas="mention-text-color" style="background-color:#222222; color:#888888; padding:3px; border:1px solid #444444">\
                    <div class="swatch" style="display:inline-block; background:#ffffff; border:1px solid #444444; width:20px; height:20px; position:relative; top:6px; left:15px;"></div>\
                    <div class="default" style="cursor:pointer; color:#888888; display: inline-block; border: 1px solid rgb(68, 68, 68); height: 20px; position: relative; top:-2px; left: 25px; padding:2px 8px 0px 8px; background: #222222">Default</div>\
                    <br />\
                    Mention BG Color<br />\
                    <input class="color-input" type="text" size="25" data-default="#00796b" data-saveas="mention-bg-color" style="background-color:#222222; color:#888888; padding:3px; border:1px solid #444444">\
                    <div class="swatch" style="display:inline-block; background:#ffffff; border:1px solid #444444; width:20px; height:20px; position:relative; top:6px; left:15px;"></div>\
                    <div class="default" style="cursor:pointer; color:#888888; display: inline-block; border: 1px solid rgb(68, 68, 68); height: 20px; position: relative; top:-2px; left: 25px; padding:2px 8px 0px 8px; background: #222222">Default</div>\
                    <br />\
                    More Comments Text Color<br />\
                    <input class="color-input" type="text" size="25" data-default="#ffffff" data-saveas="more-comments-text-color" style="background-color:#222222; color:#888888; padding:3px; border:1px solid #444444">\
                    <div class="swatch" style="display:inline-block; background:#ffffff; border:1px solid #444444; width:20px; height:20px; position:relative; top:6px; left:15px;"></div>\
                    <div class="default" style="cursor:pointer; color:#888888; display: inline-block; border: 1px solid rgb(68, 68, 68); height: 20px; position: relative; top:-2px; left: 25px; padding:2px 8px 0px 8px; background: #222222">Default</div>\
                    <br />\
                    More Comments BG Color<br />\
                    <input class="color-input" type="text" size="25" data-default="rgba(0,0,0,0.85)" data-saveas="more-comments-bg-color" style="background-color:#222222; color:#888888; padding:3px; border:1px solid #444444">\
                    <div class="swatch" style="display:inline-block; background:#ffffff; border:1px solid #444444; width:20px; height:20px; position:relative; top:6px; left:15px;"></div>\
                    <div class="default" style="cursor:pointer; color:#888888; display: inline-block; border: 1px solid rgb(68, 68, 68); height: 20px; position: relative; top:-2px; left: 25px; padding:2px 8px 0px 8px; background: #222222">Default</div>\
                    <br /><br />\
                    Donation Alert Sound URL (MP3, WAVE, OGG)<br />\
                    <span style="color:#888888;">You can upload audio files for direct linking at </span> <a href="http://www.archive.org/" target="_blank">www.archive.org</a>\
                    <br />\
                    <input type="text" size="50" data-default="" data-saveas="donation-sound" style="margin-top:6px; background-color:#222222; color:#888888; padding:3px; border:1px solid #444444">\
                    <div class="ytpo-test-audio" style="cursor:pointer; color:#888888; display: inline-block; border: 1px solid rgb(68, 68, 68); height: 20px; position: relative; top:-1px; left: 5px; padding:2px 8px 0px 8px; background: #222222">Test</div>\
                    <audio class="ytpo-test-audio-container" src=""></audio>\
                    <br /><br />\
                    <button id="ytpo-config-save" style="color:#ffffff; background:#444444; border:1px solid #222222; padding:8px; cursor:pointer;">Save &amp; Reload Chat</button> <button id="ytpo-config-cancel" style="color:#ffffff; background:#444444; border:1px solid #222222; padding:8px; cursor:pointer;">Cancel Changes</button>\
                </div>';
                var configButton = '<button id="ytpo-config-toggle" style="position:absolute; bottom:2px; right:2px; z-index:900; cursor:pointer; color:#888888">CONFIG</button>';

                $('body').prepend(configPanel);
                $('body').prepend(configButton);
                api.registerConfigEvents();
                api.loadChatConfig();
            }
        },

        registerEvents: function() {
            $('body').on('click', '#ytpo-video-toggle', function() {
                if ($('#video-player').is(':visible')) {
                    $('#video-player').hide();
                } else {
                    $('#video-player').show();   
                }
            });

            $('body').on('click', '#ytpo-popout-chat', function() {
                var url = $(this).attr('data-popout-url');
                api.popoutChat(url);
            });
        },

        stylizeChat: function() {
            if (api.page !== 'live_chat' || !api.urlContains('#ytpo-popout-chat')) { return; }

            $('body').addClass('popout-extension');
            $('.chat-popout-menu').hide();
            document.title= "Youtube Stream Chat";
            var cssContent = "\
                /* background color */\
                .popout-extension #comments-scroller-container, #comments-scroller, #live-comments-section {\
                    background:" + config['backgroundcolor'] + " !important;\
                }\
                .popout-extension .live-chat-widget .comment {\
                    background-color:" + config['comment-bg-color-odd'] + " !important;\
                }\
                .popout-extension .live-chat-widget .comment:nth-of-type(2n) {\
                    background-color:" + config['comment-bg-color-even'] + " !important;\
                }\
                .popout-extension .live-chat-widget .comment.removed, .actions-menu {\
                    display:none !important;\
                }\
                /* tip color */\
                .popout-extension .comment.fan-funding-tip, .popout-extension #comments-scroller-container .fan-funding-tip, #comments-scroller .fan-funding-tip {\
                    background:" + config['donation-bg-color'] + " !important;\
                }\
                /* username color */\
                .popout-extension .comment .yt-user-name {\
                    color:" + config['username-color'] + " !important;    \
                }\
                /* message color */\
                .popout-extension .comment .comment-text {\
                    color:" + config['comment-text-color'] + " !important;    \
                }\
                /* mention colors */\
                .popout-extension .mention {\
                    color:" + config['mention-text-color'] + " !important;    \
                    background-color:" + config['mention-bg-color'] + " !important;    \
                }\
                /* more comments color */\
                .live-chat-widget #live-comments-setting-bottom-scroll {\
                    color:" + config['more-comments-text-color'] + " !important;    \
                    background-color:" + config['more-comments-bg-color'] + " !important;\
                }\
                /* comment font */\
                .popout-extension .comment, .popout-extension .yt-user-name {\
                    font-size:1.7em;\
                    line-height:1.7em;\
                }\
                /* fix comment text indentation */\
                .popout-extension .comment .content {\
                    margin-left:65px !important;\
                }\
                /* avatar size - 45 is the highest before they start to cause layout issues */\
                .popout-extension .yt-user-photo, .popout-extension .avatar {\
                    width:45px !important;\
                    height:inherit !important;\
                }\
                /* fix emojis */\
                .yt-emoji-icon {\
                    position:relative;\
                    top:-3px;\
                    margin:0px 5px;\
                }\
                /* stretch chat out */\
                #live-comments-section {\
                    position:fixed; top:0px; left:0px; right:0px; bottom:0px;\
                }\
                /* align comments to bottom */\
                #all-comments {\
                    position:absolute;\
                    bottom:0px;\
                }\
                .watch-sidebar {\
                    background:#ffffff;\
                }\
                .watch-sidebar div {\
                    box-shadow:none !important;\
                }\
                /* username layout - this forces comments to appear under the user name */\
                .popout-extension .comment .byline {\
                    display:block !important;\
                    padding-bottom:8px;\
                }\
                /* hide our avatar next to the input box and promo header - otherwise it changes it's height and messes up our obs croppings */\
                .popout-extension #live-comments-controls .yt-user-photo, .popout-extension .gaming-promo {\
                    display:none !important;\
                }\
            ";
            
            $("<style>").prop("type","text/css").html(cssContent).appendTo("head");

        },

        monitorChat: function() {
            if (api.page !== 'live_chat' || !api.urlContains('#ytpo-popout-chat')) { return; }

            //register sound element
            if (config['donation-sound'] && config['donation-sound'] !== '') {
                //add sound element to the page
                api.log('Using donation sound: ' + config['donation-sound']);
                var donationSound = document.createElement('audio');
                donationSound.setAttribute('src', config['donation-sound']);

                //detect donations
                $('#all-comments').on('DOMNodeInserted', function(e) {
                    if ($(e.target).is('.fan-funding-tip')) {
                       api.log('Donation added!');
                       donationSound.play();
                    }
                });
            }

            //handle dynamic chat style changes
            $('#comments-scroller').attr('style','position:relative;');
            var firstLoad = false;
            var monitorInterval = setInterval(function() {
                //let's release the position-absolute ruling if there are more comments than can fit on the screen
                var offsetFromBottom = $('#comments-scroller').prop('scrollHeight') - ($('#comments-scroller').innerHeight() + $('#comments-scroller').prop('scrollTop'));
                if (typeof offsetFromBottom !== 'undefined' && !isNaN(offsetFromBottom)) {
                    //api.log('Current Scroll Top: ' + $('#comments-scroller').prop('scrollTop'));
                    //api.log('Current Scroll Height: ' + $('#comments-scroller').prop('scrollHeight'));
                    //api.log('Current Obj Height: ' + $('#comments-scroller').innerHeight());
                    //api.log('offset from bottom: ' + offsetFromBottom);

                    if ($('#all-comments').innerHeight() >= ($('#comments-scroller').innerHeight() + 2)) {
                        //are we coming from a locked state?
                        if ($('#all-comments').attr('data-locked') == '1') {
                            //we are coming from a locked state, lets scroll to the bottom
                            api.log('coming from locked state');
                            $('#all-comments').attr('data-locked','0');
                            $('#all-comments').attr('style','position:relative; left:0px; right:0px;');
                            $('#live-comments-setting-bottom-scroll').click();
                        } else {
                            //more comments than window can show, allow free scroll
                            $('#all-comments').attr('data-locked','0');
                            $('#all-comments').attr('style','position:relative; left:0px; right:0px');
                        }
                    } else {
                        //not enough comments for scrollbar yet, lock to bottom alignment
                        $('#all-comments').attr('style','position:absolute; bottom:0px; left:0px; right:0px;');
                        $('#all-comments').attr('data-locked','1');
                        $('#live-comments-setting-bottom-scroll').click();
                    }
                
                    if (!firstLoad) {
                        firstLoad = true;
                        $('#live-comments-setting-bottom-scroll').click();
                    }
                }
            }, 1000);
        },

        monitorUrl: function(callback) {
            api.url = window.parent.location.href;
            var interval = setInterval(function() {
                if (window.parent.location.href !== api.url) {
                    //our page url changed - we need to re-initialize
                    clearInterval(interval);
                    api.url = window.parent.location.href;
                    api.log('Location changed');
                    api.flush();
                    api.init(callback, true);
                }
            }, 2000);
        },

        flush: function() {
            api.log('Flushing stored values');
            api.page = false;
            api.url  = false;
        },

        init: function(callback, reinit) {
            //dont do anything if we are in an iframe (nested code could really suck...)
            if (window.location !== window.parent.location) {
                api.log(window.location.href + ' !== ' + window.parent.location.href);
                return false;
            }

            //set our current page
            api.setPage();

            //monitor url for dynamic changes
            api.monitorUrl(callback);

            api.waitForChat(function() {

                //append ui elements
                api.appendUI(api.page);

                //monitor chat for dynamic changes
                api.monitorChat();

                //update chat style
                api.stylizeChat();

                if (api.page) { api.log("Loaded for page '" + api.page + "'"); }

                if (!reinit) {
                    api.registerEvents();
                }

                //fill test data
                //api.fillTestData();

                //look for supported page before continuing
                if ($.isFunction(callback)) {
                    callback(api.page);
                }
            });
        },

        //helpers --------------------------------------------
        //----------------------------------------------------
        fillTestData: function() {
            if (api.page !== 'live_chat' || !api.urlContains('#ytpo-popout-chat')) { return; }
            var thehtml = '<li id="comment-MBeXgcfhIbTAaGfWMUaqUyoy2t1WKqDpxSg433D5Q9E" class="comment fan-funding-tip " data-id="MBeXgcfhIbTAaGfWMUaqUyoy2t1WKqDpxSg433D5Q9E" log-tag="live"><div class="accent-bar"></div><a href="/channel/UCwkReZknYwv99blQzaY_E_Q" class="yt-user-photo yt-uix-sessionlink" data-sessi> <span class="video-thumb yt-thumb yt-thumb-32 yt-thumb-fluid g-hovercard" data-ytid="UCwkReZknYwv99blQzaY_E_Q"> <span class="yt-thumb-square"> <span class="yt-thumb-clip"> <img class="yt-thumb-img" alt="TestUser" src="https://yt3.ggpht.com/-sHC6nI5I8xU/AAAAAAAAAAI/AAAAAAAAAAA/MbdLEjylIX4/s32-c-k-no/photo.jpg" width="32"> <span class="vertical-align"></span> </span> </span> </span> </a><div class="content clearfix"> <div class="yt-uix-menu actions-menu"> <button class="yt-uix-button yt-uix-button-size-default yt-uix-button-link yt-uix-button-empty yt-uix-button-has-icon no-icon-markup flip yt-uix-button-opacity comment-actions-menu-btn yt-uix-menu-trigger" type="button"  false;" aria-haspopup="true" role="button" aria-pressed="false" aria-label="Comment actions" data-button-has-sibling-menu="true" data-button-toggle="true"></button> <div class="yt-uix-menu-content yt-ui-menu-content yt-uix-menu-content-hidden" role="menu"> <div class="comment-actions-menu live-chat-actions-menu __COMMENT_ACTIONS_CLASSES__ owner-viewing" data-target="MBeXgcfhIbTAaGfWMUaqUyoy2t1WKqDpxSg433D5Q9E"><button type="button" class="yt-ui-menu-item yt-uix-menu-close-on-select comment-action-remove live-chat-comment-action" data-acti> <span class="yt-ui-menu-item-label">Remove</span> </button><button type="button" class="yt-ui-menu-item yt-uix-menu-close-on-select live-chat-comment-action" data-acti> <span class="yt-ui-menu-item-label">Report profile image</span> </button><button type="button" class="yt-ui-menu-item yt-uix-menu-close-on-select live-chat-comment-action" data-acti> <span class="yt-ui-menu-item-label">Report message</span> </button><button type="button" class="yt-ui-menu-item yt-uix-menu-close-on-select comment-action-block live-chat-comment-action" data-acti> <span class="yt-ui-menu-item-label">Block User</span> </button><button type="button" class="yt-ui-menu-item yt-uix-menu-close-on-select comment-action-unblock live-chat-comment-action" data-acti> <span class="yt-ui-menu-item-label">Unblock User</span> </button><button type="button" class="yt-ui-menu-item yt-uix-menu-close-on-select comment-action-timeout live-chat-comment-action" data-acti> <span class="yt-ui-menu-item-label">Put User in Timeout</span> </button><button type="button" class="yt-ui-menu-item yt-uix-menu-close-on-select live-chat-comment-action" data-acti> <span class="yt-ui-menu-item-label">Set User as Moderator</span> </button></div> </div></div><span class="byline"> <span class="author"> <span data-tooltip-text="Verified" aria-label="Verified" class="yt-channel-title-icon-verified yt-uix-tooltip yt-sprite"></span> <span class="badges"> <span data-tooltip-text="Owner" aria-label="Owner" class="yt-chat-badge badge-owner yt-uix-tooltip yt-sprite"></span> <span data-tooltip-text="Moderator" aria-label="Moderator" class="yt-chat-badge badge-moderator yt-uix-tooltip yt-sprite"></span> </span><a href="/channel/UCwkReZknYwv99blQzaY_E_Q" class="g-hovercard yt-uix-sessionlink yt-user-name" data-sessi data-ytid="UCwkReZknYwv99blQzaY_E_Q" dir="ltr" data-name="">TestUser</a> </span> <span class="tip-amount"> $10.00 </span> </span><div class="comment-text" dir="ltr"> (~^_^)~ </div> <div class="inline-error"> Message not sent: <span class="inline-error-message"></span> </div></div> </li>';
            var timeout = window.setTimeout(function() {
                $('#all-comments').html('');

                var count = 0;
                var interval = window.setInterval(function(){
                    count++;
                    if (count <= 14) {
                        if (count == 9 || count == 2) {
                            $('#all-comments').append(thehtml);
                        } else {
                            $('#all-comments').append(thehtml.replace("fan-funding-tip",""));
                        }
                        $('#live-comments-setting-bottom-scroll').click();
                    }
                }, 2000);

            }, 2000);
        },

        urlContains: function(text) {
            if (window.parent.location.href.indexOf(text) > -1) {
                return true;
            } else {
                return false;
            }
        },

        elExists: function(el) {
            if ($(el).length) {
                return true;
            } else {
                return false;
            }
        }
    };
    
    // page initialization ---------------------------------------------------------
    //------------------------------------------------------------------------------
    $(document).ready(function() {
        api.init(function(page) {
            //post-init code here
        });
    });
})();