HWM_RepairAssistant

Помощник для ремонта через новый интерфейс

当前为 2019-12-26 提交的版本,查看 最新版本

// ==UserScript==
// @name         HWM_RepairAssistant
// @namespace    Небылица
// @version      1.0
// @description  Помощник для ремонта через новый интерфейс
// @author       Небылица
// @include      /^https{0,1}:\/\/((www|qrator)\.heroeswm\.ru|178\.248\.235\.15)\/(art_transfer|pl_transfers)\.php/
// @grant        GM_setValue
// @grant        GM_getValue
// ==/UserScript==

(function() {
    "use strict";

    // Вспомогательные функции
    function validateNick(nick){ // Возвращает массив ["результат валидации (true/false)", "строка с тем, что не так (либо ник, если верен)"]
        if (nick.length < 3 || nick.length > 18){
            return [false, "Ник должен быть не длиннее 18 символов и не короче 3"];
        }
        if (nick.match(/^\d+$/)){
            return [false, "Ник не может содержать только цифры"];
        }
        if (nick.match(/[^\W_]/) && nick.match(/[а-яА-Я]/)){ // _, дефолтно включённое в латиницу, может сочетаться с кириллицей
            return [false, "Ник может быть либо целиком из русских букв, либо целиком из английских (смешивать нельзя)"];
        }
        if (!nick.match(/^[a-zA-zа-яА-Я\d\s_-]+$/)){
            return [false, "Ник не должен содержать символов помимо русских или английских букв, цифр, знака подчёркивания, дефиса и пробела"];
        }
        return [true, nick];
    }
    function validatePrice(price){ // Возвращает цену в формате "2/3 цифры и знак %", либо false при невозможности обработки
        if (price.match(/^\d{2,3}%$/)){return price;}
        if (price.match(/^\d{2,3}$/)){return price + "%";}
        return false;
    }
    function drawSmithsTable(smiths, smithsDiv){ // Принимает массив кузнецов и рабочий элемент, отрисовывает в него таблицу и вяжет события удаления
        if (smiths.length > 0){
            var smithsDivInnerHTML =
                "<style>" +
                ".RA_smithsTr:hover{background: #DCDCDC;}" +
                ".RA_smithsTdNick, .RA_smithsTdEfficiency, .RA_smithsTdPrice{cursor: pointer;}" +
                "</style>";

            smithsDivInnerHTML += "<table style='text-align: center;'><tbody>";
            maxI = smiths.length;
            for (i=0;i<maxI;i++){
                smithsDivInnerHTML += "<tr class='RA_smithsTr' title='" + smiths[i][3] + "'>";
                smithsDivInnerHTML += "<td class='RA_smithsTdNick' width='119px' style='font-weight: bold;'><a href='pl_info.php?nick="+ encodeCP1251(smiths[i][0]) + "' target='_blank'>" + smiths[i][0] + "</a></td>";
                smithsDivInnerHTML += "<td class='RA_smithsTdEfficiency' width='55px'>" + smiths[i][1] + "</td>";
                smithsDivInnerHTML += "<td class='RA_smithsTdPrice' width='55px'>" + smiths[i][2] + "</td>";
                smithsDivInnerHTML += "<td width='16px' title='Удалить кузнеца' style='font-size: 11px;' class='RA_smithsTdDelete' id='RA_smithsTdDeleteSmith" + i + "'>x</td>";
                smithsDivInnerHTML += "</tr>";
            }
            smithsDivInnerHTML += "</tbody></table>";

            smithsDiv.innerHTML = smithsDivInnerHTML;
            setDeleteSmithEvents();
            setFillSmithEvents();
        } else{
            smithsDiv.innerHTML = "<center>Кузнецов пока нет</center>";
        }
    }
    function setDeleteSmithEvents(){ // Вяжет события к кнопкам удаления
        var deleteButtons = document.querySelectorAll("td.RA_smithsTdDelete");
        if (deleteButtons.length > 0){
            var maxI = deleteButtons.length;
            for (i=0;i<maxI;i++){
                deleteButtons[i].onclick = function(event){
                    smiths.splice(parseInt(event.target.getAttribute("id").replace("RA_smithsTdDeleteSmith", "")), 1);
                    setSmiths(smiths);
                    drawSmithsTable(smiths, document.getElementById("RA_smithsDiv"));
                }
            }
        } else{window.setTimeout(function(){setDeleteSmithEvents()}, 50);}
    }
    function setFillSmithEvents(){ // Вяжет события подстановки кузнецов
        var smithsTds = document.querySelectorAll("td.RA_smithsTdNick, td.RA_smithsTdEfficiency, td.RA_smithsTdPrice");
        if (smithsTds.length > 0){
            var maxI = smithsTds.length;
            for (i=0;i<maxI;i++){
                smithsTds[i].onclick = function(event){
                    var thisSmithTds = event.target.parentElement.childNodes,
                        nickInput = document.querySelector("input[name='nick']"),
                        finalPrice = Math.ceil(parseInt(baseGoldPrice)*priceToFloat(thisSmithTds[2].innerText));
                    nickInput.value = thisSmithTds[0].innerText;

                    var a = document.createElement("a");
                    a.href = "#";
                    a.setAttribute("onclick", "set_price(" + finalPrice + ");");
                    document.body.appendChild(a);
                    a.click();
                    setTimeout(function(){document.body.removeChild(a);}, 0);
                }
            }
        } else{window.setTimeout(function(){setFillSmithEvents()}, 50);}
    }
    function clearNewSmith(){ // Очистка полей в newSmithDiv
        newSmithNick.value = "";
        newSmithEfficiency.value = "90%";
        newSmithPrice.value = "100%";
        newSmithNote.value = "";
    }
    function setSmiths(smiths){ // Принимает массив кузнецов, формирует и записывает в хранилище строку
        var newSmithsString = "";
        maxI = smiths.length;
        for (i=0;i<maxI;i++){
            newSmithsString += smiths[i].join(":");
            if (i !== (smiths.length - 1)){newSmithsString += "|";}
        }
        GM_setValue("smiths" + plIdSubKey, newSmithsString);
    }
    function priceToFloat(price){ // Переводит цену из строки в число (100% --> 1)
        return parseFloat(price.replace("%", "")/100);
    }
    function calculateRepairTime(baseGoldPrice, repairBeginningMomentOnServer){ // Принимает базовую цену и момент начала ремонта, возвращает массив ["время ремонта (A ч. B мин.)", "время окончания (xx:yy)", "объект времени окончания"]
        var repairHoursFull = baseGoldPrice/4000,
            repairHours = Math.trunc(repairHoursFull),
            repairMinutes = Math.ceil(60*(repairHoursFull - Math.trunc(repairHoursFull))),
            repairEndMomentOnServer = new Date(repairBeginningMomentOnServer.getTime() + Math.ceil(repairHoursFull*60*60*1000));
        return [repairHours + " ч. " + repairMinutes + " мин.",
                addLeadingZero(repairEndMomentOnServer.getHours()) + ":" +
                addLeadingZero(repairEndMomentOnServer.getMinutes()),
                repairEndMomentOnServer];
    }
    function setModifiedLayout(mode){ // true – показывать таблицу кузнецов + перенос кнопки отправки вместо стоимости арта, false – возврат обратно
        var containerDiv = document.getElementById("RA_containerDiv");
        if (mode){
            if (containerDiv){containerDiv.style.display = "block";}
            artPriceTr.style.display = "none";
            artSendButtonTr1.style.display = "table-row";
            artSendButtonTr2.style.display = "none";
        } else{
            if (containerDiv){containerDiv.style.display = "none";}
            artPriceTr.style.display = "table-row";
            artSendButtonTr1.style.display = "none";
            artSendButtonTr2.style.display = "table-row";
        }
    }
    function getBaseGoldPrice(){ // Возвращает базовую цену ремонта
        var baseGoldPriceMatch = document.documentElement.innerHTML.match(/repair_price\s=\s(\d+);/),
            baseGoldPrice =
            (baseGoldPriceMatch) ? baseGoldPriceMatch[1] :
        document.getElementById("rep").childNodes[4].childNodes[0].childNodes[6].childNodes[3].innerText.replace(",", "");
        return baseGoldPrice;
    }
    function encodeCP1251(text){ // Перекодирует русский текст так, чтобы при отправке запроса не выходили кракозябры
        var result = "",
            CP1251toUTF8 = {
                "А": "%C0",
                "Б": "%C1",
                "В": "%C2",
                "Г": "%C3",
                "Д": "%C4",
                "Е": "%C5",
                "Ж": "%C6",
                "З": "%C7",
                "И": "%C8",
                "Й": "%C9",
                "К": "%CA",
                "Л": "%CB",
                "М": "%CC",
                "Н": "%CD",
                "О": "%CE",
                "П": "%CF",

                "Р": "%D0",
                "С": "%D1",
                "Т": "%D2",
                "У": "%D3",
                "Ф": "%D4",
                "Х": "%D5",
                "Ц": "%D6",
                "Ч": "%D7",
                "Ш": "%D8",
                "Щ": "%D9",
                "Ъ": "%DA",
                "Ы": "%DB",
                "Ь": "%DC",
                "Э": "%DD",
                "Ю": "%DE",
                "Я": "%DF",

                "а": "%E0",
                "б": "%E1",
                "в": "%E2",
                "г": "%E3",
                "д": "%E4",
                "е": "%E5",
                "ж": "%E6",
                "з": "%E7",
                "и": "%E8",
                "й": "%E9",
                "к": "%EA",
                "л": "%EB",
                "м": "%EC",
                "н": "%ED",
                "о": "%EE",
                "п": "%EF",

                "р": "%F0",
                "с": "%F1",
                "т": "%F2",
                "у": "%F3",
                "ф": "%F4",
                "х": "%F5",
                "ц": "%F6",
                "ч": "%F7",
                "ш": "%F8",
                "щ": "%F9",
                "ъ": "%FA",
                "ы": "%FB",
                "ь": "%FC",
                "э": "%FD",
                "ю": "%FE",
                "я": "%FF",

                "Ё": "%A8",
                "ё": "%B8",

                " ": "%20",
                "!": "%21",
                "(": "%28",
                ")": "%29",
                "*": "%2A",
                "+": "%2B",
                ",": "%2C",
                "-": "%2D",
                ".": "%2E",
                "/": "%2F"
            };

        var i,
            maxI = text.length;
        for (i=0;i<maxI;i++){
            if (CP1251toUTF8[text[i]] !== undefined){
                result += CP1251toUTF8[text[i]];
            } else{
                result += text[i];
            }
        }

        return result;
    }
    function insertAfter(newNode, referenceNode){ // Вставка newNode после referenceNode
        referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
    }
    function addLeadingZero(string){ // Вставляет ведущий ноль в строку с элементом даты/времени, если в ней только 1 цифра
        string = string.toString();
        if (string.length === 1){string = "0" + string;}
        return string;
    }
    //

    var i,
        maxI,
        currentMoment,
        currentMomentOnServer,
        repairTimeArr,
        baseGoldPrice;
    if (location.pathname.indexOf("art_transfer") !== -1){ // передача арта
        var repairRadio = document.querySelector("input[type='radio'][name='sendtype'][value='5']");
        if (repairRadio){ // если арт сломанный
            // меняем внешний вид в зависимости от режима, создаём дублированную кнопку
            var artTableTrs = document.querySelector("table.wb[width='600'][cellpadding='4'][align='center']").childNodes[1].childNodes,
                artPriceTr = artTableTrs[8],
                artSendModeTr = artTableTrs[10],
                artSendButtonTr2 = artTableTrs[12],
                artSendButtonTr1 = artSendButtonTr2.cloneNode(true),

                noTransferRadio = document.querySelector("input[type='radio'][name='sendtype'][value='0']"),
                ownershipRadio = document.querySelector("input[type='radio'][name='sendtype'][value='1']"),
                rentRadio = document.querySelector("input[type='radio'][name='sendtype'][value='2']");
            insertAfter(artSendButtonTr1, artPriceTr);
            artSendButtonTr1.children[1].innerHTML = " <input type='button' value='Передать' id='tr_button_clone'>";
            var tr_button = document.getElementById("tr_button"),
                tr_button_clone = document.getElementById("tr_button_clone");
            tr_button_clone.onclick = function(){tr_button.click();};
            var observer = new MutationObserver(function(mutations) {
                mutations.forEach(function(mutationRecord) {
                    tr_button_clone.disabled = tr_button.disabled;
                });
            });
            observer.observe(tr_button, {attributes : true, attributeFilter : ['disabled']});

            repairRadio.addEventListener("click", function(){setModifiedLayout(repairRadio.checked);});
            if (noTransferRadio){noTransferRadio.addEventListener("click", function(){setModifiedLayout(repairRadio.checked);});}
            if (ownershipRadio){ownershipRadio.addEventListener("click", function(){setModifiedLayout(repairRadio.checked);});}
            if (rentRadio){rentRadio.addEventListener("click", function(){setModifiedLayout(repairRadio.checked);});}

            repairRadio.click();

            // получаем id текущего персонажа и кусок ключа по нему, запомнаем базовую цену ремонта
            var plId = document.querySelector("li > a[href^='pl_hunter_stat.php']").getAttribute("href").split("id=")[1],
                plIdSubKey = "|#" + plId;
            baseGoldPrice = getBaseGoldPrice();

            // задаём дефолтный/получаем список кузнецов
            if (GM_getValue("smiths" + plIdSubKey) === undefined){GM_setValue("smiths" + plIdSubKey, "");}
            var smithsString = GM_getValue("smiths" + plIdSubKey),
                smiths = (smithsString) ? smithsString.split("|") : [];
            maxI = smiths.length;
            for (i=0;i<maxI;i++){
                smiths[i] = smiths[i].split(":");
            }

            // создаём элемент под таблицу кузнецов
            var containerDiv = document.createElement("div"),
                smithsDiv = document.createElement("div"),
                containerTd = document.querySelector("td.wbwhite[valign='top'][align='right']");

            containerDiv.setAttribute("id", "RA_containerDiv");
            smithsDiv.setAttribute("id", "RA_smithsDiv");

            drawSmithsTable(smiths, smithsDiv);

            containerTd.innerHTML = "";
            containerTd.appendChild(containerDiv);
            containerDiv.appendChild(smithsDiv);
            containerDiv.innerHTML += "<hr width='100%'>";
            containerTd.nextSibling.nextSibling.setAttribute("valign", "top");

            // создаём элемент под панель добавления нового кузнеца
            var newSmithDiv = document.createElement("div");
            newSmithDiv.setAttribute("id", "RA_newSmithDiv");
            newSmithDiv.innerHTML =
                "<center><b>Добавить нового кузнеца</b><br>" +
                "<table><tbody><tr>" +
                "<td width='122px'>" +
                "<input type='text' id='RA_newSmithNick' style='width: 120px; padding-left: 3px;' placeholder='Ник'>" +
                "</td>" +
                "<td width='55px'>" +
                "<select id='RA_newSmithEfficiency' style='height: 21px;'>" +
                "<option value='90%'>90%</option>" +
                "<option value='80%'>80%</option>" +
                "<option value='70%'>70%</option>" +
                "<option value='60%'>60%</option>" +
                "<option value='50%'>50%</option>" +
                "<option value='40%'>40%</option>" +
                "<option value='30%'>30%</option>" +
                "<option value='20%'>20%</option>" +
                "<option value='10%'>10%</option>" +
                "</select></td>" +
                "<td width='55px'>" +
                "<input type='text' id='RA_newSmithPrice' style='width: 53px; padding-left: 3px;' placeholder='Цена' value='100%'>" +
                "</td></tr>" +
                "<tr><td colspan='3'><input type='text' id='RA_newSmithNote' style='width: 232px; padding-left: 3px;' placeholder='Примечание (опционально)'>" +
                "</td></tr></tbody><table>" +
                "<input type='button' id='RA_newSmithButton' value='Добавить'></center>";
            containerDiv.appendChild(newSmithDiv);

            var newSmithNick = document.getElementById("RA_newSmithNick"),
                newSmithEfficiency = document.getElementById("RA_newSmithEfficiency"),
                newSmithPrice = document.getElementById("RA_newSmithPrice"),
                newSmithNote = document.getElementById("RA_newSmithNote"),
                newSmithButton = document.getElementById("RA_newSmithButton"),
                defaultPrices = {
                    "90%": "100%",
                    "80%": "80%",
                    "70%": "70%",
                    "60%": "60%",
                    "50%": "50%",
                    "40%": "40%",
                    "30%": "30%",
                    "20%": "20%",
                    "10%": "10%"
                };

            // автоподстановка цены по эффективности
            newSmithEfficiency.onchange = function(){
                newSmithPrice.value = defaultPrices[newSmithEfficiency.value];
            };

            // запись нового кузнеца
            newSmithButton.onclick = function(){
                var newSmithNickValue = validateNick(newSmithNick.value),
                    newSmithPriceValue = validatePrice(newSmithPrice.value);
                if (newSmithNickValue[0]){ // проверка ника
                    if (newSmithPriceValue){ // проверка цены
                        smiths.push([newSmithNickValue[1], newSmithEfficiency.value, newSmithPriceValue, newSmithNote.value]);

                        // сортировка массива кузнецов: по эффективности по убыванию, затем по цене по возрастанию
                        smiths.sort(function(smith1, smith2){
                            if (smith1[1] < smith2[1]){return 1;}
                            if (smith1[1] > smith2[1]){return -1;}
                            if (smith1[1] === smith2[1]){
                                if (priceToFloat(smith1[2]) < priceToFloat(smith2[2])){return -1;}
                                if (priceToFloat(smith1[2]) > priceToFloat(smith2[2])){return 1;}
                                return 0;
                            }
                        });

                        // сохраняем кузнецов и перерисовывем таблицу
                        setSmiths(smiths);
                        drawSmithsTable(smiths, document.getElementById("RA_smithsDiv"));
                        clearNewSmith();
                    } else{
                        window.alert("Цена должна представлять из себя 2 или 3 цифры с/без знака %");
                    }
                } else{ // если ник неверен, показываем, что не так
                    alert(newSmithNickValue[1]);
                }
            };

            // вставляем элемент и вяжем показ времени ремонта
            var priceInput = document.getElementById("rep_price"),
                repairTimeSpan = document.createElement("span");
            currentMoment = new Date();
            currentMomentOnServer = new Date(Date.now() + currentMoment.getTimezoneOffset()*60000 + 10800000);
            repairTimeArr = calculateRepairTime(baseGoldPrice, currentMomentOnServer);

            repairTimeSpan.setAttribute("id", "RA_repairTimeSpan");
            repairTimeSpan.style.marginLeft = "5px";
            repairTimeSpan.innerHTML = "(" + repairTimeArr[0] + ", >=" + repairTimeArr[1] + ")";
            insertAfter(repairTimeSpan, priceInput);

            // заменяем кнопку "+10%" на -1%
            var addPrice10Button = document.querySelector("input[type='submit'][onclick*='add_price(10)']");
            addPrice10Button.setAttribute("onclick", "add_price(-1); return false;");
            addPrice10Button.value = "-1%";
        }
    } else{ // протокол передач
        var protocolTd = document.querySelector("table[cellpadding='0'][border='0'][cellspacing='0'][width='95%'] > tbody > tr > td"),
            repairsMatch = protocolTd.innerHTML.match(/\d{2}-\d{2}-\d{2}\s\d{2}:\d{2}:.+?\sза\sремонт:\s\d+\s\(\d+%\)/g),
            repairBeginningTime,
            repairBeginningTimeParts,
            repairRealGoldPrice,
            repairPercent,
            oldSubstring,
            newSubstring,
            repairBeginningMomentOnServer;
        currentMoment = new Date();
        currentMomentOnServer = new Date(Date.now() + currentMoment.getTimezoneOffset()*60000 + 10800000);

        maxI = repairsMatch.length;
        for (i=0;i<maxI;i++){
            oldSubstring = repairsMatch[i];
            repairBeginningTime = oldSubstring.match(/(\d{2}-\d{2}-\d{2}\s\d{2}:\d{2}):/)[1];
            repairBeginningTimeParts = repairBeginningTime.split(/[\s-:]/);
            repairRealGoldPrice = oldSubstring.match(/\sза\sремонт:\s(\d+)\s/)[1];
            repairPercent = oldSubstring.match(/\sза\sремонт:\s\d+\s\((\d+)%\)/)[1];

            repairBeginningMomentOnServer = new Date("20" + repairBeginningTimeParts[2],
                                                     (parseInt(repairBeginningTimeParts[1])-1),
                                                     repairBeginningTimeParts[0],
                                                     repairBeginningTimeParts[3],
                                                     repairBeginningTimeParts[4]);
            baseGoldPrice = Math.floor(repairRealGoldPrice/(repairPercent/100));
            repairTimeArr = calculateRepairTime(baseGoldPrice, repairBeginningMomentOnServer);

            if (repairTimeArr[2] > currentMomentOnServer){
                newSubstring = oldSubstring + ". <i>В ремонте до " + repairTimeArr[1] + ".</i>";
                protocolTd.innerHTML = protocolTd.innerHTML.replace(oldSubstring, newSubstring);
            }
        }
    }
})();