Xrem786 / Uniwersalny Licznik Akcji Moderatora dla Hyperreal

// ==UserScript==
// @name         Uniwersalny Licznik Akcji Moderatora dla Hyperreal
// @namespace    http://tampermonkey.net/
// @version      4.0
// @description  Zlicza szczegółowe akcje moderatorów dla wybranego miesiąca, generując czytelny raport w BBCode.
// @author       Merx
// @match        https://hyperreal.info/talk/mcp.php?i=mcp_logs*
// @grant        none
// @license     MIT
// ==/UserScript==

(function () {
  'use strict';

  // --- Konfiguracja ---
  const TOTAL_PAGES_TO_SCAN = 250; // Maksymalna liczba stron do przeskanowania wstecz
  const ENTRIES_PER_PAGE = 15;
  const REQUEST_DELAY_MS = 150; // Opóźnienie między żądaniami, aby nie obciążać serwera

  const polishMonths = {
    'stycznia': 0,
    'lutego': 1,
    'marca': 2,
    'kwietnia': 3,
    'maja': 4,
    'czerwca': 5,
    'lipca': 6,
    'sierpnia': 7,
    'września': 8,
    'października': 9,
    'listopada': 10,
    'grudnia': 11
  };

  /**
   * Parsuje datę z formatu "DD miesiąc RRRR" na obiekt Date.
   * @param {string} dateString - Data do sparsowania.
   * @returns {Date|null} - Obiekt Date lub null w przypadku błędu.
   */
  function parsePolishDate(dateString) {
    const parts = dateString.trim().split(' ');
    if (parts.length !== 3) return null;

    const day = parseInt(parts[0], 10);
    const month = polishMonths[parts[1].toLowerCase()];
    const year = parseInt(parts[2], 10);

    if (isNaN(day) || month === undefined || isNaN(year)) return null;

    return new Date(year, month, day);
  }

  /**
   * Główna funkcja uruchamiająca proces zliczania.
   */
  async function startProcessing() {
    const startButton = document.getElementById('mod-counter-start');
    const progressDiv = document.getElementById('mod-counter-progress');
    const resultDiv = document.getElementById('mod-counter-result');
    const monthSelect = document.getElementById('mod-counter-month-select');

    const selectedMonthIndex = parseInt(monthSelect.value, 10);
    const polishMonthsForTitle = [
      'Styczeń', 'Luty', 'Marzec', 'Kwiecień', 'Maj', 'Czerwiec',
      'Lipiec', 'Sierpień', 'Wrzesień', 'Październik', 'Listopad', 'Grudzień'
    ];
    const selectedMonthName = polishMonthsForTitle[selectedMonthIndex];

    startButton.disabled = true;
    monthSelect.disabled = true;
    startButton.textContent = 'Przetwarzanie...';
    progressDiv.style.display = 'block';
    resultDiv.innerHTML = '';

    const moderatorStats = {};
    let targetYear = null;

    const url = new URL(window.location.href);
    url.searchParams.delete('start');
    const baseUrl = url.toString();

    try {
      for (let page = 1; page <= TOTAL_PAGES_TO_SCAN; page++) {
        const startValue = (page - 1) * ENTRIES_PER_PAGE;
        const fetchUrl = `${baseUrl}&start=${startValue}`;

        progressDiv.textContent = `Przetwarzam stronę ${page} z ${TOTAL_PAGES_TO_SCAN}...`;

        const response = await fetch(fetchUrl);
        if (!response.ok) throw new Error(`Błąd HTTP! Status: ${response.status} dla strony ${page}`);
        const text = await response.text();

        const parser = new DOMParser();
        const doc = parser.parseFromString(text, 'text/html');

        const logEntries = doc.querySelectorAll('.card-body > form > .card.mb-3.pb-1 > .row.bg1');
        if (logEntries.length === 0) {
          progressDiv.textContent = page === 1 ?
            "Nie znaleziono żadnych wpisów w dzienniku. Sprawdź, czy selektory są wciąż aktualne." :
            'Zakończono skanowanie - dotarto do końca dziennika zdarzeń.';
          break;
        }

        let stopScan = false;
        for (const entry of logEntries) {
          const dateElement = entry.querySelector('.col-12.col-md-2.py-2.text-md-center:last-child');
          if (!dateElement) continue;

          const dateText = dateElement.textContent.replace('Czas:', '').trim();
          const entryDate = parsePolishDate(dateText);
          if (!entryDate) continue;

          const entryMonth = entryDate.getMonth();
          const entryYear = entryDate.getFullYear();

          if (entryMonth === selectedMonthIndex) {
            if (targetYear === null) {
              targetYear = entryYear;
            }

            if (entryYear === targetYear) {
              const modElement = entry.querySelector('a.username-coloured');
              const actionStrongElement = entry.querySelector('.col-12.col-md-6 strong');
              if (!modElement || !actionStrongElement) continue;

              const modName = modElement.textContent.trim();
              const fullActionText = actionStrongElement.textContent.trim();
              if (!moderatorStats[modName]) {
                moderatorStats[modName] = {};
              }
              const actionCategory = fullActionText.split('„')[0].trim();
              moderatorStats[modName][actionCategory] = (moderatorStats[modName][actionCategory] || 0) + 1;
            }
            else {
              stopScan = true;
              break;
            }
          }
          else if (targetYear !== null && (entryYear < targetYear || (entryYear === targetYear && entryMonth < selectedMonthIndex))) {
            stopScan = true;
            break;
          }
        }

        if (stopScan) {
          progressDiv.textContent = 'Zakończono skanowanie - dotarto do wpisów spoza wybranego miesiąca.';
          break;
        }

        await new Promise(resolve => setTimeout(resolve, REQUEST_DELAY_MS));
      }

      const polishMonthsLocative = [
        'styczniu', 'lutym', 'marcu', 'kwietniu', 'maju', 'czerwcu',
        'lipcu', 'sierpniu', 'wrześniu', 'październiku', 'listopadzie', 'grudniu'
      ];
      const selectedMonthLocative = polishMonthsLocative[selectedMonthIndex];

      if (Object.keys(moderatorStats).length === 0) {
        progressDiv.textContent = `Nie znaleziono żadnych akcji moderatorów w ${selectedMonthLocative}.`;
      }
      else {
        progressDiv.textContent += ' Gotowe! Poniżej znajduje się kod BBCode do skopiowania.';
        const bbcodeOutput = generateBBCodeTable(moderatorStats, selectedMonthName);
        displayResults(bbcodeOutput, resultDiv);
      }

    }
    catch (error) {
      console.error('Wystąpił błąd podczas przetwarzania:', error);
      progressDiv.textContent = `Wystąpił krytyczny błąd: ${error.message}. Sprawdź konsolę (F12) po więcej informacji.`;
      progressDiv.style.color = 'red';
    }
    finally {
      startButton.disabled = false;
      monthSelect.disabled = false;
      startButton.textContent = 'Uruchom ponownie';
    }
  }

  /**
   * Generuje nową, czytelną tabelę w formacie BBCode.
   * @param {object} stats - Obiekt ze statystykami moderatorów.
   * @param {string} monthName - Nazwa wybranego miesiąca do nagłówka.
   * @returns {string} - Tabela w formacie BBCode.
   */
  function generateBBCodeTable(stats, monthName) {
    const processedStats = Object.entries(stats).map(([name, actions]) => {
      const total = Object.values(actions).reduce((sum, count) => sum + count, 0);
      return {
        name,
        actions,
        total
      };
    });

    processedStats.sort((a, b) => b.total - a.total);

    let bbcode = '[table]\n';
    bbcode += `[tr][td][b]Moderator[/b][/td][td][b]Szczegółowa lista akcji (${monthName})[/b][/td][td][b]Łączna liczba akcji[/b][/td][/tr]\n`;

    processedStats.forEach(modData => {
      bbcode += `[tr][td][mention]${modData.name}[/mention][/td][td]`;
      bbcode += '[list]';

      const sortedActions = Object.entries(modData.actions).sort((a, b) => b[1] - a[1]);

      sortedActions.forEach(([action, count]) => {
        bbcode += `[*]${action}: [b]${count}[/b]\n`;
      });
      bbcode += '[/list][/td]';
      bbcode += `[td][b][size=150][color=red]${modData.total}[/color][/size][/b][/td][/tr]\n`;
    });

    bbcode += '[/table]';
    return bbcode;
  }

  /**
   * Wyświetla wyniki w dedykowanym polu textarea.
   * @param {string} bbcode - Wygenerowany kod BBCode.
   * @param {HTMLElement} resultContainer - Element, w którym ma zostać wyświetlony wynik.
   */
  function displayResults(bbcode, resultContainer) {
    const textarea = document.createElement('textarea');
    textarea.style.width = '95%';
    textarea.style.height = '400px';
    textarea.style.marginTop = '10px';
    textarea.readOnly = true;
    textarea.value = bbcode;
    resultContainer.appendChild(document.createTextNode('Kliknij w pole i skopiuj (Ctrl+C / Cmd+C):'));
    resultContainer.appendChild(document.createElement('br'));
    resultContainer.appendChild(textarea);
    textarea.focus();
    textarea.select();
  }

  /**
   * Tworzy i wstrzykuje interfejs użytkownika na stronę.
   */
  function createUI() {
    const targetElement = document.querySelector('h4.cp-page-title');
    if (!targetElement) return;

    const container = document.createElement('div');
    container.style.padding = '10px';
    container.style.border = '1px solid #ccc';
    container.style.backgroundColor = '#f9f9f9';
    container.style.margin = '15px 0';

    const label = document.createElement('label');
    label.textContent = 'Wybierz miesiąc: ';
    label.style.marginRight = '10px';

    const monthSelect = document.createElement('select');
    monthSelect.id = 'mod-counter-month-select';
    monthSelect.style.marginRight = '20px';
    monthSelect.style.padding = '5px';

    const polishMonthsDisplay = [
      'Styczeń', 'Luty', 'Marzec', 'Kwiecień', 'Maj', 'Czerwiec',
      'Lipiec', 'Sierpień', 'Wrzesień', 'Październik', 'Listopad', 'Grudzień'
    ];

    const lastMonth = (new Date().getMonth() - 1 + 12) % 12; // Domyślnie ustawia poprzedni miesiąc
    polishMonthsDisplay.forEach((month, index) => {
      const option = document.createElement('option');
      option.value = index;
      option.textContent = month;
      if (index === lastMonth) {
        option.selected = true;
      }
      monthSelect.appendChild(option);
    });

    const button = document.createElement('button');
    button.id = 'mod-counter-start';
    button.textContent = 'Oblicz akcje moderatorów';
    button.className = 'btn btn-primary';
    button.addEventListener('click', startProcessing);

    const progressDiv = document.createElement('div');
    progressDiv.id = 'mod-counter-progress';
    progressDiv.style.display = 'none';
    progressDiv.style.marginTop = '10px';
    progressDiv.style.fontWeight = 'bold';

    const resultDiv = document.createElement('div');
    resultDiv.id = 'mod-counter-result';

    container.appendChild(label);
    container.appendChild(monthSelect);
    container.appendChild(button);
    container.appendChild(progressDiv);
    container.appendChild(resultDiv);

    targetElement.insertAdjacentElement('afterend', container);
  }

  window.addEventListener('load', createUI);

})();