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"
", "\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)}"]