IMVU Badge Granting Automation (Username or CID)

Automate badge granting on IMVU avatars page with manual username or CID input

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         IMVU Badge Granting Automation (Username or CID)
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  Automate badge granting on IMVU avatars page with manual username or CID input
// @author       heapsofjoy
// @match        https://avatars.imvu.com/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    let cancelGranting = false;
    let useCid = false; // toggle between username and CID

    // 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);
            });
    };

    // Start granting badges
    const startGrantingBadges = async (inputValue, outputBox, titleElement) => {
        let cid;
        if (useCid) {
            cid = inputValue; // use directly as CID
        } else {
            cid = await getCid(inputValue); // resolve username → CID
        }

        if (!cid) {
            showProgress(outputBox, 'Error: Unable to get CID for the input.');
            return;
        }

        cancelGranting = false;
        titleElement.innerText = `Granting badges for ${inputValue} (${useCid ? "CID" : "Username"})...`;

        const func = async (i) => {
            if (cancelGranting) return;

            const response = await fetch('https://avatars.imvu.com/api/service/grant_badge.php', {
                credentials: 'include',
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
                    'X-Requested-With': 'XMLHttpRequest',
                },
                referrer: `https://avatars.imvu.com/${inputValue}`,
                body: `sauce=${IMVU.sauce}&badgeid=badge-${cid}-${i}`,
                method: 'POST',
                mode: 'cors',
            });

            if (response.ok) {
                showProgress(outputBox, `Granted badge #${i}`);
            } else {
                showProgress(outputBox, `Failed to grant badge #${i}. Trying next.`);
            }
        };

        const recursive = async (i) => {
            if (cancelGranting) return;
            setTimeout(async () => {
                await func(i);
                recursive(i + 1);
            }, 2500);
        };

        recursive(1);
    };

    const showProgress = (outputBox, message) => {
        outputBox.textContent += `\n${message}`;
        outputBox.scrollTop = outputBox.scrollHeight;
    };

    const createPopoutButton = () => {
        const button = document.createElement('button');
        button.innerText = '▶';
        Object.assign(button.style, {
            position: 'fixed', bottom: '20px', right: '20px', width: '40px', height: '40px',
            borderRadius: '50%', backgroundColor: '#4CAF50', color: 'white', border: 'none',
            fontSize: '20px', cursor: 'pointer', boxShadow: '0 4px 8px rgba(0,0,0,0.3)'
        });

        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);
    };

    const createInterfaceBox = () => {
        const box = document.createElement('div');
        box.id = 'badge-granting-box';
        Object.assign(box.style, {
            position: 'fixed', bottom: '20px', right: '20px', width: '340px', height: '440px',
            backgroundColor: '#fff', border: '1px solid #ccc', borderRadius: '8px',
            boxShadow: '0 4px 8px rgba(0,0,0,0.3)', padding: '15px', display: 'none'
        });

        const titleElement = document.createElement('h3');
        titleElement.innerText = 'Enter Username or CID';
        Object.assign(titleElement.style, { textAlign: 'center', marginBottom: '10px' });
        box.appendChild(titleElement);

        // Input + buttons container
        const inputContainer = document.createElement('div');
        Object.assign(inputContainer.style, { display: 'flex', marginBottom: '10px' });

        const userInput = document.createElement('input');
        userInput.type = 'text';
        userInput.placeholder = 'Enter IMVU username or CID';
        Object.assign(userInput.style, {
            flex: '1',
            padding: '6px 10px',
            fontSize: '14px',
            lineHeight: '18px',
            height: '32px',
            border: '1px solid #ccc',
            borderRadius: '4px',
            boxSizing: 'border-box',
            outline: 'none'
        });
        inputContainer.appendChild(userInput);

        // Fill from URL button
        const fillButton = document.createElement('button');
        fillButton.innerText = 'URL';
        Object.assign(fillButton.style, {
            marginLeft: '5px',
            padding: '0 8px',
            border: '1px solid #ccc',
            borderRadius: '4px',
            cursor: 'pointer',
            backgroundColor: '#eee',
            fontSize: '12px'
        });
        fillButton.addEventListener('click', () => {
            const match = window.location.pathname.match(/^\/([^\/]+)/);
            if (match) {
                userInput.value = match[1];
            }
        });
        inputContainer.appendChild(fillButton);

        // Mode toggle button
        const modeButton = document.createElement('button');
        modeButton.innerText = 'Username';
        Object.assign(modeButton.style, {
            marginLeft: '5px',
            padding: '0 8px',
            border: '1px solid #ccc',
            borderRadius: '4px',
            cursor: 'pointer',
            backgroundColor: '#eee',
            fontSize: '12px'
        });
        modeButton.addEventListener('click', () => {
            useCid = !useCid;
            modeButton.innerText = useCid ? 'CID' : 'Username';
        });
        inputContainer.appendChild(modeButton);

        box.appendChild(inputContainer);

        const outputBox = document.createElement('div');
        Object.assign(outputBox.style, {
            height: '250px', overflowY: 'auto', backgroundColor: '#f8f8f8',
            border: '1px solid #ccc', borderRadius: '5px', fontSize: '14px',
            padding: '10px', whiteSpace: 'pre-wrap'
        });
        box.appendChild(outputBox);

        const buttonContainer = document.createElement('div');
        Object.assign(buttonContainer.style, { display: 'flex', justifyContent: 'space-between', marginTop: '10px' });

        const runButton = document.createElement('button');
        runButton.innerText = 'Start';
        Object.assign(runButton.style, {
            flex: '1', padding: '10px', backgroundColor: '#4CAF50', color: 'white',
            border: 'none', borderRadius: '5px', cursor: 'pointer', marginRight: '5px'
        });
        runButton.addEventListener('click', () => {
            const value = userInput.value.trim();
            if (!value) {
                showProgress(outputBox, 'Please enter a username or CID first.');
                return;
            }
            outputBox.textContent = ''; // Reset output
            showProgress(outputBox, `Starting badge granting for ${value} (${useCid ? "CID" : "Username"})...`);
            startGrantingBadges(value, outputBox, titleElement);
        });

        const stopButton = document.createElement('button');
        stopButton.innerText = 'Stop';
        Object.assign(stopButton.style, {
            flex: '1', padding: '10px', backgroundColor: '#f44336', color: 'white',
            border: 'none', borderRadius: '5px', cursor: 'pointer', marginLeft: '5px'
        });
        stopButton.addEventListener('click', () => {
            cancelGranting = true;
            showProgress(outputBox, 'Process stopped. You can enter a new username or CID.');
            titleElement.innerText = 'Stopped';
        });

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

        document.body.appendChild(box);
    };

    createPopoutButton();
    createInterfaceBox();
})();