IMVU Badge Granting Automation (Username or CID)

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

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

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