import traceback import logging from typing import Optional, Union from regex import Pattern import urllib import discord import regex from util.sing_util import Utility from discord.ext import bridge, commands from disc_havoc import Havoc BOT_CHANIDS = [] class Sing(commands.Cog): """Sing Cog for Havoc""" def __init__(self, bot: Havoc) -> None: self.bot: Havoc = bot self.utility = Utility() global BOT_CHANIDS BOT_CHANIDS = self.bot.BOT_CHANIDS # Inherit self.control_strip_regex: Pattern = regex.compile( r"\x0f|\x1f|\035|\002|\u2064|\x02|(\x03([0-9]{1,2}))|(\x03|\003)(?:\d{1,2}(?:,\d{1,2})?)?", regex.UNICODE, ) def is_spamchan(): # type: ignore """Check if channel is spam chan""" def predicate(ctx): try: if not ctx.channel.id in BOT_CHANIDS: logging.debug("%s not found in %s", ctx.channel.id, BOT_CHANIDS) return ctx.channel.id in BOT_CHANIDS except: traceback.print_exc() return False return commands.check(predicate) @bridge.bridge_command(aliases=["sing"]) async def s(self, ctx, *, song: Optional[str] = None) -> None: """ Search for lyrics, format is artist : song. Also reads activity. """ try: with ctx.channel.typing(): interaction: bool = isinstance( ctx, discord.ext.bridge.BridgeApplicationContext ) activity: Optional[discord.Activity] = None if not song: if not ctx.author.activities: return for _activity in ctx.author.activities: if _activity.type == discord.ActivityType.listening: activity = _activity if not activity: return await ctx.respond( "**Error**: No song specified, no activity found to read." ) if interaction: await ctx.respond( "*Searching...*" ) # Must respond to interactions within 3 seconds, per Discord parsed = self.utility.parse_song_input(song, activity) if isinstance(parsed, tuple): (search_artist, search_song, search_subsearch) = parsed # await ctx.respond(f"So, {search_song} by {search_artist}? Subsearch: {search_subsearch} I will try...") # Commented, useful for debugging search_result: Optional[list] = await self.utility.lyric_search( search_artist, search_song, search_subsearch ) if not search_result: await ctx.respond("ERR: No search result.") return if len(search_result) == 1: # Error response from API error, *_ = search_result[0] return await ctx.respond(error) if not isinstance(search_result[0], tuple): return # Invalid data type ( search_result_artist, search_result_song, search_result_src, search_result_confidence, search_result_time_taken, ) = search_result[ 0 ] # First index is a tuple search_result_wrapped: list[str] = search_result[ 1 ] # Second index is the wrapped lyrics search_result_wrapped_short: list[str] = search_result[ 2 ] # Third is short wrapped lyrics if not ctx.channel.id in BOT_CHANIDS: short_lyrics = " ".join( search_result_wrapped_short ) # Replace with shortened lyrics for non spamchans short_lyrics = regex.sub( r"\p{Vert_Space}", " / ", short_lyrics.strip() ) return await ctx.respond( f"**{search_result_song}** by **{search_result_artist}**\n-# {short_lyrics}" ) c: int = 0 out_messages: list = [] footer: str = "" # Placeholder for section in search_result_wrapped: c += 1 if c == len(search_result_wrapped): footer = f"`Found on: {search_result_src}`" # if ctx.guild.id == 1145182936002482196: # section = section.upper() section = regex.sub(r"\p{Vert_Space}", " / ", section.strip()) msg: str = ( f"**{search_result_song}** by **{search_result_artist}**\n-# {section}\n{footer}" ) if c > 1: msg = "\n".join(msg.split("\n")[1:]) out_messages.append(msg.strip()) for msg in out_messages: await ctx.send(msg) except Exception as e: traceback.print_exc() await ctx.respond(f"ERR: {str(e)}") @commands.user_command(name="Sing") async def sing_context_menu(self, ctx, member: discord.Member) -> None: """ Sing Context Menu Command Args: ctx (Any): Discord context member (discord.Member): Discord member Returns: None """ try: PODY_ID: int = 1172340700663255091 IS_SPAMCHAN: bool = ctx.channel.id in BOT_CHANIDS member_display = ctx.interaction.guild.get_member(member.id).display_name if ( not (ctx.interaction.guild.get_member(member.id).activities) and not member.id == PODY_ID ): return await ctx.respond( f"No activity detected to read for {member_display}.", ephemeral=True, ) member_id: int = ( member.id ) # if not(member.id == PODY_ID) else 1234134345497837679 # Use Thomas for Pody! activity: Optional[discord.Activity] = None if IS_SPAMCHAN: await ctx.respond(f"***Reading activity of {member_display}...***") for _activity in ctx.interaction.guild.get_member(member_id).activities: if _activity.type == discord.ActivityType.listening: activity = _activity parsed: Union[tuple, bool] = self.utility.parse_song_input( song=None, activity=activity ) if not parsed: return await ctx.respond( f"Could not parse activity of {member_display}.", ephemeral=True ) if isinstance(parsed, tuple): (search_artist, search_song, search_subsearch) = parsed await ctx.respond( "*Searching...*" ) # Must respond to interactions within 3 seconds, per Discord search_result: Optional[list] = await self.utility.lyric_search( search_artist, search_song, search_subsearch ) if not search_result: await ctx.respond("ERR: No search result") return if len(search_result) == 1 and isinstance(search_result[0][0], str): return await ctx.send( "ERR: No search result" ) # Error message from API ( search_result_artist, search_result_song, search_result_src, search_result_confidence, search_result_time_taken, ) = search_result[ 0 ] # First index is a tuple search_result_wrapped: list = search_result[ 1 ] # Second index is the wrapped lyrics search_result_wrapped_short: list[str] = search_result[ 2 ] # Third index is shortened lyrics if not IS_SPAMCHAN: short_lyrics = " ".join( search_result_wrapped_short ) # Replace with shortened lyrics for non spamchans short_lyrics = regex.sub( r"\p{Vert_Space}", " / ", short_lyrics.strip() ) return await ctx.respond( f"**{search_result_song}** by **{search_result_artist}**\n-# {short_lyrics}" ) out_messages: list = [] footer: str = "" c: int = 0 for section in search_result_wrapped: c += 1 if c == len(search_result_wrapped): footer = f"`Found on: {search_result_src}`" # if ctx.guild.id == 1145182936002482196: # section = section.upper() section = regex.sub(r"\p{Vert_Space}", " / ", section.strip()) msg: str = ( f"**{search_result_song}** by **{search_result_artist}**\n-# {section}\n{footer}" ) if c > 1: msg = "\n".join(msg.split("\n")[1:]) out_messages.append(msg.strip()) for msg in out_messages: await ctx.send(msg) except Exception as e: traceback.print_exc() return await ctx.respond(f"ERR: {str(e)}") def setup(bot) -> None: """Run on Cog Load""" bot.add_cog(Sing(bot))