hwmAdvancedMenu

Расширенное меню

目前為 2024-12-05 提交的版本,檢視 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name           hwmAdvancedMenu
// @namespace      Tamozhnya1
// @author         Tamozhnya1
// @description    Расширенное меню
// @version        3.0
// @include        *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
// @license        MIT
// ==/UserScript==

const StoredForumTreadsAmount = 10;
const playerIdMatch = document.cookie.match(/pl_id=(\d+)/);
if(!playerIdMatch) {
    return;
}
const PlayerId = playerIdMatch[1];
const isEn = document.documentElement.lang == "en";
const isNewInterface = document.querySelector("div#hwm_header") ? true : false;

main();
async function main() {
    initUserName();
    processHouses();
    processQuickLinks();
    processForum();
    checkMilitaryClan();
    // После рынка вставим ссылки на ресурсы, с задержкой в пол секунды
    const auctionRef = isNewInterface ? document.querySelector("div.sh_dd_container a[href='auction.php']") : getParent(document.querySelector("li > a[href='auction.php']"), "li");
    if(auctionRef) {
        let resourcesShowTimer;
        let resourcesShown = false;
        const resources = [ { type: "1", name: isEn ? 'Wood' : 'Древесина' }, { type: "2", name: isEn ? 'Ore' : 'Руда' }, { type: "3", name: isEn ? 'Mercury' : 'Ртуть' }, { type: "4", name: isEn ? 'Sulfur' : 'Сера' }, { type: "5", name: isEn ? 'Crystals' : 'Кристаллы' }, { type: "6", name: isEn ? 'Gems' : 'Самоцветы' } ];
        auctionRef.addEventListener("mouseover", function() { if(!resourcesShown) resourcesShowTimer = setTimeout(function() {
            const html = resources.reduce((t, x) => t + getMenuItemTemplate(`auction.php?cat=res&sort=0&type=${x.type}`, `  ${x.name}`), "");
            auctionRef.insertAdjacentHTML('afterend', html);
            resourcesShown = true;
        }, 500) } );
        auctionRef.addEventListener("mouseout", function() { clearTimeout(resourcesShowTimer); });
    }
    // После передачи ресурсов вставим основные ссылки персонажа
    const transferRef = isNewInterface ? document.querySelector("div.sh_dd_container a[href='transfer.php']") : getParent(document.querySelector("li > a[href='transfer.php']"), "li");
    if(transferRef) {
        const personalReferences = [
            { href: `el_transfer.php`, text: isEn ? 'Transfer elements' : 'Передача элементов' },
            { href: 'javascript:void(0);', text: "" },
            { href: `pl_info.php?id=${PlayerId}`, text: getPlayerValue("UserName") || (isEn ? 'Character' : 'Персонаж') },
            { href: `pl_transfers.php?id=${PlayerId}`, text: isEn ? 'Transfer log' : 'Протокол передач' },
            { href: `pl_warlog.php?id=${PlayerId}`, text: isEn ? 'Combat log' : 'Протокол боев' },
            { href: `pl_cardlog.php?id=${PlayerId}`, text: isEn ? 'Game log' : 'Протокол игр' },
            { href: `friends.php`, text: isEn ? 'Your friends' : 'Ваши друзья' },
            { href: `/pl_clans.php`, text: isEn ? 'Your clans' : 'Ваши кланы' },
            { href: `ephoto_albums.php`, text: isEn ? 'Your photos' : 'Ваш фотоальбом' },
            { href: 'javascript:void(0);', text: "" },
            { href: `logout.php?${Math.round( Math.random()* 100000 )}`, text: isEn ? 'Logout' : 'Выход' }
        ];
        const html = personalReferences.reduce((t, x) => t + getMenuItemTemplate(x.href, x.text), "");
        transferRef.insertAdjacentHTML('afterend', html);
    }
    // Расширение карты
    const mapMenuContainer = isNewInterface ? document.querySelector("div.sh_dd_container a[href='map.php?st=hs']") : getParent(document.querySelector("li > a[href='map.php?st=hs']"), "li");
    if(mapMenuContainer) {
        const housesInfo = JSON.parse(getPlayerValue("PlayerHouses", "{}"));
        let mapExtenders = Object.keys(housesInfo).map(x => ({ href: `house_info.php?id=${x}`, text: housesInfo[x].replace(" ", " ") }));
        // Арендованные дома. Берутся из скрипта Transporter
        for(let locationNumber = 1; locationNumber <= 27; locationNumber++) {
            const guestInfo = JSON.parse(getValue(`GuestInfo${locationNumber}`, "{}"));
            for(const key in guestInfo) {
                mapExtenders = [...mapExtenders, { href: `house_info.php?id=${key}`, text: guestInfo[key].HostInfo, title: `до ${(new Date(guestInfo[key].ExpireDate)).toLocaleString()}` }];
            }
        }
        //
        mapExtenders = [...mapExtenders, { href: 'javascript:void(0);', text: "" }, { href: 'ecostat.php', text: isEn ? 'Economic statistics' : 'Эконом. статистика' }
        , { href: 'arts_arenda.php', text: isEn ? 'Artifacts at lease' : 'Предметы в аренде' }];
        // Ссылки по боевому клану. Появляются после захода на страницу кланов
        const clanId = getPlayerValue("MilitaryClanId");
        if(clanId) {
            const clanName = getPlayerValue("MilitaryClanName");
            mapExtenders = [...mapExtenders, { href: 'javascript:void(0);', text: "" },
                { href: `/clan_info.php?id=${clanId}`, text: clanName || (isEn ? 'Military clan' : 'Боевой клан') },
                { href: `/sklad_info.php?clan_id=${clanId}`, text: isEn ? 'Clan depository' : 'Клан-склад' },
                { href: `/sms_clans.php?clan_id=${clanId}`, text: isEn ? 'Clan post' : 'Клановая рассылка' }
            ];
        }
        const html = mapExtenders.reduce((t, x) => t + getMenuItemTemplate(x.href, x.text, x.title), "");
        //console.log(mapExtenders)
        mapMenuContainer.insertAdjacentHTML('afterend', html);
    }
    const leaderGuildRef = isNewInterface ? document.querySelector("div.sh_dd_container a[href='leader_guild.php']") : getParent(document.querySelector("li > a[href='leader_guild.php']"), "li");
    if(leaderGuildRef) {
        const leaderGuildReferences = [
            { href: `leader_army.php`, text: isEn ? 'Recruiting' : 'Набор армии' },
            { href: `leader_army_exchange.php`, text: isEn ? 'Creature Exchange' : 'Обмен существ' }
        ];
        const html = leaderGuildReferences.reduce((t, x) => t + getMenuItemTemplate(x.href, x.text), "");
        leaderGuildRef.insertAdjacentHTML('afterend', html);
    }
    // Добавим для ГЛ набор армии и объмены
    // Добавим форумов и дейли
    const forumsContainer = isNewInterface ? document.querySelector("div.sh_dd_container a[href='forum.php#t1']") : getParent(document.querySelector("li > a[href='forum.php#t1']"), "li");
    if(forumsContainer) {
        const forumExtenders = [
            { href: `forum_thread.php?id=${isEn ? '103' : '3'}`, text: isEn ? 'Ideas and suggestions' : 'Идеи и предложения' },
            { href: `forum_thread.php?id=${isEn ? '121' : '22'}`, text: isEn ? 'Smiths and Ench. services' : 'Услуги кузнецов и оруж.' },
            { href: 'javascript:void(0);', text: "" },
            { href: `${isEn ? 'http://daily.heroeswm.ru/newscom.php' : 'http://daily.heroeswm.ru/'}`, text: isEn ? 'HWM Daily ENG' : 'Геройская лента' }
        ];
        let html = forumExtenders.reduce((t, x) => t + getMenuItemTemplate(x.href, x.text), "");
        const lastForumTreads = JSON.parse(getValue("LastForumTreads", "[]"));
        if(lastForumTreads.length > 0) {
            html += getMenuItemTemplate('javascript:void(0);', "");
        }
        html += lastForumTreads.reduce((t, x) => t + getMenuItemTemplate(`/forum_messages.php?tid=${x.threadId}${x.pageIndex ? `&page=${x.pageIndex}` : ""}`, x.threadName, "", `forumReference${x.threadId}`), "");
        forumsContainer.insertAdjacentHTML('afterend', html);
        Array.from(document.querySelectorAll(`span[id^='forumReference']`)).forEach(x => x.addEventListener("click", deleteForumReferenceMenuItem));
    }
    // После чатов добавим быстрые ссылки
    const framesContainer = isNewInterface ? document.querySelector("div.sh_dd_container a[href='frames.php?room=4']") : getParent(document.querySelector("li > a[href='frames.php?room=4']"), "li");
    if(framesContainer) {
        //GM_deleteValue(`QuickLinks${PlayerId}`);
        let quickLinks = JSON.parse(getPlayerValue("QuickLinks", "[]")).filter(x => x.Name != "" && x.Reference != "");
        quickLinks.push({ Name: isEn ? "Settings" : "Настройки", Reference: "javascript:void(0);", id: `${GM_info.script.name}Settings` });
        if(quickLinks.length > 0) {
            quickLinks = [{ Reference: "javascript:void(0);", Name: "" }, ...quickLinks];
            const html = quickLinks.reduce((t, x) => t + getMenuItemTemplate(x.Reference, x.Name, undefined, undefined, x.id), "");
            framesContainer.insertAdjacentHTML('afterend', html);
        }
        document.getElementById(`${GM_info.script.name}Settings`).addEventListener("click", showSettings);
    }
    if(getPlayerBool("hideTavern")) {
        if(isNewInterface) {
            document.querySelector("div.mm_item > a[href='tavern.php']").closest("div").remove();
            document.querySelector("a[href='frames.php?room=4']").remove();
        } else {
            const menuItem = getParent(document.querySelector("li a[href='tavern.php']"), "td", 3);
            menuItem.previousElementSibling.remove();
            menuItem.remove();
            document.querySelector("a[href='frames.php?room=4']").closest("li").remove();
        }
        if(location.pathname == "/tavern.php") {
            location.href = "/home.php";
        }
        if(location.pathname == "/frames.php" && getUrlParamValue(location.href, "room") == "4") {
            location.href = "/home.php";
        }
    }
    if(getPlayerBool("hideRoulette")) {
        if(isNewInterface) {
            document.querySelector("div.mm_item > a[href='roulette.php']").closest("div").remove();
            document.querySelector("a[href='frames.php?room=3']").remove();
        } else {
            const menuItem = getParent(document.querySelector("li a[href='roulette.php']"), "td", 3);
            menuItem.previousElementSibling.remove();
            menuItem.remove();
            document.querySelector("a[href='frames.php?room=3']").closest("li").remove();
        }
        if(location.pathname == "/roulette.php") {
            location.href = "/home.php";
        }
        if(location.pathname == "/frames.php" && getUrlParamValue(location.href, "room") == "3") {
            location.href = "/home.php";
        }
        Array.from(document.querySelectorAll("a[href*='roulette.php']")).forEach(x => x.remove());
    }
    if(getPlayerBool("hideForum")) {
        if(isNewInterface) {
            document.querySelector("div.mm_item > a[href='forum.php']").closest("div").remove();
        } else {
            const menuItem = getParent(document.querySelector("li a[href='forum.php']"), "td", 3);
            menuItem.previousElementSibling.remove();
            menuItem.remove();
        }
        if(location.pathname == "/forum.php") {
            location.href = "/home.php";
        }
        Array.from(document.querySelectorAll("a[href*='forum.php']")).forEach(x => x.remove());
    }
}
function deleteForumReferenceMenuItem(e) {
    //e.stopPropagation();
    e.preventDefault();
    const threadId = e.target.id.replace("forumReference", "");
    //console.log(`threadId: ${threadId}`)
    const lastForumTreads = JSON.parse(getValue("LastForumTreads", "[]")).filter(x => x.threadId != threadId);
    setValue("LastForumTreads", JSON.stringify(lastForumTreads));
    
    const menuItem = isNewInterface ? getParent(e.target, "a") : getParent(e.target, "li");
    //console.log(menuItem)
    menuItem.remove();
}
function processForum() {
    if(location.pathname == '/forum_messages.php') {
        if(getValue("LastForumTreads")) {
            const saved = JSON.parse(getValue("LastForumTreads"));
            if(!Array.isArray(saved)) {
                GM_deleteValue("LastForumTreads");
            }
        }
        //https://www.heroeswm.ru/forum_messages.php?tid=2964583&page=5
        const threadId = getUrlParamValue(location.href, "tid");
        const pageIndex = getUrlParamValue(location.href, "page");
        const threadName = document.querySelector(`a[href='forum_messages.php?tid=${threadId}'`).innerText;
        const newThread = { threadId: threadId, pageIndex: pageIndex || 0, threadName: threadName, viewTime: Date.now() };
        let lastForumTreads = JSON.parse(getValue("LastForumTreads", "[]"));
        const thisThread = lastForumTreads.find(x => x.threadId == newThread.threadId && x.pageIndex == newThread.pageIndex);
        if(thisThread) {
            window.scrollTo(0, thisThread.scrollPosition); 
        }
        newThread.scrollPosition = window.scrollY;
        lastForumTreads = lastForumTreads.filter(x => x.threadId != newThread.threadId);
        lastForumTreads.unshift(newThread);
        lastForumTreads = lastForumTreads.slice(0, StoredForumTreadsAmount);
        setValue("LastForumTreads", JSON.stringify(lastForumTreads));
        
        document.addEventListener("scroll", (event) => {
            const lastForumTreads = JSON.parse(getValue("LastForumTreads", "[]"))
            const scrolledTread = lastForumTreads.find(x => x.threadId == newThread.threadId);
            if(scrolledTread) {
                scrolledTread.scrollPosition = window.scrollY;
                setValue("LastForumTreads", JSON.stringify(lastForumTreads));
            }
        });
    }
}
function getMenuItemTemplate(href, text, title, deleteId, id) {
    if(text.length > 30) {
        if(!title) {
            title = text;
        }
        text = text.substring(0, 30) + "...";
    }
    if(!isNewInterface) {
        if(text == "") {
            return "<hr>";
        }
        let deleteElementText = "";
        if(deleteId) {
            deleteElementText = `<span id="${deleteId}" title='${isEn ? "Delete" : "Удалить"}' style="cursor: pointer; display: inline; color: yellow;">[x]</span>`;
        }
        return `<li><a${id ? ` id="${id}"` : ""} href='${href}' title='${title || ""}' style="">${text}${deleteElementText}</a></li>`;
    } else {
        let deleteElementText = "";
        if(deleteId) {
            deleteElementText = `<span id="${deleteId}" title='${isEn ? "Delete" : "Удалить"}' style="cursor: pointer; display: inline; float: right;">[x]</span>`;
        }
        return `<a${id ? ` id="${id}"` : ""} href='${href}' title='${title || ""}' style='text-decoration: none;'><div style='${text == "" ? "padding: 0; height: 2px;" : ""}'>${text}${deleteElementText}</div></a>`;
    }
}
function processHouses() {
    if(location.pathname == "/pl_info_realty.php" && getUrlParamValue(location.href, "id") == PlayerId) {
        const housesInfo = Array.from(document.querySelectorAll("a[href^='house_info.php']")).reduce((t, x) => ({...t, [getUrlParamValue(x.href, "id")]: getParent(x, "tr").cells[4].innerText }), {});
        //console.log(housesInfo);
        setPlayerValue("PlayerHouses", JSON.stringify(housesInfo));
    }
}
function processQuickLinks() {
    if(location.pathname == "/pers_navlinks.php") {
        const tbody = getParent(document.querySelector("form[action='pers_navlinks.php']"), "tbody");
        const tr = addElement("tr", undefined, tbody);
        const td = addElement("td", undefined, tr);
        const table = addElement("table", undefined, td);
        
        const quickLinkAmount = 10;
        let quickLinks = JSON.parse(getPlayerValue("QuickLinks", `[${Array(quickLinkAmount).fill('{"Name":"","Reference":""}').join()}]`));
        if(quickLinks.length != quickLinkAmount) {
            deletePlayerValue("QuickLinks");
            quickLinks = JSON.parse(`[${Array(quickLinkAmount).fill('{"Name":"","Reference":""}').join()}]`);
        }
        //console.log(quickLinks)
        let i = 0;
        for(const quickLink of quickLinks) {
            const html = `
<tr>
    <td>
        <input id="linkName${i}" name="linkName" type="text" style="width: 200px;" />
        <input id="linkValue${i}" name="linkValue" type="text" style="width: 300px;" />
    </td>
</tr>`;
            table.insertAdjacentHTML('beforeend', html);
            table.querySelector(`#linkName${i}`).value = quickLink.Name;
            table.querySelector(`#linkValue${i}`).value = quickLink.Reference;
            table.querySelector(`#linkName${i}`).addEventListener("change", saveQuickLinks);
            table.querySelector(`#linkValue${i}`).addEventListener("change", saveQuickLinks);
            i++;
        }
    }
}
function saveQuickLinks() {
    const quickLinks = Array.from(document.querySelectorAll("input[name=linkName]")).map(x => ({ Name: x.value, Reference: document.getElementById(x.id.replace("linkName", "linkValue")).value }));
    //console.log(quickLinks)
    setPlayerValue("QuickLinks", JSON.stringify(quickLinks));
}
async function checkMilitaryClan() {
    if(location.pathname == '/pl_clans.php') {
        const doc = location.pathname == '/pl_clans.php' ? document : await getRequest(`/pl_clans.php`);
        const clanInfos = Array.from(doc.querySelectorAll("td > li > a[href^='clan_info.php']")).map(x => { return { Id: getUrlParamValue(x.href, "id"), Name: x.firstChild.innerText, Ref: x.href }; });
        for(const clanInfo of clanInfos) {
            const clanInfoDoc = await getRequest(clanInfo.Ref);
            if(clanInfoDoc.body.innerHTML.includes(isEn ? "[Military clan]" : "[боевой клан]")) {
                var militaryClanId = clanInfo.Id;
                var clanName = clanInfo.Name;
                break;
            }
        }
        if(militaryClanId) {
            setPlayerValue("MilitaryClanId", militaryClanId);
            setPlayerValue("MilitaryClanName", clanName);
        } else {
            deletePlayerValue("MilitaryClanId");
            deletePlayerValue("MilitaryClanName");
        }
    }
    if(!getPlayerValue("MilitaryClanId")) {
        console.log("Вы не состоите в боевом клане");
        return false;
    }
    return true;
}
async function initUserName() {
    if(location.pathname == "/pl_info.php" && getUrlParamValue(location.href, "id") == PlayerId) {
        //console.log(document.querySelector("h1").innerText)
        setPlayerValue("UserName", document.querySelector("h1").innerText);
    }
    if(location.pathname == "/home.php") {
        //console.log(document.querySelector(`a[href='pl_info.php?id=${PlayerId}'] > b`).innerText)
        setPlayerValue("UserName", document.querySelector(`a[href='pl_info.php?id=${PlayerId}'] > b`).innerText);
    }
    if(!getPlayerValue("UserName")) {
        const doc = await getRequest(`/pl_info.php?id=${PlayerId}`);
        setPlayerValue("UserName", doc.querySelector("h1").innerText);
    }
}
function showSettings() {
    if(showPupupPanel(GM_info.script.name)) {
        return;
    }
    const fieldsMap = [];

    const hideTavernLable = addElement("label", { for: "hideTavernCheckbox", innerText: isEn ? "Hide tavern" : "Скрыть таверну" });
    const hideTavernCheckbox = addElement("input", { id: "hideTavernCheckbox", type: "checkbox" });
    hideTavernCheckbox.checked = getPlayerBool("hideTavern");
    hideTavernCheckbox.addEventListener("change", function() { setPlayerValue("hideTavern", this.checked); }, false);
    fieldsMap.push([hideTavernLable, hideTavernCheckbox]);

    const hideRouletteLable = addElement("label", { for: "hideRouletteCheckbox", innerText: isEn ? "Hide roulette" : "Скрыть рулетку" });
    const hideRouletteCheckbox = addElement("input", { id: "hideRouletteCheckbox", type: "checkbox" });
    hideRouletteCheckbox.checked = getPlayerBool("hideRoulette");
    hideRouletteCheckbox.addEventListener("change", function() { setPlayerValue("hideRoulette", this.checked); }, false);
    fieldsMap.push([hideRouletteLable, hideRouletteCheckbox]);

    const hideForumLable = addElement("label", { for: "hideForumCheckbox", innerText: isEn ? "Hide forum" : "Скрыть форум" });
    const hideForumCheckbox = addElement("input", { id: "hideForumCheckbox", type: "checkbox" });
    hideForumCheckbox.checked = getPlayerBool("hideForum");
    hideForumCheckbox.addEventListener("change", function() { setPlayerValue("hideForum", this.checked); }, false);
    fieldsMap.push([hideForumLable, hideForumCheckbox]);

    createPupupPanel(GM_info.script.name, getScriptReferenceHtml() + " " + getSendErrorMailReferenceHtml(), fieldsMap);
}
// API
function addElement(type, data = {}, parent = undefined, insertPosition = "beforeend") {
    const el = document.createElement(type);
    for(const key in data) {
        if(key == "innerText" || key == "innerHTML") {
            el[key] = data[key];
        } else {
            el.setAttribute(key, data[key]);
        }
    }
    if(parent) {
        if(parent.insertAdjacentElement) {
            parent.insertAdjacentElement(insertPosition, el);
        } else if(parent.parentNode) {
            switch(insertPosition) {
                case "beforebegin":
                    parent.parentNode.insertBefore(el, parent);
                    break;
                case "afterend":
                    parent.parentNode.insertBefore(el, parent.nextSibling);
                    break;
            }
        }
    }
    return el;
}
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 getUrlParamValue(url, paramName) { return (new URLSearchParams(url.split("?")[1])).get(paramName); }
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 getValue(key, defaultValue) { return GM_getValue(key, defaultValue); };
function setValue(key, value) { GM_setValue(key, value); };
function deleteValue(key) { return GM_deleteValue(key); };
function getPlayerValue(key, defaultValue) { return GM_getValue(`${key}${PlayerId}`, defaultValue); };
function setPlayerValue(key, value) { GM_setValue(`${key}${PlayerId}`, value); };
function deletePlayerValue(key) { return GM_deleteValue(`${key}${PlayerId}`); };
function listValues() { return GM_listValues(); }
function getPlayerBool(valueName, defaultValue = false) { return getBool(valueName + PlayerId, defaultValue); }
function getBool(valueName, defaultValue = false) {
    const value = getValue(valueName);
    //console.log(`valueName: ${valueName}, value: ${value}, ${typeof(value)}`)
    if(value != undefined) {
        if(typeof(value) == "string") {
            return value == "true";
        }
        if(typeof(value) == "boolean") {
            return value;
        }
    }
    return defaultValue;
}
function getScriptLastAuthor() {
    let authors = GM_info.script.author;
    if(!authors) {
        const authorsMatch = GM_info.scriptMetaStr.match(/@author(.+)\n/);
        authors = authorsMatch ? authorsMatch[1] : "";
    }
    const authorsArr = authors.split(",").map(x => x.trim()).filter(x => x);
    return authorsArr[authorsArr.length - 1];
}
function getDownloadUrl() {
    let result = GM_info.script.downloadURL;
    if(!result) {
        const downloadURLMatch = GM_info.scriptMetaStr.match(/@downloadURL(.+)\n/);
        result = downloadURLMatch ? downloadURLMatch[1] : "";
        result = result.trim();
    }
    return result;
}
function createPupupPanel(panelName, panelTitle, fieldsMap, panelToggleHandler) {
    const backgroundPopupPanel = addElement("div", { 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;" }, document.body);
    backgroundPopupPanel.addEventListener("click", function() { hidePupupPanel(panelName, panelToggleHandler); });

    const popupPanel = addElement("div", { 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);` }, document.body);
    const contentDiv = addElement("div", { id: panelName + "3", style: "border: 1px solid #abc; padding: 5px; margin: 2px; display: flex; flex-wrap: wrap;" }, popupPanel);

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

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

    if(fieldsMap) {
        let contentTable = addElement("table", undefined, contentDiv);
        for(const rowData of fieldsMap) {
            if(rowData.length == 0) { // Спомощью передачи пустой стороки-массива, указываем, что надо начать новую таблицу после брейка
                addElement("div", { style: "flex-basis: 100%; height: 0;"}, contentDiv);
                contentTable = addElement("table", undefined, contentDiv);
                continue;
            }
            const row = addElement("tr", undefined, contentTable);
            for(const cellData of rowData) {
                const cell = addElement("td", undefined, 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 getScriptReferenceHtml() { return `<a href="${getDownloadUrl()}" title="${isEn ? "Check for update" : "Проверить обновление скрипта"}" target=_blanc>${GM_info.script.name} ${GM_info.script.version}</a>`; }
function getSendErrorMailReferenceHtml() { return `<a href="sms-create.php?mailto=${getScriptLastAuthor()}&subject=${isEn ? "Error in" : "Ошибка в"} ${GM_info.script.name} ${GM_info.script.version} (${GM_info.scriptHandler} ${GM_info.version})" target=_blanc>${isEn ? "Bug report" : "Сообщить об ошибке"}</a>`; }
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;
            }
        }
    }
}