diff --git a/.gitignore b/.gitignore index 651e34a..1aca9bc 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ deezspot_test.log spotify_downloads deezer_spo_downloads __pycache__ +test_librespot.py diff --git a/deezspot/models/librespot/__init__.py b/deezspot/models/librespot/__init__.py index 0f93d6b..38854f8 100644 --- a/deezspot/models/librespot/__init__.py +++ b/deezspot/models/librespot/__init__.py @@ -5,6 +5,7 @@ from .track import Track from .album import Album from .playlist import Playlist, PlaylistItem, TrackStub, TracksPage, Owner, UserMini from .artist import Artist +from .search import SearchResult, SearchTracksPage, SearchAlbumsPage, SearchArtistsPage, SearchPlaylistsPage __all__ = [ "Image", @@ -20,4 +21,9 @@ __all__ = [ "Owner", "UserMini", "Artist", + "SearchResult", + "SearchTracksPage", + "SearchAlbumsPage", + "SearchArtistsPage", + "SearchPlaylistsPage", ] \ No newline at end of file diff --git a/deezspot/models/librespot/search.py b/deezspot/models/librespot/search.py new file mode 100644 index 0000000..2940763 --- /dev/null +++ b/deezspot/models/librespot/search.py @@ -0,0 +1,179 @@ +#!/usr/bin/python3 + +from dataclasses import dataclass, field +from typing import Optional, Dict, Any, List + +from .track import Track +from .album import Album +from .artist import Artist +from .playlist import Playlist +from .types import _int + + +@dataclass +class SearchTracksPage: + limit: int = 0 + offset: int = 0 + total: int = 0 + items: List[Track] = field(default_factory=list) + + @staticmethod + def from_dict(obj: Any) -> "SearchTracksPage": + if not isinstance(obj, dict): + return SearchTracksPage() + items: List[Track] = [] + for it in obj.get("items", []) or []: + if isinstance(it, dict): + items.append(Track.from_dict(it)) + return SearchTracksPage( + limit=_int(obj.get("limit")) or 0, + offset=_int(obj.get("offset")) or 0, + total=_int(obj.get("total")) or len(items), + items=items, + ) + + def to_dict(self) -> Dict[str, Any]: + return { + "limit": self.limit, + "offset": self.offset, + "total": self.total, + "items": [it.to_dict() for it in (self.items or [])] + } + + +@dataclass +class SearchAlbumsPage: + limit: int = 0 + offset: int = 0 + total: int = 0 + items: List[Album] = field(default_factory=list) + + @staticmethod + def from_dict(obj: Any) -> "SearchAlbumsPage": + if not isinstance(obj, dict): + return SearchAlbumsPage() + items: List[Album] = [] + for it in obj.get("items", []) or []: + if isinstance(it, dict): + items.append(Album.from_dict(it)) + return SearchAlbumsPage( + limit=_int(obj.get("limit")) or 0, + offset=_int(obj.get("offset")) or 0, + total=_int(obj.get("total")) or len(items), + items=items, + ) + + def to_dict(self) -> Dict[str, Any]: + return { + "limit": self.limit, + "offset": self.offset, + "total": self.total, + "items": [it.to_dict() for it in (self.items or [])] + } + + +@dataclass +class SearchArtistsPage: + limit: int = 0 + offset: int = 0 + total: int = 0 + items: List[Artist] = field(default_factory=list) + + @staticmethod + def from_dict(obj: Any) -> "SearchArtistsPage": + if not isinstance(obj, dict): + return SearchArtistsPage() + items: List[Artist] = [] + for it in obj.get("items", []) or []: + if isinstance(it, dict): + items.append(Artist.from_dict(it)) + return SearchArtistsPage( + limit=_int(obj.get("limit")) or 0, + offset=_int(obj.get("offset")) or 0, + total=_int(obj.get("total")) or len(items), + items=items, + ) + + def to_dict(self) -> Dict[str, Any]: + return { + "limit": self.limit, + "offset": self.offset, + "total": self.total, + "items": [it.to_dict() for it in (self.items or [])] + } + + +@dataclass +class SearchPlaylistsPage: + limit: int = 0 + offset: int = 0 + total: int = 0 + items: List[Playlist] = field(default_factory=list) + + @staticmethod + def from_dict(obj: Any) -> "SearchPlaylistsPage": + if not isinstance(obj, dict): + return SearchPlaylistsPage() + items: List[Playlist] = [] + for it in obj.get("items", []) or []: + if isinstance(it, dict): + items.append(Playlist.from_dict(it)) + return SearchPlaylistsPage( + limit=_int(obj.get("limit")) or 0, + offset=_int(obj.get("offset")) or 0, + total=_int(obj.get("total")) or len(items), + items=items, + ) + + def to_dict(self) -> Dict[str, Any]: + return { + "limit": self.limit, + "offset": self.offset, + "total": self.total, + "items": [it.to_dict() for it in (self.items or [])] + } + + +@dataclass +class SearchResult: + query: Optional[str] = None + tracks: Optional[SearchTracksPage] = None + albums: Optional[SearchAlbumsPage] = None + artists: Optional[SearchArtistsPage] = None + playlists: Optional[SearchPlaylistsPage] = None + # raw passthrough for unsupported sections (e.g., shows, episodes) + other: Dict[str, Any] = field(default_factory=dict) + + @staticmethod + def from_dict(obj: Any) -> "SearchResult": + if not isinstance(obj, dict): + return SearchResult() + tracks = SearchTracksPage.from_dict(obj.get("tracks", {})) if isinstance(obj.get("tracks"), dict) else None + albums = SearchAlbumsPage.from_dict(obj.get("albums", {})) if isinstance(obj.get("albums"), dict) else None + artists = SearchArtistsPage.from_dict(obj.get("artists", {})) if isinstance(obj.get("artists"), dict) else None + playlists = SearchPlaylistsPage.from_dict(obj.get("playlists", {})) if isinstance(obj.get("playlists"), dict) else None + known = {k for k in ("query", "tracks", "albums", "artists", "playlists")} + other = {k: v for k, v in obj.items() if k not in known} + return SearchResult( + query=obj.get("query"), + tracks=tracks, + albums=albums, + artists=artists, + playlists=playlists, + other=other, + ) + + def to_dict(self) -> Dict[str, Any]: + out: Dict[str, Any] = {} + if self.query is not None: + out["query"] = self.query + if self.tracks is not None: + out["tracks"] = self.tracks.to_dict() + if self.albums is not None: + out["albums"] = self.albums.to_dict() + if self.artists is not None: + out["artists"] = self.artists.to_dict() + if self.playlists is not None: + out["playlists"] = self.playlists.to_dict() + out.update(self.other or {}) + return out \ No newline at end of file