App Store Metadata Viewer

Displays bundle ID & full metadata on App Store pages with advanced features, copy/export buttons, and improved UI

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 or Violentmonkey 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         App Store Metadata Viewer
// @namespace    http://tampermonkey.net/
// @version      2.3
// @description  Displays bundle ID & full metadata on App Store pages with advanced features, copy/export buttons, and improved UI
// @author       sharmanhall
// @match        https://apps.apple.com/*/app/*/id*
// @match        https://apps.apple.com/app/id*
// @grant        GM_setClipboard
// @grant        GM_download
// @grant        GM_registerMenuCommand
// @license      MIT
// @icon         https://www.google.com/s2/favicons?sz=64&domain=apple.com
// ==/UserScript==

(function () {
    'use strict';

    const settings = {
        displayMode: 'overlay',
        autoCopy: false
    };

    GM_registerMenuCommand('Toggle Display Mode', () => {
        settings.displayMode = settings.displayMode === 'overlay' ? 'inline' : 'overlay';
        alert(`Switched to: ${settings.displayMode}`);
        location.reload();
    });

    GM_registerMenuCommand('Toggle Auto-Copy', () => {
        settings.autoCopy = !settings.autoCopy;
        alert(`Auto-copy is now ${settings.autoCopy ? 'enabled' : 'disabled'}`);
    });

    function waitForAppId(attempt = 0) {
        const appIdMatch = window.location.href.match(/id(\d+)/);
        if (!appIdMatch && attempt < 10) {
            return setTimeout(() => waitForAppId(attempt + 1), 1000);
        } else if (!appIdMatch) {
            console.error('[Metadata Viewer] App ID not found.');
            return;
        }
        fetchAppData(appIdMatch[1]);
    }

    async function fetchAppData(appId) {
        const lookupUrl = `https://itunes.apple.com/lookup?id=${appId}`;
        try {
            const res = await fetch(lookupUrl);
            const data = await res.json();
            if (!data.results || !data.results.length) throw 'No results';
            showInfo(data.results[0]);
        } catch (err) {
            console.error('[Metadata Viewer] Failed to fetch metadata:', err);
        }
    }

    function formatBytes(bytes) {
        const kb = bytes / 1024;
        if (kb < 1024) return `${Math.round(kb)} KB`;
        return `${(kb / 1024).toFixed(1)} MB`;
    }

    function showInfo(app) {
        const {
            bundleId,
            version,
            minimumOsVersion,
            releaseDate,
            primaryGenreName,
            languageCodesISO2A,
            fileSizeBytes,
            sellerName,
            trackViewUrl
        } = app;

        const country = new URL(window.location.href).pathname.split('/')[1].toUpperCase();
        const lang = languageCodesISO2A?.join(', ') || 'N/A';

        const infoHTML = `
            <div id="bundle-id-widget" style="font-family: 'Segoe UI', Roboto, monospace; font-size: 13px; line-height: 1.8;">
                <div style="margin-bottom: 8px;"><strong>📦 Bundle ID:</strong> ${bundleId}</div>
                <div><strong>👨‍💻 Developer:</strong> ${sellerName}</div>
                <div><strong>📱 Version:</strong> ${version}</div>
                <div><strong>📅 Release Date:</strong> ${releaseDate?.split('T')[0]}</div>
                <div><strong>📂 Size:</strong> ${formatBytes(fileSizeBytes)}</div>
                <div><strong>🧭 Min OS:</strong> ${minimumOsVersion || 'N/A'}</div>
                <div><strong>🗂 Genre:</strong> ${primaryGenreName}</div>
                <div><strong>🌍 Country:</strong> ${country}</div>
                <div style="margin-bottom: 12px;"><strong>🗣️ Language(s):</strong> ${lang}</div>

                <div style="display: flex; gap: 10px; flex-wrap: wrap;">
                    <button id="copyBtn" style="background:#1E88E5;color:white;border:none;padding:6px 10px;border-radius:5px;cursor:pointer;">📋 Copy Bundle ID</button>
                    <button id="jsonBtn" style="background:#333;color:white;border:none;padding:6px 10px;border-radius:5px;cursor:pointer;">🗄 Export JSON</button>
                    <a href="${trackViewUrl}" target="_blank" style="color:#42A5F5;text-decoration:none;padding:6px 10px;border-radius:5px;background:#222;display:inline-block;">🔗 View on App Store</a>
                </div>
            </div>
        `;

        const container = document.createElement('div');
        container.innerHTML = infoHTML;

        if (settings.displayMode === 'inline') {
            container.style.background = '#f9f9f9';
            container.style.padding = '14px';
            container.style.border = '1px solid #ccc';
            container.style.borderRadius = '8px';
            container.style.marginTop = '20px';
            const target = document.querySelector('h1') || document.body;
            target.parentElement.insertBefore(container, target.nextSibling);
        } else {
            Object.assign(container.style, {
                position: 'fixed',
                bottom: '20px',
                right: '20px',
                background: '#111',
                color: '#fff',
                padding: '16px',
                borderRadius: '10px',
                boxShadow: '0 0 16px rgba(0,0,0,0.6)',
                zIndex: 99999,
                maxWidth: '300px',
                opacity: '85%'
            });
            document.body.appendChild(container);
        }

        const copyBtn = document.getElementById('copyBtn');
        copyBtn.onclick = () => {
            (typeof GM_setClipboard === 'function' ? GM_setClipboard : navigator.clipboard.writeText)(bundleId);
            copyBtn.textContent = '✅ Copied!';
            setTimeout(() => (copyBtn.textContent = '📋 Copy Bundle ID'), 1500);
        };

        const jsonBtn = document.getElementById('jsonBtn');
        jsonBtn.onclick = () => {
            const blob = new Blob([JSON.stringify(app, null, 2)], { type: 'application/json' });
            const url = URL.createObjectURL(blob);
            GM_download({ url, name: `bundleinfo-${bundleId}.json` });
        };

        if (settings.autoCopy) {
            (typeof GM_setClipboard === 'function' ? GM_setClipboard : navigator.clipboard.writeText)(bundleId);
        }
    }

    window.addEventListener('load', waitForAppId);
})();