retire lastfm endpoints/minor formatting

This commit is contained in:
2025-09-23 13:44:54 -04:00
parent 19afb287cd
commit d6658512d8
9 changed files with 4 additions and 753 deletions

View File

@@ -94,7 +94,6 @@ routes: dict = {
"lyrics": importlib.import_module("endpoints.lyric_search").LyricSearch( "lyrics": importlib.import_module("endpoints.lyric_search").LyricSearch(
app, util, constants app, util, constants
), ),
"lastfm": importlib.import_module("endpoints.lastfm").LastFM(app, util, constants),
"yt": importlib.import_module("endpoints.yt").YT(app, util, constants), "yt": importlib.import_module("endpoints.yt").YT(app, util, constants),
"radio": importlib.import_module("endpoints.radio").Radio( "radio": importlib.import_module("endpoints.radio").Radio(
app, util, constants, loop app, util, constants, loop

View File

@@ -4,84 +4,6 @@ from pydantic import BaseModel
Station = Literal["main", "rock", "rap", "electronic", "pop"] Station = Literal["main", "rock", "rap", "electronic", "pop"]
"""
LastFM
"""
class LastFMException(Exception):
pass
class ValidArtistSearchRequest(BaseModel):
"""
Request model for searching an artist by name.
Attributes:
- **a** (str): Artist name.
"""
a: str
model_config = {
"json_schema_extra": {
"examples": [
{
"a": "eminem",
}
]
}
}
class ValidAlbumDetailRequest(BaseModel):
"""
Request model for album details.
Attributes:
- **a** (str): Artist name.
- **release** (str): Album/release name.
"""
a: str
release: str
model_config = {
"json_schema_extra": {
"examples": [
{
"a": "eminem",
"release": "houdini",
}
]
}
}
class ValidTrackInfoRequest(BaseModel):
"""
Request model for track info.
Attributes:
- **a** (str): Artist name.
- **t** (str): Track name.
"""
a: str
t: str
model_config = {
"json_schema_extra": {
"examples": [
{
"a": "eminem",
"t": "rap god",
}
]
}
}
""" """
Rand Msg Rand Msg
""" """

View File

@@ -1,255 +0,0 @@
import importlib
import logging
import traceback
from typing import Optional, Union
from fastapi import FastAPI, Depends
from fastapi_throttle import RateLimiter
from fastapi.responses import JSONResponse
from .constructors import (
ValidArtistSearchRequest,
ValidAlbumDetailRequest,
ValidTrackInfoRequest,
LastFMException,
)
class LastFM(FastAPI):
"""Last.FM Endpoints"""
def __init__(self, app: FastAPI, util, constants) -> None:
"""Initialize LastFM endpoints."""
self.app: FastAPI = app
self.util = util
self.constants = constants
self.lastfm = importlib.import_module("utils.lastfm_wrapper").LastFM()
self.endpoints: dict = {
"lastfm/get_artist_by_name": self.artist_by_name_handler,
"lastfm/get_artist_albums": self.artist_album_handler,
"lastfm/get_release": self.release_detail_handler,
"lastfm/get_release_tracklist": self.release_tracklist_handler,
"lastfm/get_track_info": self.track_info_handler,
# tbd
}
for endpoint, handler in self.endpoints.items():
app.add_api_route(
f"/{endpoint}",
handler,
methods=["POST"],
include_in_schema=True,
dependencies=[Depends(RateLimiter(times=2, seconds=2))],
)
async def artist_by_name_handler(
self, data: ValidArtistSearchRequest
) -> JSONResponse:
"""
Get artist information by name.
Parameters:
- **data** (ValidArtistSearchRequest): Request containing artist name.
Returns:
- **JSONResponse**: Contains artist information or an error message.
"""
artist: Optional[str] = data.a.strip()
if not artist:
return JSONResponse(
content={
"err": True,
"errorText": "No artist specified",
}
)
artist_result = await self.lastfm.search_artist(artist=artist)
if (
not artist_result
or not artist_result.get("bio")
or "err" in artist_result.keys()
):
return JSONResponse(
status_code=500,
content={
"err": True,
"errorText": "Search failed (no results?)",
},
)
return JSONResponse(
content={
"success": True,
"result": artist_result,
}
)
async def artist_album_handler(
self, data: ValidArtistSearchRequest
) -> JSONResponse:
"""
Get artist's albums/releases.
Parameters:
- **data** (ValidArtistSearchRequest): Request containing artist name.
Returns:
- **JSONResponse**: Contains a list of albums or an error message.
"""
artist: str = data.a.strip()
if not artist:
return JSONResponse(
status_code=500,
content={
"err": True,
"errorText": "Invalid request: No artist specified",
},
)
album_result: Union[dict, list[dict]] = await self.lastfm.get_artist_albums(
artist=artist
)
if isinstance(album_result, dict):
return JSONResponse(
status_code=500,
content={
"err": True,
"errorText": "General failure.",
},
)
album_result_out: list = []
seen_release_titles: list = []
for release in album_result:
release_title: str = release.get("title", "Unknown")
if release_title.lower() in seen_release_titles:
continue
seen_release_titles.append(release_title.lower())
album_result_out.append(release)
return JSONResponse(content={"success": True, "result": album_result_out})
async def release_detail_handler(
self, data: ValidAlbumDetailRequest
) -> JSONResponse:
"""
Get details of a particular release by an artist.
Parameters:
- **data** (ValidAlbumDetailRequest): Request containing artist and release name.
Returns:
- **JSONResponse**: Release details or error.
"""
artist: str = data.a.strip()
release: str = data.release.strip()
if not artist or not release:
return JSONResponse(
status_code=500,
content={
"err": True,
"errorText": "Invalid request",
},
)
release_result = await self.lastfm.get_release(artist=artist, album=release)
ret_obj = {
"id": release_result.get("id"),
"artists": release_result.get("artists"),
"title": release_result.get("title"),
"summary": release_result.get("summary"),
"tracks": release_result.get("tracks"),
}
return JSONResponse(
content={
"success": True,
"result": ret_obj,
}
)
async def release_tracklist_handler(
self, data: ValidAlbumDetailRequest
) -> JSONResponse:
"""
Get track list for a particular release by an artist.
Parameters:
- **data** (ValidAlbumDetailRequest): Request containing artist and release name.
Returns:
- **JSONResponse**: Track list or error.
"""
artist: str = data.a.strip()
release: str = data.release.strip()
if not artist or not release:
return JSONResponse(
status_code=500,
content={
"err": True,
"errorText": "Invalid request",
},
)
tracklist_result: dict = await self.lastfm.get_album_tracklist(
artist=artist, album=release
)
return JSONResponse(
content={
"success": True,
"id": tracklist_result.get("id"),
"artists": tracklist_result.get("artists"),
"title": tracklist_result.get("title"),
"summary": tracklist_result.get("summary"),
"tracks": tracklist_result.get("tracks"),
}
)
async def track_info_handler(self, data: ValidTrackInfoRequest) -> JSONResponse:
"""
Get track info from Last.FM given an artist/track.
Parameters:
- **data** (ValidTrackInfoRequest): Request containing artist and track name.
Returns:
- **JSONResponse**: Track info or error.
"""
try:
artist: str = data.a
track: str = data.t
if not artist or not track:
return JSONResponse(
status_code=500,
content={"err": True, "errorText": "Invalid request"},
)
track_info_result: Optional[dict] = await self.lastfm.get_track_info(
artist=artist, track=track
)
if not track_info_result:
return JSONResponse(
status_code=200,
content={
"err": True,
"errorText": "Not found.",
},
)
if "err" in track_info_result:
raise LastFMException(
"Unknown error occurred: %s",
track_info_result.get("errorText", "??"),
)
return JSONResponse(content={"success": True, "result": track_info_result})
except Exception as e:
logging.debug("Exception: %s", str(e))
traceback.print_exc()
return JSONResponse(
status_code=500,
content={
"err": True,
"errorText": "General error",
},
)

View File

@@ -81,6 +81,8 @@ class LyricSearch(FastAPI):
) )
for endpoint, handler in self.endpoints.items(): for endpoint, handler in self.endpoints.items():
times: int = 20
seconds: int = 2
rate_limit: tuple[int, int] = (2, 3) # Default; (Times, Seconds) rate_limit: tuple[int, int] = (2, 3) # Default; (Times, Seconds)
_schema_include = endpoint in ["lyric/search"] _schema_include = endpoint in ["lyric/search"]

View File

@@ -5,7 +5,7 @@ from fastapi.responses import JSONResponse
from utils.sr_wrapper import SRUtil from utils.sr_wrapper import SRUtil
from auth.deps import get_current_user from auth.deps import get_current_user
from redis import Redis from redis import Redis
from rq import Queue, Retry from rq import Queue
from rq.job import Job from rq.job import Job
from rq.job import JobStatus from rq.job import JobStatus
from rq.registry import ( from rq.registry import (

View File

@@ -1,32 +0,0 @@
import asyncio
import logging
import sys
sys.path.insert(0, "..")
from utils.sr_wrapper import SRUtil
# logging.getLogger("sr_wrapper").propagate = False
logger = logging.getLogger()
logger.setLevel(logging.CRITICAL)
async def main():
sr = SRUtil()
artist_search = await sr.get_artists_by_name("Ren")
# logging.critical("Artist search: %s", artist_search)
res = [
dict(x)
for x in artist_search
if x.get("popularity", 0) and x.get("artist").lower() == "ren"
]
logging.critical("Results: %s", res)
# search_res = await sr.get_album_by_name(artist[:8], album)
# logging.critical("Search result: %s", search_res)
# album = search_res
# _cover = await sr.get_cover_by_album_id(album.get('id'), 640)
# # cover = sr._get_tidal_cover_url(album.get('cover'), 640)
# logging.critical("Result: %s, Cover: %s", album, _cover)
return
asyncio.run(main())

View File

@@ -2,7 +2,7 @@
import logging import logging
from typing import Optional from typing import Optional
from fastapi import FastAPI, Response, HTTPException from fastapi import FastAPI, HTTPException
from fastapi.responses import RedirectResponse from fastapi.responses import RedirectResponse

View File

@@ -1,7 +0,0 @@
"""
LastFM
"""
class InvalidLastFMResponseException(Exception):
pass

View File

@@ -1,378 +0,0 @@
import traceback
import logging
from typing import Optional, Union
import regex
from aiohttp import ClientSession, ClientTimeout
from constants import Constants
from .constructors import InvalidLastFMResponseException
class LastFM:
"""LastFM Endpoints"""
def __init__(self, noInit: Optional[bool] = False) -> None:
self.creds = Constants().LFM_CREDS
self.api_base_url: str = "https://ws.audioscrobbler.com/2.0"
async def search_artist(self, artist: Optional[str] = None) -> dict:
"""Search LastFM for an artist"""
try:
if not artist:
return {
"err": "No artist specified.",
}
request_params: list[tuple] = [
("method", "artist.getInfo"),
("artist", artist),
("api_key", self.creds.get("key")),
("autocorrect", "1"),
("format", "json"),
]
async with ClientSession() as session:
async with await session.get(
self.api_base_url,
params=request_params,
timeout=ClientTimeout(connect=3, sock_read=8),
) as request:
request.raise_for_status()
data: dict = await request.json()
data = data.get("artist", "N/A")
ret_obj: dict = {
"id": data.get("mbid"),
"touring": data.get("ontour"),
"name": data.get("name"),
"bio": data.get("bio", None)
.get("summary")
.strip()
.split("<a href")[0],
}
return ret_obj
except Exception as e:
logging.debug("Exception: %s", str(e))
traceback.print_exc()
return {
"err": "Failed",
}
async def get_track_info(
self, artist: Optional[str] = None, track: Optional[str] = None
) -> Optional[dict]:
"""
Get Track Info from LastFM
Args:
artist (Optional[str])
track (Optional[str])
Returns:
dict
"""
try:
if not artist or not track:
logging.info("inv request")
return {
"err": "Invalid/No artist or track specified",
}
request_params: list[tuple] = [
("method", "track.getInfo"),
("api_key", self.creds.get("key")),
("autocorrect", "1"),
("artist", artist),
("track", track),
("format", "json"),
]
async with ClientSession() as session:
async with await session.get(
self.api_base_url,
params=request_params,
timeout=ClientTimeout(connect=3, sock_read=8),
) as request:
request.raise_for_status()
data: dict = await request.json()
if not data:
return None
data = data.get("track", None)
if not isinstance(data.get("artist"), dict):
return None
artist_mbid: int = data.get("artist", None).get("mbid")
album: str = data.get("album", None)
if not isinstance(album, dict):
return None
album = album.get("title")
ret_obj: dict = {
"artist_mbid": artist_mbid,
"album": album,
}
return ret_obj
except Exception as e:
traceback.print_exc()
logging.debug("Exception: %s", str(e))
return {
"err": "General Failure",
}
async def get_album_tracklist(
self, artist: Optional[str] = None, album: Optional[str] = None
) -> dict:
"""
Get Album Tracklist
Args:
artist (str)
album (str)
Returns:
dict
"""
try:
if not artist or not album:
return {
"err": "No artist or album specified",
}
tracks: dict = await self.get_release(artist=artist, album=album)
tracks = tracks.get("tracks", None)
ret_obj: dict = {
"tracks": tracks,
}
return ret_obj
except Exception as e:
traceback.print_exc()
logging.debug("Exception: %s", str(e))
return {
"err": "General Failure",
}
async def get_artist_albums(
self, artist: Optional[str] = None
) -> Union[dict, list[dict]]:
"""
Get Artists Albums from LastFM
Args:
artist (Optional[str])
Returns:
Union[dict, list[dict]]
"""
try:
if not artist:
return {
"err": "No artist specified.",
}
request_params: list[tuple] = [
("method", "artist.gettopalbums"),
("artist", artist),
("api_key", self.creds.get("key")),
("autocorrect", "1"),
("format", "json"),
]
async with ClientSession() as session:
async with await session.get(
self.api_base_url,
params=request_params,
timeout=ClientTimeout(connect=3, sock_read=8),
) as request:
request.raise_for_status()
json_data: dict = await request.json()
data: dict = json_data.get("topalbums", None).get("album")
ret_obj: list = [
{"title": item.get("name")}
for item in data
if not (item.get("name").lower() == "(null)")
and int(item.get("playcount")) >= 50
]
return ret_obj
except Exception as e:
logging.debug("Exception: %s", str(e))
traceback.print_exc()
return {
"err": "Failed",
}
async def get_artist_id(self, artist: Optional[str] = None) -> int:
"""
Get Artist ID from LastFM
Args:
artist (Optional[str])
Returns:
int
"""
try:
if not artist:
return -1
artist_search: dict = await self.search_artist(artist=artist)
if not artist_search:
logging.debug("[get_artist_id] Throwing no result error")
return -1
artist_id: int = int(artist_search[0].get("id", 0))
return artist_id
except Exception as e:
logging.debug("Exception: %s", str(e))
traceback.print_exc()
return -1
async def get_artist_info_by_id(self, artist_id: Optional[int] = None) -> dict:
"""
Get Artist info by ID from LastFM
Args:
artist_id (Optional[int])
Returns:
dict
"""
try:
if not artist_id or not str(artist_id).isnumeric():
return {
"err": "Invalid/no artist_id specified.",
}
req_url: str = f"{self.api_base_url}/artists/{artist_id}"
request_params: list[tuple] = [
("key", self.creds.get("key")),
("secret", self.creds.get("secret")),
]
async with ClientSession() as session:
async with await session.get(
req_url,
params=request_params,
timeout=ClientTimeout(connect=3, sock_read=8),
) as request:
request.raise_for_status()
data: dict = await request.json()
if not data.get("profile"):
raise InvalidLastFMResponseException(
"Data did not contain 'profile' key."
)
_id: int = data.get("id", None)
name: str = data.get("name", None)
profile: str = data.get("profile", "")
profile = regex.sub(r"(\[(\/{0,})(u|b|i)])", "", profile)
members: list = data.get("members", None)
ret_obj: dict = {
"id": _id,
"name": name,
"profile": profile,
"members": members,
}
return ret_obj
except Exception as e:
logging.debug("Exception: %s", str(e))
traceback.print_exc()
return {
"err": "Failed",
}
async def get_artist_info(self, artist: Optional[str] = None) -> dict:
"""
Get Artist Info from LastFM
Args:
artist (Optional[str])
Returns:
dict
"""
try:
if not artist:
return {
"err": "No artist specified.",
}
artist_id: Optional[int] = await self.get_artist_id(artist=artist)
if not artist_id:
return {
"err": "Failed",
}
artist_info: Optional[dict] = await self.get_artist_info_by_id(
artist_id=artist_id
)
if not artist_info:
return {
"err": "Failed",
}
return artist_info
except Exception as e:
logging.debug("Exception: %s", str(e))
traceback.print_exc()
return {
"err": "Failed",
}
async def get_release(
self, artist: Optional[str] = None, album: Optional[str] = None
) -> dict:
"""
Get Release info from LastFM
Args:
artist (Optional[str])
album (Optional[str])
Returns:
dict
"""
try:
if not artist or not album:
return {
"err": "Invalid artist/album pair",
}
request_params: list[tuple] = [
("method", "album.getinfo"),
("artist", artist),
("album", album),
("api_key", self.creds.get("key")),
("autocorrect", "1"),
("format", "json"),
]
async with ClientSession() as session:
async with await session.get(
self.api_base_url,
params=request_params,
timeout=ClientTimeout(connect=3, sock_read=8),
) as request:
request.raise_for_status()
json_data: dict = await request.json()
data: dict = json_data.get("album", None)
ret_obj: dict = {
"id": data.get("mbid"),
"artists": data.get("artist"),
"tags": data.get("tags"),
"title": data.get("name"),
"summary": (
data.get("wiki", None).get("summary").split("<a href")[0]
if "wiki" in data.keys()
else "No summary available for this release."
),
}
try:
track_key: list = data.get("tracks", None).get("track")
except Exception as e:
logging.debug("Exception: %s", str(e))
track_key = []
if isinstance(track_key, list):
ret_obj["tracks"] = [
{
"duration": item.get("duration", "N/A"),
"title": item.get("name"),
}
for item in track_key
]
else:
ret_obj["tracks"] = [
{
"duration": data.get("tracks")
.get("track")
.get("duration"),
"title": data.get("tracks").get("track").get("name"),
}
]
return ret_obj
except Exception as e:
logging.debug("Exception: %s", str(e))
traceback.print_exc()
return {
"err": "Failed",
}