Steam Friend Ranking Based on Steam Levels

Fetches Steam levels from public profiles and ranks user on friends' friend lists

// ==UserScript==
// @name         Steam Friend Ranking Based on Steam Levels
// @author       Struki
// @namespace    http://tampermonkey.net/
// @version      0.5
// @description  Fetches Steam levels from public profiles and ranks user on friends' friend lists
// @match        *://steamcommunity.com/*/*/friends*
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    const yourSteamID = null;
    let yourSteamLevel = 0;
    const maxConcurrentRequests = 5; // Limit the number of concurrent requests

    // UI: Adding options to initiate the script
    function addOptionsUI() {
        const titleBar = document.querySelector('.profile_friends.title_bar');
        if (titleBar) {
            const controlContainer = document.createElement('div');
            controlContainer.className = 'ranking_controls';
            controlContainer.style.cssText = `
                margin: 10px 0;
                display: flex;
                gap: 10px;
                align-items: center;
            `;

            // Create select input for Steam ID options
            const selectInput = document.createElement('select');
            selectInput.innerHTML = `
                <option value="Level">Steam Level</option>
                <option value="me">My Steam Profile</option>
                <option value="viewing">Current Friend's Profile</option>
            `;
            controlContainer.appendChild(selectInput);

            // Input for Steam Level
            const LevelInput = document.createElement('input');
            LevelInput.type = 'number';
            LevelInput.placeholder = 'Enter Steam Level';
            controlContainer.appendChild(LevelInput);

            // Create a button to start the ranking process
            const startButton = document.createElement('button');
            startButton.textContent = 'Start Ranking';
            controlContainer.appendChild(startButton);

            // Event listener for the dropdown selection
            selectInput.addEventListener('change', () => {
                if (selectInput.value === 'Level') {
                    LevelInput.style.display = 'inline-block';
                } else {
                    LevelInput.style.display = 'none';
                }
            });

            // Event listener for the start button
            startButton.addEventListener('click', async () => {
                if (selectInput.value === 'Level' && LevelInput.value) {
                    // Get 'yourSteamLevel' from LevelInput.value, then addRank();
                    yourSteamLevel = parseInt(LevelInput.value);
                    if (!isNaN(yourSteamLevel)) {
                        console.log(`Your Steam Level: ${yourSteamLevel}`);
                        addRanking();
                    } else {
                        console.error('Failed to read Level.');
                    }
                } else if (selectInput.value === 'me') {
                    // Get steamid from your own account
                    const steamIDMatch = document.querySelector('a.user_avatar.playerAvatar');
                    if (steamIDMatch) {
                        const steamID = steamIDMatch.href.split('/').pop();
                        yourSteamID = steamID; // Store your Steam ID
                        yourSteamLevel = await getSteamLevel(yourSteamID); // Fetch your Steam level
                        if (yourSteamLevel) {
                            addRanking();
                        } else {
                            console.error('Failed to fetch your Steam profile or level.');
                        }
                    } else {
                        console.error('Failed to fetch your Steam ID.');
                    }
                } else if (selectInput.value === 'viewing') {
                    // Get steamid from person who's friendlist you are viewing
                    const friendIDMatch = document.querySelector('.friends_header_avatar a');
                    if (friendIDMatch) {
                        const friendSteamID = friendIDMatch.href.split('/').pop();
                        yourSteamLevel = await getSteamLevel(friendSteamID); // Fetch the friend's Steam level
                        if (yourSteamLevel) {
                            addRanking();
                        } else {
                            console.error('Failed to fetch current friend\'s Steam ID.');
                        }
                    } else {
                        console.error('Failed to fetch current friend\'s Steam ID.');
                    }
                } else {
                    console.error('Please select a valid option and enter a Steam ID if necessary.');
                }
            });
            // Insert the control container into the DOM
            titleBar.insertAdjacentElement('afterend', controlContainer);
        } else {
            console.error('Failed to find title bar to insert options.');
        }
    }


    // Function to scrape a user's Steam level from their profile
    async function getSteamLevel(steamID) {
        const profileUrl = `https://steamcommunity.com/profiles/${steamID}/`;

        try {
            const response = await fetch(profileUrl);
            const text = await response.text();

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

            const levelElement = doc.querySelector('.friendPlayerLevelNum');
            const level = levelElement ? parseInt(levelElement.innerText.trim()) : null;

            if (level === null) {
                console.log(`Could not fetch Steam level for Steam ID: ${steamID}. The profile might be private.`);
            }

            return level;
        } catch (error) {
            console.error(`Error scraping Steam level for Steam ID: ${steamID}`, error);
            return null;
        }
    }

    // Function to scrape the friend's list of a friend and their Steam levels
    async function fetchFriendsLevels(friendSteamID, friendName, friendBlock) {
        const profileUrl = `https://steamcommunity.com/profiles/${friendSteamID}/friends/`;

        try {
            const response = await fetch(profileUrl);
            const text = await response.text();

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

            const friendsElements = doc.querySelectorAll('.friend_block_v2');
            const friendsSteamIDs = Array.from(friendsElements).map(el => el.getAttribute('data-steamid'));

            console.log(`Fetched ${friendsSteamIDs.length} friends for Steam ID: ${friendSteamID} (${friendName})`);

            // Limit the number of concurrent requests
            const friendsLevels = [];
            let percent = 1;
            for (let i = 0; i < friendsSteamIDs.length; i += maxConcurrentRequests) {
                const levelPromises = friendsSteamIDs.slice(i, i + maxConcurrentRequests).map(async (steamID, index) => {
                    const level = await getSteamLevel(steamID);
                    console.log(`${friendName}: ${index + i + 1}/${friendsSteamIDs.length} Steam ID: ${steamID}, Level: ${level}`); // Added log
                    if(i/friendsSteamIDs.length >= (percent/10))
                    {
                        console.log(`Progress: ${percent*10}%`);

                        // Update progress in the DOM (Ensure this only updates the friendBlock UI once per batch)
                        const progressElement = friendBlock.querySelector('.ranking_white');
                        if (progressElement) {
                            progressElement.textContent = `Progress: ${percent * 10}%`; // Just an example of progress display
                        } else {
                            const newprogressElement = document.createElement('div');
                            newprogressElement.className = 'ranking_white';
                            newprogressElement.style.cssText = `
                            position: absolute;
                            top: 2px;
                            right: -2px;
                            font-weight: bold;
                            font-size: 11px;
                            color: #6a6e70; //grey
                            background-color: rgba(29, 35, 42, 0.6);
                            padding: 2px 6px;
                            border-radius: 3px;
                        `;
                            newprogressElement.textContent = `Progress: ${percent * 10}%`;
                            friendBlock.querySelector('.friend_block_content').insertAdjacentElement('afterend', newprogressElement);
                        }
                        percent++;
                    }
                    return level;
                });
                const levels = await Promise.all(levelPromises);
                friendsLevels.push(...levels.filter(level => level !== null)); // Filter out null levels
            }
            // Remove the progress element before leaving
            const progressElement = friendBlock.querySelector('.ranking_white');
            if (progressElement) {
                progressElement.remove(); // Remove the whole ranking element
            }

            return friendsLevels;
        } catch (error) {
            console.error(`Error fetching friends' levels for Steam ID: ${friendSteamID} (${friendName})`, error);
            return [];
        }
    }

    // Function to compare levels and assign a rank based on friends' friends' levels
    function compareLevels(friendLevels) {
        const higherLevels = friendLevels.filter(level => level > yourSteamLevel);
        return higherLevels.length + 1;
    }

    // Main function to add the rank next to each friend's block
    async function addRanking() {
        const friendBlocks = document.querySelectorAll('.friend_block_v2');
        if (friendBlocks.length > 0) {
            for (let i = 0; i < friendBlocks.length; i++) {
                const friendBlock = friendBlocks[i];
                const friendSteamID = friendBlock.getAttribute('data-steamid');
                const friendName = friendBlock.querySelector('.friend_block_content').childNodes[0].textContent.trim(); // Extract the friend's name

                if (!friendBlock.querySelector('.ranking')) {
                    try {
                        console.log(`Fetching friends' levels for Steam ID: ${friendSteamID} (${friendName})`);
                        const friendLevels = await fetchFriendsLevels(friendSteamID, friendName, friendBlock);

                        let rankText = 'N/A';
                        let higherlevel = null;
                        let lowerlevel = null;
                        if (friendLevels.length > 0) {
                            const rank = compareLevels(friendLevels);
                            higherlevel = friendLevels.filter(level => level > yourSteamLevel);
                            lowerlevel = friendLevels.filter(level => level < yourSteamLevel);

                            if (higherlevel.length > 0) {
                                higherlevel = Math.min(...higherlevel);
                            }

                            // Calculate the highest lower level
                            if (lowerlevel.length > 0) {
                                lowerlevel = Math.max(...lowerlevel);
                            } else {
                                lowerlevel = null; // Explicitly set lowerlevel to null if no lower levels found
                            }

                            // Create the rank text with styled spans
                            const rankElement = document.createElement('div');
                            rankElement.className = 'ranking_white';
                            rankElement.style.cssText = `
                            position: absolute;
                            top: 2px;
                            right: -2px;  /* Move it to the side of the text */
                            font-weight: bold;
                            font-size: 11px;
                            color: #ebebeb; /* Default color for other text */
                            background-color: rgba(29, 35, 42, 0.6);
                            padding: 2px 6px;
                            border-radius: 3px;
                            z-index: 10; /* Ensure it's above other content */
                            `;

                            // Append the rank text with color coding
                            const rankTextElement = document.createElement('span');
                            rankTextElement.style.color = '#ebebeb'; // Color for "Rank: "
                            rankTextElement.textContent = `Rank: `;
                            rankElement.appendChild(rankTextElement);

                            const rankingValue = document.createElement('span');
                            rankingValue.style.color = '#f7ef8a'; // Gold color for the rank value
                            rankingValue.textContent = `${rank}`;
                            rankElement.appendChild(rankingValue);

                            const totalFriendsText = document.createElement('span');
                            totalFriendsText.style.color = '#ebebeb'; // Color for "(of ${friendLevels.length})"
                            totalFriendsText.textContent = ` (of ${friendLevels.length})`;
                            rankElement.appendChild(totalFriendsText);

                            rankElement.appendChild(document.createElement('br')); // Line break

                            // Handle higherlevel
                            if (higherlevel > yourSteamLevel) {
                                const nextText0 = document.createElement('span');
                                nextText0.style.color = '#ebebeb'; // whitish
                                nextText0.textContent = `Next: ${higherlevel} (`;
                                rankElement.appendChild(nextText0);

                                const nextText1 = document.createElement('span');
                                nextText1.style.color = '#80EF80'; // pastel green
                                nextText1.textContent = `+${higherlevel - yourSteamLevel}`;
                                rankElement.appendChild(nextText1);

                                const nextText2 = document.createElement('span');
                                nextText2.style.color = '#ebebeb'; // whitish
                                nextText2.textContent = `)`;
                                rankElement.appendChild(nextText2);
                                rankElement.appendChild(document.createElement('br')); // Line break

                            }

                            // Handle lowerlevel
                            if (lowerlevel != null) {
                                const prevText0 = document.createElement('span');
                                prevText0.style.color = '#ebebeb'; // whitish
                                prevText0.textContent = `Prev.: ${lowerlevel} (`;
                                rankElement.appendChild(prevText0);

                                const prevText1 = document.createElement('span');
                                prevText1.style.color = '#ff746c'; // pastel red
                                prevText1.textContent = `-${yourSteamLevel - lowerlevel}`;
                                rankElement.appendChild(prevText1);

                                const prevText2 = document.createElement('span');
                                prevText2.style.color = '#ebebeb'; // whitish
                                prevText2.textContent = `)`;
                                rankElement.appendChild(prevText2);
                            }

                            const friendContent = friendBlock.querySelector('.friend_block_content');
                            if (friendContent) {
                                friendContent.style.position = 'relative';
                                friendContent.insertAdjacentElement('afterend', rankElement);
                            } else {
                                console.error(`Could not find friend content block for Steam ID: ${friendSteamID}`);
                            }
                        } else {
                            // If no levels were fetched, mark as N/A
                            const rankElement = document.createElement('div');
                            rankElement.className = 'ranking_white';
                            rankElement.style.cssText = `
                            position: absolute;
                            top: 2px;
                            right: -2px;  /* Move it to the side of the text */
                            font-weight: bold;
                            font-size: 11px;
                            color: #ebebeb; /* Default color for other text */
                            background-color: rgba(0, 0, 0, 0);
                            padding: 2px 6px;
                            border-radius: 3px;
                            z-index: 10; /* Ensure it's above other content */
                            `;

                            // Mark as N/A for private profiles
                            const rankTextElement = document.createElement('span');
                            rankTextElement.style.color = '#6a6e70'; // grey
                            rankTextElement.textContent = `N/A`;
                            rankElement.appendChild(rankTextElement);

                            const friendContent = friendBlock.querySelector('.friend_block_content');
                            if (friendContent) {
                                friendContent.style.position = 'relative';
                                friendContent.insertAdjacentElement('afterend', rankElement);
                            } else {
                                console.error(`Could not find friend content block for Steam ID: ${friendSteamID}`);
                            }
                        }
                    } catch (error) {
                        console.error("Error ranking friend:", error);
                    }
                }
            }
        } else {
            console.log("No friend blocks found on the page.");
        }
    }

    // Add options UI when the page is ready
    window.onload = addOptionsUI;
})();