185 lines
9.3 KiB
Python
185 lines
9.3 KiB
Python
|
#!/usr/bin/env python3.12
|
||
|
# pylint: disable=bare-except, broad-exception-caught, global-statement, invalid-name
|
||
|
|
||
|
import traceback
|
||
|
import logging
|
||
|
from typing import Optional, Pattern
|
||
|
import urllib
|
||
|
import discord
|
||
|
import regex
|
||
|
from util.sing_util import Utility
|
||
|
from discord.ext import bridge, commands
|
||
|
|
||
|
BOT_CHANIDS = []
|
||
|
|
||
|
class Sing(commands.Cog):
|
||
|
"""Sing Cog for Havoc"""
|
||
|
def __init__(self, bot):
|
||
|
self.bot: discord.Bot = 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(): # pylint: disable=no-method-argument
|
||
|
"""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: discord.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
|
||
|
|
||
|
(search_artist, search_song, search_subsearch) = self.utility.parse_song_input(song, activity)
|
||
|
|
||
|
# await ctx.respond(f"So, {search_song} by {search_artist}? Subsearch: {search_subsearch} I will try...") # Commented, useful for debugging
|
||
|
search_result: list[str] = await self.utility.lyric_search(search_artist, search_song,
|
||
|
search_subsearch)
|
||
|
|
||
|
if len(search_result) == 1:
|
||
|
return await ctx.respond(search_result[0].strip())
|
||
|
|
||
|
(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: list[str] = 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: str = f"Found on: {search_result_src}"
|
||
|
section: str = 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.replace("\n", "\n\n"))
|
||
|
)
|
||
|
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:]:
|
||
|
await ctx.send(embed=embed)
|
||
|
except Exception as e:
|
||
|
traceback.print_exc()
|
||
|
return 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
|
||
|
for _activity in ctx.interaction.guild.get_member(member_id).activities:
|
||
|
if _activity.type == discord.ActivityType.listening:
|
||
|
activity: discord.Activity = _activity
|
||
|
parsed: 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 IS_SPAMCHAN:
|
||
|
await ctx.respond(f"***Reading activity of {member_display}...***")
|
||
|
|
||
|
(search_artist, search_song, search_subsearch) = parsed
|
||
|
await ctx.respond("*Searching...*", ephemeral=True) # Must respond to interactions within 3 seconds, per Discord
|
||
|
search_result: list = await self.utility.lyric_search(search_artist, search_song,
|
||
|
search_subsearch)
|
||
|
|
||
|
if len(search_result) == 1:
|
||
|
return await ctx.send(search_result[0].strip())
|
||
|
|
||
|
(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 index is shortened lyrics
|
||
|
|
||
|
if not IS_SPAMCHAN:
|
||
|
search_result_wrapped: list[str] = 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: str = 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.replace("\n", "\n\n"))
|
||
|
)
|
||
|
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))
|