merge: soundcloud fix from main

This commit is contained in:
wukko 2025-04-30 13:11:02 +06:00
commit 9a3d35185b
No known key found for this signature in database
GPG Key ID: 3E30B3F26C7B4AA2
4 changed files with 49 additions and 44 deletions

View File

@ -1,7 +1,7 @@
{ {
"name": "@imput/cobalt-api", "name": "@imput/cobalt-api",
"description": "save what you love", "description": "save what you love",
"version": "10.9.3", "version": "10.9.4",
"author": "imput", "author": "imput",
"exports": "./src/cobalt.js", "exports": "./src/cobalt.js",
"type": "module", "type": "module",

View File

@ -161,12 +161,8 @@ export default async function({ host, patternMatch, params }) {
isAudioOnly = true; isAudioOnly = true;
isAudioMuted = false; isAudioMuted = false;
r = await soundcloud({ r = await soundcloud({
url, ...patternMatch,
author: patternMatch.author,
song: patternMatch.song,
format: params.audioFormat, format: params.audioFormat,
shortLink: patternMatch.shortLink || false,
accessKey: patternMatch.accessKey || false
}); });
break; break;

View File

@ -1,4 +1,5 @@
import { env } from "../../config.js"; import { env } from "../../config.js";
import { resolveRedirectingURL } from "../url.js";
const cachedID = { const cachedID = {
version: '', version: '',
@ -7,22 +8,25 @@ const cachedID = {
async function findClientID() { async function findClientID() {
try { try {
let sc = await fetch('https://soundcloud.com/').then(r => r.text()).catch(() => {}); const sc = await fetch('https://soundcloud.com/').then(r => r.text()).catch(() => {});
let scVersion = String(sc.match(/<script>window\.__sc_version="[0-9]{10}"<\/script>/)[0].match(/[0-9]{10}/)); const scVersion = String(sc.match(/<script>window\.__sc_version="[0-9]{10}"<\/script>/)[0].match(/[0-9]{10}/));
if (cachedID.version === scVersion) return cachedID.id; if (cachedID.version === scVersion) {
return cachedID.id;
}
const scripts = sc.matchAll(/<script.+src="(.+)">/g);
let scripts = sc.matchAll(/<script.+src="(.+)">/g);
let clientid; let clientid;
for (let script of scripts) { for (let script of scripts) {
let url = script[1]; const url = script[1];
if (!url?.startsWith('https://a-v2.sndcdn.com/')) { if (!url?.startsWith('https://a-v2.sndcdn.com/')) {
return; return;
} }
let scrf = await fetch(url).then(r => r.text()).catch(() => {}); const scrf = await fetch(url).then(r => r.text()).catch(() => {});
let id = scrf.match(/\("client_id=[A-Za-z0-9]{32}"\)/); const id = scrf.match(/\("client_id=[A-Za-z0-9]{32}"\)/);
if (id && typeof id[0] === 'string') { if (id && typeof id[0] === 'string') {
clientid = id[0].match(/[A-Za-z0-9]{32}/)[0]; clientid = id[0].match(/[A-Za-z0-9]{32}/)[0];
@ -37,46 +41,57 @@ async function findClientID() {
} }
export default async function(obj) { export default async function(obj) {
let clientId = await findClientID(); const clientId = await findClientID();
if (!clientId) return { error: "fetch.fail" }; if (!clientId) return { error: "fetch.fail" };
let link; let link;
if (obj.url.hostname === 'on.soundcloud.com' && obj.shortLink) {
link = await fetch(`https://on.soundcloud.com/${obj.shortLink}/`, { redirect: "manual" }).then(r => { if (obj.shortLink) {
if (r.status === 302 && r.headers.get("location").startsWith("https://soundcloud.com/")) { obj = {
return r.headers.get("location").split('?', 1)[0] ...obj,
} ...await resolveRedirectingURL(
}).catch(() => {}); `https://on.soundcloud.com/${obj.shortLink}`
)
}
} }
if (!link && obj.author && obj.song) { if (obj.author && obj.song) {
link = `https://soundcloud.com/${obj.author}/${obj.song}${obj.accessKey ? `/s-${obj.accessKey}` : ''}` link = `https://soundcloud.com/${obj.author}/${obj.song}`;
if (obj.accessKey) {
link += `/s-${obj.accessKey}`;
}
} }
if (!link && obj.shortLink) return { error: "fetch.short_link" }; if (!link && obj.shortLink) return { error: "fetch.short_link" };
if (!link) return { error: "link.unsupported" }; if (!link) return { error: "link.unsupported" };
let json = await fetch(`https://api-v2.soundcloud.com/resolve?url=${link}&client_id=${clientId}`) const resolveURL = new URL("https://api-v2.soundcloud.com/resolve");
.then(r => r.status === 200 ? r.json() : false) resolveURL.searchParams.set("url", link);
.catch(() => {}); resolveURL.searchParams.set("client_id", clientId);
const json = await fetch(resolveURL).then(r => r.json()).catch(() => {});
if (!json) return { error: "fetch.fail" }; if (!json) return { error: "fetch.fail" };
if (json?.policy === "BLOCK") { if (json.duration > env.durationLimit * 1000) {
return { error: "content.too_long" };
}
if (json.policy === "BLOCK") {
return { error: "content.region" }; return { error: "content.region" };
} }
if (json?.policy === "SNIP") { if (json.policy === "SNIP") {
return { error: "content.paid" }; return { error: "content.paid" };
} }
if (!json?.media?.transcodings || !json?.media?.transcodings.length === 0) { if (!json.media?.transcodings || !json.media?.transcodings.length === 0) {
return { error: "fetch.empty" }; return { error: "fetch.empty" };
} }
let bestAudio = "opus", let bestAudio = "opus",
selectedStream = json.media.transcodings.find(v => v.preset === "opus_0_0"), selectedStream = json.media.transcodings.find(v => v.preset === "opus_0_0");
mp3Media = json.media.transcodings.find(v => v.preset === "mp3_0_0");
const mp3Media = json.media.transcodings.find(v => v.preset === "mp3_0_0");
// use mp3 if present if user prefers it or if opus isn't available // use mp3 if present if user prefers it or if opus isn't available
if (mp3Media && (obj.format === "mp3" || !selectedStream)) { if (mp3Media && (obj.format === "mp3" || !selectedStream)) {
@ -88,22 +103,17 @@ export default async function(obj) {
return { error: "fetch.empty" }; return { error: "fetch.empty" };
} }
let fileUrlBase = selectedStream.url; const fileUrl = new URL(selectedStream.url);
let fileUrl = `${fileUrlBase}${fileUrlBase.includes("?") ? "&" : "?"}client_id=${clientId}&track_authorization=${json.track_authorization}`; fileUrl.searchParams.set("client_id", clientId);
fileUrl.searchParams.set("track_authorization", json.track_authorization);
if (!fileUrl.startsWith("https://api-v2.soundcloud.com/media/soundcloud:tracks:")) const file = await fetch(fileUrl)
return { error: "fetch.empty" };
if (json.duration > env.durationLimit * 1000) {
return { error: "content.too_long" };
}
let file = await fetch(fileUrl)
.then(async r => (await r.json()).url) .then(async r => (await r.json()).url)
.catch(() => {}); .catch(() => {});
if (!file) return { error: "fetch.empty" }; if (!file) return { error: "fetch.empty" };
let fileMetadata = { const fileMetadata = {
title: json.title.trim(), title: json.title.trim(),
artist: json.user.username.trim(), artist: json.user.username.trim(),
} }
@ -113,8 +123,7 @@ export default async function(obj) {
filenameAttributes: { filenameAttributes: {
service: "soundcloud", service: "soundcloud",
id: json.id, id: json.id,
title: fileMetadata.title, ...fileMetadata
author: fileMetadata.artist
}, },
bestAudio, bestAudio,
fileMetadata fileMetadata

View File

@ -59,7 +59,7 @@
}, },
{ {
"name": "on.soundcloud link", "name": "on.soundcloud link",
"url": "https://on.soundcloud.com/wLZre", "url": "https://on.soundcloud.com/XHLLKSXRQ5yyGDuD9",
"params": {}, "params": {},
"expected": { "expected": {
"code": 200, "code": 200,