// ==UserScript==
// @name HWM smith and cost per battle
// @namespace http://tampermonkey.net/
// @version 4.8
// @description ЦЗБ текущего ремонта, данные с аукциона для сравнения
// @author o3-mini-ChatGPT
// @match https://www.heroeswm.ru/mod_workbench.php?art_id=*&type=repair
// @grant none
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// Глобальные переменные
let globalItemName = "";
let repairRatio = 0; // Соотношение стоимости ремонта (рассчитанное значение)
let ratioSpan = null; // Элемент, в котором отображается ratio
function runScript() {
console.log("Запуск скрипта...");
//-----------------------------
// Извлечение имени предмета
//-----------------------------
// Ищем элемент, содержащий информацию о предмете
let itemTd = document.querySelector('td.wb[align="center"][colspan="2"]');
if (itemTd) {
let itemText = itemTd.textContent.trim();
// Формат: "Выбрано: ИМЯ [0/..."
let parts = itemText.split(": ");
if (parts.length > 1) {
let itemName = parts[1].split(" [0/")[0];
globalItemName = itemName; // сохраняем в глобальную переменную
console.log("Имя предмета:", itemName);
} else {
console.error("Не удалось извлечь имя предмета из строки:", itemText);
}
} else {
console.error("Элемент с информацией о предмете не найден!");
}
//-----------------------------
// Часть 1: Расчёт соотношения стоимости ремонта
//-----------------------------
let form = document.querySelector("form[name='fmain']");
if (!form) {
console.error("Форма 'fmain' не найдена!");
return;
}
// Извлекаем прочность ремонта (формат "11/13")
let bTag = form.querySelector("b");
if (!bTag) {
console.error("Элемент с прочностью не найден!");
return;
}
let durabilityText = bTag.textContent.trim(); // например "11/13"
let durabilityParts = durabilityText.split('/');
let currentDurability = parseFloat(durabilityParts[0]);
if (isNaN(currentDurability)) {
console.error("Не удалось извлечь текущее значение прочности из: '" + durabilityText + "'");
return;
}
console.log("Текущая прочность:", currentDurability);
// Извлекаем стоимость ремонта
let costImg = form.querySelector("img[src*='gold.png']");
if (!costImg) {
console.error("Изображение стоимости ремонта не найдено!");
return;
}
// Ищем текстовый узел после изображения (например, "2400" или "10469")
let costTextNode = costImg.nextSibling;
while (costTextNode && costTextNode.nodeType === Node.TEXT_NODE && !costTextNode.nodeValue.trim()) {
costTextNode = costTextNode.nextSibling;
}
if (!costTextNode) {
costTextNode = costImg.nextElementSibling;
}
if (!costTextNode) {
console.error("Текстовый узел стоимости ремонта не найден!");
return;
}
let costText = costTextNode.nodeValue ? costTextNode.nodeValue.trim() : costTextNode.textContent.trim();
// Убираем разделитель тысяч (если есть)
let repairCost = parseFloat(costText.replace(/,/g, ''));
if (isNaN(repairCost)) {
console.error("Не удалось извлечь стоимость ремонта из текста: '" + costText + "'");
return;
}
console.log("Стоимость ремонта:", repairCost);
// Вычисляем соотношение стоимости ремонта к прочности
let ratio = repairCost / currentDurability;
ratio = Math.round(ratio * 100) / 100;
repairRatio = ratio; // сохраняем для дальнейшего сравнения
console.log("Вычисленное соотношение:", ratio);
// Выводим значение ratio рядом с прочностью.
// Первоначально значение ЦЗБ выводится без выделения,
// оно будет обновлено после вычисления аукционных данных.
let resultSpan = document.createElement("span");
resultSpan.style.marginLeft = "10px";
resultSpan.style.fontWeight = "bold";
resultSpan.textContent = " ЦЗБ: " + ratio;
bTag.parentNode.insertBefore(resultSpan, bTag.nextSibling);
ratioSpan = resultSpan; // сохраняем ссылку для обновления цвета
}
// Функция для получения и вывода ссылки на аукцион через iframe с использованием MutationObserver
function loadAuctionInfo() {
// Поиск ссылки на страницу с информацией о предмете (например: art_info.php?id=sword18)
let artInfoAnchor = document.querySelector('a[href^="art_info.php?id="]');
if (!artInfoAnchor) {
console.error("Ссылка на информацию о предмете не найдена!");
return;
}
let artInfoUrl = artInfoAnchor.href;
// Приводим относительный адрес к абсолютному, если нужно
artInfoUrl = new URL(artInfoUrl, window.location.origin).href;
console.log("Переход на страницу с информацией о предмете:", artInfoUrl);
// Создаем скрытый iframe для загрузки страницы информации о предмете
let iframe = document.createElement("iframe");
console.log("DEBUG: Создан iframe");
iframe.style.display = "none";
iframe.src = artInfoUrl;
console.log("DEBUG: Установлен iframe.src =", artInfoUrl);
iframe.onload = function() {
console.log("DEBUG: iframe.onload сработал");
try {
let iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
console.log("DEBUG: Получен документ iframe:", iframeDoc);
// Используем MutationObserver для отслеживания появления ссылки на аукцион
let observer = new MutationObserver(function(mutations, obs) {
console.log("DEBUG: MutationObserver сработал, число мутаций =", mutations.length);
let auctionLinkElem = iframeDoc.querySelector("a[href*='auction.php?']");
console.log("DEBUG: auctionLinkElem =", auctionLinkElem);
if (auctionLinkElem) {
// Извлекаем параметры из ссылки на аукцион
let urlParams = new URLSearchParams(auctionLinkElem.href.split('?')[1]);
let sortParam = urlParams.get('sort') || '4';
let catParam = urlParams.get('cat') || '';
let artTypeParam = urlParams.get('art_type') || '';
console.log("DEBUG: Извлечены параметры: sort =", sortParam, "| cat =", catParam, "| artType =", artTypeParam);
// Если параметры не найдены, пытаемся другую стратегию
if (!sortParam || !catParam || !artTypeParam) {
console.log("DEBUG: Основные параметры отсутствуют, пробуем извлечь из других ссылок");
// Попытка извлечения из других элементов
let links = iframeDoc.querySelectorAll("a");
links.forEach(link => {
if (link.href.indexOf("auction.php?") !== -1) {
let paramsTemp = new URLSearchParams(link.href.split('?')[1]);
sortParam = paramsTemp.get('sort') || sortParam;
catParam = paramsTemp.get('cat') || catParam;
artTypeParam = paramsTemp.get('art_type') || artTypeParam;
console.log("DEBUG: Обновленные параметры после перебора ссылки: sort =", sortParam, "| cat =", catParam, "| artType =", artTypeParam);
}
});
}
if (!sortParam || !catParam || !artTypeParam) {
console.error("DEBUG: Не удалось извлечь все параметры для формирования ссылки на аукцион.");
obs.disconnect();
iframe.remove();
return;
}
// Формируем итоговую ссылку, добавляя параметры type=0 и snew=1
let finalAuctionLink = `https://www.heroeswm.ru/auction.php?sort=${sortParam}&cat=${catParam}&art_type=${artTypeParam}&sbn=1&sau=0&snew=1`;
console.log("DEBUG: Сформированная finalAuctionLink =", finalAuctionLink);
obs.disconnect();
iframe.remove();
// Загружаем страницу аукциона и ищем лоты, где в одном из элементов присутствует имя предмета
processAuctionPage(finalAuctionLink);
}
});
observer.observe(iframeDoc.body, { childList: true, subtree: true });
console.log("DEBUG: MutationObserver установлен");
} catch(e) {
console.error("DEBUG: Ошибка в iframe.onload:", e);
iframe.remove();
}
};
document.body.appendChild(iframe);
console.log("DEBUG: iframe добавлен в document.body");
}
// Функция для загрузки страницы аукциона, поиска лотов с нужным именем предмета,
// извлечения значений прочности и цены, выбора минимальной цены для каждой прочности,
// вычисления цены за единицу прочности и формирования гиперссылки с итоговой информацией.
function processAuctionPage(finalAuctionLink) {
console.log("Загрузка страницы аукциона:", finalAuctionLink);
let auctionIframe = document.createElement("iframe");
auctionIframe.style.display = "none";
auctionIframe.src = finalAuctionLink;
auctionIframe.onload = function() {
try {
let auctionDoc = auctionIframe.contentDocument || auctionIframe.contentWindow.document;
console.log("Страница аукциона загружена. Ожидание динамического контента...");
// Задержка 2 секунды для полной загрузки динамического контента
setTimeout(() => {
// Находим все строки (элементы <tr> с классом "wb"), в которых встречается строка вида " - <itemName>"
let allRows = auctionDoc.querySelectorAll("tr.wb");
let lots = [];
allRows.forEach(row => {
if (row.innerText.indexOf("- " + globalItemName) !== -1) {
// Извлекаем прочность из ячейки с valign="top" (из строки "Прочность: NN/...")
let strengthCell = row.querySelector("td[valign='top']");
if (strengthCell) {
let strengthMatch = strengthCell.innerText.match(/Прочность:\s*(\d+)\//);
if (strengthMatch) {
let strength = Number(strengthMatch[1]);
// Извлекаем цену из ячейки, содержащей изображение золота
let goldImg = row.querySelector("img[src*='gold.png']");
if (goldImg && goldImg.parentElement) {
let priceTd = goldImg.parentElement.nextElementSibling;
if (priceTd) {
// Извлекаем значение цены (например "13,000")
let priceMatch = priceTd.textContent.match(/([\d,]+)/);
if (priceMatch) {
let priceStr = priceMatch[1].replace(/,/g, '');
let price = Number(priceStr);
lots.push({ strength, price });
}
}
}
}
}
}
});
console.log("Извлеченные лоты (прочность и цена):", lots);
// Если лотов нет, выходим
if (lots.length === 0) {
console.error("Лоты с предметом '" + globalItemName + "' не найдены.");
auctionIframe.remove();
return;
}
// Группируем по прочности (минимальная цена для каждой прочности)
let minPricesByStrength = {};
lots.forEach(lot => {
if (!minPricesByStrength[lot.strength] || lot.price < minPricesByStrength[lot.strength]) {
minPricesByStrength[lot.strength] = lot.price;
}
});
console.log("Минимальные цены для каждой прочности:", minPricesByStrength);
// Вычисляем цену за единицу прочности для каждого лота и ищем:
// - лот с минимальной прочностью
// - лот с максимальной прочностью
// - лот с наименьшей ценой за единицу прочности
let candidateMin = lots[0],
candidateMax = lots[0],
candidateBest = lots[0];
for (let lot of lots) {
if (lot.strength < candidateMin.strength) {
candidateMin = lot;
}
if (lot.strength > candidateMax.strength) {
candidateMax = lot;
}
if ((lot.price / lot.strength) < (candidateBest.price / candidateBest.strength)) {
candidateBest = lot;
}
}
let unitPriceMin = candidateMin.price / candidateMin.strength;
let unitPriceMax = candidateMax.price / candidateMax.strength;
let unitPriceBest = candidateBest.price / candidateBest.strength;
// Формируем строку вида:
// "Аук: <минимальная прочность> - <unitPrice_min>;
// <максимальная прочность> - <unitPrice_max>;
// <прочность лота с минимальной unitPrice> - <unitPrice_best (выделено жирным зелёным)>"
let linkHTML = "Аук: "
+ candidateMin.strength + " - " + unitPriceMin.toFixed(2) + "; "
+ candidateMax.strength + " - " + unitPriceMax.toFixed(2) + "; "
+ candidateBest.strength + " - " + "<span style='color:green; font-weight:bold;'>" + unitPriceBest.toFixed(2) + "</span>";
console.log("Сформированная ссылка для аукциона:", linkHTML);
// Сравнение соотношения ремонта (repairRatio) с unitPriceBest и выделение его цветом:
// Если repairRatio меньше unitPriceBest - выделяем зелёным, иначе красным.
let ratioColor = (repairRatio < unitPriceBest) ? "green" : "red";
if (ratioSpan) {
ratioSpan.innerHTML = " ЦЗБ: <span style='color:" + ratioColor + "; font-weight:bold;'>" + repairRatio.toFixed(2) + "</span>";
}
// Создаем гиперссылку на аукцион с полученной строкой
let linkElement = document.createElement("a");
linkElement.href = finalAuctionLink;
linkElement.innerHTML = linkHTML;
console.log("Сформированная ссылка для аукциона:", linkElement.outerHTML);
// Обновляем раздел стоимости ремонта на странице (в форме fmain)
// Находим текстовый узел после изображения стоимости ремонта.
let formElem = document.querySelector("form[name='fmain']");
if (formElem) {
let goldImg = formElem.querySelector("img[src*='gold.png']");
if (goldImg) {
let candidateNode = goldImg.nextSibling;
while (candidateNode && candidateNode.nodeType === Node.TEXT_NODE && !candidateNode.nodeValue.trim()) {
candidateNode = candidateNode.nextSibling;
}
if (candidateNode) {
// Если сразу после текстового узла есть перенос строки, удаляем его.
let nextElem = candidateNode.nextElementSibling;
if (nextElem && nextElem.tagName.toLowerCase() === "br") {
nextElem.remove();
}
// Ищем существующий span после текста стоимости ремонта.
let auctionSpan = candidateNode.nextElementSibling;
if (auctionSpan && auctionSpan.tagName.toLowerCase() === "span") {
// Обновляем содержимое этого span, убираем лишние <br>.
auctionSpan.innerHTML = linkElement.outerHTML;
// Убираем лишние переносы внутри span, если они есть.
let brs = auctionSpan.querySelectorAll("br");
brs.forEach(br => br.remove());
// Если за span не следует перенос строки, добавляем его.
let afterSpan = auctionSpan.nextSibling;
if (!(afterSpan && afterSpan.nodeType === Node.ELEMENT_NODE
&& afterSpan.tagName.toLowerCase() === "br")) {
let br = document.createElement("br");
auctionSpan.parentNode.insertBefore(br, auctionSpan.nextSibling);
}
} else {
// Если span отсутствует, создаем его со структурой:
// [перенос строки] <span>ссылка</span> [перенос строки]
let brBefore = document.createElement("br");
let auctionContainer = document.createElement("span");
auctionContainer.innerHTML = linkElement.outerHTML;
let brAfter = document.createElement("br");
candidateNode.parentNode.insertBefore(brBefore, candidateNode.nextSibling);
candidateNode.parentNode.insertBefore(auctionContainer, brBefore.nextSibling);
candidateNode.parentNode.insertBefore(brAfter, auctionContainer.nextSibling);
}
}
}
}
auctionIframe.remove();
}, 2000);
} catch (e) {
console.error("Ошибка при обработке страницы аукциона:", e);
auctionIframe.remove();
}
};
document.body.appendChild(auctionIframe);
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", function() {
runScript();
loadAuctionInfo();
});
} else {
runScript();
loadAuctionInfo();
}
})();