NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==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);
})();