// ==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(); })();