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