reformat (black)

This commit is contained in:
2025-04-17 14:35:56 -04:00
parent d12b066c8e
commit 1bb482315e
20 changed files with 1928 additions and 1326 deletions

View File

@ -1,6 +1,7 @@
import sys
from os import path
sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
sys.path.append(path.dirname(path.dirname(path.abspath(__file__))))
import constants
import traceback
import time
@ -14,22 +15,24 @@ from aiohttp import ClientSession, ClientTimeout
from discord.ext import bridge, commands, tasks
from disc_havoc import Havoc
class Util:
"""Karma Utility"""
def __init__(self, bot: Havoc):
self.bot: Havoc = bot
self.api_key: str = constants.PRV_API_KEY
self.karma_endpoints_base_url: str = "https://api.codey.lol/karma/"
self.karma_retrieval_url: str = f"{self.karma_endpoints_base_url}get"
self.karma_update_url: str = f"{self.karma_endpoints_base_url}modify"
self.karma_top_10_url: str = f"{self.karma_endpoints_base_url}top"
self.timers: dict = {} # discord uid : timestamp, used for rate limiting
self.karma_cooldown: int = 15 # 15 seconds between karma updates
self.karma_top_10_url: str = f"{self.karma_endpoints_base_url}top"
self.timers: dict = {} # discord uid : timestamp, used for rate limiting
self.karma_cooldown: int = 15 # 15 seconds between karma updates
async def get_karma(self, keyword: str) -> int:
"""
Get Karma for Keyword
Args:
keyword (str)
Returns:
@ -37,65 +40,75 @@ class Util:
"""
try:
async with ClientSession() as session:
async with await session.post(self.karma_retrieval_url,
json={'keyword': keyword},
headers={
'content-type': 'application/json; charset=utf-8',
'X-Authd-With': f'Bearer {constants.KARMA_API_KEY}',
}, timeout=ClientTimeout(connect=3, sock_read=5)) as request:
async with await session.post(
self.karma_retrieval_url,
json={"keyword": keyword},
headers={
"content-type": "application/json; charset=utf-8",
"X-Authd-With": f"Bearer {constants.KARMA_API_KEY}",
},
timeout=ClientTimeout(connect=3, sock_read=5),
) as request:
resp = await request.json()
return resp.get('count')
return resp.get("count")
except Exception as e:
traceback.print_exc()
return False
async def get_top(self, n: int = 10) -> Optional[dict]:
"""
Get top (n=10) Karma
Args:
n (int): Number of top results to return, default 10
Returns:
Optional[dict]
Optional[dict]
"""
try:
async with ClientSession() as session:
async with await session.post(self.karma_top_10_url,
json = {
'n': n,
},
headers={
'content-type': 'application/json; charset=utf-8',
'X-Authd-With': f'Bearer {constants.KARMA_API_KEY}'
}, timeout=ClientTimeout(connect=3, sock_read=5)) as request:
async with await session.post(
self.karma_top_10_url,
json={
"n": n,
},
headers={
"content-type": "application/json; charset=utf-8",
"X-Authd-With": f"Bearer {constants.KARMA_API_KEY}",
},
timeout=ClientTimeout(connect=3, sock_read=5),
) as request:
resp: dict = await request.json()
return resp
except:
traceback.print_exc()
return None
async def get_top_embed(self, n:int = 10) -> Optional[discord.Embed]:
async def get_top_embed(self, n: int = 10) -> Optional[discord.Embed]:
"""
Get Top Karma Embed
Args:
n (int): Number of top results to return, default 10
Returns:
Optional[discord.Embed]
Optional[discord.Embed]
"""
top: Optional[dict] = await self.get_top(n)
if not top:
return None
top_formatted: str = ""
for x, item in enumerate(top):
top_formatted += f"{x+1}. **{discord.utils.escape_markdown(item[0])}**: *{item[1]}*\n"
top_formatted += (
f"{x+1}. **{discord.utils.escape_markdown(item[0])}**: *{item[1]}*\n"
)
top_formatted = top_formatted.strip()
embed: discord.Embed = discord.Embed(title=f"Top {n} Karma",
description=top_formatted,
colour=0xff00ff)
embed: discord.Embed = discord.Embed(
title=f"Top {n} Karma", description=top_formatted, colour=0xFF00FF
)
return embed
async def update_karma(self, display: str, _id: int, keyword: str, flag: int) -> bool:
async def update_karma(
self, display: str, _id: int, keyword: str, flag: int
) -> bool:
"""
Update Karma for Keyword
Args:
@ -109,33 +122,34 @@ class Util:
"""
if not flag in [0, 1]:
return False
reqObj: dict = {
'granter': f"Discord: {display} ({_id})",
'keyword': keyword,
'flag': flag,
"granter": f"Discord: {display} ({_id})",
"keyword": keyword,
"flag": flag,
}
try:
async with ClientSession() as session:
async with await session.post(self.karma_update_url,
json=reqObj,
headers={
'content-type': 'application/json; charset=utf-8',
'X-Authd-With': f'Bearer {self.api_key}',
},
timeout=ClientTimeout(connect=3, sock_read=5)) as request:
async with await session.post(
self.karma_update_url,
json=reqObj,
headers={
"content-type": "application/json; charset=utf-8",
"X-Authd-With": f"Bearer {self.api_key}",
},
timeout=ClientTimeout(connect=3, sock_read=5),
) as request:
result = await request.json()
return result.get('success', False)
return result.get("success", False)
except:
traceback.print_exc()
return False
async def check_cooldown(self, user_id: int) -> bool:
"""
Check if member has met cooldown period prior to adjusting karma
Args:
user_id (int): The Discord UID to check
Returns:
@ -147,19 +161,21 @@ class Util:
if (now - self.timers[user_id]) < self.karma_cooldown:
return False
return True
class Karma(commands.Cog):
"""Karma Cog for Havoc"""
def __init__(self, bot: Havoc):
importlib.reload(constants)
self.bot: Havoc = bot
self.util = Util(self.bot)
# self.karma_regex = regex.compile(r'(\w+)(\+\+|\-\-)')
self.karma_regex: Pattern = regex.compile(r'(\b\w+(?:\s+\w+)*)(\+\+($|\s)|\-\-($|\s))')
self.mention_regex: Pattern = regex.compile(r'(<@([0-9]{17,20})>)(\+\+|\-\-)')
self.mention_regex_no_flag: Pattern = regex.compile(r'(<@([0-9]{17,20})>+)')
self.karma_regex: Pattern = regex.compile(
r"(\b\w+(?:\s+\w+)*)(\+\+($|\s)|\-\-($|\s))"
)
self.mention_regex: Pattern = regex.compile(r"(<@([0-9]{17,20})>)(\+\+|\-\-)")
self.mention_regex_no_flag: Pattern = regex.compile(r"(<@([0-9]{17,20})>+)")
self.karma_chanid: int = 1307065684785893406
self.karma_msgid: int = 1325442184572567686
@ -170,7 +186,6 @@ class Karma(commands.Cog):
except Exception as e:
pass
@tasks.loop(seconds=30, reconnect=True)
async def update_karma_chan(self) -> None:
"""Update the Karma Chan Leaderboard"""
@ -180,42 +195,48 @@ class Karma(commands.Cog):
if not isinstance(channel, discord.TextChannel):
return
message_to_edit = await channel.fetch_message(self.karma_msgid)
await message_to_edit.edit(embed=top_embed,
content="## This message will automatically update periodically.")
await message_to_edit.edit(
embed=top_embed,
content="## This message will automatically update periodically.",
)
except:
traceback.print_exc()
@commands.Cog.listener()
async def on_message(self, message: discord.Message) -> None:
"""
Message hook, to monitor for ++/--
Also monitors for messages to #karma to autodelete, only Havoc may post in #karma!
"""
if not self.bot.user: # No valid client instance
if not self.bot.user: # No valid client instance
return
if not isinstance(message.channel, discord.TextChannel):
return
if message.channel.id == self.karma_chanid and not message.author.id == self.bot.user.id:
if (
message.channel.id == self.karma_chanid
and not message.author.id == self.bot.user.id
):
"""Message to #karma not by Havoc, delete it"""
await message.delete(reason="Messages to #karma are not allowed")
removal_embed: discord.Embed = discord.Embed(
title="Message Deleted",
description=f"Your message to **#{message.channel.name}** has been automatically deleted.\n**Reason**: Messages to this channel by users is not allowed."
description=f"Your message to **#{message.channel.name}** has been automatically deleted.\n**Reason**: Messages to this channel by users is not allowed.",
)
await message.author.send(embed=removal_embed)
if message.author.id == self.bot.user.id: # Bots own message
if message.author.id == self.bot.user.id: # Bots own message
return
if not message.guild:
return
if not message.guild.id in [1145182936002482196, 1228740575235149855]: # Not a valid guild for cmd
if not message.guild.id in [
1145182936002482196,
1228740575235149855,
]: # Not a valid guild for cmd
return
message_content: str = message.content.strip()
mentions: list = regex.findall(self.mention_regex, message_content)
for mention in mentions:
try:
logging.debug("Mention: %s", mention)
@ -235,26 +256,28 @@ class Karma(commands.Cog):
message_content = discord.utils.escape_markdown(message_content)
karma_regex: list[str] = regex.findall(self.karma_regex, message_content.strip())
karma_regex: list[str] = regex.findall(
self.karma_regex, message_content.strip()
)
if not karma_regex: # Not a request to adjust karma
return
flooding: bool = not await self.util.check_cooldown(message.author.id)
exempt_uids: list[int] = [1172340700663255091, 992437729927376996]
exempt_uids: list[int] = [1172340700663255091, 992437729927376996]
if flooding and not message.author.id in exempt_uids:
return await message.add_reaction(emoji="")
processed_keywords_lc: list[str] = []
logging.debug("Matched: %s", karma_regex)
for matched_keyword in karma_regex:
if not isinstance(matched_keyword, tuple):
continue
if len(matched_keyword) == 4:
(keyword, friendly_flag, _, __) = matched_keyword
else:
(keyword, friendly_flag) = matched_keyword
(keyword, friendly_flag) = matched_keyword
now: int = int(time.time())
flag: int = None
@ -269,13 +292,14 @@ class Karma(commands.Cog):
if keyword.lower() in processed_keywords_lc:
continue
processed_keywords_lc.append(keyword.lower())
self.util.timers[message.author.id] = now
updated: bool = await self.util.update_karma(message.author.display_name,
message.author.id, keyword, flag)
updated: bool = await self.util.update_karma(
message.author.display_name, message.author.id, keyword, flag
)
if updated:
return await message.add_reaction(emoji="👍")
@ -288,9 +312,9 @@ class Karma(commands.Cog):
if not top_10_embed:
return
return await ctx.respond(embed=top_10_embed)
keyword = discord.utils.escape_markdown(keyword)
mentions: list[str] = regex.findall(self.mention_regex_no_flag, keyword)
for mention in mentions:
@ -299,7 +323,9 @@ class Karma(commands.Cog):
guild: Optional[discord.Guild] = self.bot.get_guild(ctx.guild.id)
if not guild:
return
guild_member: Optional[discord.Member] = guild.get_member(mentioned_uid)
guild_member: Optional[discord.Member] = guild.get_member(
mentioned_uid
)
if not guild_member:
return
display = guild_member.display_name
@ -310,21 +336,22 @@ class Karma(commands.Cog):
score: int = await self.util.get_karma(keyword)
description: str = f"**{keyword}** has a karma of *{score}*"
embed: discord.Embed = discord.Embed(title=f"Karma for {keyword}",
description=description)
embed: discord.Embed = discord.Embed(
title=f"Karma for {keyword}", description=description
)
return await ctx.respond(embed=embed)
except Exception as e:
await ctx.respond(f"Error: {str(e)}")
traceback.print_exc()
def cog_unload(self) -> None:
try:
self.update_karma_chan.cancel()
except:
"""Safe to ignore"""
pass
def setup(bot) -> None:
"""Run on Cog Load"""
bot.add_cog(Karma(bot))

View File

@ -5,8 +5,10 @@ from discord.ext import bridge, commands
from util.lovehate_db import DB
from disc_havoc import Havoc
class LoveHate(commands.Cog):
"""LoveHate Cog for Havoc"""
def __init__(self, bot: Havoc) -> None:
self.bot: Havoc = bot
self.db = DB(self.bot)
@ -14,16 +16,15 @@ class LoveHate(commands.Cog):
def join_with_and(self, items: list) -> str:
"""
Join list with and added before last item
Args:
items (list)
Returns:
str
str
"""
if len(items) > 1:
return ', '.join(items[:-1]) + ' and ' + items[-1]
return items[0] if items else ''
return ", ".join(items[:-1]) + " and " + items[-1]
return items[0] if items else ""
@bridge.bridge_command()
async def loves(self, ctx, user: Optional[str] = None) -> None:
@ -33,31 +34,32 @@ class LoveHate(commands.Cog):
try:
if not user:
display_name = ctx.author.display_name
loves: Union[list[tuple], bool] = await self.db.get_lovehates(user=display_name,
loves=True)
loves: Union[list[tuple], bool] = await self.db.get_lovehates(
user=display_name, loves=True
)
if not loves:
return await ctx.respond("You don't seem to love anything...")
out_loves: list = []
if not isinstance(loves, list):
return
for love in loves:
(love,) = love
out_loves.append(love)
out_loves_str: str = self.join_with_and(out_loves)
return await ctx.respond(f"{ctx.author.mention} loves {out_loves_str}")
loves = await self.db.get_lovehates(user=user.strip(), loves=True)
if not loves:
return await ctx.respond(f"{user} doesn't seem to love anything...")
return await ctx.respond(f"{user} doesn't seem to love anything...")
out_loves_str = self.join_with_and(out_loves)
return await ctx.respond(f"{user} loves {out_loves_str}")
except Exception as e:
traceback.print_exc()
return await ctx.respond(f"Error: {str(e)}")
@bridge.bridge_command()
async def wholoves(self, ctx, *, thing: Optional[str] = None) -> None:
"""
@ -70,38 +72,39 @@ class LoveHate(commands.Cog):
_thing = thing
if discord.utils.raw_mentions(_thing):
# There are mentions
thing_id: int = discord.utils.raw_mentions(_thing)[0] # First mention
thing_id: int = discord.utils.raw_mentions(_thing)[0] # First mention
guild: Optional[discord.Guild] = self.bot.get_guild(ctx.guild.id)
if not guild:
return
thing_member: Optional[discord.Member] = guild.get_member(thing_id)
if not thing_member:
return
_thing = thing_member.display_name
if not _thing:
return
who_loves: Union[list, bool] = await self.db.get_wholovehates(thing=_thing,
loves=True)
who_loves: Union[list, bool] = await self.db.get_wholovehates(
thing=_thing, loves=True
)
if not isinstance(who_loves, list):
return await ctx.respond(f"I couldn't find anyone who loves {thing}...")
out_wholoves: list = []
for lover in who_loves:
(lover,) = lover
out_wholoves.append(str(lover))
optional_s: str = "s" if len(out_wholoves) == 1 else ""
out_wholoves_str: str = self.join_with_and(out_wholoves)
return await ctx.respond(f"{out_wholoves_str} love{optional_s} {thing}")
except Exception as e:
traceback.print_exc()
return await ctx.respond(f"Error: {str(e)}")
@bridge.bridge_command()
async def whohates(self, ctx, *, thing: Optional[str] = None) -> None:
"""
@ -117,17 +120,18 @@ class LoveHate(commands.Cog):
guild: Optional[discord.Guild] = self.bot.get_guild(ctx.guild.id)
if not guild:
return
thing_id: int = discord.utils.raw_mentions(_thing)[0] # First mention
thing_id: int = discord.utils.raw_mentions(_thing)[0] # First mention
thing_member: Optional[discord.Member] = guild.get_member(thing_id)
if not thing_member:
return
_thing = thing_member.display_name
_thing = thing_member.display_name
who_hates: Union[list[tuple], bool] = await self.db.get_wholovehates(thing=_thing,
hates=True)
who_hates: Union[list[tuple], bool] = await self.db.get_wholovehates(
thing=_thing, hates=True
)
if not who_hates:
return await ctx.respond(f"I couldn't find anyone who hates {thing}...")
out_whohates: list = []
if not isinstance(who_hates, list):
return
@ -136,7 +140,7 @@ class LoveHate(commands.Cog):
out_whohates.append(str(hater))
optional_s: str = "s" if len(out_whohates) == 1 else ""
out_whohates_str: str = self.join_with_and(out_whohates)
return await ctx.respond(f"{out_whohates_str} hate{optional_s} {thing}")
@ -144,16 +148,14 @@ class LoveHate(commands.Cog):
traceback.print_exc()
return await ctx.respond(f"Error: {str(e)}")
@bridge.bridge_command()
async def dontcare(self, ctx, thing: str) -> None:
"""
Make me forget your opinion on <thing>
"""
try:
stop_caring: str = await self.db.update(ctx.author.display_name,
thing, 0)
return await ctx.respond(stop_caring)
stop_caring: str = await self.db.update(ctx.author.display_name, thing, 0)
return await ctx.respond(stop_caring)
except Exception as e:
await ctx.respond(f"Error: {str(e)}")
traceback.print_exc()
@ -166,29 +168,30 @@ class LoveHate(commands.Cog):
try:
if not user:
display_name = ctx.author.display_name
hates: Union[list[tuple], bool] = await self.db.get_lovehates(user=display_name,
hates=True)
hates: Union[list[tuple], bool] = await self.db.get_lovehates(
user=display_name, hates=True
)
if not hates:
return await ctx.respond("You don't seem to hate anything...")
else:
hates = await self.db.get_lovehates(user=user.strip(), hates=True)
if not hates:
return await ctx.respond(f"{user} doesn't seem to hate anything...")
return await ctx.respond(f"{user} doesn't seem to hate anything...")
out_hates: list = []
if not isinstance(hates, list):
return
for hated_thing in hates:
(hated_thing,) = hated_thing
out_hates.append(str(hated_thing))
out_hates_str: str = self.join_with_and(out_hates)
out_hates_str: str = self.join_with_and(out_hates)
return await ctx.respond(f"{user} hates {out_hates_str}")
except Exception as e:
await ctx.respond(f"Error: {str(e)}")
traceback.print_exc()
@bridge.bridge_command(aliases=['sarcastichate'])
@bridge.bridge_command(aliases=["sarcastichate"])
async def love(self, ctx, *, thing: str) -> None:
"""
Love <thing>
@ -196,7 +199,7 @@ class LoveHate(commands.Cog):
try:
if discord.utils.raw_mentions(thing):
# There are mentions
thing_id: int = discord.utils.raw_mentions(thing)[0] # First mention
thing_id: int = discord.utils.raw_mentions(thing)[0] # First mention
guild: Optional[discord.Guild] = self.bot.get_guild(ctx.guild)
if not guild:
return
@ -205,14 +208,13 @@ class LoveHate(commands.Cog):
return
thing = thing_member.display_name
love: str = await self.db.update(ctx.author.display_name,
thing, 1)
return await ctx.respond(love)
love: str = await self.db.update(ctx.author.display_name, thing, 1)
return await ctx.respond(love)
except Exception as e:
await ctx.respond(f"Error: {str(e)}")
traceback.print_exc()
@bridge.bridge_command(aliases=['sarcasticlove'])
@bridge.bridge_command(aliases=["sarcasticlove"])
async def hate(self, ctx, *, thing: str) -> None:
"""
Hate <thing>
@ -220,7 +222,7 @@ class LoveHate(commands.Cog):
try:
if discord.utils.raw_mentions(thing):
# There are mentions
thing_id: int = discord.utils.raw_mentions(thing)[0] # First mention
thing_id: int = discord.utils.raw_mentions(thing)[0] # First mention
guild: Optional[discord.Guild] = self.bot.get_guild(ctx.guild.id)
if not guild:
return
@ -228,18 +230,17 @@ class LoveHate(commands.Cog):
if not thing_member:
return
thing = thing_member.display_name
hate: str = await self.db.update(ctx.author.display_name,
thing, -1)
return await ctx.respond(hate)
hate: str = await self.db.update(ctx.author.display_name, thing, -1)
return await ctx.respond(hate)
except Exception as e:
await ctx.respond(f"Error: {str(e)}")
traceback.print_exc()
def cog_unload(self) -> None:
# not needed currently
pass
def setup(bot) -> None:
"""Run on Cog Load"""
bot.add_cog(LoveHate(bot))
bot.add_cog(LoveHate(bot))

View File

@ -4,10 +4,7 @@ import json
import io
import asyncio
import random
from typing import (LiteralString,
Optional,
Any,
Union)
from typing import LiteralString, Optional, Any, Union
import aiosqlite as sqlite3
import logging
import textwrap
@ -35,126 +32,152 @@ BOT_CHANIDS = []
TODO: Cleanup new meme leaderboard stuff
"""
class Helper:
"""Meme Helper"""
def load_meme_choices(self) -> None:
"""Load Available Meme Templates from JSON File"""
global meme_choices
memes_file: str|LiteralString = os.path.join(os.path.dirname(__file__), "memes.json")
with open(memes_file, 'r', encoding='utf-8') as f:
memes_file: str | LiteralString = os.path.join(
os.path.dirname(__file__), "memes.json"
)
with open(memes_file, "r", encoding="utf-8") as f:
meme_choices = json.loads(f.read())
class MemeView(discord.ui.View):
"""Meme Selection discord.ui.View"""
helper = Helper()
helper.load_meme_choices()
@discord.ui.select(
placeholder = "Choose a Meme!",
min_values = 1,
max_values = 1,
options = [
discord.SelectOption(
label=meme_label
) for meme_label in meme_choices[0:24]
]
placeholder="Choose a Meme!",
min_values=1,
max_values=1,
options=[
discord.SelectOption(label=meme_label) for meme_label in meme_choices[0:24]
],
)
async def select_callback(self, select: discord.ui.Select,
interaction: discord.Interaction) -> None:
async def select_callback(
self, select: discord.ui.Select, interaction: discord.Interaction
) -> None:
"""Meme Selection Callback"""
if not isinstance(select.values[0], str):
return
modal: discord.ui.Modal = MemeModal(meme=select.values[0], title="Meme Selected")
modal: discord.ui.Modal = MemeModal(
meme=select.values[0], title="Meme Selected"
)
await interaction.response.send_modal(modal)
class MemeModal(discord.ui.Modal):
"""Meme Creation discord.ui.Modal"""
def __init__(self, *args, meme: Optional[str] = None, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.selected_meme: Optional[str] = meme
self.meme_generator = JesusMemeGenerator()
self.TEXT_LIMIT: int = 80
self.add_item(discord.ui.InputText(label="Top Text",
style=discord.InputTextStyle.singleline))
self.add_item(discord.ui.InputText(label="Bottom Text",
style=discord.InputTextStyle.singleline))
self.add_item(
discord.ui.InputText(
label="Top Text", style=discord.InputTextStyle.singleline
)
)
self.add_item(
discord.ui.InputText(
label="Bottom Text", style=discord.InputTextStyle.singleline
)
)
async def callback(self, interaction: discord.Interaction) -> None:
if not self.selected_meme: # No meme selected
return
selected_meme: str = self.selected_meme
if not self.children or len(self.children) < 2: # Invalid request
if not self.selected_meme: # No meme selected
return
selected_meme: str = self.selected_meme
if not self.children or len(self.children) < 2: # Invalid request
return
if not isinstance(self.children[0].value, str) or not isinstance(
self.children[1].value, str
): # Invalid request
return
if not isinstance(self.children[0].value, str)\
or not isinstance(self.children[1].value, str): # Invalid request
return
meme_top_line: str = self.children[0].value.strip()
meme_bottom_line: str = self.children[1].value.strip()
if len(meme_top_line) > self.TEXT_LIMIT or len(meme_bottom_line) > self.TEXT_LIMIT:
await interaction.response.send_message("ERR: Text is limited to 80 characters for each the top and bottom lines.")
if (
len(meme_top_line) > self.TEXT_LIMIT
or len(meme_bottom_line) > self.TEXT_LIMIT
):
await interaction.response.send_message(
"ERR: Text is limited to 80 characters for each the top and bottom lines."
)
return
meme_link: Optional[str] = await self.meme_generator.create_meme(top_line=meme_top_line,
bottom_line=meme_bottom_line, meme=selected_meme)
meme_link: Optional[str] = await self.meme_generator.create_meme(
top_line=meme_top_line, bottom_line=meme_bottom_line, meme=selected_meme
)
if not meme_link:
await interaction.response.send_message("Failed!")
return
return
embed: discord.Embed = discord.Embed(title="Generated Meme")
embed.set_image(url=meme_link)
embed.add_field(name="Meme", value=selected_meme, inline=True)
await interaction.response.send_message(embeds=[embed])
return
class Meme(commands.Cog):
"""Meme Cog for Havoc"""
def __init__(self, bot: Havoc) -> None:
self.bot: Havoc = bot
self.stats_db_path: LiteralString = os.path.join("/usr/local/share",
"sqlite_dbs", "stats.db")
self.stats_db_path: LiteralString = os.path.join(
"/usr/local/share", "sqlite_dbs", "stats.db"
)
self.meme_choices: list = []
self.meme_counter: int = 0
self.THREADS: dict[str, dict[int, list]] = {
# Format: Guild1: [ChanId : [Webhook, ThreadId], Guild2: [ChanId : [Webhook, ThreadId]
'comic_explosm': {
"comic_explosm": {
1298729744216359055: [constants.EXPLOSM_WEBHOOK, 1299165855493390367],
1306414795049926676: [constants.EXPLOSM_WEBHOOK2, 1306416492304138364],
},
'comic_xkcd': {
1298729744216359055: [constants.XKCD_WEBHOOK, 1299165928755433483],
"comic_xkcd": {
1298729744216359055: [constants.XKCD_WEBHOOK, 1299165928755433483],
1306414795049926676: [constants.XKCD_WEBHOOK2, 1306416681991798854],
},
'comic_smbc': {
"comic_smbc": {
1298729744216359055: [constants.SMBC_WEBHOOK, 1299166071038808104],
1306414795049926676: [constants.SMBC_WEBHOOK2, 1306416842511745024],
},
'comic_qc': {
1298729744216359055: [constants.QC_WEBHOOK, 1299392115364593674],
"comic_qc": {
1298729744216359055: [constants.QC_WEBHOOK, 1299392115364593674],
1306414795049926676: [constants.QC_WEBHOOK2, 1306417084774744114],
},
'comic_dino': {
1298729744216359055: [constants.DINO_WEBHOOK, 1299771918886506557],
"comic_dino": {
1298729744216359055: [constants.DINO_WEBHOOK, 1299771918886506557],
1306414795049926676: [constants.DINO_WEBHOOK2, 1306417286713704548],
}
},
}
self.NO_THREAD_WEBHOOKS: dict[str, list] = {
'theonion': [constants.ONION_WEBHOOK, constants.ONION_WEBHOOK2],
'thn': [constants.THN_WEBHOOK],
'memes': [constants.MEME_WEBHOOK1, constants.MEME_WEBHOOK2],
"theonion": [constants.ONION_WEBHOOK, constants.ONION_WEBHOOK2],
"thn": [constants.THN_WEBHOOK],
"memes": [constants.MEME_WEBHOOK1, constants.MEME_WEBHOOK2],
}
global BOT_CHANIDS
BOT_CHANIDS = self.bot.BOT_CHANIDS # Inherit
BOT_CHANIDS = self.bot.BOT_CHANIDS # Inherit
self.meme_stream_loop.start()
self.explosm_loop.start()
self.update_meme_lb.start()
asyncio.get_event_loop().create_task(self.init_meme_leaderboard())
def is_spamchan() -> bool: # type: ignore
def is_spamchan() -> bool: # type: ignore
"""Check if channel is spamchan"""
def predicate(ctx):
try:
if not ctx.channel.id in BOT_CHANIDS:
@ -163,24 +186,23 @@ class Meme(commands.Cog):
except:
traceback.print_exc()
return False
return commands.check(predicate) # type: ignore
async def leaderboard_increment(self,
uid: int) -> None:
return commands.check(predicate) # type: ignore
async def leaderboard_increment(self, uid: int) -> None:
"""
Increment leaderboard for uid
Args:
uid (int):
uid (int):
Returns:
None
"""
if not uid in self.meme_leaderboard:
self.meme_leaderboard[uid] = 1
else:
self.meme_leaderboard[uid] += 1
async with sqlite3.connect(self.stats_db_path, timeout=2) as db_conn:
"""Attempts both insert/update"""
query_1: str = "UPDATE memes SET count = count + 1 WHERE discord_uid = ?"
@ -199,34 +221,34 @@ class Meme(commands.Cog):
try:
await self.update_meme_lb()
except Exception as e:
logging.info("Failed to update meme leaderboard following increment: %s",
str(e))
logging.info(
"Failed to update meme leaderboard following increment: %s", str(e)
)
async def init_meme_leaderboard(self) -> None:
"""
INIT MEME LEADERBOARD
"""
self.meme_leaderboard: dict [int, int] = {}
self.meme_leaderboard: dict[int, int] = {}
async with sqlite3.connect(self.stats_db_path, timeout=2) as db_conn:
db_conn.row_factory = sqlite3.Row
db_query: str = "SELECT discord_uid, count FROM memes WHERE count > 0"
async with db_conn.execute(db_query) as db_cursor:
results = await db_cursor.fetchall()
for result in results:
uid = result['discord_uid']
count = result['count']
self.meme_leaderboard[uid] = count
uid = result["discord_uid"]
count = result["count"]
self.meme_leaderboard[uid] = count
@commands.Cog.listener()
async def on_ready(self) -> None:
"""Run on Bot Ready"""
await self.init_meme_leaderboard()
async def do_autos(self, only_comics: Optional[bool] = False) -> None:
"""
Run Auto Posters
Args:
only_comics (Optional[bool]): default False
Returns:
@ -243,7 +265,7 @@ class Meme(commands.Cog):
thn_grabber = thng.THNGrabber()
explosm_comics: list[Optional[tuple]] = []
xkcd_comics: list[Optional[tuple]] = []
smbc_comics: list[Optional[tuple]] = []
smbc_comics: list[Optional[tuple]] = []
dino_comics: list[Optional[tuple]] = []
onions: list[Optional[tuple]] = []
thns: list[Optional[tuple]] = []
@ -284,28 +306,34 @@ class Meme(commands.Cog):
except:
traceback.print_exc()
agents: list[str] = constants.HTTP_UA_LIST
headers: dict = {
'User-Agent': random.choice(agents)
}
headers: dict = {"User-Agent": random.choice(agents)}
if not only_comics:
try:
for meme in memes:
if not meme:
continue
(meme_id, meme_title, meme_url) = meme
request = requests.get(meme_url, stream=True, timeout=(5, 30), headers=headers)
request = requests.get(
meme_url, stream=True, timeout=(5, 30), headers=headers
)
if not request.status_code == 200:
continue
meme_content: bytes = request.raw.read()
for meme_hook in self.NO_THREAD_WEBHOOKS.get('memes', {}):
for meme_hook in self.NO_THREAD_WEBHOOKS.get("memes", {}):
meme_image: io.BytesIO = io.BytesIO(meme_content)
ext: str = meme_url.split(".")[-1]\
.split("?")[0].split("&")[0]
ext: str = (
meme_url.split(".")[-1].split("?")[0].split("&")[0]
)
async with ClientSession() as session:
webhook: discord.Webhook = discord.Webhook.from_url(meme_hook,
session=session)
await webhook.send(file=discord.File(meme_image,
filename=f'img.{ext}'), username="r/memes")
webhook: discord.Webhook = discord.Webhook.from_url(
meme_hook, session=session
)
await webhook.send(
file=discord.File(
meme_image, filename=f"img.{ext}"
),
username="r/memes",
)
await asyncio.sleep(2)
except:
pass
@ -315,25 +343,33 @@ class Meme(commands.Cog):
continue
(comic_title, comic_url) = comic
comic_title = discord.utils.escape_markdown(comic_title)
comic_request = requests.get(comic_url, stream=True, timeout=(5, 20), headers=headers)
comic_request = requests.get(
comic_url, stream=True, timeout=(5, 20), headers=headers
)
comic_request.raise_for_status()
comic_content: bytes = comic_request.raw.read()
ext = comic_url.split(".")[-1]\
.split("?")[0].split("&")[0]
ext = comic_url.split(".")[-1].split("?")[0].split("&")[0]
async with ClientSession() as session:
for chanid, _hook in self.THREADS.get('comic_explosm', {}).items():
for chanid, _hook in self.THREADS.get(
"comic_explosm", {}
).items():
comic_image: io.BytesIO = io.BytesIO(comic_content)
channel: int = chanid
(hook_uri, thread_id) = _hook
webhook = discord.Webhook.from_url(hook_uri,
session=session)
webhook = discord.Webhook.from_url(
hook_uri, session=session
)
_channel: Any = self.bot.get_channel(channel)
if not _channel:
return
thread = _channel.get_thread(thread_id)
await webhook.send(f"**{comic_title}**", file=discord.File(comic_image, filename=f'img.{ext}'),
username="Cyanide & Happiness", thread=thread)
await webhook.send(
f"**{comic_title}**",
file=discord.File(comic_image, filename=f"img.{ext}"),
username="Cyanide & Happiness",
thread=thread,
)
await asyncio.sleep(2)
except:
pass
@ -342,119 +378,140 @@ class Meme(commands.Cog):
if not comic:
continue
(comic_title, comic_url) = comic
comic_title = discord.utils.escape_markdown(comic_title)
comic_request = requests.get(comic_url, stream=True, timeout=(5, 20), headers=headers)
comic_title = discord.utils.escape_markdown(comic_title)
comic_request = requests.get(
comic_url, stream=True, timeout=(5, 20), headers=headers
)
comic_request.raise_for_status()
comic_content = comic_request.raw.read()
comic_image = io.BytesIO(comic_request.raw.read())
ext = comic_url.split(".")[-1]\
.split("?")[0].split("&")[0]
ext = comic_url.split(".")[-1].split("?")[0].split("&")[0]
async with ClientSession() as session:
for chanid, _hook in self.THREADS.get('comic_xkcd', {}).items():
for chanid, _hook in self.THREADS.get("comic_xkcd", {}).items():
comic_image = io.BytesIO(comic_content)
channel = chanid
(hook_uri, thread_id) = _hook
webhook = discord.Webhook.from_url(hook_uri,
session=session)
webhook = discord.Webhook.from_url(
hook_uri, session=session
)
_channel = self.bot.get_channel(channel)
if not _channel:
return
thread = _channel.get_thread(thread_id)
await webhook.send(f"**{comic_title}**", file=discord.File(comic_image, filename=f'img.{ext}'),
username="xkcd", thread=thread)
await webhook.send(
f"**{comic_title}**",
file=discord.File(comic_image, filename=f"img.{ext}"),
username="xkcd",
thread=thread,
)
await asyncio.sleep(2)
except:
pass
pass
try:
for comic in smbc_comics:
if not comic:
continue
(comic_title, comic_url) = comic
comic_title = discord.utils.escape_markdown(comic_title)
comic_request = requests.get(comic_url, stream=True, timeout=(5, 20), headers=headers)
comic_title = discord.utils.escape_markdown(comic_title)
comic_request = requests.get(
comic_url, stream=True, timeout=(5, 20), headers=headers
)
comic_request.raise_for_status()
comic_content = comic_request.raw.read()
ext = comic_url.split(".")[-1]\
.split("?")[0].split("&")[0]
ext = comic_url.split(".")[-1].split("?")[0].split("&")[0]
async with ClientSession() as session:
for chanid, _hook in self.THREADS.get('comic_smbc', {}).items():
for chanid, _hook in self.THREADS.get("comic_smbc", {}).items():
comic_image = io.BytesIO(comic_content)
channel = chanid
(hook_uri, thread_id) = _hook
webhook = discord.Webhook.from_url(hook_uri,
session=session)
webhook = discord.Webhook.from_url(
hook_uri, session=session
)
_channel = self.bot.get_channel(channel)
if not _channel:
return
thread = _channel.get_thread(thread_id)
await webhook.send(f"**{comic_title}**", file=discord.File(comic_image, filename=f'img.{ext}'),
username="SMBC", thread=thread)
await webhook.send(
f"**{comic_title}**",
file=discord.File(comic_image, filename=f"img.{ext}"),
username="SMBC",
thread=thread,
)
await asyncio.sleep(2)
except:
pass
pass
try:
for comic in qc_comics:
logging.debug("Trying QC...")
if not comic:
continue
(comic_title, comic_url) = comic
comic_title = discord.utils.escape_markdown(comic_title)
comic_url = regex.sub(r'^http://ww\.', 'http://www.',
comic_url)
comic_url = regex.sub(r'\.pmg$', '.png',
comic_url)
comic_request = requests.get(comic_url, stream=True,
timeout=(5, 20), headers=headers)
comic_title = discord.utils.escape_markdown(comic_title)
comic_url = regex.sub(r"^http://ww\.", "http://www.", comic_url)
comic_url = regex.sub(r"\.pmg$", ".png", comic_url)
comic_request = requests.get(
comic_url, stream=True, timeout=(5, 20), headers=headers
)
comic_request.raise_for_status()
comic_content = comic_request.raw.read()
ext = comic_url.split(".")[-1]\
.split("?")[0].split("&")[0]
ext = comic_url.split(".")[-1].split("?")[0].split("&")[0]
async with ClientSession() as session:
for chanid, _hook in self.THREADS.get('comic_qc', {}).items():
for chanid, _hook in self.THREADS.get("comic_qc", {}).items():
comic_image = io.BytesIO(comic_content)
channel = chanid
(hook_uri, thread_id) = _hook
webhook = discord.Webhook.from_url(hook_uri,
session=session)
webhook = discord.Webhook.from_url(
hook_uri, session=session
)
_channel = self.bot.get_channel(channel)
if not _channel:
return
thread = _channel.get_thread(thread_id)
await webhook.send(f"**{comic_title}**", file=discord.File(comic_image, filename=f'img.{ext}'),
username="Questionable Content", thread=thread)
await webhook.send(
f"**{comic_title}**",
file=discord.File(comic_image, filename=f"img.{ext}"),
username="Questionable Content",
thread=thread,
)
await asyncio.sleep(2)
except:
traceback.print_exc()
pass
pass
try:
for comic in dino_comics:
if not comic:
continue
(comic_title, comic_url) = comic
comic_title = discord.utils.escape_markdown(comic_title)
comic_request = requests.get(comic_url, stream=True, timeout=(5, 20), headers=headers)
comic_title = discord.utils.escape_markdown(comic_title)
comic_request = requests.get(
comic_url, stream=True, timeout=(5, 20), headers=headers
)
comic_request.raise_for_status()
comic_content = comic_request.raw.read()
ext = comic_url.split(".")[-1]\
.split("?")[0].split("&")[0]
ext = comic_url.split(".")[-1].split("?")[0].split("&")[0]
async with ClientSession() as session:
for chanid, _hook in self.THREADS.get('comic_dino', {}).items():
for chanid, _hook in self.THREADS.get("comic_dino", {}).items():
comic_image = io.BytesIO(comic_content)
channel = chanid
(hook_uri, thread_id) = _hook
webhook = discord.Webhook.from_url(hook_uri,
session=session)
(hook_uri, thread_id) = _hook
webhook = discord.Webhook.from_url(
hook_uri, session=session
)
_channel = self.bot.get_channel(channel)
if not _channel:
return
thread = _channel.get_thread(thread_id)
await webhook.send(f"**{comic_title}**", file=discord.File(comic_image, filename=f'img.{ext}'),
username="Dinosaur Comics", thread=thread)
await asyncio.sleep(2)
await webhook.send(
f"**{comic_title}**",
file=discord.File(comic_image, filename=f"img.{ext}"),
username="Dinosaur Comics",
thread=thread,
)
await asyncio.sleep(2)
except:
pass
try:
@ -462,15 +519,20 @@ class Meme(commands.Cog):
if not onion:
continue
(onion_title, onion_description, onion_link, onion_video) = onion
onion_description = textwrap.wrap(text=onion_description,
width=860, max_lines=1)[0]
onion_description = textwrap.wrap(
text=onion_description, width=860, max_lines=1
)[0]
embed: discord.Embed = discord.Embed(title=onion_title)
embed.add_field(name="Content", value=f"{onion_description[0:960]}\n-# {onion_link}")
embed.add_field(
name="Content",
value=f"{onion_description[0:960]}\n-# {onion_link}",
)
async with ClientSession() as session:
for hook in self.NO_THREAD_WEBHOOKS.get('theonion', {}):
for hook in self.NO_THREAD_WEBHOOKS.get("theonion", {}):
hook_uri = hook
webhook = discord.Webhook.from_url(hook_uri,
session=session)
webhook = discord.Webhook.from_url(
hook_uri, session=session
)
await webhook.send(embed=embed, username="The Onion")
if onion_video:
await webhook.send(f"^ video: {onion_video}")
@ -483,16 +545,20 @@ class Meme(commands.Cog):
if not thn:
continue
(thn_title, thn_description, thn_link, thn_pubdate, thn_video) = thn
thn_description = textwrap.wrap(text=thn_description,
width=860, max_lines=1)[0]
thn_description = textwrap.wrap(
text=thn_description, width=860, max_lines=1
)[0]
embed = discord.Embed(title=thn_title)
embed.add_field(name="Content", value=f"{thn_description[0:960]}\n-# {thn_link}")
embed.add_field(
name="Content", value=f"{thn_description[0:960]}\n-# {thn_link}"
)
embed.add_field(name="Published", value=thn_pubdate, inline=False)
async with ClientSession() as session:
for hook in self.NO_THREAD_WEBHOOKS.get('thn', {}):
for hook in self.NO_THREAD_WEBHOOKS.get("thn", {}):
hook_uri = hook
webhook = discord.Webhook.from_url(hook_uri,
session=session)
webhook = discord.Webhook.from_url(
hook_uri, session=session
)
await webhook.send(embed=embed, username="The Hacker News")
if thn_video:
await webhook.send(f"^ video: {thn_video}")
@ -507,30 +573,30 @@ class Meme(commands.Cog):
async def meme_stream_loop(self) -> None:
"""Meme Stream Loop (r/memes)"""
try:
await asyncio.sleep(10) # Try to ensure we are ready first
await asyncio.sleep(10) # Try to ensure we are ready first
self.meme_counter += 1
if self.meme_counter == 1:
return await self.do_autos(only_comics=True) # Skip first iteration!
return await self.do_autos(only_comics=True) # Skip first iteration!
await self.do_autos()
except:
traceback.print_exc()
traceback.print_exc()
@tasks.loop(hours=0.5)
async def explosm_loop(self) -> None:
"""Comic Loop"""
try:
await asyncio.sleep(10) # Try to ensure we are ready first
await asyncio.sleep(10) # Try to ensure we are ready first
await self.do_autos(only_comics=True)
except:
traceback.print_exc()
@bridge.bridge_command() # type: ignore
@is_spamchan()
async def meme(self, ctx) -> None:
traceback.print_exc()
@bridge.bridge_command() # type: ignore
@is_spamchan()
async def meme(self, ctx) -> None:
"""Create Meme"""
await ctx.respond(view=MemeView())
await ctx.respond(view=MemeView())
@bridge.bridge_command(hidden=True)
@commands.is_owner()
async def domemestream(self, ctx) -> None:
@ -552,7 +618,7 @@ class Meme(commands.Cog):
except:
await ctx.respond("Fuck! :(", ephemeral=True)
traceback.print_exc()
@commands.Cog.listener()
async def on_message(self, message: discord.Message) -> None:
"""
@ -560,50 +626,56 @@ class Meme(commands.Cog):
Also monitors for messages to #memes-top-10 to autodelete, only Havoc may post in #memes-top-10!
"""
lb_chanid: int = 1352373745108652145
if not self.bot.user: # No valid client instance
if not self.bot.user: # No valid client instance
return
if not isinstance(message.channel, discord.TextChannel):
return
if message.channel.id == lb_chanid\
and not message.author.id == self.bot.user.id:
if (
message.channel.id == lb_chanid
and not message.author.id == self.bot.user.id
):
"""Message to #memes-top-10 not by Havoc, delete it"""
await message.delete(reason=f"Messages to #{message.channel.name} are not allowed")
await message.delete(
reason=f"Messages to #{message.channel.name} are not allowed"
)
removal_embed: discord.Embed = discord.Embed(
title="Message Deleted",
description=f"Your message to **#{message.channel.name}** has been automatically deleted.\n**Reason**: Messages to this channel by users is not allowed."
description=f"Your message to **#{message.channel.name}** has been automatically deleted.\n**Reason**: Messages to this channel by users is not allowed.",
)
await message.author.send(embed=removal_embed)
if message.author.id == self.bot.user.id: # Bots own message
if message.author.id == self.bot.user.id: # Bots own message
return
if not message.guild:
return
if not message.channel.id == 1147229098544988261: # Not meme channel
if not message.channel.id == 1147229098544988261: # Not meme channel
return
if not message.attachments: # No attachments to consider a meme
return
if not message.attachments: # No attachments to consider a meme
return
await self.leaderboard_increment(message.author.id)
async def get_top(self, n: int = 10) -> Optional[list[tuple]]:
"""
Get top (n=10) Memes
Args:
n (int): Number of top results to return, default 10
Returns:
Optional[dict]
Optional[dict]
"""
try:
out_top: list[tuple[int, int]] = []
async with sqlite3.connect(self.stats_db_path, timeout=2) as db_conn:
db_conn.row_factory = sqlite3.Row
query: str = "SELECT discord_uid, count FROM memes WHERE count > 0 ORDER BY count DESC"
query: str = (
"SELECT discord_uid, count FROM memes WHERE count > 0 ORDER BY count DESC"
)
async with db_conn.execute(query) as db_cursor:
db_result = await db_cursor.fetchall()
for res in db_result:
uid = res['discord_uid']
count = res['count']
uid = res["discord_uid"]
count = res["count"]
out_top.append((uid, count))
# Check for and remove missing members
guild_id: int = 1145182936002482196
@ -615,19 +687,19 @@ class Meme(commands.Cog):
member: Optional[discord.Member] = guild.get_member(uid)
if not member:
out_top.pop(x)
return out_top[0:(n+1)]
return out_top[0 : (n + 1)]
except:
traceback.print_exc()
return None
async def get_top_embed(self, n:int = 10) -> Optional[discord.Embed]:
async def get_top_embed(self, n: int = 10) -> Optional[discord.Embed]:
"""
Get Top Memes Embed
Args:
n (int): Number of top results to return, default 10
Returns:
Optional[discord.Embed]
Optional[discord.Embed]
"""
guild_id: int = 1145182936002482196
guild: Optional[discord.Guild] = self.bot.get_guild(guild_id)
@ -643,13 +715,15 @@ class Meme(commands.Cog):
if not member:
continue
display_name: str = member.display_name
top_formatted += f"{x+1}. **{discord.utils.escape_markdown(display_name)}**: *{count}*\n"
top_formatted += (
f"{x+1}. **{discord.utils.escape_markdown(display_name)}**: *{count}*\n"
)
top_formatted = top_formatted.strip()
embed: discord.Embed = discord.Embed(title=f"Top {n} Memes",
description=top_formatted,
colour=0x25bd6b)
return embed
embed: discord.Embed = discord.Embed(
title=f"Top {n} Memes", description=top_formatted, colour=0x25BD6B
)
return embed
@tasks.loop(seconds=30, reconnect=True)
async def update_meme_lb(self) -> None:
"""Update the Meme Leaderboard"""
@ -661,27 +735,33 @@ class Meme(commands.Cog):
if not isinstance(channel, discord.TextChannel):
return
message_to_edit = await channel.fetch_message(message_id)
await message_to_edit.edit(embed=top_embed,
content="## This message will automatically update periodically.")
await message_to_edit.edit(
embed=top_embed,
content="## This message will automatically update periodically.",
)
except:
traceback.print_exc()
traceback.print_exc()
@bridge.bridge_command(hidden=True)
@commands.is_owner()
async def doembed(self, ctx) -> None:
"""Do Meme Embed"""
meme_lb_chan_id: int = 1352373745108652145
meme_lb_chan: Union[discord.TextChannel, Any] = self.bot.get_channel(meme_lb_chan_id)
meme_lb_chan: Union[discord.TextChannel, Any] = self.bot.get_channel(
meme_lb_chan_id
)
embed = await self.get_top_embed()
if embed:
await meme_lb_chan.send(embed=embed)
else:
await ctx.respond("NO embed :(")
def cog_unload(self) -> None:
self.meme_stream_loop.cancel()
self.explosm_loop.cancel()
self.update_meme_lb.cancel()
def setup(bot) -> None:
"""Run on Cog Load"""
bot.add_cog(Meme(bot))
bot.add_cog(Meme(bot))

File diff suppressed because it is too large Load Diff

View File

@ -9,110 +9,114 @@ from discord.ext import bridge, commands
from disc_havoc import Havoc
import util
class Owner(commands.Cog):
"""Owner Cog for Havoc"""
def __init__(self, bot: Havoc) -> None:
self.bot: Havoc = bot
self.former_roles_store: dict[int, list[discord.Role]] = {}
self._temperature: int = random.randrange(20, 30)
@bridge.bridge_command(guild_ids=[1145182936002482196])
async def temperature(self, ctx, temp: Optional[int|str] = None) -> None:
async def temperature(self, ctx, temp: Optional[int | str] = None) -> None:
"""
Set Temperature
"""
if not temp:
return await ctx.respond(f"The current temperature is: {self._temperature} °C")
return await ctx.respond(
f"The current temperature is: {self._temperature} °C"
)
if not self.bot.is_owner(ctx.author):
return await ctx.respond("I am afraid I can't let you do that.")
try:
_temperature: int = int(temp)
except:
return await ctx.respond("Invalid input")
return await ctx.respond("Invalid input")
if _temperature < -15:
return await ctx.respond("Too cold! (-15°C minimum)")
elif _temperature > 35:
return await ctx.respond("Too hot! (35°C maximum)")
self._temperature = _temperature
return await ctx.respond(f"As per your request, I have adjusted the temperature to {_temperature} °C.")
return await ctx.respond(
f"As per your request, I have adjusted the temperature to {_temperature} °C."
)
@bridge.bridge_command()
@commands.is_owner()
async def editmsg(self, ctx,
msgid: str,
*,
newcontent: str
) -> None:
async def editmsg(self, ctx, msgid: str, *, newcontent: str) -> None:
"""
Edit a message previously sent by the bot
"""
try:
message: Optional[discord.Message] = self.bot.get_message(int(msgid))
if not message:
await ctx.respond(f"**Failed:** Message {msgid} not found.",
ephemeral=True)
await ctx.respond(
f"**Failed:** Message {msgid} not found.", ephemeral=True
)
return None
await message.edit(content=newcontent)
await ctx.respond("**Done!**", ephemeral=True)
except Exception as e:
await ctx.respond(f"**Failed:** {str(e)}",
ephemeral=True)
await ctx.respond(f"**Failed:** {str(e)}", ephemeral=True)
@bridge.bridge_command()
@commands.is_owner()
async def reload(self, ctx) -> None:
"""
Reload Cogs
"""
self.bot.load_exts(False)
await ctx.respond("Reloaded!", ephemeral=True)
@bridge.bridge_command()
@commands.is_owner()
async def say(self, ctx, *,
parameters: str) -> None:
async def say(self, ctx, *, parameters: str) -> None:
"""
Make me say something in a channel
"""
_parameters: list[str] = parameters.split(" ")
if not len(_parameters) > 1:
return await ctx.respond("**Error**: Incorrect command usage; required: <chan> <msg>", ephemeral=True)
return await ctx.respond(
"**Error**: Incorrect command usage; required: <chan> <msg>",
ephemeral=True,
)
channel: str = _parameters[0]
channel_mentions: list[int] = discord.utils.raw_channel_mentions(channel)
if channel_mentions:
channel = str(channel_mentions[0])
msg: str = " ".join(_parameters[1:])
await util.discord_helpers.send_message(self.bot, channel=channel,
message=msg)
await util.discord_helpers.send_message(self.bot, channel=channel, message=msg)
return await ctx.respond("**Done.**", ephemeral=True)
@bridge.bridge_command()
@commands.is_owner()
async def chgstatus(self, ctx, *,
status: Optional[str] = None) -> None:
async def chgstatus(self, ctx, *, status: Optional[str] = None) -> None:
"""
Change bots status
"""
if not status:
return await ctx.respond("ERR: No status provided to change to!",
ephemeral=True)
await self.bot.change_presence(status=discord.Status.online,
activity=discord.CustomActivity(name=status.strip()))
return await ctx.respond(
"ERR: No status provided to change to!", ephemeral=True
)
await self.bot.change_presence(
status=discord.Status.online,
activity=discord.CustomActivity(name=status.strip()),
)
await ctx.respond("Done!", ephemeral=True)
@commands.message_command(name="Remove Messages Starting Here")
@commands.is_owner()
async def purge(self, ctx, message: discord.Message) -> None:
"""
Purge Messages
Args:
ctx (Any): Discord context
message (discord.Message): Discord message
@ -120,10 +124,12 @@ class Owner(commands.Cog):
None
"""
try:
await ctx.channel.purge(after=message,
bulk=True,
limit=900000,
reason=f"Purge initiated by {ctx.author.display_name}")
await ctx.channel.purge(
after=message,
bulk=True,
limit=900000,
reason=f"Purge initiated by {ctx.author.display_name}",
)
await message.delete(reason=f"Purge initiated by {ctx.author.display_name}")
await ctx.respond("**Done!**")
# Wait 3 seconds, then delete interaction
@ -133,13 +139,13 @@ class Owner(commands.Cog):
except Exception as e:
traceback.print_exc()
return await ctx.respond(f"**ERR: {str(e)}**")
@commands.message_command(name="Move to Memes")
@commands.is_owner()
async def movememe(self, ctx, message: discord.Message) -> None:
"""
Move to Memes
Args:
ctx (Any): Discord context
message (discord.Message): Discord message
@ -149,34 +155,39 @@ class Owner(commands.Cog):
try:
if not isinstance(message.channel, discord.TextChannel):
return
memes_channel: discord.TextChannel = ctx.guild.get_channel(1147229098544988261)
message_content: str = message.content
memes_channel: discord.TextChannel = ctx.guild.get_channel(
1147229098544988261
)
message_content: str = message.content
message_author: str = message.author.display_name
message_channel: str = message.channel.name
_file: Optional[discord.File] = None
if message.attachments:
for item in message.attachments:
if item.url and len(item.url) >= 20:
image: io.BytesIO = io.BytesIO(requests.get(item.url, stream=True,
timeout=20).raw.read())
ext: str = item.url.split(".")[-1]\
.split("?")[0].split("&")[0]
_file = discord.File(image, filename=f'img.{ext}')
image: io.BytesIO = io.BytesIO(
requests.get(item.url, stream=True, timeout=20).raw.read()
)
ext: str = item.url.split(".")[-1].split("?")[0].split("&")[0]
_file = discord.File(image, filename=f"img.{ext}")
if not _file:
return # No file to move
await memes_channel.send(f"*Performing bureaucratic duties (this didn't belong in #{message_channel})...*\n**{message_author}:** {message_content}", file=_file)
return # No file to move
await memes_channel.send(
f"*Performing bureaucratic duties (this didn't belong in #{message_channel})...*\n**{message_author}:** {message_content}",
file=_file,
)
await message.delete()
await ctx.respond("OK!", ephemeral=True)
except:
traceback.print_exc()
return await ctx.respond("Failed! :(", ephemeral=True)
@commands.message_command(name="Move to Drugs")
@commands.is_owner()
async def movedrugs(self, ctx, message: discord.Message) -> None:
"""
Move to Drugs
Args:
ctx (Any): Discord context
message (discord.Message): Discord message
@ -186,7 +197,9 @@ class Owner(commands.Cog):
try:
if not isinstance(message.channel, discord.TextChannel):
return
drugs_channel: discord.TextChannel = ctx.guild.get_channel(1172247451047034910)
drugs_channel: discord.TextChannel = ctx.guild.get_channel(
1172247451047034910
)
message_content: str = message.content
message_author: str = message.author.display_name
message_channel: str = message.channel.name
@ -194,27 +207,30 @@ class Owner(commands.Cog):
if message.attachments:
for item in message.attachments:
if item.url and len(item.url) >= 20:
image: io.BytesIO = io.BytesIO(requests.get(item.url, stream=True,
timeout=20).raw.read())
ext: str = item.url.split(".")[-1]\
.split("?")[0].split("&")[0]
_file = discord.File(image, filename=f'img.{ext}')
image: io.BytesIO = io.BytesIO(
requests.get(item.url, stream=True, timeout=20).raw.read()
)
ext: str = item.url.split(".")[-1].split("?")[0].split("&")[0]
_file = discord.File(image, filename=f"img.{ext}")
if not _file:
return # No file to move
await drugs_channel.send(f"*Performing bureaucratic duties (this didn't belong in #{message_channel})...\
*\n**{message_author}:** {message_content}", file=_file)
return # No file to move
await drugs_channel.send(
f"*Performing bureaucratic duties (this didn't belong in #{message_channel})...\
*\n**{message_author}:** {message_content}",
file=_file,
)
await message.delete()
await ctx.respond("OK!", ephemeral=True)
except:
traceback.print_exc()
return await ctx.respond("Failed! :(", ephemeral=True)
@commands.message_command(name="Move to fun-house")
@commands.is_owner()
async def movefunhouse(self, ctx, message: discord.Message) -> None:
"""
Move to fun-house
Args:
ctx (Any): Discord context
message (discord.Message): Discord message
@ -224,7 +240,9 @@ class Owner(commands.Cog):
try:
if not isinstance(message.channel, discord.TextChannel):
return
funhouse_channel: discord.TextChannel = ctx.guild.get_channel(1213160512364478607)
funhouse_channel: discord.TextChannel = ctx.guild.get_channel(
1213160512364478607
)
message_content: str = message.content
message_author: str = message.author.display_name
message_channel: str = message.channel.name
@ -232,25 +250,27 @@ class Owner(commands.Cog):
if message.attachments:
for item in message.attachments:
if item.url and len(item.url) >= 20:
image: io.BytesIO = io.BytesIO(requests.get(item.url, stream=True,
timeout=20).raw.read())
ext: str = item.url.split(".")[-1]\
.split("?")[0].split("&")[0]
_file = discord.File(image, filename=f'img.{ext}')
await funhouse_channel.send(f"*Performing bureaucratic duties (this didn't belong in #{message_channel})\
...*\n**{message_author}:** {message_content}")
image: io.BytesIO = io.BytesIO(
requests.get(item.url, stream=True, timeout=20).raw.read()
)
ext: str = item.url.split(".")[-1].split("?")[0].split("&")[0]
_file = discord.File(image, filename=f"img.{ext}")
await funhouse_channel.send(
f"*Performing bureaucratic duties (this didn't belong in #{message_channel})\
...*\n**{message_author}:** {message_content}"
)
await message.delete()
await ctx.respond("OK!", ephemeral=True)
except:
traceback.print_exc()
return await ctx.respond("Failed! :(", ephemeral=True)
@commands.user_command(name="Einsperren!", guild_ids=[145182936002482196])
@commands.is_owner()
async def einsperren(self, ctx, member: discord.Member) -> None:
"""
Einsperren!
Args:
ctx (Any): Discord context
member (discord.Member): Discord member
@ -259,16 +279,23 @@ class Owner(commands.Cog):
"""
try:
if not ctx.guild.id == 1145182936002482196:
return # Not home server!
return # Not home server!
if not member.roles:
return # No roles
return # No roles
audit_reason: str = f"Einsperren von {ctx.user.display_name}"
member = ctx.guild.get_member(member.id)
member_display: str = member.display_name
einsperren_role: discord.Role = ctx.guild.get_role(1235415059300093973) if ctx.guild.id != 1145182936002482196\
einsperren_role: discord.Role = (
ctx.guild.get_role(1235415059300093973)
if ctx.guild.id != 1145182936002482196
else ctx.guild.get_role(1235406301614309386)
member_roles: list = [role for role in member.roles if not role.name == "@everyone"]
member_role_names: list[str] = [str(role.name).lower() for role in member_roles]
)
member_roles: list = [
role for role in member.roles if not role.name == "@everyone"
]
member_role_names: list[str] = [
str(role.name).lower() for role in member_roles
]
opers_chan: discord.TextChannel = ctx.guild.get_channel(1181416083287187546)
if not "einsperren" in member_role_names:
try:
@ -276,28 +303,37 @@ class Owner(commands.Cog):
self.former_roles_store.pop(member.id)
self.former_roles_store[member.id] = member.roles
except:
pass # Safe to ignore
pass # Safe to ignore
try:
await member.edit(roles=[einsperren_role], reason=audit_reason)
await ctx.respond(f"Gesendet {member_display} an einsperren.", ephemeral=True)
await opers_chan.send(f"@everyone: {ctx.user.display_name} gesendet {member_display} an einsperren.")
await ctx.respond(
f"Gesendet {member_display} an einsperren.", ephemeral=True
)
await opers_chan.send(
f"@everyone: {ctx.user.display_name} gesendet {member_display} an einsperren."
)
except:
traceback.print_exc()
return await ctx.respond("GOTTVERDAMMT!!", ephemeral=True)
self.former_roles_store[member.id] = member.roles
if not member.id in self.former_roles_store:
await member.edit(roles=[]) # No roles
else:
former_roles: list = self.former_roles_store.get(member.id, [0])
await member.edit(roles=former_roles, reason=f"De-{audit_reason}")
await ctx.respond(f"{member_display} wurde von der Einsperre befreit.", ephemeral=True)
await opers_chan.send(f"{member_display} wurde von {ctx.user.display_name} aus der Einsperre befreit.")
await ctx.respond(
f"{member_display} wurde von der Einsperre befreit.", ephemeral=True
)
await opers_chan.send(
f"{member_display} wurde von {ctx.user.display_name} aus der Einsperre befreit."
)
except Exception as e:
traceback.print_exc()
return await ctx.respond(f"ERR: {str(e)}", ephemeral=True)
def setup(bot) -> None:
"""Run on Cog Load"""
bot.add_cog(Owner(bot))
bot.add_cog(Owner(bot))

View File

@ -6,38 +6,44 @@ from util.radio_util import get_now_playing, skip
import discord
from disc_havoc import Havoc
class Radio(commands.Cog):
"""Radio Cog for Havoc"""
def __init__(self, bot: Havoc) -> None:
self.bot: Havoc = bot
self.channels: dict[str, tuple] = {
'sfm': (1145182936002482196, 1221615558492029050), # Tuple: Guild Id, Chan Id
}
"sfm": (
1145182936002482196,
1221615558492029050,
), # Tuple: Guild Id, Chan Id
}
self.STREAM_URL: str = "https://stream.codey.lol/sfm.ogg"
self.LAST_NP_TRACK: Optional[str] = None
try:
self.radio_state_loop.cancel()
except Exception as e:
logging.debug("Failed to cancel radio_state_loop: %s",
str(e))
logging.debug("Failed to cancel radio_state_loop: %s", str(e))
@commands.Cog.listener()
async def on_ready(self) -> None:
"""Run on Bot Ready"""
await self.radio_init()
def is_radio_chan(): # type: ignore
def is_radio_chan(): # type: ignore
"""Check if channel is radio chan"""
def predicate(ctx):
try:
return ctx.channel.id == 1221615558492029050
except:
traceback.print_exc()
return False
return commands.check(predicate)
return commands.check(predicate)
@bridge.bridge_command()
@commands.is_owner()
@commands.is_owner()
async def reinitradio(self, ctx) -> None:
"""
Reinitialize serious.FM
@ -45,15 +51,15 @@ class Radio(commands.Cog):
loop: discord.asyncio.AbstractEventLoop = self.bot.loop
loop.create_task(self.radio_init())
await ctx.respond("Done!", ephemeral=True)
async def radio_init(self) -> None:
"""Init Radio"""
try:
(radio_guild, radio_chan) = self.channels['sfm']
(radio_guild, radio_chan) = self.channels["sfm"]
guild: Optional[discord.Guild] = self.bot.get_guild(radio_guild)
if not guild:
return
channel = guild.get_channel(radio_chan)
channel = guild.get_channel(radio_chan)
if not isinstance(channel, discord.VoiceChannel):
return
if not self.bot.voice_clients:
@ -62,8 +68,7 @@ class Radio(commands.Cog):
try:
self.radio_state_loop.cancel()
except Exception as e:
logging.debug("Failed to cancel radio_state_loop: %s",
str(e))
logging.debug("Failed to cancel radio_state_loop: %s", str(e))
self.radio_state_loop.start()
logging.info("radio_state_loop task started!")
except:
@ -72,12 +77,12 @@ class Radio(commands.Cog):
except:
traceback.print_exc()
return
@tasks.loop(seconds=5.0)
async def radio_state_loop(self) -> None:
"""Radio State Loop"""
try:
(radio_guild, radio_chan) = self.channels['sfm']
(radio_guild, radio_chan) = self.channels["sfm"]
try:
vc: discord.VoiceProtocol = self.bot.voice_clients[-1]
except:
@ -90,41 +95,47 @@ class Radio(commands.Cog):
return
await channel.connect()
vc = self.bot.voice_clients[-1]
if not vc.is_playing() or vc.is_paused(): # type: ignore
"""
Mypy does not seem aware of the is_playing, play, and is_paused methods,
but they exist.
"""
logging.info("Detected VC not playing... playing!")
source: discord.FFmpegAudio = discord.FFmpegOpusAudio(self.STREAM_URL,
before_options="-timeout 3000000")
vc.play(source, # type: ignore
after=lambda e: logging.info("Error: %s", e) if e\
else None)
# Get Now Playing
source: discord.FFmpegAudio = discord.FFmpegOpusAudio(
self.STREAM_URL, before_options="-timeout 3000000"
)
vc.play(
source, # type: ignore
after=lambda e: logging.info("Error: %s", e) if e else None,
)
# Get Now Playing
np_track: Optional[str] = await get_now_playing()
if np_track and not self.LAST_NP_TRACK == np_track:
self.LAST_NP_TRACK = np_track
await self.bot.change_presence(activity=discord.Activity(type=discord.ActivityType.listening, name=np_track))
await self.bot.change_presence(
activity=discord.Activity(
type=discord.ActivityType.listening, name=np_track
)
)
except:
traceback.print_exc()
@bridge.bridge_command()
@commands.is_owner()
async def skip(self, ctx) -> None:
"""
Skip - Convenience Command
"""
await skip()
return await ctx.respond("OK", ephemeral=True)
def cog_unload(self) -> None:
"""Run on Cog Unload"""
"""Run on Cog Unload"""
self.radio_state_loop.cancel()
def setup(bot) -> None:
"""Run on Cog Load"""
bot.add_cog(Radio(bot))
bot.add_cog(Radio(bot))

View File

@ -11,18 +11,23 @@ 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
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:
@ -31,18 +36,19 @@ class Sing(commands.Cog):
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:
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)
interaction: bool = isinstance(
ctx, discord.ext.bridge.BridgeApplicationContext
)
activity: Optional[discord.Activity] = None
if not song:
if not ctx.author.activities:
@ -51,68 +57,90 @@ class Sing(commands.Cog):
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.")
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
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)
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
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
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}")
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
footer: str = "" # Placeholder
for section in search_result_wrapped:
c+=1
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}"
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)
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
@ -122,67 +150,99 @@ class Sing(commands.Cog):
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!
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}...***")
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)
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)
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)
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 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}")
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
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}"
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:])
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))