hwmAdvancedMenu

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

当前为 2024-12-05 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 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;
            }
        }
    }
}