diff --git a/chatgpt-suno-tampermonkey.user.js b/chatgpt-suno-tampermonkey.user.js index 3d7ba4c..8794e21 100644 --- a/chatgpt-suno-tampermonkey.user.js +++ b/chatgpt-suno-tampermonkey.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name ChatGPT to Suno Prompt Copier // @namespace local.suno-helper -// @version 0.8.1 +// @version 0.8.2 // @description Copy structured ChatGPT song prompt versions into Suno fields. // @author local // @match https://chatgpt.com/* @@ -28,7 +28,7 @@ (function () { "use strict"; - const SCRIPT_VERSION = "0.8.1"; + const SCRIPT_VERSION = "0.8.2"; const STORAGE_KEY = "chatgptSunoPromptSlots"; const SLOT_COUNT = 2; const isChatGPT = location.hostname.includes("chatgpt.com") || location.hostname.includes("chat.openai.com"); @@ -551,6 +551,30 @@ return unique([...String(text || "").matchAll(pattern)].map((match) => match[0].replace(/\\u002F/g, "/"))); } + function collectStringValues(value, output = []) { + if (value == null) return output; + if (typeof value === "string") { + output.push(value); + return output; + } + if (Array.isArray(value)) { + for (const item of value) collectStringValues(item, output); + return output; + } + if (typeof value === "object") { + for (const item of Object.values(value)) collectStringValues(item, output); + } + return output; + } + + function extractAudioUrlsFromValue(value) { + const text = collectStringValues(value).join("\n"); + return unique([ + ...extractUrls(text, /https?:\\?\/\\?\/cdn[0-9]*\.suno\.ai\\?\/[^"' <>)]+?\.(?:mp3|m4a|oga|ogg)(?:\?[^"' <>)]+)?/gi), + ...extractUrls(text, /https?:\/\/[^"' <>)]+?\.(?:mp3|m4a|oga|ogg)(?:\?[^"' <>)]+)?/gi), + ].map((url) => normalizeSunoMediaUrl(url.replace(/\\\//g, "/")))); + } + function readJsonScripts() { const results = []; for (const script of document.querySelectorAll("script")) { @@ -604,7 +628,8 @@ const title = String(firstValue(object, ["title", "name"]) || id || "suno-track").trim(); const imageUrl = firstValue(object, ["image_url", "imageUrl", "image_large_url", "imageLargeUrl", "cover_url", "coverUrl"]); const audioPath = firstValue(object, ["audio_url", "audioUrl", "stream_audio_url", "streamAudioUrl", "play_url", "playUrl", "play_path", "playPath", "mp3_url", "mp3Url", "video_url", "videoUrl"]); - const audioUrl = normalizeSunoMediaUrl(audioPath); + const audioUrls = extractAudioUrlsFromValue(object); + const audioUrl = normalizeSunoMediaUrl(audioPath) || audioUrls[0] || ""; const wavPath = firstValue(object, ["wav_url", "wavUrl", "download_url", "downloadUrl"]); const wavUrl = normalizeSunoMediaUrl(wavPath); const lyrics = firstValue(object, ["prompt", "lyrics", "lyric", "text"]); @@ -624,6 +649,7 @@ createdAt, owner: owner || ownerFromUser, audioUrl, + detectedAudioUrls: audioUrls, wavUrl, imageUrl, pageUrl: location.href, @@ -637,6 +663,7 @@ for (const key of ["title", "lyrics", "style", "model", "createdAt", "audioUrl", "wavUrl", "imageUrl", "pageUrl"]) { if (!target[key] && source[key]) target[key] = source[key]; } + target.detectedAudioUrls = unique([...(target.detectedAudioUrls || []), ...(source.detectedAudioUrls || [])]); target.metadata = target.metadata && Object.keys(target.metadata).length ? target.metadata : source.metadata; return target; } @@ -1098,7 +1125,13 @@ function getMp3Candidates(track) { const candidates = []; if (track.audioUrl && !/\.wav(?:\?|$)/i.test(track.audioUrl)) candidates.push(track.audioUrl); + for (const url of track.detectedAudioUrls || []) { + if (url && !/\.wav(?:\?|$)/i.test(url)) candidates.push(url); + } const metadata = track.metadata || {}; + for (const url of extractAudioUrlsFromValue(metadata)) { + if (url && !/\.wav(?:\?|$)/i.test(url)) candidates.push(url); + } for (const value of [ metadata.audio_url, metadata.audioUrl, @@ -1556,9 +1589,11 @@ selectedTrack: selectedTrack ? { id: selectedTrack.id || "", title: selectedTrack.title || "", + audioUrl: selectedTrack.audioUrl || "", hasWavUrl: Boolean(selectedTrack.wavUrl), hasImageUrl: Boolean(selectedTrack.imageUrl), hasLyrics: Boolean(selectedTrack.lyrics), + mp3Candidates: getMp3Candidates(selectedTrack).slice(0, 10), } : null, capturedSunoResponses: capturedSunoResponses.length, companion: healthError ? { ok: false, error: healthError } : health,