#!/usr/bin/python3 from spotipy import Spotify from deezspot.exceptions import InvalidLink from spotipy.exceptions import SpotifyException from spotipy.oauth2 import SpotifyClientCredentials import os class Spo: __error_codes = [404, 400] # Class-level API instance and credentials __api = None __client_id = None __client_secret = None __initialized = False @classmethod def __init__(cls, client_id, client_secret): """ Initialize the Spotify API client. Args: client_id (str): Spotify API client ID. client_secret (str): Spotify API client secret. """ if not client_id or not client_secret: raise ValueError("Spotify API credentials required. Provide client_id and client_secret.") client_credentials_manager = SpotifyClientCredentials( client_id=client_id, client_secret=client_secret ) # Store the credentials and API instance cls.__client_id = client_id cls.__client_secret = client_secret cls.__api = Spotify( auth_manager=client_credentials_manager ) cls.__initialized = True @classmethod def __check_initialized(cls): """Check if the class has been initialized with credentials""" if not cls.__initialized: raise ValueError("Spotify API not initialized. Call Spo.__init__(client_id, client_secret) first.") @classmethod def __get_api(cls, client_id=None, client_secret=None): """ Get a Spotify API instance with the provided credentials or use stored credentials. Args: client_id (str, optional): Spotify API client ID client_secret (str, optional): Spotify API client secret Returns: A Spotify API instance """ # If new credentials are provided, create a new API instance if client_id and client_secret: client_credentials_manager = SpotifyClientCredentials( client_id=client_id, client_secret=client_secret ) return Spotify(auth_manager=client_credentials_manager) # Otherwise, use the existing class-level API cls.__check_initialized() return cls.__api @classmethod def __lazy(cls, results, api=None): """Process paginated results""" api = api or cls.__api albums = results['items'] while results['next']: results = api.next(results) albums.extend(results['items']) return results @classmethod def get_track(cls, ids, client_id=None, client_secret=None): """ Get track information by ID. Args: ids (str): Spotify track ID client_id (str, optional): Optional custom Spotify client ID client_secret (str, optional): Optional custom Spotify client secret Returns: dict: Track information """ api = cls.__get_api(client_id, client_secret) try: track_json = api.track(ids) except SpotifyException as error: if error.http_status in cls.__error_codes: raise InvalidLink(ids) return track_json @classmethod def get_tracks(cls, ids: list, market: str = None, client_id=None, client_secret=None): """ Get information for multiple tracks by a list of IDs. Args: ids (list): A list of Spotify track IDs. market (str, optional): An ISO 3166-1 alpha-2 country code. client_id (str, optional): Optional custom Spotify client ID. client_secret (str, optional): Optional custom Spotify client secret. Returns: dict: A dictionary containing a list of track information. """ api = cls.__get_api(client_id, client_secret) try: tracks_json = api.tracks(ids, market=market) except SpotifyException as error: if error.http_status in cls.__error_codes: # Create a string of the first few IDs for the error message ids_preview = ', '.join(ids[:3]) + ('...' if len(ids) > 3 else '') raise InvalidLink(f"one or more IDs in the list: [{ids_preview}]") return tracks_json @classmethod def get_album(cls, ids, client_id=None, client_secret=None): """ Get album information by ID. Args: ids (str): Spotify album ID client_id (str, optional): Optional custom Spotify client ID client_secret (str, optional): Optional custom Spotify client secret Returns: dict: Album information """ api = cls.__get_api(client_id, client_secret) try: album_json = api.album(ids) except SpotifyException as error: if error.http_status in cls.__error_codes: raise InvalidLink(ids) tracks = album_json['tracks'] cls.__lazy(tracks, api) return album_json @classmethod def get_playlist(cls, ids, client_id=None, client_secret=None): """ Get playlist information by ID. Args: ids (str): Spotify playlist ID client_id (str, optional): Optional custom Spotify client ID client_secret (str, optional): Optional custom Spotify client secret Returns: dict: Playlist information """ api = cls.__get_api(client_id, client_secret) try: playlist_json = api.playlist(ids) except SpotifyException as error: if error.http_status in cls.__error_codes: raise InvalidLink(ids) tracks = playlist_json['tracks'] cls.__lazy(tracks, api) return playlist_json @classmethod def get_episode(cls, ids, client_id=None, client_secret=None): """ Get episode information by ID. Args: ids (str): Spotify episode ID client_id (str, optional): Optional custom Spotify client ID client_secret (str, optional): Optional custom Spotify client secret Returns: dict: Episode information """ api = cls.__get_api(client_id, client_secret) try: episode_json = api.episode(ids) except SpotifyException as error: if error.http_status in cls.__error_codes: raise InvalidLink(ids) return episode_json @classmethod def search(cls, query, search_type='track', limit=10, client_id=None, client_secret=None): """ Search for tracks, albums, artists, or playlists. Args: query (str): Search query search_type (str, optional): Type of search ('track', 'album', 'artist', 'playlist') limit (int, optional): Maximum number of results to return client_id (str, optional): Optional custom Spotify client ID client_secret (str, optional): Optional custom Spotify client secret Returns: dict: Search results """ api = cls.__get_api(client_id, client_secret) search = api.search(q=query, type=search_type, limit=limit) return search @classmethod def get_artist(cls, ids, client_id=None, client_secret=None): """ Get artist information by ID. Args: ids (str): Spotify artist ID client_id (str, optional): Optional custom Spotify client ID client_secret (str, optional): Optional custom Spotify client secret Returns: dict: Artist information """ api = cls.__get_api(client_id, client_secret) try: artist_json = api.artist(ids) except SpotifyException as error: if error.http_status in cls.__error_codes: raise InvalidLink(ids) return artist_json @classmethod def get_artist_discography(cls, ids, album_type='album,single,compilation,appears_on', limit=50, offset=0, client_id=None, client_secret=None): """ Get artist information and discography by ID. Args: ids (str): Spotify artist ID album_type (str, optional): Types of albums to include limit (int, optional): Maximum number of results client_id (str, optional): Optional custom Spotify client ID client_secret (str, optional): Optional custom Spotify client secret Returns: dict: Artist discography """ api = cls.__get_api(client_id, client_secret) try: # Request all types of releases by the artist. discography = api.artist_albums( ids, album_type=album_type, limit=limit, offset=offset ) except SpotifyException as error: if error.http_status in cls.__error_codes: raise InvalidLink(ids) else: raise # Ensure that all pages of results are fetched. cls.__lazy(discography, api) return discography