Guest User

Quick and dirty Udio metadata downloader

a guest
Nov 2nd, 2025
106
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
JavaScript 5.98 KB | Source Code | 0 0
  1. // ==UserScript==
  2. // @name         Udio Lyrics Auto Downloader (with prompt + tags + created-at + author)
  3. // @namespace    http://tampermonkey.net/
  4. // @version      2.1
  5. // @description  Auto-download lyrics from Udio song pages, with title, optional prompt, tags, created-at, and author. Uses URL ID in filename, waits for lyrics, shows indicator, and polls URL for SPA navigation.
  6. // @author       Microsoft Copilot
  7. // @match        https://www.udio.com/*
  8. // @grant        none
  9. // ==/UserScript==
  10.  
  11. (function() {
  12.     'use strict';
  13.  
  14.     function sanitizeFilename(name) {
  15.         return name.replace(/[\\/:*?"<>|]/g, "_");
  16.    }
  17.  
  18.    function showIndicator(message, success = true) {
  19.        const indicator = document.createElement("div");
  20.        indicator.textContent = message;
  21.        indicator.style.position = "fixed";
  22.        indicator.style.top = "20px";
  23.        indicator.style.left = "50%";
  24.        indicator.style.transform = "translateX(-50%)";
  25.        indicator.style.padding = "12px 20px";
  26.        indicator.style.fontSize = "18px";
  27.        indicator.style.fontWeight = "bold";
  28.        indicator.style.color = success ? "#fff" : "#000";
  29.        indicator.style.background = success ? "green" : "red";
  30.        indicator.style.border = "3px solid black";
  31.        indicator.style.zIndex = "99999";
  32.        indicator.style.borderRadius = "6px";
  33.        indicator.style.boxShadow = "0 0 10px rgba(0,0,0,0.5)";
  34.        indicator.style.opacity = "1";
  35.        indicator.style.transition = "opacity 1s ease-out";
  36.  
  37.        document.body.appendChild(indicator);
  38.  
  39.        setTimeout(() => {
  40.            indicator.style.opacity = "0";
  41.            setTimeout(() => indicator.remove(), 1000);
  42.        }, 3000);
  43.    }
  44.  
  45.    function waitForLyricsAndDownload(maxWaitMs = 15000, intervalMs = 300) {
  46.        const start = Date.now();
  47.  
  48.        const check = () => {
  49.            const titleEls = document.querySelectorAll("h1.truncate-2-lines");
  50.            const lyricCandidates = Array.from(document.querySelectorAll("pre.whitespace-pre-wrap"))
  51.                .map(el => el.textContent.trim())
  52.                .filter(text => text.length > 0);
  53.  
  54.            if (titleEls.length === 1 && lyricCandidates.length >= 1) {
  55.                const title = titleEls[0].textContent.trim();
  56.                const lyrics = lyricCandidates[0];
  57.  
  58.                // Optional: Prompt
  59.                let promptLine = "";
  60.                const promptSpan = Array.from(document.querySelectorAll("span"))
  61.                    .map(el => el.textContent.trim())
  62.                    .find(text => text.startsWith("Prompt:"));
  63.                if (promptSpan) {
  64.                    promptLine = promptSpan;
  65.                }
  66.  
  67.                // Optional: Tags
  68.                const tags = Array.from(document.querySelectorAll("a[href^='/tags/'] span"))
  69.                    .map(el => el.textContent.trim())
  70.                    .filter(t => t.length > 0);
  71.                let tagsLine = "";
  72.                if (tags.length > 0) {
  73.                    tagsLine = "Tags: " + tags.join(", ");
  74.                }
  75.  
  76.                // Optional: Created at
  77.                let createdLine = "";
  78.                const createdDiv = Array.from(document.querySelectorAll("div[title^='Created at ']"))
  79.                    .map(el => el.getAttribute("title"))
  80.                    .find(t => t && t.startsWith("Created at "));
  81.                if (createdDiv) {
  82.                    createdLine = createdDiv;
  83.                }
  84.  
  85.                // Optional: Author from meta og:title
  86.                let authorLine = "";
  87.                const meta = document.querySelector("meta[property='og:title']");
  88.                if (meta) {
  89.                    const content = meta.getAttribute("content") || "";
  90.                    const parts = content.split(" - ");
  91.                    if (parts.length > 1) {
  92.                        authorLine = "Author: " + parts[0].trim();
  93.                    }
  94.                }
  95.  
  96.                // Build content
  97.                let content = `Title: ${title}\n`;
  98.                if (authorLine) content += `${authorLine}\n`;
  99.                if (promptLine) content += `${promptLine}\n`;
  100.                if (tagsLine) content += `${tagsLine}\n`;
  101.                if (createdLine) content += `${createdLine}\n`;
  102.                content += `\n${lyrics}`;
  103.  
  104.                // Save file
  105.                const blob = new Blob([content], { type: "text/plain" });
  106.                const url = URL.createObjectURL(blob);
  107.  
  108.                const safeTitle = sanitizeFilename(title);
  109.                const pathParts = window.location.pathname.split("/");
  110.                const uniqueId = pathParts[pathParts.length - 1];
  111.                const filename = `${safeTitle} [${uniqueId}].txt`;
  112.  
  113.                const a = document.createElement("a");
  114.                a.href = url;
  115.                a.download = filename;
  116.                document.body.appendChild(a);
  117.                a.click();
  118.                document.body.removeChild(a);
  119.                URL.revokeObjectURL(url);
  120.  
  121.                console.log("Saved:", filename);
  122.                showIndicator("✅ Lyrics Saved", true);
  123.                return;
  124.            }
  125.  
  126.            if (Date.now() - start < maxWaitMs) {
  127.                setTimeout(check, intervalMs);
  128.            } else {
  129.                showIndicator("❌ Lyrics not loaded", false);
  130.            }
  131.        };
  132.  
  133.        check();
  134.    }
  135.  
  136.    // Poll for URL changes every half second
  137.    let lastUrl = location.href;
  138.    setInterval(() => {
  139.        const currentUrl = location.href;
  140.        if (currentUrl !== lastUrl) {
  141.            lastUrl = currentUrl;
  142.            if (/^https:\/\/www\.udio\.com\/songs\//.test(currentUrl)) {
  143.                waitForLyricsAndDownload();
  144.            }
  145.        }
  146.    }, 500);
  147.  
  148.    // Also run immediately if we land directly on a song page
  149.    if (/^https:\/\/www\.udio\.com\/songs\//.test(location.href)) {
  150.        waitForLyricsAndDownload();
  151.    }
  152. })();
  153.  
Advertisement
Add Comment
Please, Sign In to add comment