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