[HWM] GuildTimersOnMain

Таймеры для гильдии охотников, наёмников, воров и лидеров

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         [HWM] GuildTimersOnMain
// @namespace    [HWM] GuildTimersOnMain
// @version      0.3.4
// @description  Таймеры для гильдии охотников, наёмников, воров и лидеров
// @author       Komdosh
// @include      http*://*.heroeswm.ru/home.php*
// @include      http*://*.heroeswm.ru/map.php*
// @include      http*://*.heroeswm.ru/mercenary_guild.php*
// @include      http*://*.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;
                return (parseInt(taskMessage.split('\u0447\u0435\u0440\u0435\u0437 ')[1].split(' ')[0])+1) * 60 ;
            }
        });
    }

    //***************************************************************************
    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.querySelector('div[class="global_a_hover"]').innerHTML.split('br');

            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)) {  // Караван выигран
                    let battleDate = battle.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);
                } else {
                    return 0;
                }
            }

            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]');
        const infoLinkValues = infoLink.href.split("id=");

        return {id: infoLinkValues[infoLinkValues.length - 1], name: infoLink.innerText};
    }

    //*******************
    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;
    }
})();