Files
songbox/songbox/music.py
ポキ 3eba8700cd feat: add initial songbox API client library
This commit introduces the initial version of the songbox API client library, including:
- Core client implementation with authentication and HTTP handling
- Modular sub-clients for auth, user, and music operations
- Comprehensive exception hierarchy for error handling
- CLI interface with support for all API operations
- Setup configuration with dependencies and package metadata
- Documentation including README with usage examples
2025-06-07 01:06:55 -07:00

247 lines
8.5 KiB
Python

"""Music client for songbox API"""
from typing import Dict, Any, List, Optional
from .exceptions import AuthenticationError, NotFoundError, ValidationError
class MusicClient:
"""Client for handling music-related operations with the songbox API.
This client handles search, albums, songs, and other music operations.
"""
def __init__(self, client):
self._client = client
def search(self, query: str) -> List[Dict[str, Any]]:
"""Search for songs, artists, and albums.
Args:
query: Search query string
Returns:
List of search results categorized by type (Songs, Artists, Albums)
Raises:
ValidationError: If query is empty
AuthenticationError: If not authenticated
Example:
>>> results = client.music.search("huttaa")
>>> for category in results:
... print(f"Category: {category['title']}")
... for item in category['items']:
... print(f" - {item['heading']}")
"""
if not query or not query.strip():
raise ValidationError("Search query cannot be empty")
try:
response = self._client.request(
method="GET",
endpoint=f"/v1/search/{query.strip()}",
require_auth=True
)
# Ensure we return a list
if isinstance(response, list):
return response
elif isinstance(response, dict):
# If response is a dict, try to extract results
return response.get('results', response.get('data', []))
else:
return []
except Exception as e:
if "401" in str(e) or "unauthorized" in str(e).lower():
raise AuthenticationError("Authentication required or token expired")
raise
def get_album(self, album_id: str) -> Dict[str, Any]:
"""Get detailed information about an album.
Args:
album_id: ID of the album
Returns:
Album information including songs, artist, artwork, etc.
Raises:
ValidationError: If album_id is empty
AuthenticationError: If not authenticated
NotFoundError: If album not found
Example:
>>> album = client.music.get_album("808")
>>> print(f"Album: {album['name']} by {album['artist_name']}")
>>> print(f"Songs: {len(album['songs'])}")
"""
if not album_id or not str(album_id).strip():
raise ValidationError("Album ID cannot be empty")
try:
response = self._client.request(
method="GET",
endpoint=f"/v1/albums/{album_id}",
require_auth=True
)
return response
except Exception as e:
if "401" in str(e) or "unauthorized" in str(e).lower():
raise AuthenticationError("Authentication required or token expired")
elif "404" in str(e) or "not found" in str(e).lower():
raise NotFoundError(f"Album with ID {album_id} not found")
raise
def get_song(self, song_id: str) -> Dict[str, Any]:
"""Get detailed information about a song.
Args:
song_id: ID of the song
Returns:
Song information including artist, album, duration, URLs, etc.
Raises:
ValidationError: If song_id is empty
AuthenticationError: If not authenticated
NotFoundError: If song not found
Example:
>>> song = client.music.get_song("6200")
>>> print(f"Song: {song['name']} by {song['artist_name']}")
>>> print(f"Duration: {song['duration']} seconds")
"""
if not song_id or not str(song_id).strip():
raise ValidationError("Song ID cannot be empty")
try:
response = self._client.request(
method="GET",
endpoint=f"/v1/songs/{song_id}",
require_auth=True
)
return response
except Exception as e:
if "401" in str(e) or "unauthorized" in str(e).lower():
raise AuthenticationError("Authentication required or token expired")
elif "404" in str(e) or "not found" in str(e).lower():
raise NotFoundError(f"Song with ID {song_id} not found")
raise
def get_artist(self, artist_id: str) -> Dict[str, Any]:
"""Get detailed information about an artist.
Args:
artist_id: ID of the artist
Returns:
Artist information including albums, songs, etc.
Raises:
ValidationError: If artist_id is empty
AuthenticationError: If not authenticated
NotFoundError: If artist not found
Example:
>>> artist = client.music.get_artist("124")
>>> print(f"Artist: {artist['name']}")
"""
if not artist_id or not str(artist_id).strip():
raise ValidationError("Artist ID cannot be empty")
try:
response = self._client.request(
method="GET",
endpoint=f"/v1/artists/{artist_id}",
require_auth=True
)
return response
except Exception as e:
if "401" in str(e) or "unauthorized" in str(e).lower():
raise AuthenticationError("Authentication required or token expired")
elif "404" in str(e) or "not found" in str(e).lower():
raise NotFoundError(f"Artist with ID {artist_id} not found")
raise
def get_recommendations(self, limit: Optional[int] = None) -> List[Dict[str, Any]]:
"""Get personalized music recommendations.
Args:
limit: Maximum number of items to return
Returns:
List of recommended music items
Raises:
AuthenticationError: If not authenticated
Example:
>>> recommendations = client.music.get_recommendations(limit=5)
>>> for item in recommendations:
... print(f"Recommended: {item.get('name', 'Unknown')}")
"""
params = {}
if limit is not None:
params['limit'] = limit
try:
response = self._client.request(
method="GET",
endpoint="/v1/recommendations",
params=params,
require_auth=True
)
# Ensure we return a list
if isinstance(response, list):
return response
elif isinstance(response, dict):
return response.get('data', response.get('items', []))
else:
return []
except Exception as e:
if "401" in str(e) or "unauthorized" in str(e).lower():
raise AuthenticationError("Authentication required or token expired")
raise
def get_playlists(self) -> List[Dict[str, Any]]:
"""Get user's playlists.
Returns:
List of user's playlists
Raises:
AuthenticationError: If not authenticated
Example:
>>> playlists = client.music.get_playlists()
>>> for playlist in playlists:
... print(f"Playlist: {playlist.get('name', 'Unknown')}")
"""
try:
response = self._client.request(
method="GET",
endpoint="/v1/playlists",
require_auth=True
)
# Ensure we return a list
if isinstance(response, list):
return response
elif isinstance(response, dict):
return response.get('data', response.get('playlists', []))
else:
return []
except Exception as e:
if "401" in str(e) or "unauthorized" in str(e).lower():
raise AuthenticationError("Authentication required or token expired")
raise