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