from __future__ import annotations
from typing import TYPE_CHECKING
import uuid
from ..._shared import TTLCache
from ._shared import TIDALResourceAPI
if TYPE_CHECKING:
from typing import Any
from ...._types import Collection
[docs]
class UsersAPI(TIDALResourceAPI):
"""
User Collections, User Recommendations, and Users API endpoints for
the TIDAL API.
.. important::
This class is managed by :class:`~minim.api.tidal.TIDALAPIClient`
and should not be instantiated directly.
"""
_COLLECTION_RELATIONSHIPS = {
"albums",
"artists",
"owners",
"playlists",
"tracks",
"videos",
}
_COLLECTION_RESOURCE_RELATIONSHIPS = {"items", "owners"}
_RECOMMENDATION_RELATIONSHIPS = {
"discoveryMixes",
"myMixes",
"newArrivalMixes",
}
__slots__ = ()
@classmethod
def _prepare_resource_identifiers(
cls,
resource_type: str,
resource_ids: int
| str
| dict[str, int | str]
| Collection[int | str | dict[str, int | str]],
/,
*,
recursive: bool = True,
) -> list[dict[str, str]]:
"""
Validate, normalize, and prepare a collection of resource
identifiers.
Parameters
----------
resource_type : str; positional-only
Resource type.
**Valid values**: :code:`"albums"`, :code:`"artists"`,
:code:`"owners"`, :code:`"playlists"`, :code:`"tracks"`,
:code:`"videos"`.
resource_ids : str, dict[str, int | str], or \
Collection[int | str | dict[str, int | str]]; positional-only
TIDAL IDs or UUIDs of the items.
Returns
-------
resource_identifiers : list[dict[str, str]]
List of resource identifiers.
"""
if isinstance(resource_ids, dict):
resource_id = resource_ids.get("id")
if resource_ids.get("type") != resource_type:
raise ValueError(
f"The item with ID {resource_id!r} is not a "
f"{resource_type[:-1]}."
)
elif isinstance(resource_ids, int | str):
resource_id = resource_ids
else:
num_resources = len(resource_ids)
if not num_resources:
raise ValueError(
f"At least one {resource_type[:-1]} must be specified."
)
if num_resources > 20:
raise ValueError(
f"A maximum of 20 {resource_type} can be sent in a request."
)
return [
cls._prepare_resource_identifiers(resource_id, recursive=False)
for resource_id in resource_ids
]
resource_identifier = {"id": str(resource_id), "type": resource_type}
if recursive:
return [resource_identifier]
return resource_identifier
def _get_user_collection_relationship(
self,
relationship: str,
/,
*,
collection_id: str | None = None,
user_id: int | str | None = None,
country_code: str | None = None,
locale: str | None = None,
include_metadata: bool = False,
cursor: str | None = None,
sort_by: str | None = None,
descending: bool | None = None,
sort_fields: set[str] | None = None,
params: dict[str, Any] | None = None,
) -> dict[str, Any]:
"""
Get TIDAL catalog information for items of a resource type in a
user's collection.
Parameters
----------
relationship : str; positional-only
Related resource type.
**Valid values**: :code:`"albums"`, :code:`"artists"`,
:code:`"owners"`, :code:`"playlists"`, :code:`"tracks"`,
:code:`"videos"`.
collection_id : str; keyword-only; optional
TIDAL ID of the user collection. If authenticated,
:code:`"me"` can be used in lieu of a TIDAL ID for the
current user's collection. If not specified, :code:`"me"` is
used.
user_id : int or str; keyword-only; optional
TIDAL ID of the user. If not specified, the TIDAL ID of
current user is used.
country_code : str; keyword-only; optional
ISO 3166-1 alpha-2 country code.
**Example**: :code:`"US"`.
locale : str; keyword-only; optional
IETF BCP 47 language tag.
include_metadata : bool; keyword-only; default: :code:`False`
Whether to include TIDAL metadata for the specified
resource.
cursor : str; keyword-only; optional
Cursor for fetching the next page of results.
**Example**: :code:`"3nI1Esi"`.
sort_by : str; keyword-only; optional
Field to sort the returned items by.
descending : bool; keyword-only; optional
Whether to sort in descending order.
**API default**: :code:`False`.
sort_fields : set[str]; keyword-only; optional
Valid fields to sort by.
params : dict[str, Any]; keyword-only; optional
Query parameters to include in the request. If not provided,
an empty dictionary will be created.
.. note::
This `dict` is mutated in-place.
Returns
-------
resource : dict[str, Any]
TIDAL metadata for the specified resource.
"""
if params is None:
params = {}
if user_id is None:
id_ = collection_id
has_user_id = False
else:
if collection_id is not None:
raise ValueError(
"At most one of 'collection_id' or 'user_id' can "
"be provided."
)
id_ = user_id
has_user_id = True
if has_user_id:
if sort_by is not None:
self._process_sort(
sort_by,
descending=descending,
prefix=f"{relationship}.",
sort_fields=sort_fields,
params=params,
)
return self._get_resource_relationship(
"userCollections",
id_ or self._client._my_profile["id"],
relationship,
country_code=country_code,
locale=locale,
include_metadata=include_metadata,
cursor=cursor,
params=params,
)
if sort_by is not None:
self._process_sort(
sort_by,
descending=descending,
prefix="",
sort_fields=sort_fields,
params=params,
)
return self._get_resource_relationship(
f"userCollection{relationship.capitalize()}",
id_ or "me",
"items",
country_code=country_code,
locale=locale,
include_metadata=include_metadata,
cursor=cursor,
params=params,
)
def _modify_user_collection_resources(
self,
method: str,
resource_type: str,
resource_ids: str
| dict[str, int | str]
| Collection[int | str | dict[str, int | str]],
/,
*,
collection_id: str | None = None,
user_id: int | str | None = None,
country_code: str | None = None,
) -> None:
"""
Add/remove items of a resource type to/from a user's collection.
Parameters
----------
method : str; positional-only
HTTP method.
**Valid values**: :code:`"POST"`, :code:`"DELETE"`.
resource_type : str; positional-only
Resource type.
**Valid values**: :code:`"albums"`, :code:`"artists"`,
:code:`"owners"`, :code:`"playlists"`, :code:`"tracks"`,
:code:`"videos"`.
resource_ids : str, dict[str, int | str], or \
Collection[int | str | dict[str, int | str]]; positional-only
TIDAL IDs, UUIDs, or resource identifiers of the items.
collection_id : str; keyword-only; optional
TIDAL ID of the user collection. If authenticated,
:code:`"me"` can be used in lieu of a TIDAL ID for the
current user's collection. If not specified, :code:`"me"` is
used.
user_id : int or str; keyword-only; optional
TIDAL ID of the user. If not specified, the TIDAL ID of
current user is used.
country_code : str; optional
ISO 3166-1 alpha-2 country code.
"""
if user_id is None:
id_ = collection_id or "me"
endpoint = (
f"userCollection{resource_type.capitalize()}"
f"/{id_}/relationships/items"
)
else:
if collection_id is not None:
raise ValueError(
"At most one of 'collection_id' or 'user_id' can "
"be provided."
)
id_ = user_id or self._client._my_profile["id"]
endpoint = f"userCollections/{id_}/relationships/{resource_type}"
self._validate_tidal_ids(id_, recursive=False)
params = {}
if country_code is not None:
self._validate_country_code(country_code)
params["countryCode"] = country_code
self._client._request(
method,
endpoint,
headers={"Idempotency-Key": str(uuid.uuid4())},
params=params,
json={
"data": self._prepare_resource_identifiers(
resource_type, resource_ids
)
},
)
[docs]
@TTLCache.cached_method(ttl="user")
def get_user_collection(
self,
user_id: int | str | None = None,
/,
*,
country_code: str | None = None,
locale: str | None = None,
expand: str | Collection[str] | None = None,
) -> dict[str, Any]:
"""
`User Collections > Get Single User Collection
<https://tidal-music.github.io/tidal-api-reference/#
/userCollections/get_userCollections__id_>`_: Get TIDAL catalog
information for a user's collection.
.. admonition:: Authorization scope
:class: entitlement
.. tab-set::
.. tab-item:: Required
:code:`collection.read` scope
Read access to a user's collection.
Parameters
----------
user_id : int or str; positional-only; optional
TIDAL ID of the user. If not specified, the TIDAL ID of
current user is used.
country_code : str; keyword-only; optional
ISO 3166-1 alpha-2 country code.
**Example**: :code:`"US"`.
locale : str; keyword-only; optional
IETF BCP 47 language tag.
expand : str or Collection[str]; keyword-only; optional
Related resources to include metadata for in the response.
**Valid values**: :code:`"albums"`, :code:`"artists"`,
:code:`"owners"`, :code:`"playlists"`, :code:`"tracks"`,
:code:`"videos"`.
**Examples**: :code:`"albums"`, :code:`["tracks", "videos"]`.
Returns
-------
collection : dict[str, Any]
TIDAL metadata for the user collection.
.. admonition:: Sample response
:class: response dropdown
.. code-block::
{
"data": {
"attributes": {},
"id": <str>,
"relationships": {
"albums": {
"data": [
{
"id": <str>,
"meta": {
"addedAt": <str>
},
"type": "albums"
}
],
"links": {
"self": <str>
}
},
"artists": {
"data": [
{
"id": <str>,
"meta": {
"addedAt": <str>
},
"type": "artists"
}
],
"links": {
"self": <str>
}
},
"owners": {
"data": [
{
"id": <str>,
"type": "users"
}
],
"links": {
"self": <str>
}
},
"playlists": {
"data": [
{
"id": <str>,
"meta": {
"addedAt": <str>
},
"type": "playlists"
}
],
"links": {
"self": <str>
}
},
"tracks": {
"data": [
{
"id": <str>,
"meta": {
"addedAt": <str>
},
"type": "tracks"
}
],
"links": {
"self": <str>
}
},
"videos": {
"data": [
{
"id": <str>,
"meta": {
"addedAt": <str>
},
"type": "videos"
}
],
"links": {
"self": <str>
}
}
},
"type": "userCollections"
},
"included": [
{
"attributes": {
"accessType": <str>,
"availability": <list[str]>,
"barcodeId": <str>,
"copyright": {
"text": <str>
},
"duration": <str>,
"explicit": <bool>,
"externalLinks": [
{
"href": <str>,
"meta": {
"type": <str>
}
}
],
"mediaTags": <list[str]>,
"numberOfItems": <int>,
"numberOfVolumes": <int>,
"popularity": <float>,
"releaseDate": <str>,
"title": <str>,
"type": "ALBUM"
},
"id": <str>,
"relationships": {
"artists": {
"links": {
"self": <str>
}
},
"coverArt": {
"links": {
"self": <str>
}
},
"genres": {
"links": {
"self": <str>
}
},
"items": {
"links": {
"self": <str>
}
},
"owners": {
"links": {
"self": <str>
}
},
"providers": {
"links": {
"self": <str>
}
},
"similarAlbums": {
"links": {
"self": <str>
}
},
"suggestedCoverArts" : {
"links": {
"self": <str>
}
}
},
"type": "albums"
},
{
"attributes": {
"contributionsEnabled": <bool>,
"externalLinks": [
{
"href": <str>,
"meta": {
"type": <str>
}
}
],
"name": <str>,
"popularity": <float>,
"spotlighted": <bool>
},
"id": <str>,
"relationships": {
"albums": {
"links": {
"self": <str>
}
},
"biography": {
"links": {
"self": <str>
}
},
"followers": {
"links": {
"self": <str>
}
},
"following": {
"links": {
"self": <str>
}
},
"owners": {
"links": {
"self": <str>
}
},
"profileArt": {
"links": {
"self": <str>
}
},
"radio": {
"links": {
"self": <str>
}
},
"roles": {
"links": {
"self": <str>
}
},
"similarArtists": {
"links": {
"self": <str>
}
},
"trackProviders": {
"links": {
"self": <str>
}
},
"tracks": {
"links": {
"self": <str>
}
},
"videos": {
"links": {
"self": <str>
}
}
},
"type": "artists"
},
{
"attributes": {
"accessType": <str>,
"bounded": <bool>,
"createdAt": <str>,
"description": <str>,
"duration": <str>,
"externalLinks": [
{
"href": <str>,
"meta": {
"type": <str>
}
}
],
"lastModifiedAt": <str>,
"name": <str>,
"numberOfItems": <int>,
"playlistType": "USER"
},
"id": <str>,
"relationships": {
"coverArt": {
"links": {
"self": <str>
}
},
"items": {
"links": {
"self": <str>
}
},
"owners": {
"links": {
"self": <str>
}
}
},
"type": "playlists"
},
{
"attributes": {
"accessType": <str>,
"availability": <list[str]>,
"bpm": <float>,
"copyright": {
"text": <str>
},
"duration": <str>,
"explicit": <bool>,
"externalLinks": [
{
"href": <str>,
"meta": {
"type": <str>
}
}
],
"isrc": <str>,
"key": <str>,
"keyScale": <str>,
"mediaTags": <list[str]>,
"popularity": <float>,
"spotlighted": <bool>,
"title": <str>,
"toneTags": <list[str]>,
"version": <str>
},
"id": <str>,
"relationships": {
"albums": {
"links": {
"self": <str>
}
},
"artists": {
"links": {
"self": <str>
}
},
"genres": {
"links": {
"self": <str>
}
},
"lyrics": {
"links": {
"self": <str>
}
},
"owners": {
"links": {
"self": <str>
}
},
"providers": {
"links": {
"self": <str>
}
},
"radio": {
"links": {
"self": <str>
}
},
"shares": {
"links": {
"self": <str>
}
},
"similarTracks": {
"links": {
"self": <str>
}
},
"sourceFile": {
"links": {
"self": <str>
}
},
"trackStatistics": {
"links": {
"self": <str>
}
}
},
"type": "tracks"
},
{
"attributes": {
"country": <str>,
"email": <str>,
"emailVerified": <bool>,
"firstName": <str>,
"username": <str>
},
"id": <str>,
"type": "users"
},
{
"attributes": {
"availability": <list[str]>,
"copyright": {
"text": <str>
},
"duration": <str>,
"explicit": <bool>,
"externalLinks": [
{
"href": <str>,
"meta": {
"type": <str>
}
}
],
"isrc": <str>,
"popularity": <float>
"releaseDate": <str>,
"title": <str>
},
"id": <str>,
"relationships": {
"albums": {
"links": {
"self": <str>
}
},
"artists": {
"links": {
"self": <str>
}
},
"providers": {
"links": {
"self": <str>
}
},
"thumbnailArt": {
"links": {
"self": <str>
}
}
},
"type": "videos"
}
],
"links": {
"self": <str>
}
}
"""
self._client._require_scopes(
"users.get_user_collection", "collection.read"
)
if user_id is None:
user_id = self._client._my_profile["id"]
else:
self._validate_tidal_ids(user_id, recursive=False)
return self._get_resources(
"userCollections",
user_id,
country_code=country_code,
locale=locale,
expand=expand,
)
[docs]
@TTLCache.cached_method(ttl="static")
def get_user_collection_owners(
self,
user_id: int | str | None = None,
/,
*,
include_metadata: bool = False,
cursor: str | None = None,
) -> dict[str, Any]:
"""
`User Collections > Get Owners Relationship
<https://tidal-music.github.io/tidal-api-reference/#
/userCollections
/get_userCollections__id__relationships_owners>`_: Get TIDAL
profile information for the owners of a user collection
resource.
.. admonition:: Authorization scope
:class: entitlement
.. tab-set::
.. tab-item:: Required
:code:`collection.read` scope
Read access to a user's collection.
Parameters
----------
user_id : int or str; positional-only; optional
TIDAL ID of the user. If not specified, the TIDAL ID of
current user is used.
include_metadata : bool; keyword-only; default: :code:`False`
Whether to include metadata for the owners.
cursor : str; keyword-only; optional
Cursor for fetching the next page of results.
**Example**: :code:`"3nI1Esi"`.
Returns
-------
owners : dict[str, Any]
Page of TIDAL profile information for the user collection
resource's owners.
.. admonition:: Sample response
:class: response dropdown
.. code-block::
{
"data": {
"id": <str>,
"type": "users"
},
"included": [
{
"attributes": {
"country": <str>,
"email": <str>,
"emailVerified": <bool>,
"firstName": <str>,
"username": <str>
},
"id": <str>,
"type": "users"
}
],
"links": {
"self": <str>
}
}
"""
self._client._require_scopes(
"users.get_user_collection_owners", "collection.read"
)
if user_id is None:
user_id = self._client._my_profile["id"]
else:
self._validate_tidal_ids(user_id, recursive=False)
return self._get_resource_relationship(
"userCollections",
user_id,
"owners",
include_metadata=include_metadata,
cursor=cursor,
)
[docs]
@TTLCache.cached_method(ttl="user")
def get_album_collection(
self,
collection_id: str | None = None,
/,
*,
country_code: str | None = None,
locale: str | None = None,
expand: str | Collection[str] | None = None,
) -> dict[str, Any]:
"""
`User Collection Albums > Get User Album Collection
<https://tidal-music.github.io/tidal-api-reference/#
/userCollectionAlbums/get_userCollectionAlbums__id_>`_: Get
TIDAL catalog information for a user album collection.
.. admonition:: Authorization scope
:class: entitlement
.. tab-set::
.. tab-item:: Required
:code:`collection.read` scope
Read access to a user's collection.
Parameters
----------
collection_id : str; positional-only; optional
TIDAL ID of the user collection. If authenticated,
:code:`"me"` can be used in lieu of a TIDAL ID for the
current user's collection. If not specified, :code:`"me"` is
used.
country_code : str; keyword-only; optional
ISO 3166-1 alpha-2 country code.
**Example**: :code:`"US"`.
locale : str; keyword-only; optional
IETF BCP 47 language tag.
expand : str or Collection[str]; keyword-only; optional
Related resources to include metadata for in the response.
**Valid values**: :code:`"items"`, :code:`"owners"`.
**Examples**: :code:`"items"`, :code:`["items", "owners"]`.
Returns
-------
collection : dict[str, Any]
TIDAL metadata for the user album collection.
.. admonition:: Sample response
:class: response dropdown
.. code-block::
{
"data": {
"attributes": {},
"id": <str>,
"relationships": {
"items": {
"data": [
{
"id": <str>,
"meta": {
"addedAt": <str>
},
"type": "albums"
}
],
"links": {
"self": <str>
}
},
"owners": {
"data": [
{
"id": <str>,
"type": "users"
}
],
"links": {
"self": <str>
}
}
},
"type": "userCollectionAlbums"
},
"included": [
{
"attributes": {
"accessType": <str>,
"albumType": <str>,
"availability": <list[str]>,
"barcodeId": <str>,
"copyright": {
"text": <str>
},
"duration": <str>,
"explicit": <bool>,
"externalLinks": [
{
"href": <str>,
"meta": {
"type": <str>
}
}
],
"mediaTags": <list[str]>,
"numberOfItems": <int>,
"numberOfVolumes": <int>,
"popularity": <float>,
"releaseDate": <str>,
"title": <str>,
"type": <str>
},
"id": <str>,
"relationships": {
"artists": {
"links": {
"self": <str>
}
},
"coverArt": {
"links": {
"self": <str>
}
},
"genres": {
"links": {
"self": <str>
}
},
"items": {
"links": {
"self": <str>
}
},
"owners": {
"links": {
"self": <str>
}
},
"providers": {
"links": {
"self": <str>
}
},
"similarAlbums": {
"links": {
"self": <str>
}
},
"suggestedCoverArts": {
"links": {
"self": <str>
}
}
},
"type": "albums"
},
{
"attributes": {
"country": <str>,
"email": <str>,
"emailVerified": <bool>,
"firstName": <str>,
"username": <str>
},
"id": <str>,
"type": "users"
}
],
"links": {
"self": <str>
}
}
"""
self._client._require_scopes(
"users.get_album_collection", "collection.read"
)
return self._get_resources(
"userCollectionAlbums",
collection_id or "me",
country_code=country_code,
locale=locale,
expand=expand,
relationships=self._COLLECTION_RESOURCE_RELATIONSHIPS,
)
[docs]
@TTLCache.cached_method(ttl="user")
def get_user_saved_albums(
self,
*,
collection_id: str | None = None,
user_id: int | str | None = None,
country_code: str | None = None,
locale: str | None = None,
include_metadata: bool = False,
cursor: str | None = None,
sort_by: str | None = None,
descending: bool | None = None,
) -> dict[str, Any]:
"""
`User Collection Albums > Get Items Relationship
<https://tidal-music.github.io/tidal-api-reference/#
/userCollectionAlbums/get_userCollectionAlbums__id_>`_: Get
TIDAL catalog information for albums in a user collection․
`User Collections > Get Albums Relationship
<https://tidal-music.github.io/tidal-api-reference/#
/userCollections
/get_userCollections__id__relationships_albums>`_: Get TIDAL
catalog information for albums in a user's collection.
.. admonition:: Authorization scope
:class: entitlement
.. tab-set::
.. tab-item:: Required
:code:`collection.read` scope
Read access to a user's collection.
.. important::
At most one of `collection_id` or `user_id` must be provided.
If `user_id` is provided, the legacy
:code:`GET /userCollections/{user_id}/relationships/albums`
endpoint is used.
Parameters
----------
collection_id : str; keyword-only; optional
TIDAL ID of the user collection. If authenticated,
:code:`"me"` can be used in lieu of a TIDAL ID for the
current user's collection. If not specified, :code:`"me"` is
used.
user_id : int or str; keyword-only; optional
TIDAL ID of the user.
country_code : str; keyword-only; optional
ISO 3166-1 alpha-2 country code.
**Example**: :code:`"US"`.
locale : str; keyword-only; optional
IETF BCP 47 language tag.
include_metadata : bool; keyword-only; default: :code:`False`
Whether to include metadata for the albums in the user's
collection.
cursor : str; keyword-only; optional
Cursor for fetching the next page of results.
**Example**: :code:`"3nI1Esi"`.
sort_by : str; keyword-only; optional
Field to sort the albums by.
**Valid values**: :code:`"addedAt"`, :code:`"artists.name`,
:code:`"releaseDate"`, :code:`"title"`.
descending : bool; keyword-only; optional
Whether to sort in descending order.
**API default**: :code:`False`.
Returns
-------
albums : dict[str, Any]
Page of TIDAL metadata for the albums in the user's
collection.
.. admonition:: Sample response
:class: response dropdown
.. code::
{
"data": [
{
"id": <str>,
"meta": {
"addedAt": <str>
},
"type": "albums"
}
],
"included": [
{
"attributes": {
"accessType": <str>,
"availability": <list[str]>,
"barcodeId": <str>,
"copyright": {
"text": <str>
},
"duration": <str>,
"explicit": <bool>,
"externalLinks": [
{
"href": <str>,
"meta": {
"type": <str>
}
}
],
"mediaTags": <list[str]>,
"numberOfItems": <int>,
"numberOfVolumes": <int>,
"popularity": <float>,
"releaseDate": <str>,
"title": <str>,
"type": "ALBUM"
},
"id": <str>,
"relationships": {
"artists": {
"links": {
"self": <str>
}
},
"coverArt": {
"links": {
"self": <str>
}
},
"genres": {
"links": {
"self": <str>
}
},
"items": {
"links": {
"self": <str>
}
},
"owners": {
"links": {
"self": <str>
}
},
"providers": {
"links": {
"self": <str>
}
},
"similarAlbums": {
"links": {
"self": <str>
}
},
"suggestedCoverArts" : {
"links": {
"self": <str>
}
}
},
"type": "albums"
}
],
"links": {
"meta": {
"nextCursor": <str>
},
"next": <str>,
"self": <str>
}
}
"""
self._client._require_scopes(
"users.get_user_saved_albums", "collection.read"
)
return self._get_user_collection_relationship(
"albums",
collection_id=collection_id,
user_id=user_id,
country_code=country_code,
locale=locale,
include_metadata=include_metadata,
cursor=cursor,
sort_by=sort_by,
descending=descending,
sort_fields={"addedAt", "artists.name", "releaseDate", "title"},
)
[docs]
def save_albums(
self,
album_ids: int
| str
| dict[str, int | str]
| Collection[int | str | dict[str, int | str]],
/,
*,
collection_id: str | None = None,
user_id: int | str | None = None,
country_code: str | None = None,
) -> None:
"""
`User Collection Albums > Add to Items Relationship
<https://tidal-music.github.io/tidal-api-reference/#
/userCollectionAlbums
/post_userCollectionAlbums__id__relationships_items>`_: Add
albums to a user collection․
`User Collections > Add to Albums Relationship
<https://tidal-music.github.io/tidal-api-reference/#
/userCollections
/post_userCollections__id__relationships_albums>`_: Add albums
to a user's collection.
.. admonition:: Authorization scope
:class: entitlement
.. tab-set::
.. tab-item:: Required
:code:`collection.write` scope
Write access to a user's collection.
.. important::
At most one of `collection_id` or `user_id` must be provided.
If `user_id` is provided, the legacy
:code:`POST /userCollections/{user_id}/relationships/albums`
endpoint is used.
Parameters
----------
album_ids : int, str, dict[str, int | str], or \
Collection[int | str | dict[str, int | str]]; positional-only
TIDAL IDs and/or resource identifiers of the albums.
**Examples**:
* :code:`46369321`
* :code:`"251380836"`
* :code:`{"id": "46369321", "types": "albums"}`
* :code:`["251380836",
{"id": "46369321", "types": "albums"}]`
collection_id : str; keyword-only; optional
TIDAL ID of the user collection. If authenticated,
:code:`"me"` can be used in lieu of a TIDAL ID for the
current user's collection. If not specified, :code:`"me"` is
used.
user_id : int or str; keyword-only; optional
TIDAL ID of the user.
country_code : str; keyword-only; optional
ISO 3166-1 alpha-2 country code.
**Example**: :code:`"US"`.
"""
self._client._require_scopes("users.save_albums", "collection.write")
self._modify_user_collection_resources(
"POST",
"albums",
album_ids,
collection_id=collection_id,
user_id=user_id,
country_code=country_code,
)
[docs]
def remove_saved_albums(
self,
album_ids: int
| str
| dict[str, int | str]
| Collection[int | str | dict[str, int | str]],
/,
*,
collection_id: str | None = None,
user_id: int | str | None = None,
) -> None:
"""
`User Collection Albums > Delete from Items Relationship
<https://tidal-music.github.io/tidal-api-reference/#
/userCollectionAlbums
/delete_userCollectionAlbums__id__relationships_items>`_: Remove
albums from a user collection․
`User Collections > Delete from Albums Relationship
<https://tidal-music.github.io/tidal-api-reference/#
/userCollections
/delete_userCollections__id__relationships_albums>`_: Remove
albums from a user's collection.
.. admonition:: Authorization scope
:class: entitlement
.. tab-set::
.. tab-item:: Required
:code:`collection.write` scope
Write access to a user's collection.
.. important::
At most one of `collection_id` or `user_id` must be provided.
If `user_id` is provided, the legacy
:code:`DELETE /userCollections/{user_id}/relationships
/albums` endpoint is used.
Parameters
----------
album_ids : int, str, dict[str, int | str], or \
Collection[int | str | dict[str, int | str]]; positional-only
TIDAL IDs and/or resource identifiers of the albums.
**Examples**:
* :code:`46369321`
* :code:`"251380836"`
* :code:`{"id": "46369321", "types": "albums"}`
* :code:`["251380836",
{"id": "46369321", "types": "albums"}]`
collection_id : str; keyword-only; optional
TIDAL ID of the user collection. If authenticated,
:code:`"me"` can be used in lieu of a TIDAL ID for the
current user's collection. If not specified, :code:`"me"` is
used.
user_id : int or str; keyword-only; optional
TIDAL ID of the user.
"""
self._client._require_scopes(
"users.remove_saved_albums", "collection.write"
)
self._modify_user_collection_resources(
"DELETE",
"albums",
album_ids,
collection_id=collection_id,
user_id=user_id,
)
[docs]
@TTLCache.cached_method(ttl="user")
def get_artist_collection(
self,
collection_id: str | None = None,
/,
*,
country_code: str | None = None,
locale: str | None = None,
expand: str | Collection[str] | None = None,
) -> dict[str, Any]:
"""
`User Collection Artists > Get User Artist Collection
<https://tidal-music.github.io/tidal-api-reference/#
/userCollectionArtists/get_userCollectionArtists__id_>`_: Get
TIDAL catalog information for a user artist collection.
.. admonition:: Authorization scope
:class: entitlement
.. tab-set::
.. tab-item:: Required
:code:`collection.read` scope
Read access to a user's collection.
Parameters
----------
collection_id : str; positional-only; optional
TIDAL ID of the user collection. If authenticated,
:code:`"me"` can be used in lieu of a TIDAL ID for the
current user's collection. If not specified, :code:`"me"` is
used.
country_code : str; keyword-only; optional
ISO 3166-1 alpha-2 country code.
**Example**: :code:`"US"`.
locale : str; keyword-only; optional
IETF BCP 47 language tag.
expand : str or Collection[str]; keyword-only; optional
Related resources to include metadata for in the response.
**Valid values**: :code:`"items"`, :code:`"owners"`.
**Examples**: :code:`"items"`, :code:`["items", "owners"]`.
Returns
-------
collection : dict[str, Any]
TIDAL metadata for the user artist collection.
.. admonition:: Sample response
:class: response dropdown
.. code-block::
{
"data": {
"attributes": {},
"id": <str>,
"relationships": {
"items": {
"data": [
{
"id": <str>,
"meta": {
"addedAt": <str>
},
"type": "artists"
}
],
"links": {
"self": <str>
}
},
"owners": {
"data": [
{
"id": <str>,
"type": "users"
}
],
"links": {
"self": <str>
}
}
},
"type": "userCollectionArtists"
},
"included": [
{
"attributes": {
"externalLinks": [
{
"href": <str>,
"meta": {
"type": <str>
}
}
],
"name": <str>,
"popularity": <float>
},
"id": <str>,
"relationships": {
"albums": {
"links": {
"self": <str>
}
},
"biography": {
"links": {
"self": <str>
}
},
"followers": {
"links": {
"self": <str>
}
},
"following": {
"links": {
"self": <str>
}
},
"owners": {
"links": {
"self": <str>
}
},
"profileArt": {
"links": {
"self": <str>
}
},
"radio": {
"links": {
"self": <str>
}
},
"roles": {
"links": {
"self": <str>
}
},
"similarArtists": {
"links": {
"self": <str>
}
},
"trackProviders": {
"links": {
"self": <str>
}
},
"tracks": {
"links": {
"self": <str>
}
},
"videos": {
"links": {
"self": <str>
}
}
},
"type": "artists"
},
{
"attributes": {
"country": <str>,
"email": <str>,
"emailVerified": <bool>,
"firstName": <str>,
"username": <str>
},
"id": <str>,
"type": "users"
}
],
"links": {
"self": <str>
}
}
"""
self._client._require_scopes(
"users.get_artist_collection", "collection.read"
)
return self._get_resources(
"userCollectionArtists",
collection_id or "me",
country_code=country_code,
locale=locale,
expand=expand,
relationships=self._COLLECTION_RESOURCE_RELATIONSHIPS,
)
[docs]
@TTLCache.cached_method(ttl="user")
def get_user_followed_artists(
self,
*,
collection_id: str | None = None,
user_id: int | str | None = None,
country_code: str | None = None,
locale: str | None = None,
include_metadata: bool = False,
cursor: str | None = None,
sort_by: str | None = None,
descending: bool | None = None,
) -> dict[str, Any]:
"""
`User Collection Artists > Get Items Relationship
<https://tidal-music.github.io/tidal-api-reference/#
/userCollectionArtists/get_userCollectionArtists__id_>`_: Get
TIDAL catalog information for artists in a user collection․
`User Collections > Get Artists Relationship
<https://tidal-music.github.io/tidal-api-reference/#
/userCollections
/get_userCollections__id__relationships_artists>`_: Get TIDAL
catalog information for artists in a user's collection.
.. admonition:: Authorization scope
:class: entitlement
.. tab-set::
.. tab-item:: Required
:code:`collection.read` scope
Read access to a user's collection.
.. important::
At most one of `collection_id` or `user_id` must be provided.
If `user_id` is provided, the legacy
:code:`GET /userCollections/{user_id}/relationships/artists`
endpoint is used.
Parameters
----------
collection_id : str; keyword-only; optional
TIDAL ID of the user collection. If authenticated,
:code:`"me"` can be used in lieu of a TIDAL ID for the
current user's collection. If not specified, :code:`"me"` is
used.
user_id : int or str; keyword-only; optional
TIDAL ID of the user.
country_code : str; keyword-only; optional
ISO 3166-1 alpha-2 country code.
**Example**: :code:`"US"`.
locale : str; keyword-only; optional
IETF BCP 47 language tag.
include_metadata : bool; keyword-only; default: :code:`False`
Whether to include metadata for the artists in the user's
collection.
cursor : str; keyword-only; optional
Cursor for fetching the next page of results.
**Example**: :code:`"3nI1Esi"`.
sort_by : str; keyword-only; optional
Field to sort the artists by.
**Valid values**: :code:`"addedAt"`, :code:`"name"`.
descending : bool; keyword-only; optional
Whether to sort in descending order.
**API default**: :code:`False`.
Returns
-------
artists : dict[str, Any]
Page of TIDAL metadata for the artists in the user's
collection.
.. admonition:: Sample response
:class: response dropdown
.. code-block::
{
"data": [
{
"id": <str>,
"meta": {
"addedAt": <str>
},
"type": "artists"
}
],
"included": [
{
"attributes": {
"contributionsEnabled": <bool>,
"externalLinks": [
{
"href": <str>,
"meta": {
"type": <str>
}
}
],
"name": <str>,
"popularity": <float>,
"spotlighted": <bool>
},
"id": <str>,
"relationships": {
"albums": {
"links": {
"self": <str>
}
},
"biography": {
"links": {
"self": <str>
}
},
"followers": {
"links": {
"self": <str>
}
},
"following": {
"links": {
"self": <str>
}
},
"owners": {
"links": {
"self": <str>
}
},
"profileArt": {
"links": {
"self": <str>
}
},
"radio": {
"links": {
"self": <str>
}
},
"roles": {
"links": {
"self": <str>
}
},
"similarArtists": {
"links": {
"self": <str>
}
},
"trackProviders": {
"links": {
"self": <str>
}
},
"tracks": {
"links": {
"self": <str>
}
},
"videos": {
"links": {
"self": <str>
}
}
},
"type": "artists"
}
],
"links": {
"meta": {
"nextCursor": <str>
},
"next": <str>,
"self": <str>
}
}
"""
self._client._require_scopes(
"users.get_user_followed_artists", "collection.read"
)
return self._get_user_collection_relationship(
"artists",
collection_id=collection_id,
user_id=user_id,
country_code=country_code,
locale=locale,
include_metadata=include_metadata,
cursor=cursor,
sort_by=sort_by,
descending=descending,
sort_fields={"addedAt", "name"},
)
[docs]
def follow_artists(
self,
artist_ids: int
| str
| dict[str, int | str]
| Collection[int | str | dict[str, int | str]],
/,
*,
collection_id: str | None = None,
user_id: int | str | None = None,
country_code: str | None = None,
) -> None:
"""
`User Collection Artists > Add to Items Relationship
<https://tidal-music.github.io/tidal-api-reference/#
/userCollectionArtists
/post_userCollectionArtists__id__relationships_items>`_: Add
artists to a user collection․
`User Collections > Add to Artists Relationship
<https://tidal-music.github.io/tidal-api-reference/#
/userCollections
/post_userCollections__id__relationships_artists>`_: Add artists
to a user's collection.
.. admonition:: Authorization scope
:class: entitlement
.. tab-set::
.. tab-item:: Required
:code:`collection.write` scope
Write access to a user's collection.
.. important::
At most one of `collection_id` or `user_id` must be provided.
If `user_id` is provided, the legacy
:code:`POST /userCollections/{user_id}/relationships/artists`
endpoint is used.
Parameters
----------
artist_ids : int, str, dict[str, int | str], or \
Collection[int | str | dict[str, int | str]]; positional-only
TIDAL IDs and/or resource identifiers of the artists.
**Examples**:
* :code:`1566`
* :code:`"4676988"`
* :code:`{"id": "1566", "types": "artists"}`
* :code:`["4676988",
{"id": "46369321", "types": "artists"}]`
collection_id : str; keyword-only; optional
TIDAL ID of the user collection. If authenticated,
:code:`"me"` can be used in lieu of a TIDAL ID for the
current user's collection. If not specified, :code:`"me"` is
used.
user_id : int or str; keyword-only; optional
TIDAL ID of the user.
country_code : str; keyword-only; optional
ISO 3166-1 alpha-2 country code.
**Example**: :code:`"US"`.
"""
self._client._require_scopes(
"users.follow_artists", "collection.write"
)
self._modify_user_collection_resources(
"POST",
"artists",
artist_ids,
collection_id=collection_id,
user_id=user_id,
country_code=country_code,
)
[docs]
def unfollow_artists(
self,
artist_ids: int
| str
| dict[str, int | str]
| Collection[int | str | dict[str, int | str]],
/,
*,
collection_id: str | None = None,
user_id: int | str | None = None,
) -> None:
"""
`User Collection Artists > Delete from Items Relationship
<https://tidal-music.github.io/tidal-api-reference/#
/userCollectionArtists
/delete_userCollectionArtists__id__relationships_items>`_: Remove
artists from a user collection․
`User Collections > Delete from Items Relationship
<https://tidal-music.github.io/tidal-api-reference/#
/userCollections
/delete_userCollections__id__relationships_artists>`_: Remove
artists from a user's collection.
.. admonition:: Authorization scope
:class: entitlement
.. tab-set::
.. tab-item:: Required
:code:`collection.write` scope
Write access to a user's collection.
.. important::
At most one of `collection_id` or `user_id` must be provided.
If `user_id` is provided, the legacy
:code:`DELETE /userCollections/{user_id}/relationships
/artists` endpoint is used.
Parameters
----------
artist_ids : int, str, dict[str, int | str], or \
Collection[int | str | dict[str, int | str]]; positional-only
TIDAL IDs and/or resource identifiers of the artists.
**Examples**:
* :code:`1566`
* :code:`"4676988"`
* :code:`{"id": "1566", "types": "artists"}`
* :code:`["4676988",
{"id": "46369321", "types": "artists"}]`
collection_id : str; keyword-only; optional
TIDAL ID of the user collection. If authenticated,
:code:`"me"` can be used in lieu of a TIDAL ID for the
current user's collection. If not specified, :code:`"me"` is
used.
user_id : int or str; keyword-only; optional
TIDAL ID of the user.
"""
self._client._require_scopes(
"users.unfollow_artists", "collection.write"
)
self._modify_user_collection_resources(
"DELETE",
"artists",
artist_ids,
collection_id=collection_id,
user_id=user_id,
)
[docs]
@TTLCache.cached_method(ttl="user")
def get_playlist_collection(
self,
collection_id: str | None = None,
/,
*,
country_code: str | None = None,
locale: str | None = None,
expand: str | Collection[str] | None = None,
) -> dict[str, Any]:
"""
`User Collection Playlists > Get User Playlist Collection
<https://tidal-music.github.io/tidal-api-reference/#
/userCollectionPlaylists/get_userCollectionPlaylists__id_>`_:
Get TIDAL catalog information for a user playlist collection.
.. admonition:: Authorization scope
:class: entitlement
.. tab-set::
.. tab-item:: Required
:code:`collection.read` scope
Read access to a user's collection.
Parameters
----------
collection_id : str; positional-only; optional
TIDAL ID of the user collection. If authenticated,
:code:`"me"` can be used in lieu of a TIDAL ID for the
current user's collection. If not specified, :code:`"me"` is
used.
country_code : str; keyword-only; optional
ISO 3166-1 alpha-2 country code.
**Example**: :code:`"US"`.
locale : str; keyword-only; optional
IETF BCP 47 language tag.
expand : str or Collection[str]; keyword-only; optional
Related resources to include metadata for in the response.
**Valid values**: :code:`"items"`, :code:`"owners"`.
**Examples**: :code:`"items"`, :code:`["items", "owners"]`.
Returns
-------
collection : dict[str, Any]
TIDAL metadata for the user playlist collection.
.. admonition:: Sample response
:class: response dropdown
.. code-block::
{
"data": {
"attributes": {},
"id": <str>,
"relationships": {
"items": {
"data": [
{
"id": <str>,
"meta": {
"addedAt": <str>
},
"type": "playlists"
}
],
"links": {
"self": <str>
}
},
"owners": {
"data": [
{
"id": <str>,
"type": "users"
}
],
"links": {
"self": <str>
}
}
},
"type": "userCollectionPlaylists"
},
"included": [
{
"attributes": {
"accessType": <str>,
"bounded": <bool>,
"createdAt": <str>,
"description": <str>,
"duration": "PT3H16M5S",
"externalLinks": [
{
"href": <str>,
"meta": {
"type": <str>
}
}
],
"lastModifiedAt": <str>,
"name": <str>,
"numberOfFollowers": <int>,
"numberOfItems": <int>,
"playlistType": <str>
},
"id": <str>,
"relationships": {
"coverArt": {
"links": {
"self": <str>
}
},
"items": {
"links": {
"self": <str>
}
},
"ownerProfiles": {
"links": {
"self": <str>
}
},
"owners": {
"links": {
"self": <str>
}
}
},
"type": "playlists"
}
],
"links": {
"self": <str>
}
}
"""
self._client._require_scopes(
"users.get_playlist_collection", "collection.read"
)
return self._get_resources(
"userCollectionPlaylists",
collection_id or "me",
country_code=country_code,
locale=locale,
expand=expand,
relationships=self._COLLECTION_RESOURCE_RELATIONSHIPS,
)
[docs]
@TTLCache.cached_method(ttl="user")
def get_user_followed_playlists(
self,
*,
collection_id: str | None = None,
user_id: int | str | None = None,
include_folders: bool = False,
include_metadata: bool = False,
cursor: str | None = None,
sort_by: str | None = None,
descending: bool | None = None,
) -> dict[str, Any]:
"""
`User Collection Playlists > Get Items Relationship
<https://tidal-music.github.io/tidal-api-reference/#
/userCollectionPlaylists/get_userCollectionPlaylists__id_>`_: Get
TIDAL catalog information for playlists in a user collection․
`User Collections > Get Playlists Relationship
<https://tidal-music.github.io/tidal-api-reference/#
/userCollections
/get_userCollections__id__relationships_playlists>`_: Get TIDAL
catalog information for playlists in a user's collection.
.. admonition:: Authorization scope
:class: entitlement
.. tab-set::
.. tab-item:: Required
:code:`collection.read` scope
Read access to a user's collection.
.. important::
At most one of `collection_id` or `user_id` must be provided.
If `user_id` is provided, the legacy
:code:`GET /userCollections/{user_id}/relationships
/playlists` endpoint is used.
Parameters
----------
collection_id : str; keyword-only; optional
TIDAL ID of the user collection. If authenticated,
:code:`"me"` can be used in lieu of a TIDAL ID for the
current user's collection. If not specified, :code:`"me"` is
used.
user_id : int or str; keyword-only; optional
TIDAL ID of the user.
include_folders : bool; keyword-only; default: :code:`False`
Whether to include metadata for playlist folders in the
user's collection.
include_metadata : bool; keyword-only; default: :code:`False`
Whether to include metadata for the playlists in the user's
collection.
cursor : str; keyword-only; optional
Cursor for fetching the next page of results.
**Example**: :code:`"3nI1Esi"`.
sort_by : str; keyword-only; optional
Field to sort the playlists by.
**Valid values**: :code:`"addedAt"`,
:code:`"lastUpdatedAt"`, :code:`"name"`.
descending : bool; keyword-only; optional
Whether to sort in descending order.
**API default**: :code:`False`.
Returns
-------
playlists : dict[str, Any]
Page of TIDAL metadata for the playlists (and playlist
folders) in the user's collection.
.. admonition:: Sample response
:class: response dropdown
.. code-block::
{
"data": [
{
"id": <str>,
"meta": {
"addedAt": <str>
},
"type": "playlists"
},
{
"id": <str>,
"meta": {
"addedAt": <str>
},
"type": "userCollectionFolders"
}
],
"included": [
{
"attributes": {
"accessType": <str>,
"bounded": <bool>,
"createdAt": <str>,
"description": <str>,
"duration": <str>,
"externalLinks": [
{
"href": <str>,
"meta": {
"type": <str>
}
}
],
"lastModifiedAt": <str>,
"name": <str>,
"numberOfItems": <int>,
"playlistType": "USER"
},
"id": <str>,
"relationships": {
"coverArt": {
"links": {
"self": <str>
}
},
"items": {
"links": {
"self": <str>
}
},
"owners": {
"links": {
"self": <str>
}
}
},
"type": "playlists"
}
],
"links": {
"meta": {
"nextCursor": <str>
},
"next": <str>,
"self": <str>
}
}
"""
self._client._require_scopes(
"users.get_user_followed_playlists", "collection.read"
)
params = {}
if include_folders:
params["collectionView"] = "FOLDERS"
return self._get_user_collection_relationship(
"playlists",
collection_id=collection_id,
user_id=user_id,
include_metadata=include_metadata,
cursor=cursor,
sort_by=sort_by,
descending=descending,
sort_fields={"addedAt", "lastUpdatedAt", "name"},
params=params,
)
[docs]
def follow_playlists(
self,
playlist_uuids: str
| dict[str, str]
| Collection[str | dict[str, str]],
/,
*,
collection_id: str | None = None,
user_id: int | str | None = None,
) -> None:
"""
`User Collection Playlists > Add to Items Relationship
<https://tidal-music.github.io/tidal-api-reference/#
/userCollectionPlaylists
/post_userCollectionPlaylists__id__relationships_items>`_: Add
playlists to a user collection․
`User Collections > Add Playlists to User's Collection
<https://tidal-music.github.io/tidal-api-reference/#
/userCollections
/post_userCollections__id__relationships_playlists>`_: Add
playlists to a user's collection.
.. admonition:: Authorization scope
:class: entitlement
.. tab-set::
.. tab-item:: Required
:code:`collection.write` scope
Write access to a user's collection.
.. important::
At most one of `collection_id` or `user_id` must be provided.
If `user_id` is provided, the legacy
:code:`POST /userCollections/{user_id}/relationships
/playlists` endpoint is used.
Parameters
----------
playlist_uuids : str, dict[str, str], or \
Collection[str | dict[str, str]]; positional-only
UUIDs and/or resource identifiers of the playlists.
**Examples**:
* :code:`"f0d6f5c4-081f-4348-9b65-ae677d92767b"`
* .. code-block::
{
"id": "1e4c73df-b805-47cd-9e44-9a8721c5cb45",
"types": "playlists"
}
* .. code-block::
[
"f0d6f5c4-081f-4348-9b65-ae677d92767b",
{
"id": "1e4c73df-b805-47cd-9e44-9a8721c5cb45",
"types": "playlists"
}
]
collection_id : str; keyword-only; optional
TIDAL ID of the user collection. If authenticated,
:code:`"me"` can be used in lieu of a TIDAL ID for the
current user's collection. If not specified, :code:`"me"` is
used.
user_id : int or str; keyword-only; optional
TIDAL ID of the user.
"""
self._client._require_scopes(
"users.follow_playlists", "collection.write"
)
self._modify_user_collection_resources(
"POST",
"playlists",
playlist_uuids,
collection_id=collection_id,
user_id=user_id,
)
[docs]
def unfollow_playlists(
self,
playlist_uuids: str
| dict[str, str]
| Collection[str | dict[str, str]],
/,
*,
collection_id: str | None = None,
user_id: int | str | None = None,
) -> None:
"""
`User Collection Playlists > Delete from Items Relationship
<https://tidal-music.github.io/tidal-api-reference/#
/userCollectionPlaylists
/delete_userCollectionPlaylists__id__relationships_items>`_:
Remove playlists from a user collection․
`User Collections > Remove Playlists from User's Collection
<https://tidal-music.github.io/tidal-api-reference/#
/userCollections
/delete_userCollections__id__relationships_playlists>`_: Remove
playlists from a user's collection.
.. admonition:: Authorization scope
:class: entitlement
.. tab-set::
.. tab-item:: Required
:code:`collection.write` scope
Write access to a user's collection.
.. important::
At most one of `collection_id` or `user_id` must be provided.
If `user_id` is provided, the legacy
:code:`DELETE /userCollections/{user_id}/relationships
/playlists` endpoint is used.
Parameters
----------
playlist_uuids : str, dict[str, str], or \
Collection[str | dict[str, str]]; positional-only
UUIDs and/or resource identifiers of the playlists.
**Examples**:
* :code:`"f0d6f5c4-081f-4348-9b65-ae677d92767b"`
* .. code-block::
{
"id": "1e4c73df-b805-47cd-9e44-9a8721c5cb45",
"types": "playlists"
}
* .. code-block::
[
"f0d6f5c4-081f-4348-9b65-ae677d92767b",
{
"id": "1e4c73df-b805-47cd-9e44-9a8721c5cb45",
"types": "playlists"
}
]
collection_id : str; keyword-only; optional
TIDAL ID of the user collection. If authenticated,
:code:`"me"` can be used in lieu of a TIDAL ID for the
current user's collection. If not specified, :code:`"me"` is
used.
user_id : int or str; keyword-only; optional
TIDAL ID of the user.
"""
self._client._require_scopes(
"users.unfollow_playlists", "collection.write"
)
self._modify_user_collection_resources(
"DELETE",
"playlists",
playlist_uuids,
collection_id=collection_id,
user_id=user_id,
)
[docs]
@TTLCache.cached_method(ttl="user")
def get_user_saved_tracks(
self,
*,
collection_id: str | None = None,
user_id: int | str | None = None,
country_code: str | None = None,
locale: str | None = None,
include_metadata: bool = False,
cursor: str | None = None,
sort_by: str | None = None,
descending: bool | None = None,
) -> dict[str, Any]:
"""
`User Collection Tracks > Get Items Relationship
<https://tidal-music.github.io/tidal-api-reference/#
/userCollectionTracks/get_userCollectionTracks__id_>`_: Get
TIDAL catalog information for tracks in a user collection․
`User Collections > Get Tracks Relationship
<https://tidal-music.github.io/tidal-api-reference/#
/userCollections
/get_userCollections__id__relationships_tracks>`_: Get TIDAL
catalog information for tracks in a user's collection.
.. admonition:: Authorization scope
:class: entitlement
.. tab-set::
.. tab-item:: Required
:code:`collection.read` scope
Read access to a user's collection.
.. important::
At most one of `collection_id` or `user_id` must be provided.
If `user_id` is provided, the legacy
:code:`GET /userCollections/{user_id}/relationships/tracks`
endpoint is used.
Parameters
----------
collection_id : str; keyword-only; optional
TIDAL ID of the user collection. If authenticated,
:code:`"me"` can be used in lieu of a TIDAL ID for the
current user's collection. If not specified, :code:`"me"` is
used.
user_id : int or str; keyword-only; optional
TIDAL ID of the user.
country_code : str; keyword-only; optional
ISO 3166-1 alpha-2 country code.
**Example**: :code:`"US"`.
locale : str; keyword-only; optional
IETF BCP 47 language tag.
include_metadata : bool; keyword-only; default: :code:`False`
Whether to include metadata for the tracks in the user's
collection.
cursor : str; keyword-only; optional
Cursor for fetching the next page of results.
**Example**: :code:`"3nI1Esi"`.
sort_by : str; keyword-only; optional
Field to sort the tracks by.
**Valid values**: :code:`"addedAt"`, :code:`"albums.title"`,
:code:`"artists.name"`, :code:`"duration"`, :code:`"title"`.
descending : bool; keyword-only; optional
Whether to sort in descending order.
**API default**: :code:`False`.
Returns
-------
tracks : dict[str, Any]
Page of TIDAL metadata for the tracks in the user's
collection.
.. admonition:: Sample response
:class: response dropdown
.. code-block::
{
"data": [
{
"id": <str>,
"meta": {
"addedAt": <str>
},
"type": "tracks"
}
],
"included": [
{
"attributes": {
"accessType": <str>,
"availability": <list[str]>,
"bpm": <float>,
"copyright": {
"text": <str>
},
"duration": <str>,
"explicit": <bool>,
"externalLinks": [
{
"href": <str>,
"meta": {
"type": <str>
}
}
],
"isrc": <str>,
"key": <str>,
"keyScale": <str>,
"mediaTags": <list[str]>,
"popularity": <float>,
"spotlighted": <bool>,
"title": <str>,
"toneTags": <list[str]>,
"version": <str>
},
"id": <str>,
"relationships": {
"albums": {
"links": {
"self": <str>
}
},
"artists": {
"links": {
"self": <str>
}
},
"genres": {
"links": {
"self": <str>
}
},
"lyrics": {
"links": {
"self": <str>
}
},
"owners": {
"links": {
"self": <str>
}
},
"providers": {
"links": {
"self": <str>
}
},
"radio": {
"links": {
"self": <str>
}
},
"shares": {
"links": {
"self": <str>
}
},
"similarTracks": {
"links": {
"self": <str>
}
},
"sourceFile": {
"links": {
"self": <str>
}
},
"trackStatistics": {
"links": {
"self": <str>
}
}
},
"type": "tracks"
}
],
"links": {
"meta": {
"nextCursor": <str>
},
"next": <str>,
"self": <str>
}
}
"""
self._client._require_scopes(
"users.get_user_saved_tracks", "collection.read"
)
return self._get_user_collection_relationship(
"tracks",
collection_id=collection_id,
user_id=user_id,
country_code=country_code,
locale=locale,
include_metadata=include_metadata,
cursor=cursor,
sort_by=sort_by,
descending=descending,
sort_fields={
"addedAt",
"albums.title",
"artists.name",
"duration",
"title",
},
)
[docs]
def save_tracks(
self,
track_ids: int
| str
| dict[str, int | str]
| Collection[int | str | dict[str, int | str]],
/,
*,
collection_id: str | None = None,
user_id: int | str | None = None,
country_code: str | None = None,
) -> None:
"""
`User Collection Tracks > Add to Items Relationship
<https://tidal-music.github.io/tidal-api-reference/#
/userCollectionTracks
/post_userCollectionTracks__id__relationships_items>`_: Add
tracks to a user collection․
`User Collections > Add to Tracks Relationship
<https://tidal-music.github.io/tidal-api-reference/#
/userCollections
/post_userCollections__id__relationships_tracks>`_: Add tracks
to a user's collection.
.. admonition:: Authorization scope
:class: entitlement
.. tab-set::
.. tab-item:: Required
:code:`collection.write` scope
Write access to a user's collection.
.. important::
At most one of `collection_id` or `user_id` must be provided.
If `user_id` is provided, the legacy
:code:`POST /userCollections/{user_id}/relationships/tracks`
endpoint is used.
Parameters
----------
track_ids : int, str, dict[str, int | str], or \
Collection[int | str | dict[str, int | str]]; positional-only
TIDAL IDs and/or resource identifiers of the tracks.
**Examples**:
* :code:`46369325`
* :code:`"75413016"`
* :code:`{"id": "46369325", "types": "tracks"}`
* :code:`["75413016",
{"id": "46369325", "types": "tracks"}]`
collection_id : str; keyword-only; optional
TIDAL ID of the user collection. If authenticated,
:code:`"me"` can be used in lieu of a TIDAL ID for the
current user's collection. If not specified, :code:`"me"` is
used.
user_id : int or str; keyword-only; optional
TIDAL ID of the user.
country_code : str; keyword-only; optional
ISO 3166-1 alpha-2 country code.
**Example**: :code:`"US"`.
"""
self._client._require_scopes("users.save_tracks", "collection.write")
self._modify_user_collection_resources(
"POST",
"tracks",
track_ids,
collection_id=collection_id,
user_id=user_id,
country_code=country_code,
)
[docs]
def remove_saved_tracks(
self,
track_ids: int
| str
| dict[str, int | str]
| Collection[int | str | dict[str, int | str]],
/,
*,
collection_id: str | None = None,
user_id: int | str | None = None,
) -> None:
"""
`User Collection Tracks > Delete from Items Relationship
<https://tidal-music.github.io/tidal-api-reference/#
/userCollectionTracks
/delete_userCollectionTracks__id__relationships_items>`_: Remove
tracks from a user collection․
`User Collections > Delete from Tracks Relationship
<https://tidal-music.github.io/tidal-api-reference/#
/userCollections
/delete_userCollections__id__relationships_tracks>`_: Remove
tracks from a user's collection.
.. admonition:: Authorization scope
:class: entitlement
.. tab-set::
.. tab-item:: Required
:code:`collection.write` scope
Write access to a user's collection.
.. important::
At most one of `collection_id` or `user_id` must be provided.
If `user_id` is provided, the legacy
:code:`DELETE /userCollections/{user_id}/relationships
/tracks` endpoint is used.
Parameters
----------
track_ids : int, str, dict[str, int | str], or \
Collection[int | str | dict[str, int | str]]; positional-only
TIDAL IDs and/or resource identifiers of the tracks.
**Examples**:
* :code:`46369325`
* :code:`"75413016"`
* :code:`{"id": "46369325", "types": "tracks"}`
* :code:`["75413016",
{"id": "46369325", "types": "tracks"}]`
collection_id : str; keyword-only; optional
TIDAL ID of the user collection. If authenticated,
:code:`"me"` can be used in lieu of a TIDAL ID for the
current user's collection. If not specified, :code:`"me"` is
used.
user_id : int or str; keyword-only; optional
TIDAL ID of the user.
"""
self._client._require_scopes(
"users.remove_saved_tracks", "collection.write"
)
self._modify_user_collection_resources(
"DELETE",
"tracks",
track_ids,
collection_id=collection_id,
user_id=user_id,
)
[docs]
@TTLCache.cached_method(ttl="user")
def get_user_saved_videos(
self,
*,
collection_id: str | None = None,
user_id: int | str | None = None,
country_code: str | None = None,
locale: str | None = None,
include_metadata: bool = False,
cursor: str | None = None,
sort_by: str | None = None,
descending: bool | None = None,
) -> dict[str, Any]:
"""
`User Collection Videos > Get Items Relationship
<https://tidal-music.github.io/tidal-api-reference/#
/userCollectionVideos/get_userCollectionVideos__id_>`_: Get
TIDAL catalog information for videos in a user collection․
`User Collections > Get Videos Relationship
<https://tidal-music.github.io/tidal-api-reference/#
/userCollections
/get_userCollections__id__relationships_videos>`_: Get TIDAL
catalog information for videos in a user's collection.
.. admonition:: Authorization scope
:class: entitlement
.. tab-set::
.. tab-item:: Required
:code:`collection.read` scope
Read access to a user's collection.
.. important::
At most one of `collection_id` or `user_id` must be provided.
If `user_id` is provided, the legacy
:code:`GET /userCollections/{user_id}/relationships/videos`
endpoint is used.
Parameters
----------
collection_id : str; keyword-only; optional
TIDAL ID of the user collection. If authenticated,
:code:`"me"` can be used in lieu of a TIDAL ID for the
current user's collection. If not specified, :code:`"me"` is
used.
user_id : int or str; keyword-only; optional
TIDAL ID of the user.
country_code : str; keyword-only; optional
ISO 3166-1 alpha-2 country code.
**Example**: :code:`"US"`.
locale : str; keyword-only; optional
IETF BCP 47 language tag.
include_metadata : bool; keyword-only; default: :code:`False`
Whether to include metadata for the videos in the user's
collection.
cursor : str; keyword-only; optional
Cursor for fetching the next page of results.
**Example**: :code:`"3nI1Esi"`.
sort_by : str; keyword-only; optional
Field to sort the videos by.
**Valid values**: :code:`"addedAt"`, :code:`"artists.name"`,
:code:`"duration"`, :code:`"title"`.
descending : bool; keyword-only; optional
Whether to sort in descending order.
**API default**: :code:`False`.
Returns
-------
videos : dict[str, Any]
Page of TIDAL metadata for the videos in the user's
collection.
.. admonition:: Sample response
:class: response dropdown
.. code-block::
{
"data": [
{
"id": <str>,
"meta": {
"addedAt": <str>
},
"type": "videos"
}
],
"included": [
{
"attributes": {
"availability": <list[str]>,
"copyright": {
"text": <str>
},
"duration": <str>,
"explicit": <bool>,
"externalLinks": [
{
"href": <str>,
"meta": {
"type": <str>
}
}
],
"isrc": <str>,
"popularity": <float>
"releaseDate": <str>,
"title": <str>
},
"id": <str>,
"relationships": {
"albums": {
"links": {
"self": <str>
}
},
"artists": {
"links": {
"self": <str>
}
},
"providers": {
"links": {
"self": <str>
}
},
"thumbnailArt": {
"links": {
"self": <str>
}
}
},
"type": "videos"
}
],
"links": {
"meta": {
"nextCursor": <str>
},
"next": <str>,
"self": <str>
}
}
"""
self._client._require_scopes(
"users.get_user_saved_videos", "collection.read"
)
return self._get_user_collection_relationship(
"videos",
collection_id=collection_id,
user_id=user_id,
country_code=country_code,
locale=locale,
include_metadata=include_metadata,
cursor=cursor,
sort_by=sort_by,
descending=descending,
sort_fields={"addedAt", "artists.name", "duration", "title"},
)
[docs]
def save_videos(
self,
video_ids: str
| dict[str, int | str]
| Collection[int | str | dict[str, int | str]],
/,
*,
collection_id: str | None = None,
user_id: int | str | None = None,
country_code: str | None = None,
) -> None:
"""
`User Collection Videos > Add to Items Relationship
<https://tidal-music.github.io/tidal-api-reference/#
/userCollectionVideos
/post_userCollectionVideos__id__relationships_items>`_: Add
videos to a user collection․
`User Collections > Add to Videos Relationship
<https://tidal-music.github.io/tidal-api-reference/#
/userCollections
/post_userCollections__id__relationships_videos>`_: Add videos
to a user's collection.
.. admonition:: Authorization scope
:class: entitlement
.. tab-set::
.. tab-item:: Required
:code:`collection.write` scope
Write access to a user's collection.
.. important::
At most one of `collection_id` or `user_id` must be provided.
If `user_id` is provided, the legacy
:code:`POST /userCollections/{user_id}/relationships/videos`
endpoint is used.
Parameters
----------
video_ids : int, str, dict[str, int | str], or \
Collection[int | str | dict[str, int | str]]; positional-only
TIDAL IDs and/or resource identifiers of the videos.
**Examples**:
* :code:`53315642`
* :code:`"75623239"`
* :code:`{"id": "75623239", "types": "videos"}`
* :code:`["53315642",
{"id": "75623239", "types": "videos"}]`
collection_id : str; keyword-only; optional
TIDAL ID of the user collection. If authenticated,
:code:`"me"` can be used in lieu of a TIDAL ID for the
current user's collection. If not specified, :code:`"me"` is
used.
user_id : int or str; keyword-only; optional
TIDAL ID of the user.
country_code : str; keyword-only; optional
ISO 3166-1 alpha-2 country code.
**Example**: :code:`"US"`.
"""
self._client._require_scopes("users.save_videos", "collection.write")
self._modify_user_collection_resources(
"POST",
"videos",
video_ids,
collection_id=collection_id,
user_id=user_id,
country_code=country_code,
)
[docs]
def remove_saved_videos(
self,
video_ids: str
| dict[str, int | str]
| Collection[int | str | dict[str, int | str]],
/,
*,
collection_id: str | None = None,
user_id: int | str | None = None,
) -> None:
"""
`User Collection Videos > Delete from Items Relationship
<https://tidal-music.github.io/tidal-api-reference/#
/userCollectionVideos
/delete_userCollectionVideos__id__relationships_items>`_: Remove
videos from a user collection․
`User Collections > Delete from Videos Relationship
<https://tidal-music.github.io/tidal-api-reference/#
/userCollections
/delete_userCollections__id__relationships_videos>`_: Remove
videos from a user's collection.
.. admonition:: Authorization scope
:class: entitlement
.. tab-set::
.. tab-item:: Required
:code:`collection.write` scope
Write access to a user's collection.
.. important::
At most one of `collection_id` or `user_id` must be provided.
If `user_id` is provided, the legacy
:code:`DELETE /userCollections/{user_id}/relationships
/videos` endpoint is used.
Parameters
----------
video_ids : int, str, dict[str, int | str], or \
Collection[int | str | dict[str, int | str]]; positional-only
TIDAL IDs and/or resource identifiers of the videos.
**Examples**:
* :code:`53315642`
* :code:`"75623239"`
* :code:`{"id": "75623239", "types": "videos"}`
* :code:`["53315642",
{"id": "75623239", "types": "videos"}]`
collection_id : str; keyword-only; optional
TIDAL ID of the user collection. If authenticated,
:code:`"me"` can be used in lieu of a TIDAL ID for the
current user's collection. If not specified, :code:`"me"` is
used.
user_id : int or str; keyword-only; optional
TIDAL ID of the user.
"""
self._client._require_scopes(
"users.remove_saved_videos", "collection.write"
)
self._modify_user_collection_resources(
"DELETE",
"videos",
video_ids,
collection_id=collection_id,
user_id=user_id,
)
[docs]
@TTLCache.cached_method(ttl="recommendation")
def get_personalized_mixes(
self,
*,
user_id: str | None = None,
country_code: str | None = None,
locale: str | None = None,
expand: str | Collection[str] | None = None,
) -> dict[str, Any]:
"""
`User Recommendations > Get Single User Recommendation
<https://tidal-music.github.io/tidal-api-reference/#
/userRecommendations/get_userRecommendations__id_>`_: Get TIDAL
catalog information for mixes curated for a user.
.. admonition:: Authorization scope
:class: entitlement
.. tab-set::
.. tab-item:: Required
:code:`recommendations.read` scope
Read access to a user's personal recommendations.
Parameters
----------
user_id : int or str; keyword-only; optional
TIDAL ID of the user. If not specified, the current user's
TIDAL ID is used.
country_code : str; optional
ISO 3166-1 alpha-2 country code.
**Example**: :code:`"US"`.
locale : str; keyword-only; optional
IETF BCP 47 language tag.
expand : str or Collection[str]; keyword-only; optional
Related resources to include in the response.
**Valid values**: :code:`"discoveryMixes"`,
:code:`"myMixes"`, :code:`"newArrivalMixes"`.
**Examples**: :code:`"myMixes"`,
:code:`["discoveryMixes", "newArrivalMixes"]`.
Returns
-------
recommendations : dict[str, Any]
TIDAL metadata for the curated mixes.
.. admonition:: Sample response
:class: response dropdown
.. code-block::
{
"data": {
"attributes": {},
"id": <str>,
"relationships": {
"discoveryMixes": {
"data": [],
"links": {
"self": <str>
}
},
"myMixes": {
"data": [],
"links": {
"self": <str>
}
},
"newArrivalMixes": {
"data": [],
"links": {
"self": <str>
}
}
},
"type": "userRecommendations"
},
"included": [],
"links": {
"self": <str>
}
}
"""
self._client._require_scopes(
"users.get_recommendations", "recommendations.read"
)
if user_id is None:
user_id = self._client._my_profile["id"]
else:
self._validate_tidal_ids(user_id, recursive=False)
return self._get_resources(
"userRecommendations",
user_id,
country_code=country_code,
locale=locale,
expand=expand,
relationships=self._RECOMMENDATION_RELATIONSHIPS,
)
[docs]
@TTLCache.cached_method(ttl="daily")
def get_discovery_mixes(
self,
*,
user_id: str | None = None,
country_code: str | None = None,
locale: str | None = None,
include_metadata: bool = False,
cursor: int | None = None,
) -> dict[str, Any]:
"""
`User Recommendations > Get Discovery Mixes Relationship
<https://tidal-music.github.io/tidal-api-reference/#
/userRecommendations
/get_userRecommendations__id__relationships_discoveryMixes>`_:
Get TIDAL catalog information for a user's Discovery Mixes.
.. admonition:: Authorization scope
:class: entitlement
.. tab-set::
.. tab-item:: Required
:code:`recommendations.read` scope
Read access to a user's personal recommendations.
Parameters
----------
user_id : int or str; keyword-only; optional
TIDAL ID of the user. If not specified, the current user's
TIDAL ID is used.
country_code : str; optional
ISO 3166-1 alpha-2 country code.
**Example**: :code:`"US"`.
locale : str; keyword-only; optional
IETF BCP 47 language tag.
include_metadata : bool; keyword-only; default: :code:`False`
Whether to include metadata for the user's Discovery Mixes.
cursor : str; keyword-only; optional
Cursor for fetching the next page of results.
**Example**: :code:`"3nI1Esi"`.
Returns
-------
mixes : dict[str, Any]
Page of TIDAL catalog information for the user's Discovery
Mixes.
.. admonition:: Sample response
:class: response dropdown
.. code-block::
{
"data": [],
"included": [],
"links": {
"meta": {
"nextCursor": <str>
},
"next": <str>,
"self": <str>
}
}
"""
self._client._require_scopes(
"users.get_discovery_mixes", "recommendations.read"
)
self._get_resource_relationship(
"userRecommendations",
user_id,
"discoveryMixes",
country_code=country_code,
locale=locale,
include_metadata=include_metadata,
cursor=cursor,
)
[docs]
@TTLCache.cached_method(ttl="daily")
def get_user_mixes(
self,
*,
user_id: str | None = None,
country_code: str | None = None,
locale: str | None = None,
include_metadata: bool = False,
cursor: int | None = None,
) -> dict[str, Any]:
"""
`User Recommendations > Get My Mixes Relationship
<https://tidal-music.github.io/tidal-api-reference/#
/userRecommendations
/get_userRecommendations__id__relationships_mixes>`_:
Get TIDAL catalog information for a user's mixes.
.. admonition:: Authorization scope
:class: entitlement
.. tab-set::
.. tab-item:: Required
:code:`recommendations.read` scope
Read access to a user's personal recommendations.
Parameters
----------
user_id : int or str; keyword-only; optional
TIDAL ID of the user. If not specified, the current user's
TIDAL ID is used.
country_code : str; optional
ISO 3166-1 alpha-2 country code.
**Example**: :code:`"US"`.
locale : str; keyword-only; optional
IETF BCP 47 language tag.
include_metadata : bool; keyword-only; default: :code:`False`
Whether to include metadata for the user's mixes.
cursor : str; keyword-only; optional
Cursor for fetching the next page of results.
**Example**: :code:`"3nI1Esi"`.
Returns
-------
mixes : dict[str, Any]
Page of TIDAL catalog information for the user's mixes.
.. admonition:: Sample response
:class: response dropdown
.. code-block::
{
"data": [],
"included": [],
"links": {
"meta": {
"nextCursor": <str>
},
"next": <str>,
"self": <str>
}
}
"""
self._client._require_scopes("users.get_mixes", "recommendations.read")
self._get_resource_relationship(
"userRecommendations",
user_id,
"myMixes",
country_code=country_code,
locale=locale,
include_metadata=include_metadata,
cursor=cursor,
)
[docs]
@TTLCache.cached_method(ttl="daily")
def get_new_arrival_mixes(
self,
*,
user_id: str | None = None,
country_code: str | None = None,
locale: str | None = None,
include_metadata: bool = False,
cursor: int | None = None,
) -> dict[str, Any]:
"""
`User Recommendations > Get New Arrival Mixes Relationship
<https://tidal-music.github.io/tidal-api-reference/#
/userRecommendations
/get_userRecommendations__id__relationships_newArrivalMixes>`_:
Get TIDAL catalog information for the user's New Arrival Mixes.
.. admonition:: Authorization scope
:class: entitlement
.. tab-set::
.. tab-item:: Required
:code:`recommendations.read` scope
Read access to a user's personal recommendations.
Parameters
----------
user_id : int or str; keyword-only; optional
TIDAL ID of the user. If not specified, the current user's
TIDAL ID is used.
country_code : str; optional
ISO 3166-1 alpha-2 country code.
**Example**: :code:`"US"`.
locale : str; keyword-only; optional
IETF BCP 47 language tag.
include_metadata : bool; keyword-only; default: :code:`False`
Whether to include metadata for the user's New Arrival
Mixes.
cursor : str; keyword-only; optional
Cursor for fetching the next page of results.
**Example**: :code:`"3nI1Esi"`.
Returns
-------
mixes : dict[str, Any]
Page of TIDAL catalog information for the user's New Arrival
Mixes.
.. admonition:: Sample response
:class: response dropdown
.. code-block::
{
"data": [],
"included": [],
"links": {
"meta": {
"nextCursor": <str>
},
"next": <str>,
"self": <str>
}
}
"""
self._client._require_scopes(
"users.get_new_arrival_mixes", "recommendations.read"
)
self._get_resource_relationship(
"userRecommendations",
user_id,
"newArrivalMixes",
country_code=country_code,
locale=locale,
include_metadata=include_metadata,
cursor=cursor,
)
[docs]
@TTLCache.cached_method(ttl="static")
def get_user(self, user_id: int | str | None = None, /) -> dict[str, Any]:
"""
`Users > Get Single User
<https://tidal-music.github.io/tidal-api-reference/#/users
/get_users__id_>`_: Get TIDAL profile information for a user.
.. admonition:: Authorization scope
:class: entitlement
.. tab-set::
.. tab-item:: Required
:code:`user.read` scope
Read access to a user's account information, such as
country and email address.
Parameters
----------
user_id : int or str; keyword-only; optional
TIDAL ID of the user. If authenticated, :code:`"me"` can be
used in lieu of a TIDAL ID for the current user. If not
specified, :code:`"me"` is used.
Returns
-------
profile : dict[str, Any]
TIDAL profile information for the user.
.. admonition:: Sample response
:class: response dropdown
.. code-block::
{
"data": {
"attributes": {
"country": <str>,
"email": <str>,
"emailVerified": <bool>,
"firstName": <str>,
"username": <str>
},
"id": <str>,
"type": "users",
},
"links": {
"self": "/users/me"
}
}
"""
self._client._require_scopes("users.get_user", "user.read")
user_id = user_id or "me"
self._validate_tidal_ids(user_id)
return self._client._request("GET", f"users/{user_id}").json()