From bd572bf9aa248dc264c01a867c1650bffbffb184 Mon Sep 17 00:00:00 2001 From: Rob Sonke Date: Sun, 5 Jan 2025 20:32:38 +0100 Subject: [PATCH] Bumped libraries, improved readme, improved playlists and implemented a method to fetch much faster all track details --- .pre-commit-config.yaml | 6 +++--- README.md | 10 +++++----- example.py | 5 +++++ pyproject.toml | 25 ++++++++++++------------ soundcloudpy/soundcloudpy.py | 37 +++++++++++++++++++++++++++++++----- 5 files changed, 58 insertions(+), 25 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a7d6130..789a820 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,14 +9,14 @@ repos: types: [python] entry: scripts/run-in-env.sh ruff check --fix require_serial: true - stages: [commit, push, manual] + stages: [pre-commit, pre-push, manual] - id: ruff-format name: 🐶 Ruff Formatter language: system types: [python] entry: scripts/run-in-env.sh ruff format require_serial: true - stages: [commit, push, manual] + stages: [pre-commit, pre-push, manual] - id: check-ast name: 🐍 Check Python AST language: system @@ -36,7 +36,7 @@ repos: language: system types: [text, executable] entry: scripts/run-in-env.sh check-executables-have-shebangs - stages: [commit, push, manual] + stages: [pre-commit, pre-push, manual] # - id: check-merge-conflict # name: 💥 Check for merge conflicts # language: system diff --git a/README.md b/README.md index 9b87e20..51267a8 100644 --- a/README.md +++ b/README.md @@ -14,22 +14,22 @@ The package is published on PyPI and can be installed by running: pip install soundcloudpy ``` -## How to get O-Auth and Client id +## How to get OAuth and Client id 1. Go to [soundcloud](https://soundcloud.com) and login in 2. Open the "Inspect" tool (F12 on most browsers) 3. Refresh the page 4. Go to the page "Network" on the inspect terminal -5. Search on the column "File" for the "client_id" and "Request headers" for "Authorization" +5. Search on the column "File" for the "client_id" and the "oauth_token" cookie for "Authorization" `client_id`: string of 32 bytes alphanumeric -`authorization`: string that begins with O-Auth and a string (the o-auth token is "O-Auth . . .") +`authorization`: string that begins with OAuth and a string (the o-auth token is "OAuth . . .") -Example (O-Auth and client_id are NOT real, use yours): +Example (OAuth and client_id are NOT real, use yours): ``` -python -m example --client_id jHvc9wa0Ejf092wj3f3920w3F920as02 --auth_token 'O-Auth 3-26432-21446-asdif2309fj' +python -m example --client_id jHvc9wa0Ejf092wj3f3920w3F920as02 --auth_token 'OAuth 3-26432-21446-asdif2309fj' ``` diff --git a/example.py b/example.py index f31a9f3..fc4616a 100644 --- a/example.py +++ b/example.py @@ -54,6 +54,11 @@ async def connect(args: argparse.Namespace, session: ClientSession) -> None: tracks.append(item) LOGGER.info("Tracks: %s", tracks) + track_details = [] + async for item in soundcloud.get_track_details_liked(me["id"]): + track_details.append(item) + LOGGER.info("Track details: %s", track_details) + stream_url = await soundcloud.get_stream_url(tracks[0]) LOGGER.info("Stream url for track %s: %r", tracks[0], stream_url) diff --git a/pyproject.toml b/pyproject.toml index 6f95313..7f92590 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "soundcloudpy" -version = "1.0.0" +version = "1.0.1" license = { text = "Apache-2.0" } description = "Client for async connection to the Soundcloud api." readme = "README.md" @@ -13,8 +13,9 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", ] -dependencies = ["aiohttp>=3.8.4"] +dependencies = ["aiohttp>=3.11.11"] [project.optional-dependencies] # speedups = [ @@ -24,18 +25,18 @@ dependencies = ["aiohttp>=3.8.4"] # "orjson>=3.8.9", # ] test = [ - "black==24.2.0", - "codespell==2.2.6", + "black==24.10.0", + "codespell==2.3.0", "isort==5.13.2", - "mypy==1.8.0", - "pre-commit==3.6.1", - "pre-commit-hooks==4.5.0", - "pylint==3.0.3", - "pytest==8.0.0", + "mypy==1.14.1", + "pre-commit==4.0.1", + "pre-commit-hooks==5.0.0", + "pylint==3.3.3", + "pytest==8.3.4", "pytest-aiohttp==1.0.5", - "pytest-cov==4.1.0", - "ruff==0.2.1", - "safety==3.0.1", + "pytest-cov==6.0.0", + "ruff==0.8.5", + "safety==3.2.14", ] [tool.codespell] diff --git a/soundcloudpy/soundcloudpy.py b/soundcloudpy/soundcloudpy.py index 0d775f0..84de56c 100644 --- a/soundcloudpy/soundcloudpy.py +++ b/soundcloudpy/soundcloudpy.py @@ -128,7 +128,7 @@ async def get_track_details(self, track_id: str) -> dict[str, Any]: headers=self.headers, ) - async def get_tracks_liked(self, limit: int = 0) -> AsyncGenerator[list[dict[str, Any]], None]: + async def get_tracks_liked(self, limit: int = 0) -> AsyncGenerator[dict[str, Any], None]: """Obtain the authenticated user's liked tracks. :param limit: number of tracks to get. if 0, will fetch all tracks. @@ -150,6 +150,27 @@ async def get_tracks_liked(self, limit: int = 0) -> AsyncGenerator[list[dict[str yield track + async def get_track_details_liked( + self, user_id: str, limit: int = 0 + ) -> AsyncGenerator[list[dict[str, Any]], None]: + """Obtain the authenticated user's liked tracks with details. + + :param limit: number of tracks to get. if 0, will fetch all tracks. + :returns: list of track ids liked by the current user + """ + query_limit = limit + if query_limit == 0: + query_limit = 100 + + num_items = 0 + async for track in self._paginated_query( + f"/users/{user_id}/track_likes", params={"limit": str(query_limit)} + ): + num_items += 1 + if num_items >= limit > 0: + return + yield track["track"] + async def get_track_by_genre_recent(self, genre: str, limit: int = 10) -> dict[str, Any]: """Get track by genre recent. @@ -202,11 +223,13 @@ async def get_popular_tracks_user(self, user_id: str, limit: int = 12) -> dict[s # ---------------- PLAYLISTS ---------------- - async def get_account_playlists(self) -> AsyncGenerator[list[dict[str, Any]], None]: + async def get_account_playlists(self) -> AsyncGenerator[dict[str, Any], None]: """Get account playlists, albums and stations.""" # NOTE: This returns all track lists in reverse chronological order (most recent first). async for playlist in self._paginated_query("/me/library/all"): - yield playlist + # Skip some kind of system playlists, they have a different structure. + if "playlist" in playlist: + yield playlist async def get_playlist_details(self, playlist_id: str) -> dict[str, Any]: """:param playlist_id: playlist id""" @@ -337,7 +360,7 @@ async def _paginated_query( self, path: str, params: dict[str, str] | None = None, - ) -> AsyncGenerator[list[dict[str, Any]], None]: + ) -> AsyncGenerator[dict[str, Any], None]: """Paginate response queries. Soundcloud paginates its queries using the same pattern. As such, we leverage the @@ -365,7 +388,11 @@ async def _paginated_query( yield item # Handle case when results requested exceeds number of actual results. - if int(params.get("limit", 0)) and len(response["collection"]) < int(params["limit"]): + if ( + int(params.get("limit", 0)) + and len(response["collection"]) < int(params["limit"]) + and "next_href" not in response + ): return try: