IMVU _contents.json Viewer with Zip Download

Show previews, organize image types, and allow ZIP download with proper naming

Versión del día 13/04/2025. Echa un vistazo a la versión más reciente.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         IMVU _contents.json Viewer with Zip Download
// @namespace    http://tampermonkey.net/
// @version      1.01
// @auther       heapsofjoy
// @description  Show previews, organize image types, and allow ZIP download with proper naming
// @match        https://userimages-akm.imvu.com/productdata/*/*/_contents.json
// @grant        none
// @require      https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js
// ==/UserScript==

(function () {
    'use strict';

    let files;
    try {
        files = JSON.parse(document.body.innerText);
    } catch (e) {
        console.error("Failed to parse JSON:", e);
        return;
    }

    const baseUrl = window.location.href.replace(/\/_contents\.json$/, '');
    const parts = window.location.pathname.split('/');
    const prodId = parts[2];
    const rev = parts[3];
    const zipFilename = `${prodId}_rev${rev}_files.zip`;


    // Basic styling
    document.body.innerHTML = '';
    document.body.style.background = '#111';
    document.body.style.color = '#eee';
    document.body.style.fontFamily = 'Arial, sans-serif';
    document.body.style.padding = '20px';

    const heading = document.createElement('h1');
    heading.textContent = '📦 Product Files';
    heading.style.marginBottom = '10px';
    document.body.appendChild(heading);

    // Download ZIP button
    const downloadAllBtn = document.createElement('button');
    downloadAllBtn.textContent = '⬇ Download All as ZIP';
    downloadAllBtn.style.marginBottom = '25px';
    downloadAllBtn.style.padding = '10px 20px';
    downloadAllBtn.style.fontSize = '16px';
    downloadAllBtn.style.backgroundColor = '#ff69b4';
    downloadAllBtn.style.color = 'white';
    downloadAllBtn.style.border = 'none';
    downloadAllBtn.style.borderRadius = '8px';
    downloadAllBtn.style.cursor = 'pointer';

    downloadAllBtn.onclick = async () => {
        downloadAllBtn.disabled = true;
        downloadAllBtn.textContent = 'Zipping...';

        const zip = new JSZip();

        for (const file of files) {
            const originalName = file.name || file.url;
            if (isBlocked(originalName)) continue;

            const url = `${baseUrl}/${file.url || file.name}`;
            try {
                const res = await fetch(url);
                const blob = await res.blob();
                let adjustedName = originalName;

                if (originalName.toLowerCase().endsWith('.png') && blob.type === 'image/jpeg') {
                    adjustedName = originalName.replace(/\.png$/i, '.jpg');
                }

                zip.file(adjustedName, blob);
            } catch (e) {
                console.error('Failed to fetch:', originalName, e);
            }
        }

        zip.generateAsync({ type: 'blob' }).then(blob => {
            const a = document.createElement('a');
            a.href = URL.createObjectURL(blob);
            a.download = zipFilename;
            document.body.appendChild(a);
            a.click();
            a.remove();
            downloadAllBtn.textContent = '⬇ Download All as ZIP';
            downloadAllBtn.disabled = false;
        });
    };
    document.body.appendChild(downloadAllBtn);

    // Layout containers
    const imageGrid = document.createElement('div');
    imageGrid.style.display = 'grid';
    imageGrid.style.gridTemplateColumns = 'repeat(auto-fit, minmax(250px, 1fr))';
    imageGrid.style.gap = '15px';
    imageGrid.style.marginBottom = '50px';
    document.body.appendChild(imageGrid);

    const otherImagesSection = document.createElement('div');
    otherImagesSection.style.marginTop = '30px';
    document.body.appendChild(otherImagesSection);

    const fileList = document.createElement('div');
    fileList.style.paddingTop = '20px';
    fileList.style.borderTop = '1px solid #444';
    fileList.style.marginTop = '30px';
    document.body.appendChild(fileList);

    const blockedExts = [
        String.fromCharCode(120, 109, 102),
        String.fromCharCode(120, 115, 102)
    ];

    const isBlocked = (filename) => {
        return blockedExts.some(ext => filename.toLowerCase().endsWith(ext));
    };

    // Helper functions
    const isPreviewImage = (filename) => /\.(png|jpe?g|gif|webp)$/i.test(filename);
    const isOtherImageType = (filename) => /\.(tga|dds|bmp)$/i.test(filename);

    // Render all files
    files.forEach(file => {
        const name = file.name || file.url || 'Unnamed';
        const urlPart = file.url || file.name;

        // Skip blocked file types
        if (isBlocked(name)) return;

        const fileUrl = `${baseUrl}/${urlPart}`;

        if (isPreviewImage(urlPart)) {
            const container = document.createElement('div');
            container.style.background = '#222';
            container.style.padding = '10px';
            container.style.borderRadius = '10px';
            container.style.boxShadow = '0 0 10px rgba(0,0,0,0.3)';
            container.style.textAlign = 'center';

            const img = document.createElement('img');
            img.src = fileUrl;
            img.alt = name;
            img.style.maxWidth = '100%';
            img.style.maxHeight = '200px';
            img.style.display = 'block';
            img.style.margin = '0 auto 10px';
            container.appendChild(img);

            const link = document.createElement('a');
            link.href = fileUrl;
            link.download = name;
            link.textContent = name;
            link.style.color = '#ff69b4';
            link.style.textDecoration = 'none';
            link.style.wordBreak = 'break-all';
            container.appendChild(link);

            imageGrid.appendChild(container);
        } else if (isOtherImageType(name)) {
            if (!otherImagesSection.hasChildNodes()) {
                const label = document.createElement('h2');
                label.textContent = '🖼️ Other Image Files (Not Previewable)';
                label.style.marginBottom = '15px';
                otherImagesSection.appendChild(label);
            }

            const entry = document.createElement('div');
            const link = document.createElement('a');
            link.href = fileUrl;
            link.download = name;
            link.textContent = name;
            link.style.color = '#ff69b4';
            link.style.textDecoration = 'none';
            link.style.display = 'block';
            link.style.marginBottom = '8px';
            link.style.wordBreak = 'break-all';
            entry.appendChild(link);
            otherImagesSection.appendChild(entry);
        } else {
            const fileEntry = document.createElement('div');
            fileEntry.style.marginBottom = '10px';

            const link = document.createElement('a');
            link.href = fileUrl;
            link.download = name;
            link.textContent = name;
            link.style.color = '#ff69b4';
            link.style.textDecoration = 'none';
            link.style.wordBreak = 'break-all';
            fileEntry.appendChild(link);

            // If it's XML, allow preview
            if (/\.xml$/i.test(name)) {
                const previewBtn = document.createElement('button');
                previewBtn.textContent = '↓';
                previewBtn.style.marginLeft = '10px';
                previewBtn.style.padding = '2px 8px';
                previewBtn.style.fontSize = '12px';
                previewBtn.style.background = '#333';
                previewBtn.style.color = '#fff';
                previewBtn.style.border = '1px solid #666';
                previewBtn.style.borderRadius = '4px';
                previewBtn.style.cursor = 'pointer';

                const previewBox = document.createElement('pre');
                previewBox.style.display = 'none';
                previewBox.style.background = '#1e1e1e';
                previewBox.style.color = '#ccc';
                previewBox.style.padding = '10px';
                previewBox.style.marginTop = '5px';
                previewBox.style.border = '1px solid #333';
                previewBox.style.borderRadius = '6px';
                previewBox.style.maxHeight = '300px';
                previewBox.style.overflowY = 'auto';
                previewBox.style.whiteSpace = 'pre-wrap';

                previewBtn.onclick = async () => {
                    if (previewBox.style.display === 'none') {
                        previewBtn.textContent = '↑';
                        if (!previewBox.textContent) {
                            try {
                                const res = await fetch(fileUrl);
                                const text = await res.text();
                                previewBox.textContent = text;
                            } catch (err) {
                                previewBox.textContent = '[Error loading XML]';
                            }
                        }
                        previewBox.style.display = 'block';
                    } else {
                        previewBtn.textContent = '↓';
                        previewBox.style.display = 'none';
                    }
                };

                fileEntry.appendChild(previewBtn);
                fileEntry.appendChild(previewBox);
            }

            fileList.appendChild(fileEntry);
        }
    });
})();