cleanup
This commit is contained in:
parent
60416c493f
commit
39d1ddaffa
125
endpoints/ai.py
125
endpoints/ai.py
@ -2,26 +2,24 @@
|
||||
# pylint: disable=bare-except, broad-exception-caught, invalid-name
|
||||
|
||||
import logging
|
||||
import traceback
|
||||
import regex
|
||||
from regex import Pattern
|
||||
from typing import Union
|
||||
from aiohttp import ClientSession, ClientTimeout
|
||||
from fastapi import FastAPI, Request, HTTPException, BackgroundTasks
|
||||
from .constructors import ValidHookSongRequest, ValidAISongRequest
|
||||
from fastapi import FastAPI, Request, HTTPException
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
class AI(FastAPI):
|
||||
"""AI Endpoints"""
|
||||
def __init__(self, app: FastAPI, my_util, constants): # pylint: disable=super-init-not-called
|
||||
self.app = app
|
||||
def __init__(self, app: FastAPI,
|
||||
my_util, constants): # pylint: disable=super-init-not-called
|
||||
self.app: FastAPI = app
|
||||
self.util = my_util
|
||||
self.constants = constants
|
||||
self.url_clean_regex: Pattern = regex.compile(r'^\/ai\/(openai|base)\/')
|
||||
self.endpoints: dict = {
|
||||
"ai/openai": self.ai_openai_handler,
|
||||
"ai/base": self.ai_handler,
|
||||
"ai/song": self.ai_song_handler,
|
||||
"ai/hook": self.ai_hook_handler,
|
||||
#tbd
|
||||
}
|
||||
|
||||
@ -29,44 +27,7 @@ class AI(FastAPI):
|
||||
app.add_api_route(f"/{endpoint}", handler, methods=["GET", "POST"],
|
||||
include_in_schema=False)
|
||||
|
||||
|
||||
async def respond_via_webhook(self, data: ValidHookSongRequest, originalRequest: Request) -> bool:
|
||||
"""Respond via Webhook"""
|
||||
try:
|
||||
logging.debug("Request received: %s", data)
|
||||
data2 = data.copy()
|
||||
del data2.hook
|
||||
|
||||
if not data.hook:
|
||||
return False
|
||||
|
||||
response = await self.ai_song_handler(data2, originalRequest)
|
||||
if not response.get('resp'):
|
||||
logging.critical("NO RESP!")
|
||||
return False
|
||||
response = response.get('resp')
|
||||
hook_data = {
|
||||
'username': 'Claude',
|
||||
"embeds": [{
|
||||
"title": "Claude's Feedback",
|
||||
"description": response,
|
||||
"footer": {
|
||||
"text": "Current model: claude-3-haiku-20240307",
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
async with ClientSession() as session:
|
||||
async with await session.post(data.hook, json=hook_data,
|
||||
timeout=ClientTimeout(connect=5, sock_read=5), headers={
|
||||
'content-type': 'application/json; charset=utf-8',}) as request:
|
||||
request.raise_for_status()
|
||||
return True
|
||||
except:
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
async def ai_handler(self, request: Request):
|
||||
async def ai_handler(self, request: Request) -> JSONResponse:
|
||||
"""
|
||||
/ai/base
|
||||
AI BASE Request
|
||||
@ -88,15 +49,15 @@ class AI(FastAPI):
|
||||
headers=local_llm_headers,
|
||||
timeout=ClientTimeout(connect=15, sock_read=30)) as out_request:
|
||||
response = await out_request.json()
|
||||
return response
|
||||
return JSONResponse(content=response)
|
||||
except Exception as e: # pylint: disable=broad-exception-caught
|
||||
logging.error("Error: %s", e)
|
||||
return {
|
||||
return JSONResponse(status_code=500, content={
|
||||
'err': True,
|
||||
'errorText': 'General Failure'
|
||||
}
|
||||
})
|
||||
|
||||
async def ai_openai_handler(self, request: Request):
|
||||
async def ai_openai_handler(self, request: Request) -> JSONResponse:
|
||||
"""
|
||||
/ai/openai
|
||||
AI Request
|
||||
@ -122,70 +83,10 @@ class AI(FastAPI):
|
||||
headers=local_llm_headers,
|
||||
timeout=ClientTimeout(connect=15, sock_read=30)) as out_request:
|
||||
response = await out_request.json()
|
||||
return response
|
||||
return JSONResponse(content=response)
|
||||
except Exception as e: # pylint: disable=broad-exception-caught
|
||||
logging.error("Error: %s", e)
|
||||
return {
|
||||
return JSONResponse(status_code=500, content={
|
||||
'err': True,
|
||||
'errorText': 'General Failure'
|
||||
}
|
||||
|
||||
async def ai_hook_handler(self, data: ValidHookSongRequest, request: Request, background_tasks: BackgroundTasks):
|
||||
"""AI Hook Handler"""
|
||||
background_tasks.add_task(self.respond_via_webhook, data, request)
|
||||
return {
|
||||
'success': True,
|
||||
}
|
||||
|
||||
async def ai_song_handler(self, data: Union[ValidAISongRequest, ValidHookSongRequest], request: Request):
|
||||
"""
|
||||
/ai/song
|
||||
AI (Song Info) Request [Public]
|
||||
"""
|
||||
ai_prompt = "You are a helpful assistant who will provide tidbits of info on songs the user may listen to."
|
||||
ai_question = f"I am going to listen to the song \"{data.s}\" by \"{data.a}\"."
|
||||
local_llm_headers = {
|
||||
'x-api-key': self.constants.CLAUDE_API_KEY,
|
||||
'anthropic-version': '2023-06-01',
|
||||
'content-type': 'application/json',
|
||||
}
|
||||
|
||||
request_data = {
|
||||
'model': 'claude-3-haiku-20240307',
|
||||
'max_tokens': 512,
|
||||
'temperature': 0.6,
|
||||
'system': ai_prompt,
|
||||
'messages': [
|
||||
{
|
||||
"role": "user",
|
||||
"content": ai_question.strip(),
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
try:
|
||||
async with ClientSession() as session:
|
||||
async with await session.post('https://api.anthropic.com/v1/messages',
|
||||
json=request_data,
|
||||
headers=local_llm_headers,
|
||||
timeout=ClientTimeout(connect=15, sock_read=30)) as aiohttp_request:
|
||||
response = await aiohttp_request.json()
|
||||
logging.debug("Response: %s",
|
||||
response)
|
||||
if response.get('type') == 'error':
|
||||
error_type = response.get('error').get('type')
|
||||
error_message = response.get('error').get('message')
|
||||
result = {
|
||||
'resp': f"{error_type} error ({error_message})"
|
||||
}
|
||||
else:
|
||||
result = {
|
||||
'resp': response.get('content')[0].get('text').strip()
|
||||
}
|
||||
return result
|
||||
except Exception as e: # pylint: disable=broad-exception-caught
|
||||
logging.error("Error: %s", e)
|
||||
return {
|
||||
'err': True,
|
||||
'errorText': 'General Failure'
|
||||
}
|
||||
})
|
@ -6,30 +6,6 @@ from pydantic import BaseModel
|
||||
# Constructors
|
||||
# TODO: REORDER
|
||||
|
||||
"""
|
||||
AI
|
||||
"""
|
||||
|
||||
class ValidAISongRequest(BaseModel):
|
||||
"""
|
||||
- **a**: artist
|
||||
- **s**: track title
|
||||
"""
|
||||
|
||||
a: str
|
||||
s: str
|
||||
|
||||
class ValidHookSongRequest(BaseModel):
|
||||
"""
|
||||
- **a**: artist
|
||||
- **s**: track title
|
||||
- **hook**: hook to return
|
||||
"""
|
||||
|
||||
a: str
|
||||
s: str
|
||||
hook: str | None = ""
|
||||
|
||||
"""
|
||||
Karma
|
||||
"""
|
||||
@ -58,7 +34,7 @@ class ValidTopKarmaRequest(BaseModel):
|
||||
"""
|
||||
- **n**: Number of top results to return (default: 10)
|
||||
"""
|
||||
n: int | None = 10
|
||||
n: Optional[int] = 10
|
||||
|
||||
"""
|
||||
LastFM
|
||||
@ -124,7 +100,7 @@ class RandMsgRequest(BaseModel):
|
||||
- **short**: Short randmsg?
|
||||
"""
|
||||
|
||||
short: Optional[bool] = False
|
||||
short: Optional[bool]
|
||||
|
||||
"""
|
||||
YT
|
||||
@ -152,7 +128,7 @@ class ValidXCRequest(BaseModel):
|
||||
key: str
|
||||
bid: int
|
||||
cmd: str
|
||||
data: dict | None = None
|
||||
data: Optional[dict]
|
||||
|
||||
"""
|
||||
Transcriptions
|
||||
@ -190,14 +166,14 @@ class ValidLyricRequest(BaseModel):
|
||||
- **excluded_sources**: sources to exclude (new only)
|
||||
"""
|
||||
|
||||
a: str | None = None
|
||||
s: str | None = None
|
||||
t: str | None = None
|
||||
sub: str | None = None
|
||||
extra: bool | None = False
|
||||
lrc: bool | None = False
|
||||
a: Optional[str] = None
|
||||
s: Optional[str] = None
|
||||
t: Optional[str] = None
|
||||
sub: Optional[str] = None
|
||||
extra: Optional[bool] = False
|
||||
lrc: Optional[bool] = False
|
||||
src: str
|
||||
excluded_sources: list | None = None
|
||||
excluded_sources: Optional[list] = None
|
||||
|
||||
model_config = {
|
||||
"json_schema_extra": {
|
||||
@ -218,7 +194,7 @@ class ValidTypeAheadRequest(BaseModel):
|
||||
"""
|
||||
- **query**: query string
|
||||
"""
|
||||
pre_query: str|None = None
|
||||
pre_query: Optional[str] = None
|
||||
query: str
|
||||
|
||||
"""
|
||||
@ -237,10 +213,10 @@ class ValidRadioSongRequest(BaseModel):
|
||||
- **alsoSkip**: Whether to skip immediately to this track [not implemented]
|
||||
"""
|
||||
key: str
|
||||
artist: str | None = None
|
||||
song: str | None = None
|
||||
artistsong: str | None = None
|
||||
alsoSkip: bool = False
|
||||
artist: Optional[str] = None
|
||||
song: Optional[str] = None
|
||||
artistsong: Optional[str] = None
|
||||
alsoSkip: Optional[bool] = False
|
||||
|
||||
class ValidRadioQueueGetRequest(BaseModel):
|
||||
"""
|
||||
@ -248,8 +224,8 @@ class ValidRadioQueueGetRequest(BaseModel):
|
||||
- **limit**: optional, default: 15k
|
||||
"""
|
||||
|
||||
key: str|None = None
|
||||
limit: int|None = 15000
|
||||
key: Optional[str] = None
|
||||
limit: Optional[int] = 15_000
|
||||
|
||||
class ValidRadioNextRequest(BaseModel):
|
||||
"""
|
||||
@ -257,7 +233,7 @@ class ValidRadioNextRequest(BaseModel):
|
||||
- **skipTo**: UUID to skip to [optional]
|
||||
"""
|
||||
key: str
|
||||
skipTo: str|None = None
|
||||
skipTo: Optional[str] = None
|
||||
|
||||
class ValidRadioReshuffleRequest(ValidRadioNextRequest):
|
||||
"""
|
||||
@ -272,7 +248,7 @@ class ValidRadioQueueShiftRequest(BaseModel):
|
||||
"""
|
||||
key: str
|
||||
uuid: str
|
||||
next: bool = False
|
||||
next: Optional[bool] = False
|
||||
|
||||
class ValidRadioQueueRemovalRequest(BaseModel):
|
||||
"""
|
||||
|
@ -7,8 +7,9 @@ import time
|
||||
import datetime
|
||||
import traceback
|
||||
import aiosqlite as sqlite3
|
||||
from typing import LiteralString, Optional
|
||||
from typing import LiteralString, Optional, Union
|
||||
from fastapi import FastAPI, Request, HTTPException
|
||||
from fastapi.responses import JSONResponse
|
||||
from .constructors import ValidTopKarmaRequest, ValidKarmaRetrievalRequest,\
|
||||
ValidKarmaUpdateRequest
|
||||
|
||||
@ -18,12 +19,12 @@ class KarmaDB:
|
||||
self.db_path: LiteralString = os.path.join("/", "usr", "local", "share",
|
||||
"sqlite_dbs", "karma.db")
|
||||
|
||||
async def get_karma(self, keyword: str) -> int | dict:
|
||||
async def get_karma(self, keyword: str) -> Union[int, dict]:
|
||||
"""Get Karma Value for Keyword
|
||||
Args:
|
||||
keyword (str): The keyword to search
|
||||
Returns:
|
||||
int|dict
|
||||
Union[int, dict]
|
||||
"""
|
||||
async with sqlite3.connect(self.db_path, timeout=2) as db_conn:
|
||||
async with await db_conn.execute("SELECT score FROM karma WHERE keyword LIKE ? LIMIT 1", (keyword,)) as db_cursor:
|
||||
@ -37,7 +38,8 @@ class KarmaDB:
|
||||
}
|
||||
|
||||
async def get_top(self, n: Optional[int] = 10) -> Optional[list[tuple]]:
|
||||
"""Get Top n=10 Karma Entries
|
||||
"""
|
||||
Get Top n=10 Karma Entries
|
||||
Args:
|
||||
n (Optional[int]) = 10: The number of top results to return
|
||||
Returns:
|
||||
@ -51,8 +53,10 @@ class KarmaDB:
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
async def update_karma(self, granter: str, keyword: str, flag: int) -> Optional[bool]:
|
||||
"""Update Karma for Keyword
|
||||
async def update_karma(self, granter: str, keyword: str,
|
||||
flag: int) -> Optional[bool]:
|
||||
"""
|
||||
Update Karma for Keyword
|
||||
Args:
|
||||
granter (str): The user who granted (increased/decreased) the karma
|
||||
keyword (str): The keyword to update
|
||||
@ -93,9 +97,11 @@ class KarmaDB:
|
||||
return False
|
||||
|
||||
class Karma(FastAPI):
|
||||
"""Karma Endpoints"""
|
||||
def __init__(self, app: FastAPI, util, constants): # pylint: disable=super-init-not-called
|
||||
self.app = app
|
||||
"""
|
||||
Karma Endpoints
|
||||
"""
|
||||
def __init__(self, app: FastAPI, util, constants) -> None: # pylint: disable=super-init-not-called
|
||||
self.app: FastAPI = app
|
||||
self.util = util
|
||||
self.constants = constants
|
||||
self.db = KarmaDB()
|
||||
@ -111,8 +117,11 @@ class Karma(FastAPI):
|
||||
include_in_schema=False)
|
||||
|
||||
|
||||
async def top_karma_handler(self, request: Request, data: ValidTopKarmaRequest | None = None) -> list[tuple]|dict:
|
||||
"""Get top keywords for karma"""
|
||||
async def top_karma_handler(self, request: Request,
|
||||
data: Optional[ValidTopKarmaRequest] = None) -> JSONResponse:
|
||||
"""
|
||||
Get top keywords for karma
|
||||
"""
|
||||
|
||||
if not self.util.check_key(request.url.path, request.headers.get('X-Authd-With')):
|
||||
raise HTTPException(status_code=403, detail="Unauthorized")
|
||||
@ -125,19 +134,20 @@ class Karma(FastAPI):
|
||||
try:
|
||||
top10: Optional[list[tuple]] = await self.db.get_top(n=n)
|
||||
if not top10:
|
||||
return {
|
||||
return JSONResponse(status_code=500, content={
|
||||
'err': True,
|
||||
'errorText': 'General failure',
|
||||
}
|
||||
return top10
|
||||
})
|
||||
return JSONResponse(content=top10)
|
||||
except:
|
||||
traceback.print_exc()
|
||||
return {
|
||||
return JSONResponse(status_code=500, content={
|
||||
'err': True,
|
||||
'errorText': 'Exception occurred.',
|
||||
}
|
||||
})
|
||||
|
||||
async def get_karma_handler(self, data: ValidKarmaRetrievalRequest, request: Request):
|
||||
async def get_karma_handler(self, data: ValidKarmaRetrievalRequest,
|
||||
request: Request) -> JSONResponse:
|
||||
"""Get current karma value"""
|
||||
|
||||
if not self.util.check_key(request.url.path, request.headers.get('X-Authd-With')):
|
||||
@ -145,30 +155,32 @@ class Karma(FastAPI):
|
||||
|
||||
keyword: str = data.keyword
|
||||
try:
|
||||
count: int|dict = await self.db.get_karma(keyword)
|
||||
return {
|
||||
count: Union[int, dict] = await self.db.get_karma(keyword)
|
||||
return JSONResponse(content={
|
||||
'keyword': keyword,
|
||||
'count': count,
|
||||
}
|
||||
})
|
||||
except:
|
||||
traceback.print_exc()
|
||||
return {
|
||||
return JSONResponse(status_code=500, content={
|
||||
'err': True,
|
||||
'errorText': "Exception occurred."
|
||||
}
|
||||
'errorText': "Exception occurred.",
|
||||
})
|
||||
|
||||
async def modify_karma_handler(self, data: ValidKarmaUpdateRequest, request: Request) -> dict:
|
||||
async def modify_karma_handler(self, data: ValidKarmaUpdateRequest,
|
||||
request: Request) -> JSONResponse:
|
||||
"""Update karma count"""
|
||||
|
||||
if not self.util.check_key(request.url.path, request.headers.get('X-Authd-With'), 2):
|
||||
raise HTTPException(status_code=403, detail="Unauthorized")
|
||||
|
||||
if not data.flag in [0, 1]:
|
||||
return {
|
||||
return JSONResponse(status_code=500, content={
|
||||
'err': True,
|
||||
'errorText': 'Invalid request'
|
||||
}
|
||||
'errorText': 'Invalid request',
|
||||
})
|
||||
|
||||
return {
|
||||
'success': await self.db.update_karma(data.granter, data.keyword, data.flag)
|
||||
}
|
||||
return JSONResponse(content={
|
||||
'success': await self.db.update_karma(data.granter,
|
||||
data.keyword, data.flag)
|
||||
})
|
@ -3,15 +3,17 @@
|
||||
|
||||
import importlib
|
||||
import traceback
|
||||
from typing import Optional
|
||||
from typing import Optional, Union
|
||||
from fastapi import FastAPI
|
||||
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: # pylint: disable=super-init-not-called
|
||||
self.app = app
|
||||
def __init__(self, app: FastAPI,
|
||||
util, constants) -> None: # pylint: disable=super-init-not-called
|
||||
self.app: FastAPI = app
|
||||
self.util = util
|
||||
self.constants = constants
|
||||
self.lastfm = importlib.import_module("lastfm_wrapper").LastFM()
|
||||
@ -29,43 +31,43 @@ class LastFM(FastAPI):
|
||||
app.add_api_route(f"/{endpoint}", handler, methods=["POST"],
|
||||
include_in_schema=True)
|
||||
|
||||
async def artist_by_name_handler(self, data: ValidArtistSearchRequest):
|
||||
async def artist_by_name_handler(self, data: ValidArtistSearchRequest) -> JSONResponse:
|
||||
"""
|
||||
Get artist info
|
||||
- **a**: Artist to search
|
||||
"""
|
||||
artist: Optional[str] = data.a.strip()
|
||||
if not artist:
|
||||
return {
|
||||
return JSONResponse(content={
|
||||
'err': True,
|
||||
'errorText': 'No artist specified'
|
||||
}
|
||||
'errorText': 'No artist specified',
|
||||
})
|
||||
|
||||
artist_result = await self.lastfm.search_artist(artist=artist)
|
||||
if not artist_result or "err" in artist_result.keys():
|
||||
return {
|
||||
return JSONResponse(status_code=500, content={
|
||||
'err': True,
|
||||
'errorText': 'Search failed (no results?)'
|
||||
}
|
||||
'errorText': 'Search failed (no results?)',
|
||||
})
|
||||
|
||||
return {
|
||||
return JSONResponse(content={
|
||||
'success': True,
|
||||
'result': artist_result
|
||||
}
|
||||
'result': artist_result,
|
||||
})
|
||||
|
||||
async def artist_album_handler(self, data: ValidArtistSearchRequest) -> dict:
|
||||
async def artist_album_handler(self, data: ValidArtistSearchRequest) -> JSONResponse:
|
||||
"""
|
||||
Get artist's albums/releases
|
||||
- **a**: Artist to search
|
||||
"""
|
||||
artist: str = data.a.strip()
|
||||
if not artist:
|
||||
return {
|
||||
return JSONResponse(status_code=500, content={
|
||||
'err': True,
|
||||
'errorText': 'No artist specified'
|
||||
}
|
||||
'errorText': 'Invalid request: No artist specified',
|
||||
})
|
||||
|
||||
album_result: dict|list[dict] = await self.lastfm.get_artist_albums(artist=artist)
|
||||
album_result: Union[dict, list[dict]] = await self.lastfm.get_artist_albums(artist=artist)
|
||||
album_result_out: list = []
|
||||
seen_release_titles: list = []
|
||||
|
||||
@ -76,12 +78,12 @@ class LastFM(FastAPI):
|
||||
seen_release_titles.append(release_title.lower())
|
||||
album_result_out.append(release)
|
||||
|
||||
return {
|
||||
return JSONResponse(content={
|
||||
'success': True,
|
||||
'result': album_result_out
|
||||
}
|
||||
})
|
||||
|
||||
async def release_detail_handler(self, data: ValidAlbumDetailRequest) -> dict:
|
||||
async def release_detail_handler(self, data: ValidAlbumDetailRequest) -> JSONResponse:
|
||||
"""
|
||||
Get details of a particular release by an artist
|
||||
- **a**: Artist to search
|
||||
@ -91,10 +93,10 @@ class LastFM(FastAPI):
|
||||
release: str = data.release.strip()
|
||||
|
||||
if not artist or not release:
|
||||
return {
|
||||
return JSONResponse(status_code=500, content={
|
||||
'err': True,
|
||||
'errorText': 'Invalid request'
|
||||
}
|
||||
'errorText': 'Invalid request',
|
||||
})
|
||||
|
||||
release_result = await self.lastfm.get_release(artist=artist, album=release)
|
||||
ret_obj = {
|
||||
@ -102,15 +104,15 @@ class LastFM(FastAPI):
|
||||
'artists': release_result.get('artists'),
|
||||
'title': release_result.get('title'),
|
||||
'summary': release_result.get('summary'),
|
||||
'tracks': release_result.get('tracks')
|
||||
'tracks': release_result.get('tracks'),
|
||||
}
|
||||
|
||||
return {
|
||||
return JSONResponse(content={
|
||||
'success': True,
|
||||
'result': ret_obj
|
||||
}
|
||||
'result': ret_obj,
|
||||
})
|
||||
|
||||
async def release_tracklist_handler(self, data: ValidAlbumDetailRequest) -> dict:
|
||||
async def release_tracklist_handler(self, data: ValidAlbumDetailRequest) -> JSONResponse:
|
||||
"""
|
||||
Get track list for a particular release by an artist
|
||||
- **a**: Artist to search
|
||||
@ -120,22 +122,22 @@ class LastFM(FastAPI):
|
||||
release: str = data.release.strip()
|
||||
|
||||
if not artist or not release:
|
||||
return {
|
||||
return JSONResponse(status_code=500, content={
|
||||
'err': True,
|
||||
'errorText': 'Invalid request'
|
||||
}
|
||||
'errorText': 'Invalid request',
|
||||
})
|
||||
|
||||
tracklist_result: dict = await self.lastfm.get_album_tracklist(artist=artist, album=release)
|
||||
return {
|
||||
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')
|
||||
}
|
||||
'tracks': tracklist_result.get('tracks'),
|
||||
})
|
||||
|
||||
async def track_info_handler(self, data: ValidTrackInfoRequest) -> dict:
|
||||
async def track_info_handler(self, data: ValidTrackInfoRequest) -> JSONResponse:
|
||||
"""
|
||||
Get track info from Last.FM given an artist/track
|
||||
- **a**: Artist to search
|
||||
@ -146,22 +148,23 @@ class LastFM(FastAPI):
|
||||
track: str = data.t
|
||||
|
||||
if not artist or not track:
|
||||
return {
|
||||
return JSONResponse(status_code=500, content={
|
||||
'err': True,
|
||||
'errorText': 'Invalid request'
|
||||
}
|
||||
})
|
||||
|
||||
track_info_result: dict = await self.lastfm.get_track_info(artist=artist, track=track)
|
||||
track_info_result: dict = await self.lastfm.get_track_info(artist=artist,
|
||||
track=track)
|
||||
if "err" in track_info_result:
|
||||
raise LastFMException("Unknown error occurred: %s",
|
||||
track_info_result.get('errorText', '??'))
|
||||
return {
|
||||
return JSONResponse(content={
|
||||
'success': True,
|
||||
'result': track_info_result
|
||||
}
|
||||
})
|
||||
except:
|
||||
traceback.print_exc()
|
||||
return {
|
||||
return JSONResponse(status_code=500, content={
|
||||
'err': True,
|
||||
'errorText': 'General error',
|
||||
}
|
||||
})
|
||||
|
@ -7,7 +7,8 @@ import urllib.parse
|
||||
import regex
|
||||
import aiosqlite as sqlite3
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from typing import LiteralString, Optional, Callable
|
||||
from fastapi.responses import JSONResponse
|
||||
from typing import LiteralString, Optional, Union
|
||||
from regex import Pattern
|
||||
from .constructors import ValidTypeAheadRequest, ValidLyricRequest
|
||||
from lyric_search.constructors import LyricsResult
|
||||
@ -15,13 +16,18 @@ from lyric_search.sources import aggregate
|
||||
from lyric_search import notifier
|
||||
|
||||
class CacheUtils:
|
||||
"""Lyrics Cache DB Utils"""
|
||||
"""
|
||||
Lyrics Cache DB Utils
|
||||
"""
|
||||
def __init__(self) -> None:
|
||||
self.lyrics_db_path: LiteralString = os.path.join("/usr/local/share",
|
||||
"sqlite_dbs", "cached_lyrics.db")
|
||||
|
||||
async def check_typeahead(self, s: str, pre_query: str | None = None) -> list[dict]:
|
||||
"""Check s against artists stored - for typeahead"""
|
||||
async def check_typeahead(self, s: str,
|
||||
pre_query: Optional[str] = None) -> list[dict]:
|
||||
"""
|
||||
Check s against artists stored - for typeahead
|
||||
"""
|
||||
async with sqlite3.connect(self.lyrics_db_path,
|
||||
timeout=2) as db_conn:
|
||||
db_conn.row_factory = sqlite3.Row
|
||||
@ -36,9 +42,12 @@ class CacheUtils:
|
||||
|
||||
|
||||
class LyricSearch(FastAPI):
|
||||
"""Lyric Search Endpoint"""
|
||||
def __init__(self, app: FastAPI, util, constants): # pylint: disable=super-init-not-called
|
||||
self.app = app
|
||||
"""
|
||||
Lyric Search Endpoint
|
||||
"""
|
||||
def __init__(self, app: FastAPI,
|
||||
util, constants) -> None: # pylint: disable=super-init-not-called
|
||||
self.app: FastAPI = app
|
||||
self.util = util
|
||||
self.constants = constants
|
||||
self.cache_utils = CacheUtils()
|
||||
@ -70,36 +79,39 @@ class LyricSearch(FastAPI):
|
||||
_schema_include = endpoint in ["lyric/search"]
|
||||
app.add_api_route(f"/{endpoint}", handler, methods=["POST"], include_in_schema=_schema_include)
|
||||
|
||||
async def artist_typeahead_handler(self, data: ValidTypeAheadRequest) -> list[str]|dict:
|
||||
"""Artist Type Ahead Handler"""
|
||||
async def artist_typeahead_handler(self, data: ValidTypeAheadRequest) -> JSONResponse:
|
||||
"""
|
||||
Artist Type Ahead Handler
|
||||
"""
|
||||
if not isinstance(data.query, str) or len(data.query) < 2:
|
||||
return {
|
||||
return JSONResponse(status_code=500, content={
|
||||
'err': True,
|
||||
'errorText': 'Invalid request',
|
||||
}
|
||||
})
|
||||
query: str = data.query
|
||||
typeahead_result: list[dict] = await self.cache_utils.check_typeahead(query)
|
||||
typeahead_list: list[str] = [str(r.get('artist')) for r in typeahead_result]
|
||||
return typeahead_list
|
||||
return JSONResponse(content=typeahead_list)
|
||||
|
||||
async def song_typeahead_handler(self, data: ValidTypeAheadRequest) -> list[str]|dict:
|
||||
"""Song Type Ahead Handler"""
|
||||
async def song_typeahead_handler(self, data: ValidTypeAheadRequest) -> JSONResponse:
|
||||
"""
|
||||
Song Type Ahead Handler
|
||||
"""
|
||||
if not isinstance(data.pre_query, str)\
|
||||
or not isinstance(data.query, str|None):
|
||||
return {
|
||||
or not isinstance(data.query, str):
|
||||
return JSONResponse(status_code=500, content={
|
||||
'err': True,
|
||||
'errorText': 'Invalid request',
|
||||
}
|
||||
})
|
||||
pre_query: str = data.pre_query
|
||||
query: str = data.query
|
||||
typeahead_result: list[dict] = await self.cache_utils.check_typeahead(query, pre_query)
|
||||
typeahead_list: list[str] = [str(r.get('song')) for r in typeahead_result]
|
||||
return typeahead_list
|
||||
return JSONResponse(content=typeahead_list)
|
||||
|
||||
async def lyric_search_handler(self, data: ValidLyricRequest) -> dict:
|
||||
async def lyric_search_handler(self, data: ValidLyricRequest) -> JSONResponse:
|
||||
"""
|
||||
Search for lyrics
|
||||
|
||||
- **a**: artist
|
||||
- **s**: song
|
||||
- **t**: track (artist and song combined) [used only if a & s are not used]
|
||||
@ -109,26 +121,23 @@ class LyricSearch(FastAPI):
|
||||
- **src**: the script/utility which initiated the request
|
||||
- **excluded_sources**: sources to exclude [optional, default: none]
|
||||
"""
|
||||
|
||||
if (not data.a or not data.s) and not data.t or not data.src:
|
||||
raise HTTPException(detail="Invalid request", status_code=500)
|
||||
|
||||
if data.src.upper() not in self.acceptable_request_sources:
|
||||
await self.notifier.send(f"ERROR @ {__file__.rsplit("/", maxsplit=1)[-1]}",
|
||||
f"Unknown request source: {data.src}")
|
||||
return {
|
||||
return JSONResponse(status_code=500, content={
|
||||
'err': True,
|
||||
'errorText': f'Unknown request source: {data.src}',
|
||||
}
|
||||
})
|
||||
|
||||
if not data.t:
|
||||
search_artist: Optional[str] = data.a
|
||||
search_song: Optional[str] = data.s
|
||||
else:
|
||||
t_split = data.t.split(" - ", maxsplit=1)
|
||||
search_artist = t_split[0]
|
||||
search_song = t_split[1]
|
||||
|
||||
t_split: tuple = tuple(data.t.split(" - ", maxsplit=1))
|
||||
(search_artist, search_song) = t_split
|
||||
|
||||
if search_artist and search_song:
|
||||
search_artist = str(self.constants.DOUBLE_SPACE_REGEX.sub(" ", search_artist.strip()))
|
||||
@ -137,21 +146,21 @@ class LyricSearch(FastAPI):
|
||||
search_song = urllib.parse.unquote(search_song)
|
||||
|
||||
if not isinstance(search_artist, str) or not isinstance(search_song, str):
|
||||
return {
|
||||
return JSONResponse(status_code=500, content={
|
||||
'err': True,
|
||||
'errorText': 'Invalid request',
|
||||
}
|
||||
})
|
||||
|
||||
excluded_sources: Optional[list] = data.excluded_sources
|
||||
aggregate_search = aggregate.Aggregate(exclude_methods=excluded_sources)
|
||||
plain_lyrics: bool = not data.lrc
|
||||
result: Optional[LyricsResult|dict] = await aggregate_search.search(search_artist, search_song, plain_lyrics)
|
||||
result: Optional[Union[LyricsResult, dict]] = await aggregate_search.search(search_artist, search_song, plain_lyrics)
|
||||
|
||||
if not result:
|
||||
return {
|
||||
return JSONResponse(content={
|
||||
'err': True,
|
||||
'errorText': 'Sources exhausted, lyrics not located.',
|
||||
}
|
||||
})
|
||||
|
||||
result = vars(result)
|
||||
|
||||
@ -167,9 +176,11 @@ class LyricSearch(FastAPI):
|
||||
break
|
||||
|
||||
if not seeked_found_line:
|
||||
return {
|
||||
return JSONResponse(status_code=500, content={
|
||||
'err': True,
|
||||
'errorText': 'Seek (a.k.a. subsearch) failed.',
|
||||
'failed_seek': True,
|
||||
}
|
||||
})
|
||||
result['lyrics'] = " / ".join(lyric_lines[seeked_found_line:])
|
||||
|
||||
result['confidence'] = int(result.get('confidence', 0))
|
||||
@ -188,4 +199,4 @@ class LyricSearch(FastAPI):
|
||||
if not data.extra:
|
||||
result.pop('src')
|
||||
|
||||
return result
|
||||
return JSONResponse(content=result)
|
@ -2,18 +2,20 @@
|
||||
# pylint: disable=bare-except, broad-exception-caught, invalid-name
|
||||
|
||||
import time
|
||||
import logging
|
||||
from typing import Optional
|
||||
from fastapi import FastAPI
|
||||
from fastapi.responses import JSONResponse
|
||||
import redis.asyncio as redis
|
||||
from lyric_search.sources import private, cache as LyricsCache, redis_cache
|
||||
|
||||
class Misc(FastAPI):
|
||||
"""Misc Endpoints"""
|
||||
def __init__(self, app: FastAPI, my_util, constants, radio): # pylint: disable=super-init-not-called
|
||||
self.app = app
|
||||
def __init__(self, app: FastAPI, my_util,
|
||||
constants, radio) -> None: # pylint: disable=super-init-not-called
|
||||
self.app: FastAPI = app
|
||||
self.util = my_util
|
||||
self.constants = constants
|
||||
self.radio_pubkey: str = "XC-AJCJS89-AOLOFKZ92921AK-AKASKZJAN178-3D1"
|
||||
self.lyr_cache = LyricsCache.Cache()
|
||||
self.redis_cache = redis_cache.RedisCache()
|
||||
self.redis_client = redis.Redis(password=private.REDIS_PW)
|
||||
@ -44,7 +46,7 @@ class Misc(FastAPI):
|
||||
return artistsong
|
||||
|
||||
|
||||
async def homepage_redis_widget(self) -> dict:
|
||||
async def homepage_redis_widget(self) -> JSONResponse:
|
||||
"""Homepage Redis Widget Handler"""
|
||||
|
||||
# Measure response time w/ test lyric search
|
||||
@ -58,33 +60,44 @@ class Misc(FastAPI):
|
||||
num_ci_keys = len(ci_keys)
|
||||
index_info = await self.redis_client.ft().info()
|
||||
indexed_lyrics: int = index_info.get('num_docs')
|
||||
return {
|
||||
return JSONResponse(content={
|
||||
'responseTime': round(response_time, 7),
|
||||
'storedKeys': total_keys,
|
||||
'indexedLyrics': indexed_lyrics,
|
||||
'sessions': num_ci_keys,
|
||||
}
|
||||
})
|
||||
|
||||
async def homepage_sqlite_widget(self) -> dict:
|
||||
async def homepage_sqlite_widget(self) -> JSONResponse:
|
||||
"""Homepage SQLite Widget Handler"""
|
||||
|
||||
row_count: int = await self.lyr_cache.sqlite_rowcount()
|
||||
distinct_artists: int = await self.lyr_cache.sqlite_distinct("artist")
|
||||
lyrics_length: int = await self.lyr_cache.sqlite_lyrics_length()
|
||||
return {
|
||||
return JSONResponse(content={
|
||||
'storedRows': row_count,
|
||||
'distinctArtists': distinct_artists,
|
||||
'lyricsLength': lyrics_length,
|
||||
}
|
||||
})
|
||||
|
||||
async def homepage_lyrics_widget(self) -> dict:
|
||||
"""Homepage Lyrics Widget Handler"""
|
||||
found_counts: dict = await self.redis_cache.get_found_counts()
|
||||
if not isinstance(found_counts, dict):
|
||||
return {
|
||||
'err': True,
|
||||
'errorText': 'General failure.',
|
||||
}
|
||||
logging.info("Found: %s", found_counts)
|
||||
return found_counts
|
||||
|
||||
return await self.redis_cache.get_found_counts()
|
||||
|
||||
async def homepage_radio_widget(self) -> dict:
|
||||
async def homepage_radio_widget(self) -> JSONResponse:
|
||||
"""Homepage Radio Widget Handler"""
|
||||
|
||||
return {
|
||||
radio_np: str = await self.get_radio_np()
|
||||
if not radio_np:
|
||||
return JSONResponse(status_code=500, content={
|
||||
'err': True,
|
||||
'errorText': 'General failure.',
|
||||
})
|
||||
return JSONResponse(content={
|
||||
'now_playing': await self.get_radio_np(),
|
||||
}
|
||||
})
|
@ -2,22 +2,16 @@
|
||||
|
||||
import logging
|
||||
import traceback
|
||||
import os
|
||||
import aiosqlite as sqlite3
|
||||
import time
|
||||
import random
|
||||
import asyncio
|
||||
import regex
|
||||
import music_tag
|
||||
from . import radio_util
|
||||
from .constructors import ValidRadioNextRequest, ValidRadioReshuffleRequest, ValidRadioQueueShiftRequest,\
|
||||
ValidRadioQueueRemovalRequest, ValidRadioSongRequest,\
|
||||
ValidRadioQueueGetRequest, RadioException
|
||||
ValidRadioQueueRemovalRequest, ValidRadioSongRequest, RadioException
|
||||
from uuid import uuid4 as uuid
|
||||
from typing import Optional, LiteralString
|
||||
from typing import Optional
|
||||
from fastapi import FastAPI, BackgroundTasks, Request, Response, HTTPException
|
||||
from fastapi.responses import RedirectResponse
|
||||
from aiohttp import ClientSession, ClientTimeout
|
||||
from fastapi.responses import RedirectResponse, JSONResponse
|
||||
# pylint: disable=bare-except, broad-exception-caught, invalid-name
|
||||
|
||||
|
||||
@ -28,8 +22,9 @@ TODO:
|
||||
|
||||
class Radio(FastAPI):
|
||||
"""Radio Endpoints"""
|
||||
def __init__(self, app: FastAPI, my_util, constants) -> None: # pylint: disable=super-init-not-called
|
||||
self.app = app
|
||||
def __init__(self, app: FastAPI,
|
||||
my_util, constants) -> None: # pylint: disable=super-init-not-called
|
||||
self.app: FastAPI = app
|
||||
self.util = my_util
|
||||
self.constants = constants
|
||||
self.radio_util = radio_util.RadioUtil(self.constants)
|
||||
@ -55,7 +50,8 @@ class Radio(FastAPI):
|
||||
asyncio.get_event_loop().run_until_complete(self.radio_util.load_playlist())
|
||||
asyncio.get_event_loop().run_until_complete(self.radio_util._ls_skip())
|
||||
|
||||
async def radio_skip(self, data: ValidRadioNextRequest, request: Request) -> bool:
|
||||
async def radio_skip(self, data: ValidRadioNextRequest,
|
||||
request: Request) -> JSONResponse:
|
||||
"""
|
||||
Skip to the next track in the queue, or to uuid specified in skipTo if provided
|
||||
"""
|
||||
@ -65,17 +61,28 @@ class Radio(FastAPI):
|
||||
if data.skipTo:
|
||||
queue_item = self.radio_util.get_queue_item_by_uuid(data.skipTo)
|
||||
if not queue_item:
|
||||
return False
|
||||
return JSONResponse(status_code=500, content={
|
||||
'err': True,
|
||||
'errorText': 'No such queue item.',
|
||||
})
|
||||
self.radio_util.active_playlist = self.radio_util.active_playlist[queue_item[0]:]
|
||||
if not self.radio_util.active_playlist:
|
||||
await self.radio_util.load_playlist()
|
||||
return await self.radio_util._ls_skip()
|
||||
skip_result: bool = await self.radio_util._ls_skip()
|
||||
status_code = 200 if skip_result else 500
|
||||
return JSONResponse(status_code=status_code, content={
|
||||
'success': skip_result,
|
||||
})
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
return False
|
||||
return JSONResponse(status_code=500, content={
|
||||
'err': True,
|
||||
'errorText': 'General failure.',
|
||||
})
|
||||
|
||||
|
||||
async def radio_reshuffle(self, data: ValidRadioReshuffleRequest, request: Request) -> dict:
|
||||
async def radio_reshuffle(self, data: ValidRadioReshuffleRequest,
|
||||
request: Request) -> JSONResponse:
|
||||
"""
|
||||
Reshuffle the play queue
|
||||
"""
|
||||
@ -83,16 +90,16 @@ class Radio(FastAPI):
|
||||
raise HTTPException(status_code=403, detail="Unauthorized")
|
||||
|
||||
random.shuffle(self.radio_util.active_playlist)
|
||||
return {
|
||||
return JSONResponse(content={
|
||||
'ok': True
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
async def radio_get_queue(self, request: Request, limit: Optional[int] = 15_000) -> dict:
|
||||
async def radio_get_queue(self, request: Request,
|
||||
limit: Optional[int] = 15_000) -> JSONResponse:
|
||||
"""
|
||||
Get current play queue, up to limit [default: 15k]
|
||||
"""
|
||||
|
||||
queue_out: list[dict] = []
|
||||
for x, item in enumerate(self.radio_util.active_playlist[0:limit]):
|
||||
queue_out.append({
|
||||
@ -104,45 +111,52 @@ class Radio(FastAPI):
|
||||
'artistsong': item.get('artistsong'),
|
||||
'duration': item.get('duration'),
|
||||
})
|
||||
return {
|
||||
return JSONResponse(content={
|
||||
'items': queue_out
|
||||
}
|
||||
})
|
||||
|
||||
async def radio_queue_shift(self, data: ValidRadioQueueShiftRequest, request: Request) -> dict:
|
||||
"""Shift position of a UUID within the queue [currently limited to playing next or immediately]"""
|
||||
async def radio_queue_shift(self, data: ValidRadioQueueShiftRequest,
|
||||
request: Request) -> JSONResponse:
|
||||
"""
|
||||
Shift position of a UUID within the queue
|
||||
[currently limited to playing next or immediately]
|
||||
"""
|
||||
if not self.util.check_key(path=request.url.path, req_type=4, key=data.key):
|
||||
raise HTTPException(status_code=403, detail="Unauthorized")
|
||||
|
||||
queue_item = self.radio_util.get_queue_item_by_uuid(data.uuid)
|
||||
if not queue_item:
|
||||
return {
|
||||
return JSONResponse(status_code=500, content={
|
||||
'err': True,
|
||||
'errorText': 'Queue item not found.',
|
||||
}
|
||||
})
|
||||
(x, item) = queue_item
|
||||
self.radio_util.active_playlist.pop(x)
|
||||
self.radio_util.active_playlist.insert(0, item)
|
||||
if not data.next:
|
||||
await self.radio_util._ls_skip()
|
||||
return {
|
||||
return JSONResponse(content={
|
||||
'ok': True,
|
||||
}
|
||||
})
|
||||
|
||||
async def radio_queue_remove(self, data: ValidRadioQueueRemovalRequest, request: Request) -> dict:
|
||||
"""Remove an item from the current play queue"""
|
||||
async def radio_queue_remove(self, data: ValidRadioQueueRemovalRequest,
|
||||
request: Request) -> JSONResponse:
|
||||
"""
|
||||
Remove an item from the current play queue
|
||||
"""
|
||||
if not self.util.check_key(path=request.url.path, req_type=4, key=data.key):
|
||||
raise HTTPException(status_code=403, detail="Unauthorized")
|
||||
|
||||
queue_item = self.radio_util.get_queue_item_by_uuid(data.uuid)
|
||||
if not queue_item:
|
||||
return {
|
||||
return JSONResponse(status_code=500, content={
|
||||
'err': True,
|
||||
'errorText': 'Queue item not found.',
|
||||
}
|
||||
})
|
||||
self.radio_util.active_playlist.pop(queue_item[0])
|
||||
return {
|
||||
return JSONResponse(content={
|
||||
'ok': True,
|
||||
}
|
||||
})
|
||||
|
||||
async def album_art_handler(self, request: Request, track_id: Optional[int] = None) -> Response:
|
||||
"""
|
||||
@ -164,22 +178,22 @@ class Radio(FastAPI):
|
||||
return RedirectResponse(url="https://codey.lol/images/radio_art_default.jpg",
|
||||
status_code=302)
|
||||
|
||||
async def radio_now_playing(self, request: Request) -> dict:
|
||||
"""Get currently playing track info"""
|
||||
async def radio_now_playing(self, request: Request) -> JSONResponse:
|
||||
"""
|
||||
Get currently playing track info
|
||||
"""
|
||||
ret_obj: dict = {**self.radio_util.now_playing}
|
||||
cur_elapsed: int = self.radio_util.now_playing.get('elapsed', -1)
|
||||
cur_duration: int = self.radio_util.now_playing.get('duration', 999999)
|
||||
try:
|
||||
ret_obj['elapsed'] = int(time.time()) - ret_obj['start']
|
||||
except KeyError:
|
||||
traceback.print_exc()
|
||||
ret_obj['elapsed'] = 0
|
||||
ret_obj.pop('file_path')
|
||||
return ret_obj
|
||||
return JSONResponse(content=ret_obj)
|
||||
|
||||
|
||||
async def radio_get_next(self, data: ValidRadioNextRequest, request: Request,
|
||||
background_tasks: BackgroundTasks) -> Optional[dict]:
|
||||
background_tasks: BackgroundTasks) -> JSONResponse:
|
||||
"""
|
||||
Get next track
|
||||
Track will be removed from the queue in the process.
|
||||
@ -189,13 +203,19 @@ class Radio(FastAPI):
|
||||
if not isinstance(self.radio_util.active_playlist, list) or not self.radio_util.active_playlist:
|
||||
await self.radio_util.load_playlist()
|
||||
await self.radio_util._ls_skip()
|
||||
return None
|
||||
return JSONResponse(status_code=500, content={
|
||||
'err': True,
|
||||
'errorText': 'General failure occurred, prompting playlist reload.',
|
||||
})
|
||||
next = self.radio_util.active_playlist.pop(0)
|
||||
if not isinstance(next, dict):
|
||||
logging.critical("next is of type: %s, reloading playlist...", type(next))
|
||||
await self.radio_util.load_playlist()
|
||||
await self.radio_util._ls_skip()
|
||||
return None
|
||||
return JSONResponse(status_code=500, content={
|
||||
'err': True,
|
||||
'errorText': 'General failure occurred, prompting playlist reload.',
|
||||
})
|
||||
|
||||
duration: int = next['duration']
|
||||
time_started: int = int(time.time())
|
||||
@ -216,37 +236,41 @@ class Radio(FastAPI):
|
||||
try:
|
||||
if not await self.radio_util.get_album_art(file_path=next['file_path']):
|
||||
album_art = await self.radio_util.get_album_art(file_path=next['file_path'])
|
||||
if not album_art:
|
||||
return None
|
||||
await self.radio_util.cache_album_art(next['id'], album_art)
|
||||
if album_art:
|
||||
await self.radio_util.cache_album_art(next['id'], album_art)
|
||||
else:
|
||||
logging.debug("Could not read album art for %s",
|
||||
next['file_path'])
|
||||
except:
|
||||
traceback.print_exc()
|
||||
return next
|
||||
return JSONResponse(content=next)
|
||||
|
||||
|
||||
async def radio_request(self, data: ValidRadioSongRequest, request: Request) -> dict:
|
||||
"""Song request handler"""
|
||||
async def radio_request(self, data: ValidRadioSongRequest, request: Request) -> JSONResponse:
|
||||
"""
|
||||
Song request handler
|
||||
"""
|
||||
if not self.util.check_key(path=request.url.path, req_type=4, key=data.key):
|
||||
raise HTTPException(status_code=403, detail="Unauthorized")
|
||||
artistsong: Optional[str] = data.artistsong
|
||||
artist: Optional[str] = data.artist
|
||||
song: Optional[str] = data.song
|
||||
if artistsong and (artist or song):
|
||||
return {
|
||||
return JSONResponse(status_code=500, content={
|
||||
'err': True,
|
||||
'errorText': 'Invalid request',
|
||||
}
|
||||
})
|
||||
if not artistsong and (not artist or not song):
|
||||
return {
|
||||
return JSONResponse(status_code=500, content={
|
||||
'err': True,
|
||||
'errorText': 'Invalid request',
|
||||
}
|
||||
})
|
||||
|
||||
search: bool = await self.radio_util.search_playlist(artistsong=artistsong,
|
||||
artist=artist,
|
||||
song=song)
|
||||
if data.alsoSkip:
|
||||
await self.radio_util._ls_skip()
|
||||
return {
|
||||
return JSONResponse(content={
|
||||
'result': search
|
||||
}
|
||||
})
|
@ -20,13 +20,17 @@ from .constructors import RadioException
|
||||
double_space = regex.compile(r'\s{2,}')
|
||||
|
||||
class RadioUtil:
|
||||
"""
|
||||
Radio Utils
|
||||
"""
|
||||
def __init__(self, constants) -> None:
|
||||
self.constants = constants
|
||||
self.gpt = gpt.GPT(self.constants)
|
||||
self.ls_uri: str = "http://10.10.10.101:29000"
|
||||
self.sqlite_exts: list[str] = ['/home/singer/api/solibs/spellfix1.cpython-311-x86_64-linux-gnu.so']
|
||||
self.active_playlist_path: str|LiteralString = os.path.join("/usr/local/share",
|
||||
"sqlite_dbs", "track_file_map.db")
|
||||
self.active_playlist_path: Union[str, LiteralString] = os.path\
|
||||
.join("/usr/local/share",
|
||||
"sqlite_dbs", "track_file_map.db")
|
||||
self.active_playlist_name = "default" # not used
|
||||
self.active_playlist: list[dict] = []
|
||||
self.now_playing: dict = {
|
||||
@ -49,17 +53,19 @@ class RadioUtil:
|
||||
}
|
||||
}
|
||||
|
||||
def duration_conv(self, s: int|float) -> str:
|
||||
def duration_conv(self,
|
||||
s: Union[int, float]) -> str:
|
||||
"""
|
||||
Convert duration given in seconds to hours, minutes, and seconds (h:m:s)
|
||||
Args:
|
||||
s (int|float): seconds to convert
|
||||
s (Union[int, float]): seconds to convert
|
||||
Returns:
|
||||
str
|
||||
"""
|
||||
return str(datetime.timedelta(seconds=s)).split(".", maxsplit=1)[0]
|
||||
|
||||
async def search_playlist(self, artistsong: Optional[str] = None, artist: Optional[str] = None,
|
||||
async def search_playlist(self, artistsong: Optional[str] = None,
|
||||
artist: Optional[str] = None,
|
||||
song: Optional[str] = None) -> bool:
|
||||
"""
|
||||
Search for track, add it up next in play queue if found
|
||||
@ -137,7 +143,7 @@ class RadioUtil:
|
||||
LIMITED TO ONE ARTIST...
|
||||
"""
|
||||
|
||||
# db_query: str = 'SELECT distinct(artist || " - " || song) AS artistdashsong, id, artist, song, genre, file_path, duration FROM tracks\
|
||||
# db_query = 'SELECT distinct(artist || " - " || song) AS artistdashsong, id, artist, song, genre, file_path, duration FROM tracks\
|
||||
# WHERE artist LIKE "%bad omens%" GROUP BY artistdashsong ORDER BY RANDOM()'
|
||||
|
||||
async with sqlite3.connect(self.active_playlist_path,
|
||||
@ -160,7 +166,8 @@ class RadioUtil:
|
||||
except:
|
||||
traceback.print_exc()
|
||||
|
||||
async def cache_album_art(self, track_id: int, album_art: bytes) -> None:
|
||||
async def cache_album_art(self, track_id: int,
|
||||
album_art: bytes) -> None:
|
||||
"""
|
||||
Cache Album Art to SQLite DB
|
||||
Args:
|
||||
@ -201,7 +208,7 @@ class RadioUtil:
|
||||
|
||||
async with await db_conn.execute(query,
|
||||
query_params) as db_cursor:
|
||||
result: Optional[sqlite3.Row|bool] = await db_cursor.fetchone()
|
||||
result: Optional[Union[sqlite3.Row, bool]] = await db_cursor.fetchone()
|
||||
if not result or not isinstance(result, sqlite3.Row):
|
||||
return None
|
||||
return result['album_art']
|
||||
@ -209,13 +216,14 @@ class RadioUtil:
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
def get_queue_item_by_uuid(self, uuid: str) -> Optional[tuple[int, dict]]:
|
||||
def get_queue_item_by_uuid(self,
|
||||
uuid: str) -> Optional[tuple[int, dict]]:
|
||||
"""
|
||||
Get queue item by UUID
|
||||
Args:
|
||||
uuid: The UUID to search
|
||||
Returns:
|
||||
dict|None
|
||||
Optional[tuple[int, dict]]
|
||||
"""
|
||||
for x, item in enumerate(self.active_playlist):
|
||||
if item.get('uuid') == uuid:
|
||||
@ -242,16 +250,18 @@ class RadioUtil:
|
||||
|
||||
return False # failsafe
|
||||
|
||||
async def get_ai_song_info(self, artist: str, song: str) -> Optional[str]:
|
||||
async def get_ai_song_info(self, artist: str,
|
||||
song: str) -> Optional[str]:
|
||||
"""
|
||||
Get AI Song Info
|
||||
Args:
|
||||
artist (str)
|
||||
song (str)
|
||||
Returns:
|
||||
str|None
|
||||
Optional[str]
|
||||
"""
|
||||
response: Optional[str] = await self.gpt.get_completion(prompt=f"I am going to listen to {song} by {artist}.")
|
||||
prompt: str = f" am going to listen to {song} by {artist}."
|
||||
response: Optional[str] = await self.gpt.get_completion(prompt)
|
||||
if not response:
|
||||
logging.critical("No response received from GPT?")
|
||||
return None
|
||||
@ -298,7 +308,7 @@ class RadioUtil:
|
||||
},
|
||||
{
|
||||
"name": "Higher Res",
|
||||
"value": "[stream/icecast](https://relay.sfm.codey.lol/aces.ogg) || [web player](https://codey.lol/radio)",
|
||||
"value": "[stream/icecast](https://relay.sfm.codey.lol/aces.ogg) | [web player](https://codey.lol/radio)",
|
||||
"inline": True,
|
||||
}
|
||||
]
|
||||
|
@ -2,22 +2,26 @@
|
||||
|
||||
import os
|
||||
import random
|
||||
from typing import LiteralString, Optional
|
||||
from typing import Union, LiteralString
|
||||
import aiosqlite as sqlite3
|
||||
from fastapi import FastAPI
|
||||
from fastapi.responses import JSONResponse
|
||||
from .constructors import RandMsgRequest
|
||||
|
||||
class RandMsg(FastAPI):
|
||||
"""Random Message Endpoint"""
|
||||
def __init__(self, app: FastAPI, util, constants): # pylint: disable=super-init-not-called
|
||||
self.app = app
|
||||
"""
|
||||
Random Message Endpoint
|
||||
"""
|
||||
def __init__(self, app: FastAPI,
|
||||
util, constants) -> None: # pylint: disable=super-init-not-called
|
||||
self.app: FastAPI = app
|
||||
self.util = util
|
||||
self.constants = constants
|
||||
self.endpoint_name = "randmsg"
|
||||
|
||||
app.add_api_route(f"/{self.endpoint_name}", self.randmsg_handler, methods=["POST"])
|
||||
|
||||
async def randmsg_handler(self, data: RandMsgRequest):
|
||||
async def randmsg_handler(self, data: RandMsgRequest) -> JSONResponse:
|
||||
"""
|
||||
Get a randomly generated message
|
||||
"""
|
||||
@ -25,16 +29,16 @@ class RandMsg(FastAPI):
|
||||
short: bool = data.short if data.short else False
|
||||
|
||||
if not short:
|
||||
db_rand_selected = random.choice([0, 1, 3])
|
||||
db_rand_selected: int = random.choice([0, 1, 3])
|
||||
else:
|
||||
db_rand_selected = 9
|
||||
title_attr = "Unknown"
|
||||
title_attr: str = "Unknown"
|
||||
|
||||
match db_rand_selected:
|
||||
case 0:
|
||||
randmsg_db_path = os.path.join("/usr/local/share",
|
||||
randmsg_db_path: Union[str, LiteralString] = os.path.join("/usr/local/share",
|
||||
"sqlite_dbs", "qajoke.db") # For qajoke db
|
||||
db_query = "SELECT id, ('<b>Q:</b> ' || question || '<br/><b>A:</b> ' \
|
||||
db_query: str = "SELECT id, ('<b>Q:</b> ' || question || '<br/><b>A:</b> ' \
|
||||
|| answer) FROM jokes ORDER BY RANDOM() LIMIT 1" # For qajoke db
|
||||
title_attr = "QA Joke DB"
|
||||
case 1 | 9:
|
||||
@ -74,23 +78,16 @@ class RandMsg(FastAPI):
|
||||
db_query = """SELECT id, (title || "<br>" || body) FROM jokes \
|
||||
WHERE score >= 10000 ORDER BY RANDOM() LIMIT 1"""
|
||||
title_attr = "r/jokes DB"
|
||||
case 6:
|
||||
randmsg_db_path = os.path.join("/usr/local/share",
|
||||
"sqlite_dbs",
|
||||
"donnies.db") # Donnies DB
|
||||
random.seed()
|
||||
twilight_or_mice: str = random.choice(["twilight", "mice"])
|
||||
db_query = f"SELECT id, text FROM {twilight_or_mice} ORDER BY RANDOM() LIMIT 1"
|
||||
title_attr = "Donnies DB"
|
||||
|
||||
async with sqlite3.connect(database=randmsg_db_path, timeout=1) as _db:
|
||||
async with await _db.execute(db_query) as _cursor:
|
||||
result = await _cursor.fetchone()
|
||||
result: sqlite3.Row = await _cursor.fetchone()
|
||||
(result_id, result_msg) = result
|
||||
result_msg = result_msg.strip()
|
||||
return {
|
||||
"id": result_id,
|
||||
"msg": result_msg,
|
||||
'title': title_attr
|
||||
}
|
||||
return JSONResponse(content=
|
||||
{
|
||||
"id": result_id,
|
||||
"msg": result_msg,
|
||||
"title": title_attr,
|
||||
})
|
||||
|
@ -3,13 +3,16 @@
|
||||
import os
|
||||
import aiosqlite as sqlite3
|
||||
from fastapi import FastAPI
|
||||
from typing import Optional, LiteralString
|
||||
from fastapi.responses import JSONResponse
|
||||
from typing import Optional, LiteralString, Union
|
||||
from .constructors import ValidShowEpisodeLineRequest, ValidShowEpisodeListRequest
|
||||
|
||||
class Transcriptions(FastAPI):
|
||||
"""Transcription Endpoints"""
|
||||
"""
|
||||
Transcription Endpoints
|
||||
"""
|
||||
def __init__(self, app: FastAPI, util, constants) -> None: # pylint: disable=super-init-not-called
|
||||
self.app = app
|
||||
self.app: FastAPI = app
|
||||
self.util = util
|
||||
self.constants = constants
|
||||
|
||||
@ -23,26 +26,28 @@ class Transcriptions(FastAPI):
|
||||
app.add_api_route(f"/{endpoint}", handler, methods=["POST"],
|
||||
include_in_schema=False)
|
||||
|
||||
async def get_episodes_handler(self, data: ValidShowEpisodeListRequest) -> dict:
|
||||
"""Get list of episodes by show id"""
|
||||
async def get_episodes_handler(self, data: ValidShowEpisodeListRequest) -> JSONResponse:
|
||||
"""
|
||||
Get list of episodes by show id
|
||||
"""
|
||||
show_id: int = data.s
|
||||
db_path: Optional[str|LiteralString] = None
|
||||
db_path: Optional[Union[str, LiteralString]] = None
|
||||
db_query: Optional[str] = None
|
||||
show_title: Optional[str] = None
|
||||
|
||||
if show_id is None:
|
||||
return {
|
||||
if not show_id:
|
||||
return JSONResponse(status_code=500, content={
|
||||
'err': True,
|
||||
'errorText': 'Invalid request',
|
||||
}
|
||||
})
|
||||
|
||||
show_id = int(show_id)
|
||||
|
||||
if not(str(show_id).isnumeric()) or show_id not in [0, 1, 2]:
|
||||
return {
|
||||
return JSONResponse(status_code=500, content={
|
||||
'err': True,
|
||||
'errorText': 'Show not found.',
|
||||
}
|
||||
})
|
||||
|
||||
match show_id:
|
||||
case 0:
|
||||
@ -61,33 +66,35 @@ class Transcriptions(FastAPI):
|
||||
db_query = """SELECT DISTINCT(("S" || EP_S || "E" || EP_EP || " " || EP_TITLE)), EP_ID FROM clean_dialog ORDER BY EP_S, EP_EP"""
|
||||
show_title = "Parks And Rec"
|
||||
case _:
|
||||
return {
|
||||
return JSONResponse(status_code=500, content={
|
||||
'err': True,
|
||||
'errorText': 'Unknown error.'
|
||||
}
|
||||
'errorText': 'Unknown error.',
|
||||
})
|
||||
|
||||
async with sqlite3.connect(database=db_path, timeout=1) as _db:
|
||||
async with await _db.execute(db_query) as _cursor:
|
||||
result: list[tuple] = await _cursor.fetchall()
|
||||
return {
|
||||
return JSONResponse(content={
|
||||
"show_title": show_title,
|
||||
"episodes": [
|
||||
{
|
||||
'id': item[1],
|
||||
'ep_friendly': item[0]
|
||||
} for item in result]
|
||||
}
|
||||
'ep_friendly': item[0],
|
||||
} for item in result],
|
||||
})
|
||||
|
||||
async def get_episode_lines_handler(self, data: ValidShowEpisodeLineRequest) -> dict:
|
||||
"""Get lines for a particular episode"""
|
||||
show_id: int = data.s
|
||||
episode_id: int = data.e
|
||||
async def get_episode_lines_handler(self, data: ValidShowEpisodeLineRequest) -> JSONResponse:
|
||||
"""
|
||||
Get lines for a particular episode
|
||||
"""
|
||||
show_id: int = int(data.s)
|
||||
episode_id: int = int(data.e)
|
||||
# pylint: disable=line-too-long
|
||||
match show_id:
|
||||
case 0:
|
||||
db_path = os.path.join("/usr/local/share",
|
||||
db_path: Union[str, LiteralString] = os.path.join("/usr/local/share",
|
||||
"sqlite_dbs", "sp.db")
|
||||
db_query = """SELECT ("S" || Season || "E" || Episode || " " || Title), Character, Line FROM SP_DAT WHERE ID = ?"""
|
||||
db_query: str = """SELECT ("S" || Season || "E" || Episode || " " || Title), Character, Line FROM SP_DAT WHERE ID = ?"""
|
||||
case 1:
|
||||
db_path = os.path.join("/usr/local/share",
|
||||
"sqlite_dbs", "futur.db")
|
||||
@ -98,23 +105,24 @@ class Transcriptions(FastAPI):
|
||||
db_query = """SELECT ("S" || EP_S || "E" || EP_EP || " " || EP_TITLE), EP_LINE_SPEAKER, EP_LINE FROM clean_dialog WHERE EP_ID = ? ORDER BY id ASC"""
|
||||
|
||||
case _:
|
||||
return {
|
||||
return JSONResponse(status_code=500, content={
|
||||
'err': True,
|
||||
'errorText': 'Unknown error'
|
||||
}
|
||||
'errorText': 'Unknown error',
|
||||
})
|
||||
|
||||
async with sqlite3.connect(database=db_path, timeout=1) as _db:
|
||||
async with sqlite3.connect(database=db_path,
|
||||
timeout=1) as _db:
|
||||
params: tuple = (episode_id,)
|
||||
async with await _db.execute(db_query, params) as _cursor:
|
||||
result: list[tuple] = await _cursor.fetchall()
|
||||
first_result: tuple = result[0]
|
||||
return {
|
||||
return JSONResponse(content={
|
||||
'episode_id': episode_id,
|
||||
'ep_friendly': first_result[0].strip(),
|
||||
'lines': [
|
||||
{
|
||||
'speaker': item[1].strip(),
|
||||
'line': item[2].strip()
|
||||
} for item in result]
|
||||
}
|
||||
'line': item[2].strip(),
|
||||
} for item in result],
|
||||
})
|
||||
|
@ -3,7 +3,7 @@
|
||||
import logging
|
||||
from typing import Optional
|
||||
from fastapi import FastAPI, Request, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from fastapi.responses import JSONResponse
|
||||
from aiohttp import ClientSession, ClientTimeout
|
||||
from .constructors import ValidXCRequest
|
||||
# pylint: disable=invalid-name
|
||||
@ -11,7 +11,7 @@ from .constructors import ValidXCRequest
|
||||
class XC(FastAPI):
|
||||
"""XC (CrossComm) Endpoints"""
|
||||
def __init__(self, app: FastAPI, util, constants) -> None: # pylint: disable=super-init-not-called
|
||||
self.app = app
|
||||
self.app: FastAPI = app
|
||||
self.util = util
|
||||
self.constants = constants
|
||||
|
||||
@ -23,12 +23,13 @@ class XC(FastAPI):
|
||||
app.add_api_route(f"/{endpoint}", handler, methods=["POST"],
|
||||
include_in_schema=False)
|
||||
|
||||
async def xc_handler(self, data: ValidXCRequest, request: Request) -> dict:
|
||||
async def xc_handler(self, data: ValidXCRequest,
|
||||
request: Request) -> JSONResponse:
|
||||
"""Handle XC Commands"""
|
||||
|
||||
try:
|
||||
key: str = data.key
|
||||
bid: int = data.bid
|
||||
bid: int = int(data.bid)
|
||||
cmd: str = data.cmd
|
||||
cmd_data: Optional[dict] = data.data
|
||||
if not self.util.check_key(path=request.url.path, req_type=0, key=key):
|
||||
@ -40,10 +41,10 @@ class XC(FastAPI):
|
||||
}
|
||||
|
||||
if not bid in BID_ADDR_MAP:
|
||||
return {
|
||||
return JSONResponse(status_code=500, content={
|
||||
'err': True,
|
||||
'errorText': 'Invalid bot id'
|
||||
}
|
||||
})
|
||||
|
||||
bot_api_url: str = f'http://{BID_ADDR_MAP[bid]}/'
|
||||
async with ClientSession() as session:
|
||||
@ -51,13 +52,13 @@ class XC(FastAPI):
|
||||
'Content-Type': 'application/json; charset=utf-8'
|
||||
}, timeout=ClientTimeout(connect=5, sock_read=5)) as aiohttp_request:
|
||||
response: dict = await aiohttp_request.json()
|
||||
return {
|
||||
return JSONResponse(content={
|
||||
'success': True,
|
||||
'response': response
|
||||
}
|
||||
})
|
||||
except Exception as e:
|
||||
logging.debug("Error: %s", str(e))
|
||||
return {
|
||||
return JSONResponse(status_code=500, content={
|
||||
'err': True,
|
||||
'errorText': 'General error.',
|
||||
}
|
||||
})
|
@ -2,28 +2,30 @@
|
||||
|
||||
import importlib
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
from fastapi.responses import JSONResponse
|
||||
from typing import Optional, Union
|
||||
from .constructors import ValidYTSearchRequest
|
||||
|
||||
class YT(FastAPI):
|
||||
"""YT Endpoints"""
|
||||
def __init__(self, app: FastAPI, util, constants) -> None: # pylint: disable=super-init-not-called
|
||||
self.app = app
|
||||
"""
|
||||
YT Endpoints
|
||||
"""
|
||||
def __init__(self, app: FastAPI, util,
|
||||
constants) -> None: # pylint: disable=super-init-not-called
|
||||
self.app: FastAPI = app
|
||||
self.util = util
|
||||
self.constants = constants
|
||||
self.ytsearch = importlib.import_module("youtube_search_async").YoutubeSearch()
|
||||
|
||||
self.endpoints: dict = {
|
||||
"yt/search": self.yt_video_search_handler,
|
||||
#tbd
|
||||
}
|
||||
|
||||
for endpoint, handler in self.endpoints.items():
|
||||
app.add_api_route(f"/{endpoint}", handler, methods=["POST"],
|
||||
include_in_schema=True)
|
||||
|
||||
async def yt_video_search_handler(self, data: ValidYTSearchRequest) -> dict:
|
||||
async def yt_video_search_handler(self, data: ValidYTSearchRequest) -> JSONResponse:
|
||||
"""
|
||||
Search for YT Video by Title (closest match returned)
|
||||
- **t**: Title to search
|
||||
@ -32,13 +34,13 @@ class YT(FastAPI):
|
||||
title: str = data.t
|
||||
yts_res: Optional[list[dict]] = await self.ytsearch.search(title)
|
||||
if not yts_res:
|
||||
return {
|
||||
return JSONResponse(status_code=404, content={
|
||||
'err': True,
|
||||
'errorText': 'No result.',
|
||||
}
|
||||
yt_video_id: str|bool = yts_res[0].get('id', False)
|
||||
})
|
||||
yt_video_id: Union[str, bool] = yts_res[0].get('id', False)
|
||||
|
||||
return {
|
||||
return JSONResponse(content={
|
||||
'video_id': yt_video_id,
|
||||
'extras': yts_res[0]
|
||||
}
|
||||
'extras': yts_res[0],
|
||||
})
|
@ -3,16 +3,18 @@ from typing import Optional
|
||||
from openai import AsyncOpenAI
|
||||
|
||||
class GPT:
|
||||
def __init__(self, constants):
|
||||
def __init__(self, constants) -> None:
|
||||
self.constants = constants
|
||||
self.api_key = self.constants.OPENAI_API_KEY
|
||||
self.client = AsyncOpenAI(
|
||||
self.api_key: str = self.constants.OPENAI_API_KEY
|
||||
self.client: AsyncOpenAI = AsyncOpenAI(
|
||||
api_key=self.api_key,
|
||||
timeout=10.0,
|
||||
)
|
||||
self.default_system_prompt = "You are a helpful assistant who will provide only totally accurate tidbits of info on the specific songs the user may listen to."
|
||||
self.default_system_prompt: str = """You are a helpful assistant who will provide only totally accurate tidbits of \
|
||||
info on the specific songs the user may listen to."""
|
||||
|
||||
async def get_completion(self, prompt: str, system_prompt: Optional[str] = None) -> str:
|
||||
async def get_completion(self, prompt: str,
|
||||
system_prompt: Optional[str] = None) -> Optional[str]:
|
||||
if not system_prompt:
|
||||
system_prompt = self.default_system_prompt
|
||||
chat_completion = await self.client.chat.completions.create(
|
||||
@ -29,4 +31,5 @@ class GPT:
|
||||
model="gpt-4o-mini",
|
||||
temperature=0.35,
|
||||
)
|
||||
return chat_completion.choices[0].message.content
|
||||
response: Optional[str] = chat_completion.choices[0].message.content
|
||||
return response
|
@ -20,7 +20,7 @@ class LastFM:
|
||||
async def search_artist(self, artist: Optional[str] = None) -> dict:
|
||||
"""Search LastFM for an artist"""
|
||||
try:
|
||||
if artist is None:
|
||||
if not artist:
|
||||
return {
|
||||
'err': 'No artist specified.',
|
||||
}
|
||||
@ -91,8 +91,7 @@ class LastFM:
|
||||
dict
|
||||
"""
|
||||
try:
|
||||
if artist is None or album is None:
|
||||
logging.info("inv request")
|
||||
if not artist or not album:
|
||||
return {
|
||||
'err': 'No artist or album specified',
|
||||
}
|
||||
@ -111,16 +110,16 @@ class LastFM:
|
||||
'err': 'General Failure',
|
||||
}
|
||||
|
||||
async def get_artist_albums(self, artist: Optional[str] = None) -> dict|list[dict]:
|
||||
async def get_artist_albums(self, artist: Optional[str] = None) -> Union[dict, list[dict]]:
|
||||
"""
|
||||
Get Artists Albums from LastFM
|
||||
Args:
|
||||
artist (Optional[str])
|
||||
Returns:
|
||||
dict|list[dict]
|
||||
Union[dict, list[dict]]
|
||||
"""
|
||||
try:
|
||||
if artist is None:
|
||||
if not artist:
|
||||
return {
|
||||
'err': 'No artist specified.',
|
||||
}
|
||||
@ -149,10 +148,10 @@ class LastFM:
|
||||
Args:
|
||||
artist (Optional[str])
|
||||
Returns:
|
||||
int|dict
|
||||
int
|
||||
"""
|
||||
try:
|
||||
if artist is None:
|
||||
if not artist:
|
||||
return -1
|
||||
artist_search: dict = await self.search_artist(artist=artist)
|
||||
if not artist_search:
|
||||
|
@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env python3.12
|
||||
|
||||
from dataclasses import dataclass, asdict
|
||||
from dataclasses import dataclass
|
||||
from typing import Union
|
||||
|
||||
@dataclass
|
||||
class LyricsResult:
|
||||
@ -10,12 +11,12 @@ class LyricsResult:
|
||||
artist (str): returned artist
|
||||
song (str): returned song
|
||||
src (str): source result was fetched from
|
||||
lyrics (str|list): str if plain lyrics, list for lrc
|
||||
lyrics (Union[str, list]): str if plain lyrics, list for lrc
|
||||
time (float): time taken to retrieve lyrics from source
|
||||
"""
|
||||
artist: str
|
||||
song: str
|
||||
src: str
|
||||
lyrics: str|list
|
||||
lyrics: Union[str, list]
|
||||
confidence: int
|
||||
time: float = 0.00
|
@ -1,5 +1,4 @@
|
||||
#!/usr/bin/env python3.12
|
||||
# pylint: disable=wrong-import-order, wrong-import-position
|
||||
|
||||
from typing import Optional
|
||||
from lyric_search.constructors import LyricsResult
|
||||
@ -22,7 +21,8 @@ class Aggregate:
|
||||
self.redis_cache = redis_cache.RedisCache()
|
||||
self.notifier = notifier.DiscordNotifier()
|
||||
|
||||
async def search(self, artist: str, song: str, plain: bool = True) -> Optional[LyricsResult]:
|
||||
async def search(self, artist: str, song: str,
|
||||
plain: Optional[bool] = True) -> Optional[LyricsResult]:
|
||||
"""
|
||||
Aggregate Search
|
||||
Args:
|
||||
@ -30,7 +30,7 @@ class Aggregate:
|
||||
song (str): Song to search
|
||||
plain (bool): Search for plain lyrics (lrc otherwise)
|
||||
Returns:
|
||||
LyricsResult|None: The result, if found - None otherwise.
|
||||
Optional[LyricsResult]: The result, if found - None otherwise.
|
||||
"""
|
||||
if not plain:
|
||||
logging.info("LRCs requested, limiting search to LRCLib")
|
||||
|
@ -9,7 +9,7 @@ import sys
|
||||
import traceback
|
||||
sys.path.insert(1,'..')
|
||||
sys.path.insert(1,'.')
|
||||
from typing import Optional, Any
|
||||
from typing import Optional, Union, LiteralString
|
||||
import aiosqlite as sqlite3
|
||||
from . import redis_cache
|
||||
from lyric_search import utils, notifier
|
||||
@ -23,7 +23,7 @@ log_level = logging.getLevelName(logger.level)
|
||||
class Cache:
|
||||
"""Cache Search Module"""
|
||||
def __init__(self) -> None:
|
||||
self.cache_db: str = os.path.join("/", "usr", "local", "share",
|
||||
self.cache_db: Union[str, LiteralString] = os.path.join("/", "usr", "local", "share",
|
||||
"sqlite_dbs", "cached_lyrics.db")
|
||||
self.redis_cache = redis_cache.RedisCache()
|
||||
self.notifier = notifier.DiscordNotifier()
|
||||
@ -34,16 +34,17 @@ class Cache:
|
||||
self.label: str = "Cache"
|
||||
|
||||
def get_matched(self, matched_candidate: tuple, confidence: int,
|
||||
sqlite_rows: list[sqlite3.Row] = None, redis_results: Any = None) -> Optional[LyricsResult]:
|
||||
sqlite_rows: Optional[list[sqlite3.Row]] = None,
|
||||
redis_results: Optional[list] = None) -> Optional[LyricsResult]:
|
||||
"""
|
||||
Get Matched Result
|
||||
Args:
|
||||
matched_candidate (tuple): the correctly matched candidate returned by matcher.best_match
|
||||
confidence (int): % confidence
|
||||
sqlite_rows (list[sqlite3.Row]|None): List of returned rows from SQLite DB, or None if Redis
|
||||
sqlite_rows (Optional[list[sqlite3.Row]]): List of returned rows from SQLite DB, or None if Redis
|
||||
redis_results (Any): List of Redis returned data, or None if SQLite
|
||||
Returns:
|
||||
LyricsResult|None: The result, if found - None otherwise.
|
||||
Optional[LyricsResult]: The result, if found - None otherwise.
|
||||
"""
|
||||
matched_id: int = matched_candidate[0]
|
||||
if redis_results:
|
||||
@ -60,7 +61,7 @@ class Cache:
|
||||
else:
|
||||
for row in sqlite_rows:
|
||||
if row[0] == matched_id:
|
||||
(_id, artist, song, lyrics, original_src, _confidence) = row
|
||||
(_id, artist, song, lyrics, original_src) = row[:-1]
|
||||
return LyricsResult(
|
||||
artist=artist,
|
||||
song=song,
|
||||
@ -119,7 +120,8 @@ class Cache:
|
||||
await self.notifier.send(f"ERROR @ {__file__.rsplit("/", maxsplit=1)[-1]}",
|
||||
f"cache::store >> {str(e)}")
|
||||
|
||||
async def sqlite_rowcount(self, where: Optional[str] = None, params: Optional[tuple] = None) -> int:
|
||||
async def sqlite_rowcount(self, where: Optional[str] = None,
|
||||
params: Optional[tuple] = None) -> int:
|
||||
"""
|
||||
Get rowcount for cached_lyrics DB
|
||||
Args:
|
||||
@ -217,7 +219,7 @@ class Cache:
|
||||
artist: the artist to search
|
||||
song: the song to search
|
||||
Returns:
|
||||
LyricsResult|None: The result, if found - None otherwise.
|
||||
Optional[LyricsResult]: The result, if found - None otherwise.
|
||||
"""
|
||||
try:
|
||||
# pylint: enable=unused-argument
|
||||
@ -253,7 +255,7 @@ class Cache:
|
||||
result_tracks.append((key, f"{track['artist']} - {track['song']}"))
|
||||
|
||||
if not random_search:
|
||||
best_match: tuple|None = matcher.find_best_match(input_track=input_track,
|
||||
best_match: Optional[tuple] = matcher.find_best_match(input_track=input_track,
|
||||
candidate_tracks=result_tracks)
|
||||
else:
|
||||
best_match = (result_tracks[0], 100)
|
||||
@ -298,7 +300,7 @@ class Cache:
|
||||
(_id, _artist, _song, _lyrics, _src, _confidence) = track
|
||||
result_tracks.append((_id, f"{_artist} - {_song}"))
|
||||
if not random_search:
|
||||
best_match: tuple|None = matcher.find_best_match(input_track=input_track,
|
||||
best_match: Optional[tuple] = matcher.find_best_match(input_track=input_track,
|
||||
candidate_tracks=result_tracks)
|
||||
else:
|
||||
best_match = (result_tracks[0], 100)
|
||||
@ -316,4 +318,3 @@ class Cache:
|
||||
return matched
|
||||
except:
|
||||
traceback.print_exc()
|
||||
return
|
@ -23,7 +23,9 @@ class InvalidResponseException(Exception):
|
||||
"""
|
||||
|
||||
class Genius:
|
||||
"""Genius Search Module"""
|
||||
"""
|
||||
Genius Search Module
|
||||
"""
|
||||
def __init__(self) -> None:
|
||||
self.label: str = "Genius"
|
||||
self.genius_url: str = private.GENIUS_URL
|
||||
@ -36,14 +38,15 @@ class Genius:
|
||||
self.redis_cache = redis_cache.RedisCache()
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
async def search(self, artist: str, song: str, **kwargs) -> Optional[LyricsResult]:
|
||||
async def search(self, artist: str, song: str,
|
||||
**kwargs) -> Optional[LyricsResult]:
|
||||
"""
|
||||
Genius Search
|
||||
Args:
|
||||
artist (str): the artist to search
|
||||
song (str): the song to search
|
||||
Returns:
|
||||
LyricsResult|None: The result, if found - None otherwise.
|
||||
Optional[LyricsResult]: The result, if found - None otherwise.
|
||||
"""
|
||||
try:
|
||||
# pylint: enable=unused-argument
|
||||
@ -59,7 +62,10 @@ class Genius:
|
||||
timeout=self.timeout,
|
||||
headers=self.headers) as request:
|
||||
request.raise_for_status()
|
||||
text: str|None = await request.text()
|
||||
text: Optional[str] = await request.text()
|
||||
|
||||
if not text:
|
||||
raise InvalidResponseException("No search response.")
|
||||
|
||||
if len(text) < 100:
|
||||
raise InvalidResponseException("Search response text was invalid (len < 100 chars.)")
|
||||
@ -94,14 +100,17 @@ class Genius:
|
||||
timeout=self.timeout,
|
||||
headers=self.headers) as scrape_request:
|
||||
scrape_request.raise_for_status()
|
||||
scrape_text: str|None = await scrape_request.text()
|
||||
scrape_text: Optional[str] = await scrape_request.text()
|
||||
|
||||
if not scrape_text:
|
||||
raise InvalidResponseException("No scrape response.")
|
||||
|
||||
if len(scrape_text) < 100:
|
||||
raise InvalidResponseException("Scrape response was invalid (len < 100 chars.)")
|
||||
|
||||
|
||||
html = BeautifulSoup(htm.unescape(scrape_text).replace('<br/>', '\n'), "html.parser")
|
||||
divs: ResultSet|None = html.find_all("div", {"data-lyrics-container": "true"})
|
||||
divs: Optional[ResultSet] = html.find_all("div", {"data-lyrics-container": "true"})
|
||||
|
||||
if not divs:
|
||||
return
|
||||
@ -124,8 +133,5 @@ class Genius:
|
||||
await self.redis_cache.increment_found_count(self.label)
|
||||
await self.cache.store(matched)
|
||||
return matched
|
||||
|
||||
except:
|
||||
# if log_level == "DEBUG":
|
||||
traceback.print_exc()
|
||||
return
|
@ -32,14 +32,15 @@ class LRCLib:
|
||||
self.cache = cache.Cache()
|
||||
self.redis_cache = redis_cache.RedisCache()
|
||||
|
||||
async def search(self, artist: str, song: str, plain: bool = True) -> Optional[LyricsResult]:
|
||||
async def search(self, artist: str, song: str,
|
||||
plain: Optional[bool] = True) -> Optional[LyricsResult]:
|
||||
"""
|
||||
LRCLib Search
|
||||
Args:
|
||||
artist (str): the artist to search
|
||||
song (str): the song to search
|
||||
Returns:
|
||||
LyricsResult|None: The result, if found - None otherwise.
|
||||
Optional[LyricsResult]: The result, if found - None otherwise.
|
||||
"""
|
||||
try:
|
||||
artist: str = artist.strip().lower()
|
||||
@ -61,12 +62,16 @@ class LRCLib:
|
||||
timeout=self.timeout,
|
||||
headers=self.headers) as request:
|
||||
request.raise_for_status()
|
||||
text: str|None = await request.text()
|
||||
|
||||
text: Optional[str] = await request.text()
|
||||
if not text:
|
||||
raise InvalidResponseException("No search response.")
|
||||
if len(text) < 100:
|
||||
raise InvalidResponseException("Search response text was invalid (len < 100 chars.)")
|
||||
|
||||
search_data: dict|None = await request.json()
|
||||
search_data: Optional[dict] = await request.json()
|
||||
if not isinstance(search_data, dict):
|
||||
raise InvalidResponseException("No JSON search data.")
|
||||
|
||||
# logging.info("Search Data:\n%s", search_data)
|
||||
|
||||
@ -126,4 +131,3 @@ class LRCLib:
|
||||
return matched
|
||||
except:
|
||||
traceback.print_exc()
|
||||
return
|
@ -1,7 +1,4 @@
|
||||
#!/usr/bin/env python3.12
|
||||
# pylint: disable=bare-except, broad-exception-caught, wrong-import-order
|
||||
# pylint: disable=wrong-import-position
|
||||
|
||||
|
||||
import logging
|
||||
import traceback
|
||||
@ -9,7 +6,9 @@ import json
|
||||
import time
|
||||
import sys
|
||||
import regex
|
||||
from regex import Pattern
|
||||
import asyncio
|
||||
from typing import Union, Optional
|
||||
sys.path.insert(1,'..')
|
||||
from lyric_search import notifier
|
||||
from lyric_search.constructors import LyricsResult
|
||||
@ -20,10 +19,6 @@ from redis.commands.search.field import TextField, TagField
|
||||
from redis.commands.json.path import Path
|
||||
from . import private
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
logger = logging.getLogger()
|
||||
log_level = logging.getLevelName(logger.level)
|
||||
|
||||
@ -41,14 +36,15 @@ class RedisCache:
|
||||
self.redis_client = redis.Redis(password=private.REDIS_PW)
|
||||
self.notifier = notifier.DiscordNotifier()
|
||||
self.notify_warnings = True
|
||||
self.regexes = [
|
||||
self.regexes: list[Pattern] = [
|
||||
regex.compile(r'\-'),
|
||||
regex.compile(r'[^a-zA-Z0-9\s]'),
|
||||
]
|
||||
try:
|
||||
asyncio.get_event_loop().create_task(self.create_index())
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
logging.debug("Failed to create redis create_index task: %s",
|
||||
str(e))
|
||||
|
||||
async def create_index(self) -> None:
|
||||
"""Create Index"""
|
||||
@ -64,10 +60,11 @@ class RedisCache:
|
||||
if str(result) != "OK":
|
||||
raise RedisException(f"Redis: Failed to create index: {result}")
|
||||
except Exception as e:
|
||||
pass
|
||||
# await self.notifier.send(f"ERROR @ {__file__.rsplit("/", maxsplit=1)[-1]}", f"Failed to create idx: {str(e)}")
|
||||
logging.debug("Failed to create redis index: %s",
|
||||
str(e))
|
||||
|
||||
def sanitize_input(self, artist: str, song: str, fuzzy: bool = False) -> tuple[str, str]:
|
||||
def sanitize_input(self, artist: str, song: str,
|
||||
fuzzy: Optional[bool] = False) -> tuple[str, str]:
|
||||
"""
|
||||
Sanitize artist/song input (convert to redis matchable fuzzy query)
|
||||
Args:
|
||||
@ -121,7 +118,9 @@ class RedisCache:
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
async def search(self, **kwargs) -> list[tuple]:
|
||||
async def search(self, artist: Optional[str] = None,
|
||||
song: Optional[str] = None,
|
||||
lyrics: Optional[str] = None) -> list[tuple]:
|
||||
"""
|
||||
Search Redis Cache
|
||||
Args:
|
||||
@ -133,9 +132,6 @@ class RedisCache:
|
||||
"""
|
||||
|
||||
try:
|
||||
artist = kwargs.get('artist', '')
|
||||
song = kwargs.get('song', '')
|
||||
lyrics = kwargs.get('lyrics')
|
||||
fuzzy_artist = None
|
||||
fuzzy_song = None
|
||||
is_random_search = artist == "!" and song == "!"
|
||||
@ -148,10 +144,10 @@ class RedisCache:
|
||||
logging.debug("Redis: Searching normally first")
|
||||
(artist, song) = self.sanitize_input(artist, song)
|
||||
logging.debug("Seeking: %s - %s", artist, song)
|
||||
search_res = await self.redis_client.ft().search(Query(
|
||||
search_res: Union[dict, list] = await self.redis_client.ft().search(Query(
|
||||
f"@artist:{artist} @song:{song}"
|
||||
))
|
||||
search_res_out = [(result['id'].split(":",
|
||||
search_res_out: list[tuple] = [(result['id'].split(":",
|
||||
maxsplit=1)[1], dict(json.loads(result['json'])))
|
||||
for result in search_res.docs]
|
||||
if not search_res_out:
|
||||
@ -167,8 +163,8 @@ class RedisCache:
|
||||
for result in search_res.docs]
|
||||
|
||||
else:
|
||||
random_redis_key = await self.redis_client.randomkey()
|
||||
out_id = str(random_redis_key).split(":",
|
||||
random_redis_key: str = await self.redis_client.randomkey()
|
||||
out_id: str = str(random_redis_key).split(":",
|
||||
maxsplit=1)[1][:-1]
|
||||
search_res = await self.redis_client.json().get(random_redis_key)
|
||||
search_res_out = [(out_id, search_res)]
|
||||
@ -179,7 +175,8 @@ class RedisCache:
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
# await self.notifier.send(f"ERROR @ {__file__.rsplit("/", maxsplit=1)[-1]}", f"{str(e)}\nSearch was: {artist} - {song}; fuzzy: {fuzzy_artist} - {fuzzy_song}")
|
||||
async def redis_store(self, sqlite_id: int, lyr_result: LyricsResult) -> None:
|
||||
async def redis_store(self, sqlite_id: int,
|
||||
lyr_result: LyricsResult) -> None:
|
||||
"""
|
||||
Store lyrics to redis cache
|
||||
Args:
|
||||
@ -191,7 +188,7 @@ class RedisCache:
|
||||
try:
|
||||
(search_artist, search_song) = self.sanitize_input(lyr_result.artist,
|
||||
lyr_result.song)
|
||||
redis_mapping = {
|
||||
redis_mapping: dict = {
|
||||
'id': sqlite_id,
|
||||
'src': lyr_result.src,
|
||||
'date_retrieved': time.time(),
|
||||
@ -206,8 +203,8 @@ class RedisCache:
|
||||
'tags': '(none)',
|
||||
'liked': 0,
|
||||
}
|
||||
newkey = f"lyrics:000{sqlite_id}"
|
||||
jsonset = await self.redis_client.json().set(newkey, Path.root_path(),
|
||||
newkey: str = f"lyrics:000{sqlite_id}"
|
||||
jsonset: bool = await self.redis_client.json().set(newkey, Path.root_path(),
|
||||
redis_mapping)
|
||||
if not jsonset:
|
||||
raise RedisException(f"Failed to store {lyr_result.artist} - {lyr_result.song} (SQLite id: {sqlite_id}) to redis:\n{jsonset}")
|
||||
|
@ -1,9 +1,10 @@
|
||||
#!/usr/bin/env python3.12
|
||||
|
||||
from difflib import SequenceMatcher
|
||||
from typing import List, Optional, Tuple
|
||||
from typing import List, Optional, Union, Any
|
||||
import logging
|
||||
import regex
|
||||
from regex import Pattern
|
||||
|
||||
class TrackMatcher:
|
||||
"""Track Matcher"""
|
||||
@ -17,7 +18,7 @@ class TrackMatcher:
|
||||
"""
|
||||
self.threshold = threshold
|
||||
|
||||
def find_best_match(self, input_track: str, candidate_tracks: List[tuple[int|str, str]]) -> Optional[Tuple[str, float]]:
|
||||
def find_best_match(self, input_track: str, candidate_tracks: List[tuple[int|str, str]]) -> Optional[tuple]:
|
||||
"""
|
||||
Find the best matching track from the candidate list.
|
||||
|
||||
@ -26,7 +27,7 @@ class TrackMatcher:
|
||||
candidate_tracks (List[tuple[int|str, str]]): List of candidate tracks
|
||||
|
||||
Returns:
|
||||
Optional[Tuple[int, str, float]]: Tuple of (best matching track, similarity score)
|
||||
Optional[tuple[int, str, float]]: Tuple of (best matching track, similarity score)
|
||||
or None if no good match found
|
||||
"""
|
||||
|
||||
@ -38,7 +39,7 @@ class TrackMatcher:
|
||||
input_track = self._normalize_string(input_track)
|
||||
|
||||
best_match = None
|
||||
best_score = 0
|
||||
best_score: float = 0.0
|
||||
|
||||
for candidate in candidate_tracks:
|
||||
normalized_candidate = self._normalize_string(candidate[1])
|
||||
@ -56,7 +57,10 @@ class TrackMatcher:
|
||||
best_match = candidate
|
||||
|
||||
# Return the match only if it meets the threshold
|
||||
return (best_match, round(best_score * 100)) if best_score >= self.threshold else None
|
||||
if best_score >= self.threshold:
|
||||
return None
|
||||
match: tuple = (best_match, round(best_score * 100))
|
||||
return match
|
||||
|
||||
def _normalize_string(self, text: str) -> str:
|
||||
"""
|
||||
@ -98,9 +102,13 @@ class DataUtils:
|
||||
Data Utils
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
self.lrc_regex = regex.compile(r'\[([0-9]{2}:[0-9]{2})\.[0-9]{1,3}\](\s(.*)){0,}')
|
||||
|
||||
self.scrub_regex_1: Pattern = regex.compile(r'(\[.*?\])(\s){0,}(\:){0,1}')
|
||||
self.scrub_regex_2: Pattern = regex.compile(r'(\d?)(Embed\b)',
|
||||
flags=regex.IGNORECASe)
|
||||
self.scrub_regex_3: Pattern = regex.compile(r'\n{2}')
|
||||
self.scrub_regex_4: Pattern = regex.compile(r'[0-9]\b$')
|
||||
|
||||
def scrub_lyrics(self, lyrics: str) -> str:
|
||||
"""
|
||||
@ -110,10 +118,10 @@ class DataUtils:
|
||||
Returns:
|
||||
str: Regex scrubbed lyrics
|
||||
"""
|
||||
lyrics = regex.sub(r'(\[.*?\])(\s){0,}(\:){0,1}', '', lyrics)
|
||||
lyrics = regex.sub(r'(\d?)(Embed\b)', '', lyrics, flags=regex.IGNORECASE)
|
||||
lyrics = regex.sub(r'\n{2}', '\n', lyrics) # Gaps between verses
|
||||
lyrics = regex.sub(r'[0-9]\b$', '', lyrics)
|
||||
lyrics = self.scrub_regex_1.sub('', lyrics)
|
||||
lyrics = self.scrub_regex_2.sub('', lyrics, flags=regex.IGNORECASE)
|
||||
lyrics = self.scrub_regex_3.sub('\n', lyrics) # Gaps between verses
|
||||
lyrics = self.scrub_regex_3.sub('', lyrics)
|
||||
return lyrics
|
||||
|
||||
def create_lrc_object(self, lrc_str: str) -> list[dict]:
|
||||
|
35
util.py
35
util.py
@ -1,29 +1,36 @@
|
||||
#!/usr/bin/env python3.12
|
||||
|
||||
import logging
|
||||
|
||||
from typing import Optional
|
||||
from fastapi import FastAPI, Response, HTTPException
|
||||
from fastapi.responses import RedirectResponse
|
||||
|
||||
|
||||
class Utilities:
|
||||
"""API Utilities"""
|
||||
"""
|
||||
API Utilities
|
||||
"""
|
||||
def __init__(self, app: FastAPI, constants):
|
||||
self.constants = constants
|
||||
self.blocked_redirect_uri = "https://codey.lol"
|
||||
self.app = app
|
||||
|
||||
def get_blocked_response(self, path: str | None = None): # pylint: disable=unused-argument
|
||||
"""Get Blocked HTTP Response"""
|
||||
def get_blocked_response(self, path: Optional[str] = None):
|
||||
"""
|
||||
Get Blocked HTTP Response
|
||||
"""
|
||||
logging.error("Rejected request: Blocked")
|
||||
return RedirectResponse(url=self.blocked_redirect_uri)
|
||||
|
||||
def get_no_endpoint_found(self, path: str | None = None): # pylint: disable=unused-argument
|
||||
"""Get 404 Response"""
|
||||
def get_no_endpoint_found(self, path: Optional[str] = None):
|
||||
"""
|
||||
Get 404 Response
|
||||
"""
|
||||
logging.error("Rejected request: No such endpoint")
|
||||
raise HTTPException(detail="Unknown endpoint", status_code=404)
|
||||
|
||||
def check_key(self, path: str, key: str, req_type: int = 0):
|
||||
def check_key(self, path: str, key: str,
|
||||
req_type: int = 0) -> bool:
|
||||
"""
|
||||
Accepts path as an argument to allow fine tuning access for each API key, not currently in use.
|
||||
"""
|
||||
@ -31,19 +38,19 @@ class Utilities:
|
||||
if not key or not key.startswith("Bearer "):
|
||||
return False
|
||||
|
||||
key = key.split("Bearer ", maxsplit=1)[1].strip()
|
||||
_key: str = key.split("Bearer ", maxsplit=1)[1].strip()
|
||||
|
||||
if not key in self.constants.API_KEYS:
|
||||
if not _key in self.constants.API_KEYS:
|
||||
return False
|
||||
|
||||
if req_type == 2:
|
||||
return key.startswith("PRV-")
|
||||
return _key.startswith("PRV-")
|
||||
elif req_type == 4:
|
||||
return key.startswith("RAD-")
|
||||
|
||||
if path.lower().startswith("/xc/") and not key.startswith("XC-"):
|
||||
return False
|
||||
return _key.startswith("RAD-")
|
||||
|
||||
if path.lower().startswith("/xc/")\
|
||||
and not key.startswith("XC-"):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user