diff --git a/chatgpt-suno-tampermonkey.user.js b/chatgpt-suno-tampermonkey.user.js index 3655511..c51028d 100644 --- a/chatgpt-suno-tampermonkey.user.js +++ b/chatgpt-suno-tampermonkey.user.js @@ -30,7 +30,7 @@ (function () { "use strict"; - const SCRIPT_VERSION = "0.9.5"; + const SCRIPT_VERSION = "0.9.6"; const STORAGE_KEY = "chatgptSunoPromptSlots"; const SLOT_COUNT = 2; const isChatGPT = location.hostname.includes("chatgpt.com") || location.hostname.includes("chat.openai.com"); @@ -1024,6 +1024,15 @@ return data; } + function hasHttpStatus(error, statuses) { + const message = String(error?.message || error || ""); + return statuses.some((status) => message.startsWith(`${status} `) || message.includes(` ${status} `)); + } + + function shouldUseTampermonkeyFirst(url) { + return /^https?:\/\/cdn[0-9]*\.suno\.ai\//i.test(url); + } + async function callSunoText(url, options = {}) { const page = typeof unsafeWindow !== "undefined" ? unsafeWindow : window; const headers = { @@ -1031,7 +1040,7 @@ ...(options.method && options.method !== "GET" && options.method !== "HEAD" ? { "content-type": "application/json" } : {}), ...(options.headers || {}), }; - if (typeof GM_xmlhttpRequest === "function" && /^https?:\/\/[^/]*suno\./i.test(url)) { + if (typeof GM_xmlhttpRequest === "function" && shouldUseTampermonkeyFirst(url)) { return callTampermonkeyRequest(url, { ...options, headers }); } try { @@ -1207,6 +1216,7 @@ try { await callSunoJson(`${base}/convert_wav/`, { method: "POST" }); } catch (error) { + if (hasHttpStatus(error, [401, 403])) throw new Error("SUNO WAV 변환 권한이 없어. SUNO 페이지를 새로고침한 뒤 다시 시도해줘."); console.warn("[ChatGPT to Suno] convert_wav failed", error); } @@ -1217,6 +1227,7 @@ if (direct) return direct; if (await canReadUrl(cdnWavUrl)) return cdnWavUrl; } catch (error) { + if (hasHttpStatus(error, [401, 403])) throw new Error("SUNO WAV URL 권한이 없어. SUNO 페이지를 새로고침한 뒤 다시 시도해줘."); console.warn("[ChatGPT to Suno] wav_file failed", error); if (await canReadUrl(cdnWavUrl)) return cdnWavUrl; } @@ -1229,11 +1240,11 @@ async function prepareSunoWavCandidate(track) { if (!track.id) return extractWavUrl(track.wavUrl); const base = `https://studio-api-prod.suno.com/api/gen/${encodeURIComponent(track.id)}`; - const billingDownloadUrl = `https://studio-api-prod.suno.com/api/billing/clips/${encodeURIComponent(track.id)}/download/`; const cdnWavUrl = `https://cdn1.suno.ai/${encodeURIComponent(track.id)}.wav`; try { await callSunoJson(`${base}/convert_wav/`, { method: "POST" }); } catch (error) { + if (hasHttpStatus(error, [401, 403])) throw new Error("SUNO WAV 변환 권한이 없어. SUNO 페이지를 새로고침한 뒤 다시 시도해줘."); console.warn("[ChatGPT to Suno] convert_wav failed; local retry will continue", error); } try { @@ -1241,15 +1252,9 @@ const direct = extractWavUrl(data); if (direct) return direct; } catch (error) { + if (hasHttpStatus(error, [401, 403])) throw new Error("SUNO WAV URL 권한이 없어. SUNO 페이지를 새로고침한 뒤 다시 시도해줘."); console.warn("[ChatGPT to Suno] wav_file probe failed; local retry will continue", error); } - try { - const data = await callSunoJson(billingDownloadUrl, { method: "GET" }); - const direct = extractWavUrl(data); - if (direct) return direct; - } catch (error) { - console.warn("[ChatGPT to Suno] billing download probe failed; local retry will continue", error); - } return extractWavUrl(track.wavUrl) || cdnWavUrl; } @@ -1262,18 +1267,20 @@ try { await callSunoJson(`${base}/convert_wav/`, { method: "POST" }); } catch (error) { + if (hasHttpStatus(error, [401, 403])) throw new Error("SUNO WAV 변환 권한이 없어. SUNO 페이지를 새로고침한 뒤 다시 시도해줘."); console.warn("[ChatGPT to Suno] convert_wav failed before browser download", error); } for (let attempt = 0; attempt < 20; attempt += 1) { try { + setStatus(statusTarget, `SUNO WAV URL 확인 중 ${attempt + 1}/20: ${track.title || track.id}`); const data = await callSunoJson(`${base}/wav_file/`, { method: "GET" }); const direct = extractWavUrl(data); if (direct) candidates.push(direct); } catch (error) { + if (hasHttpStatus(error, [401, 403])) throw new Error("SUNO WAV URL 권한이 없어. SUNO 페이지를 새로고침한 뒤 다시 시도해줘."); console.warn("[ChatGPT to Suno] wav_file probe failed before browser download", error); } - candidates.push(`https://studio-api-prod.suno.com/api/billing/clips/${encodeURIComponent(track.id)}/download/`); candidates.push(`https://cdn1.suno.ai/${encodeURIComponent(track.id)}.wav`); for (const url of unique(candidates)) {