CCCPaste

Producer.ai Metadata Downloader

// ==UserScript==
// @name         Producer.ai Metadata Downloader
// @namespace    http://tampermonkey.net/
// @version      0.3
// @description  Add a button to download metadata JSON for the current song
// @match        https://www.producer.ai/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    const BUTTON_ID = "producer-ai-download-btn";

    // --- Helper: extract GUID from URL ---
    function getSongId() {
        const match = window.location.pathname.match(/\/song\/([a-f0-9-]+)/);
        return match ? match[1] : null;
    }

    // --- Helper: sanitize filename ---
    function sanitizeFilename(name) {
        return name.replace(/[^a-z0-9_\-]+/gi, "_");
    }

    // --- Helper: download JSON as file ---
    function downloadJSON(data, filename) {
        const blob = new Blob([JSON.stringify(data, null, 2)], { type: "application/json" });
        const url = URL.createObjectURL(blob);
        const a = document.createElement("a");
        a.href = url;
        a.download = filename;
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
        URL.revokeObjectURL(url);
    }

    // --- Helper: Handle the actual API call ---
    async function handleDownload() {
        const songId = getSongId();
        if (!songId) {
            alert("Could not determine song ID from URL.");
            return;
        }

        const btn = document.getElementById(BUTTON_ID);
        if(btn) btn.textContent = "⏳ Fetching...";

        try {
            const response = await fetch("https://www.producer.ai/__api/v2/generations", {
                method: "POST",
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify({ riff_ids: [songId] })
            });

            if (!response.ok) throw new Error("Network response was not ok");

            const json = await response.json();
            const generation = json.generations && json.generations[0];

            if (!generation) {
                alert("No generation data found in response.");
                return;
            }

            if (generation.id !== songId) {
                alert(`ID mismatch: expected ${songId}, got ${generation.id}`);
                return;
            }

            // Use title for filename if available
            const title = generation.title ? sanitizeFilename(generation.title) : songId;
            const filename = `${title}.json`;

            downloadJSON(generation, filename);

        } catch (err) {
            console.error("Error fetching lyric timing:", err);
            alert("Failed to fetch lyric timing JSON. See console for details.");
        } finally {
             if(btn) btn.textContent = "⬇️ Download Lyrics JSON";
        }
    }

    // --- Main Logic: Add Button if missing ---
    function checkAndAddButton() {
        // 1. Check if we are actually on a song page
        if (!getSongId()) {
            // Optional: Remove button if we navigate AWAY from a song page
            const existingBtn = document.getElementById(BUTTON_ID);
            if (existingBtn) existingBtn.remove();
            return;
        }

        // 2. Check if button already exists (prevent duplicates)
        if (document.getElementById(BUTTON_ID)) return;

        // 3. Create and append button
        const btn = document.createElement("button");
        btn.id = BUTTON_ID; // Important for tracking
        btn.textContent = "⬇️ Download Lyrics JSON";
        btn.style.position = "fixed";
        btn.style.top = "10px";
        btn.style.right = "10px";
        btn.style.zIndex = 9999;
        btn.style.padding = "8px";
        btn.style.background = "#4CAF50";
        btn.style.color = "white";
        btn.style.border = "none";
        btn.style.borderRadius = "4px";
        btn.style.cursor = "pointer";
        btn.onclick = handleDownload; // Attach the click handler

        document.body.appendChild(btn);
    }

    // --- SPA Observer ---
    // This watches the body for changes (navigation, rendering) and runs our check
    const observer = new MutationObserver(() => {
        checkAndAddButton();
    });

    // Start observing the document body for changes
    observer.observe(document.body, { childList: true, subtree: true });

    // Initial check in case the page is already loaded
    checkAndAddButton();

})();