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. Args: ctx (Any): Discord context song (Optional[str]): Song to search Returns: None """ 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...*", ephemeral=True) # 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: return await ctx.respond("ERR: Not found!") 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: search_result_wrapped = search_result_wrapped_short # Replace with shortened lyrics for non spamchans embeds: list[Optional[discord.Embed]] = [] embed_url: str = f"[on codey.lol](https://codey.lol/#{urllib.parse.quote(search_artist)}/{urllib.parse.quote(search_song)})" c: int = 0 footer: str = "To be continued..." #Placeholder for section in search_result_wrapped: c+=1 if c == len(search_result_wrapped): footer = f"Found on: {search_result_src}" section = self.control_strip_regex.sub('', section) # if ctx.guild.id == 1145182936002482196: # section = section.upper() embed: discord.Embed = discord.Embed( title=f"{search_result_song} by {search_result_artist}", description=discord.utils.escape_markdown(section) ) embed.add_field(name="Confidence", value=search_result_confidence, inline=True) embed.add_field(name="Time Taken", value=search_result_time_taken, inline=True) embed.add_field(name="Link", value=embed_url) embed.set_footer(text=footer) embeds.append(embed) await ctx.respond(embed=embeds[0]) for _embed in embeds[1:]: if isinstance(_embed, discord.Embed): await ctx.send(embed=_embed) 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...*", ephemeral=True) # 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 isinstance(search_result[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: search_result_wrapped = search_result_wrapped_short # Swap for shortened lyrics if not spam chan embeds: list[Optional[discord.Embed]] = [] c: int = 0 footer: str = "To be continued..." #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() embed: discord.Embed = discord.Embed( title=f"{search_result_song} by {search_result_artist}", description=discord.utils.escape_markdown(section) ) embed.add_field(name="Confidence", value=search_result_confidence, inline=True) embed.add_field(name="Time Taken", value=search_result_time_taken, inline=True) embed.add_field(name="Link", value=f"[on codey.lol](https://codey.lol/#{urllib.parse.quote(search_result_artist)}/{urllib.parse.quote(search_result_song)})") embed.set_footer(text=footer) embeds.append(embed) for _embed in embeds: await ctx.send(embed=_embed) 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))