IMVU Creator PID Backup Utility - No JSON Numbers

Scrapes product IDs and names from IMVU shop pages, navigating through all pages, showing a cute button, and saving results as JSON.

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください。
// ==UserScript==
// @name         IMVU Creator PID Backup Utility - No JSON Numbers
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Scrapes product IDs and names from IMVU shop pages, navigating through all pages, showing a cute button, and saving results as JSON.
// @author       heapsofjoy
// @match        https://www.imvu.com/shop/web_search.php?manufacturers_id=*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // Create a button to trigger scraping
    const button = document.createElement('button');
    button.innerHTML = '💖 Scrape All Pages 💖';  // Add a cute emoji to the button
    button.style.position = 'fixed';
    button.style.bottom = '10px';  // Position near the bottom of the page
    button.style.right = '10px';   // Position near the right edge of the page
    button.style.zIndex = '10001';
    button.style.padding = '12px 20px';
    button.style.backgroundColor = '#ff69b4';  // Pink color
    button.style.color = 'white';
    button.style.border = 'none';
    button.style.borderRadius = '20px';  // Rounded corners
    button.style.fontFamily = 'Arial, sans-serif';
    button.style.fontSize = '16px';
    button.style.cursor = 'pointer';
    button.style.boxShadow = '0px 4px 10px rgba(0,0,0,0.1)';
    document.body.appendChild(button);

    // Create a sidebar to hold product IDs and names (above the button)
    const sidebar = document.createElement('div');
    sidebar.id = 'product-sidebar';
    sidebar.style.position = 'fixed';
    sidebar.style.bottom = '60px';  // Position above the button
    sidebar.style.right = '10px';
    sidebar.style.width = '300px';
    sidebar.style.height = '70%';
    sidebar.style.overflowY = 'scroll';
    sidebar.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
    sidebar.style.color = 'white';
    sidebar.style.padding = '10px';
    sidebar.style.fontFamily = 'Arial, sans-serif';
    sidebar.style.fontSize = '14px';
    sidebar.style.zIndex = '10000';
    sidebar.style.borderRadius = '8px';
    sidebar.style.boxShadow = '0px 0px 10px rgba(0,0,0,0.5)';
    sidebar.style.display = 'none'; // Hidden until button is pressed
    document.body.appendChild(sidebar);

    const header = document.createElement('h2');
    header.textContent = 'Product List';
    header.style.color = 'yellow';
    sidebar.appendChild(header);

    const productList = document.createElement('ul');
    productList.id = 'product-list';
    productList.style.listStyleType = 'none';
    productList.style.padding = '0';
    sidebar.appendChild(productList);

    // Create a Save as JSON button (hidden initially)
    const saveButton = document.createElement('button');
    saveButton.textContent = 'Save as JSON';
    saveButton.style.display = 'none';  // Hidden until scraping completes
    saveButton.style.marginTop = '10px';
    saveButton.style.padding = '10px 15px';
    saveButton.style.backgroundColor = '#ff69b4';  // Pink color
    saveButton.style.color = 'white';
    saveButton.style.border = 'none';
    saveButton.style.borderRadius = '20px';  // Rounded corners
    saveButton.style.cursor = 'pointer';
    sidebar.appendChild(saveButton);

    // Create loading spinner inside the button
    const loadingSpinner = document.createElement('div');
    loadingSpinner.id = 'loading-spinner';
    loadingSpinner.style.display = 'none';  // Hidden initially
    loadingSpinner.style.width = '16px';
    loadingSpinner.style.height = '16px';
    loadingSpinner.style.border = '3px solid #f3f3f3';
    loadingSpinner.style.borderTop = '3px solid white';  // Matching white to the button text
    loadingSpinner.style.borderRadius = '50%';
    loadingSpinner.style.animation = 'spin 1s linear infinite';
    loadingSpinner.style.marginLeft = '8px';  // Spacing between the text and the spinner
    button.appendChild(loadingSpinner);  // Add spinner inside the button

    // Add the CSS for the spinner animation
    const spinnerStyle = document.createElement('style');
    spinnerStyle.innerHTML = `
        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }
    `;
    document.head.appendChild(spinnerStyle);

    // To track all scraped products across pages
    const allProducts = [];
    const addedProductIds = new Set(); // To track unique product IDs

    // Function to extract product IDs and names from the current page
    function scrapeProducts() {
        const productLinks = document.querySelectorAll('a[href*="products_id="]'); // Select all <a> tags that contain "products_id=" in href
        const productData = [];

        productLinks.forEach(product => {
            const productIdMatch = product.href.match(/products_id=(\d+)/); // Extract the product ID from the href attribute
            const productName = product.getAttribute('title') || product.textContent.trim(); // Get the product name from the title or inner text

            if (productIdMatch && productName) {
                const productId = productIdMatch[1];
                if (!addedProductIds.has(productId)) { // If the product hasn't been added yet
                    productData.push({ id: parseInt(productId), name: productName }); // Ensure ID is treated as a number
                    addedProductIds.add(productId); // Mark this product ID as added
                }
            }
        });

        return productData;
    }

    // Function to add leading zeros to numbers (e.g., 1 -> 0001)
    function formatNumber(number, digits) {
        return number.toString().padStart(digits, '0');
    }

    // Function to add product data to the sidebar
    function displayProducts(products) {
        products.sort((a, b) => a.id - b.id); // Sort by product ID (numeric)
        productList.innerHTML = ''; // Clear any existing content

        products.forEach((product, index) => {
            const listItem = document.createElement('li');
            const formattedIndex = formatNumber(index + 1, 4); // Add leading zeros to the index (e.g., 0001, 0002, ...)
            listItem.textContent = `${formattedIndex}. ${product.name} - (${product.id})`;
            productList.appendChild(listItem);
        });
    }

    // Function to navigate to a specific page by modifying the URL
    function navigateToPage(pageNumber, manufacturerId) {
        const newUrl = `https://www.imvu.com/shop/web_search.php?manufacturers_id=${manufacturerId}&page=${pageNumber}`;
        window.history.pushState({}, '', newUrl); // Change URL without reloading the page
        return fetch(newUrl)
            .then(response => response.text())
            .then(html => {
                const parser = new DOMParser();
                const doc = parser.parseFromString(html, 'text/html');
                return doc;
            });
    }

    // Function to scrape through all pages one by one
    function scrapeAllPages(pageNumber, manufacturerId) {
        navigateToPage(pageNumber, manufacturerId).then(doc => {
            const productLinks = doc.querySelectorAll('a[href*="products_id="]');
            if (productLinks.length === 0) {
                // No more products on this page, stop
                sidebar.style.display = 'block';
                loadingSpinner.style.display = 'none'; // Hide loading spinner
                button.innerHTML = '💖 Scrape All Pages 💖';  // Reset the button text
                displayProducts(allProducts); // Display all products collected
                navigateToPage(1, manufacturerId);  // Navigate back to page 1

                // Show the Save as JSON button
                saveButton.style.display = 'block';

                return;
            }

            // Scrape products from the current page
            const productData = [];
            productLinks.forEach(product => {
                const productIdMatch = product.href.match(/products_id=(\d+)/);
                const productName = product.getAttribute('title') || product.textContent.trim();
                if (productIdMatch && productName) {
                    const productId = productIdMatch[1];
                    if (!addedProductIds.has(productId)) {
                        productData.push({ id: parseInt(productId), name: productName });
                        addedProductIds.add(productId);
                    }
                }
            });

            allProducts.push(...productData); // Append new products

            // Go to the next page
            scrapeAllPages(pageNumber + 1, manufacturerId);
        });
    }

    // Add event listener to the button to trigger scraping
    button.addEventListener('click', function() {
        const manufacturerIdMatch = window.location.href.match(/manufacturers_id=(\d+)/);
        if (!manufacturerIdMatch) {
            alert('Manufacturer ID not found in URL.');
            return;
        }

        const manufacturerId = manufacturerIdMatch[1];

        // Clear previous products
        allProducts.length = 0;
        addedProductIds.clear();
        productList.innerHTML = ''; // Clear any existing content

        // Show loading spinner and update button text
        loadingSpinner.style.display = 'inline-block';
        button.innerHTML = '💖 Scraping... ';
        button.appendChild(loadingSpinner);

        // Start scraping from page 1
        scrapeAllPages(1, manufacturerId);
    });

    // Add event listener to the Save as JSON button to save the data
    saveButton.addEventListener('click', function() {
        const jsonContent = JSON.stringify(allProducts, null, 2);
        const blob = new Blob([jsonContent], { type: 'application/json' });
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = 'imvu_products.json';
        a.click();
        URL.revokeObjectURL(url);
    });

})();