hwmTimers

Таймеры гильдии рабочих, воров, наёмников, рейнджеров, охотников, кузнецов, восстановления здоровья и маны

目前為 2023-12-19 提交的版本,檢視 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name           hwmTimers
// @namespace      Tamozhnya1
// @author         Tamozhnya1
// @description    Таймеры гильдии рабочих, воров, наёмников, рейнджеров, охотников, кузнецов, восстановления здоровья и маны
// @version        7.3
// @include        https://www.heroeswm.ru/*
// @include        *.lordswm.com/*
// @exclude        */rightcol.php*
// @exclude        */ch_box.php*
// @exclude        */chat*
// @exclude        */ticker.html*
// @exclude        */frames*
// @exclude        */brd.php*
// @grant          GM_deleteValue
// @grant          GM_getValue
// @grant          GM_setValue
// @grant 		   GM.xmlHttpRequest
// @grant 		   GM.notification
// @license        MIT
// ==/UserScript==

if(!this.GM_getValue || (this.GM_getValue.toString && this.GM_getValue.toString().indexOf("not supported") > -1)) {
    this.GM_getValue = function(key, def) { return localStorage[key] || def; };
    this.GM_setValue = function(key, value) { localStorage[key] = value; };
    this.GM_deleteValue = function(key) { return delete localStorage[key]; };
}
const playerIdMatch = document.cookie.match(/pl_id=(\d+)/);
if(!playerIdMatch) {
    return;
}
const PlayerId = playerIdMatch[1];
const isEn = document.documentElement.lang == "en";
const isHeartOnPage = document.querySelector("canvas#heart") || document.querySelector("div#heart_js_mobile") ? true : false;
const isNewInterface = document.querySelector("div#hwm_header") ? true : false;
const mooving = location.pathname == '/map.php' && !document.getElementById("map_right_block");
const windowObject = window.wrappedJSObject || unsafeWindow;
const timerNames = ["health", "work", "smith", "merc", "hunt", "thief", "mana"];
const disable_alarm_delay = 30; //секунд задержки после предыдущего сигнала
let healthAudio;
let workAudio;
let smithAudio;
let warlikeAudio;
let defaultAudio;
let texts;

main();
function main() {
    initUserName();
    verifyOptionKeys();
    texts = setTexts();
    if(location.pathname == '/war.php') {
        inBattle(); // в бою
        return;
    }
    if(!isHeartOnPage) {
        return;
    }
    const [army_percent] = healthTimer();
    // Обработка результатов битвы, если только что из неё вышли
    const bselect_link = document.querySelector("a[href='bselect.php']") || document.querySelector("a[href='plstats.php']");
    if(bselect_link && bselect_link.parentNode.innerHTML.indexOf("#ff0000") === -1) { // Сидим на форуме, меню - красное
        checkBattleResults(army_percent < 100 ? "fail" : "win");
    }
    initAudios();
    requestServerTime();
    checkWork();
    checkPremiumAccount();
    checkHuntLicense();
    checkWorkaholic();
    checkMercenary();
    if(location.pathname == "/leader_guild.php") {
        Array.from(document.querySelectorAll("form[name^='f'] input[type='submit']")).forEach(x => x.addEventListener("click", function () { updateOption("battleType", "leader"); }));
    }
    checkRangerGuild();
    checkModWorkebench();
    if(location.pathname == '/map.php') {
        checkThiefAmbush();
        checkRangerAmbush();
        checkMapHunter();
    }
    createTimersPanel();
    timersPanelDataBind();
    tick();
}
function verifyOptionKeys() {
    const defaultOptions = {
        "healthNotification": "no",
        "workNotification": "yes",
        "enrollNumber": "0",
        "smithNotification": "yes",
        "mercNotification": "yes",
        "huntNotification": "yes",
        "thiefNotification": "yes",
        "customTimeRate": "1",
        "abuBlessRate": "1",
        "abuBlessExpirationTime": "0",
        "abuBlessInfo": "",
        "huntLicenseRate": "1",
        "huntLicenseExpirationTime": "0",
        "huntLicenseText": "",
        "thiefOrRanger": "0",
        "joinRangerBattle": "0",
        "showWorkaholicAlarmLastTwoEnrolls": "1",
        "disableWorkaholicAlarm": "0",
        "isShowWorkTimer": "1",
        "isShowSmithTimer": "1",
        "isShowMercTimer": "1",
        "isShowHuntTimer": "1",
        "isShowThiefTimer": "1",
        "workSound": "",
        "warlikeSound": "",
        "smithSound": "",
        "healthSound": "",
        "battleType": ""
    };
    const options = JSON.parse(GM_getValue(`hwmTimersOptions${PlayerId}`, JSON.stringify(defaultOptions)));
    Object.keys(defaultOptions).forEach(x => { if(!options.hasOwnProperty(x)) { options[x] = defaultOptions[x]; } });
    Object.keys(options).forEach(x => { if(!defaultOptions.hasOwnProperty(x)) { delete options[x]; } });
    GM_setValue(`hwmTimersOptions${PlayerId}`, JSON.stringify(options));
    //console.log(options);
}
function inBattle() {
    if(/warlog\|0/.exec(document.querySelector("html").innerHTML)) {
        //flash & html: warlog|0| -> бой происходит сейчас, warlog|1| -> запись боя, |player|7146446| -> id текущего игрока
        const playerIdExec = /\|player\|(\d+)\|/.exec(document.querySelector("html").innerHTML);
        if(playerIdExec && playerIdExec[1] == PlayerId) {
            GM_setValue(`HoldBattle${PlayerId}`, true);
            const finalResultDiv = document.getElementById("finalresult_text");
            if(finalResultDiv.innerHTML.length <= 10) {
                observe(finalResultDiv, parseBattleResultPanel);
            }
        }
    }
}
function parseBattleResultPanel() {
    const finalResultDiv = document.getElementById("finalresult_text");
    if(finalResultDiv.innerHTML.length > 10) {
        const bolds = finalResultDiv.querySelectorAll("font b");
        let result = "fail";
        for(const bold of bolds) {
            if(bold.innerHTML == (isEn ? "Victorious:" : "Победившая сторона:")) {
                //console.log(`${bold.parentNode.nextSibling.nextSibling.firstChild.innerText}, UserName: ${GM_getValue("UserName")}`);
                if(bold.parentNode.nextSibling.nextSibling.firstChild.innerText == GM_getValue("UserName")) {
                    result = "win";
                }
                break;
            }
        }
        checkBattleResults(result);
    }
}
function checkBattleResults(result) {
    if(gmGetBool(`HoldBattle${PlayerId}`)) {
        const options = JSON.parse(GM_getValue(`hwmTimersOptions${PlayerId}`));
        console.log(`battleType: ${options.battleType}, result: ${result}`);
        switch(options.battleType) {
            case "thief":
                if(result == "fail") {
                    GM_setValue(`thiefTimeoutEnd${PlayerId}`, Date.now() + 60 * 60000 * options.customTimeRate * options.abuBlessRate);
                } else {
                    GM_deleteValue(`thiefTimeoutEnd${PlayerId}`);
                }
                break;
            case "hunt":
                setHuntTimeout();
                break;
            case "merc":
                setMercTimeout(result);
                break;
        }
        if(result == "win" && options.battleType != "leader") {
            updateOption("enrollNumber", "0");
        }
        updateOption("battleType", "");
        GM_deleteValue(`HoldBattle${PlayerId}`);
    }
}
function setTexts() {
    const options = JSON.parse(GM_getValue(`hwmTimersOptions${PlayerId}`));
    var obj;
    if(isEn) {
        obj = {
            healthNotificationEnabled: 'Army restore alarm on',
            onceHealthNotificationEnabled: 'Alarm once at army restore',
            workNotificationEnabled: 'Workshift alarm on',
            notificationDisabled: 'Alarm off',
            smithNotificationEnabled: 'Blacksmith alarm on',
            mercNotificationEnabled: 'Mercenaries Guild alarm on',
            regexp_timegn0: /Come back in (\d+) minutes\./,
            regexp_timegn1: /\. Time left: (\d+) minutes\./,
            regexp_timegn2: /ou have (\d+) minutes left/,
            regexp_timegn3: /\. Time left: (\d+) minutes\./,
            regexp_timegn4: /still have (\d+) minutes/,
            regexp_timegn5: /you still have \d+ attempts and (\d+) minutes/,
            huntNotificationEnabled: 'Hunters Guild alarm on',
            regexp_go_timer: 'Next hunt available in',
            thiefNotificationEnabled: 'Thieves Guild alarm on',
            rangerNotificationEnabled: 'Rangers Guild alarm on',
            regexp_timegre: /Come in (\d+) min/,
            time_home: /You may enroll again in (\d+) min/,
            workPlace: 'Work place:',
            alert_health: 'Troops ready: 100%',
            workMessage: 'LG: You may enroll again',
            smithMessage: 'BS: Blacksmith works are finished',
            mercMessage: 'MG: Mercenaries Guild has a quest for you',
            huntMessage: 'HG: You notice traces ...',
            thiefMessage: options.thiefOrRanger == '0' ? 'TG: You may set an ambush' : 'RG: Rangers Guild has a quest for you',
            audio_file: 'Audio file ',
            alarm_mode: '<b>Timer alarm mode</b>:',
            alarm_mode_sound: 'audio',
            alarm_mode_alert: 'message',
            alarm_mode_both: 'notification',
            alarm_mode_none: 'both',
            h_t: 'health',
            gonv_t: 'MHT(R)G',
            workTimerPanelCaption: 'LG',
            smithTimerPanelCaption: 'BS',
            smithWelcome: 'To Blacksmith',
            mercTimerPanelCaption: 'MG',
            mercWelcome: 'To Mercenaries\' Guild',
            huntTimerPanelCaption: 'HG',
            huntTimerPanelTitle: 'To Hunters\' Guild',
            thiefTimerPanelCaption: 'TG',
            thiefWelcome: 'To Thieves\' Guild',
            rangerTimerPanelCaption: 'RG',
            rangerWelcome: 'To Rangers Guild post',
            manaWelcome: 'Settings',
            successfullyEnrolled: 'You have successfully enrolled',
            currentlyUnemployed: 'You are currently unemployed',
            regexp_map_go: 'During the journey you have access to the',
            huntLicenseExpirationMessage: 'The license expires ',
            alert_go_lic_exp: 'HG: Hunter license has expired',
            alert_prem_exp: 'Abu-Bakir\'s Charm has expired',
            st_start: 'All settings adjustments will apply after page is reloaded',
            st_null_timers: 'Reset all timers',
            st_gv_n_time: 'Set TG/RG timer for once to',
            st_percent_faster: 'Quests HG, MG, TG, RG more often',
            joinRangerBattleText: 'Immediately initiate Rangers\' guild battle on arrival',
            st_show_timers: 'Show timers:',
            st_work_trudogolik_show: 'Notify about workaholic penalty only 2 workshifts away',
            st_work_trudogolik_off: 'Turn off all notifications on workaholic penalty',
            st_predupr_go_lic: '<b>Hunter license</b> is detected automatically in Hunters\' Guild',
            st_go_timer_hide: 'Hide',
            st_disable_multiple_alarms: 'Disable repeat signals for ' + disable_alarm_delay + ' sec',
            workaholic_penalty: 'Workaholic penalty',
            regexp_sm: /Completion time: (\d+)-(\d+) (\d+):(\d+)/,
            uze_ustroen: 'You are already employed\.',
            uze_ustroen2: 'Less than one hour passed since last enrollment\. Please wait\.',
            uze_ustroen3: 'No vacancies\.'
        };
    } else {
        obj = {
            healthNotificationEnabled: 'Будет предупреждение о восстановлении армии',
            onceHealthNotificationEnabled: 'Установить единоразово предупреждение о восстановлении армии',
            workNotificationEnabled: 'Будет предупреждение о конце рабочего часа',
            notificationDisabled: 'Не будет предупреждения',
            smithNotificationEnabled: 'Будет предупреждение о завершении работ в Кузнице',
            mercNotificationEnabled: 'Будет предупреждение Гильдии Наемников',
            regexp_timegn0: /Приходи через (\d+) мин/,
            regexp_timegn1: /Осталось времени: (\d+) минут/,
            regexp_timegn2: /тебя осталось (\d+) минут/,
            regexp_timegn3: /у тебя еще есть (\d+) минут/,
            regexp_timegn4: /\. Осталось (\d+) минут\./,
            regexp_timegn5: /осталось \d+ попыток и (\d+) минут/,
            huntNotificationEnabled: 'Будет предупреждение Гильдии Охотников',
            regexp_go_timer: 'Следующая охота будет доступна через',
            thiefNotificationEnabled: 'Будет предупреждение Гильдии Воров',
            rangerNotificationEnabled: 'Будет предупреждение Гильдии Рейнджеров',
            regexp_timegre: /приходи через (\d+) мин/,
            time_home: /Вы можете устроиться на работу через (\d+)/,
            workPlace: 'Место работы:',
            alert_health: 'Готовность армии: 100%',
            workMessage: 'ГР: Пора на работу',
            smithMessage: 'ГК: Работа в Кузнице завершена',
            mercMessage: 'ГН: Для Вас есть задание в Гильдии Наемников',
            huntMessage: 'ГО: Вы увидели следы ...',
            thiefMessage: options.thiefOrRanger == '0' ? 'ГВ: Вы можете устроить засаду' : 'ГРж: Есть задание в Гильдии Рейнджеров',
            audio_file: 'Звук сигнала ',
            alarm_mode: '<b>Режим оповещения</b> окончания таймера:',
            alarm_mode_sound: 'звук',
            alarm_mode_alert: 'сообщение',
            alarm_mode_both: 'оповещение',
            alarm_mode_none: 'отключен',
            h_t: 'здоровья',
            gonv_t: 'ГОНВ(Рж)',
            workTimerPanelCaption: 'ГР',
            smithTimerPanelCaption: 'ГК',
            smithWelcome: 'В Кузницу',
            mercTimerPanelCaption: 'ГН',
            mercWelcome: 'В здание Гильдии Наемников',
            huntTimerPanelCaption: 'ГО',
            huntTimerPanelTitle: 'В здание Гильдии Охотников',
            thiefTimerPanelCaption: 'ГВ',
            thiefWelcome: 'В здание Гильдии Воров',
            rangerTimerPanelCaption: 'ГРж',
            rangerWelcome: 'В здание Гильдии Рейнджеров',
            manaWelcome: 'Настройки',
            successfullyEnrolled: 'Вы устроены на работу',
            currentlyUnemployed: 'Вы нигде не работаете',
            regexp_map_go: 'Во время пути Вам доступны',
            huntLicenseExpirationMessage: 'Лицензия истекает ',
            alert_go_lic_exp: 'ГО: Лицензия охотника истекла',
            alert_prem_exp: 'Благословение Абу-Бекра истекло',
            workaholic_penalty: 'Штраф трудоголика',
            regexp_sm: /Завершение работы: (\d+)-(\d+) (\d+):(\d+)/,
            st_start: 'Все изменения будут видны после перезагрузки страницы',
            st_null_timers: 'Обнулить все таймеры',
            st_gv_n_time: 'Единоразово установить таймер ГВ/ГРж равным',
            st_percent_faster: 'Задания ГО, ГН, ГВ, ГРж чаще на',
            joinRangerBattleText: 'По прибытии вступать в бои Гильдии Рейнджеров',
            st_show_timers: '<b>Отображать:</b>',
            st_work_trudogolik_show: '<b>Показывать</b> штраф трудоголика только <b>за 2 часа</b>',
            st_work_trudogolik_off: '<b>Отключить</b> ВСЕ уведомления о штрафе трудоголика',
            st_predupr_go_lic: '<b>Лицензия охотника</b> определяется автоматически (в Гильдии Охотников)',
            st_go_timer_hide: '<b>Скрывать</b>',
            st_disable_multiple_alarms: 'Запретить повторные сигналы в течении ' + disable_alarm_delay + ' секунд',
            uze_ustroen: 'Вы уже устроены\.',
            uze_ustroen2: 'Прошло меньше часа с последнего устройства на работу\. Ждите\.',
            uze_ustroen3: 'Нет рабочих мест\.'
        };
    }
    return obj;
}
function createTimersPanel() {
    const shContainer = document.querySelector("div.sh_container");
    const sh_MenuPanel = document.querySelector("div.sh_MenuPanel");

    const dragonLeft = document.querySelector("img[src*='i/top'][src*='/dragon__left']"); // https://dcdn1.heroeswm.ru/i/top_ny_rus/dragon__left_.jpg
    const dragonRight = document.querySelector("img[src*='i/top'][src*='/dragon__right']");

    //document.querySelector("img.mm_decor1") // https://dcdn.heroeswm.ru/i/new_top_ny/mm_decor1.png

    let folder = "https://dcdn2.heroeswm.ru/i/top_ny_rus/line/";
    const img_link = document.querySelector("img[src*='i/top'][src*='/line/t_end']");
    if(img_link) {
        folder = /(\S*\/line\/)/.exec(img_link.src)[1];
    }
    const newYearSuffix = document.querySelector("img[src*='i/top_ny']") || document.querySelector("img[src*='i/new_top_ny']") ? "_" : ""; // если новый год
    //console.log(`folder: ${folder}, newYearSuffix: ${newYearSuffix}, ${folder}t_end${newYearSuffix}.jpg`); // folder: https://dcdn2.heroeswm.ru/i/top_ny_rus/line/, newYearSuffix: _

    let container = dragonLeft?.parentNode || sh_MenuPanel;
    let timersContainerWidth;
    let zIndex = 0;
    let leftMargin = 0;
    let topMargin = 0;
    let height;
    if(isNewInterface) {
        timersContainerWidth = shContainer.getBoundingClientRect().width - 30 * 2;
        topMargin = -67;
        leftMargin = 159;
        zIndex = 2;
        height = 26;
        //GM_addStyle(`.noTransition { -webkit-transition: none !important; -moz-transition: none !important; -o-transition: none !important; transition: none !important; }`);
        //Array.from(document.querySelectorAll(".mm_item")).forEach(x => { x.classList.add('noTransition'); x.style.marginTop = "18px"; }); window.innerHeight; // Не получилось убрать плавный сдвиг меню вниз
        document.querySelector("div#ResourcesPanel").style.height = "20px";
    } else {
        timersContainerWidth = dragonRight.getBoundingClientRect().left - dragonLeft.getBoundingClientRect().left + 124;
        topMargin = -26;
        leftMargin = -43;
        height = 26;
    }
    let timersHtml = `
<style>
    .hwm_tb * {font-size: 11px; color: #f5c137;}
    .hwm_tb_cell {border-collapse: collapse; background-color: #6b6b69;}
    .hwm_tb_cell TD {padding: 0px;}
    .cell_t {height: 3px; background: url(${folder}t_top_bkg${newYearSuffix}.jpg);}
    .cell_c {white-space: nowrap; height: 18px; background: url(${folder}t_com_bkg${newYearSuffix}.jpg); font-weight: bold;}
    .cell_b {height: 5px; background: url(${folder}t_bot_bkg${newYearSuffix}.jpg); text-align: center;}
    .cell_b IMG {width: 17px; height: 5px;}
</style>
<table cellpadding=0 cellspacing=0 align="center" class="hwm_tb" style="width: ${timersContainerWidth}px;">
    <tr>
        <td>
            <table width="100%" cellpadding=0 cellspacing=0 style="background: url(${folder}t_bkg${newYearSuffix}.jpg);">
                <tr valign=middle align=center>
                    <td width=5 style="overflow: hidden;">
                        <img src="${folder}t_end${newYearSuffix}.jpg" alt="" width=9 height=${height}px style="margin:0px 0px 0px -4px;">
                    </td>`;
    const timersWidths = { "health": { width: 44 }, "mana": { width: 44, closeWidth: "5", closeStyle: 'style="overflow: hidden;"', closeImageStyle: 'style="margin:0px -4px 0px 0px;"' } };
    for(const tag of timerNames) {
        const timerName = `${tag}Timer`;
        timersHtml += `
                    <td id="${timerName}Cell" ${timersWidths[tag] ? `width: ${timersWidths[tag].width}px` : ""}>
                        <table class="hwm_tb_cell">
                            <tr>
                                <td class="cell_t" />
                            </tr>
                            <tr>
                                <td class="cell_c">
                                    <span style="cursor: pointer;" id="${timerName}PanelCaption">${texts[`${timerName}PanelCaption`] ? texts[`${timerName}PanelCaption`] + ":" : ""}</span>
                                    <a id="${timerName}Panel" href="javascript: void(0);" style="text-decoration: none;">00:00</a>
                                </td>
                            </tr>
                            <tr>
                                <td class="cell_b">
                                    <img src="${folder}t_center${newYearSuffix}.jpg">
                                </td>
                            </tr>
                        </table>
                    </td>
                    <td id="${timerName}CloseCell" width="${timersWidths[tag]?.closeWidth || "9"}px" ${timersWidths[tag]?.closeStyle || ""}>
                        <img src="${folder}t_end${newYearSuffix}.jpg" width=9 height="${height}px" ${timersWidths[tag]?.closeImageStyle || ""}>
                    </td>

`;
}
    timersHtml += `
                </tr>
            </table>
        </td>
    </tr>
</table>`;
    const timersPanel = addElement("div", container, { style: `position: absolute; margin: ${topMargin}px 0px 0px ${leftMargin}px; text-align: center; z-index: ${zIndex};`, innerHTML: timersHtml });
    timersPanel.querySelector('#healthTimerPanel').addEventListener("click", function() { updateOption("healthNotification", x => x.healthNotification == 'yes' ? 'no' : 'yes'); timersPanelDataBind(); });
    timersPanel.querySelector('#workTimerPanelCaption').addEventListener("click", function() { updateOption("workNotification", x => x.workNotification == 'yes' ? 'no' : 'yes'); timersPanelDataBind(); });
    timersPanel.querySelector('#smithTimerPanelCaption').addEventListener("click", function() { updateOption("smithNotification", x => x.smithNotification == 'yes' ? 'no' : 'yes'); timersPanelDataBind(); });
    timersPanel.querySelector('#mercTimerPanelCaption').addEventListener("click", function() { updateOption("mercNotification", x => x.mercNotification == 'yes' ? 'no' : 'yes'); timersPanelDataBind(); });
    timersPanel.querySelector('#huntTimerPanelCaption').addEventListener("click", function() { updateOption("huntNotification", x => x.huntNotification == 'yes' ? 'no' : 'yes'); timersPanelDataBind(); });
    timersPanel.querySelector('#thiefTimerPanelCaption').addEventListener("click", function() { updateOption("thiefNotification", x => x.thiefNotification == 'yes' ? 'no' : 'yes'); timersPanelDataBind(); });
    timersPanel.querySelector("#manaTimerPanel").addEventListener("click", settings);
}
function timersPanelDataBind() {
    const options = JSON.parse(GM_getValue(`hwmTimersOptions${PlayerId}`));
    const healthTimerPanel = document.getElementById('healthTimerPanel');
    healthTimerPanel.style.color = options.healthNotification == 'yes' ? '#ff9c00' : '#f5c137';
    healthTimerPanel.title = options.healthNotification == 'yes' ? texts.healthNotificationEnabled : texts.onceHealthNotificationEnabled;

    const workTimerPanelCaption = document.getElementById('workTimerPanelCaption');
    workTimerPanelCaption.style.color = options.workNotification == 'yes' ? '#FF0000' : '#f5c137';
    workTimerPanelCaption.title = options.workNotification == 'yes' ? texts.workNotificationEnabled : texts.notificationDisabled;
    const workTimerPanel = document.getElementById('workTimerPanel');
    let workTimerPanelTtitle = options.abuBlessInfo;
    if(options.enrollNumber) {
        if(workTimerPanelTtitle) {
            workTimerPanelTtitle += '\n';
        }
        workTimerPanelTtitle += texts.workaholic_penalty + ": " + (9 - Number(options.enrollNumber));
        if(options.disableWorkaholicAlarm == '0' && options.enrollNumber > 8) {
            workTimerPanel.style.color = '#ff9c00';
        }
    }
    workTimerPanel.title = workTimerPanelTtitle;
    if(GM_getValue(`LastWorkObjectId${PlayerId}`)) {
        workTimerPanel.href = `object-info.php?id=${GM_getValue(`LastWorkObjectId${PlayerId}`)}`;
    }

    const smithTimerPanelCaption = document.getElementById('smithTimerPanelCaption');
    smithTimerPanelCaption.style.color = options.smithNotification == 'yes' ? '#FF0000' : '#f5c137';
    smithTimerPanelCaption.title = options.smithNotification == 'yes' ? texts.smithNotificationEnabled : texts.notificationDisabled;
    const smithTimerPanel = document.getElementById('smithTimerPanel');
    smithTimerPanel.href = "/mod_workbench.php?type=repair";
    smithTimerPanel.title = texts.smithWelcome;
    
    const mercTimerPanelCaption = document.getElementById('mercTimerPanelCaption');
    mercTimerPanelCaption.style.color = options.mercNotification == 'yes' ? '#FF0000' : '#f5c137';
    mercTimerPanelCaption.title = options.mercNotification == 'yes' ? texts.mercNotificationEnabled : texts.notificationDisabled;
    const mercTimerPanel = document.getElementById('mercTimerPanel');
    mercTimerPanel.href = "/mercenary_guild.php";
    mercTimerPanel.title = texts.mercWelcome;

    const huntTimerPanelCaption = document.getElementById('huntTimerPanelCaption');
    huntTimerPanelCaption.innerText = texts.huntTimerPanelCaption;
    huntTimerPanelCaption.style.color = options.huntNotification == 'yes' ? '#FF0000' : '#f5c137';
    huntTimerPanelCaption.title = options.huntNotification == 'yes' ? texts.huntNotificationEnabled : texts.notificationDisabled;
    const huntTimerPanel = document.getElementById('huntTimerPanel');
    let huntTimerPanelTitle = texts.huntTimerPanelTitle;
    if(texts.huntLicenseText) {
        huntTimerPanelTitle += '\n' + texts.huntLicenseExpirationMessage + options.huntLicenseText;
    }
    huntTimerPanel.href = "/hunter_guild.php";
    huntTimerPanel.title = huntTimerPanelTitle;

    const thiefTimerPanelCaption = document.getElementById('thiefTimerPanelCaption');
    thiefTimerPanelCaption.style.color = options.thiefNotification == 'yes' ? '#FF0000' : '#f5c137';
    thiefTimerPanelCaption.title = options.thiefNotification == 'yes' ? (options.thiefOrRanger == '0' ? texts.thiefNotificationEnabled : texts.rangerNotificationEnabled) : texts.notificationDisabled;
    thiefTimerPanelCaption.innerText = options.thiefOrRanger == '0' ? texts.thiefTimerPanelCaption : texts.rangerTimerPanelCaption;
    const thiefTimerPanel = document.getElementById('thiefTimerPanel');
    thiefTimerPanel.title = options.thiefOrRanger == '0' ? texts.thiefWelcome : texts.rangerWelcome;
    thiefTimerPanel.href = options.thiefOrRanger == '0' ? "/thief_guild.php" : "/ranger_guild.php"
    
    const manaTimerPanel = document.getElementById('manaTimerPanel');
    manaTimerPanel.title = texts.manaWelcome;

    document.getElementById("workTimerCell").style.display = document.getElementById("workTimerCloseCell").style.display = (options.isShowWorkTimer == "1" ? '' : "none");
    document.getElementById("smithTimerCell").style.display = document.getElementById("smithTimerCloseCell").style.display = (options.isShowSmithTimer == "1" ? '' : "none");
    document.getElementById("mercTimerCell").style.display = document.getElementById("mercTimerCloseCell").style.display = (options.isShowMercTimer == "1" ? '' : "none");
    document.getElementById("huntTimerCell").style.display = document.getElementById("huntTimerCloseCell").style.display = (options.isShowHuntTimer == "1" ? '' : "none");
    document.getElementById("thiefTimerCell").style.display = document.getElementById("thiefTimerCloseCell").style.display = (options.isShowThiefTimer == "1" ? '' : "none");
}
function timersDataBind() { timerNames.forEach(x => document.getElementById(`${x}TimerPanel`).innerHTML = secondsFormat(getSecondsLeft(x))); }
function checkWork() {
    if(location.pathname == '/object_do.php' || location.pathname == '/object-info.php') {
        if(document.body.innerHTML.match(texts.successfullyEnrolled)) {
            setWorkTimeoutEnd();
        }
    }
    if(location.pathname == '/home.php') {
        if(document.body.innerHTML.match(texts.currentlyUnemployed)) {
            GM_deleteValue(`workTimeoutEnd${PlayerId}`);
            return;
        }
        const workObjectRef = Array.from(document.querySelectorAll("a[href^='object-info.php']")).find(x => getParent(x, "td").innerHTML.includes(isEn ? "Currently employed at:" : "Место работы:"))
        const workObjectId = workObjectRef ? getUrlParamValue(workObjectRef.href, "id") : "";
        // подхватывание времени окончания работы с home.php и его проверка
        var time_home_time = texts.time_home.exec(document.body.innerHTML);
        if(time_home_time) {
            setWorkTimeoutEnd(getServerTime() + Number(time_home_time[1]) * 60000, workObjectId);
        } else {
            time_home_time = new RegExp(` ${isEn ? "since" : "с"} (\\d{1,2}:\\d{1,2})`).exec(document.body.innerHTML);
            if(time_home_time) {
                setWorkTimeoutEnd(parseDate(time_home_time[1], false, true).getTime() + 60 * 60 * 1000, workObjectId);
            }
        }
    }
}
function setWorkTimeoutEnd(workTimeoutEnd, workObjectId) {
    workTimeoutEnd = workTimeoutEnd || Date.now() + 60 * 60000;
    workObjectId = workObjectId || getUrlParamValue(location.href, "id");
    const oldValue = parseInt(GM_getValue(`workTimeoutEnd${PlayerId}`, 0));
    if(Math.abs(oldValue - workTimeoutEnd) > 70000) {
        GM_setValue(`workTimeoutEnd${PlayerId}`, workTimeoutEnd);
        GM_setValue(`LastWorkObjectId${PlayerId}`, workObjectId);
        updateOption("enrollNumber", x => Number(x.enrollNumber) + 1);
    }
}
function checkPremiumAccount() {
    const options = JSON.parse(GM_getValue(`hwmTimersOptions${PlayerId}`));
    // проверка наличия эффекта блага АБУ Бекра (премиум аккаунт) // skipn=1 это 'Ознакомился' или 'Got it!'
    if(location.pathname == '/home.php' && document.querySelector("img[src*='i/icons/attr_defense.png']") && !document.querySelector("a[href*='home.php?skipn=1']")) {
        const starImage = document.querySelector("img[src$='i/star_extend.png']") || document.querySelector("img[src$='i/star.png']");
        options.abuBlessRate = starImage ? "0.7" : "1";
        options.abuBlessExpirationTime = '0';
        options.abuBlessInfo = starImage ? starImage.title : '';
        if(starImage) {
            starImage.align = "absmiddle";
            const time_prem = /(\d+-\d+-\d+ \d+:\d+)/.exec(starImage.title);
            if(time_prem) {
                const abuEnd = parseDate(time_prem[1], true);
                console.log(abuEnd);
                options.abuBlessExpirationTime = abuEnd.getTime();
            }
        }
    }
    if(options.abuBlessInfo && Number(options.abuBlessExpirationTime) < serverNow()) {
        options.abuBlessRate = '1';
        options.abuBlessExpirationTime = '0';
        options.abuBlessInfo = '';
    }
    GM_setValue(`hwmTimersOptions${PlayerId}`, JSON.stringify(options));
}
function checkHuntLicense() {
    const options = JSON.parse(GM_getValue(`hwmTimersOptions${PlayerId}`));
    var form_f2 = document.querySelector("form[name='f2']");
    if(location.pathname == '/hunter_guild.php' && form_f2) {
        while(form_f2.tagName != 'TR') {
            form_f2 = form_f2.parentNode;
        }
        options.huntLicenseRate = '1';
        options.huntLicenseExpirationTime = '0';
        options.huntLicenseText = '';
        if(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/.exec(form_f2.innerHTML)) {
            if(!form_f2.querySelector("input[type='submit'][onclick*='confirm']")) {
                // лицензия МО
                options.huntLicenseRate = '' + (50 / 100);
            } else {
                // лицензия О
                options.huntLicenseRate = '' + (75 / 100);
            }
            const forms = form_f2.querySelectorAll("td");
            var time_lic_mo_max = 0;
            for(const form of forms) {
                if(form.innerHTML.indexOf("<td") != -1) {
                    continue;
                }
                var time_lic_mo = /(\d+-\d+-\d+ \d+:\d+)/.exec(form.innerHTML);
                if(time_lic_mo) {
                    const licEndTime = parseDate(time_lic_mo[1], true).getTime();
                    if(licEndTime > time_lic_mo_max) {
                        time_lic_mo_max = licEndTime;
                        options.huntLicenseExpirationTime = licEndTime;
                        options.huntLicenseText = time_lic_mo[0];
                    }
                }
            }
        }
    }
    if(options.huntLicenseText && Number(options.huntLicenseExpirationTime) < serverNow()) {
        // лицензия охотника истекла
        options.huntLicenseRate = '1';
        options.huntLicenseExpirationTime = '0';
        options.huntLicenseText = '';
    }
    GM_setValue(`hwmTimersOptions${PlayerId}`, JSON.stringify(options));
}
function setHuntTimeout(restSeconds, rate = 1) {
    const options = JSON.parse(GM_getValue(`hwmTimersOptions${PlayerId}`));
    restSeconds = restSeconds || ((getServerDateTime().getHours() < 8 ? 20 : 40) * rate * 60 * options.customTimeRate * options.abuBlessRate * options.huntLicenseRate);
    GM_setValue(`huntTimeoutEnd${PlayerId}`, Date.now() + restSeconds * 1000);
}
function skipHunt() {
    setHuntTimeout(undefined, 0.5);
    const map_hunt_block_div = document.querySelector("div#map_hunt_block_div");
    if(map_hunt_block_div) {
        observe(map_hunt_block_div, hideNativeHuntTimerPanel, true);
    }
}
function checkWorkaholic() {
    if(location.pathname == '/object-info.php') {
        var parent_trud = document.querySelector("a[href*='objectworkers.php']");
        if(parent_trud) {
            const options = JSON.parse(GM_getValue(`hwmTimersOptions${PlayerId}`));
            const workaholicPenaltyExec = new RegExp(`\\*\\&nbsp;(0\\.?\\d?) ${isEn ? "workaholic penalty" : "штраф трудоголика"}`).exec(document.body.innerHTML);
            // отработано смен
            let enrollNumber = Number(options.enrollNumber);
            if(workaholicPenaltyExec) {
                var workaholicPenalty = Number(workaholicPenaltyExec[1]);
                if(workaholicPenalty == 0.8) {
                    enrollNumber = 9;
                } else if(workaholicPenalty == 0.7) {
                    enrollNumber = 10;
                } else if(workaholicPenalty == 0.6) {
                    enrollNumber = 11;
                } else if(workaholicPenalty == 0.5) {
                    enrollNumber = 12;
                } else if(workaholicPenalty == 0.4) {
                    enrollNumber = 13;
                } else if(workaholicPenalty == 0.2) {
                    enrollNumber = 14;
                } else if(workaholicPenalty == 0.1 && enrollNumber < 15) {
                    enrollNumber = 15;
                } else if(workaholicPenalty == 0 && enrollNumber < 48) {
                    enrollNumber = 48;
                }
            } else if(enrollNumber > 8) {
                enrollNumber = 8;
            }
            console.log(`oldEnrollNumber: ${options.enrollNumber}, enrollNumber: ${enrollNumber}, workaholicPenalty: ${workaholicPenalty}`);
            updateOption("enrollNumber", enrollNumber);
            if(options.disableWorkaholicAlarm == '0' && (options.showWorkaholicAlarmLastTwoEnrolls == '0' || enrollNumber >= 7)) {
                let alarmText = "";
                const limits = [9, 11, 48];
                if(enrollNumber < limits[0]) {
                    alarmText = isEn ? `Workaholic penalty through ${limits[0] - enrollNumber} enrollments` : `Штраф трудоголика через ${limits[0] - enrollNumber} устройств`;
                } else if(enrollNumber < limits[1]) {
                    alarmText = isEn ? `It will be impossible to get a job in production through ${limits[1] - enrollNumber} enrollments` : `Невозможно устроиться на производство будет через ${limits[1] - enrollNumber} устройств`;
                } else if(enrollNumber < limits[2]) {
                    alarmText = isEn ? `The opportunity to get a job is ending through ${limits[2] - enrollNumber} enrollments` : `Возможность устраиваться на работу заканчивается через ${limits[2] - enrollNumber} устройств`;
                }
                const add_trud = document.createElement('span');
                if(enrollNumber >= 7) {
                    add_trud.setAttribute('style', 'color: red; font-weight: bold;'); // выделить цветом
                }
                add_trud.innerHTML = alarmText;
                parent_trud = parent_trud.parentNode.previousSibling.previousSibling;
                parent_trud.parentNode.insertBefore(add_trud, parent_trud);
            }
            // замена "Уже устроен"
            parent_trud = document.querySelector("a[href*='objectworkers.php']").parentNode.parentNode;
            if(Date.now() > parseInt(GM_getValue(`workTimeoutEnd${PlayerId}`, 0)) && (parent_trud.innerHTML.match(texts.uze_ustroen) || (texts.uze_ustroen = parent_trud.innerHTML.match(texts.uze_ustroen2)) || (texts.uze_ustroen = parent_trud.innerHTML.match(texts.uze_ustroen3)))) {
                parent_trud.innerHTML = parent_trud.innerHTML.replace(texts.uze_ustroen, '<style>@-webkit-keyframes blink {80% {opacity:0.0;}} @-moz-keyframes blink {80% {opacity:0.0;}} @-o-keyframes blink {80% {opacity:0.0;}} @keyframes blink {80% {opacity:0.0;}}</style><font color=blue style="-webkit-animation: blink 1s steps(1,end) 0s infinite; -moz-animation: blink 1s steps(1,end) 0s infinite; -o-animation: blink 1s steps(1,end) 0s infinite; animation: blink 1s steps(1,end) 0s infinite"><b>' + texts.uze_ustroen + '</b></font>');
            }
        }
    }
}
function checkMercenary() {
    if(location.pathname == '/mercenary_guild.php') {
        const mercReputation = parseFloat(new RegExp(`${isEn ? "Reputation" : "Репутация"}: <b>([\\d\\.]+)`).exec(document.body.innerHTML)[1]);
        GM_setValue(`mercReputation${PlayerId}`, mercReputation);
        const options = JSON.parse(GM_getValue(`hwmTimersOptions${PlayerId}`));
        const mercTimeout = (40 - mercReputation * 2) * options.customTimeRate * options.abuBlessRate * 60000;
        let newMercTaskRestTimeExec;
        if(document.querySelector("a[href^='/mercenary_guild.php?action=accept']")) {
            GM_deleteValue(`mercTimeoutEnd${PlayerId}`);
        } else if((newMercTaskRestTimeExec = texts.regexp_timegn0.exec(document.body.innerHTML)) || (newMercTaskRestTimeExec = texts.regexp_timegn1.exec(document.body.innerHTML)) || (newMercTaskRestTimeExec = texts.regexp_timegn2.exec(document.body.innerHTML)) || (newMercTaskRestTimeExec = texts.regexp_timegn3.exec(document.body.innerHTML)) || (newMercTaskRestTimeExec = texts.regexp_timegn4.exec(document.body.innerHTML)) || (newMercTaskRestTimeExec = texts.regexp_timegn5.exec(document.body.innerHTML))) {
            let restTimeout = Number(newMercTaskRestTimeExec[1]);
            if(texts.regexp_timegn0.exec(document.body.innerHTML) && (restTimeout == 19 || restTimeout == 13)) {
                restTimeout++;
            }
            setMercTimeout(null, restTimeout * 60000);
        }
    }
    if(location.pathname == "/map.php") {
        const mercTaskTable = getParent(document.querySelector("table.wbwhite center a[href='mercenary_guild.php']"), "table");
        if(mercTaskTable) {
            const execTaskButton = mercTaskTable.querySelector("div[id^='hunt_but_']");
            if(execTaskButton) {
                execTaskButton.addEventListener("click", function() { updateOption("battleType", "merc"); });
            }
        }
    }
}
function setMercTimeout(battleResult = null, timeout = undefined) {
    console.log(`battleResult: ${battleResult}, timeout: ${timeout}`);
    let mercReputation = parseFloat(GM_getValue(`mercReputation${PlayerId}`, 0));
    if(battleResult == "win") {
        mercReputation = Math.min(mercReputation + 0.5, 10);
        GM_setValue(`mercReputation${PlayerId}`, mercReputation);
    }
    if(battleResult == "fail") {
        mercReputation = Math.max(mercReputation - 1, 0);
        GM_setValue(`mercReputation${PlayerId}`, mercReputation);
    }
    const options = JSON.parse(GM_getValue(`hwmTimersOptions${PlayerId}`));
    timeout = timeout || (40 - mercReputation * 2) * options.customTimeRate * options.abuBlessRate * 60000;
    const newTimeoutEnd = Date.now() + timeout;
    if(Math.abs(parseInt(GM_getValue(`mercTimeoutEnd${PlayerId}`, 0)) - newTimeoutEnd) > 70000) {
        GM_setValue(`mercTimeoutEnd${PlayerId}`, newTimeoutEnd);
    }
}
function checkRangerGuild() {
    if(location.pathname == '/ranger_guild.php') {
        if(document.querySelector("a[href^='ranger_guild.php?action=accept']")) {
            GM_deleteValue(`thiefTimeoutEnd${PlayerId}`);
            updateOption("thiefOrRanger", "1");
        }
        var time_gv = texts.regexp_timegre.exec(document.body.innerHTML);
        if(time_gv) {
            time_gv = Number(time_gv[1]) * 60000; // в миллисекундах
            const now = Date.now();
            var time_gv_temp = time_gv - Math.abs(parseInt(GM_getValue(`thiefTimeoutEnd${PlayerId}`, 0)) - now);
            if(Math.abs(time_gv_temp) > 70000) {
                GM_setValue(`thiefTimeoutEnd${PlayerId}`, now + time_gv)
                updateOption("thiefOrRanger", "1");
            }
        }
    }
    if(location.pathname == '/ranger_list.php') {
        var link_ranger_attack = document.querySelectorAll("a[href^='ranger_attack.php?join']");
        if(link_ranger_attack.length > 0) {
            GM_deleteValue(`thiefTimeoutEnd${PlayerId}`)
            updateOption("thiefOrRanger", "1");
            for(const link_ranger_attackItem of link_ranger_attack) {
                link_ranger_attackItem.addEventListener("click", function() { updateOption("battleType", 'thief'); });
            }
        }
    }
}
function checkModWorkebench() {
    if(location.pathname == '/mod_workbench.php') {
        parseSmithPage(document);
    }
}
function parseSmithPage(doc) {
    const allb = doc.querySelectorAll("b");
    for(const bold of allb) {
        if(bold.innerText.includes(isEn ? "Under repair" : "В ремонте")) {
            var repairData = bold.innerText;
            break;
        }
    }
    if(repairData) {
        const restRepairTime = { Hours: 0, Minutes: 0, Seconds: 59 };
        //В ремонте: еще 1 ч. 31 мин. //Under repair another 1 h. 17 min.
        const hoursRegex = new RegExp(`(\\d+) ${isEn ? "h" : "ч"}\\.`);
        const hoursRegexResult = hoursRegex.exec(repairData);
        if(hoursRegexResult) {
            restRepairTime.Hours = parseInt(hoursRegexResult[1]);
        }
        const minutesRegex = new RegExp(`(\\d+) ${isEn ? "min" : "мин"}\\.`);
        const minutesRegexResult = minutesRegex.exec(repairData);
        if(minutesRegexResult) {
            restRepairTime.Minutes = parseInt(minutesRegexResult[1]);
        }
        //console.log(repairData);
        //console.log(restRepairTime);
        const repairEnd = new Date();
        repairEnd.setHours(repairEnd.getHours() + restRepairTime.Hours);
        repairEnd.setMinutes(repairEnd.getMinutes() + restRepairTime.Minutes);
        repairEnd.setSeconds(repairEnd.getSeconds() + restRepairTime.Seconds);
        //console.log(repairEnd);

        const savedRepairEnd = GM_getValue(`smithTimeoutEnd${PlayerId}`);
        if(!savedRepairEnd || Math.abs(repairEnd.getTime() - parseInt(savedRepairEnd)) / 1000 / 60 > 2) {
            GM_setValue(`smithTimeoutEnd${PlayerId}`, repairEnd.getTime());
        }
    } else {
        GM_deleteValue(`smithTimeoutEnd${PlayerId}`);
    }
}
function checkThiefAmbush() {
    const thief_ambush_cancel = document.querySelector("a[href^='thief_ambush_cancel.php']");
    const form_thief_ambush = document.querySelector("form[action='thief_ambush.php']");
    if(thief_ambush_cancel || form_thief_ambush) {
        GM_deleteValue(`thiefTimeoutEnd${PlayerId}`);
        updateOption("thiefOrRanger", "0");
    }
    if(thief_ambush_cancel) {
        updateOption("battleType", "thief"); // Сидим в засаде, будет воровской бой
    }
}
function checkRangerAmbush() {
    var form_ranger_attack = document.querySelector("form[action='ranger_attack.php']");
    if(form_ranger_attack) {
        GM_deleteValue(`thiefTimeoutEnd${PlayerId}`);
        updateOption("thiefOrRanger", "1");
        form_ranger_attack.querySelector("input[type='submit']").addEventListener("click", function() { updateOption("battleType", 'thief'); });
        const options = JSON.parse(GM_getValue(`hwmTimersOptions${PlayerId}`));
        if(options.joinRangerBattle == '1') {
            setTimeout(function() { updateOption("battleType", 'thief'); form_ranger_attack.submit(); }, 500);
        }
    }
}
function checkMapHunter() {
    const map_hunt_block_div = document.querySelector("div#map_hunt_block_div");
    if(map_hunt_block_div) {
        const nativeHuntTimer = map_hunt_block_div.querySelector("div#next_ht_new");
        if(nativeHuntTimer) {
            setHuntTimeout(windowObject.MapHunterDelta);
        } else {
            GM_deleteValue(`huntTimeoutEnd${PlayerId}`);
            const attackButtons = map_hunt_block_div.querySelectorAll(`div[hint='${isEn ? "Attack" : "Напасть"}'] > img`);
            for(const button of attackButtons) {
                //console.log(button)
                button.addEventListener("click", function() { updateOption("battleType", "hunt"); });
            }
            const skipButtons = map_hunt_block_div.querySelectorAll(`div[hint^='${isEn ? "Pass" : "Пройти"}'] > img`);
            for(const button of skipButtons) {
                button.addEventListener("click", function() { skipHunt(); });
            }
            const callButtons = map_hunt_block_div.querySelectorAll(`div[hint^='${isEn ? "Ask" : "Позвать"}'] > img`);
            for(const button of callButtons) {
                button.addEventListener("click", function() { setTimeout(function() { Array.from(document.querySelectorAll("form[action='/map.php']")).forEach(x => x.querySelector("input[type='submit']").addEventListener("click", function() { updateOption("battleType", "hunt"); })); }, 200); });
            }
        }
    }
    if(mooving && !GM_getValue(`huntTimeoutEnd${PlayerId}`)) {
        skipHunt();
    }
    hideNativeHuntTimerPanel();
}
function hideNativeHuntTimerPanel() {
    const next_ht_new = document.querySelector("div#next_ht_new");
    if(next_ht_new && gmGetBool("HideHuntTimer")) {
        document.querySelector("div#map_hunt_block_div").style.display = "none";
    }
}
function parseWaitTime(timeText) {
    const restTime = parseTime(timeText);
    const date = new Date();
    date.setHours(date.getHours() + restTime.Hours);
    date.setMinutes(date.getMinutes() + restTime.Minutes);
    date.setSeconds(date.getSeconds() + restTime.Seconds);
    return date;
}
function parseTime(timeText) {
    const time = { Hours: 0, Minutes: 0, Seconds: 0 };
    const hoursRegex = new RegExp(`(\\d+) ${isEn ? "h" : "ч"}\\.`);
    const hoursRegexResult = hoursRegex.exec(timeText);
    if(hoursRegexResult) {
        time.Hours = parseInt(hoursRegexResult[1]);
    }
    const minutesRegex = new RegExp(`(\\d+) ${isEn ? "min" : "мин"}\\.`);
    const minutesRegexResult = minutesRegex.exec(timeText);
    if(minutesRegexResult) {
        time.Minutes = parseInt(minutesRegexResult[1]);
    }
    const secondsRegex = new RegExp(`(\\d+) ${isEn ? "sec" : "с"}\\.`);
    const secondsRegexResult = secondsRegex.exec(timeText);
    if(secondsRegexResult) {
        time.Seconds = parseInt(secondsRegexResult[1]);
    }
    return time;
}
function secondsFormat(secondsLeft) {
    if(!secondsLeft || secondsLeft < 0) {
        return "00:00";
    }
    const days = Math.floor(secondsLeft / 86400);
    const hours = Math.floor((secondsLeft - days * 86400) / 3600);
    const minutes = Math.floor((secondsLeft - days * 86400 - hours * 3600) / 60);
    const seconds = secondsLeft % 60;
    return (days === 0 ? '' : ((days < 10) ? '0' : '') + days + ':') + (days === 0 && hours === 0 ? '' : ((hours < 10) ? '0' : '') + hours + ':') + ((minutes < 10) ? '0' : '') + minutes + ':' + ((seconds < 10) ? '0' : '') + seconds;
}
function getSecondsLeft(timerName) {
    //console.log(`${timerName}: ${GM_getValue(`${timerName}TimeoutEnd${PlayerId}`)}`)
    if(GM_getValue(`${timerName}TimeoutEnd${PlayerId}`)) {
        const result = Math.round((parseInt(GM_getValue(`${timerName}TimeoutEnd${PlayerId}`)) - Date.now()) / 1000);
        if(result >= 0) {
            return result;
        }
    }
}
function tick() {
    timersDataBind();
    const audios = { "health": healthAudio, "work": workAudio, "smith": smithAudio, "merc": warlikeAudio, "hunt": warlikeAudio, "thief": warlikeAudio };
    for(const timerName of timerNames) {
        const secondsLeft = getSecondsLeft(timerName);
        if(secondsLeft == 0) {
            GM_deleteValue(`${timerName}TimeoutEnd${PlayerId}`);
            const options = JSON.parse(GM_getValue(`hwmTimersOptions${PlayerId}`));
            if(options[`${timerName}Notification`] == 'yes') {
                setTimeout(function() { signal(timerName, texts[`${timerName}Message`], audios[timerName]); }, 100);
                if(timerName == 'health') {
                    updateOption("healthNotification", 'no');
                    timersPanelDataBind();
                }
            }
        }
    }
    setTimeout(tick, 1000);
}
function signal(timerName, message, sound) {
    const options = JSON.parse(GM_getValue(`hwmTimersOptions${PlayerId}`));
    if(Date.now() < parseInt(GM_getValue(`${timerName}LastNotificationTime${PlayerId}`, 0)) + 60000) {
        return;
    }
    GM_setValue(`${timerName}LastNotificationTime${PlayerId}`, Date.now());
    switch(GM_getValue(`NotificationType${PlayerId}`, '0')) {
        case '0':
            sound.play();
            break;
        case '1':
            alert(message);
            break;
        case '2':
            GM.notification(message, "ГВД", "https://dcdn.heroeswm.ru/i/rewards/fast_t/3x3_1.png", function() { window.focus(); });
            break;
        default: //including '3'
    }
}
function settings() {
    if(showPupupPanel(GM_info.script.name)) {
        return;
    }
    const options = JSON.parse(GM_getValue(`hwmTimersOptions${PlayerId}`));
    const bgcinnerHTML = `
<tr>
    <td align="center"> <b>${texts.st_start}</b> </td>
</tr>
<tr>
    <td>${texts.st_show_timers}&nbsp;&nbsp;${texts.workTimerPanelCaption}:<input id=isShowWorkTimerCheckbox type=checkbox>
&nbsp;&nbsp;${texts.smithTimerPanelCaption}:<input id=isShowSmithTimerCheckbox type=checkbox>
&nbsp;&nbsp;${texts.mercTimerPanelCaption}:<input id=isShowMercTimerCheckbox type=checkbox>
&nbsp;&nbsp;${texts.huntTimerPanelCaption}:<input id=isShowHuntTimerCheckbox type=checkbox>
&nbsp;&nbsp;${texts.thiefTimerPanelCaption} (${texts.rangerTimerPanelCaption}):<input id=isShowThiefTimerCheckbox type=checkbox>
    </td>
</tr>
<tr>
    <td>${texts.joinRangerBattleText}: <input id="joinRangerBattleCheckbox" type=checkbox></td>
</tr>
<tr>
    <td>${texts.st_go_timer_hide} "<i>${texts.regexp_go_timer} ..</i>": <input id=go_timer_hide_id type=checkbox></td>
</tr>
<tr>
    <td>${texts.st_work_trudogolik_off}: <input id=trudogolik_off_id type=checkbox></td>
</tr>
<tr>
    <td>${texts.st_work_trudogolik_show}: <input id=trudogolik_show_id type=checkbox></td>
</tr>
<tr>
    <td id="twmTimersSettingsAbuText"></td>
</tr>
<tr>
    <td>${texts.st_predupr_go_lic}</td>
</tr>
<tr>
    <td>${texts.st_percent_faster} <input id="gv_n_percent" type="number" style="width: 50px;" maxlength="2"> <b>%</b> <input type="submit" id="gv_n_percent_ok" value="ok"></td>
</tr>
<tr>
    <td>${texts.st_gv_n_time} <input id="gv_n_time" type="number" style="width: 50px;" maxlength="2"> <b>min</b>
    <input type="submit" id="gv_n_time_ok" value="ok"></td>
</tr>
<tr>
    <td> <input type="submit" id="null_tr_id" value="${texts.st_null_timers}">&nbsp;&nbsp;&nbsp;</td>
</tr>
<tr>
    <td>${texts.alarm_mode} <input type="radio" name="r_notify_type" id="r_notify_0">${texts.alarm_mode_sound}
    <input type="radio" name="r_notify_type" id="r_notify_1">${texts.alarm_mode_alert}
    <input type="radio" name="r_notify_type" id="r_notify_2">${texts.alarm_mode_both}
    <input type="radio" name="r_notify_type" id="r_notify_3">${texts.alarm_mode_none}
    </td>
</tr>
<tr>
    <td>
        <table>
            <tr>
                <td>${texts.audio_file + texts.workTimerPanelCaption}</td>
                <td><input size=55 type="text" id="workSoundInput" value="${options.workSound || ''}"></td>
                <td><input size=55 type="button" id="play_audio_gr" value="Play!">  </td>
            </tr>
            <tr>
                <td>${texts.audio_file + texts.gonv_t}</td>
                <td><input size=55 type="text" id="warlikeSoundInput" value="${options.warlikeSound || ''}"></td>
                <td><input size=55 type="button" id="play_audio_gonv" value="Play!">  </td>
            </tr>
            <tr>
                <td>${texts.audio_file + texts.smithTimerPanelCaption}</td>
                <td><input size=55 type="text" id="smithSoundInput" value="${options.smithSound || ''}"></td>
                <td><input size=55 type="button" id="play_audio_gk" value="Play!">  </td>
            </tr>
            <tr>
                <td>${texts.audio_file + texts.h_t}</td>
                <td><input size=55 type="text" id="healthSoundInput" value="${options.healthSound || ''}"></td>
                <td><input size=55 type="button" id="play_audio_h" value="Play!">  </td>
            </tr>
        </table>
    </td>
</tr>
`;
    const optionsContainer = createElement("table", { innerHTML: bgcinnerHTML });

    optionsContainer.querySelector("#null_tr_id").addEventListener("click", resetTimers);
    optionsContainer.querySelector("#gv_n_time_ok").addEventListener("click", function() { if(Number(document.getElementById("gv_n_time").value) >= 0) { GM_setValue(`thiefTimeoutEnd${PlayerId}`, Date.now() + document.getElementById("gv_n_time").value * 60000); } });
    optionsContainer.querySelector("#gv_n_percent_ok").addEventListener("click", function() { updateOption("customTimeRate", (100 - document.getElementById("gv_n_percent").value) / 100); settingsDataBind(); });
    optionsContainer.querySelector("#joinRangerBattleCheckbox").addEventListener("click", function() { updateOption("joinRangerBattle", this.checked ? '1' : '0'); });
    optionsContainer.querySelector("#go_timer_hide_id").addEventListener("click", function() { GM_setValue("HideHuntTimer", this.checked); }, false);
    optionsContainer.querySelector("#trudogolik_show_id").addEventListener("click", function() { updateOption("showWorkaholicAlarmLastTwoEnrolls", this.checked ? '1' : '0'); });
    optionsContainer.querySelector("#trudogolik_off_id").addEventListener("click", function() { updateOption("disableWorkaholicAlarm", this.checked ? '1' : '0'); });

    optionsContainer.querySelector("#isShowWorkTimerCheckbox").addEventListener("click", function() { updateOption("isShowWorkTimer", this.checked ? '1' : '0'); timersPanelDataBind(); });
    optionsContainer.querySelector("#isShowSmithTimerCheckbox").addEventListener("click", function() { updateOption("isShowSmithTimer", this.checked ? '1' : '0'); timersPanelDataBind(); });
    optionsContainer.querySelector("#isShowMercTimerCheckbox").addEventListener("click", function() { updateOption("isShowMercTimer", this.checked ? '1' : '0'); timersPanelDataBind(); });
    optionsContainer.querySelector("#isShowHuntTimerCheckbox").addEventListener("click", function() { updateOption("isShowHuntTimer", this.checked ? '1' : '0'); timersPanelDataBind(); });
    optionsContainer.querySelector("#isShowThiefTimerCheckbox").addEventListener("click", function() { updateOption("isShowThiefTimer", this.checked ? '1' : '0'); timersPanelDataBind(); });

    optionsContainer.querySelector("#workSoundInput").addEventListener("change", function() { updateOption("workSound", this.value.trim()); workAudio = initAudio(this.value.trim()); });
    optionsContainer.querySelector("#warlikeSoundInput").addEventListener("change", function() { updateOption("warlikeSound", this.value.trim()); warlikeAudio = initAudio(this.value.trim()); });
    optionsContainer.querySelector("#smithSoundInput").addEventListener("change", function() { updateOption("smithSound", this.value.trim()); smithAudio = initAudio(this.value.trim()); });
    optionsContainer.querySelector("#healthSoundInput").addEventListener("change", function() { updateOption("healthSound", this.value.trim()); healthAudio = initAudio(this.value.trim()); });

    optionsContainer.querySelector("#play_audio_gr").addEventListener("click", function() { playAudio(document.getElementById("workSoundInput").value); });
    optionsContainer.querySelector("#play_audio_gonv").addEventListener("click", function() { playAudio(document.getElementById("warlikeSoundInput").value); });
    optionsContainer.querySelector("#play_audio_gk").addEventListener("click", function() { playAudio(document.getElementById("smithSoundInput").value); });
    optionsContainer.querySelector("#play_audio_h").addEventListener("click", function() { playAudio(document.getElementById("healthSoundInput").value); });

    optionsContainer.querySelector("#r_notify_0").addEventListener("click", function() { if(this.checked) { GM_setValue(`NotificationType${PlayerId}`, "0"); initAudios(); } });
    optionsContainer.querySelector("#r_notify_1").addEventListener("click", function() { if(this.checked) { GM_setValue(`NotificationType${PlayerId}`, "1"); } });
    optionsContainer.querySelector("#r_notify_2").addEventListener("click", function() { if(this.checked) { GM_setValue(`NotificationType${PlayerId}`, "2"); } });
    optionsContainer.querySelector("#r_notify_3").addEventListener("click", function() { if(this.checked) { GM_setValue(`NotificationType${PlayerId}`, "3"); } });
    optionsContainer.querySelector(`#r_notify_${GM_getValue(`NotificationType${PlayerId}`, 0)}`).checked = true;

    createPupupPanel(GM_info.script.name, `${isEn ? "Options" : "Настройки"} ${GM_info.script.name}`, [[optionsContainer]]);

    settingsDataBind();
}
function settingsDataBind() {
    const options = JSON.parse(GM_getValue(`hwmTimersOptions${PlayerId}`));
    document.getElementById("isShowWorkTimerCheckbox").checked = options.isShowWorkTimer == "1";
    document.getElementById("isShowSmithTimerCheckbox").checked = options.isShowSmithTimer == "1";
    document.getElementById("isShowMercTimerCheckbox").checked = options.isShowMercTimer == "1";
    document.getElementById("isShowHuntTimerCheckbox").checked = options.isShowHuntTimer == "1";
    document.getElementById("isShowThiefTimerCheckbox").checked = options.isShowThiefTimer == "1";

    document.getElementById("joinRangerBattleCheckbox").checked = options.joinRangerBattle == "1";
    document.getElementById("go_timer_hide_id").checked = gmGetBool("HideHuntTimer");
    document.getElementById("trudogolik_off_id").checked = options.disableWorkaholicAlarm == "1";
    document.getElementById("trudogolik_show_id").checked = options.showWorkaholicAlarmLastTwoEnrolls == "1";

    document.getElementById("twmTimersSettingsAbuText").innerHTML = options.abuBlessInfo;

    document.getElementById("gv_n_percent").value = 100 - options.customTimeRate * 100;
    document.getElementById("gv_n_time").value = 60 * options.customTimeRate * options.abuBlessRate;
}
function initAudios() {
    const options = JSON.parse(GM_getValue(`hwmTimersOptions${PlayerId}`));
    defaultAudio = new Audio('data:audio/mp3;base64,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');
    defaultAudio.preload = 'auto';
    workAudio = initAudio(options.workSound);
    warlikeAudio = initAudio(options.warlikeSound);
    smithAudio = initAudio(options.smithSound);
    healthAudio = initAudio(options.healthSound);
}
function initAudio(src) {
    if(src && src !== '') {
        var audio = new Audio();
        audio.src = src;
        audio.preload = 'auto';
        return audio;
    }
    return defaultAudio;
}
function playAudio(src) {
    if(src && src != '') {
        var paudio = new Audio();
        paudio.preload = 'auto';
        paudio.src = src;
        paudio.play();
    } else {
        defaultAudio.play();
    }
}
function updateOption(key, val) {
    const options = JSON.parse(GM_getValue(`hwmTimersOptions${PlayerId}`));
    if(typeof val == "function") {
        options[key] = val(options);
    } else {
        options[key] = val;
    }
    if(key == "battleType") {
        console.log(`battleType: ${options[key]}`);
    }
    GM_setValue(`hwmTimersOptions${PlayerId}`, JSON.stringify(options));
}
function resetTimers() {
    GM_deleteValue(`workTimeoutEnd${PlayerId}`);
    GM_deleteValue(`smithTimeoutEnd${PlayerId}`);
    GM_deleteValue(`mercTimeoutEnd${PlayerId}`);
    GM_deleteValue(`huntTimeoutEnd${PlayerId}`);
    GM_deleteValue(`thiefTimeoutEnd${PlayerId}`);
}
function getUrlParamValue(url, paramName) { return (new URLSearchParams(url.split("?")[1])).get(paramName); }
function addElement(type, parent, data) {
    let el = createElement(type, data);
    if(parent) {
        parent.appendChild(el);
    }
    return el;
}
function createElement(type, data) {
    let el = document.createElement(type);
    if(data) {
        for(let key in data) {
            if(key == "innerText" || key == "innerHTML") {
                el[key] = data[key];
            } else {
                el.setAttribute(key, data[key]);
            }
        }
    }
    return el;
}
function GM_addStyle(css) { addElement("style", document.head, { type: "text/css", innerHTML: css }); }
function getParent(element, parentType, number = 1) {
    if(!element) {
        return;
    }
    let result = element;
    let foundNumber = 0;
    while(result = result.parentNode) {
        if(result.nodeName.toLowerCase() == parentType.toLowerCase()) {
            foundNumber++;
            if(foundNumber == number) {
                return result;
            }
        }
    }
}
function getServerTime() { return Date.now() + parseInt(GM_getValue("ClientServerTimeDifference", 0)); }
function getServerDateTime() { return new Date(getServerTime()); }
function toServerTime(clientTime) { return clientTime - parseInt(GM_getValue("ClientServerTimeDifference", 0)); }
function serverNow() { return toServerTime(Date.now()); }
async function requestServerTime() {
    if(parseInt(GM_getValue("LastClientServerTimeDifferenceRequestDate", 0)) + 60 * 60 * 1000 < Date.now()) {
        GM_setValue("LastClientServerTimeDifferenceRequestDate", Date.now());
        const responseText = await getRequestText("/time.php");
        const responseParcing = /now (\d+)/.exec(responseText); //responseText: now 1681711364 17-04-23 09:02
        if(responseParcing) {
            GM_setValue("ClientServerTimeDifference", Date.now() - parseInt(responseParcing[1]) * 1000);
        }
    } else {
        setTimeout(requestServerTime, 60 * 60 * 1000);
    }
}
function gmGetBool(valueName) {
    const value = GM_getValue(valueName);
    if(value) {
        if(typeof(value) == "string") {
            return value == "true";
        }
        if(typeof(value) == "boolean") {
            return value;
        }
    }
    return false;
}
function observe(target, handler, once = false) {
    const config = { childList: true, subtree: true };
    const ob = new MutationObserver(async function(mut, observer) {
        observer.disconnect();
        if(handler.constructor.name === 'AsyncFunction') {
            await handler();
        } else {
            handler();
        }
        if(!once) {
            observer.observe(target, config);
        }
    });
    ob.observe(target, config);
}
function createPupupPanel(panelName, panelTitle, fieldsMap, panelToggleHandler) {
    const backgroundPopupPanel = addElement("div", document.body, { id: panelName + "1", style: "position: fixed; left: 0pt; width: 100%; background: none repeat scroll 0% 0% gray; opacity: 0.5; top: 0px; height: 100%; display: block; z-index: 200;" });
    backgroundPopupPanel.addEventListener("click", function() { hidePupupPanel(panelName, panelToggleHandler); });

    const popupPanel = addElement("div", document.body, { id: panelName + "2", style: `position: fixed; width: 650px; background: none repeat scroll 0% 0%; background-image: linear-gradient(to right, #eea2a2 0%, #bbc1bf 19%, #57c6e1 42%, #b49fda 79%, #7ac5d8 100%); left: ${((document.body.offsetWidth - 650) / 2)}px; top: 150px; display: block; z-index: 200; border: 4mm ridge rgba(211, 220, 50, .6);` });
    const contentDiv = addElement("div", popupPanel, { id: panelName + "3", style: "border: 1px solid #abc; padding: 5px; margin: 2px; display: flex; flex-wrap: wrap;" });

    if(panelTitle) {
        addElement("b", contentDiv, { innerText: panelTitle, style: "text-align: center; margin: auto; width: 90%; display: block;" });
    }
    const divClose = addElement("div", contentDiv, { id: panelName + "close", title: "Close", innerText: "x", style: "border: 1px solid #abc; width: 15px; height: 15px; text-align: center; cursor: pointer;" });
    divClose.addEventListener("click", function() { hidePupupPanel(panelName, panelToggleHandler); });

    addElement("div", contentDiv, { style: "flex-basis: 100%; height: 0;"});

    if(fieldsMap) {
        let contentTable = addElement("table", contentDiv);
        for(const rowData of fieldsMap) {
            if(rowData.length == 0) { // Спомощью передачи пустой стороки-массива, указываем, что надо начать новую таблицу после брейка
                addElement("div", contentDiv, { style: "flex-basis: 100%; height: 0;"});
                contentTable = addElement("table", contentDiv);
                continue;
            }
            const row = addElement("tr", contentTable);
            for(const cellData of rowData) {
                const cell = addElement("td", row);
                if(cellData) {
                    if(typeof(cellData) == "string") {
                        cell.innerText = cellData;
                    } else {
                        cell.appendChild(cellData);
                    }
                }
            }
        }
    }
    if(panelToggleHandler) {
        panelToggleHandler(true);
    }
    return contentDiv;
}
function showPupupPanel(panelName, panelToggleHandler) {
    let backgroundPopupPanel = document.getElementById(panelName + "1");
    let popupPanel = document.getElementById(panelName + "2");
    if(backgroundPopupPanel) {
        backgroundPopupPanel.style.display = popupPanel.style.display = 'block';
        if(panelToggleHandler) {
            panelToggleHandler(true);
        }
        return true;
    }
    return false;
}
function hidePupupPanel(panelName, panelToggleHandler) {
    let backgroundPopupPanel = document.getElementById(panelName + "1");
    let popupPanel = document.getElementById(panelName + "2");
    backgroundPopupPanel.style.display = popupPanel.style.display = 'none';
    if(panelToggleHandler) {
        panelToggleHandler(false);
    }
}
function healthTimer() {
    if(isHeartOnPage) {
        const health_amount = document.getElementById("health_amount");
        let heart; // 78
        let maxHeart; // 100
        let timeHeart; // 405
        if(health_amount) {
            const res = /top_line_draw_canvas_heart\((\d+), (\d+), (\d+)\);/.exec(document.body.innerHTML); // top_line_draw_canvas_heart(0, 100, 405);
            if(res) {
                heart = parseInt(res[1]);
                maxHeart = parseInt(res[2]);
                timeHeart = parseInt(res[3]);
            }
        } else {
            heart = windowObject.heart;
            maxHeart = windowObject.max_heart;
            timeHeart = windowObject.time_heart;
        }
        //console.log(`healthTimer heart: ${heart}, maxHeart: ${maxHeart}, timeHeart: ${timeHeart}`);
        let restSeconds = timeHeart * (maxHeart - heart) / maxHeart;
        if(restSeconds > 0) {
            GM_setValue(`healthTimeoutEnd${PlayerId}`, Date.now() + restSeconds * 1000);
        } else {
            GM_deleteValue(`healthTimeoutEnd${PlayerId}`);
        }
        return [heart, maxHeart, timeHeart];
    }
}
async function initUserName() {
    if(GM_getValue("TransporterUserName")) {
        GM_deleteValue("UserName");
        GM_deleteValue("TransporterUserName");
    }
    if(location.pathname == "/pl_info.php" && getUrlParamValue(location.href, "id") == PlayerId) {
        //console.log(document.querySelector("h1").innerText)
        GM_setValue("UserName", document.querySelector("h1").innerText);
    }
    if(location.pathname == "/home.php") {
        //console.log(document.querySelector(`a[href='pl_info.php?id=${PlayerId}'] > b`).innerText)
        GM_setValue("UserName", document.querySelector(`a[href='pl_info.php?id=${PlayerId}'] > b`).innerText);
    }
    if(!GM_getValue("UserName")) {
        const doc = await getRequest(`/pl_info.php?id=${PlayerId}`);
        GM_setValue("UserName", doc.querySelector("h1").innerText);
    }
}
function parseDate(dateString, isFuture = false, isPast = false) {
    //console.log(dateString)
    if(!dateString) {
        return;
    }
    const dateStrings = dateString.split(" ");

    let hours = 0;
    let minutes = 0;
    let seconds = 0;
    const timePart = dateStrings.find(x => x.includes(":"));
    if(timePart) {
        var time = timePart.split(":");
        hours = parseInt(time[0]);
        minutes = parseInt(time[1]);
        if(time.length > 2) {
            seconds = parseInt(time[2]);
        }
    }

    const now = new Date();
    let year = now.getFullYear();
    let month = now.getMonth();
    let day = now.getDate();
    const datePart = dateStrings.find(x => x.includes("-"));
    if(datePart) {
        const date = datePart.split("-");
        month = parseInt(date[isEn ? (date.length == 3 ? 1 : 0) : 1]) - 1;
        day = parseInt(date[isEn ? (date.length == 3 ? 2 : 1) : 0]);
        if(date.length == 3) {
            year = isEn ? parseInt(date[0]) : parseInt(date[2]);
            if(year < 1000) {
                year += Math.floor((new Date()).getFullYear() / 1000) * 1000;
            }
        } else {
            if(isFuture && month == 0 && now.getMonth() == 11) {
                year += 1;
            }
        }
    }
    if(dateStrings.length > 2) {
        const letterDateExec = /(\d{2}):(\d{2}) (\d{2}) (.{3,4})/.exec(dateString);
        if(letterDateExec) {
            //console.log(letterDateExec)
            day = parseInt(letterDateExec[3]);
            //const monthNames = ['января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря'];
            const monthShortNames = ['янв', 'фев', 'март', 'апр', 'май', 'июнь', 'июль', 'авг', 'сент', 'окт', 'ноя', 'дек'];
            month = monthShortNames.findIndex(x => x.toLowerCase() == letterDateExec[4].toLowerCase());
            if(isPast && (new Date(year, month, day, hours, minutes, seconds)).getTime() > Date.now()) {
                year -= 1;
            }
        }
    }
    //console.log(`year: ${year}, month: ${month}, day: ${day}, time[0]: ${time[0]}, time[1]: ${time[1]}, ${new Date(year, month, day, parseInt(time[0]), parseInt(time[1]))}`);
    return new Date(year, month, day, hours, minutes, seconds);
}
function getRequest(url) {
    return new Promise((resolve, reject) => {
        GM.xmlHttpRequest({ method: "GET", url: url, overrideMimeType: "text/html; charset=windows-1251",
            onload: function(response) { resolve((new DOMParser).parseFromString(response.responseText, "text/html")); },
            onerror: function(error) { reject(error); }
        });
    });
}
function getRequestText(url, overrideMimeType = "text/html; charset=windows-1251") {
    return new Promise((resolve, reject) => {
        GM.xmlHttpRequest({ method: "GET", url: url, overrideMimeType: overrideMimeType,
            onload: function(response) { resolve(response.responseText); },
            onerror: function(error) { reject(error); }
        });
    });
}