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