ResetEra Live Thread

Update threads without refreshing

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

You will need to install an extension such as Tampermonkey to install this script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         ResetEra Live Thread
// @namespace    http://madjoki.com
// @version      4.0.15
// @description  Update threads without refreshing
// @author       Madjoki
// @match        https://metacouncil.com/threads/*
// @match        https://www.resetera.com/threads/*
// @match        https://bbs.io-tech.fi/threads/*
// @require      https://cdnjs.cloudflare.com/ajax/libs/favico.js/0.3.10/favico.min.js
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    let favicon;

    // This is to disable scrolldown behaviour when XenForo insert "new messages" box.
    const original = XF.Message.insertMessages;

    XF.Message.insertMessages = function (dataHtml, $container, ascending, onInsert)
    {
        console.log(dataHtml, $container, ascending, onInsert);

        if (dataHtml.content.indexOf('js-newMessagesIndicator') > -1)
            return;

        original(dataHtml, $container, ascending, onInsert);
    }

    var favIconUpdate = function (count) {
        favicon.badge(count);
        favicon.badge(count);
    }

    if (window.location.host === 'www.resetera.com') {
        // Chrome Fix
        //let icon = $('link[rel*=icon]').first().clone();
        //$('link[rel*=icon]').remove();
        //$('head').append(icon);

        favicon = new Favico({
            animation: 'none',
            fontFamily: 'FontAwesome',
            fontStyle: 'normal'
        });

    }
    else {
        favicon = new Favico({
            animation: 'none',
            fontFamily: 'FontAwesome',
            fontStyle: 'normal'
        });
    }

    favIconUpdate();

    var timeoptions = [
        {
            name: "5s",
            value: 5,
        },
        {
            name: "10s",
            value: 10,
        },
        {
            name: "15s",
            value: 15,
        },
        {
            name: "30s",
            value: 30,
        },
        {
            name: "1m",
            value: 60,
        },
        {
            name: "2m",
            value: 120,
        },
    ];

    let defaults = {
        timer: 5,
        enabledByDefault: false,
    };

    let threadID = $('html').data('content-key');
    let userSettings = {}
    let threadSettings = {}
    let recentErrors = 0;
    let countNewMessages = 0;
    let updating = false;
    let enabled = false;
    let paused = false;
    let currentTimer = 120;
    let hasFocus = true;

    // Read Global Settings
    let settingsJson = localStorage.getItem("livethreadSettings");

    if (settingsJson !== null)
        userSettings = JSON.parse(settingsJson) || {};

    // Read Thread Settings
    let threadJson = localStorage.getItem("livethread_" + threadID);

    if (threadJson !== null)
        threadSettings = JSON.parse(threadJson) || {};

    let currentSettings = {}

    function updateSettings() {
        currentSettings = {
            ...defaults,
            ...userSettings,
            ...threadSettings
        }

        if (!("enabled" in currentSettings))
            currentSettings.enabled = currentSettings.enabledByDefault;

        if (!currentSettings.timer || currentSettings.timer < 0)
            currentSettings.enabled = false;

        enabled = currentSettings.enabled;
        paused = !enabled;
    }

    updateSettings();
    currentTimer = currentSettings.timer;

    function getPages(dom) {
        return {
            current: parseInt(dom.find('li.pageNav-page--current').first().text(), 10) || 0,
            next: parseInt(dom.find('.pageNav-page.pageNav-page--later').first().text()) || parseInt(dom.find('.pageNav-page').last().text()) || 0,
            last: parseInt(dom.find('.pageNav-page').last().text()) || 0
        };
    }

    function updateFavIcon() {
        if (countNewMessages > 0) {
            favIconUpdate(countNewMessages);
        }
        else if (currentSettings.enabled && !paused) {
            favIconUpdate('');
        }
        else {
            favIconUpdate(0);
        }
    }

    function addoptions(el, values) {

        $(el).find("option").remove();

        $(values).each(function (i, o) {
            $(el).append($("<option>", {text: o.name, value: o.value}));
        })
    }

    // CSS
    $('body').append(`<style>
    #livethreadPanel {
        display: none;
        text-align: center;
    }
    #livethreadPanel ul {
        display: inline-block;
        margin-bottom: 15px;
    }
    #livethreadPanel ul li {
        display: block;
        text-align: left;
    }
    #updateTime {
        margin-left: 5px;
        padding: 0px;
    }
    #updateTimeDefault {
        margin-left: 5px;
        padding: 0px;
    }
    body.darktheme #livethreadPanel ul {
       color: #8e50be;   /*dark theme only*/
    }
    .livethreadStatus {
        text-align: center;
    }
    .liveThread_enabled .globalAction {
        display: none !important;
    }
    .liveThreadControls a {
        padding: 5px;
    }
</style>`);

    function getTimeOfLastMessage()
    {
        return $('article.message time').last().data('time');
    }

    function getPageUrl(page)
    {
        $('meta[property="og:url"]').attr('content') + `page-${page}`;
    }

    // Get date of last message
    var $lastDate = $('input[name="last_date"]');
    var $container = $('.js-replyNewMessageContainer');
    var pages = getPages($('body'));
    var lastPageWithData = pages.current;

    // If zero messages, it's non thread page like reply page
    if ($('article.message').length === 0)
        return;

    // Pause if this isn't last page
    if (pages.current !== pages.last)
        paused = true;

    // Create Control Panel
    var controlsContainer = $('<div>', { class: 'block-outer-opposite liveThreadControls' });

    var statusText = $('<a>', { href: '#', class: 'livethreadStatus livethreadRefresh postsRemaining' });
    var startPauseBtn = $('<a>', { href: '#', class: 'livethreadStartPause' }).append($('<i>', { class: 'fa' }));
    var settingsBtn = $('<a>', { href: '#', class: 'livethreadSettings' }).append($('<i>', { class: 'fa fa-cog' }));
    var refreshBtn = $('<a>', { href: '#', class: 'livethreadRefresh' }).append($('<i>', { class: 'fa fa-refresh' }));

    controlsContainer.append(statusText);
    controlsContainer.append(startPauseBtn);
    controlsContainer.append(refreshBtn);
    controlsContainer.append(settingsBtn);

    $('.block-outer.block-outer--after').append(controlsContainer);

    // Build Settings
    $('.block-outer.block-outer--after').last().after('\
<div id="livethreadPanel" class="DiscussionListOptions secondaryContent">\
    <h2 class="heading h1">This Thread</h2>\
    <ul>\
       <li><label for="updateTime">Update Speed:</label> <select id="updateTime" class="textCtrl"></select></li>\
    </ul>\
    <h2 class="heading h1">Global Settings</h2>\
    <ul>\
			<li style="display: none"><label><input type="checkbox" id="liveThread_remember" value="1"> Remember New Threads by Default</label></li>\
			<li><label><input type="checkbox" id="liveThread_enableByDefault" value="1"> Enable By Default</label></li>\
			<li><label>Default Update Speed: <select id="updateTimeDefault" class="textCtrl"></select></label></li>\
			<li style="display: none"><label><input type="checkbox" id="liveThread_debug" value="1"> Log Debug Data to Console (only for testing)</label></li>\
    </ul>\
</div>');

    function saveSettings()
	{
        localStorage.setItem("livethread_" + threadID, JSON.stringify(threadSettings));
        localStorage.setItem("livethreadSettings", JSON.stringify(userSettings));

        updateForm();
	}

    $('#liveThread_enableByDefault').change(function () {
        userSettings.enabledByDefault = $('#liveThread_enableByDefault').is(':checked');
        saveSettings();
    });

    $('#liveThread_messageMarkers').change(function () {
        userSettings.useNewMessageMarker = $('#liveThread_messageMarkers').is(':checked');
        saveSettings();
    });

    $('#liveThread_remember').change(function () {
        userSettings.rememberThreads = $('#liveThread_remember').is(':checked');
        saveSettings();
    });

    $('#liveThread_debug').change(function () {
        userSettings.enableDebug = $('#liveThread_debug').is(':checked');
        saveSettings();
    });

    $('#updateTime').change(function () {
        const time = parseInt($('#updateTime').val());
        if (time)
            threadSettings.timer = parseInt(time);
        else
            delete threadSettings.timer;

		saveSettings();
    });

    $('#updateTimeDefault').change(function () {
        userSettings.timer = parseInt($('#updateTimeDefault').val());
		saveSettings();
    });

    // Control Panel
    function updateForm()
    {
        addoptions($("#updateTime"), [{
            name: "Default",
            value: 0,
        }, {
            name: "Disabled",
            value: -1,
        }, ...timeoptions]);
        addoptions($("#updateTimeDefault"), timeoptions);

        $("#updateTime option[value='" + threadSettings.timer + "']").attr("selected", true);
        $("#updateTimeDefault option[value='" + userSettings.timer + "']").attr("selected", true);

        $("#liveThread_remember").attr("checked", userSettings.rememberThreads);
        //$("#liveThread_messageMarkers").attr("checked", userSettings.useNewMessageMarker);
        $("#liveThread_enableByDefault").attr("checked", userSettings.enabledByDefault);
        //$('#liveThread_currentRemember').attr("checked", isRememberedThread);
        //$("#liveThread_debug").attr("checked", globalSettings.enableDebug);
    }

    updateForm();

    function insertMessagesAlternative(data) {
        console.log(data);

        var html = $.parseHTML(data.html.content);
        var $html = $(html);

        var pagesNew = getPages($html);

        if (pagesNew.current !== pages.current)
        {
            console.log("page changed", pagesNew, pages);

            pages = pagesNew;
            lastPageWithData = pages.current;

            history.pushState({}, "", `page-${pages.current}`);

            var $navNew = $html.find('.pageNavWrapper').first();
            $('.pageNavWrapper').html($navNew.html());
        }

        $html.find('article.message').each(function () {
            insertMessage($(this), $container, true);
        });

        updateFavIcon();

        updating = false;
    }

    function insertMessages(data) {

        if (data.message)
        {
            recentErrors++;
            return;
        }

        if (data.lastDate)
            $lastDate.val(data.lastDate);

        recentErrors = 0;

        if (data.html) {
            XF.setupHtmlInsert(data.html, function ($html, container, onComplete, onInsert) {
                // TODO: Check if DIV is there and load additional messages automatically
                var div = $html.children('div');

                if (div.length)
                {
                    console.log(div);
                }

                $html.each(function () {

                    if (!this.tagName) {
                        return;
                    }

                    if (this.tagName === 'DIV') {
                        console.log(this);

                        var $msg = $(this);

                        // TEMP
                        $container.append(this);
                    }

                    insertMessage($(this), $container, true);
                });

                if (onInsert) {
                    onInsert($html);
                }

                updateFavIcon();
                updating = false;
            });
        }
    }

    function insertMessage($message, $container, ascending) {
        if (!$message.data('author')) // Fix for empty messages
            return;

        // post-15795528
        var id = $message.attr('id');

        var $msg = $(`#${id}`);

        if ($msg.length)
        {
            console.log(`not inserting ${id}, already in page`);
            // TODO: update
            return;
        }

        countNewMessages++;

        var $firstChild = $container.children().first();

        //$message.hide();

        if ($firstChild.is('form') && !ascending) {
            $message.insertAfter($firstChild);
        }
        else if (!ascending) {
            $container.prepend($message);
        }
        else {
            $container.append($message);
        }

        //$message.xfFadeDown();
        $message.addClass('livethread_unread');

        XF.activate($message);
    }

    function loadMessages() {
        if (updating)
            return;

        if (lastPageWithData !== pages.last)
            loadMessagesAlternative();
        else
            loadMessagesFast();

        updating = true;
    }

    // Use api to get messages
    function loadMessagesFast() {
        XF.ajax('GET', 'new-posts', { after: getTimeOfLastMessage() }, insertMessages).always(function () { updating = false }, {useError: false});
        updateControls();
    }

    function loadMessagesAlternative() {
        const page = pages.next || pages.current;

        XF.ajax('GET', `page-${page}`, insertMessagesAlternative).always(function () { updating = false }, {useError: false});
    }

    function timer() {
        if (paused || !enabled)
            return;

        currentTimer--;

        // Delay if there's recent errors
        var errorDelay = 10000 * Math.min(5, recentErrors);

        if (currentTimer === 0) {
            loadMessages();
            currentTimer = currentSettings.timer + errorDelay;
        }

        updateControls();
    }

    function getStatusText() {
        var status = "";

        if (updating)
            status += "Updating";
        else if (currentTimer && !paused && enabled) {
            status += "Next Update In " + currentTimer + " seconds";

            //if (countNewLast > 0)
            //    status += " - " + countNewLast + " New Messages!";
        }
        else
            status = "Disabled";

        return status;
    }

    function updateControls() {
        $(".livethreadStartPause i").toggleClass('fa-pause', !paused);
        $(".livethreadStartPause i").toggleClass('fa-play', paused);
        $(".livethreadRefresh i").toggleClass('fa-spin', updating);

        $(".livethreadStatus").text(getStatusText());

        $('body').toggleClass('liveThread_enabled', !paused);
    }

    function isvisible($ele) {
        var lBound = $(window).scrollTop(),
            uBound = lBound + $(window).height(),
            top = $ele.offset().top,
            bottom = top + $ele.outerHeight(true);

        return (top > lBound && top < uBound) || (bottom > lBound && bottom < uBound) || (lBound >= top && lBound <= bottom) || (uBound >= top && uBound <= bottom);
    }

    function handleScroll() {
        $('.livethread_unread').each(function (i, el) {
            var $el = $(el);

            if (isvisible($el)) {
                $el.removeClass('livethread_unread');
                $el.prevAll('.livethread_unread').removeClass('livethread_unread');
            }
        });

        countNewMessages = $('.livethread_unread').length;
        updateFavIcon();
    }

    $(window).scroll(function () {
        handleScroll();
    });

    $(window).focus(function () {
        handleScroll();
        hasFocus = true;
    });

    $(window).focusout(function () {
        handleScroll();
        hasFocus = false;
    });

    updateControls();

    setInterval(timer, 1000);

    $('.livethreadRefresh').click(function (event) {
        event.preventDefault();
        loadMessages();
    });

     $('.livethreadStartPause').click(function (event) {
        event.preventDefault();
        enabled = true;
        paused = !paused;
        updateControls();
    });

    $('.livethreadSettings').click(function (event) {
        event.preventDefault();
        $('#livethreadPanel').toggle();
        //$('#livethreadPanel').scrollintoview();
    });

})();