187 lines
7.5 KiB
Python
187 lines
7.5 KiB
Python
import logging
|
|
import regex
|
|
import aiohttp
|
|
import textwrap
|
|
import traceback
|
|
from discord import Activity
|
|
from typing import Optional, Union
|
|
|
|
|
|
class Utility:
|
|
"""Sing Utility"""
|
|
|
|
def __init__(self) -> None:
|
|
self.api_url: str = "http://127.0.0.1:52111/lyric/search"
|
|
self.api_src: str = "DISC-HAVOC"
|
|
|
|
def parse_song_input(
|
|
self, song: Optional[str] = None, activity: Optional[Activity] = None
|
|
) -> Union[bool, tuple]:
|
|
"""
|
|
Parse Song (Sing Command) Input
|
|
|
|
Args:
|
|
song (Optional[str]): Song to search
|
|
activity (Optional[discord.Activity]): Discord activity, used to attempt lookup if no song is provided
|
|
|
|
Returns:
|
|
Union[bool, tuple]
|
|
"""
|
|
try:
|
|
if (not song or len(song) < 2) and not activity:
|
|
return False
|
|
if not song and activity:
|
|
if not activity.name:
|
|
return False # No valid activity found
|
|
match activity.name.lower():
|
|
case "codey toons" | "cider" | "sonixd":
|
|
search_artist: str = " ".join(
|
|
str(activity.state).strip().split(" ")[1:]
|
|
)
|
|
search_artist = regex.sub(
|
|
r"(\s{0,})(\[(spotify|tidal|sonixd|browser|yt music)])$",
|
|
"",
|
|
search_artist.strip(),
|
|
flags=regex.IGNORECASE,
|
|
)
|
|
search_song = str(activity.details)
|
|
song = f"{search_artist} : {search_song}"
|
|
case "tidal hi-fi":
|
|
search_artist = str(activity.state)
|
|
search_song = str(activity.details)
|
|
song = f"{search_artist} : {search_song}"
|
|
case "spotify":
|
|
if not activity.title or not activity.artist: # type: ignore
|
|
"""
|
|
Attributes exist, but mypy does not recognize them. Ignored.
|
|
"""
|
|
return False
|
|
search_artist = str(activity.title) # type: ignore
|
|
search_song = str(activity.artist) # type: ignore
|
|
song = f"{search_artist} : {search_song}"
|
|
case "serious.fm" | "cocks.fm" | "something":
|
|
if not activity.details:
|
|
song = str(activity.state)
|
|
else:
|
|
search_artist = str(activity.state).rsplit("[", maxsplit=1)[
|
|
0
|
|
] # Strip genre
|
|
search_song = str(activity.details)
|
|
song = f"{search_artist} : {search_song}"
|
|
case _:
|
|
return False # Unsupported activity detected
|
|
|
|
search_split_by: str = (
|
|
":" if not (song) or len(song.split(":")) > 1 else "-"
|
|
) # Support either : or - to separate artist/track
|
|
if not song:
|
|
return False
|
|
search_artist = song.split(search_split_by)[0].strip()
|
|
search_song = "".join(song.split(search_split_by)[1:]).strip()
|
|
search_subsearch: Optional[str] = None
|
|
if (
|
|
search_split_by == ":" and len(song.split(":")) > 2
|
|
): # Support sub-search if : is used (per instructions)
|
|
search_song = song.split(search_split_by)[
|
|
1
|
|
].strip() # Reduce search_song to only the 2nd split of : [the rest is meant to be lyric text]
|
|
search_subsearch = "".join(
|
|
song.split(search_split_by)[2:]
|
|
) # Lyric text from split index 2 and beyond
|
|
return (search_artist, search_song, search_subsearch)
|
|
except:
|
|
traceback.print_exc()
|
|
return False
|
|
|
|
async def lyric_search(
|
|
self, artist: str, song: str, sub: Optional[str] = None
|
|
) -> Optional[list]:
|
|
"""
|
|
Lyric Search
|
|
|
|
Args:
|
|
artist (str): Artist to search
|
|
song (str): Song to search
|
|
sub (Optional[str]): Lyrics for subsearch
|
|
Returns:
|
|
Optional[list]
|
|
"""
|
|
try:
|
|
if not artist or not song:
|
|
return [("FAIL! Artist/Song not provided",)]
|
|
|
|
search_obj: dict = {
|
|
"a": artist.strip(),
|
|
"s": song.strip(),
|
|
"extra": True,
|
|
"src": self.api_src,
|
|
}
|
|
|
|
if len(song.strip()) < 1:
|
|
search_obj.pop("a")
|
|
search_obj.pop("s")
|
|
search_obj["t"] = artist.strip() # Parse failed, try title without sep
|
|
|
|
if sub and len(sub) >= 2:
|
|
search_obj["sub"] = sub.strip()
|
|
|
|
async with aiohttp.ClientSession() as session:
|
|
async with await session.post(
|
|
self.api_url,
|
|
json=search_obj,
|
|
timeout=aiohttp.ClientTimeout(connect=5, sock_read=10),
|
|
) as request:
|
|
request.raise_for_status()
|
|
response: dict = await request.json()
|
|
if response.get("err"):
|
|
return [(f"ERR: {response.get('errorText')}",)]
|
|
|
|
out_lyrics = regex.sub(
|
|
r"<br>", "\u200b\n", response.get("lyrics", "")
|
|
)
|
|
response_obj: dict = {
|
|
"artist": response.get("artist"),
|
|
"song": response.get("song"),
|
|
"lyrics": out_lyrics,
|
|
"src": response.get("src"),
|
|
"confidence": float(response.get("confidence", 0.0)),
|
|
"time": float(response.get("time", -1.0)),
|
|
}
|
|
|
|
lyrics = response_obj.get("lyrics")
|
|
if not lyrics:
|
|
return None
|
|
response_obj["lyrics"] = textwrap.wrap(
|
|
text=lyrics.strip(),
|
|
width=1500,
|
|
drop_whitespace=False,
|
|
replace_whitespace=False,
|
|
break_long_words=True,
|
|
break_on_hyphens=True,
|
|
max_lines=8,
|
|
)
|
|
response_obj["lyrics_short"] = textwrap.wrap(
|
|
text=lyrics.strip(),
|
|
width=750,
|
|
drop_whitespace=False,
|
|
replace_whitespace=False,
|
|
break_long_words=True,
|
|
break_on_hyphens=True,
|
|
max_lines=1,
|
|
)
|
|
|
|
return [
|
|
(
|
|
response_obj.get("artist"),
|
|
response_obj.get("song"),
|
|
response_obj.get("src"),
|
|
f"{int(response_obj.get('confidence', -1.0))}%",
|
|
f"{response_obj.get('time', -666.0):.4f}s",
|
|
),
|
|
response_obj.get("lyrics"),
|
|
response_obj.get("lyrics_short"),
|
|
]
|
|
except Exception as e:
|
|
traceback.print_exc()
|
|
return [f"Retrieval failed: {str(e)}"]
|