ROBLOX Developer Products Preview

Preview developer products on a ROBLOX game page (some may not be available for purchase in-game).

// ==UserScript==
// @name         ROBLOX Developer Products Preview
// @namespace    http://tampermonkey.net/
// @version      1337
// @description  Preview developer products on a ROBLOX game page (some may not be available for purchase in-game).
// @author       Andrew Oliver
// @match        https://www.roblox.com/games/*
// @grant        none
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';

    let previewButtonAdded = false;

    function getPlaceId(url) {
        const match = url.match(/\/games\/(\d+)/);
        return match ? match[1] : null;
    }

    async function init() {
        const placeId = getPlaceId(window.location.href);
        if (!placeId || previewButtonAdded) return;

        previewButtonAdded = true;

        async function getUniverseId(placeId) {
            const url = `https://apis.roblox.com/universes/v1/places/${placeId}/universe`;
            while (true) {
                try {
                    const response = await fetch(url);
                    if (response.ok) {
                        const data = await response.json();
                        if (data.universeId) return data.universeId;
                    }
                } catch (err) { console.error(err); }
                await new Promise(r => setTimeout(r, 5000));
            }
        }

        async function getDeveloperProducts(universeId) {
            const url = `https://apis.roblox.com/developer-products/v2/universes/${universeId}/developerproducts?limit=100`;
            while (true) {
                try {
                    const response = await fetch(url);
                    if (response.ok) {
                        const data = await response.json();
                        const products = [];
                        if (data.developerProducts && data.developerProducts.length > 0) {
                            for (const p of data.developerProducts) {
                                products.push({
                                    DisplayName: p.displayName || p.Name,
                                    PriceInRobux: p.PriceInRobux,
                                    DeveloperProductId: p.DeveloperProductId
                                });
                            }
                        }
                        return products;
                    }
                } catch (err) { console.error(err); }
                await new Promise(r => setTimeout(r, 5000));
            }
        }

        async function getDeveloperProductImages(productIds) {
            if (productIds.length === 0) return {};
            const url = `https://thumbnails.roblox.com/v1/developer-products/icons?developerProductIds=${productIds.join(",")}&size=150x150&format=png&isCircular=false`;
            while (true) {
                try {
                    const response = await fetch(url);
                    if (response.ok) {
                        const data = await response.json();
                        const images = {};
                        (data.data || []).forEach(item => images[item.targetId] = item.imageUrl);
                        return images;
                    }
                } catch (err) { console.error(err); }
                await new Promise(r => setTimeout(r, 5000));
            }
        }

        let previewVisible = false;
        let previewContainer = null;
        let dataLoaded = false;
        let cachedProducts = [];
        let cachedGameUrl = "";

        const previewButton = document.createElement('button');
        previewButton.textContent = 'Preview Products';
        previewButton.style.position = 'fixed';
        previewButton.style.bottom = '30px';
        previewButton.style.right = '30px';
        previewButton.style.padding = '12px 22px';
        previewButton.style.fontSize = '16px';
        previewButton.style.background = '#28a745';
        previewButton.style.color = '#fff';
        previewButton.style.border = 'none';
        previewButton.style.borderRadius = '8px';
        previewButton.style.cursor = 'pointer';
        previewButton.style.zIndex = '99999';
        previewButton.style.fontWeight = "600";
        document.body.appendChild(previewButton);

        previewButton.addEventListener('click', async function handlePreviewClick() {
            if (!dataLoaded) {
                previewButton.disabled = true;
                const originalText = previewButton.textContent;
                previewButton.textContent = 'Loading...';

                try {
                    const gameUrl = window.location.href;
                    const universeId = await getUniverseId(placeId);
                    let productsList = await getDeveloperProducts(universeId);

                    const productIds = productsList.map(p => p.DeveloperProductId);
                    const imagesMap = await getDeveloperProductImages(productIds);
                    const placeholderImage = "https://tr.rbxcdn.com/180DAY-155b79f176f56ede3583d6c4bd4eefff/150/150/Hat/Webp/noFilter";

                    productsList.forEach(p => {
                        p.ImageUrl = imagesMap[p.DeveloperProductId] || placeholderImage;
                    });

                    cachedProducts = productsList;
                    cachedGameUrl = gameUrl;

                    buildPreviewDOM(cachedProducts, cachedGameUrl);
                    dataLoaded = true;

                    previewContainer.style.display = 'block';
                    previewVisible = true;
                } finally {
                    previewButton.textContent = originalText;
                    previewButton.disabled = false;
                }
                return;
            }

            if (previewContainer) {
                previewContainer.style.display = previewVisible ? 'none' : 'block';
                previewVisible = !previewVisible;
            }
        });

        function buildPreviewDOM(productsList, gameUrl) {
            const bodyClass = document.body.className;

            previewContainer = document.createElement('div');
            previewContainer.id = 'preview-products-container';
            previewContainer.style.position = 'fixed';
            previewContainer.style.top = '50%';
            previewContainer.style.left = '50%';
            previewContainer.style.transform = 'translate(-50%, -50%)';
            previewContainer.style.padding = '20px';
            previewContainer.style.borderRadius = '10px';
            previewContainer.style.zIndex = '100000';
            previewContainer.style.maxHeight = '80vh';
            previewContainer.style.width = '100%';
            previewContainer.style.maxWidth = '1200px';
            previewContainer.style.overflowY = 'auto';
            previewContainer.style.boxSizing = 'border-box';

            if (bodyClass.includes('dark-theme')) {
                previewContainer.style.backgroundColor = '#1e1e1e';
                previewContainer.style.color = '#f0f0f0';
                previewContainer.style.boxShadow = '0 0 15px rgba(4,4,8,0.25)';
            } else {
                previewContainer.style.backgroundColor = '#ffffff';
                previewContainer.style.color = '#111';
                previewContainer.style.boxShadow = '0 0 15px rgba(0,0,0,0.08)';
            }

            const closeBtn = document.createElement('button');
            closeBtn.type = 'button';
            closeBtn.textContent = '✕';
            closeBtn.setAttribute('aria-label', 'Close preview');
            closeBtn.style.position = 'absolute';
            closeBtn.style.top = '8px';
            closeBtn.style.right = '12px';
            closeBtn.style.cursor = 'pointer';
            closeBtn.style.fontSize = '18px';
            closeBtn.style.background = 'transparent';
            closeBtn.style.border = 'none';
            closeBtn.style.color = bodyClass.includes('dark-theme') ? '#ddd' : '#444';
            closeBtn.style.padding = '0';
            closeBtn.style.lineHeight = '1';
            closeBtn.addEventListener('click', () => {
                previewContainer.style.display = 'none';
                previewVisible = false;
            });
            previewContainer.appendChild(closeBtn);

            const urlTitle = document.createElement('h3');
            urlTitle.textContent = 'Game URL:';
            previewContainer.appendChild(urlTitle);

            const urlP = document.createElement('p');
            urlP.textContent = gameUrl;
            previewContainer.appendChild(urlP);

            const dpTitle = document.createElement('h3');
            dpTitle.textContent = 'Developer Products:';
            previewContainer.appendChild(dpTitle);

            const ul = document.createElement('ul');
            ul.className = 'hlist store-cards gear-passes-container';
            ul.style.overflowX = 'auto';
            ul.style.whiteSpace = 'nowrap';
            ul.style.listStyle = 'none';
            ul.style.padding = '10px';
            ul.style.margin = '0';

            if (productsList.length === 0) {
                const li = document.createElement('li');
                li.textContent = 'No developer products found.';
                li.style.fontStyle = 'italic';
                li.style.color = bodyClass.includes('dark-theme') ? '#ccc' : '#666';
                ul.appendChild(li);
            } else {
                productsList.forEach(p => {
                    const li = document.createElement('li');
                    li.className = 'list-item real-game-pass';
                    li.style.display = 'inline-block';
                    li.style.marginRight = '12px';
                    li.style.verticalAlign = 'top';

                    const card = document.createElement('div');
                    card.className = 'store-card';
                    card.style.width = '150px';

                    const img = document.createElement('img');
                    img.src = p.ImageUrl;
                    img.alt = p.DisplayName;
                    img.style.width = '150px';
                    img.style.height = '150px';
                    img.style.backgroundColor = p.ImageUrl ? "transparent" : bodyClass.includes('dark-theme') ? "#2a2a2a" : "#f0f0f0";
                    card.appendChild(img);

                    const caption = document.createElement('div');
                    caption.className = 'store-card-caption';

                    const nameDiv = document.createElement('div');
                    nameDiv.className = 'text-overflow store-card-name';
                    nameDiv.title = p.DisplayName;
                    nameDiv.textContent = p.DisplayName;
                    nameDiv.style.color = bodyClass.includes('dark-theme') ? '#f0f0f0' : '#111';
                    caption.appendChild(nameDiv);

                    const priceDiv = document.createElement('div');
                    priceDiv.className = 'store-card-price';
                    priceDiv.innerHTML = `<span class="icon-robux-16x16"></span> <span class="text-robux">${p.PriceInRobux}</span>`;
                    priceDiv.style.color = bodyClass.includes('dark-theme') ? '#ddd' : '#333';
                    caption.appendChild(priceDiv);

                    card.appendChild(caption);
                    li.appendChild(card);
                    ul.appendChild(li);
                });
            }

            previewContainer.appendChild(ul);
            previewContainer.style.display = 'none';
            document.body.appendChild(previewContainer);
        }
    }

    let lastUrl = location.href;
    new MutationObserver(() => {
        const url = location.href;
        if (url !== lastUrl) {
            lastUrl = url;
            init();
        }
    }).observe(document, {subtree: true, childList: true});

    init();
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址