dailyhwm / GuildTimersOnMain

// ==UserScript==
// @name GuildTimersOnMain
// @namespace GuildTimersOnMain
// @version 0.3.4.1
// @homepage https://greasyfork.org/ru/scripts/437040
// @description Таймеры для гильдии охотников, наёмников, воров и лидеров на home.php
// @author Komdosh
// @include *://*.heroeswm.ru/home.php*
// @include *://*.heroeswm.ru/map.php*
// @include *://*.heroeswm.ru/mercenary_guild.php*
// @include *://*.heroeswm.ru/leader_guild.php*
// @grant none
// @license MIT
// ==/UserScript==
(function () {
    'use strict';

    const GUILDS_INFO_MERCENARY = 'GUILDS_INFO_MERCENARY';
    const GUILDS_INFO_HUNTER = 'GUILDS_INFO_HUNTER';
    const GUILDS_INFO_LEADER = 'GUILDS_INFO_LEADER';
    const GUILDS_INFO_THIEF = 'GUILDS_INFO_THIEF';

    if (/map.php/.test(location.href)) {
        const hunterInfo = getWithExpiry(GUILDS_INFO_HUNTER);
        if (hunterInfo != null && hunterInfo === '-1') {
            localStorage.removeItem(GUILDS_INFO_HUNTER);
        }
        const thiefInfo = getWithExpiry(GUILDS_INFO_THIEF);
        if (thiefInfo != null && thiefInfo === '-1') {
            localStorage.removeItem(GUILDS_INFO_THIEF);
        }
        return;
    } else if (/leader_guild.php/.test(location.href)) {
        const leaderInfo = getWithExpiry(GUILDS_INFO_LEADER);
        if (leaderInfo != null && leaderInfo === '-1') {
            localStorage.removeItem(GUILDS_INFO_LEADER);
        }
        return;
    }

    localStorage.removeItem(GUILDS_INFO_MERCENARY);

    const isThiefsAvailable = parseInt(Array.from(document.querySelectorAll('.home_guild_text'))
        .find(el => el.innerText === 'Воров').parentElement.querySelector('.home_text_exp').innerText) > 0;

    const userInfo = getUserInfo();

    const guildsInfoDiv = document.createElement('div');
    guildsInfoDiv.className += "home_container_block";
    guildsInfoDiv.style = "align-items: left;";

    const guildsInfoHeader = document.createElement('div');
    guildsInfoHeader.className += "global_container_block_header global_a_hover";
    guildsInfoHeader.innerHTML = 'Таймеры';
    guildsInfoDiv.append(guildsInfoHeader);

    const guildsInfoContentDiv = document.createElement('div');
    guildsInfoContentDiv.className += "home_inside_margins global_a_hover";
    guildsInfoDiv.append(guildsInfoContentDiv);

    const workerGuild = document.querySelector(".home_work_block");

    workerGuild.before(guildsInfoDiv);

    const loading = document.createElement('span');
    loading.innerText = 'Данные обновляются...';
    guildsInfoContentDiv.append(loading);

    requestHunterInfo();
    requestMercenaryInfo();
    if (isThiefsAvailable) {
        requestThiefInfo(userInfo.id);
    }
    requestLeadersInfo();

    //***************************************************************************
    function requestLeadersInfo() {
        request('leader', 'Новое задание через ', "/leader_guild.php",
            GUILDS_INFO_LEADER,
            'ГЛ',
            '<a href="leader_guild.php" style="text-decoration:underline">Все цели обнаружены</a>',
            'Требуется лидер',
            (respDoc, timerDiv) => {
                const nextTime = respDoc.querySelector('#next_lg');
                if (nextTime == null) {
                    return 0;
                }
                const script = nextTime.parentElement.getElementsByTagName('script')[0];

                const scriptDeltaText = script.text.match(/Delta2 = (\d+)/);

                if (scriptDeltaText == null) {
                    return 0;
                } else {
                    return parseInt(scriptDeltaText[1]);
                }
            });
    }

    //***************************************************************************
    function requestHunterInfo() {
        request('hunter', 'Охота будет доступна через ', "/map.php",
            GUILDS_INFO_HUNTER,
            'ГО',
            '<a href="map.php" style="text-decoration:underline">Новая охота</a>',
            'Охотники зовут на помощь',
            (respDoc, timerDiv) => {
                const script = document.querySelector('#map_right_block_inside').getElementsByTagName('script')[0];

                const scriptDeltaText = script.text.match(/MapHunterDelta = (\d+)/);

                if (scriptDeltaText == null) {
                    return 0;
                } else {
                    return parseInt(scriptDeltaText[1]);
                }
            });
    }

    //***************************************************************************
    function requestMercenaryInfo() {
    request('mercenary', 'Новое задание через ', "/mercenary_guild.php", GUILDS_INFO_MERCENARY,
        'ГН',
        '<a href="mercenary_guild.php" style="text-decoration:underline">Новое задание</a>',
        'Наёмникам требуется герой!',
        (respDoc, timerDiv) => {
            const taskMessageDiv = respDoc.querySelector('table:not([align="center"])').querySelector('table:not([align="center"], [class="wbwhite"])');

            if (taskMessageDiv == null) {
                return 0;
            } else {
                const taskMessage = taskMessageDiv.querySelector('td').innerText;
                const timeRemaining = taskMessage.includes('через') ? (parseInt(taskMessage.split('через ')[1].split(' ')[0]) + 1) * 60 : 0;
                return timeRemaining;
            }
        });
    }

    //***************************************************************************
    function requestThiefInfo(userId) {
    request('thief', 'Новое задание через ', `/pl_warlog.php?id=${userId}`, GUILDS_INFO_THIEF,
        'ГВ',
        '<a href="map.php" style="text-decoration:underline">Новая засада</a>',
        'Воры замышляют новое нападение!',
        (respDoc, timerDiv) => {
            const battles = respDoc.querySelectorAll('div[class="global_a_hover"]');

            let nextTime = 1000 * 60 * 60;
            if (isPremium()) {
                nextTime *= 0.7;
            }

            for (const battle of battles) {
                if (/<b>\u041A\u0430\u0440\u0430\u0432\u0430\u043D/.test(battle.innerHTML)) {  // Караван выигран
                    let battleDate = battle.innerHTML.split('>')[1].split('<')[0];
                    const battleDateSplit = battleDate.split('-');
                    battleDate = battleDateSplit[1] + '-' + battleDateSplit[0] + '-' + battleDateSplit[2];
                    const thiefDateTime = new Date(battleDate);

                    return Math.floor(((thiefDateTime.getTime() + nextTime) - Date.now()) / 1000);
                }
            }

            return 0;
        });
    }


    function request(domId, taskHtml, link, localStorageName, notifyMeLinkName, newTaskInstantlyDom, notifyText, processor) {
        const timerDiv = document.createElement('div');
        timerDiv.id = domId;
        guildsInfoContentDiv.append(timerDiv);

        const expiration = getWithExpiry(localStorageName);

        const notifyMeLink = document.createElement('a');
        notifyMeLink.style = 'cursor: pointer';

        let isNotify = localStorage.getItem(`${localStorageName}_SETTINGS`) === 'true';

        notifyMeLink.innerHTML = isNotify ? `<b>${notifyMeLinkName}</b>: ` : `${notifyMeLinkName}: `;

        notifyMeLink.onclick = () => {
            isNotify = !isNotify;

            toggleNotification(isNotify,
                notifyMeLink,
                notifyMeLinkName);

            localStorage.setItem(`${localStorageName}_SETTINGS`, isNotify);
        };

        const wrapper = document.createElement('span');
        wrapper.append(notifyMeLink);

        if (expiration != null) {
            if (loading != null) {
                loading.remove();
            }

            if (expiration === '-1') {
                timerDiv.append(createElementFromTextAndAppend(wrapper, newTaskInstantlyDom));
                return;
            }

            toggleNotification(isNotify,
                notifyMeLink,
                notifyMeLinkName);

            const delay = Math.floor((expiration - Date.now()) / 1000);
            initTimer(delay, timerDiv, createElementFromTextAndAppend(wrapper, taskHtml), () => {
                timerFinished(isNotify, notifyText, timerDiv, newTaskInstantlyDom);
            });
            return;
        }
        const xhr = new XMLHttpRequest();
        xhr.open('GET', encodeURI(link));
        xhr.overrideMimeType('text/xml; charset=windows-1251');
        xhr.onload = function () {
            if (xhr.status === 200) {
                const div = document.createElement('div');
                div.id = 'kom-' + domId;
                div.style.display = 'none';
                div.innerHTML = xhr.responseText;
                document.getElementsByTagName('body')[0].appendChild(div);
                const respDoc = document.getElementById('kom-' + domId);
                if (loading != null) {
                    loading.remove();
                }
                const delta = processor(respDoc, timerDiv);

                if (delta > 0) {
                    const expiration = Date.now() + delta * 1000;

                    setWithExpiry(localStorageName, expiration, expiration);

                    toggleNotification(isNotify,
                        notifyMeLink,
                        notifyMeLinkName);

                    initTimer(delta, timerDiv, createElementFromTextAndAppend(wrapper, taskHtml), () => {
                        timerFinished(isNotify, notifyText, timerDiv, newTaskInstantlyDom);
                    });
                } else {
                    setWithExpiry(localStorageName, '-1', Date.now() + 60 * 60 * 1000);
                    timerDiv.append(createElementFromTextAndAppend(wrapper, newTaskInstantlyDom));
                }

                respDoc.remove();
            } else {
                console.log('Request failed.  Returned status of ' + xhr.status);
            }
        };
        xhr.send();
    }

    //***************************************************************************
    function setWithExpiry(key, value, expirationTime) {
        const item = {
            value: value,
            expiry: expirationTime,
        }
        localStorage.setItem(key, JSON.stringify(item))
    }

    //***************************************************************************
    function getWithExpiry(key) {
        const itemStr = localStorage.getItem(key)
        // if the item doesn't exist, return null
        if (!itemStr) {
            return null;
        }
        const item = JSON.parse(itemStr)
        const now = new Date()
        // compare the expiry time of the item with the current time
        if (now.getTime() > item.expiry) {
            // If the item is expired, delete the item from storage
            // and return null
            localStorage.removeItem(key)
            return null
        }
        return item.value
    }

    //***************************************************************************
    function initTimer(Delta, timerDiv, html, onTimeEnd) {
        const timeSpan = document.createElement('span');
        html.append(timeSpan);

        function print_time(t) {
            if (t < 0) t = 0;

            const min = Math.floor(t / 60);
            const sec = t % 60;
            let s = '';

            if (min) s = min + ' ' + 'мин. ';
            s = s + sec + ' ' + 'с. ';

            timeSpan.innerText = ' ' + s;
            if (timerDiv.firstChild != null) {
                timerDiv.replaceChild(html, timerDiv.firstChild);
            } else {
                timerDiv.append(html);
            }
        }

        const Refresh = () => {
            if (Timer >= 0) clearTimeout(Timer);
            Delta = Delta - 1;
            print_time(Delta);
            if (Delta >= 0) {
                Timer = setTimeout(Refresh, 1000);
            } else {
                onTimeEnd();
            }
        }

        let Timer = setTimeout(Refresh, 1000);
        print_time(Delta);
    }

    //*******************
    function getUserInfo() {
        const infoLink = document.querySelector('center>a[href^=pl_info]');
        if (infoLink) {
            const infoLinkValues = infoLink.href.split("id=");
            return { id: infoLinkValues[infoLinkValues.length - 1], name: infoLink.innerText };
        } else {
            //console.error("Error: infoLink is null");
            return { id: "", name: "" };
        }
    }

    //*******************
    function isPremium() {
        return document.querySelector('a[href="shop.php?cat=potions#vip"]') != null;
    }

    //***************************************************************************
    function toggleNotification(isNotify, notifyMeLink, notifyMeLinkName) {
        if (isNotify) {
            notifyMeLink.innerHTML = `<b>${notifyMeLinkName}</b>: `;
            notifyMeLink.title = 'Уведомление: включено';
        } else {
            notifyMeLink.innerHTML = `${notifyMeLinkName}: `;
            notifyMeLink.title = 'Уведомление: выключено';
        }
    }

    //***************************************************************************
    function createElementFromTextAndAppend(wrapper, html) {
        const span = document.createElement('span');
        span.innerHTML = html.trim();
        wrapper.append(span);
        return wrapper;
    }

    //***************************************************************************
    function timerFinished(isNotify, notifyText, timerDiv, finishedHtml) {
        if (isNotify) {
            notifyAfter(notifyText);
        }

        const span = document.createElement('span');
        span.innerHTML = finishedHtml.trim();

        timerDiv = timerDiv.lastChild;
        let child = timerDiv.lastChild;
        while (timerDiv.children.length > 1) {
            timerDiv.removeChild(child);
            child = timerDiv.lastChild;
        }

        timerDiv.append(span);
    }

    //***************************************************************************
    function notifyAfter(text) {
        var notify = alert;

        const cancellation = setTimeout(() => {
            notify(text);
        }, 1000);

        Notification.requestPermission().then((permission) => {
            if (permission === 'granted') {
                notify = (t) => new Notification(t);
            }
        });

        return cancellation;
    }
})();