TransferLog Search & Balance

HWM TransferLog Search with balance calculation

// ==UserScript==
// @name			TransferLog Search & Balance
// @author			Neleus
// @namespace		Neleus
// @description	HWM TransferLog Search with balance calculation
// @version			1.2
// @include				/^https?:\/\/(www|mirror|my)?\.?(heroeswm|lordswm)\.(ru|com)\/pl_transfers\.php.*/
// @grant			none
// @run-at			document-end
// @license			GNU GPLv3
// ==/UserScript==

(function () {
  // Constants
  const SELECTORS = {
    CONTAINER_HEADER: "global_container_block_header global_a_hover",
    GLOBAL_HOVER: "global_a_hover",
  };

  const ELEMENT_IDS = {
    STOP: "stop",
    SEARCH_DIV: "transferSearchDiv",
    SEARCH_STATUS: "TSearch",
    CHECKBOX_LABEL: "HWM_transfer_search_checkbox_label",
    SHOW_BLOCK: "show_transfer_block",
  };

  const INPUT_IDS = {
    NICK: "TSearchNick",
    ART: "TSearchArt",
    ALL: "TSearchAll",
    PAGE_FROM: "TSearch_inp_page_from",
    PAGE_TO: "TSearch_inp_page_to",
  };

  const SEARCH_TYPES = {
    NICK: "Nick",
    FINE: "Fine",
    ART: "Art",
    ANY: "Any",
  };

  const DATE_TIME_PATTERN = "[0-9]{2}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}";

  // Global variables
  const url = location.protocol + "//" + location.hostname + "/";
  const playerId = getPlayerId();
  const hiddenDiv = createHiddenDiv();
  let searchBalance = 0; // Общий баланс найденных операций
  let totalBalance = 0; // Общий баланс всех операций на странице

  // Initialize

  // Глобальная функция для остановки поиска
  window.stopSearch = function () {
    const stopButton = $(ELEMENT_IDS.STOP);
    if (stopButton) {
      stopButton.value = "1";
    }
  };

  const rootElement = findRootElement();

  applyInitialColoringAndBalance();
  createSearchInterface(rootElement);
  setupEventListeners(rootElement);

  // Balance Processing Functions
  function makeColoredLine(line, gold, color) {
    return `<span style='background:rgba(${
      color === "green" ? "100,255,100,.1" : "255,100,100,.1"
    })'>${line}&nbsp; <b style='color:${color}'>${gold}</b></span>`;
  }

  function parseTransferLines(htmlContent) {
    const matches = [];
    const regex = /(&nbsp;&nbsp;.+?)<br>/g;
    let match;
    while ((match = regex.exec(htmlContent)) !== null) {
      matches.push(match[1]);
    }
    return matches;
  }

  // Применяем раскраску и баланс при загрузке страницы (как в старом скрипте)
  function applyInitialColoringAndBalance() {
    const transferDiv = rootElement.getElementsByClassName("global_a_hover")[0];
    if (!transferDiv) return;

    const originalHTML = transferDiv.innerHTML;

    if (
      originalHTML.includes("<span style=") &&
      originalHTML.includes("Баланс золота:")
    ) {
      return;
    }

    const bodyHTML = document.body.innerHTML;
    const lines = parseTransferLines(bodyHTML);

    let html = "";
    totalBalance = 0;
    let processedCount = 0;

    for (let line of lines) {
      if (line.trim()) {
        const { processedLine, goldAmount } = processTransferBalance(line);
        html += processedLine + "<br>";
        totalBalance += goldAmount;
        processedCount++;
      }
    }

    if (lines.length > 0) {
      transferDiv.innerHTML = html;
      transferDiv.insertAdjacentHTML(
        "beforeend",
        `<br><b style='padding:20px'>Баланс золота: <span style='color:${
          totalBalance < 0 ? "red" : "green"
        }'>${totalBalance > 0 ? "+" : ""}${totalBalance.toLocaleString(
          "en-US"
        )}</span></b>`
      );
    }
  }

  function extractOriginalText(coloredLine) {
    if (!coloredLine || !coloredLine.includes("<span style=")) {
      return coloredLine;
    }

    return coloredLine
      .replace(/<span[^>]*>/g, "")
      .replace(/<\/span>/g, "")
      .replace(/<b[^>]*>.*?<\/b>/g, "")
      .replace(/&nbsp;/g, " ")
      .trim();
  }

  function cleanLineForParsing(line) {
    return line.replace(/<[^>]*>/g, "");
  }

  function extractGoldAmount(line, pattern, isNegative = false) {
    const cleanLine = cleanLineForParsing(line);
    const match = pattern.exec(cleanLine);
    if (match) {
      const amount = +match[1];
      return isNegative ? -amount : amount;
    }
    return 0;
  }

  function extractGoldWithCommission(
    line,
    goldPattern,
    commissionPattern,
    isNegative = false
  ) {
    const cleanLine = cleanLineForParsing(line);
    const goldMatch = goldPattern.exec(cleanLine);
    const commissionMatch = commissionPattern
      ? commissionPattern.exec(cleanLine)
      : null;

    if (goldMatch) {
      let amount = +goldMatch[1];
      if (commissionMatch) {
        amount -= +commissionMatch[1];
      }
      return isNegative ? -amount : amount;
    }
    return 0;
  }

  function processTransferBalance(line) {
    let gold = 0;
    let newline = line;

    try {
      // Обмен бриллиантов на золото: "10 бриллиантов обменяно на 50000 золота"
      if (/обменян/.test(line)) {
        gold = extractGoldAmount(line, /на (?:<b>)?(\d+)/);
        newline = makeColoredLine(line, "+" + gold, "green");
      }

      // Продажи и передачи с комиссией
      else if (/(?:Передан .+?за \d+ (?:Золото|золота)|Продан)/.test(line)) {
        gold = extractGoldWithCommission(
          line,
          /за (\d+) (?:золота|Золото)/,
          /комиссия:* (\d+)/
        );
        newline = makeColoredLine(line, "+" + gold, "green");
      }

      // Передача предмета с получением золота
      else if (/Передан предмет.*Получено: \d+ золота/.test(line)) {
        gold = extractGoldAmount(line, /Получено: (\d+) золота/);
        newline = makeColoredLine(line, "+" + gold, "green");
      }

      // Получение предмета и золота за ремонт
      else if (/Получен .+?на ремонт/.test(line)) {
        const cleanLine = cleanLineForParsing(line);
        const g = /ремонт: (\d+)/.exec(cleanLine)?.[1];
        const p = /(\d+)%/.exec(cleanLine)?.[1];
        if (g && p) {
          gold = Math.ceil(g / p) * (p - 100);
          newline = makeColoredLine(
            line,
            gold >= 0 ? "+" + gold : gold,
            gold >= 0 ? "green" : "red"
          );
        }
      }

      // Оплата за ремонт
      else if (/Оплачено за/.test(line)) {
        gold = extractGoldWithCommission(
          line,
          /ремонт: (\d+)/,
          /комиссия: (\d+)/,
          true
        );
        newline = makeColoredLine(line, gold, "red");
      }

      // Аренда артефакта
      else if (/Арендован/.test(line)) {
        gold = extractGoldWithCommission(
          line,
          /Стоимость: (\d+)/,
          /комиссия: (\d+)/,
          true
        );
        newline = makeColoredLine(line, gold, "red");
      }

      // Получение золота, заработок, обмен бриллиантов
      else if (
        /Получено \d+ (?:Золото|золота)\s+от|Взято|Заработано|бриллиант[ао]в? обменяно на \d+ золота/.test(
          line
        )
      ) {
        gold = extractGoldAmount(line, /(\d+) (?:Золото|золота)/);
        newline = makeColoredLine(line, "+" + gold, "green");
      }

      // Взят в ремонт
      else if (/Взят в ремонт/.test(line)) {
        gold = extractGoldAmount(line, /Заработано: (\d+)/);
        newline = makeColoredLine(line, "+" + gold, "green");
      }

      // Передача денег в клан
      else if (/Передано \d+ золота на счет клана/.test(line)) {
        gold = extractGoldAmount(line, /(\d+) золота/, true);
        newline = makeColoredLine(line, gold, "red");
      }

      // Передача золота игроку
      else if (/Передано \d+ (?:Золото|золота)\s+для/.test(line)) {
        gold = extractGoldWithCommission(
          line,
          /(\d+) (?:Золото|золота)/,
          /комиссия (\d+)/,
          true
        );
        newline = makeColoredLine(line, gold, "red");
      }

      // Покупки
      else if (
        /Получен (?:элемент|предмет).+?за \d+ (?:золота|Золото)|Куплен|Куплена? \d+ часть артефакта/.test(
          line
        )
      ) {
        gold = extractGoldAmount(line, /за (\d+) (?:золота|Золото)/, true);
        newline = makeColoredLine(line, gold, "red");
      }

      // Продажи частей артефакта
      else if (/Продано \d+ \(\d+ -> \d+\) частей артефакта/.test(line)) {
        gold = extractGoldWithCommission(
          line,
          /за (\d+) золота/,
          /комиссия: (\d+)/
        );
        newline = makeColoredLine(line, "+" + gold, "green");
      }

      // Продажи элементов
      else if (/Продан "[^"]+"(?: \d+ шт\.)? за \d+ золота/.test(line)) {
        gold = extractGoldWithCommission(
          line,
          /за (\d+) золота/,
          /комиссия: (\d+)/
        );
        newline = makeColoredLine(line, "+" + gold, "green");
      }

      // Возвраты с возвратом золота
      else if (/Неиспользовано|^Вернул/.test(line) && !/c ремонта/.test(line)) {
        gold = extractGoldAmount(line, /(?:золота|Возврат золота): (\d+)/);
        newline = makeColoredLine(line, "+" + gold, "green");
      }

      // Операции без влияния на баланс
      else if (
        /Забран предмет|Получен предмет(?!.+за \d+)|Передан предмет для|Возвращен предмет|Возвращено автоматически|Вернул c ремонта|Игрок (?:освобожден|помещен|разблокирован|заблокирован)/.test(
          line
        )
      ) {
        gold = 0;
        newline = line;
      }

      // Штрафы игрока
      else if (/Игрок оштрафован/.test(line)) {
        gold = extractGoldAmount(line, /на (\d+) золота/, true);
        newline = makeColoredLine(line, gold, "red");
      }
      // Остальные покупки и траты
      else if (/Оплачено \d+|Куплен|Внесено/.test(line)) {
        gold = extractGoldAmount(line, /(\d+) золота/, true);
        newline = makeColoredLine(line, gold, "red");
      }
    } catch (error) {}

    return { processedLine: newline, goldAmount: gold };
  }

  // Page Management Functions
  function getTotalPages(playerId, callback) {
    let callbackCalled = false;
    const safeCallback = (pages) => {
      if (!callbackCalled) {
        callbackCalled = true;
        callback(pages);
      }
    };

    const xhr = createXMLHttpRequest();
    try {
      xhr.open(
        "GET",
        `${url}pl_transfers.php?id=${playerId}&page=999999`,
        true
      );
      setRequestHeaders(xhr);

      xhr.onreadystatechange = function () {
        if (xhr.readyState === 4) {
          if (xhr.status === 200) {
            const responseDiv = document.createElement("div");
            responseDiv.innerHTML = xhr.responseText;

            const pagination = responseDiv.querySelector(".hwm_pagination");
            if (pagination) {
              const activeLink = pagination.querySelector("a.active");
              if (activeLink) {
                const totalPages = parseInt(activeLink.textContent.trim());
                safeCallback(totalPages);
                return;
              }

              const allLinks = pagination.querySelectorAll('a[href*="page="]');
              let maxPageNumber = 0;

              allLinks.forEach((link) => {
                const pageMatch = link.href.match(/page=(\d+)/);
                if (pageMatch) {
                  const pageNum = parseInt(pageMatch[1]);
                  maxPageNumber = Math.max(maxPageNumber, pageNum);
                }

                const linkText = parseInt(link.textContent.trim());
                if (!isNaN(linkText)) {
                  maxPageNumber = Math.max(maxPageNumber, linkText);
                }
              });

              const totalPages = maxPageNumber + 1;
              safeCallback(totalPages);
            } else {
              safeCallback(1);
            }
          } else {
            safeCallback(1);
          }
        }
      };

      xhr.send(null);
    } catch (error) {
      safeCallback(1);
    }
  }

  // Core Functions
  function makeRequest(requestUrl, callback, callbackData, firstPage) {
    const xhr = createXMLHttpRequest();
    try {
      xhr.open("GET", requestUrl, true);
      setRequestHeaders(xhr);
      xhr.onreadystatechange = function () {
        callback(xhr, callbackData, firstPage);
      };
      xhr.send(null);
    } catch (error) {
      alert("HWM_TransferSearch: " + error);
    }
  }

  function startSearch(playerId, rootElement, searchType) {
    createStopButton();

    const stopButton = $(ELEMENT_IDS.STOP);
    if (stopButton && stopButton.value === "1") {
      return;
    }

    if (stopButton) {
      stopButton.value = "0";
    }

    searchBalance = 0;

    const pageFrom = Math.max(
      1,
      Math.floor(getNumberFieldValue(INPUT_IDS.PAGE_FROM, 1))
    );
    let pageTo = getNumberFieldValue(INPUT_IDS.PAGE_TO, 0);

    hideSearchInterface();

    if (pageTo === 0) {
      displaySearchStatus(
        searchType,
        getSearchString(searchType),
        "?",
        pageFrom
      );
      const statusContainer = $(ELEMENT_IDS.SEARCH_STATUS);
      statusContainer.innerHTML +=
        "<br><i>Определяем общее количество страниц...</i>";

      getTotalPages(playerId, function (totalPages) {
        pageTo = totalPages;
        continueSearch(playerId, rootElement, searchType, pageFrom, pageTo);
      });
    } else {
      continueSearch(playerId, rootElement, searchType, pageFrom, pageTo);
    }
  }

  function getSearchString(searchType) {
    const inputMap = {
      [SEARCH_TYPES.NICK]: INPUT_IDS.NICK,
      [SEARCH_TYPES.FINE]: ELEMENT_IDS.CHECKBOX_LABEL,
      [SEARCH_TYPES.ART]: INPUT_IDS.ART,
      [SEARCH_TYPES.ANY]: INPUT_IDS.ALL,
    };

    const inputId = inputMap[searchType];
    if (!inputId) return "";

    const element = $(inputId);
    if (!element) {
      return "";
    }

    if (searchType === SEARCH_TYPES.FINE) {
      return element.checked ? "true" : "false";
    }

    return element.value.trim();
  }

  function continueSearch(playerId, rootElement, searchType, pageFrom, pageTo) {
    if (pageFrom > pageTo) {
      alert("Начальная страница не может быть больше конечной!");
      showSearchInterface();
      return;
    }

    performSearch(playerId, searchType, rootElement, pageTo, pageFrom);
  }

  function performSearch(
    playerId,
    searchType,
    rootElement,
    totalPages,
    firstPage
  ) {
    let regex, searchString;

    try {
      ({ regex, searchString } = createSearchRegex(searchType));
    } catch (error) {
      alert(error.message);
      showSearchInterface();
      return;
    }

    const playerNick = getPlayerNick(rootElement);

    clearAndSetupResults(rootElement, playerNick, playerId);
    searchBalance = 0;

    const matchesElement = $("matches");
    if (matchesElement) {
      matchesElement.innerHTML = "0";
    }

    displaySearchStatus(searchType, searchString, totalPages, firstPage);

    executePageSearch(
      firstPage,
      playerId,
      regex,
      totalPages,
      rootElement,
      searchType,
      searchString,
      firstPage,
      null
    );
  }

  function createSearchRegex(searchType) {
    const searchString = getSearchString(searchType);

    if (
      !searchString &&
      searchType !== SEARCH_TYPES.FINE &&
      searchString !== "false"
    ) {
      const errorMessages = {
        [SEARCH_TYPES.NICK]: "Введите ник для поиска",
        [SEARCH_TYPES.ART]: "Введите название артефакта для поиска",
        [SEARCH_TYPES.ANY]: "Введите текст для поиска",
      };
      throw new Error(
        errorMessages[searchType] || `Unknown search type: ${searchType}`
      );
    }

    const regexPatterns = {
      [SEARCH_TYPES.NICK]: `${DATE_TIME_PATTERN}: .*<b>${escapeRegexString(
        searchString
      )}</b></a>`,
      [SEARCH_TYPES.FINE]: `${DATE_TIME_PATTERN}: <b>Игрок${
        searchString === "true"
          ? " (?:оштрафован|освобожден|помещен|разблокирован|заблокирован)"
          : " оштрафован"
      }`,
      [SEARCH_TYPES.ART]: `${DATE_TIME_PATTERN}: .*предмет [\"'][^\"']*${escapeRegexString(
        searchString
      )}[^\"']*[\"']`,
      [SEARCH_TYPES.ANY]: `${DATE_TIME_PATTERN}: .*${escapeRegexString(
        searchString
      )}.*`,
    };

    const pattern = regexPatterns[searchType];
    if (!pattern) {
      throw new Error(`Unknown search type: ${searchType}`);
    }

    const flags = "i";
    const regex = new RegExp(pattern, flags);

    return { regex, searchString };
  }

  function executePageSearch(
    currentPage,
    playerId,
    regex,
    lastPage,
    rootElement,
    searchType,
    searchString,
    firstPage,
    currentPageDate
  ) {
    const stopButton = $(ELEMENT_IDS.STOP);
    if (stopButton && stopButton.value !== "1" && currentPage <= lastPage) {
      const searchArgs = {
        pg: currentPage,
        id: playerId,
        reg: regex,
        lastPg: lastPage,
        elem: rootElement,
        type: searchType,
        search_str: searchString,
        first_page: firstPage,
      };

      makeRequest(
        `${url}pl_transfers.php?id=${playerId}&page=${currentPage - 1}`,
        processPageResults,
        searchArgs
      );
    } else {
      displaySearchComplete(
        searchType,
        searchString,
        firstPage,
        currentPage - 1,
        currentPageDate
      );
    }
  }

  function processPageResults(xhr, searchArgs) {
    if (xhr.readyState === 4 && xhr.status === 200) {
      hiddenDiv.innerHTML = xhr.responseText;
      const transferContainer = hiddenDiv.getElementsByClassName(
        SELECTORS.CONTAINER_HEADER
      )[0].nextElementSibling;
      const transferElement = transferContainer.getElementsByClassName(
        SELECTORS.GLOBAL_HOVER
      )[0];

      if (!transferElement) return;

      const transferText = transferElement.innerHTML;
      const transfers = transferText.split("<br>");
      let firstTimeOnPage = null;
      const timeRegex = /&nbsp;&nbsp;(\d\d-\d\d-\d\d \d\d:\d\d):/;

      for (let i = 0; i < transfers.length; i++) {
        if (!firstTimeOnPage) {
          const timeMatch = timeRegex.exec(transfers[i]);
          if (timeMatch) firstTimeOnPage = timeMatch[1];
        }

        const originalText = extractOriginalText(transfers[i]);

        if (searchArgs.reg.test(originalText)) {
          const matchesElement = $("matches");
          if (matchesElement) {
            matchesElement.innerHTML = Number(matchesElement.innerHTML) + 1;
          }

          const result = processTransferBalance(transfers[i]);
          const displayLine = result.processedLine;
          const goldAmount = result.goldAmount;

          searchBalance += goldAmount;
          searchArgs.elem.innerHTML += displayLine;
          searchArgs.elem.appendChild(document.createElement("br"));
        }
      }

      if (firstTimeOnPage) {
        const currDateElement = $("curr_date");
        if (currDateElement) {
          currDateElement.innerHTML = firstTimeOnPage;
        }
      }
      updateSearchProgress(searchArgs.lastPg, searchArgs.first_page);
      updateSearchBalance();

      executePageSearch(
        searchArgs.pg + 1,
        searchArgs.id,
        searchArgs.reg,
        searchArgs.lastPg,
        searchArgs.elem,
        searchArgs.type,
        searchArgs.search_str,
        searchArgs.first_page,
        firstTimeOnPage
      );
    }
  }

  // UI Helper Functions
  function formatSearchTypeDisplay(searchType, searchString) {
    const typeFormatters = {
      [SEARCH_TYPES.NICK]: () => {
        return `по нику <a href="pl_info.php?nick=${searchString}" style="text-decoration: none;"><b>${searchString}</b></a>`;
      },
      [SEARCH_TYPES.FINE]: () =>
        searchString === "true" ? "штрафы, блок, тюрьма" : "штрафы",
      [SEARCH_TYPES.ART]: () => `по артефакту "${searchString}"`,
      [SEARCH_TYPES.ANY]: () => `по подстроке "${searchString}"`,
    };

    return typeFormatters[searchType]
      ? typeFormatters[searchType]()
      : searchType;
  }

  function displaySearchStatus(
    searchType,
    searchString,
    totalPages,
    firstPage
  ) {
    const formattedType = formatSearchTypeDisplay(searchType, searchString);

    const oldStatus = $(ELEMENT_IDS.SEARCH_STATUS);
    if (oldStatus) {
      oldStatus.remove();
    }

    const statusContainer = document.createElement("div");
    statusContainer.id = ELEMENT_IDS.SEARCH_STATUS;
    statusContainer.style.textAlign = "center";

    statusContainer.innerHTML = `
      Идёт поиск ${formattedType}... 
      (<a href="javascript: void(0);" id="cancel" onclick="stopSearch();">стоп</a>)
      <br />
      Просмотрено <text id="viewed">0</text> страничек из ${
        totalPages - firstPage + 1
      } 
      (с ${firstPage} по ${totalPages}): <text id="percent">0</text>%
      <br />
      Дата последней операции на текущей странице: <text id="curr_date"></text>
      <br />
      Найдено <text id="matches">0</text> записей. Баланс: <text id="current_balance">0</text>
    `;

    rootElement.appendChild(statusContainer);
    rootElement.appendChild(document.createElement("br"));
  }

  function displaySearchComplete(
    searchType,
    searchString,
    firstPage,
    lastPage,
    currentPageDate
  ) {
    const matches = $("matches").innerHTML;
    const formattedType = formatSearchTypeDisplay(searchType, searchString);
    const dateInfo = currentPageDate
      ? `<br/>Дата последней операции на странице ${lastPage}: ${currentPageDate}`
      : "";

    const formattedBalance = searchBalance.toLocaleString("en-US");
    const balanceColor = searchBalance < 0 ? "red" : "green";
    const balanceSign = searchBalance > 0 ? "+" : "";

    $(ELEMENT_IDS.SEARCH_STATUS).innerHTML = `
      Поиск ${formattedType} закончен!<br>
      Найдено ${matches} записей на страницах ${firstPage}-${lastPage}:${dateInfo}
      <br><br><b style='padding:20px'>Баланс найденных операций: 
      <span style='color:${balanceColor}'>${balanceSign}${formattedBalance}</span></b>
    `;
  }

  function updateSearchProgress(totalPages, firstPage) {
    const viewedElement = $("viewed");
    const percentElement = $("percent");

    if (viewedElement && percentElement) {
      viewedElement.innerHTML = Number(viewedElement.innerHTML) + 1;
      percentElement.innerHTML = Math.round(
        (viewedElement.innerHTML * 100) / (totalPages - firstPage + 1)
      );
    }
  }

  function updateSearchBalance() {
    const balanceElement = $("current_balance");
    if (balanceElement) {
      const formattedBalance = searchBalance.toLocaleString("en-US");
      const balanceColor = searchBalance < 0 ? "red" : "green";
      const balanceSign = searchBalance > 0 ? "+" : "";
      balanceElement.innerHTML = `<span style='color:${balanceColor}'>${balanceSign}${formattedBalance}</span>`;
    }
  }

  // UI Creation Functions
  function createSearchInterface(rootElement) {
    insertAfter(
      rootElement.getElementsByTagName("center")[0].firstChild,
      document.createElement("br")
    );

    const searchToggle = document.createElement("text");
    searchToggle.id = ELEMENT_IDS.SEARCH_STATUS;
    searchToggle.innerHTML = `&nbsp;(<a id="${ELEMENT_IDS.SHOW_BLOCK}" href="javascript: void(0);">Поиск по протоколу</a>)`;
    rootElement.getElementsByTagName("center")[0].appendChild(searchToggle);

    const searchContainer = createSearchForm();
    rootElement.getElementsByTagName("center")[0].appendChild(searchContainer);
  }

  function createSearchForm() {
    const container = document.createElement("div");
    container.id = ELEMENT_IDS.SEARCH_DIV;
    container.style.display = "none";

    container.innerHTML = `
      <table>
        <tr>
          <td>Поиск по нику:</td>
          <td><input type="text" id="${INPUT_IDS.NICK}" form="form_nick" /></td>
          <td>
            <form action="" style="padding:0;margin:0;border:0;" id="form_nick" onSubmit="return false;">
              <input type="submit" id="TSearchByNick" value="Поиск" />
            </form>
          </td>
        </tr>
        <tr>
          <td>Поиск штрафов:</td>
          <td title="Блок/Штраф/Тюрьма">
            <input type="checkbox" id="${ELEMENT_IDS.CHECKBOX_LABEL}" /> <label for="${ELEMENT_IDS.CHECKBOX_LABEL}">Блок/Штраф/Тюрьма</label>
          </td>
          <td><input type="submit" id="TSearchByFine" value="Поиск" /></td>
        </tr>
        <tr>
          <td>Поиск по артефакту:</td>
          <td><input type="text" id="${INPUT_IDS.ART}" form="form_art" /></td>
          <td>
            <form action="" style="padding:0;margin:0;border:0;" id="form_art" onSubmit="return false;">
              <input type="submit" id="TSearchByArt" value="Поиск" />
            </form>
          </td>
        </tr>
        <tr>
          <td>Общий Поиск:</td>
          <td><input type="text" id="${INPUT_IDS.ALL}" form="form_any" /></td>
          <td>
            <form action="" style="padding:0;margin:0;border:0;" id="form_any" onSubmit="return false;">
              <input type="submit" id="TSearchAny" value="Поиск" />
            </form>
          </td>
        </tr>

        <tr>
          <td>с страницы</td>
          <td title="Начать поиск с указанной страницы">
            <input type="text" id="${INPUT_IDS.PAGE_FROM}" value="1"/>
          </td>
          <td>&nbsp;</td>
        </tr>
        <tr>
          <td>по страницу</td>
          <td title="Закончить поиск на указанной странице (0 = до конца)">
            <input type="text" id="${INPUT_IDS.PAGE_TO}" value="0" placeholder="0 = до конца"/>
          </td>
          <td>
            <button type="button" id="TSearch_get_total_pages" title="Определить общее количество страниц">
              Узнать всего
            </button>
          </td>
        </tr>
      </table>
    `;

    return container;
  }

  function setupEventListeners(rootElement) {
    if (rootElement._listenersAdded) {
      return;
    }

    const eventHandlers = {
      [ELEMENT_IDS.SHOW_BLOCK]: toggleSearchInterface,
      TSearchByNick: () =>
        startSearch(playerId, rootElement, SEARCH_TYPES.NICK),
      TSearchByFine: () =>
        startSearch(playerId, rootElement, SEARCH_TYPES.FINE),
      TSearchByArt: () => startSearch(playerId, rootElement, SEARCH_TYPES.ART),
      TSearchAny: () => startSearch(playerId, rootElement, SEARCH_TYPES.ANY),

      TSearch_get_total_pages: () => handleGetTotalPages(),
    };

    Object.entries(eventHandlers).forEach(([elementId, handler]) => {
      addClickHandler(elementId, handler);
    });

    rootElement._listenersAdded = true;
  }

  function handleGetTotalPages() {
    const button = $("TSearch_get_total_pages");
    const pageToField = $(INPUT_IDS.PAGE_TO);

    if (button.disabled) return;

    if (handleGetTotalPages.isRunning) {
      return;
    }

    handleGetTotalPages.isRunning = true;
    button.textContent = "Загрузка...";
    button.disabled = true;

    getTotalPages(playerId, function (totalPages) {
      pageToField.value = totalPages;
      button.textContent = "Узнать всего";
      button.disabled = false;
      handleGetTotalPages.isRunning = false;
    });
  }

  // Utility Functions
  function findRootElement() {
    return document.getElementsByClassName(SELECTORS.CONTAINER_HEADER)[0]
      .nextElementSibling;
  }

  function getPlayerNick(rootElement) {
    return rootElement.previousSibling.getElementsByTagName("a")[0].innerHTML;
  }

  function clearAndSetupResults(rootElement, playerNick, playerId) {
    const headerElement = rootElement.getElementsByClassName(
      SELECTORS.GLOBAL_HOVER
    )[0];

    headerElement.innerHTML = "";

    const titleContainer = document.createElement("div");
    titleContainer.style.textAlign = "center";
    titleContainer.innerHTML = "Поиск по протоколу передач игрока ";

    const playerLink = document.createElement("a");
    playerLink.href = `pl_info.php?id=${playerId}`;
    playerLink.style.textDecoration = "none";
    playerLink.innerHTML = playerNick;
    titleContainer.appendChild(playerLink);

    headerElement.appendChild(titleContainer);
    headerElement.appendChild(document.createElement("br"));
  }

  function createStopButton() {
    let stopButton = $(ELEMENT_IDS.STOP);

    if (!stopButton) {
      stopButton = document.createElement("input");
      stopButton.type = "hidden";
      stopButton.value = "0";
      stopButton.id = ELEMENT_IDS.STOP;
      document.getElementsByTagName("body")[0].appendChild(stopButton);
    }

    return stopButton;
  }

  function hideSearchInterface() {
    $(ELEMENT_IDS.SEARCH_DIV).style.display = "none";
    $(ELEMENT_IDS.SEARCH_STATUS).style.display = "none";
  }

  function showSearchInterface() {
    $(ELEMENT_IDS.SEARCH_DIV).style.display = "block";
    $(ELEMENT_IDS.SEARCH_STATUS).style.display = "block";
  }

  function createHiddenDiv() {
    const div = document.createElement("div");
    div.style.display = "none";
    div.id = "hwm_transfer_search_hidden";
    document.body.appendChild(div);
    return div;
  }

  function escapeRegexString(str) {
    return str
      .replace(/\\/g, "\\\\")
      .replace(/\[/g, "\\[")
      .replace(/\]/g, "\\]")
      .replace(/\(/g, "\\(")
      .replace(/\)/g, "\\)")
      .replace(/\./g, "\\.")
      .replace(/\+/g, "\\+")
      .replace(/\*/g, "\\*")
      .replace(/\?/g, "\\?")
      .replace(/\$/g, "\\$")
      .replace(/\|/g, "\\|");
  }

  function toggleSearchInterface() {
    const searchDiv = $(ELEMENT_IDS.SEARCH_DIV);
    searchDiv.style.display =
      searchDiv.style.display === "none" ? "block" : "none";
  }

  function insertAfter(referenceNode, newNode) {
    referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
  }

  function addClickHandler(elementId, handler) {
    const element = $(elementId);
    if (element && handler) {
      addEventHandler(element, "click", handler);
    }
  }

  function addEventHandler(element, eventType, handler) {
    if (element) {
      if (element.addEventListener) {
        element.addEventListener(eventType, handler, false);
      } else if (element.attachEvent) {
        element.attachEvent("on" + eventType, handler);
      } else {
        element["on" + eventType] = handler;
      }
    }
  }

  function getPlayerId() {
    const match = location.href.match(/\?(?:.*=.*&)*id=([0-9]*)(?:&.*=.*)*/);
    return match[1];
  }

  function $(elementId) {
    return document.getElementById(elementId);
  }

  function getNumberFieldValue(fieldId, defaultValue) {
    const element = $(fieldId);
    return element ? getValidNumber(element.value) : defaultValue || 0;
  }

  function getValidNumber(value) {
    const num = Number(value);
    const validNum = isNaN(num) ? 0 : num;
    return validNum < 0 ? 0 : validNum;
  }

  function createXMLHttpRequest() {
    if (window.XMLHttpRequest) {
      return new XMLHttpRequest();
    } else if (window.ActiveXObject) {
      return new ActiveXObject("Microsoft.XMLHTTP");
    } else {
      alert("Can't create XMLHttpRequest!");
      return null;
    }
  }

  function setRequestHeaders(xhr) {
    xhr.setRequestHeader("Content-type", "text/html; charset=windows-1251");
    if (xhr.overrideMimeType) {
      xhr.overrideMimeType("text/html; charset=windows-1251");
    }
  }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址