Rustmagic data analyzer

Scrapes all pages of game history, analyzes the data, displays stats with graphs, and allows CSV export.

// ==UserScript==
// @name         Rustmagic data analyzer
// @namespace    http://tampermonkey.net/
// @version      1.4
// @description  Scrapes all pages of game history, analyzes the data, displays stats with graphs, and allows CSV export.
// @author       Puskevit
// @match        *://rustmagic.com/*
// @grant        GM_addStyle
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.min.js
// @license MIT 
// ==/UserScript==

(function() {
    'use strict';

    const GAME_NAME_MAP = {
        'Battle': 'Case Battles',
        'Opening': 'Case Opening',
    };

    let allBetsData = [];
    let chartObjects = {};

    function showInitialPrompt() {
        if (document.getElementById('scraper-prompt')) return;
        const prompt = document.createElement('div');
        prompt.id = 'scraper-prompt';
        prompt.innerHTML = 'Click Your Avatar On Top Right & Go To "Games History" to Open The Scraper GUI';
        document.body.appendChild(prompt);
    }

    function createGUI() {
        if (document.getElementById('scraper-panel')) return;

        const panel = document.createElement('div');
        panel.id = 'scraper-panel';
        panel.innerHTML = `
            <div class="panel-header">
                <h2>History Analyzer</h2>
                <button id="close-panel-btn">✖</button>
            </div>
            <div class="panel-content">
                <p>Ensure the "All" game type is selected, then click start.</p>
                <div class="button-container">
                    <button id="start-scrape-btn" class="action-btn">📊 Start Scraping</button>
                    <button id="download-csv-btn" class="action-btn" style="display:none;">📄 Download CSV</button>
                </div>
                <div id="scraper-status"></div>
                <div id="overall-stats" class="stats-container" style="display:none;"></div>
                <div id="charts-container"></div>
            </div>
        `;
        document.body.appendChild(panel);

        document.getElementById('start-scrape-btn').addEventListener('click', startScraping);
        document.getElementById('download-csv-btn').addEventListener('click', downloadCSV);
        document.getElementById('close-panel-btn').addEventListener('click', () => panel.style.display = 'none');
    }

    function addStyling() {
        GM_addStyle(`
            #scraper-prompt {
                position: fixed;
                top: 20px;
                right: 20px;
                padding: 15px 20px;
                background-color: #1a1a2e;
                color: #e0e0e0;
                border: 1px solid #5a5a9a;
                border-radius: 10px;
                box-shadow: 0 5px 20px rgba(0,0,0,0.5);
                z-index: 9998;
                font-family: Arial, sans-serif;
                font-size: 15px;
                font-weight: bold;
            }
            #scraper-panel {
                position: fixed;
                top: 20px;
                right: 20px;
                width: 450px;
                max-height: 90vh;
                background-color: #1a1a2e;
                color: #e0e0e0;
                border: 1px solid #4a4a7a;
                border-radius: 10px;
                box-shadow: 0 5px 20px rgba(0,0,0,0.5);
                z-index: 9999;
                font-family: Arial, sans-serif;
                display: flex;
                flex-direction: column;
            }
            .panel-header {
                padding: 10px 15px;
                background-color: #2a2a4a;
                border-bottom: 1px solid #4a4a7a;
                border-radius: 10px 10px 0 0;
                display: flex;
                justify-content: space-between;
                align-items: center;
            }
            .panel-header h2 { margin: 0; font-size: 18px; color: #fff; }
            #close-panel-btn { background: none; border: none; color: #9793BA; font-size: 20px; cursor: pointer; }
            .panel-content { padding: 15px; overflow-y: auto; }
            .panel-content p { font-size: 14px; line-height: 1.5; margin-bottom: 15px; }
            .button-container { display: flex; gap: 10px; margin-bottom: 15px; }
            .action-btn {
                flex-grow: 1; padding: 10px 15px; border: none; border-radius: 5px;
                background-color: #5a5a9a; color: white; font-size: 14px; font-weight: bold;
                cursor: pointer; transition: background-color 0.3s;
            }
            .action-btn:hover { background-color: #7a7ac2; }
            .action-btn:disabled { background-color: #444; cursor: not-allowed; }
            #scraper-status { margin-top: 10px; font-style: italic; color: #9793BA; min-height: 20px; }
            .stats-container { background-color: #2a2a4a; padding: 15px; border-radius: 5px; margin-top: 10px; }
            .stats-container h3 { margin-top: 0; border-bottom: 1px solid #4a4a7a; padding-bottom: 5px; color: #fff; }
            .stats-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; font-size: 14px; }
            .stat-item { display: flex; justify-content: space-between; }
            .stat-label { color: #ccc; }
            .stat-value.profit { color: #a5ec60; }
            .stat-value.loss { color: #ff6b6b; }
            #charts-container { margin-top: 20px; }
            .chart-wrapper { margin-bottom: 20px; background-color: #2a2a4a; padding: 10px; border-radius: 5px; }
        `);
    }

    async function startScraping() {
        document.getElementById('start-scrape-btn').disabled = true;
        document.getElementById('start-scrape-btn').textContent = "Scraping...";
        const statusEl = document.getElementById('scraper-status');
        const overallStatsEl = document.getElementById('overall-stats');
        const chartsContainer = document.getElementById('charts-container');

        allBetsData = [];
        overallStatsEl.style.display = 'none';
        chartsContainer.innerHTML = '';
        Object.values(chartObjects).forEach(chart => chart.destroy());
        chartObjects = {};
        let pageCount = 1;

        while (true) {
            statusEl.textContent = `Scraping page ${pageCount}...`;
            await new Promise(r => setTimeout(r, 500));

            const tableBeforeScrape = getTableContent();
            scrapeCurrentPage();

            const nextButton = Array.from(document.querySelectorAll('div.sc-aXZVg.dKpxMo.sc-bXCLTC.cpAwwW'))
                .find(el => el.querySelector('svg') && !el.style.cssText.includes('rotate'));

            if (!nextButton || nextButton.disabled || !nextButton.offsetParent) {
                statusEl.textContent = `Scraping finished! Reached the last page.`;
                break;
            }

            nextButton.click();
            pageCount++;

            try {
                await waitForTableLoad(tableBeforeScrape, 10000);
            } catch (error) {
                statusEl.textContent = `Scraping stopped. Table didn't update after click.`;
                console.error(error.message);
                break;
            }
        }

        statusEl.textContent = `Scraping complete! Processed ${pageCount} pages and ${allBetsData.length} bets.`;
        analyzeAndDisplayData();
        document.getElementById('start-scrape-btn').disabled = false;
        document.getElementById('start-scrape-btn').textContent = "📊 Re-Scrape";
        document.getElementById('download-csv-btn').style.display = 'inline-block';
    }

    function scrapeCurrentPage() {
        const rows = document.querySelectorAll('.sc-kOHTFB.cXnurL');
        rows.forEach(row => {
            const cells = row.querySelectorAll(':scope > div');
            if (cells.length < 5) return;

            const id = cells[0]?.textContent.trim();
            let game = cells[1]?.textContent.trim();
            if (GAME_NAME_MAP[game]) game = GAME_NAME_MAP[game];

            const wagerText = cells[2]?.textContent.trim();
            const payoutText = cells[3]?.textContent.trim();
            const time = cells[4]?.textContent.trim();
            const wager = parseFloat(wagerText.replace(/[^0-9.]/g, '')) || 0;

            const isWin = payoutText.includes('+') || cells[3].querySelector('div')?.style.color === 'rgb(165, 236, 96)';
            const payoutValue = parseFloat(payoutText.replace(/[^0-9.]/g, '')) || 0;
            const netProfit = isWin ? payoutValue : -wager;

            allBetsData.push({ id, game, wager, payout: isWin ? wager + payoutValue : 0, netProfit, time });
        });
    }

    function getTableContent() {
        const firstRow = document.querySelector('.sc-kOHTFB.cXnurL');
        return firstRow ? firstRow.textContent : '';
    }

    function waitForTableLoad(previousContent, timeout = 10000) {
        return new Promise((resolve, reject) => {
            const startTime = Date.now();
            const interval = setInterval(() => {
                const newContent = getTableContent();
                if (newContent && newContent !== previousContent) {
                    clearInterval(interval);
                    resolve();
                } else if (Date.now() - startTime > timeout) {
                    clearInterval(interval);
                    reject(new Error("Timeout: Table content did not change after click."));
                }
            }, 200);
        });
    }

    function analyzeAndDisplayData() {
        if (allBetsData.length === 0) return;

        const totalWagered = allBetsData.reduce((sum, bet) => sum + bet.wager, 0);
        const totalProfit = allBetsData.reduce((sum, bet) => sum + bet.netProfit, 0);
        const totalWon = allBetsData.filter(b => b.netProfit > 0).reduce((sum, b) => sum + b.payout, 0);
        const totalLost = allBetsData.filter(b => b.netProfit < 0).reduce((sum, b) => sum + b.wager, 0);

        const overallStatsEl = document.getElementById('overall-stats');
        overallStatsEl.innerHTML = `
            <h3>Overall Stats</h3>
            <div class="stats-grid">
                <div class="stat-item"><span class="stat-label">Total Wagered:</span> <span class="stat-value">${totalWagered.toFixed(2)}</span></div>
                <div class="stat-item"><span class="stat-label">Total Won:</span> <span class="stat-value profit">+${totalWon.toFixed(2)}</span></div>
                <div class="stat-item"><span class="stat-label">Total Lost:</span> <span class="stat-value loss">-${totalLost.toFixed(2)}</span></div>
                <div class="stat-item"><span class="stat-label">Total Profit:</span> <span class="stat-value ${totalProfit >= 0 ? 'profit' : 'loss'}">${totalProfit.toFixed(2)}</span></div>
            </div>
        `;
        overallStatsEl.style.display = 'block';

        const statsByGame = allBetsData.reduce((acc, bet) => {
            if (!acc[bet.game]) {
                acc[bet.game] = { wager: 0, profit: 0, wins: 0, losses: 0, count: 0 };
            }
            acc[bet.game].wager += bet.wager;
            acc[bet.game].profit += bet.netProfit;
            acc[bet.game].count++;
            if (bet.netProfit > 0) acc[bet.game].wins++;
            else acc[bet.game].losses++;
            return acc;
        }, {});

        const chartsContainer = document.getElementById('charts-container');
        for (const gameName in statsByGame) {
            const gameData = statsByGame[gameName];
            const avgBet = gameData.wager / gameData.count;
            const winRate = (gameData.wins / gameData.count) * 100;
            const totalWonForGame = gameData.wager + gameData.profit;

            const chartWrapper = document.createElement('div');
            chartWrapper.className = 'chart-wrapper';
            chartWrapper.innerHTML = `
                <h3>${gameName}</h3>
                <div class="stats-grid">
                     <div class="stat-item"><span class="stat-label">Profit:</span> <span class="stat-value ${gameData.profit >= 0 ? 'profit' : 'loss'}">${gameData.profit.toFixed(2)}</span></div>
                     <div class="stat-item"><span class="stat-label">Avg. Bet:</span> <span class="stat-value">${avgBet.toFixed(2)}</span></div>
                     <div class="stat-item"><span class="stat-label">Win Rate:</span> <span class="stat-value">${winRate.toFixed(1)}%</span></div>
                     <div class="stat-item"><span class="stat-label">W/L:</span> <span class="stat-value">${gameData.wins}/${gameData.losses}</span></div>
                </div>
                <canvas id="chart-${gameName.replace(/\s+/g, '-')}"></canvas>
            `;
            chartsContainer.appendChild(chartWrapper);

            const ctx = document.getElementById(`chart-${gameName.replace(/\s+/g, '-')}`).getContext('2d');
            chartObjects[gameName] = new Chart(ctx, {
                type: 'bar', data: {
                    labels: ['Total Wagered', 'Total Won'],
                    datasets: [{
                        label: 'Amount', data: [gameData.wager, totalWonForGame],
                        backgroundColor: ['rgba(255, 99, 132, 0.5)', 'rgba(75, 192, 192, 0.5)'],
                        borderColor: ['rgba(255, 99, 132, 1)', 'rgba(75, 192, 192, 1)'], borderWidth: 1
                    }]
                },
                options: {
                    scales: { y: { beginAtZero: true, ticks: { color: '#e0e0e0' }}, x: { ticks: { color: '#e0e0e0' }}},
                    plugins: { legend: { display: false }}
                }
            });
        }
    }

    function downloadCSV() {
        if (allBetsData.length === 0) {
            alert("No data to download!"); return;
        }
        const headers = "ID,Game,Wager,Payout,NetProfit,Time";
        const rows = allBetsData.map(bet => [bet.id, bet.game, bet.wager.toFixed(2), bet.payout.toFixed(2), bet.netProfit.toFixed(2), `"${bet.time}"`].join(','));
        const csvContent = "data:text/csv;charset=utf-8," + headers + "\n" + rows.join("\n");
        const encodedUri = encodeURI(csvContent);
        const link = document.createElement("a");
        link.setAttribute("href", encodedUri);
        link.setAttribute("download", "rustmagic_history_export.csv");
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
    }

    function waitForElement(selector, callback) {
        const interval = setInterval(() => {
            const element = document.querySelector(selector);
            if (element) {
                clearInterval(interval);
                callback(element);
            }
        }, 500);
    }

    addStyling();
    showInitialPrompt();

    waitForElement('.sc-kYxDKI.hzPxqP', () => {
        const prompt = document.getElementById('scraper-prompt');
        if (prompt) prompt.remove();
        createGUI();
    });

})();