OP.GG Total & Average Game Time Calculator (Robust Selector)

Enhance your OP.GG experience with this powerful match history analyzer! Automatically calculates your total playtime, average game duration, and most played champions. Features include: 🕒 Total and average game time tracking, 🏆 Most played champion statistics, 📊 Average laning scores, and 🔄 Auto-scroll functionality to load your entire match history. Perfect for players who want to track their gaming habits and performance over time!

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         OP.GG Total & Average Game Time Calculator (Robust Selector)
// @namespace    http://tampermonkey.net/
// @version      0.8
// @description  Enhance your OP.GG experience with this powerful match history analyzer! Automatically calculates your total playtime, average game duration, and most played champions. Features include: 🕒 Total and average game time tracking, 🏆 Most played champion statistics, 📊 Average laning scores, and 🔄 Auto-scroll functionality to load your entire match history. Perfect for players who want to track their gaming habits and performance over time!
// @author       SimronJ
// @match        https://*.op.gg/lol/summoners*
// @grant        none
// @license      MIT
// ==/UserScript==
(function() {
    'use strict';
    // Parse time strings like "32m 21s" or "1h 12m 5s" into seconds
    function parseTimeToSeconds(timeString) {
        let h=0, m=0, s=0;
        const parts = timeString.match(/(?:(\d+)h)?\s*(?:(\d+)m)?\s*(?:(\d+)s)?/);
        if (parts) {
            h = parseInt(parts[1]||0);
            m = parseInt(parts[2]||0);
            s = parseInt(parts[3]||0);
        }
        return h*3600 + m*60 + s;
    }
    // Format seconds into "Xh Ym Zs"
    function formatTime(sec) {
        const h = Math.floor(sec/3600);
        const m = Math.floor((sec%3600)/60);
        const s = sec%60;
        let str = '';
        if (h>0) str += h+'h ';
        if (m>0 || h>0) str += m+'m ';
        str += s+'s';
        return str.trim();
    }
    // Go through each .box-border entry, find time, sum, and count games with time
    // Also count champion occurrences
    function calculateStats() {
        const entries = document.querySelectorAll('.box-border');
        let totalTimeInSeconds = 0;
        let gamesWithTimeCount = 0;
        const championCounts = {};
        const championLaningScores = {}; // Store laning scores per champion
        let totalLaningScore = 0;
        let gamesWithLaningScore = 0;

        entries.forEach(entry => {
            // Find the champion image and get its alt text
            const championImg = entry.querySelector('.flex.items-center.gap-1 a img');
            if (championImg && championImg.alt) {
                const championName = championImg.alt;
                championCounts[championName] = (championCounts[championName] || 0) + 1;
                
                // Initialize champion laning scores if not exists
                if (!championLaningScores[championName]) {
                    championLaningScores[championName] = { total: 0, count: 0 };
                }
            }

            // Find laning score
            const laningElement = entry.querySelector('.text-xs.text-gray-500');
            if (laningElement) {
                const laningText = laningElement.textContent;
                const laningMatch = laningText.match(/Laning (\d+):/);
                if (laningMatch) {
                    const laningScore = parseInt(laningMatch[1]);
                    totalLaningScore += laningScore;
                    gamesWithLaningScore++;
                    
                    // Add to champion-specific laning score
                    if (championImg && championImg.alt) {
                        const championName = championImg.alt;
                        championLaningScores[championName].total += laningScore;
                        championLaningScores[championName].count++;
                    }
                }
            }

            // Find the one span that looks like a game-timer
            const spans = entry.querySelectorAll('span');
            for (let sp of spans) {
                const txt = sp.textContent.trim();
                if (/^\d+h\s*\d+m\s*\d+s$/.test(txt) || /^\d+m\s*\d+s$/.test(txt)) {
                    totalTimeInSeconds += parseTimeToSeconds(txt);
                    gamesWithTimeCount++;
                    break;
                }
            }
        });

        // Find the most frequent champion
        let mostFrequentChampion = 'N/A';
        let maxCount = 0;
        for (const champion in championCounts) {
            if (championCounts[champion] > maxCount) {
                maxCount = championCounts[champion];
                mostFrequentChampion = champion;
            }
        }

        // Calculate average laning scores
        const averageLaningScore = gamesWithLaningScore > 0 ? Math.round(totalLaningScore / gamesWithLaningScore) : 0;
        const mostPlayedChampionLaningScore = mostFrequentChampion !== 'N/A' && championLaningScores[mostFrequentChampion] ?
            Math.round(championLaningScores[mostFrequentChampion].total / championLaningScores[mostFrequentChampion].count) : 0;

        return {
            totalSeconds: totalTimeInSeconds,
            gamesWithTimeCount: gamesWithTimeCount,
            mostFrequentChampion: mostFrequentChampion,
            mostFrequentChampionGames: maxCount,
            averageLaningScore: averageLaningScore,
            mostPlayedChampionLaningScore: mostPlayedChampionLaningScore
        };
    }
    // Create or update floating widget
    function updateDisplay() {
        const { totalSeconds, gamesWithTimeCount, mostFrequentChampion, mostFrequentChampionGames, averageLaningScore, mostPlayedChampionLaningScore } = calculateStats();
        const avgSeconds = gamesWithTimeCount > 0 ? Math.floor(totalSeconds / gamesWithTimeCount) : 0;
        const totalStr = formatTime(totalSeconds);
        const avgStr   = formatTime(avgSeconds);
        let el = document.getElementById('time-stats-display');
        if (!el) {
            el = document.createElement('div');
            el.id = 'time-stats-display';
            Object.assign(el.style, {
                position:       'fixed',
                top:            '50%',
                left:           '10px',
                transform:      'translateY(-50%)',
                backgroundColor:'rgba(0,0,0,0.7)',
                color:          '#fff',
                padding:        '10px',
                borderRadius:   '5px',
                zIndex:         '10000',
                fontFamily:     'sans-serif',
                whiteSpace:     'pre-line'
            });
            document.body.appendChild(el);
        }
        el.textContent =
            `Total Played Time: ${totalStr}\n` +
            `Average per Game:  ${avgStr} (${gamesWithTimeCount} games)\n` +
            `Most Played Champion: ${mostFrequentChampion} (${mostFrequentChampionGames} games | ${mostPlayedChampionLaningScore} Avg Lane)\n` +
            `Average Laning Score: ${averageLaningScore}`;

        // Add scroll button if it doesn't exist
        let scrollButton = document.getElementById('auto-scroll-button');
        if (!scrollButton) {
            scrollButton = document.createElement('button');
            scrollButton.id = 'auto-scroll-button';
            scrollButton.textContent = 'Auto Scroll';
            Object.assign(scrollButton.style, {
                display: 'block',
                marginTop: '10px',
                padding: '5px 10px',
                backgroundColor: '#4CAF50',
                color: 'white',
                border: 'none',
                borderRadius: '3px',
                cursor: 'pointer',
                width: '100%'
            });
            scrollButton.addEventListener('click', autoScroll);
            el.appendChild(scrollButton);
        }
    }

    // Function to handle auto-scrolling
    function autoScroll() {
        const button = document.getElementById('auto-scroll-button');
        button.textContent = 'Loading...';
        button.disabled = true;
        
        let lastHeight = document.body.scrollHeight;
        let noChangeCount = 0;
        
        const loadMore = () => {
            const showMoreButton = document.querySelector('button.text-13px.border-1.box-border.block.h-\\[40px\\].w-full.border.border-gray-250.bg-gray-0.px-0.py-\\[8px\\].text-center.text-gray-900.no-underline.md\\:rounded');
            
            if (showMoreButton) {
                showMoreButton.click();
                // Check if the page height changed after clicking
                setTimeout(() => {
                    const newHeight = document.body.scrollHeight;
                    if (newHeight === lastHeight) {
                        noChangeCount++;
                        if (noChangeCount >= 3) { // If height hasn't changed for 3 attempts
                            clearInterval(loadInterval);
                            button.textContent = 'Auto Scroll';
                            button.disabled = false;
                        }
                    } else {
                        noChangeCount = 0;
                        lastHeight = newHeight;
                    }
                }, 1000);
            } else {
                clearInterval(loadInterval);
                button.textContent = 'Auto Scroll';
                button.disabled = false;
            }
        };

        const loadInterval = setInterval(loadMore, 1500); // Try to load more every 1.5 seconds
    }

    // Watch for newly loaded games (infinite scroll)
    const observer = new MutationObserver(muts => {
        for (let m of muts) {
            if (m.addedNodes.length) {
                updateDisplay();
                break;
            }
        }
    });
    const container = document.querySelector('#content-container') || document.body;
    observer.observe(container, { childList: true, subtree: true });
    // Initial render
    updateDisplay();
})();