Skip to content

Instantly share code, notes, and snippets.

@tzengyuxio
Last active July 15, 2025 10:19
Show Gist options
  • Save tzengyuxio/483388b0eb95d3fb8c81d547e680f354 to your computer and use it in GitHub Desktop.
Save tzengyuxio/483388b0eb95d3fb8c81d547e680f354 to your computer and use it in GitHub Desktop.
Extract Song Details from SUNO to JSON
/**
* v2 bulk-suno.js
* 🎼 擷取 React App 中的歌曲資訊(透過 Fiber Tree)
*
* ✅ 使用方式:
* 1. 確保已安裝並啟用 React Developer Tools(Chrome 擴充)
* 2. 展開至少一首歌詞(讓歌詞與 metadata 被載入)
* 3. 打開開發者工具 Console,貼上此腳本執行
* 4. 成功後會將 JSON 格式資料自動下載並複製至剪貼簿
*
* ✅ 每筆歌曲包含欄位:
* - id
* - title
* - styleDescription
* - lyrics
* - createdAt
* - isLiked
* - imageUrl
* - videoUrl
* - duration
* - language
* - artist
*/
((filenamePrefix) => {
const lyricsMap = new Map();
function traverseFiber(node) {
if (!node) return;
const props = node.memoizedProps;
if (props && props.clip && props.clip.metadata?.prompt) {
const target = props.clip["[[Target]]"] || props.clip;
const meta = target.metadata || {};
const id = target.id;
const title = target.title;
const lyrics = meta.prompt?.trim();
const styleDescription = meta.tags?.trim?.() || meta.tags;
const createdAt = target.created_at;
const isLiked = target.is_liked;
const imageUrl = target.image_url;
const videoUrl = target.video_url;
const duration = meta.duration;
const language = meta.language;
const artist = meta.artist;
if (id && title && lyrics && !lyricsMap.has(id)) {
lyricsMap.set(id, {
id,
title,
styleDescription,
lyrics,
createdAt,
isLiked,
imageUrl,
videoUrl,
duration,
language,
artist
});
console.log("🎯 Found:", id, title);
}
}
traverseFiber(node.child);
traverseFiber(node.sibling);
}
try {
const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
const rendererId = hook?.renderers?.keys().next().value;
const roots = [...(hook?.getFiberRoots?.(rendererId) || [])];
if (!roots.length) {
console.error("❌ 無法取得 Fiber Roots,請先載入 React DevTools");
return;
}
for (const root of roots) {
traverseFiber(root.current);
}
const results = Array.from(lyricsMap.values());
if (results.length === 0) {
console.warn("⚠️ 沒有成功抓取任何歌曲資料");
} else {
const json = JSON.stringify(results, null, 2);
// ✅ 複製到剪貼簿
navigator.clipboard.writeText(json)
.then(() => console.log("📋 已複製至剪貼簿"))
.catch(err => console.warn("⚠️ 剪貼簿複製失敗:", err));
// ✅ 自動下載
const blob = new Blob([json], { type: "application/json" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
const filename = filenamePrefix
? `${filenamePrefix}_production_notes.json`
: `production_notes_${Date.now()}.json`;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
console.log("✅ 共擷取", results.length, "筆資料,已下載 JSON");
}
} catch (err) {
console.error("❌ 錯誤:", err);
}
})("suno"); // 如果你要自訂名稱,把 "suno" 改掉,或傳入空值、刪掉參數使用 timestamp 命名
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment