This commit is contained in:
2025-02-16 20:07:02 -05:00
parent bb59b5a457
commit 9df3b37619
17 changed files with 196 additions and 56 deletions

View File

@ -29,6 +29,7 @@ class CatboxAsync:
def generateRandomFileName(self, fileExt: Optional[str] = None) -> str:
"""
Generate random file name
Args:
fileExt (Optional[str]): File extension to use for naming
Returns:
@ -41,6 +42,7 @@ class CatboxAsync:
async def upload(self, file: str) -> Optional[str]:
"""
Upload file to catbox
Args:
file (str): Path of file to be uploaded
Returns:

View File

@ -11,6 +11,7 @@ async def get_channel_by_name(bot: discord.Bot, channel: str,
guild: int | None = None) -> Optional[Any]: # Optional[Any] used as pycord channel types can be ambigious
"""
Get Channel by Name
Args:
bot (discord.Bot)
channel (str)
@ -37,6 +38,7 @@ async def send_message(bot: discord.Bot, channel: str,
"""
Send Message to the provided channel. If guild is provided, will limit to channels within that guild to ensure the correct
channel is selected. Useful in the event a channel exists in more than one guild that the bot resides in.
Args:
bot (discord.Bot)
channel (str)

View File

@ -35,12 +35,15 @@ class JesusMemeGenerator():
meme="Jesus-Talking-To-Cool-Dude") -> Optional[str]:
"""
Create Meme
Args:
top_line (str): Top line of meme
bottom_line (str): Bottom line of meme
meme (str): The meme to use, defaults to Jesus-Talking-To-Cool-Dude
Returns:
Optional[str]
"""
try:
if not top_line or not bottom_line:

View File

@ -30,6 +30,7 @@ class LitterboxAsync:
def generateRandomFileName(self, fileExt: Optional[str] = None) -> str:
"""
Generate Random Filename
Args:
fileExt (Optional[str]): File extension to use for naming
Returns:
@ -44,6 +45,7 @@ class LitterboxAsync:
time='1h') -> Optional[str]:
"""
Upload File to Litterbox
Args:
file (Union[str, bytes, BufferedReader]): File to upload (accepts either filepath or io.BufferedReader)
time (str): Expiration time, default: 1h

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python3.12
import os
import logging
from typing import Optional, LiteralString
from typing import LiteralString, Optional, Union
import aiosqlite as sqlite3
from constructors import LoveHateException
@ -47,16 +47,18 @@ class DB:
return result
async def get_lovehates(self, loves: bool = False, hates: bool = False,
user: Optional[str] = None, thing: Optional[str] = None) -> list[tuple]|bool:
user: Optional[str] = None, thing: Optional[str] = None) -> Union[list[tuple], bool]:
"""
Get a list of either 1) what {user} loves/hates, or who loves/hates {thing}, depending on bools loves, hates
Args:
loves (bool): Are we looking for loves?
hates (bool): ...OR are we looking for hates?
user (Optional[str]): the user to query against
thing (Optional[str]): ... OR the thing to query against
Returns:
list[tuple]|bool
Union[list[tuple], bool]
"""
query: str = ""
@ -94,6 +96,7 @@ class DB:
async def check_existence(self, user: str, thing: str) -> Optional[int]:
"""
Determine whether a user is opinionated on a <thing>
Args:
user (str): The user to check
thing (str): The thing to check if the user has an opinion on
@ -114,6 +117,7 @@ class DB:
async def update(self, user: str, thing: str, flag: int) -> str:
"""
Updates the lovehate database, and returns an appropriate response
Args:
user (str): The user to update
thing (str): The thing the user loves/hates/doesn't care about anymore

View File

@ -7,6 +7,12 @@ from aiohttp import ClientSession, ClientTimeout
"""Radio Utils"""
async def get_now_playing() -> Optional[str]:
"""
Get radio now playing
Returns:
str
"""
np_url: str = "https://api.codey.lol/radio/np"
try:
async with ClientSession() as session:

View File

@ -4,23 +4,26 @@ import regex
import aiohttp
import textwrap
import traceback
from typing import Optional
from discord import Activity
from typing import Optional, Union
class Utility:
"""Sing Utility"""
def __init__(self):
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) -> bool|tuple:
"""Parse Song (Sing Command) Input
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:
bool|tuple
Union[bool, tuple]
"""
logging.debug("Activity? %s", activity)
try:
@ -28,58 +31,70 @@ class Utility:
# pylint: disable=superfluous-parens
return False
# pylint: enable=superfluous-parens
if not song and activity:
if not song and isinstance(activity, 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: str = regex.sub(r"(\s{0,})(\[(spotify|tidal|sonixd|browser|yt music)])$", "",
search_artist = regex.sub(r"(\s{0,})(\[(spotify|tidal|sonixd|browser|yt music)])$", "",
search_artist.strip(), flags=regex.IGNORECASE)
search_song: str = str(activity.details)
song: str = f"{search_artist} : {search_song}"
search_song = str(activity.details)
song = f"{search_artist} : {search_song}"
case "tidal hi-fi":
search_artist: str = str(activity.state)
search_song: str = str(activity.details)
song: str = f"{search_artist} : {search_song}"
search_artist = str(activity.state)
search_song = str(activity.details)
song = f"{search_artist} : {search_song}"
case "spotify":
search_artist: str = str(activity.title)
search_song: str = str(activity.artist)
song: str = f"{search_artist} : {search_song}"
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 = str(activity.state)
song = str(activity.state)
else:
search_artist: str = str(activity.state)
search_song: str = str(activity.details)
song: str = f"{search_artist} : {search_song}"
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
search_artist: str = song.split(search_split_by)[0].strip()
search_song: str = "".join(song.split(search_split_by)[1:]).strip()
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: str = 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: str = "".join(song.split(search_split_by)[2:]) # Lyric text from split index 2 and beyond
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) -> list[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
sub (Optional[str]): Lyrics for subsearch
Returns:
Optional[list]
"""
try:
if not artist or not song:
return ["FAIL! Artist/Song not provided"]
return [("FAIL! Artist/Song not provided",)]
search_obj: dict = {
'a': artist.strip(),
@ -103,7 +118,7 @@ class Utility:
request.raise_for_status()
response: dict = await request.json()
if response.get('err'):
return [f"ERR: {response.get('errorText')}"]
return [(f"ERR: {response.get('errorText')}",)]
out_lyrics = regex.sub(r'<br>', '\u200B\n', response.get('lyrics'))
response_obj: dict = {
@ -111,11 +126,13 @@ class Utility:
'song': response.get('song'),
'lyrics': out_lyrics,
'src': response.get('src'),
'confidence': float(response.get('confidence')),
'time': float(response.get('time')),
'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,
@ -128,7 +145,7 @@ class Utility:
return [
(
response_obj.get('artist'), response_obj.get('song'), response_obj.get('src'),
f"{int(response_obj.get('confidence'))}%",
f"{int(response_obj.get('confidence', -1.0))}%",
f"{response_obj.get('time', -666.0):.4f}s",
),
response_obj.get('lyrics'),