293 lines
8.4 KiB
Python
293 lines
8.4 KiB
Python
import msgspec
|
|
|
|
from core.exceptions import ExternalServiceError
|
|
|
|
|
|
class LastFmToken(msgspec.Struct):
|
|
token: str
|
|
|
|
|
|
class LastFmSession(msgspec.Struct):
|
|
name: str
|
|
key: str
|
|
subscriber: int = 0
|
|
|
|
|
|
class LastFmTag(msgspec.Struct):
|
|
name: str
|
|
url: str = ""
|
|
|
|
|
|
class LastFmArtist(msgspec.Struct):
|
|
name: str
|
|
mbid: str | None = None
|
|
playcount: int = 0
|
|
listeners: int = 0
|
|
url: str = ""
|
|
|
|
|
|
class LastFmAlbum(msgspec.Struct):
|
|
name: str
|
|
artist_name: str
|
|
mbid: str | None = None
|
|
playcount: int = 0
|
|
listeners: int = 0
|
|
url: str = ""
|
|
image_url: str = ""
|
|
|
|
|
|
class LastFmTrack(msgspec.Struct):
|
|
name: str
|
|
artist_name: str
|
|
mbid: str | None = None
|
|
playcount: int = 0
|
|
listeners: int = 0
|
|
url: str = ""
|
|
|
|
|
|
class LastFmSimilarArtist(msgspec.Struct):
|
|
name: str
|
|
mbid: str | None = None
|
|
match: float = 0.0
|
|
url: str = ""
|
|
|
|
|
|
class LastFmArtistInfo(msgspec.Struct):
|
|
name: str
|
|
mbid: str | None = None
|
|
listeners: int = 0
|
|
playcount: int = 0
|
|
url: str = ""
|
|
bio_summary: str = ""
|
|
tags: list[LastFmTag] | None = None
|
|
similar: list[LastFmSimilarArtist] | None = None
|
|
|
|
|
|
class LastFmAlbumTrack(msgspec.Struct):
|
|
name: str
|
|
duration: int = 0
|
|
rank: int = 0
|
|
url: str = ""
|
|
|
|
|
|
class LastFmAlbumInfo(msgspec.Struct):
|
|
name: str
|
|
artist_name: str
|
|
mbid: str | None = None
|
|
listeners: int = 0
|
|
playcount: int = 0
|
|
url: str = ""
|
|
image_url: str = ""
|
|
summary: str = ""
|
|
tags: list[LastFmTag] | None = None
|
|
tracks: list[LastFmAlbumTrack] | None = None
|
|
|
|
|
|
class LastFmRecentTrack(msgspec.Struct):
|
|
track_name: str
|
|
artist_name: str
|
|
album_name: str = ""
|
|
artist_mbid: str | None = None
|
|
album_mbid: str | None = None
|
|
timestamp: int = 0
|
|
now_playing: bool = False
|
|
image_url: str = ""
|
|
|
|
|
|
class LastFmLovedTrack(msgspec.Struct):
|
|
track_name: str
|
|
artist_name: str
|
|
album_name: str = ""
|
|
track_mbid: str | None = None
|
|
artist_mbid: str | None = None
|
|
url: str = ""
|
|
image_url: str = ""
|
|
|
|
|
|
ALLOWED_LASTFM_PERIOD = [
|
|
"overall", "7day", "1month", "3month", "6month", "12month",
|
|
]
|
|
|
|
|
|
def parse_weekly_album_chart_item(item: dict) -> "LastFmAlbum":
|
|
artist = item.get("artist", {})
|
|
artist_name = artist.get("#text", "") if isinstance(artist, dict) else str(artist)
|
|
return LastFmAlbum(
|
|
name=item.get("name", ""),
|
|
artist_name=artist_name,
|
|
mbid=item.get("mbid") or None,
|
|
playcount=_safe_int(item.get("playcount")),
|
|
url=item.get("url", ""),
|
|
image_url=_extract_image(item.get("image")),
|
|
)
|
|
|
|
|
|
def parse_token(data: dict) -> LastFmToken:
|
|
token_value = data.get("token")
|
|
if not token_value:
|
|
raise ExternalServiceError("Last.fm auth.getToken response missing 'token'")
|
|
return LastFmToken(token=token_value)
|
|
|
|
|
|
def parse_session(data: dict) -> LastFmSession:
|
|
session_data = data.get("session", data)
|
|
name = session_data.get("name")
|
|
key = session_data.get("key")
|
|
if not name or not key:
|
|
raise ExternalServiceError("Last.fm auth.getSession response missing 'name' or 'key'")
|
|
return LastFmSession(
|
|
name=name,
|
|
key=key,
|
|
subscriber=int(session_data.get("subscriber", 0)),
|
|
)
|
|
|
|
|
|
def _extract_image(images: list[dict] | None, size: str = "extralarge") -> str:
|
|
if not images:
|
|
return ""
|
|
for img in images:
|
|
if img.get("size") == size:
|
|
return img.get("#text", "")
|
|
return images[-1].get("#text", "") if images else ""
|
|
|
|
|
|
def _safe_int(value: str | int | None, default: int = 0) -> int:
|
|
if value is None:
|
|
return default
|
|
try:
|
|
return int(value)
|
|
except (ValueError, TypeError):
|
|
return default
|
|
|
|
|
|
def _safe_float(value: str | float | None, default: float = 0.0) -> float:
|
|
if value is None:
|
|
return default
|
|
try:
|
|
return float(value)
|
|
except (ValueError, TypeError):
|
|
return default
|
|
|
|
|
|
def parse_top_artist(item: dict) -> LastFmArtist:
|
|
return LastFmArtist(
|
|
name=item.get("name", ""),
|
|
mbid=item.get("mbid") or None,
|
|
playcount=_safe_int(item.get("playcount")),
|
|
listeners=_safe_int(item.get("listeners")),
|
|
url=item.get("url", ""),
|
|
)
|
|
|
|
|
|
def parse_top_album(item: dict) -> LastFmAlbum:
|
|
artist = item.get("artist", {})
|
|
artist_name = artist.get("name", "") if isinstance(artist, dict) else str(artist)
|
|
return LastFmAlbum(
|
|
name=item.get("name", ""),
|
|
artist_name=artist_name,
|
|
mbid=item.get("mbid") or None,
|
|
playcount=_safe_int(item.get("playcount")),
|
|
listeners=_safe_int(item.get("listeners")),
|
|
url=item.get("url", ""),
|
|
image_url=_extract_image(item.get("image")),
|
|
)
|
|
|
|
|
|
def parse_top_track(item: dict) -> LastFmTrack:
|
|
artist = item.get("artist", {})
|
|
artist_name = artist.get("name", "") if isinstance(artist, dict) else str(artist)
|
|
return LastFmTrack(
|
|
name=item.get("name", ""),
|
|
artist_name=artist_name,
|
|
mbid=item.get("mbid") or None,
|
|
playcount=_safe_int(item.get("playcount")),
|
|
listeners=_safe_int(item.get("listeners")),
|
|
url=item.get("url", ""),
|
|
)
|
|
|
|
|
|
def parse_similar_artist(item: dict) -> LastFmSimilarArtist:
|
|
return LastFmSimilarArtist(
|
|
name=item.get("name", ""),
|
|
mbid=item.get("mbid") or None,
|
|
match=_safe_float(item.get("match")),
|
|
url=item.get("url", ""),
|
|
)
|
|
|
|
|
|
def parse_artist_info(data: dict) -> LastFmArtistInfo:
|
|
artist = data.get("artist", {})
|
|
stats = artist.get("stats", {})
|
|
tags_data = artist.get("tags", {}).get("tag", [])
|
|
similar_data = artist.get("similar", {}).get("artist", [])
|
|
bio = artist.get("bio", {})
|
|
return LastFmArtistInfo(
|
|
name=artist.get("name", ""),
|
|
mbid=artist.get("mbid") or None,
|
|
listeners=_safe_int(stats.get("listeners")),
|
|
playcount=_safe_int(stats.get("playcount")),
|
|
url=artist.get("url", ""),
|
|
bio_summary=bio.get("summary", ""),
|
|
tags=[LastFmTag(name=t.get("name", ""), url=t.get("url", "")) for t in tags_data],
|
|
similar=[parse_similar_artist(s) for s in similar_data],
|
|
)
|
|
|
|
|
|
def parse_album_info(data: dict) -> LastFmAlbumInfo:
|
|
album = data.get("album", {})
|
|
tags_data = album.get("tags", {}).get("tag", [])
|
|
tracks_data = album.get("tracks", {}).get("track", [])
|
|
wiki = album.get("wiki", {})
|
|
tracks = [
|
|
LastFmAlbumTrack(
|
|
name=t.get("name", ""),
|
|
duration=_safe_int(t.get("duration")),
|
|
rank=_safe_int(t.get("@attr", {}).get("rank")),
|
|
url=t.get("url", ""),
|
|
)
|
|
for t in tracks_data
|
|
] if tracks_data else None
|
|
return LastFmAlbumInfo(
|
|
name=album.get("name", ""),
|
|
artist_name=album.get("artist", ""),
|
|
mbid=album.get("mbid") or None,
|
|
listeners=_safe_int(album.get("listeners")),
|
|
playcount=_safe_int(album.get("playcount")),
|
|
url=album.get("url", ""),
|
|
image_url=_extract_image(album.get("image")),
|
|
summary=wiki.get("summary", ""),
|
|
tags=[LastFmTag(name=t.get("name", ""), url=t.get("url", "")) for t in tags_data],
|
|
tracks=tracks,
|
|
)
|
|
|
|
|
|
def parse_recent_track(item: dict) -> LastFmRecentTrack:
|
|
artist = item.get("artist", {})
|
|
album = item.get("album", {})
|
|
date = item.get("date", {})
|
|
attr = item.get("@attr", {})
|
|
return LastFmRecentTrack(
|
|
track_name=item.get("name", ""),
|
|
artist_name=artist.get("#text", "") if isinstance(artist, dict) else str(artist),
|
|
album_name=album.get("#text", "") if isinstance(album, dict) else str(album),
|
|
artist_mbid=artist.get("mbid") or None if isinstance(artist, dict) else None,
|
|
album_mbid=album.get("mbid") or None if isinstance(album, dict) else None,
|
|
timestamp=_safe_int(date.get("uts")) if isinstance(date, dict) else 0,
|
|
now_playing=attr.get("nowplaying") == "true",
|
|
image_url=_extract_image(item.get("image")),
|
|
)
|
|
|
|
|
|
def parse_loved_track(item: dict) -> LastFmLovedTrack:
|
|
artist = item.get("artist", {})
|
|
album = item.get("album", {})
|
|
return LastFmLovedTrack(
|
|
track_name=item.get("name", ""),
|
|
artist_name=artist.get("name", "") if isinstance(artist, dict) else str(artist),
|
|
album_name=album.get("#text", "") if isinstance(album, dict) else str(album),
|
|
track_mbid=item.get("mbid") or None,
|
|
artist_mbid=artist.get("mbid") if isinstance(artist, dict) else None,
|
|
url=item.get("url", ""),
|
|
image_url=_extract_image(item.get("image")),
|
|
)
|