soundcloud
soundcloud.py
Python wrapper for some of the internal v2 SoundCloud API (read/GET only methods). Does not require an API key.
Note: This is NOT the official SoundCloud developer API
SoundCloud is not accepting any more application registration requests 1 so I made this library so developers can use SoundCloud's internal API for their projects.
Installation
pip install soundcloud-v2
Documentation
https://7x11x13.xyz/soundcloud.py
Example
from soundcloud import SoundCloud
sc = SoundCloud(auth_token="auth_token")
assert sc.is_client_id_valid()
assert sc.is_auth_token_valid()
me = sc.get_user_by_username("7x11x13")
assert me.permalink == "7x11x13"
Notes on auth_token
Some methods require authentication in the form of an OAuth2 access token. You can find your token in your browser cookies for SoundCloud under the name "oauth_token". A new token will be generated each time you log out and log back in.
Notes on **kwargs
All API methods have a **kwargs argument which you can use to pass extra, undocumented
arguments to the SoundCloud v2 API in case I missed some parameter which you find useful.
If this is the case, feel free to create an issue or pull request to document the missing
argument.
License
1# ruff: noqa 2""" 3# soundcloud.py 4 5[](https://github.com/7x11x13/soundcloud.py/actions/workflows/ci.yml) 6[](https://coveralls.io/github/7x11x13/soundcloud.py?branch=main) 7[](https://pypi.org/project/soundcloud-v2/) 8[](https://pypi.org/project/soundcloud-v2/) 9[](https://pypi.org/project/soundcloud-v2/) 10[](https://pepy.tech/project/soundcloud-v2) 11 12Python wrapper for some of the internal v2 SoundCloud API (read/GET only methods). Does not require an API key. 13 14### Note: This is NOT the official [SoundCloud developer API](https://developers.soundcloud.com/docs/api/guide) 15 16SoundCloud is not accepting any more application registration requests [^1] so 17I made this library so developers can use SoundCloud's internal API for their projects. 18 19 20[^1]: https://docs.google.com/forms/d/e/1FAIpQLSfNxc82RJuzC0DnISat7n4H-G7IsPQIdaMpe202iiHZEoso9w/closedform 21 22## Installation 23 24```bash 25pip install soundcloud-v2 26``` 27 28## Documentation 29 30https://7x11x13.xyz/soundcloud.py 31 32## Example 33 34```python 35from soundcloud import SoundCloud 36 37sc = SoundCloud(auth_token="auth_token") 38assert sc.is_client_id_valid() 39assert sc.is_auth_token_valid() 40me = sc.get_user_by_username("7x11x13") 41assert me.permalink == "7x11x13" 42``` 43 44## Notes on `auth_token` 45Some methods require authentication in the form of an OAuth2 access token. 46You can find your token in your browser cookies for SoundCloud under the name "oauth_token". 47A new token will be generated each time you log out and log back in. 48 49## Notes on `**kwargs` 50All API methods have a `**kwargs` argument which you can use to pass extra, undocumented 51arguments to the SoundCloud v2 API in case I missed some parameter which you find useful. 52If this is the case, feel free to create an issue or pull request to document the missing 53argument. 54 55## License 56[MIT](https://choosealicense.com/licenses/mit/) 57 58""" 59 60from soundcloud.exceptions import * 61from soundcloud.exceptions import __all__ as ex_all 62from soundcloud.resource import * 63from soundcloud.resource import __all__ as res_all 64from soundcloud.soundcloud import * 65from soundcloud.soundcloud import __all__ as sc_all 66 67__version__ = "1.6.2" 68 69__all__ = sc_all + ex_all + res_all
76class SoundCloud: 77 """ 78 SoundCloud v2 API client 79 """ 80 81 _DEFAULT_USER_AGENT = ( 82 "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:93.0) Gecko/20100101 Firefox/93.0" 83 ) 84 _ASSETS_SCRIPTS_REGEX = re.compile( 85 r"src=\"(https:\/\/a-v2\.sndcdn\.com/assets/.*\.js)\"" 86 ) 87 _CLIENT_ID_REGEX = re.compile(r"client_id:\"([^\"]+)\"") 88 client_id: str 89 """SoundCloud client ID. Needed for all requests.""" 90 _user_agent: str 91 _auth_token: Optional[str] 92 _authorization: Optional[str] 93 94 def __init__( 95 self, 96 client_id: Optional[str] = None, 97 auth_token: Optional[str] = None, 98 user_agent: str = _DEFAULT_USER_AGENT, 99 ) -> None: 100 if not client_id: 101 client_id = self.generate_client_id() 102 103 self.client_id = client_id 104 self._user_agent = user_agent 105 self._auth_token = None 106 self._authorization = None 107 self.auth_token = auth_token 108 109 @property 110 def auth_token(self) -> Optional[str]: 111 """SoundCloud auth token. Only needed for some requests.""" 112 return self._auth_token 113 114 @auth_token.setter 115 def auth_token(self, new_auth_token: Optional[str]) -> None: 116 if new_auth_token is not None: 117 if new_auth_token.startswith("OAuth"): 118 new_auth_token = new_auth_token.split()[-1] 119 self._authorization = f"OAuth {new_auth_token}" if new_auth_token else None 120 self._auth_token = new_auth_token 121 122 @auth_token.deleter 123 def auth_token(self): 124 self.auth_token = None 125 126 def _get_default_headers(self) -> Dict[str, str]: 127 return {"User-Agent": self._user_agent} 128 129 @classmethod 130 def generate_client_id(cls) -> str: 131 """Generates a SoundCloud client ID 132 133 Raises: 134 ClientIDGenerationError: Client ID could not be generated. 135 136 Returns: 137 str: Valid client ID 138 """ 139 r = requests.get("https://soundcloud.com") 140 r.raise_for_status() 141 matches = cls._ASSETS_SCRIPTS_REGEX.findall(r.text) 142 if not matches: 143 raise ClientIDGenerationError("No asset scripts found") 144 for url in matches: 145 r = requests.get(url) 146 r.raise_for_status() 147 client_id = cls._CLIENT_ID_REGEX.search(r.text) 148 if client_id: 149 return client_id.group(1) 150 raise ClientIDGenerationError(f"Could not find client_id in script '{url}'") 151 152 def is_client_id_valid(self) -> bool: 153 """ 154 Checks if current client_id is valid 155 """ 156 try: 157 TrackRequest(self, track_id=1032303631, use_auth=False) 158 return True 159 except HTTPError as err: 160 if err.response.status_code == 401: 161 return False 162 else: 163 raise 164 165 def is_auth_token_valid(self) -> bool: 166 """ 167 Checks if current auth_token is valid 168 """ 169 try: 170 MeRequest(self) 171 return True 172 except HTTPError as err: 173 if err.response.status_code == 401: 174 return False 175 else: 176 raise 177 178 def get_me(self) -> Optional[User]: 179 """ 180 Gets the user associated with client's auth token 181 """ 182 return MeRequest(self) 183 184 def get_my_history(self, **kwargs) -> Generator[HistoryItem, None, None]: 185 """ 186 Returns the stream of recently listened tracks 187 for the client's auth token 188 """ 189 return MeHistoryRequest(self, **kwargs) 190 191 def get_my_stream(self, **kwargs) -> Generator[StreamItem, None, None]: 192 """ 193 Returns the stream of recent uploads/reposts 194 for the client's auth token 195 """ 196 return MeStreamRequest(self, **kwargs) 197 198 def resolve(self, url: str) -> Optional[SearchItem]: 199 """ 200 Returns the resource at the given URL if it 201 exists, otherwise return None 202 """ 203 return ResolveRequest(self, url=url) 204 205 def search(self, query: str, **kwargs) -> Generator[SearchItem, None, None]: 206 """ 207 Search for users, tracks, and playlists 208 """ 209 return SearchRequest(self, q=query, **kwargs) 210 211 def search_albums( 212 self, query: str, **kwargs 213 ) -> Generator[AlbumPlaylist, None, None]: 214 """ 215 Search for albums (not playlists) 216 """ 217 return SearchAlbumsRequest(self, q=query, **kwargs) 218 219 def search_playlists( 220 self, query: str, **kwargs 221 ) -> Generator[AlbumPlaylist, None, None]: 222 """ 223 Search for playlists 224 """ 225 return SearchPlaylistsRequest(self, q=query, **kwargs) 226 227 def search_tracks(self, query: str, **kwargs) -> Generator[Track, None, None]: 228 """ 229 Search for tracks 230 """ 231 return SearchTracksRequest(self, q=query, **kwargs) 232 233 def search_users(self, query: str, **kwargs) -> Generator[User, None, None]: 234 """ 235 Search for users 236 """ 237 return SearchUsersRequest(self, q=query, **kwargs) 238 239 def get_tag_tracks_recent(self, tag: str, **kwargs) -> Generator[Track, None, None]: 240 """ 241 Get most recent tracks for this tag 242 """ 243 return TagRecentTracksRequest(self, tag=tag, **kwargs) 244 245 def get_playlist(self, playlist_id: int) -> Optional[BasicAlbumPlaylist]: 246 """ 247 Returns the playlist with the given playlist_id. 248 If the ID is invalid, return None 249 """ 250 return PlaylistRequest(self, playlist_id=playlist_id) 251 252 def post_playlist( 253 self, sharing: Literal["private", "public"], title: str, tracks: List[int] 254 ) -> Optional[BasicAlbumPlaylist]: 255 """ 256 Create a new playlist 257 """ 258 body = {"playlist": {"sharing": sharing, "title": title, "tracks": tracks}} 259 return PostPlaylistRequest(self, body=body) 260 261 def delete_playlist(self, playlist_id: int) -> Optional[NoContentResponse]: 262 """ 263 Delete a playlist 264 """ 265 return DeletePlaylistRequest(self, playlist_id=playlist_id) 266 267 def get_playlist_likers( 268 self, playlist_id: int, **kwargs 269 ) -> Generator[User, None, None]: 270 """ 271 Get people who liked this playlist 272 """ 273 return PlaylistLikersRequest(self, playlist_id=playlist_id, **kwargs) 274 275 def get_playlist_reposters( 276 self, playlist_id: int, **kwargs 277 ) -> Generator[User, None, None]: 278 """ 279 Get people who reposted this playlist 280 """ 281 return PlaylistRepostersRequest(self, playlist_id=playlist_id, **kwargs) 282 283 def get_track(self, track_id: int) -> Optional[BasicTrack]: 284 """ 285 Returns the track with the given track_id. 286 If the ID is invalid, return None 287 """ 288 return TrackRequest(self, track_id=track_id) 289 290 def get_tracks( 291 self, 292 track_ids: List[int], 293 playlistId: Optional[int] = None, 294 playlistSecretToken: Optional[str] = None, 295 **kwargs, 296 ) -> List[BasicTrack]: 297 """ 298 Returns the tracks with the given track_ids. 299 Can be used to get track info for hidden tracks in a hidden playlist. 300 """ 301 if playlistId is not None: 302 kwargs["playlistId"] = playlistId 303 if playlistSecretToken is not None: 304 kwargs["playlistSecretToken"] = playlistSecretToken 305 return TracksRequest( 306 self, ids=",".join([str(id) for id in track_ids]), **kwargs 307 ) 308 309 def get_track_albums( 310 self, track_id: int, **kwargs 311 ) -> Generator[BasicAlbumPlaylist, None, None]: 312 """ 313 Get albums that this track is in 314 """ 315 return TrackAlbumsRequest(self, track_id=track_id, **kwargs) 316 317 def get_track_playlists( 318 self, track_id: int, **kwargs 319 ) -> Generator[BasicAlbumPlaylist, None, None]: 320 """ 321 Get playlists that this track is in 322 """ 323 return TrackPlaylistsRequest(self, track_id=track_id, **kwargs) 324 325 def get_track_comments( 326 self, track_id: int, threaded: int = 0, **kwargs 327 ) -> Generator[BasicComment, None, None]: 328 """ 329 Get comments on this track 330 """ 331 return TrackCommentsRequest( 332 self, track_id=track_id, threaded=threaded, **kwargs 333 ) 334 335 def get_track_comments_with_interactions( 336 self, track_id: int, threaded: int = 0, **kwargs 337 ) -> Generator[CommentWithInteractions, None, None]: 338 """ 339 Get comments on this track with interaction data. Requires authentication. 340 """ 341 track = self.get_track(track_id) 342 if not track: 343 return 344 track_urn = track.urn 345 creator_urn = track.user.urn 346 comments = self.get_track_comments(track_id, threaded, **kwargs) 347 while True: 348 chunk = list(itertools.islice(comments, 10)) 349 if not chunk: 350 return 351 comment_urns = [comment.self.urn for comment in chunk] 352 result = UserInteractionsRequest( 353 self, 354 UserInteractionsQueryParams( 355 creator_urn, 356 "sc:interactiontype:reaction", 357 track_urn, 358 comment_urns, 359 ), 360 ) 361 if not result: 362 return 363 comments_with_interactions = [] 364 for comment, user_interactions, creator_interactions in zip( 365 chunk, result.user, result.creator 366 ): 367 assert user_interactions.interactionCounts is not None 368 likes = list( 369 filter( 370 lambda x: x.interactionTypeValueUrn 371 == "sc:interactiontypevalue:like", 372 user_interactions.interactionCounts, 373 ) 374 ) 375 num_likes = likes[0].count if likes else 0 376 comments_with_interactions.append( 377 CommentWithInteractions( 378 comment=comment, 379 likes=num_likes or 0, 380 liked_by_creator=creator_interactions.userInteraction 381 == "sc:interactiontypevalue:like", 382 liked_by_user=user_interactions.userInteraction 383 == "sc:interactiontypevalue:like", 384 ) 385 ) 386 yield from comments_with_interactions 387 388 def get_track_likers(self, track_id: int, **kwargs) -> Generator[User, None, None]: 389 """ 390 Get users who liked this track 391 """ 392 return TrackLikersRequest(self, track_id=track_id, **kwargs) 393 394 def get_track_related( 395 self, track_id: int, **kwargs 396 ) -> Generator[BasicTrack, None, None]: 397 """ 398 Get related tracks 399 """ 400 return TrackRelatedRequest(self, track_id=track_id, **kwargs) 401 402 def get_track_reposters( 403 self, track_id: int, **kwargs 404 ) -> Generator[User, None, None]: 405 """ 406 Get users who reposted this track 407 """ 408 return TrackRepostersRequest(self, track_id=track_id, **kwargs) 409 410 def get_track_original_download( 411 self, track_id: int, token: Optional[str] = None 412 ) -> Optional[str]: 413 """ 414 Get track original download link. If track is private, 415 requires secret token to be provided (last part of secret URL). 416 Requires authentication. 417 """ 418 if token is not None: 419 download = TrackOriginalDownloadRequest( 420 self, track_id=track_id, secret_token=token 421 ) 422 else: 423 download = TrackOriginalDownloadRequest(self, track_id=track_id) 424 if download is None: 425 return None 426 else: 427 return download.redirectUri 428 429 def get_user(self, user_id: int) -> Optional[User]: 430 """ 431 Returns the user with the given user_id. 432 If the ID is invalid, return None 433 """ 434 return UserRequest(self, user_id=user_id) 435 436 def get_user_by_username(self, username: str) -> Optional[User]: 437 """ 438 Returns the user with the given username. 439 If the username is invalid, return None 440 """ 441 resource = self.resolve(f"https://soundcloud.com/{username}") 442 if resource and isinstance(resource, User): 443 return resource 444 else: 445 return None 446 447 def get_user_comments( 448 self, user_id: int, **kwargs 449 ) -> Generator[Comment, None, None]: 450 """ 451 Get comments by this user 452 """ 453 return UserCommentsRequest(self, user_id=user_id, **kwargs) 454 455 def get_conversation_messages( 456 self, user_id: int, conversation_id: int, **kwargs 457 ) -> Generator[Message, None, None]: 458 """ 459 Get messages in this conversation 460 """ 461 return UserConversationMessagesRequest( 462 self, user_id=user_id, conversation_id=conversation_id, **kwargs 463 ) 464 465 def get_conversations( 466 self, user_id: int, **kwargs 467 ) -> Generator[Conversation, None, None]: 468 """ 469 Get conversations including this user 470 """ 471 return UserConversationsRequest(self, user_id=user_id, **kwargs) 472 473 def get_unread_conversations( 474 self, user_id: int, **kwargs 475 ) -> Generator[Conversation, None, None]: 476 """ 477 Get conversations unread by this user 478 """ 479 return UserConversationsUnreadRequest(self, user_id=user_id, **kwargs) 480 481 def get_user_emails( 482 self, user_id: int, **kwargs 483 ) -> Generator[UserEmail, None, None]: 484 """ 485 Get user's email addresses. Requires authentication. 486 """ 487 return UserEmailsRequest(self, user_id=user_id, **kwargs) 488 489 def get_user_featured_profiles( 490 self, user_id: int, **kwargs 491 ) -> Generator[User, None, None]: 492 """ 493 Get profiles featured by this user 494 """ 495 return UserFeaturedProfilesRequest(self, user_id=user_id, **kwargs) 496 497 def get_user_followers(self, user_id: int, **kwargs) -> Generator[User, None, None]: 498 """ 499 Get user's followers 500 """ 501 return UserFollowersRequest(self, user_id=user_id, **kwargs) 502 503 def get_user_following(self, user_id: int, **kwargs) -> Generator[User, None, None]: 504 """ 505 Get users this user is following 506 """ 507 return UserFollowingsRequest(self, user_id=user_id, **kwargs) 508 509 def get_user_likes(self, user_id: int, **kwargs) -> Generator[Like, None, None]: 510 """ 511 Get likes by this user 512 """ 513 return UserLikesRequest(self, user_id=user_id, **kwargs) 514 515 def get_user_related_artists( 516 self, user_id: int, **kwargs 517 ) -> Generator[User, None, None]: 518 """ 519 Get artists related to this user 520 """ 521 return UserRelatedArtistsRequest(self, user_id=user_id, **kwargs) 522 523 def get_user_reposts( 524 self, user_id: int, **kwargs 525 ) -> Generator[RepostItem, None, None]: 526 """ 527 Get reposts by this user 528 """ 529 return UserRepostsRequest(self, user_id=user_id, **kwargs) 530 531 def get_user_stream( 532 self, user_id: int, **kwargs 533 ) -> Generator[StreamItem, None, None]: 534 """ 535 Returns generator of track uploaded by given user and 536 reposts by this user 537 """ 538 return UserStreamRequest(self, user_id=user_id, **kwargs) 539 540 def get_user_tracks( 541 self, user_id: int, **kwargs 542 ) -> Generator[BasicTrack, None, None]: 543 """ 544 Get tracks uploaded by this user 545 """ 546 return UserTracksRequest(self, user_id=user_id, **kwargs) 547 548 def get_user_popular_tracks( 549 self, user_id: int, **kwargs 550 ) -> Generator[BasicTrack, None, None]: 551 """ 552 Get popular tracks uploaded by this user 553 """ 554 return UserToptracksRequest(self, user_id=user_id, **kwargs) 555 556 def get_user_albums( 557 self, user_id: int, **kwargs 558 ) -> Generator[BasicAlbumPlaylist, None, None]: 559 """ 560 Get albums uploaded by this user 561 """ 562 return UserAlbumsRequest(self, user_id=user_id, **kwargs) 563 564 def get_user_playlists( 565 self, user_id: int, **kwargs 566 ) -> Generator[BasicAlbumPlaylist, None, None]: 567 """ 568 Get playlists uploaded by this user 569 """ 570 return UserPlaylistsRequest(self, user_id=user_id, **kwargs) 571 572 def get_user_links(self, user_urn: str, **kwargs) -> List[WebProfile]: 573 """ 574 Get links in this user's description 575 """ 576 return UserWebProfilesRequest(self, user_urn=user_urn, **kwargs)
SoundCloud v2 API client
94 def __init__( 95 self, 96 client_id: Optional[str] = None, 97 auth_token: Optional[str] = None, 98 user_agent: str = _DEFAULT_USER_AGENT, 99 ) -> None: 100 if not client_id: 101 client_id = self.generate_client_id() 102 103 self.client_id = client_id 104 self._user_agent = user_agent 105 self._auth_token = None 106 self._authorization = None 107 self.auth_token = auth_token
109 @property 110 def auth_token(self) -> Optional[str]: 111 """SoundCloud auth token. Only needed for some requests.""" 112 return self._auth_token
SoundCloud auth token. Only needed for some requests.
129 @classmethod 130 def generate_client_id(cls) -> str: 131 """Generates a SoundCloud client ID 132 133 Raises: 134 ClientIDGenerationError: Client ID could not be generated. 135 136 Returns: 137 str: Valid client ID 138 """ 139 r = requests.get("https://soundcloud.com") 140 r.raise_for_status() 141 matches = cls._ASSETS_SCRIPTS_REGEX.findall(r.text) 142 if not matches: 143 raise ClientIDGenerationError("No asset scripts found") 144 for url in matches: 145 r = requests.get(url) 146 r.raise_for_status() 147 client_id = cls._CLIENT_ID_REGEX.search(r.text) 148 if client_id: 149 return client_id.group(1) 150 raise ClientIDGenerationError(f"Could not find client_id in script '{url}'")
Generates a SoundCloud client ID
Raises:
- ClientIDGenerationError: Client ID could not be generated.
Returns:
str: Valid client ID
152 def is_client_id_valid(self) -> bool: 153 """ 154 Checks if current client_id is valid 155 """ 156 try: 157 TrackRequest(self, track_id=1032303631, use_auth=False) 158 return True 159 except HTTPError as err: 160 if err.response.status_code == 401: 161 return False 162 else: 163 raise
Checks if current client_id is valid
165 def is_auth_token_valid(self) -> bool: 166 """ 167 Checks if current auth_token is valid 168 """ 169 try: 170 MeRequest(self) 171 return True 172 except HTTPError as err: 173 if err.response.status_code == 401: 174 return False 175 else: 176 raise
Checks if current auth_token is valid
178 def get_me(self) -> Optional[User]: 179 """ 180 Gets the user associated with client's auth token 181 """ 182 return MeRequest(self)
Gets the user associated with client's auth token
184 def get_my_history(self, **kwargs) -> Generator[HistoryItem, None, None]: 185 """ 186 Returns the stream of recently listened tracks 187 for the client's auth token 188 """ 189 return MeHistoryRequest(self, **kwargs)
Returns the stream of recently listened tracks for the client's auth token
191 def get_my_stream(self, **kwargs) -> Generator[StreamItem, None, None]: 192 """ 193 Returns the stream of recent uploads/reposts 194 for the client's auth token 195 """ 196 return MeStreamRequest(self, **kwargs)
Returns the stream of recent uploads/reposts for the client's auth token
198 def resolve(self, url: str) -> Optional[SearchItem]: 199 """ 200 Returns the resource at the given URL if it 201 exists, otherwise return None 202 """ 203 return ResolveRequest(self, url=url)
Returns the resource at the given URL if it exists, otherwise return None
205 def search(self, query: str, **kwargs) -> Generator[SearchItem, None, None]: 206 """ 207 Search for users, tracks, and playlists 208 """ 209 return SearchRequest(self, q=query, **kwargs)
Search for users, tracks, and playlists
211 def search_albums( 212 self, query: str, **kwargs 213 ) -> Generator[AlbumPlaylist, None, None]: 214 """ 215 Search for albums (not playlists) 216 """ 217 return SearchAlbumsRequest(self, q=query, **kwargs)
Search for albums (not playlists)
219 def search_playlists( 220 self, query: str, **kwargs 221 ) -> Generator[AlbumPlaylist, None, None]: 222 """ 223 Search for playlists 224 """ 225 return SearchPlaylistsRequest(self, q=query, **kwargs)
Search for playlists
227 def search_tracks(self, query: str, **kwargs) -> Generator[Track, None, None]: 228 """ 229 Search for tracks 230 """ 231 return SearchTracksRequest(self, q=query, **kwargs)
Search for tracks
233 def search_users(self, query: str, **kwargs) -> Generator[User, None, None]: 234 """ 235 Search for users 236 """ 237 return SearchUsersRequest(self, q=query, **kwargs)
Search for users
239 def get_tag_tracks_recent(self, tag: str, **kwargs) -> Generator[Track, None, None]: 240 """ 241 Get most recent tracks for this tag 242 """ 243 return TagRecentTracksRequest(self, tag=tag, **kwargs)
Get most recent tracks for this tag
245 def get_playlist(self, playlist_id: int) -> Optional[BasicAlbumPlaylist]: 246 """ 247 Returns the playlist with the given playlist_id. 248 If the ID is invalid, return None 249 """ 250 return PlaylistRequest(self, playlist_id=playlist_id)
Returns the playlist with the given playlist_id. If the ID is invalid, return None
252 def post_playlist( 253 self, sharing: Literal["private", "public"], title: str, tracks: List[int] 254 ) -> Optional[BasicAlbumPlaylist]: 255 """ 256 Create a new playlist 257 """ 258 body = {"playlist": {"sharing": sharing, "title": title, "tracks": tracks}} 259 return PostPlaylistRequest(self, body=body)
Create a new playlist
261 def delete_playlist(self, playlist_id: int) -> Optional[NoContentResponse]: 262 """ 263 Delete a playlist 264 """ 265 return DeletePlaylistRequest(self, playlist_id=playlist_id)
Delete a playlist
267 def get_playlist_likers( 268 self, playlist_id: int, **kwargs 269 ) -> Generator[User, None, None]: 270 """ 271 Get people who liked this playlist 272 """ 273 return PlaylistLikersRequest(self, playlist_id=playlist_id, **kwargs)
Get people who liked this playlist
275 def get_playlist_reposters( 276 self, playlist_id: int, **kwargs 277 ) -> Generator[User, None, None]: 278 """ 279 Get people who reposted this playlist 280 """ 281 return PlaylistRepostersRequest(self, playlist_id=playlist_id, **kwargs)
Get people who reposted this playlist
283 def get_track(self, track_id: int) -> Optional[BasicTrack]: 284 """ 285 Returns the track with the given track_id. 286 If the ID is invalid, return None 287 """ 288 return TrackRequest(self, track_id=track_id)
Returns the track with the given track_id. If the ID is invalid, return None
290 def get_tracks( 291 self, 292 track_ids: List[int], 293 playlistId: Optional[int] = None, 294 playlistSecretToken: Optional[str] = None, 295 **kwargs, 296 ) -> List[BasicTrack]: 297 """ 298 Returns the tracks with the given track_ids. 299 Can be used to get track info for hidden tracks in a hidden playlist. 300 """ 301 if playlistId is not None: 302 kwargs["playlistId"] = playlistId 303 if playlistSecretToken is not None: 304 kwargs["playlistSecretToken"] = playlistSecretToken 305 return TracksRequest( 306 self, ids=",".join([str(id) for id in track_ids]), **kwargs 307 )
Returns the tracks with the given track_ids. Can be used to get track info for hidden tracks in a hidden playlist.
309 def get_track_albums( 310 self, track_id: int, **kwargs 311 ) -> Generator[BasicAlbumPlaylist, None, None]: 312 """ 313 Get albums that this track is in 314 """ 315 return TrackAlbumsRequest(self, track_id=track_id, **kwargs)
Get albums that this track is in
317 def get_track_playlists( 318 self, track_id: int, **kwargs 319 ) -> Generator[BasicAlbumPlaylist, None, None]: 320 """ 321 Get playlists that this track is in 322 """ 323 return TrackPlaylistsRequest(self, track_id=track_id, **kwargs)
Get playlists that this track is in
325 def get_track_comments( 326 self, track_id: int, threaded: int = 0, **kwargs 327 ) -> Generator[BasicComment, None, None]: 328 """ 329 Get comments on this track 330 """ 331 return TrackCommentsRequest( 332 self, track_id=track_id, threaded=threaded, **kwargs 333 )
Get comments on this track
335 def get_track_comments_with_interactions( 336 self, track_id: int, threaded: int = 0, **kwargs 337 ) -> Generator[CommentWithInteractions, None, None]: 338 """ 339 Get comments on this track with interaction data. Requires authentication. 340 """ 341 track = self.get_track(track_id) 342 if not track: 343 return 344 track_urn = track.urn 345 creator_urn = track.user.urn 346 comments = self.get_track_comments(track_id, threaded, **kwargs) 347 while True: 348 chunk = list(itertools.islice(comments, 10)) 349 if not chunk: 350 return 351 comment_urns = [comment.self.urn for comment in chunk] 352 result = UserInteractionsRequest( 353 self, 354 UserInteractionsQueryParams( 355 creator_urn, 356 "sc:interactiontype:reaction", 357 track_urn, 358 comment_urns, 359 ), 360 ) 361 if not result: 362 return 363 comments_with_interactions = [] 364 for comment, user_interactions, creator_interactions in zip( 365 chunk, result.user, result.creator 366 ): 367 assert user_interactions.interactionCounts is not None 368 likes = list( 369 filter( 370 lambda x: x.interactionTypeValueUrn 371 == "sc:interactiontypevalue:like", 372 user_interactions.interactionCounts, 373 ) 374 ) 375 num_likes = likes[0].count if likes else 0 376 comments_with_interactions.append( 377 CommentWithInteractions( 378 comment=comment, 379 likes=num_likes or 0, 380 liked_by_creator=creator_interactions.userInteraction 381 == "sc:interactiontypevalue:like", 382 liked_by_user=user_interactions.userInteraction 383 == "sc:interactiontypevalue:like", 384 ) 385 ) 386 yield from comments_with_interactions
Get comments on this track with interaction data. Requires authentication.
388 def get_track_likers(self, track_id: int, **kwargs) -> Generator[User, None, None]: 389 """ 390 Get users who liked this track 391 """ 392 return TrackLikersRequest(self, track_id=track_id, **kwargs)
Get users who liked this track
402 def get_track_reposters( 403 self, track_id: int, **kwargs 404 ) -> Generator[User, None, None]: 405 """ 406 Get users who reposted this track 407 """ 408 return TrackRepostersRequest(self, track_id=track_id, **kwargs)
Get users who reposted this track
410 def get_track_original_download( 411 self, track_id: int, token: Optional[str] = None 412 ) -> Optional[str]: 413 """ 414 Get track original download link. If track is private, 415 requires secret token to be provided (last part of secret URL). 416 Requires authentication. 417 """ 418 if token is not None: 419 download = TrackOriginalDownloadRequest( 420 self, track_id=track_id, secret_token=token 421 ) 422 else: 423 download = TrackOriginalDownloadRequest(self, track_id=track_id) 424 if download is None: 425 return None 426 else: 427 return download.redirectUri
Get track original download link. If track is private, requires secret token to be provided (last part of secret URL). Requires authentication.
429 def get_user(self, user_id: int) -> Optional[User]: 430 """ 431 Returns the user with the given user_id. 432 If the ID is invalid, return None 433 """ 434 return UserRequest(self, user_id=user_id)
Returns the user with the given user_id. If the ID is invalid, return None
436 def get_user_by_username(self, username: str) -> Optional[User]: 437 """ 438 Returns the user with the given username. 439 If the username is invalid, return None 440 """ 441 resource = self.resolve(f"https://soundcloud.com/{username}") 442 if resource and isinstance(resource, User): 443 return resource 444 else: 445 return None
Returns the user with the given username. If the username is invalid, return None
447 def get_user_comments( 448 self, user_id: int, **kwargs 449 ) -> Generator[Comment, None, None]: 450 """ 451 Get comments by this user 452 """ 453 return UserCommentsRequest(self, user_id=user_id, **kwargs)
Get comments by this user
455 def get_conversation_messages( 456 self, user_id: int, conversation_id: int, **kwargs 457 ) -> Generator[Message, None, None]: 458 """ 459 Get messages in this conversation 460 """ 461 return UserConversationMessagesRequest( 462 self, user_id=user_id, conversation_id=conversation_id, **kwargs 463 )
Get messages in this conversation
465 def get_conversations( 466 self, user_id: int, **kwargs 467 ) -> Generator[Conversation, None, None]: 468 """ 469 Get conversations including this user 470 """ 471 return UserConversationsRequest(self, user_id=user_id, **kwargs)
Get conversations including this user
473 def get_unread_conversations( 474 self, user_id: int, **kwargs 475 ) -> Generator[Conversation, None, None]: 476 """ 477 Get conversations unread by this user 478 """ 479 return UserConversationsUnreadRequest(self, user_id=user_id, **kwargs)
Get conversations unread by this user
481 def get_user_emails( 482 self, user_id: int, **kwargs 483 ) -> Generator[UserEmail, None, None]: 484 """ 485 Get user's email addresses. Requires authentication. 486 """ 487 return UserEmailsRequest(self, user_id=user_id, **kwargs)
Get user's email addresses. Requires authentication.
489 def get_user_featured_profiles( 490 self, user_id: int, **kwargs 491 ) -> Generator[User, None, None]: 492 """ 493 Get profiles featured by this user 494 """ 495 return UserFeaturedProfilesRequest(self, user_id=user_id, **kwargs)
Get profiles featured by this user
497 def get_user_followers(self, user_id: int, **kwargs) -> Generator[User, None, None]: 498 """ 499 Get user's followers 500 """ 501 return UserFollowersRequest(self, user_id=user_id, **kwargs)
Get user's followers
503 def get_user_following(self, user_id: int, **kwargs) -> Generator[User, None, None]: 504 """ 505 Get users this user is following 506 """ 507 return UserFollowingsRequest(self, user_id=user_id, **kwargs)
Get users this user is following
509 def get_user_likes(self, user_id: int, **kwargs) -> Generator[Like, None, None]: 510 """ 511 Get likes by this user 512 """ 513 return UserLikesRequest(self, user_id=user_id, **kwargs)
Get likes by this user
523 def get_user_reposts( 524 self, user_id: int, **kwargs 525 ) -> Generator[RepostItem, None, None]: 526 """ 527 Get reposts by this user 528 """ 529 return UserRepostsRequest(self, user_id=user_id, **kwargs)
Get reposts by this user
531 def get_user_stream( 532 self, user_id: int, **kwargs 533 ) -> Generator[StreamItem, None, None]: 534 """ 535 Returns generator of track uploaded by given user and 536 reposts by this user 537 """ 538 return UserStreamRequest(self, user_id=user_id, **kwargs)
Returns generator of track uploaded by given user and reposts by this user
540 def get_user_tracks( 541 self, user_id: int, **kwargs 542 ) -> Generator[BasicTrack, None, None]: 543 """ 544 Get tracks uploaded by this user 545 """ 546 return UserTracksRequest(self, user_id=user_id, **kwargs)
Get tracks uploaded by this user
548 def get_user_popular_tracks( 549 self, user_id: int, **kwargs 550 ) -> Generator[BasicTrack, None, None]: 551 """ 552 Get popular tracks uploaded by this user 553 """ 554 return UserToptracksRequest(self, user_id=user_id, **kwargs)
Get popular tracks uploaded by this user
556 def get_user_albums( 557 self, user_id: int, **kwargs 558 ) -> Generator[BasicAlbumPlaylist, None, None]: 559 """ 560 Get albums uploaded by this user 561 """ 562 return UserAlbumsRequest(self, user_id=user_id, **kwargs)
Get albums uploaded by this user
564 def get_user_playlists( 565 self, user_id: int, **kwargs 566 ) -> Generator[BasicAlbumPlaylist, None, None]: 567 """ 568 Get playlists uploaded by this user 569 """ 570 return UserPlaylistsRequest(self, user_id=user_id, **kwargs)
Get playlists uploaded by this user
2class ClientIDGenerationError(Exception): 3 """ 4 Raised when a client ID could not be dynamically generated. 5 """
Raised when a client ID could not be dynamically generated.
16@dataclass 17class BasicComment(BaseData): 18 """Comment without a specified track""" 19 20 kind: str 21 id: int 22 body: str 23 created_at: datetime.datetime 24 timestamp: Optional[int] 25 track_id: int 26 user_id: int 27 self: CommentSelf 28 user: BasicUser
Comment without a specified track
31@dataclass 32class Comment(BasicComment): 33 """Comment with a specified track""" 34 35 track: CommentTrack
Comment with a specified track
25@dataclass 26class CommentWithInteractions(BaseData): 27 comment: BasicComment 28 likes: int 29 """Number of likes on comment""" 30 31 liked_by_creator: bool 32 """Whether comment was liked by the track creator""" 33 34 liked_by_user: bool 35 """Whether the current logged in user liked the comment"""
11@dataclass 12class Conversation(BaseData): 13 """DM conversation between two users""" 14 15 id: str 16 last_message: Message 17 read: bool 18 started_at: datetime.datetime 19 summary: str 20 users: Tuple[Union[BasicUser, MissingUser], ...]
DM conversation between two users
7@dataclass 8class OriginalDownload(BaseData): 9 """Contains a download link for a track""" 10 11 redirectUri: str
Contains a download link for a track
8@dataclass 9class HistoryItem(BaseData): 10 """Item in user's listen history""" 11 12 played_at: int 13 track: BasicTrack 14 track_id: int
Item in user's listen history
23@dataclass 24class PlaylistLike(BaseLike): 25 """Like on a playlist""" 26 27 playlist: AlbumPlaylistNoTracks
Like on a playlist
Like on a track
10@dataclass 11class Message(BaseData): 12 """Single DM between two users""" 13 14 content: str 15 conversation_id: str 16 sender: Union[BasicUser, MissingUser] 17 sender_urn: str 18 sender_type: str 19 sent_at: datetime.datetime
Single DM between two users
22@dataclass 23class AlbumPlaylist(BaseAlbumPlaylist): 24 """Playlist or album with full user info""" 25 26 user: User
Playlist or album with full user info
36@dataclass 37class AlbumPlaylistNoTracks(BaseData): 38 """Playlist or album with no track info""" 39 40 artwork_url: Optional[str] 41 created_at: datetime.datetime 42 duration: int 43 id: int 44 kind: str 45 last_modified: datetime.datetime 46 likes_count: Optional[int] 47 managed_by_feeds: bool 48 permalink: str 49 permalink_url: str 50 public: bool 51 reposts_count: Optional[int] 52 secret_token: Optional[str] 53 sharing: str 54 title: str 55 track_count: int 56 uri: str 57 user_id: int 58 set_type: str 59 is_album: bool 60 published_at: Optional[datetime.datetime] 61 release_date: Optional[str] 62 display_date: datetime.datetime 63 user: BasicUser
Playlist or album with no track info
29@dataclass 30class BasicAlbumPlaylist(BaseAlbumPlaylist): 31 """Playlist or album with partial user info""" 32 33 user: BasicUser
Playlist or album with partial user info
47@dataclass 48class PlaylistStreamItem(BaseStreamItem): 49 """Album or playlist post in user's feed""" 50 51 playlist: BasicAlbumPlaylist
Album or playlist post in user's feed
54@dataclass 55class PlaylistStreamRepostItem(BaseStreamRepostItem): 56 """Album or playlist repost in user's feed""" 57 58 playlist: BasicAlbumPlaylist
Album or playlist repost in user's feed
33@dataclass 34class TrackStreamItem(BaseStreamItem): 35 """Track post in user's feed""" 36 37 track: BasicTrack
Track post in user's feed
40@dataclass 41class TrackStreamRepostItem(BaseStreamRepostItem): 42 """Track repost in user's feed""" 43 44 track: BasicTrack
Track repost in user's feed
80@dataclass 81class BasicTrack(BaseTrack): 82 """Track with partial user info""" 83 84 user: BasicUser
Track with partial user info
12@dataclass 13class Format(BaseData): 14 """Track file format""" 15 16 protocol: str 17 mime_type: str
Track file format
32@dataclass 33class Media(BaseData): 34 """List of available transcodings""" 35 36 transcodings: Tuple[Transcoding, ...]
List of available transcodings
87@dataclass 88class MiniTrack(BaseData): 89 """Track with minimal info""" 90 91 id: int 92 kind: str 93 monetization_model: str 94 policy: str
Track with minimal info
39@dataclass 40class PublisherMetadata(BaseData): 41 """Publisher info""" 42 43 id: str 44 urn: str 45 contains_music: bool
Publisher info
Track with full user info
20@dataclass 21class Transcoding(BaseData): 22 """Available transcoding for track""" 23 24 url: str 25 preset: str 26 duration: int 27 snipped: bool 28 format: Format 29 quality: str
Available transcoding for track
97@dataclass 98class CommentTrack(BaseData): 99 """Track with partial info""" 100 101 artwork_url: Optional[str] 102 caption: Optional[str] 103 id: int 104 kind: str 105 last_modified: datetime.datetime 106 permalink: str 107 permalink_url: str 108 public: bool 109 secret_token: Optional[str] 110 sharing: str 111 title: str 112 uri: str 113 urn: str 114 user_id: int 115 full_duration: int 116 duration: int 117 display_date: datetime.datetime 118 media: Media 119 station_urn: Optional[str] 120 station_permalink: Optional[str] 121 track_authorization: str 122 monetization_model: str 123 policy: str 124 user: BasicUser
Track with partial info
20@dataclass 21class Badges(BaseData): 22 """User badges""" 23 24 pro: bool 25 pro_unlimited: bool 26 verified: bool
User badges
29@dataclass 30class BasicUser(BaseData): 31 """User with partial information""" 32 33 avatar_url: str 34 first_name: str 35 followers_count: int 36 full_name: str 37 id: int 38 kind: str 39 last_modified: datetime.datetime 40 last_name: str 41 permalink: str 42 permalink_url: str 43 uri: str 44 urn: str 45 username: str 46 verified: bool 47 city: Optional[str] 48 country_code: Optional[str] 49 badges: Badges 50 station_urn: Optional[str] 51 station_permalink: Optional[str]
User with partial information
54@dataclass 55class User(BasicUser): 56 """User with full information""" 57 58 comments_count: int 59 created_at: Optional[datetime.datetime] 60 creator_subscriptions: Tuple[CreatorSubscription, ...] 61 creator_subscription: CreatorSubscription 62 description: Optional[str] 63 followings_count: int 64 groups_count: int 65 likes_count: Optional[int] 66 playlist_likes_count: Optional[int] 67 playlist_count: int 68 reposts_count: Optional[int] 69 track_count: int 70 visuals: Optional[Visuals]
User with full information
87@dataclass 88class UserEmail(BaseData): 89 """Email address associated with a user""" 90 91 address: str 92 confirmed: bool 93 id: int 94 kind: str 95 last_modified: datetime.datetime 96 primary: bool 97 urn: str 98 user_id: str
Email address associated with a user
7@dataclass 8class NoContentResponse(BaseData): 9 """Response with no content, mainly for DELETE requests""" 10 11 status_code: int
Response with no content, mainly for DELETE requests