mirror of
https://github.com/nathom/streamrip.git
synced 2025-05-14 07:04:51 -04:00
Dynamically find soundcloud client ID
This commit is contained in:
parent
61079a6c7b
commit
1f3b24e5b7
3 changed files with 76 additions and 38 deletions
|
@ -188,7 +188,11 @@ class Config:
|
||||||
if source == "deezer":
|
if source == "deezer":
|
||||||
return {"arl": self.file["deezer"]["arl"]}
|
return {"arl": self.file["deezer"]["arl"]}
|
||||||
if source == "soundcloud":
|
if source == "soundcloud":
|
||||||
return {}
|
soundcloud = self.file["soundcloud"]
|
||||||
|
return {
|
||||||
|
"client_id": soundcloud["client_id"],
|
||||||
|
"app_version": soundcloud["app_version"],
|
||||||
|
}
|
||||||
|
|
||||||
raise InvalidSourceError(source)
|
raise InvalidSourceError(source)
|
||||||
|
|
||||||
|
|
|
@ -63,6 +63,9 @@ deezloader_warnings = true
|
||||||
[soundcloud]
|
[soundcloud]
|
||||||
# Only 0 is available for now
|
# Only 0 is available for now
|
||||||
quality = 0
|
quality = 0
|
||||||
|
# This changes periodically, so it needs to be updated
|
||||||
|
client_id = ""
|
||||||
|
app_version = ""
|
||||||
|
|
||||||
[youtube]
|
[youtube]
|
||||||
# Only 0 is available for now
|
# Only 0 is available for now
|
||||||
|
@ -164,4 +167,4 @@ progress_bar = "dainty"
|
||||||
|
|
||||||
[misc]
|
[misc]
|
||||||
# Metadata to identify this config file. Do not change.
|
# Metadata to identify this config file. Do not change.
|
||||||
version = "1.5"
|
version = "1.6"
|
||||||
|
|
|
@ -1127,42 +1127,81 @@ class SoundCloudClient(Client):
|
||||||
|
|
||||||
source = "soundcloud"
|
source = "soundcloud"
|
||||||
max_quality = 0
|
max_quality = 0
|
||||||
logged_in = True
|
logged_in = False
|
||||||
|
|
||||||
|
client_id: str = ""
|
||||||
|
app_version: str = ""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Create a SoundCloudClient."""
|
"""Create a SoundCloudClient."""
|
||||||
self.session = gen_threadsafe_session(
|
self.session = gen_threadsafe_session(
|
||||||
headers={
|
headers={
|
||||||
"User-Agent": AGENT,
|
"User-Agent": AGENT,
|
||||||
"Host": "api-v2.soundcloud.com",
|
|
||||||
"Origin": "https://soundcloud.com",
|
|
||||||
"Referer": "https://soundcloud.com/",
|
|
||||||
"Sec-Fetch-Dest": "empty",
|
|
||||||
"Sec-Fetch-Mode": "cors",
|
|
||||||
"Sec-Fetch-Site": "same-site",
|
|
||||||
"Sec-GPC": "1",
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
def login(self):
|
def login(self, **kwargs):
|
||||||
"""Login is not necessary for SoundCloud."""
|
self.client_id = kwargs.get("client_id")
|
||||||
raise NotImplementedError
|
self.app_version = kwargs.get("app_version")
|
||||||
|
logger.debug("client_id: %s, app_version: %s", self.client_id, self.app_version)
|
||||||
|
|
||||||
|
# if (not self.client_id) or (not self.app_version) or (not self._announce()):
|
||||||
|
if not (self.client_id and self.app_version and self._announce()):
|
||||||
|
logger.debug(
|
||||||
|
"Refreshing client_id=%s and app_version=%s",
|
||||||
|
self.client_id,
|
||||||
|
self.app_version,
|
||||||
|
)
|
||||||
|
self._refresh_tokens()
|
||||||
|
|
||||||
|
self.logged_in = True
|
||||||
|
|
||||||
|
def _announce(self):
|
||||||
|
return self._get("announcements").status_code == 200
|
||||||
|
|
||||||
|
def _refresh_tokens(self):
|
||||||
|
STOCK_URL = "https://soundcloud.com/"
|
||||||
|
|
||||||
|
resp = self.session.get(STOCK_URL)
|
||||||
|
resp.encoding = "utf-8"
|
||||||
|
|
||||||
|
*_, client_id_url_match = re.finditer(
|
||||||
|
r"<script\s+crossorigin\s+src=\"([^\"]+)\"", resp.text
|
||||||
|
)
|
||||||
|
client_id_url = client_id_url_match.group(1)
|
||||||
|
|
||||||
|
self.app_version = re.search(
|
||||||
|
r'<script>window\.__sc_version="(\d+)"</script>', resp.text
|
||||||
|
).group(1)
|
||||||
|
|
||||||
|
resp2 = self.session.get(client_id_url)
|
||||||
|
self.client_id = re.search(r'client_id:\s*"(\w+)"', resp2.text).group(1)
|
||||||
|
|
||||||
|
def resolve_url(self, url: str) -> dict:
|
||||||
|
resp = self._get(f"resolve?url={url}").json()
|
||||||
|
from pprint import pformat
|
||||||
|
|
||||||
|
logger.debug(pformat(resp))
|
||||||
|
return resp
|
||||||
|
|
||||||
|
def get_tokens(self):
|
||||||
|
return self.client_id, self.app_version
|
||||||
|
|
||||||
def get(self, id, media_type="track"):
|
def get(self, id, media_type="track"):
|
||||||
"""Get metadata for a media type given an id.
|
"""Get metadata for a media type given a soundcloud url.
|
||||||
|
|
||||||
:param id:
|
:param id:
|
||||||
:param media_type:
|
:param media_type:
|
||||||
"""
|
"""
|
||||||
assert media_type in (
|
assert media_type in {
|
||||||
"track",
|
"track",
|
||||||
"playlist",
|
"playlist",
|
||||||
), f"{media_type} not supported"
|
}, f"{media_type} not supported"
|
||||||
|
|
||||||
if "http" in str(id):
|
if media_type == "track":
|
||||||
resp, _ = self._get(f"resolve?url={id}")
|
resp = self._get(f"{media_type}s/{id}")
|
||||||
elif media_type == "track":
|
resp.raise_for_status()
|
||||||
resp, _ = self._get(f"{media_type}s/{id}")
|
resp = resp.json()
|
||||||
else:
|
else:
|
||||||
raise Exception(id)
|
raise Exception(id)
|
||||||
|
|
||||||
|
@ -1194,16 +1233,13 @@ class SoundCloudClient(Client):
|
||||||
url = None
|
url = None
|
||||||
for tc in track["media"]["transcodings"]:
|
for tc in track["media"]["transcodings"]:
|
||||||
fmt = tc["format"]
|
fmt = tc["format"]
|
||||||
if (
|
if fmt["protocol"] == "hls" and fmt["mime_type"] == "audio/mpeg":
|
||||||
fmt["protocol"] == "hls"
|
|
||||||
and fmt["mime_type"] == "audio/mpeg"
|
|
||||||
):
|
|
||||||
url = tc["url"]
|
url = tc["url"]
|
||||||
break
|
break
|
||||||
|
|
||||||
assert url is not None
|
assert url is not None
|
||||||
|
|
||||||
resp, _ = self._get(url, no_base=True)
|
resp = self._get(url, no_base=True).json()
|
||||||
return {"url": resp["url"], "type": "mp3"}
|
return {"url": resp["url"], "type": "mp3"}
|
||||||
|
|
||||||
def search(self, query: str, media_type="album", limit=50, offset=50):
|
def search(self, query: str, media_type="album", limit=50, offset=50):
|
||||||
|
@ -1222,8 +1258,10 @@ class SoundCloudClient(Client):
|
||||||
"offset": offset,
|
"offset": offset,
|
||||||
"linked_partitioning": "1",
|
"linked_partitioning": "1",
|
||||||
}
|
}
|
||||||
resp, _ = self._get(f"search/{media_type}s", params=params)
|
result = self._get(f"search/{media_type}s", params=params)
|
||||||
return resp
|
|
||||||
|
# The response
|
||||||
|
return result.json()
|
||||||
|
|
||||||
def _get(
|
def _get(
|
||||||
self,
|
self,
|
||||||
|
@ -1232,7 +1270,7 @@ class SoundCloudClient(Client):
|
||||||
no_base=False,
|
no_base=False,
|
||||||
skip_decode=False,
|
skip_decode=False,
|
||||||
headers=None,
|
headers=None,
|
||||||
) -> Optional[Tuple[dict, int]]:
|
):
|
||||||
"""Send a request to the SoundCloud API.
|
"""Send a request to the SoundCloud API.
|
||||||
|
|
||||||
:param path:
|
:param path:
|
||||||
|
@ -1244,8 +1282,8 @@ class SoundCloudClient(Client):
|
||||||
"""
|
"""
|
||||||
param_arg = params
|
param_arg = params
|
||||||
params = {
|
params = {
|
||||||
"client_id": SOUNDCLOUD_CLIENT_ID,
|
"client_id": self.client_id,
|
||||||
"app_version": SOUNDCLOUD_APP_VERSION,
|
"app_version": self.app_version,
|
||||||
"app_locale": "en",
|
"app_locale": "en",
|
||||||
}
|
}
|
||||||
if param_arg is not None:
|
if param_arg is not None:
|
||||||
|
@ -1257,11 +1295,4 @@ class SoundCloudClient(Client):
|
||||||
url = f"{SOUNDCLOUD_BASE}/{path}"
|
url = f"{SOUNDCLOUD_BASE}/{path}"
|
||||||
|
|
||||||
logger.debug("Fetching url %s with params %s", url, params)
|
logger.debug("Fetching url %s with params %s", url, params)
|
||||||
r = self.session.get(url, params=params, headers=headers)
|
return self.session.get(url, params=params, headers=headers)
|
||||||
|
|
||||||
r.raise_for_status()
|
|
||||||
|
|
||||||
if skip_decode:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return r.json(), r.status_code
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue