193 lines
9.3 KiB
Python
Raw Normal View History

2025-02-13 14:51:35 -05:00
import traceback
import logging
from typing import Optional, Union
2025-02-14 07:42:32 -05:00
from regex import Pattern
2025-02-13 14:51:35 -05:00
import urllib
import discord
import regex
from util.sing_util import Utility
from discord.ext import bridge, commands
2025-02-15 08:36:45 -05:00
from disc_havoc import Havoc
2025-02-13 14:51:35 -05:00
BOT_CHANIDS = []
class Sing(commands.Cog):
"""Sing Cog for Havoc"""
2025-02-15 08:36:45 -05:00
def __init__(self, bot: Havoc) -> None:
self.bot: Havoc = bot
2025-02-13 14:51:35 -05:00
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)
2025-02-15 08:36:45 -05:00
def is_spamchan(): # type: ignore
2025-02-13 14:51:35 -05:00
"""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.
2025-02-16 20:07:02 -05:00
2025-02-13 14:51:35 -05:00
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:
2025-02-15 08:36:45 -05:00
activity = _activity
2025-02-13 14:51:35 -05:00
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
2025-02-13 14:51:35 -05:00
2025-02-15 08:36:45 -05:00
parsed = self.utility.parse_song_input(song, activity)
2025-03-01 07:56:47 -05:00
2025-02-15 08:36:45 -05:00
if isinstance(parsed, tuple):
(search_artist, search_song, search_subsearch) = parsed
2025-02-13 14:51:35 -05:00
# await ctx.respond(f"So, {search_song} by {search_artist}? Subsearch: {search_subsearch} I will try...") # Commented, useful for debugging
2025-02-24 06:21:56 -05:00
search_result: Optional[list] = await self.utility.lyric_search(search_artist, search_song,
2025-02-13 14:51:35 -05:00
search_subsearch)
2025-02-24 06:21:56 -05:00
if not search_result:
await ctx.respond("ERR: No search result.")
return
2025-02-13 14:51:35 -05:00
if len(search_result) == 1:
2025-02-24 06:21:56 -05:00
return await ctx.respond("ERR: Not found!")
2025-02-15 08:36:45 -05:00
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
2025-02-13 14:51:35 -05:00
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}")
2025-03-29 06:41:03 -04:00
2025-02-13 14:51:35 -05:00
c: int = 0
2025-03-29 06:41:03 -04:00
out_messages: list = []
footer: str = "" # Placeholder
2025-02-13 14:51:35 -05:00
for section in search_result_wrapped:
c+=1
2025-03-29 06:41:03 -04:00
if c == len(search_result_wrapped):
2025-02-15 08:36:45 -05:00
footer = f"Found on: {search_result_src}"
2025-02-13 14:51:35 -05:00
# if ctx.guild.id == 1145182936002482196:
# section = section.upper()
2025-03-29 06:41:03 -04:00
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)
2025-02-13 14:51:35 -05:00
except Exception as e:
traceback.print_exc()
2025-02-24 06:21:56 -05:00
await ctx.respond(f"ERR: {str(e)}")
2025-02-13 14:51:35 -05:00
@commands.user_command(name="Sing")
async def sing_context_menu(self, ctx, member: discord.Member) -> None:
"""
Sing Context Menu Command
2025-02-16 20:07:02 -05:00
2025-02-13 14:51:35 -05:00
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
2025-03-16 07:54:41 -04:00
if IS_SPAMCHAN:
await ctx.respond(f"***Reading activity of {member_display}...***")
2025-02-13 14:51:35 -05:00
for _activity in ctx.interaction.guild.get_member(member_id).activities:
if _activity.type == discord.ActivityType.listening:
2025-02-15 08:36:45 -05:00
activity = _activity
parsed: Union[tuple, bool] = self.utility.parse_song_input(song=None,
2025-02-13 14:51:35 -05:00
activity=activity)
if not parsed:
return await ctx.respond(f"Could not parse activity of {member_display}.", ephemeral=True)
2025-02-15 08:36:45 -05:00
if isinstance(parsed, tuple):
(search_artist, search_song, search_subsearch) = parsed
await ctx.respond("*Searching...*") # Must respond to interactions within 3 seconds, per Discord
2025-02-24 06:21:56 -05:00
search_result: Optional[list] = await self.utility.lyric_search(search_artist, search_song,
2025-02-13 14:51:35 -05:00
search_subsearch)
2025-02-24 06:21:56 -05:00
if not search_result:
await ctx.respond("ERR: No search result")
return
2025-03-16 08:11:40 -04:00
if len(search_result) == 1 and\
isinstance(search_result[0][0], str):
2025-02-24 06:21:56 -05:00
return await ctx.send("ERR: No search result") # Error message from API
2025-02-13 14:51:35 -05:00
2025-02-15 08:36:45 -05:00
(search_result_artist, search_result_song, search_result_src,
search_result_confidence, search_result_time_taken) = search_result[0] # First index is a tuple
2025-02-24 06:21:56 -05:00
search_result_wrapped: list = search_result[1] # Second index is the wrapped lyrics
2025-02-15 08:36:45 -05:00
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}")
2025-02-15 08:36:45 -05:00
2025-03-29 06:41:03 -04:00
out_messages: list = []
footer: str = ""
2025-02-15 08:36:45 -05:00
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()
2025-03-29 06:41:03 -04:00
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)
2025-02-13 14:51:35 -05:00
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))