reformat (black)
This commit is contained in:
185
cogs/karma.py
185
cogs/karma.py
@ -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))
|
||||
|
103
cogs/lovehate.py
103
cogs/lovehate.py
@ -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))
|
||||
|
482
cogs/meme.py
482
cogs/meme.py
@ -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))
|
||||
|
875
cogs/misc.py
875
cogs/misc.py
File diff suppressed because it is too large
Load Diff
210
cogs/owner.py
210
cogs/owner.py
@ -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))
|
||||
|
@ -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))
|
||||
|
204
cogs/sing.py
204
cogs/sing.py
@ -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))
|
||||
|
Reference in New Issue
Block a user