IMVU Badge Granting Automation (Improved UI with URL-based Username)

Automate badge granting on IMVU avatars page with pop-out interface and clean layout

// ==UserScript==
// @name         IMVU Badge Granting Automation (Improved UI with URL-based Username)
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Automate badge granting on IMVU avatars page with pop-out interface and clean layout
// @author       heapsofjoy
// @match        https://avatars.imvu.com/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // Extract username from the URL
    const getUsernameFromUrl = () => {
        const url = window.location.href; // Get the current URL
        console.log("Current URL:", url); // Debugging: check if URL is correct
        const match = url.match(/https:\/\/avatars\.imvu\.com\/([A-Za-z0-9_]+)/); // Match the username part in the URL
        if (match) {
            console.log("Username extracted from URL:", match[1]); // Debugging: show username extracted
            return match[1];
        } else {
            console.error("Username extraction failed from URL."); // Error if username isn't found
            return null;
        }
    };

    // Fetch CID for the entered username
    const getCid = (name) => {
        return fetch(`https://api.imvu.com/user?username=${name}`, { credentials: 'include' })
            .then(response => response.json())
            .then(data => {
                return Object.entries(data.denormalized)[0][1].data.legacy_cid;
            })
            .catch(err => {
                console.error("Error fetching CID:", err); // Debugging: if fetch fails
            });
    };

    // Start granting badges
    let cancelGranting = false; // Flag to control the cancellation of the badge granting process
    const startGrantingBadges = async (referrer, outputBox, titleElement) => {
        const cid = await getCid(referrer);
        if (!cid) {
            showProgress(outputBox, 'Error: Unable to get CID for the username.');
            return;
        }

        // Function to grant a badge
        const func = async (i) => {
            if (cancelGranting) return; // Stop if the user has clicked the stop button

            // Grant the badge
            const response = await fetch('https://avatars.imvu.com/api/service/grant_badge.php', {
                credentials: 'include', // Include credentials (cookies/session info)
                headers: {
                    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:87.0) Gecko/20100101 Firefox/87.0',
                    'Accept': '*/*',
                    'Accept-Language': 'en-US,en;q=0.5',
                    'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
                    'X-Requested-With': 'XMLHttpRequest',
                },
                referrer: `https://avatars.imvu.com/${referrer}`,
                body: `sauce=${IMVU.sauce}&badgeid=badge-${cid}-${i}`,
                method: 'POST',
                mode: 'cors',
            });

            // If the badge was granted (check for successful response)
            if (response.ok) {
                showProgress(outputBox, `Granted badge #${i}`);
            } else {
                showProgress(outputBox, `Failed to grant badge #${i}. Trying next.`);
            }
        };

        // Recursive function to iterate badge granting
        const recursive = async (i) => {
            if (cancelGranting) return; // Stop if the user has clicked the stop button
            setTimeout(async () => {
                await func(i); // Try granting the badge
                recursive(i + 1); // Move to the next badge
            }, 2500);  // Delay between badge granting
        };

        // Update the title to indicate the process is running
        titleElement.innerText = `Granting badges for ${referrer}...`;

        // Start the recursive badge granting process
        recursive(1);
    };

    // Show progress in the output box
    const showProgress = (outputBox, message) => {
        outputBox.textContent += `\n${message}`;
        outputBox.scrollTop = outputBox.scrollHeight;  // Scroll to the bottom
    };

    // Create pop-out button to toggle the interface visibility
    const createPopoutButton = () => {
        const button = document.createElement('button');
        button.innerText = '▶'; // Use a play button icon for a more intuitive look
        button.style.position = 'fixed';
        button.style.bottom = '20px';
        button.style.right = '20px';
        button.style.width = '40px';
        button.style.height = '40px';
        button.style.borderRadius = '50%';
        button.style.backgroundColor = '#4CAF50';
        button.style.color = 'white';
        button.style.border = 'none';
        button.style.fontSize = '20px';
        button.style.cursor = 'pointer';
        button.style.boxShadow = '0 4px 8px rgba(0,0,0,0.3)';
        button.style.transition = 'background-color 0.3s, transform 0.3s';

        // Hover effect
        button.addEventListener('mouseover', () => {
            button.style.backgroundColor = '#45a049';
            button.style.transform = 'scale(1.1)';
        });
        button.addEventListener('mouseout', () => {
            button.style.backgroundColor = '#4CAF50';
            button.style.transform = 'scale(1)';
        });

        // Toggle pop-out box visibility
        button.addEventListener('click', () => {
            const popoutBox = document.getElementById('badge-granting-box');
            popoutBox.style.display = (popoutBox.style.display === 'none' || !popoutBox.style.display) ? 'block' : 'none';
        });

        document.body.appendChild(button);
    };

    // Create the interface container
    const createInterfaceBox = () => {
        const box = document.createElement('div');
        box.id = 'badge-granting-box';
        box.style.position = 'fixed';
        box.style.bottom = '20px';
        box.style.right = '20px';
        box.style.width = '320px';
        box.style.height = '400px';
        box.style.backgroundColor = '#fff';
        box.style.border = '1px solid #ccc';
        box.style.borderRadius = '8px';
        box.style.boxShadow = '0 4px 8px rgba(0,0,0,0.3)';
        box.style.padding = '15px';
        box.style.display = 'none'; // Initially hidden

        // Add Title Above Output Box
        const titleElement = document.createElement('h3');
        titleElement.innerText = 'Initializing...';
        titleElement.style.textAlign = 'center';
        titleElement.style.marginBottom = '10px';
        titleElement.style.fontSize = '18px';
        titleElement.style.fontWeight = 'bold';
        box.appendChild(titleElement);

        // Create output box for displaying progress
        const outputBox = document.createElement('div');
        outputBox.style.height = '250px';
        outputBox.style.overflowY = 'auto';
        outputBox.style.backgroundColor = '#f8f8f8';
        outputBox.style.border = '1px solid #ccc';
        outputBox.style.borderRadius = '5px';
        outputBox.style.fontSize = '14px';
        outputBox.style.padding = '10px';
        outputBox.style.whiteSpace = 'pre-wrap'; // Preserve line breaks
        box.appendChild(outputBox);

        // Create run and stop buttons
        const buttonContainer = document.createElement('div');
        buttonContainer.style.display = 'flex';
        buttonContainer.style.justifyContent = 'space-between';
        buttonContainer.style.marginTop = '10px';

        // Run Button
        const runButton = document.createElement('button');
        runButton.innerText = 'Run';
        runButton.style.flex = '1';
        runButton.style.padding = '10px';
        runButton.style.backgroundColor = '#4CAF50';
        runButton.style.color = 'white';
        runButton.style.border = 'none';
        runButton.style.borderRadius = '5px';
        runButton.style.cursor = 'pointer';
        runButton.addEventListener('click', () => {
            const username = getUsernameFromUrl(); // Get username from the URL
            if (username) {
                showProgress(outputBox, 'Starting badge granting...');
                startGrantingBadges(username, outputBox, titleElement);
            } else {
                showProgress(outputBox, 'Error: Could not extract username.');
            }
        });

        // Stop Button
        const stopButton = document.createElement('button');
        stopButton.innerText = 'Stop';
        stopButton.style.flex = '1';
        stopButton.style.padding = '10px';
        stopButton.style.backgroundColor = '#f44336';
        stopButton.style.color = 'white';
        stopButton.style.border = 'none';
        stopButton.style.borderRadius = '5px';
        stopButton.style.cursor = 'pointer';
        stopButton.addEventListener('click', () => {
            cancelGranting = true; // Stop the granting process
            showProgress(outputBox, 'Process stopped.');
            titleElement.innerText = 'Stopped';
        });

        buttonContainer.appendChild(runButton);
        buttonContainer.appendChild(stopButton);
        box.appendChild(buttonContainer);

        document.body.appendChild(box);
    };

    // Initialize everything
    createPopoutButton();
    createInterfaceBox();
})();