From d764720481136f786593a67f152ac876ef7b151d Mon Sep 17 00:00:00 2001 From: Rohan Date: Wed, 2 Dec 2020 21:52:10 +0530 Subject: Add Reddit class and emojis to constants file. --- bot/constants.py | 18 ++++++ bot/exts/evergreen/reddit.py | 128 ------------------------------------------- 2 files changed, 18 insertions(+), 128 deletions(-) delete mode 100644 bot/exts/evergreen/reddit.py (limited to 'bot') diff --git a/bot/constants.py b/bot/constants.py index 6999f321..d1ffd30f 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -17,6 +17,7 @@ __all__ = ( "Roles", "Tokens", "Wolfram", + "Reddit", "RedisConfig", "MODERATION_ROLES", "STAFF_ROLES", @@ -144,6 +145,15 @@ class Emojis: status_dnd = "<:status_dnd:470326272082313216>" status_offline = "<:status_offline:470326266537705472>" + # Reddit emojis + reddit = "<:reddit:676030265734332427>" + reddit_post_text = "<:reddit_post_text:676030265910493204>" + reddit_post_video = "<:reddit_post_video:676030265839190047>" + reddit_post_photo = "<:reddit_post_photo:676030265734201344>" + reddit_upvote = "<:reddit_upvote:755845219890757644>" + reddit_comments = "<:reddit_comments:755845255001014384>" + reddit_users = "<:reddit_users:755845303822974997>" + class Icons: questionmark = "https://cdn.discordapp.com/emojis/512367613339369475.png" @@ -231,6 +241,14 @@ class Source: github_avatar_url = "https://avatars1.githubusercontent.com/u/9919" +class Reddit: + subreddits = ["r/Python"] + + client_id = environ.get("REDDIT_CLIENT_ID") + secret = environ.get("REDDIT_SECRET") + webhook = int(environ.get("REDDIT_WEBHOOK", 635408384794951680)) + + # Default role combinations MODERATION_ROLES = Roles.moderator, Roles.admin, Roles.owner STAFF_ROLES = Roles.helpers, Roles.moderator, Roles.admin, Roles.owner diff --git a/bot/exts/evergreen/reddit.py b/bot/exts/evergreen/reddit.py deleted file mode 100644 index 49127bea..00000000 --- a/bot/exts/evergreen/reddit.py +++ /dev/null @@ -1,128 +0,0 @@ -import logging -import random - -import discord -from discord.ext import commands -from discord.ext.commands.cooldowns import BucketType - -from bot.utils.pagination import ImagePaginator - -log = logging.getLogger(__name__) - - -class Reddit(commands.Cog): - """Fetches reddit posts.""" - - def __init__(self, bot: commands.Bot): - self.bot = bot - - async def fetch(self, url: str) -> dict: - """Send a get request to the reddit API and get json response.""" - session = self.bot.http_session - params = { - 'limit': 50 - } - headers = { - 'User-Agent': 'Iceman' - } - - async with session.get(url=url, params=params, headers=headers) as response: - return await response.json() - - @commands.command(name='reddit') - @commands.cooldown(1, 10, BucketType.user) - async def get_reddit(self, ctx: commands.Context, subreddit: str = 'python', sort: str = "hot") -> None: - """ - Fetch reddit posts by using this command. - - Gets a post from r/python by default. - Usage: - --> .reddit [subreddit_name] [hot/top/new] - """ - pages = [] - sort_list = ["hot", "new", "top", "rising"] - if sort.lower() not in sort_list: - await ctx.send(f"Invalid sorting: {sort}\nUsing default sorting: `Hot`") - sort = "hot" - - data = await self.fetch(f'https://www.reddit.com/r/{subreddit}/{sort}/.json') - - try: - posts = data["data"]["children"] - except KeyError: - return await ctx.send('Subreddit not found!') - if not posts: - return await ctx.send('No posts available!') - - if posts[1]["data"]["over_18"] is True: - return await ctx.send( - "You cannot access this Subreddit as it is ment for those who " - "are 18 years or older." - ) - - embed_titles = "" - - # Chooses k unique random elements from a population sequence or set. - random_posts = random.sample(posts, k=5) - - # ----------------------------------------------------------- - # This code below is bound of change when the emojis are added. - - upvote_emoji = self.bot.get_emoji(755845219890757644) - comment_emoji = self.bot.get_emoji(755845255001014384) - user_emoji = self.bot.get_emoji(755845303822974997) - text_emoji = self.bot.get_emoji(676030265910493204) - video_emoji = self.bot.get_emoji(676030265839190047) - image_emoji = self.bot.get_emoji(676030265734201344) - reddit_emoji = self.bot.get_emoji(676030265734332427) - - # ------------------------------------------------------------ - - for i, post in enumerate(random_posts, start=1): - post_title = post["data"]["title"][0:50] - post_url = post['data']['url'] - if post_title == "": - post_title = "No Title." - elif post_title == post_url: - post_title = "Title is itself a link." - - # ------------------------------------------------------------------ - # Embed building. - - embed_titles += f"**{i}.[{post_title}]({post_url})**\n" - image_url = " " - post_stats = f"{text_emoji}" # Set default content type to text. - - if post["data"]["is_video"] is True or "youtube" in post_url.split("."): - # This means the content type in the post is a video. - post_stats = f"{video_emoji} " - - elif post_url.endswith("jpg") or post_url.endswith("png") or post_url.endswith("gif"): - # This means the content type in the post is an image. - post_stats = f"{image_emoji} " - image_url = post_url - - votes = f'{upvote_emoji}{post["data"]["ups"]}' - comments = f'{comment_emoji}\u2002{ post["data"]["num_comments"]}' - post_stats += ( - f"\u2002{votes}\u2003" - f"{comments}" - f'\u2003{user_emoji}\u2002{post["data"]["author"]}\n' - ) - embed_titles += f"{post_stats}\n" - page_text = f"**[{post_title}]({post_url})**\n{post_stats}\n{post['data']['selftext'][0:200]}" - - embed = discord.Embed() - page_tuple = (page_text, image_url) - pages.append(page_tuple) - - # ------------------------------------------------------------------ - - pages.insert(0, (embed_titles, " ")) - embed.set_author(name=f"r/{posts[0]['data']['subreddit']} - {sort}", icon_url=reddit_emoji.url) - await ImagePaginator.paginate(pages, ctx, embed) - - -def setup(bot: commands.Bot) -> None: - """Load the Cog.""" - bot.add_cog(Reddit(bot)) -- cgit v1.2.3 From 04f122348eca25dbbf44afdcc6fcd417aa98bf89 Mon Sep 17 00:00:00 2001 From: Rohan Date: Wed, 2 Dec 2020 21:53:29 +0530 Subject: Add Subreddit converter. --- bot/utils/converters.py | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/utils/converters.py b/bot/utils/converters.py index 228714c9..27804170 100644 --- a/bot/utils/converters.py +++ b/bot/utils/converters.py @@ -1,5 +1,6 @@ import discord -from discord.ext.commands.converter import MessageConverter +from discord.ext.commands import BadArgument, Context +from discord.ext.commands.converter import Converter, MessageConverter class WrappedMessageConverter(MessageConverter): @@ -14,3 +15,32 @@ class WrappedMessageConverter(MessageConverter): argument = argument[1:-1] return await super().convert(ctx, argument) + + +class Subreddit(Converter): + """Forces a string to begin with "r/" and checks if it's a valid subreddit.""" + + @staticmethod + async def convert(ctx: Context, sub: str) -> str: + """ + Force sub to begin with "r/" and check if it's a valid subreddit. + + If sub is a valid subreddit, return it prepended with "r/" + """ + sub = sub.lower() + + if not sub.startswith("r/"): + sub = f"r/{sub}" + + resp = await ctx.bot.http_session.get( + "https://www.reddit.com/subreddits/search.json", + params={"q": sub} + ) + + json = await resp.json() + if not json["data"]["children"]: + raise BadArgument( + f"The subreddit `{sub}` either doesn't exist, or it has no posts." + ) + + return sub -- cgit v1.2.3 From f4e550bc1db9725577459f9a46d02da038e39c32 Mon Sep 17 00:00:00 2001 From: Rohan Date: Wed, 2 Dec 2020 21:54:14 +0530 Subject: Add sub_clyde utility function. --- bot/utils/messages.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 bot/utils/messages.py (limited to 'bot') diff --git a/bot/utils/messages.py b/bot/utils/messages.py new file mode 100644 index 00000000..a6c035f9 --- /dev/null +++ b/bot/utils/messages.py @@ -0,0 +1,19 @@ +import re +from typing import Optional + + +def sub_clyde(username: Optional[str]) -> Optional[str]: + """ + Replace "e"/"E" in any "clyde" in `username` with a Cyrillic "е"/"E" and return the new string. + + Discord disallows "clyde" anywhere in the username for webhooks. It will return a 400. + Return None only if `username` is None. + """ + def replace_e(match: re.Match) -> str: + char = "е" if match[2] == "e" else "Е" + return match[1] + char + + if username: + return re.sub(r"(clyd)(e)", replace_e, username, flags=re.I) + else: + return username # Empty string or None -- cgit v1.2.3 From 6e32bda97aa91af1d100ea46f7efdf7031f87bff Mon Sep 17 00:00:00 2001 From: Rohan Date: Wed, 2 Dec 2020 21:54:20 +0530 Subject: Migrate reddit command from Bot repo and add pagination. --- bot/exts/evergreen/reddit.py | 360 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 360 insertions(+) create mode 100644 bot/exts/evergreen/reddit.py (limited to 'bot') diff --git a/bot/exts/evergreen/reddit.py b/bot/exts/evergreen/reddit.py new file mode 100644 index 00000000..fb447cda --- /dev/null +++ b/bot/exts/evergreen/reddit.py @@ -0,0 +1,360 @@ +import asyncio +import logging +import random +import textwrap +from collections import namedtuple +from datetime import datetime, timedelta +from typing import List, Union + +from aiohttp import BasicAuth, ClientError +from discord import Colour, Embed, TextChannel +from discord.ext.commands import Cog, Context, group, has_any_role +from discord.ext.tasks import loop +from discord.utils import escape_markdown, sleep_until + +from bot.bot import Bot +from bot.constants import Channels, ERROR_REPLIES, Emojis, Reddit as RedditConfig, STAFF_ROLES +from bot.utils.converters import Subreddit +from bot.utils.messages import sub_clyde +from bot.utils.pagination import ImagePaginator, LinePaginator + +log = logging.getLogger(__name__) + +AccessToken = namedtuple("AccessToken", ["token", "expires_at"]) + + +class Reddit(Cog): + """Track subreddit posts and show detailed statistics about them.""" + + HEADERS = {"User-Agent": "python3:python-discord/bot:1.0.0 (by /u/PythonDiscord)"} + URL = "https://www.reddit.com" + OAUTH_URL = "https://oauth.reddit.com" + MAX_RETRIES = 3 + + def __init__(self, bot: Bot): + self.bot = bot + + self.webhook = None + self.access_token = None + self.client_auth = BasicAuth(RedditConfig.client_id, RedditConfig.secret) + + bot.loop.create_task(self.init_reddit_ready()) + self.auto_poster_loop.start() + + def cog_unload(self) -> None: + """Stop the loop task and revoke the access token when the cog is unloaded.""" + self.auto_poster_loop.cancel() + if self.access_token and self.access_token.expires_at > datetime.utcnow(): + asyncio.create_task(self.revoke_access_token()) + + async def init_reddit_ready(self) -> None: + """Sets the reddit webhook when the cog is loaded.""" + await self.bot.wait_until_guild_available() + if not self.webhook: + self.webhook = await self.bot.fetch_webhook(RedditConfig.webhook) + + @property + def channel(self) -> TextChannel: + """Get the #reddit channel object from the bot's cache.""" + return self.bot.get_channel(Channels.reddit) + + def build_pagination_pages(self, posts: List[dict]) -> List[tuple]: + """Build embed pages required for Paginator.""" + pages = [] + first_page = "" + for i, post in enumerate(posts, start=1): + post_page = "" + image_url = "" + + data = post["data"] + + title = textwrap.shorten(data["title"], width=64, placeholder="...") + + # Normal brackets interfere with Markdown. + title = escape_markdown(title).replace("[", "⦋").replace("]", "⦌") + link = self.URL + data["permalink"] + + first_page += f"**{i}. [{title.replace('*', '')}]({link})**\n" + post_page += f"**{i}. [{title}]({link})**\n\n" + + text = data["selftext"] + if text: + first_page += textwrap.shorten(text, width=128, placeholder="...").replace("*", "") + "\n" + post_page += textwrap.shorten(text, width=252, placeholder="...") + "\n\n" + + ups = data["ups"] + comments = data["num_comments"] + author = data["author"] + + content_type = Emojis.reddit_post_text + if data["is_video"] is True or "youtube" in data["url"].split("."): + # This means the content type in the post is a video. + content_type = f"{Emojis.reddit_post_video}" + + elif any(data["url"].endswith(pic_format) for pic_format in ("jpg", "png", "gif")): + # This means the content type in the post is an image. + content_type = f"{Emojis.reddit_post_photo}" + image_url = data["url"] + + first_page += ( + f"{content_type}\u2003{Emojis.reddit_upvote}{ups}\u2003{Emojis.reddit_comments}" + f"\u2002{comments}\u2003{Emojis.reddit_users}{author}\n\n" + ) + post_page += ( + f"{content_type}\u2003{Emojis.reddit_upvote}{ups}\u2003{Emojis.reddit_comments}\u2002" + f"{comments}\u2003{Emojis.reddit_users}{author}" + ) + + pages.append((post_page, image_url)) + + pages.insert(0, (first_page, "")) + return pages + + async def get_access_token(self) -> None: + """ + Get a Reddit API OAuth2 access token and assign it to self.access_token. + + A token is valid for 1 hour. There will be MAX_RETRIES to get a token, after which the cog + will be unloaded and a ClientError raised if retrieval was still unsuccessful. + """ + for i in range(1, self.MAX_RETRIES + 1): + response = await self.bot.http_session.post( + url=f"{self.URL}/api/v1/access_token", + headers=self.HEADERS, + auth=self.client_auth, + data={ + "grant_type": "client_credentials", + "duration": "temporary" + } + ) + + if response.status == 200 and response.content_type == "application/json": + content = await response.json() + expiration = int(content["expires_in"]) - 60 # Subtract 1 minute for leeway. + self.access_token = AccessToken( + token=content["access_token"], + expires_at=datetime.utcnow() + timedelta(seconds=expiration) + ) + + log.debug(f"New token acquired; expires on UTC {self.access_token.expires_at}") + return + else: + log.debug( + f"Failed to get an access token: " + f"status {response.status} & content type {response.content_type}; " + f"retrying ({i}/{self.MAX_RETRIES})" + ) + + await asyncio.sleep(3) + + self.bot.remove_cog(self.qualified_name) + raise ClientError("Authentication with the Reddit API failed. Unloading the cog.") + + async def revoke_access_token(self) -> None: + """ + Revoke the OAuth2 access token for the Reddit API. + + For security reasons, it's good practice to revoke the token when it's no longer being used. + """ + response = await self.bot.http_session.post( + url=f"{self.URL}/api/v1/revoke_token", + headers=self.HEADERS, + auth=self.client_auth, + data={ + "token": self.access_token.token, + "token_type_hint": "access_token" + } + ) + + if response.status == 204 and response.content_type == "application/json": + self.access_token = None + else: + log.warning(f"Unable to revoke access token: status {response.status}.") + + async def fetch_posts(self, route: str, *, amount: int = 25, params: dict = None) -> List[dict]: + """A helper method to fetch a certain amount of Reddit posts at a given route.""" + # Reddit's JSON responses only provide 25 posts at most. + if not 25 >= amount > 0: + raise ValueError("Invalid amount of subreddit posts requested.") + + # Renew the token if necessary. + if not self.access_token or self.access_token.expires_at < datetime.utcnow(): + await self.get_access_token() + + url = f"{self.OAUTH_URL}/{route}" + for _ in range(self.MAX_RETRIES): + response = await self.bot.http_session.get( + url=url, + headers={**self.HEADERS, "Authorization": f"bearer {self.access_token.token}"}, + params=params + ) + if response.status == 200 and response.content_type == 'application/json': + # Got appropriate response - process and return. + content = await response.json() + posts = content["data"]["children"] + + filtered_posts = [post for post in posts if not post["data"]["over_18"]] + + return filtered_posts[:amount] + + await asyncio.sleep(3) + + log.debug(f"Invalid response from: {url} - status code {response.status}, mimetype {response.content_type}") + return list() # Failed to get appropriate response within allowed number of retries. + + async def get_top_posts( + self, subreddit: Subreddit, time: str = "all", amount: int = 5, paginate: bool = False + ) -> Union[Embed, List[tuple]]: + """ + Get the top amount of posts for a given subreddit within a specified timeframe. + + A time of "all" will get posts from all time, "day" will get top daily posts and "week" will get the top + weekly posts. + + The amount should be between 0 and 25 as Reddit's JSON requests only provide 25 posts at most. + """ + embed = Embed(description="") + + posts = await self.fetch_posts( + route=f"{subreddit}/top", + amount=amount, + params={"t": time} + ) + if not posts: + embed.title = random.choice(ERROR_REPLIES) + embed.colour = Colour.red() + embed.description = ( + "Sorry! We couldn't find any SFW posts from that subreddit. " + "If this problem persists, please let us know." + ) + + return embed + + pages = self.build_pagination_pages(posts) + + if paginate: + return pages + + embed.description += pages[0] + embed.colour = Colour.blurple() + return embed + + @loop() + async def auto_poster_loop(self) -> None: + """Post the top 5 posts daily, and the top 5 posts weekly.""" + # once d.py get support for `time` parameter in loop decorator, + # this can be removed and the loop can use the `time=datetime.time.min` parameter + now = datetime.utcnow() + tomorrow = now + timedelta(days=1) + midnight_tomorrow = tomorrow.replace(hour=0, minute=0, second=0) + + await sleep_until(midnight_tomorrow) + + await self.bot.wait_until_guild_available() + if not self.webhook: + await self.bot.fetch_webhook(RedditConfig.webhook) + + if datetime.utcnow().weekday() == 0: + await self.top_weekly_posts() + # if it's a monday send the top weekly posts + + for subreddit in RedditConfig.subreddits: + top_posts = await self.get_top_posts(subreddit=subreddit, time="day") + username = sub_clyde(f"{subreddit} Top Daily Posts") + message = await self.webhook.send(username=username, embed=top_posts, wait=True) + + if message.channel.is_news(): + await message.publish() + + async def top_weekly_posts(self) -> None: + """Post a summary of the top posts.""" + for subreddit in RedditConfig.subreddits: + # Send and pin the new weekly posts. + top_posts = await self.get_top_posts(subreddit=subreddit, time="week") + username = sub_clyde(f"{subreddit} Top Weekly Posts") + message = await self.webhook.send(wait=True, username=username, embed=top_posts) + + if subreddit.lower() == "r/python": + if not self.channel: + log.warning("Failed to get #reddit channel to remove pins in the weekly loop.") + return + + # Remove the oldest pins so that only 12 remain at most. + pins = await self.channel.pins() + + while len(pins) >= 12: + await pins[-1].unpin() + del pins[-1] + + await message.pin() + + if message.channel.is_news(): + await message.publish() + + @group(name="reddit", invoke_without_command=True) + async def reddit_group(self, ctx: Context) -> None: + """View the top posts from various subreddits.""" + await ctx.send_help(ctx.command) + + @reddit_group.command(name="top") + async def top_command(self, ctx: Context, subreddit: Subreddit = "r/Python") -> None: + """Send the top posts of all time from a given subreddit.""" + async with ctx.typing(): + pages = await self.get_top_posts(subreddit=subreddit, time="all", paginate=True) + + embed = Embed( + title=f"{Emojis.reddit} {subreddit} - Top\n\n", + color=Colour.blurple() + ) + + await ImagePaginator.paginate(pages, ctx, embed) + + @reddit_group.command(name="daily") + async def daily_command(self, ctx: Context, subreddit: Subreddit = "r/Python") -> None: + """Send the top posts of today from a given subreddit.""" + async with ctx.typing(): + pages = await self.get_top_posts(subreddit=subreddit, time="day", paginate=True) + + embed = Embed( + title=f"{Emojis.reddit} {subreddit} - Daily\n\n", + color=Colour.blurple() + ) + + await ImagePaginator.paginate(pages, ctx, embed) + + @reddit_group.command(name="weekly") + async def weekly_command(self, ctx: Context, subreddit: Subreddit = "r/Python") -> None: + """Send the top posts of this week from a given subreddit.""" + async with ctx.typing(): + pages = await self.get_top_posts(subreddit=subreddit, time="week", paginate=True) + + embed = Embed( + title=f"{Emojis.reddit} {subreddit} - Weekly\n\n", + color=Colour.blurple() + ) + + await ImagePaginator.paginate(pages, ctx, embed) + + @has_any_role(*STAFF_ROLES) + @reddit_group.command(name="subreddits", aliases=("subs",)) + async def subreddits_command(self, ctx: Context) -> None: + """Send a paginated embed of all the subreddits we're relaying.""" + embed = Embed() + embed.title = "Relayed subreddits." + embed.colour = Colour.blurple() + + await LinePaginator.paginate( + RedditConfig.subreddits, + ctx, embed, + footer_text="Use the reddit commands along with these to view their posts.", + empty=False, + max_lines=15 + ) + + +def setup(bot: Bot) -> None: + """Load the Reddit cog.""" + if not RedditConfig.secret or not RedditConfig.client_id: + log.error("Credentials not provided, cog not loaded.") + return + bot.add_cog(Reddit(bot)) -- cgit v1.2.3 From 94822085a9f3e1677a8c566796fd1655b0b40ebf Mon Sep 17 00:00:00 2001 From: Rohan Date: Wed, 9 Dec 2020 20:55:27 +0530 Subject: Changes to command output. --- bot/exts/evergreen/reddit.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/reddit.py b/bot/exts/evergreen/reddit.py index fb447cda..ddc0cc27 100644 --- a/bot/exts/evergreen/reddit.py +++ b/bot/exts/evergreen/reddit.py @@ -62,24 +62,24 @@ class Reddit(Cog): """Build embed pages required for Paginator.""" pages = [] first_page = "" - for i, post in enumerate(posts, start=1): + for post in posts: post_page = "" image_url = "" data = post["data"] - title = textwrap.shorten(data["title"], width=64, placeholder="...") + title = textwrap.shorten(data["title"], width=50, placeholder="...") # Normal brackets interfere with Markdown. title = escape_markdown(title).replace("[", "⦋").replace("]", "⦌") link = self.URL + data["permalink"] - first_page += f"**{i}. [{title.replace('*', '')}]({link})**\n" - post_page += f"**{i}. [{title}]({link})**\n\n" + first_page += f"**[{title.replace('*', '')}]({link})**\n" + post_page += f"**[{title}]({link})**\n\n" text = data["selftext"] if text: - first_page += textwrap.shorten(text, width=128, placeholder="...").replace("*", "") + "\n" + first_page += textwrap.shorten(text, width=100, placeholder="...").replace("*", "") + "\n" post_page += textwrap.shorten(text, width=252, placeholder="...") + "\n\n" ups = data["ups"] @@ -107,7 +107,7 @@ class Reddit(Cog): pages.append((post_page, image_url)) - pages.insert(0, (first_page, "")) + pages.insert(0, (first_page, "")) # Using image paginator, hence settings image url to empty string return pages async def get_access_token(self) -> None: @@ -235,6 +235,7 @@ class Reddit(Cog): if paginate: return pages + # Use only starting summary page for #reddit channel posts. embed.description += pages[0] embed.colour = Colour.blurple() return embed @@ -302,8 +303,8 @@ class Reddit(Cog): async with ctx.typing(): pages = await self.get_top_posts(subreddit=subreddit, time="all", paginate=True) + await ctx.send("Here are the top r/Python posts of all time!") embed = Embed( - title=f"{Emojis.reddit} {subreddit} - Top\n\n", color=Colour.blurple() ) @@ -315,8 +316,8 @@ class Reddit(Cog): async with ctx.typing(): pages = await self.get_top_posts(subreddit=subreddit, time="day", paginate=True) + await ctx.send("Here are today's top r/Python posts!") embed = Embed( - title=f"{Emojis.reddit} {subreddit} - Daily\n\n", color=Colour.blurple() ) @@ -328,8 +329,8 @@ class Reddit(Cog): async with ctx.typing(): pages = await self.get_top_posts(subreddit=subreddit, time="week", paginate=True) + await ctx.send("Here are this week's top r/Python posts!") embed = Embed( - title=f"{Emojis.reddit} {subreddit} - Weekly\n\n", color=Colour.blurple() ) -- cgit v1.2.3 From ac68262b8c3ec96f4476db7d4a00ebeb6b4149f8 Mon Sep 17 00:00:00 2001 From: Rohan Date: Tue, 29 Dec 2020 09:40:42 +0530 Subject: Fix bug in auto_poster_loop() regarding embed description. --- bot/exts/evergreen/reddit.py | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/reddit.py b/bot/exts/evergreen/reddit.py index ddc0cc27..f5134105 100644 --- a/bot/exts/evergreen/reddit.py +++ b/bot/exts/evergreen/reddit.py @@ -58,7 +58,7 @@ class Reddit(Cog): """Get the #reddit channel object from the bot's cache.""" return self.bot.get_channel(Channels.reddit) - def build_pagination_pages(self, posts: List[dict]) -> List[tuple]: + def build_pagination_pages(self, posts: List[dict], paginate) -> Union[List[tuple], str]: """Build embed pages required for Paginator.""" pages = [] first_page = "" @@ -75,19 +75,17 @@ class Reddit(Cog): link = self.URL + data["permalink"] first_page += f"**[{title.replace('*', '')}]({link})**\n" - post_page += f"**[{title}]({link})**\n\n" text = data["selftext"] if text: first_page += textwrap.shorten(text, width=100, placeholder="...").replace("*", "") + "\n" - post_page += textwrap.shorten(text, width=252, placeholder="...") + "\n\n" ups = data["ups"] comments = data["num_comments"] author = data["author"] content_type = Emojis.reddit_post_text - if data["is_video"] is True or "youtube" in data["url"].split("."): + if data["is_video"] is True or {"youtube", "youtu.be"}.issubset(set(data["url"].split("."))): # This means the content type in the post is a video. content_type = f"{Emojis.reddit_post_video}" @@ -100,12 +98,21 @@ class Reddit(Cog): f"{content_type}\u2003{Emojis.reddit_upvote}{ups}\u2003{Emojis.reddit_comments}" f"\u2002{comments}\u2003{Emojis.reddit_users}{author}\n\n" ) - post_page += ( - f"{content_type}\u2003{Emojis.reddit_upvote}{ups}\u2003{Emojis.reddit_comments}\u2002" - f"{comments}\u2003{Emojis.reddit_users}{author}" - ) - pages.append((post_page, image_url)) + if paginate: + post_page += f"**[{title}]({link})**\n\n" + if text: + post_page += textwrap.shorten(text, width=252, placeholder="...") + "\n\n" + post_page += ( + f"{content_type}\u2003{Emojis.reddit_upvote}{ups}\u2003{Emojis.reddit_comments}\u2002" + f"{comments}\u2003{Emojis.reddit_users}{author}" + ) + + pages.append((post_page, image_url)) + + if not paginate: + # Return the first summery page if pagination is not required + return first_page pages.insert(0, (first_page, "")) # Using image paginator, hence settings image url to empty string return pages @@ -213,7 +220,7 @@ class Reddit(Cog): The amount should be between 0 and 25 as Reddit's JSON requests only provide 25 posts at most. """ - embed = Embed(description="") + embed = Embed() posts = await self.fetch_posts( route=f"{subreddit}/top", @@ -230,13 +237,11 @@ class Reddit(Cog): return embed - pages = self.build_pagination_pages(posts) - if paginate: - return pages + return self.build_pagination_pages(posts, paginate=True) # Use only starting summary page for #reddit channel posts. - embed.description += pages[0] + embed.description = self.build_pagination_pages(posts, paginate=False) embed.colour = Colour.blurple() return embed @@ -303,7 +308,7 @@ class Reddit(Cog): async with ctx.typing(): pages = await self.get_top_posts(subreddit=subreddit, time="all", paginate=True) - await ctx.send("Here are the top r/Python posts of all time!") + await ctx.send(f"Here are the top {subreddit} posts of all time!") embed = Embed( color=Colour.blurple() ) @@ -316,7 +321,7 @@ class Reddit(Cog): async with ctx.typing(): pages = await self.get_top_posts(subreddit=subreddit, time="day", paginate=True) - await ctx.send("Here are today's top r/Python posts!") + await ctx.send(f"Here are today's top {subreddit} posts!") embed = Embed( color=Colour.blurple() ) @@ -329,7 +334,7 @@ class Reddit(Cog): async with ctx.typing(): pages = await self.get_top_posts(subreddit=subreddit, time="week", paginate=True) - await ctx.send("Here are this week's top r/Python posts!") + await ctx.send(f"Here are this week's top {subreddit} posts!") embed = Embed( color=Colour.blurple() ) -- cgit v1.2.3 From c9f0d26601f7d3bf01257fbff9384df76aa381f6 Mon Sep 17 00:00:00 2001 From: Rohan Date: Wed, 27 Jan 2021 13:02:06 +0530 Subject: Fix lint error: Missing type annotation for function arugment `paginate`. --- bot/exts/evergreen/reddit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/reddit.py b/bot/exts/evergreen/reddit.py index f5134105..1a4f9add 100644 --- a/bot/exts/evergreen/reddit.py +++ b/bot/exts/evergreen/reddit.py @@ -58,7 +58,7 @@ class Reddit(Cog): """Get the #reddit channel object from the bot's cache.""" return self.bot.get_channel(Channels.reddit) - def build_pagination_pages(self, posts: List[dict], paginate) -> Union[List[tuple], str]: + def build_pagination_pages(self, posts: List[dict], paginate: bool) -> Union[List[tuple], str]: """Build embed pages required for Paginator.""" pages = [] first_page = "" -- cgit v1.2.3 From f63af81bb9f1613ca2b4e00c3f12978a02a86983 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 18 Feb 2021 19:23:04 +0000 Subject: Move gender options to a resource file --- bot/resources/pride/gender_options.json | 41 +++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 bot/resources/pride/gender_options.json (limited to 'bot') diff --git a/bot/resources/pride/gender_options.json b/bot/resources/pride/gender_options.json new file mode 100644 index 00000000..062742fb --- /dev/null +++ b/bot/resources/pride/gender_options.json @@ -0,0 +1,41 @@ +{ + "agender": "agender", + "androgyne": "androgyne", + "androgynous": "androgyne", + "aromantic": "aromantic", + "aro": "aromantic", + "ace": "asexual", + "asexual": "asexual", + "bigender": "bigender", + "bisexual": "bisexual", + "bi": "bisexual", + "demiboy": "demiboy", + "demigirl": "demigirl", + "demi": "demisexual", + "demisexual": "demisexual", + "gay": "gay", + "lgbt": "gay", + "queer": "gay", + "homosexual": "gay", + "fluid": "genderfluid", + "genderfluid": "genderfluid", + "genderqueer": "genderqueer", + "intersex": "intersex", + "lesbian": "lesbian", + "non-binary": "nonbinary", + "enby": "nonbinary", + "nb": "nonbinary", + "nonbinary": "nonbinary", + "omnisexual": "omnisexual", + "omni": "omnisexual", + "pansexual": "pansexual", + "pan": "pansexual", + "pangender": "pangender", + "poly": "polysexual", + "polysexual": "polysexual", + "polyamory": "polyamory", + "polyamorous": "polyamory", + "transgender": "transgender", + "trans": "transgender", + "trigender": "trigender" +} -- cgit v1.2.3 From dd59719df6a36f6cea98faba262fa171ce3289f0 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 18 Feb 2021 19:25:47 +0000 Subject: Merge all avatar cogs into one. Also includes some in_executor and max_concurrency code. --- bot/exts/easter/avatar_easterifier.py | 128 ------------- bot/exts/evergreen/8bitify.py | 54 ------ bot/exts/evergreen/pfp_modify.py | 337 ++++++++++++++++++++++++++++++++++ bot/exts/halloween/spookyavatar.py | 52 ------ bot/exts/pride/pride_avatar.py | 177 ------------------ 5 files changed, 337 insertions(+), 411 deletions(-) delete mode 100644 bot/exts/easter/avatar_easterifier.py delete mode 100644 bot/exts/evergreen/8bitify.py create mode 100644 bot/exts/evergreen/pfp_modify.py delete mode 100644 bot/exts/halloween/spookyavatar.py delete mode 100644 bot/exts/pride/pride_avatar.py (limited to 'bot') diff --git a/bot/exts/easter/avatar_easterifier.py b/bot/exts/easter/avatar_easterifier.py deleted file mode 100644 index 8e8a3500..00000000 --- a/bot/exts/easter/avatar_easterifier.py +++ /dev/null @@ -1,128 +0,0 @@ -import asyncio -import logging -from io import BytesIO -from pathlib import Path -from typing import Tuple, Union - -import discord -from PIL import Image -from PIL.ImageOps import posterize -from discord.ext import commands - -log = logging.getLogger(__name__) - -COLOURS = [ - (255, 247, 0), (255, 255, 224), (0, 255, 127), (189, 252, 201), (255, 192, 203), - (255, 160, 122), (181, 115, 220), (221, 160, 221), (200, 162, 200), (238, 130, 238), - (135, 206, 235), (0, 204, 204), (64, 224, 208) -] # Pastel colours - Easter-like - - -class AvatarEasterifier(commands.Cog): - """Put an Easter spin on your avatar or image!""" - - def __init__(self, bot: commands.Bot): - self.bot = bot - - @staticmethod - def closest(x: Tuple[int, int, int]) -> Tuple[int, int, int]: - """ - Finds the closest easter colour to a given pixel. - - Returns a merge between the original colour and the closest colour - """ - r1, g1, b1 = x - - def distance(point: Tuple[int, int, int]) -> Tuple[int, int, int]: - """Finds the difference between a pastel colour and the original pixel colour.""" - r2, g2, b2 = point - return ((r1 - r2)**2 + (g1 - g2)**2 + (b1 - b2)**2) - - closest_colours = sorted(COLOURS, key=lambda point: distance(point)) - r2, g2, b2 = closest_colours[0] - r = (r1 + r2) // 2 - g = (g1 + g2) // 2 - b = (b1 + b2) // 2 - - return (r, g, b) - - @commands.command(pass_context=True, aliases=["easterify"]) - async def avatareasterify(self, ctx: commands.Context, *colours: Union[discord.Colour, str]) -> None: - """ - This "Easterifies" the user's avatar. - - Given colours will produce a personalised egg in the corner, similar to the egg_decorate command. - If colours are not given, a nice little chocolate bunny will sit in the corner. - Colours are split by spaces, unless you wrap the colour name in double quotes. - Discord colour names, HTML colour names, XKCD colour names and hex values are accepted. - """ - async def send(*args, **kwargs) -> str: - """ - This replaces the original ctx.send. - - When invoking the egg decorating command, the egg itself doesn't print to to the channel. - Returns the message content so that if any errors occur, the error message can be output. - """ - if args: - return args[0] - - async with ctx.typing(): - - # Grabs image of avatar - image_bytes = await ctx.author.avatar_url_as(size=256).read() - - old = Image.open(BytesIO(image_bytes)) - old = old.convert("RGBA") - - # Grabs alpha channel since posterize can't be used with an RGBA image. - alpha = old.getchannel("A").getdata() - old = old.convert("RGB") - old = posterize(old, 6) - - data = old.getdata() - setted_data = set(data) - new_d = {} - - for x in setted_data: - new_d[x] = self.closest(x) - await asyncio.sleep(0) # Ensures discord doesn't break in the background. - new_data = [(*new_d[x], alpha[i]) if x in new_d else x for i, x in enumerate(data)] - - im = Image.new("RGBA", old.size) - im.putdata(new_data) - - if colours: - send_message = ctx.send - ctx.send = send # Assigns ctx.send to a fake send - egg = await ctx.invoke(self.bot.get_command("eggdecorate"), *colours) - if isinstance(egg, str): # When an error message occurs in eggdecorate. - return await send_message(egg) - - ratio = 64 / egg.height - egg = egg.resize((round(egg.width * ratio), round(egg.height * ratio))) - egg = egg.convert("RGBA") - im.alpha_composite(egg, (im.width - egg.width, (im.height - egg.height)//2)) # Right centre. - ctx.send = send_message # Reassigns ctx.send - else: - bunny = Image.open(Path("bot/resources/easter/chocolate_bunny.png")) - im.alpha_composite(bunny, (im.width - bunny.width, (im.height - bunny.height)//2)) # Right centre. - - bufferedio = BytesIO() - im.save(bufferedio, format="PNG") - - bufferedio.seek(0) - - file = discord.File(bufferedio, filename="easterified_avatar.png") # Creates file to be used in embed - embed = discord.Embed( - name="Your Lovely Easterified Avatar", - description="Here is your lovely avatar, all bright and colourful\nwith Easter pastel colours. Enjoy :D" - ) - embed.set_image(url="attachment://easterified_avatar.png") - embed.set_footer(text=f"Made by {ctx.author.display_name}", icon_url=ctx.author.avatar_url) - - await ctx.send(file=file, embed=embed) - - -def setup(bot: commands.Bot) -> None: - """Avatar Easterifier Cog load.""" - bot.add_cog(AvatarEasterifier(bot)) diff --git a/bot/exts/evergreen/8bitify.py b/bot/exts/evergreen/8bitify.py deleted file mode 100644 index 54e68f80..00000000 --- a/bot/exts/evergreen/8bitify.py +++ /dev/null @@ -1,54 +0,0 @@ -from io import BytesIO - -import discord -from PIL import Image -from discord.ext import commands - - -class EightBitify(commands.Cog): - """Make your avatar 8bit!""" - - def __init__(self, bot: commands.Bot) -> None: - self.bot = bot - - @staticmethod - def pixelate(image: Image) -> Image: - """Takes an image and pixelates it.""" - return image.resize((32, 32), resample=Image.NEAREST).resize((1024, 1024), resample=Image.NEAREST) - - @staticmethod - def quantize(image: Image) -> Image: - """Reduces colour palette to 256 colours.""" - return image.quantize() - - @commands.command(name="8bitify") - async def eightbit_command(self, ctx: commands.Context) -> None: - """Pixelates your avatar and changes the palette to an 8bit one.""" - async with ctx.typing(): - image_bytes = await ctx.author.avatar_url.read() - avatar = Image.open(BytesIO(image_bytes)) - avatar = avatar.convert("RGBA").resize((1024, 1024)) - - eightbit = self.pixelate(avatar) - eightbit = self.quantize(eightbit) - - bufferedio = BytesIO() - eightbit.save(bufferedio, format="PNG") - bufferedio.seek(0) - - file = discord.File(bufferedio, filename="8bitavatar.png") - - embed = discord.Embed( - title="Your 8-bit avatar", - description='Here is your avatar. I think it looks all cool and "retro"' - ) - - embed.set_image(url="attachment://8bitavatar.png") - embed.set_footer(text=f"Made by {ctx.author.display_name}", icon_url=ctx.author.avatar_url) - - await ctx.send(file=file, embed=embed) - - -def setup(bot: commands.Bot) -> None: - """Cog load.""" - bot.add_cog(EightBitify(bot)) diff --git a/bot/exts/evergreen/pfp_modify.py b/bot/exts/evergreen/pfp_modify.py new file mode 100644 index 00000000..f356d2f6 --- /dev/null +++ b/bot/exts/evergreen/pfp_modify.py @@ -0,0 +1,337 @@ +import asyncio +import json +import logging +import os +import typing as t +from concurrent.futures import ThreadPoolExecutor +from io import BytesIO +from pathlib import Path + +import aiofiles +import aiohttp +import discord +from PIL import Image, ImageDraw, ImageOps, UnidentifiedImageError +from discord.ext import commands + +from bot.constants import Colours +from bot.utils.halloween import spookifications + +log = logging.getLogger(__name__) + +EASTER_COLOURS = [ + (255, 247, 0), (255, 255, 224), (0, 255, 127), (189, 252, 201), (255, 192, 203), + (255, 160, 122), (181, 115, 220), (221, 160, 221), (200, 162, 200), (238, 130, 238), + (135, 206, 235), (0, 204, 204), (64, 224, 208) +] # Pastel colours - Easter-like + +_EXECUTOR = ThreadPoolExecutor(10) + + +async def in_thread(func: t.Callable, *args) -> asyncio.Future: + """Allows non-async functions to work in async functions.""" + loop = asyncio.get_event_loop() + return await loop.run_in_executor(_EXECUTOR, func, *args) + + +class PfpModify(commands.Cog): + """Various commands for users to change their own profile picture.""" + + def __init__(self, bot: commands.Bot) -> None: + self.bot = bot + self.bot.loop.create_task(self.init_cog()) + + async def init_cog(self) -> None: + """Initial load from resources asynchronously.""" + async with aiofiles.open('bot/resources/pride/gender_options.json') as f: + self.GENDER_OPTIONS = json.loads(await f.read()) + + @staticmethod + def closest(x: t.Tuple[int, int, int]) -> t.Tuple[int, int, int]: + """ + Finds the closest easter colour to a given pixel. + + Returns a merge between the original colour and the closest colour + """ + r1, g1, b1 = x + + def distance(point: t.Tuple[int, int, int]) -> t.Tuple[int, int, int]: + """Finds the difference between a pastel colour and the original pixel colour.""" + r2, g2, b2 = point + return ((r1 - r2)**2 + (g1 - g2)**2 + (b1 - b2)**2) + + closest_colours = sorted(EASTER_COLOURS, key=lambda point: distance(point)) + r2, g2, b2 = closest_colours[0] + r = (r1 + r2) // 2 + g = (g1 + g2) // 2 + b = (b1 + b2) // 2 + + return (r, g, b) + + @staticmethod + def crop_avatar(avatar: Image) -> Image: + """This crops the avatar given into a circle.""" + mask = Image.new("L", avatar.size, 0) + draw = ImageDraw.Draw(mask) + draw.ellipse((0, 0) + avatar.size, fill=255) + avatar.putalpha(mask) + return avatar + + @staticmethod + def crop_ring(ring: Image, px: int) -> Image: + """This crops the given ring into a circle.""" + mask = Image.new("L", ring.size, 0) + draw = ImageDraw.Draw(mask) + draw.ellipse((0, 0) + ring.size, fill=255) + draw.ellipse((px, px, 1024-px, 1024-px), fill=0) + ring.putalpha(mask) + return ring + + def process_options(self, option: str, pixels: int) -> t.Tuple[str, int, str]: + """Does some shared preprocessing for the prideavatar commands.""" + return option.lower(), max(0, min(512, pixels)), self.GENDER_OPTIONS.get(option) + + def process_image( + self, + image_bytes: bytes, + pixels: int, + flag: str + ) -> discord.File: + """Constructs and returns the final image. Used by the pride commands.""" + # This line can raise UnidentifiedImageError and must be handled by the calling func. + avatar = Image.open(BytesIO(image_bytes)) + avatar = avatar.convert("RGBA").resize((1024, 1024)) + + avatar = self.crop_avatar(avatar) + + ring = Image.open(Path(f"bot/resources/pride/flags/{flag}.png")).resize((1024, 1024)) + ring = ring.convert("RGBA") + ring = self.crop_ring(ring, pixels) + + avatar.alpha_composite(ring, (0, 0)) + bufferedio = BytesIO() + avatar.save(bufferedio, format="PNG") + bufferedio.seek(0) + + return discord.File(bufferedio, filename="pride_avatar.png") # Creates file to be used in embed + + async def send_image( + self, + ctx: commands.Context, + image_bytes: bytes, + pixels: int, + flag: str, + option: str + ) -> None: + """Gets and sends the image in an embed. Used by the pride commands.""" + try: + file = await in_thread(self.process_image, image_bytes, pixels, flag) + except UnidentifiedImageError: + ctx.send("Cannot identify image from provided URL") + return + + embed = discord.Embed( + name="Your Lovely Pride Avatar", + description=f"Here is your lovely avatar, surrounded by\n a beautiful {option} flag. Enjoy :D" + ) + embed.set_image(url="attachment://pride_avatar.png") + embed.set_footer(text=f"Made by {ctx.author.display_name}", icon_url=ctx.author.avatar_url) + await ctx.send(file=file, embed=embed) + + @commands.max_concurrency(1, commands.BucketType.guild, wait=True) + @commands.group() + async def pfp_modify(self, ctx: commands.Context) -> None: + """Groups all of the pfp modifing commands to allow a single concurrency limit.""" + if not ctx.invoked_subcommand: + await ctx.send_help(ctx.command) + + @pfp_modify.command(name="8bitify") + async def eightbit_command(self, ctx: commands.Context) -> None: + """Pixelates your avatar and changes the palette to an 8bit one.""" + async with ctx.typing(): + image_bytes = await ctx.author.avatar_url.read() + avatar = Image.open(BytesIO(image_bytes)) + avatar = avatar.convert("RGBA").resize((1024, 1024)) + + # Pixilate and quantize + eightbit = avatar.resize((32, 32), resample=Image.NEAREST).resize((1024, 1024), resample=Image.NEAREST) + eightbit = eightbit.quantize() + + bufferedio = BytesIO() + eightbit.save(bufferedio, format="PNG") + bufferedio.seek(0) + + file = discord.File(bufferedio, filename="8bitavatar.png") + + embed = discord.Embed( + title="Your 8-bit avatar", + description='Here is your avatar. I think it looks all cool and "retro"' + ) + + embed.set_image(url="attachment://8bitavatar.png") + embed.set_footer(text=f"Made by {ctx.author.display_name}", icon_url=ctx.author.avatar_url) + + await ctx.send(file=file, embed=embed) + + @pfp_modify.command(pass_context=True, aliases=["easterify"]) + async def avatareasterify(self, ctx: commands.Context, *colours: t.Union[discord.Colour, str]) -> None: + """ + This "Easterifies" the user's avatar. + + Given colours will produce a personalised egg in the corner, similar to the egg_decorate command. + If colours are not given, a nice little chocolate bunny will sit in the corner. + Colours are split by spaces, unless you wrap the colour name in double quotes. + Discord colour names, HTML colour names, XKCD colour names and hex values are accepted. + """ + async def send(*args, **kwargs) -> str: + """ + This replaces the original ctx.send. + + When invoking the egg decorating command, the egg itself doesn't print to to the channel. + Returns the message content so that if any errors occur, the error message can be output. + """ + if args: + return args[0] + + async with ctx.typing(): + + # Grabs image of avatar + image_bytes = await ctx.author.avatar_url_as(size=256).read() + + old = Image.open(BytesIO(image_bytes)) + old = old.convert("RGBA") + + # Grabs alpha channel since posterize can't be used with an RGBA image. + alpha = old.getchannel("A").getdata() + old = old.convert("RGB") + old = ImageOps.posterize(old, 6) + + data = old.getdata() + setted_data = set(data) + new_d = {} + + for x in setted_data: + new_d[x] = self.closest(x) + await asyncio.sleep(0) # Ensures discord doesn't break in the background. + new_data = [(*new_d[x], alpha[i]) if x in new_d else x for i, x in enumerate(data)] + + im = Image.new("RGBA", old.size) + im.putdata(new_data) + + if colours: + send_message = ctx.send + ctx.send = send # Assigns ctx.send to a fake send + egg = await ctx.invoke(self.bot.get_command("eggdecorate"), *colours) + if isinstance(egg, str): # When an error message occurs in eggdecorate. + return await send_message(egg) + + ratio = 64 / egg.height + egg = egg.resize((round(egg.width * ratio), round(egg.height * ratio))) + egg = egg.convert("RGBA") + im.alpha_composite(egg, (im.width - egg.width, (im.height - egg.height)//2)) # Right centre. + ctx.send = send_message # Reassigns ctx.send + else: + bunny = Image.open(Path("bot/resources/easter/chocolate_bunny.png")) + im.alpha_composite(bunny, (im.width - bunny.width, (im.height - bunny.height)//2)) # Right centre. + + bufferedio = BytesIO() + im.save(bufferedio, format="PNG") + + bufferedio.seek(0) + + file = discord.File(bufferedio, filename="easterified_avatar.png") # Creates file to be used in embed + embed = discord.Embed( + name="Your Lovely Easterified Avatar", + description="Here is your lovely avatar, all bright and colourful\nwith Easter pastel colours. Enjoy :D" + ) + embed.set_image(url="attachment://easterified_avatar.png") + embed.set_footer(text=f"Made by {ctx.author.display_name}", icon_url=ctx.author.avatar_url) + + await ctx.send(file=file, embed=embed) + + @pfp_modify.group(aliases=["avatarpride", "pridepfp", "prideprofile"], invoke_without_command=True) + async def prideavatar(self, ctx: commands.Context, option: str = "lgbt", pixels: int = 64) -> None: + """ + This surrounds an avatar with a border of a specified LGBT flag. + + This defaults to the LGBT rainbow flag if none is given. + The amount of pixels can be given which determines the thickness of the flag border. + This has a maximum of 512px and defaults to a 64px border. + The full image is 1024x1024. + """ + option, pixels, flag = self.process_options(option, pixels) + if flag is None: + return await ctx.send("I don't have that flag!") + + async with ctx.typing(): + image_bytes = await ctx.author.avatar_url.read() + await self.send_image(ctx, image_bytes, pixels, flag, option) + + @prideavatar.command() + async def image(self, ctx: commands.Context, url: str, option: str = "lgbt", pixels: int = 64) -> None: + """ + This surrounds the image specified by the URL with a border of a specified LGBT flag. + + This defaults to the LGBT rainbow flag if none is given. + The amount of pixels can be given which determines the thickness of the flag border. + This has a maximum of 512px and defaults to a 64px border. + The full image is 1024x1024. + """ + option, pixels, flag = self.process_options(option, pixels) + if flag is None: + return await ctx.send("I don't have that flag!") + + async with ctx.typing(): + async with aiohttp.ClientSession() as session: + try: + response = await session.get(url) + except aiohttp.client_exceptions.ClientConnectorError: + return await ctx.send("Cannot connect to provided URL!") + except aiohttp.client_exceptions.InvalidURL: + return await ctx.send("Invalid URL!") + if response.status != 200: + return await ctx.send("Bad response from provided URL!") + image_bytes = await response.read() + await self.send_image(ctx, image_bytes, pixels, flag, option) + + @prideavatar.command() + async def flags(self, ctx: commands.Context) -> None: + """This lists the flags that can be used with the prideavatar command.""" + choices = sorted(set(self.GENDER_OPTIONS.values())) + options = "• " + "\n• ".join(choices) + embed = discord.Embed( + title="I have the following flags:", + description=options, + colour=Colours.soft_red + ) + + await ctx.send(embed=embed) + + @pfp_modify.command( + name='savatar', + aliases=('spookyavatar', 'spookify'), + brief='Spookify an user\'s avatar.' + ) + async def spooky_avatar(self, ctx: commands.Context, user: discord.Member = None) -> None: + """A command to print the user's spookified avatar.""" + if user is None: + user = ctx.message.author + + async with ctx.typing(): + embed = discord.Embed(colour=0xFF0000) + embed.title = "Is this you or am I just really paranoid?" + embed.set_author(name=str(user.name), icon_url=user.avatar_url) + + image_bytes = await ctx.author.avatar_url.read() + im = Image.open(BytesIO(image_bytes)) + modified_im = spookifications.get_random_effect(im) + modified_im.save(str(ctx.message.id)+'.png') + f = discord.File(str(ctx.message.id)+'.png') + embed.set_image(url='attachment://'+str(ctx.message.id)+'.png') + + await ctx.send(file=f, embed=embed) + os.remove(str(ctx.message.id)+'.png') + + +def setup(bot: commands.Bot) -> None: + """Load the PfpModify cog.""" + bot.add_cog(PfpModify(bot)) diff --git a/bot/exts/halloween/spookyavatar.py b/bot/exts/halloween/spookyavatar.py deleted file mode 100644 index 2d7df678..00000000 --- a/bot/exts/halloween/spookyavatar.py +++ /dev/null @@ -1,52 +0,0 @@ -import logging -import os -from io import BytesIO - -import aiohttp -import discord -from PIL import Image -from discord.ext import commands - -from bot.utils.halloween import spookifications - -log = logging.getLogger(__name__) - - -class SpookyAvatar(commands.Cog): - """A cog that spookifies an avatar.""" - - def __init__(self, bot: commands.Bot): - self.bot = bot - - async def get(self, url: str) -> bytes: - """Returns the contents of the supplied URL.""" - async with aiohttp.ClientSession() as session: - async with session.get(url) as resp: - return await resp.read() - - @commands.command(name='savatar', aliases=('spookyavatar', 'spookify'), - brief='Spookify an user\'s avatar.') - async def spooky_avatar(self, ctx: commands.Context, user: discord.Member = None) -> None: - """A command to print the user's spookified avatar.""" - if user is None: - user = ctx.message.author - - async with ctx.typing(): - embed = discord.Embed(colour=0xFF0000) - embed.title = "Is this you or am I just really paranoid?" - embed.set_author(name=str(user.name), icon_url=user.avatar_url) - - image_bytes = await ctx.author.avatar_url.read() - im = Image.open(BytesIO(image_bytes)) - modified_im = spookifications.get_random_effect(im) - modified_im.save(str(ctx.message.id)+'.png') - f = discord.File(str(ctx.message.id)+'.png') - embed.set_image(url='attachment://'+str(ctx.message.id)+'.png') - - await ctx.send(file=f, embed=embed) - os.remove(str(ctx.message.id)+'.png') - - -def setup(bot: commands.Bot) -> None: - """Spooky avatar Cog load.""" - bot.add_cog(SpookyAvatar(bot)) diff --git a/bot/exts/pride/pride_avatar.py b/bot/exts/pride/pride_avatar.py deleted file mode 100644 index 2eade796..00000000 --- a/bot/exts/pride/pride_avatar.py +++ /dev/null @@ -1,177 +0,0 @@ -import logging -from io import BytesIO -from pathlib import Path -from typing import Tuple - -import aiohttp -import discord -from PIL import Image, ImageDraw, UnidentifiedImageError -from discord.ext.commands import Bot, Cog, Context, group - -from bot.constants import Colours - -log = logging.getLogger(__name__) - -OPTIONS = { - "agender": "agender", - "androgyne": "androgyne", - "androgynous": "androgyne", - "aromantic": "aromantic", - "aro": "aromantic", - "ace": "asexual", - "asexual": "asexual", - "bigender": "bigender", - "bisexual": "bisexual", - "bi": "bisexual", - "demiboy": "demiboy", - "demigirl": "demigirl", - "demi": "demisexual", - "demisexual": "demisexual", - "gay": "gay", - "lgbt": "gay", - "queer": "gay", - "homosexual": "gay", - "fluid": "genderfluid", - "genderfluid": "genderfluid", - "genderqueer": "genderqueer", - "intersex": "intersex", - "lesbian": "lesbian", - "non-binary": "nonbinary", - "enby": "nonbinary", - "nb": "nonbinary", - "nonbinary": "nonbinary", - "omnisexual": "omnisexual", - "omni": "omnisexual", - "pansexual": "pansexual", - "pan": "pansexual", - "pangender": "pangender", - "poly": "polysexual", - "polysexual": "polysexual", - "polyamory": "polyamory", - "polyamorous": "polyamory", - "transgender": "transgender", - "trans": "transgender", - "trigender": "trigender" -} - - -class PrideAvatar(Cog): - """Put an LGBT spin on your avatar!""" - - def __init__(self, bot: Bot): - self.bot = bot - - @staticmethod - def crop_avatar(avatar: Image) -> Image: - """This crops the avatar into a circle.""" - mask = Image.new("L", avatar.size, 0) - draw = ImageDraw.Draw(mask) - draw.ellipse((0, 0) + avatar.size, fill=255) - avatar.putalpha(mask) - return avatar - - @staticmethod - def crop_ring(ring: Image, px: int) -> Image: - """This crops the ring into a circle.""" - mask = Image.new("L", ring.size, 0) - draw = ImageDraw.Draw(mask) - draw.ellipse((0, 0) + ring.size, fill=255) - draw.ellipse((px, px, 1024-px, 1024-px), fill=0) - ring.putalpha(mask) - return ring - - @staticmethod - def process_options(option: str, pixels: int) -> Tuple[str, int, str]: - """Does some shared preprocessing for the prideavatar commands.""" - return option.lower(), max(0, min(512, pixels)), OPTIONS.get(option) - - async def process_image(self, ctx: Context, image_bytes: bytes, pixels: int, flag: str, option: str) -> None: - """Constructs the final image, embeds it, and sends it.""" - try: - avatar = Image.open(BytesIO(image_bytes)) - except UnidentifiedImageError: - return await ctx.send("Cannot identify image from provided URL") - avatar = avatar.convert("RGBA").resize((1024, 1024)) - - avatar = self.crop_avatar(avatar) - - ring = Image.open(Path(f"bot/resources/pride/flags/{flag}.png")).resize((1024, 1024)) - ring = ring.convert("RGBA") - ring = self.crop_ring(ring, pixels) - - avatar.alpha_composite(ring, (0, 0)) - bufferedio = BytesIO() - avatar.save(bufferedio, format="PNG") - bufferedio.seek(0) - - file = discord.File(bufferedio, filename="pride_avatar.png") # Creates file to be used in embed - embed = discord.Embed( - name="Your Lovely Pride Avatar", - description=f"Here is your lovely avatar, surrounded by\n a beautiful {option} flag. Enjoy :D" - ) - embed.set_image(url="attachment://pride_avatar.png") - embed.set_footer(text=f"Made by {ctx.author.display_name}", icon_url=ctx.author.avatar_url) - await ctx.send(file=file, embed=embed) - - @group(aliases=["avatarpride", "pridepfp", "prideprofile"], invoke_without_command=True) - async def prideavatar(self, ctx: Context, option: str = "lgbt", pixels: int = 64) -> None: - """ - This surrounds an avatar with a border of a specified LGBT flag. - - This defaults to the LGBT rainbow flag if none is given. - The amount of pixels can be given which determines the thickness of the flag border. - This has a maximum of 512px and defaults to a 64px border. - The full image is 1024x1024. - """ - option, pixels, flag = self.process_options(option, pixels) - if flag is None: - return await ctx.send("I don't have that flag!") - - async with ctx.typing(): - image_bytes = await ctx.author.avatar_url.read() - await self.process_image(ctx, image_bytes, pixels, flag, option) - - @prideavatar.command() - async def image(self, ctx: Context, url: str, option: str = "lgbt", pixels: int = 64) -> None: - """ - This surrounds the image specified by the URL with a border of a specified LGBT flag. - - This defaults to the LGBT rainbow flag if none is given. - The amount of pixels can be given which determines the thickness of the flag border. - This has a maximum of 512px and defaults to a 64px border. - The full image is 1024x1024. - """ - option, pixels, flag = self.process_options(option, pixels) - if flag is None: - return await ctx.send("I don't have that flag!") - - async with ctx.typing(): - async with aiohttp.ClientSession() as session: - try: - response = await session.get(url) - except aiohttp.client_exceptions.ClientConnectorError: - return await ctx.send("Cannot connect to provided URL!") - except aiohttp.client_exceptions.InvalidURL: - return await ctx.send("Invalid URL!") - if response.status != 200: - return await ctx.send("Bad response from provided URL!") - image_bytes = await response.read() - await self.process_image(ctx, image_bytes, pixels, flag, option) - - @prideavatar.command() - async def flags(self, ctx: Context) -> None: - """This lists the flags that can be used with the prideavatar command.""" - choices = sorted(set(OPTIONS.values())) - options = "• " + "\n• ".join(choices) - embed = discord.Embed( - title="I have the following flags:", - description=options, - colour=Colours.soft_red - ) - - await ctx.send(embed=embed) - - -def setup(bot: Bot) -> None: - """Cog load.""" - bot.add_cog(PrideAvatar(bot)) -- cgit v1.2.3 From e79a7730a3d8151622670f9ba38d05f2b267e2ec Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 18 Feb 2021 19:32:14 +0000 Subject: Log what func is being ran in the executor. --- bot/exts/evergreen/pfp_modify.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/pfp_modify.py b/bot/exts/evergreen/pfp_modify.py index f356d2f6..4245432e 100644 --- a/bot/exts/evergreen/pfp_modify.py +++ b/bot/exts/evergreen/pfp_modify.py @@ -29,6 +29,7 @@ _EXECUTOR = ThreadPoolExecutor(10) async def in_thread(func: t.Callable, *args) -> asyncio.Future: """Allows non-async functions to work in async functions.""" + log.trace(f"Running {func.__name__} in an executor.") loop = asyncio.get_event_loop() return await loop.run_in_executor(_EXECUTOR, func, *args) @@ -138,7 +139,6 @@ class PfpModify(commands.Cog): await ctx.send(file=file, embed=embed) @commands.max_concurrency(1, commands.BucketType.guild, wait=True) - @commands.group() async def pfp_modify(self, ctx: commands.Context) -> None: """Groups all of the pfp modifing commands to allow a single concurrency limit.""" if not ctx.invoked_subcommand: -- cgit v1.2.3 From 032d4ae8300ed4570e0b471dd49f628f446cb1fa Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 18 Feb 2021 19:43:26 +0000 Subject: Add root alias support for commands --- bot/__init__.py | 9 +++++++++ bot/bot.py | 41 ++++++++++++++++++++++++++++++++++++++++ bot/command.py | 18 ++++++++++++++++++ bot/exts/evergreen/help.py | 4 +++- bot/exts/evergreen/pfp_modify.py | 3 ++- 5 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 bot/command.py (limited to 'bot') diff --git a/bot/__init__.py b/bot/__init__.py index bdb18666..c8550537 100644 --- a/bot/__init__.py +++ b/bot/__init__.py @@ -2,10 +2,13 @@ import asyncio import logging import logging.handlers import os +from functools import partial, partialmethod from pathlib import Path import arrow +from discord.ext import commands +from bot.command import Command from bot.constants import Client @@ -70,3 +73,9 @@ logging.getLogger().info('Logging initialization complete') # On Windows, the selector event loop is required for aiodns. if os.name == "nt": asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) + + +# Monkey-patch discord.py decorators to use the Command subclass which supports root aliases. +# Must be patched before any cogs are added. +commands.command = partial(commands.command, cls=Command) +commands.GroupMixin.command = partialmethod(commands.GroupMixin.command, cls=Command) diff --git a/bot/bot.py b/bot/bot.py index 97b09243..f51139bb 100644 --- a/bot/bot.py +++ b/bot/bot.py @@ -64,6 +64,26 @@ class Bot(commands.Bot): super().add_cog(cog) log.info(f"Cog loaded: {cog.qualified_name}") + def add_command(self, command: commands.Command) -> None: + """Add `command` as normal and then add its root aliases to the bot.""" + super().add_command(command) + self._add_root_aliases(command) + + def remove_command(self, name: str) -> Optional[commands.Command]: + """ + Remove a command/alias as normal and then remove its root aliases from the bot. + + Individual root aliases cannot be removed by this function. + To remove them, either remove the entire command or manually edit `bot.all_commands`. + """ + command = super().remove_command(name) + if command is None: + # Even if it's a root alias, there's no way to get the Bot instance to remove the alias. + return + + self._remove_root_aliases(command) + return command + async def on_command_error(self, context: commands.Context, exception: DiscordException) -> None: """Check command errors for UserInputError and reset the cooldown if thrown.""" if isinstance(exception, commands.UserInputError): @@ -124,6 +144,27 @@ class Bot(commands.Bot): """ await self._guild_available.wait() + def _add_root_aliases(self, command: commands.Command) -> None: + """Recursively add root aliases for `command` and any of its subcommands.""" + if isinstance(command, commands.Group): + for subcommand in command.commands: + self._add_root_aliases(subcommand) + + for alias in getattr(command, "root_aliases", ()): + if alias in self.all_commands: + raise commands.CommandRegistrationError(alias, alias_conflict=True) + + self.all_commands[alias] = command + + def _remove_root_aliases(self, command: commands.Command) -> None: + """Recursively remove root aliases for `command` and any of its subcommands.""" + if isinstance(command, commands.Group): + for subcommand in command.commands: + self._remove_root_aliases(subcommand) + + for alias in getattr(command, "root_aliases", ()): + self.all_commands.pop(alias, None) + _allowed_roles = [discord.Object(id_) for id_ in constants.MODERATION_ROLES] diff --git a/bot/command.py b/bot/command.py new file mode 100644 index 00000000..0fb900f7 --- /dev/null +++ b/bot/command.py @@ -0,0 +1,18 @@ +from discord.ext import commands + + +class Command(commands.Command): + """ + A `discord.ext.commands.Command` subclass which supports root aliases. + + A `root_aliases` keyword argument is added, which is a sequence of alias names that will act as + top-level commands rather than being aliases of the command's group. It's stored as an attribute + also named `root_aliases`. + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.root_aliases = kwargs.get("root_aliases", []) + + if not isinstance(self.root_aliases, (list, tuple)): + raise TypeError("Root aliases of a command must be a list or a tuple of strings.") diff --git a/bot/exts/evergreen/help.py b/bot/exts/evergreen/help.py index 91147243..f557e42e 100644 --- a/bot/exts/evergreen/help.py +++ b/bot/exts/evergreen/help.py @@ -289,7 +289,9 @@ class HelpSession: parent = self.query.full_parent_name + ' ' if self.query.parent else '' paginator.add_line(f'**```{prefix}{parent}{signature}```**') - aliases = ', '.join(f'`{a}`' for a in self.query.aliases) + aliases = [f"`{alias}`" if not parent else f"`{parent} {alias}`" for alias in self.query.aliases] + aliases += [f"`{alias}`" for alias in getattr(self.query, "root_aliases", ())] + aliases = ", ".join(sorted(aliases)) if aliases: paginator.add_line(f'**Can also use:** {aliases}\n') diff --git a/bot/exts/evergreen/pfp_modify.py b/bot/exts/evergreen/pfp_modify.py index 4245432e..a3f7e3f8 100644 --- a/bot/exts/evergreen/pfp_modify.py +++ b/bot/exts/evergreen/pfp_modify.py @@ -139,12 +139,13 @@ class PfpModify(commands.Cog): await ctx.send(file=file, embed=embed) @commands.max_concurrency(1, commands.BucketType.guild, wait=True) + @commands.group() async def pfp_modify(self, ctx: commands.Context) -> None: """Groups all of the pfp modifing commands to allow a single concurrency limit.""" if not ctx.invoked_subcommand: await ctx.send_help(ctx.command) - @pfp_modify.command(name="8bitify") + @pfp_modify.command(name="8bitify", root_aliases=("8bitify",)) async def eightbit_command(self, ctx: commands.Context) -> None: """Pixelates your avatar and changes the palette to an 8bit one.""" async with ctx.typing(): -- cgit v1.2.3 From c7de5a2577b4f2975b3fab683f2d7459c5bda636 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 18 Feb 2021 22:04:27 +0000 Subject: Extend root aliases to support commands.Group --- bot/__init__.py | 6 +++++- bot/exts/evergreen/pfp_modify.py | 9 +++++++-- bot/group.py | 18 ++++++++++++++++++ 3 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 bot/group.py (limited to 'bot') diff --git a/bot/__init__.py b/bot/__init__.py index c8550537..d0992912 100644 --- a/bot/__init__.py +++ b/bot/__init__.py @@ -10,6 +10,7 @@ from discord.ext import commands from bot.command import Command from bot.constants import Client +from bot.group import Group # Configure the "TRACE" logging level (e.g. "log.trace(message)") @@ -75,7 +76,10 @@ if os.name == "nt": asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) -# Monkey-patch discord.py decorators to use the Command subclass which supports root aliases. +# Monkey-patch discord.py decorators to use the both the Command and Group subclasses which supports root aliases. # Must be patched before any cogs are added. commands.command = partial(commands.command, cls=Command) commands.GroupMixin.command = partialmethod(commands.GroupMixin.command, cls=Command) + +commands.group = partial(commands.group, cls=Group) +commands.GroupMixin.group = partialmethod(commands.GroupMixin.group, cls=Group) diff --git a/bot/exts/evergreen/pfp_modify.py b/bot/exts/evergreen/pfp_modify.py index a3f7e3f8..8a3eb77c 100644 --- a/bot/exts/evergreen/pfp_modify.py +++ b/bot/exts/evergreen/pfp_modify.py @@ -173,7 +173,7 @@ class PfpModify(commands.Cog): await ctx.send(file=file, embed=embed) - @pfp_modify.command(pass_context=True, aliases=["easterify"]) + @pfp_modify.command(pass_context=True, aliases=["easterify"], root_aliases=("easterify", "avatareasterify")) async def avatareasterify(self, ctx: commands.Context, *colours: t.Union[discord.Colour, str]) -> None: """ This "Easterifies" the user's avatar. @@ -249,7 +249,11 @@ class PfpModify(commands.Cog): await ctx.send(file=file, embed=embed) - @pfp_modify.group(aliases=["avatarpride", "pridepfp", "prideprofile"], invoke_without_command=True) + @pfp_modify.group( + aliases=["avatarpride", "pridepfp", "prideprofile"], + root_aliases=("prideavatar", "avatarpride", "pridepfp", "prideprofile"), + invoke_without_command=True + ) async def prideavatar(self, ctx: commands.Context, option: str = "lgbt", pixels: int = 64) -> None: """ This surrounds an avatar with a border of a specified LGBT flag. @@ -310,6 +314,7 @@ class PfpModify(commands.Cog): @pfp_modify.command( name='savatar', aliases=('spookyavatar', 'spookify'), + root_aliases=('spookyavatar', 'spookify'), brief='Spookify an user\'s avatar.' ) async def spooky_avatar(self, ctx: commands.Context, user: discord.Member = None) -> None: diff --git a/bot/group.py b/bot/group.py new file mode 100644 index 00000000..77092adf --- /dev/null +++ b/bot/group.py @@ -0,0 +1,18 @@ +from discord.ext import commands + + +class Group(commands.Group): + """ + A `discord.ext.commands.Group` subclass which supports root aliases. + + A `root_aliases` keyword argument is added, which is a sequence of alias names that will act as + top-level commands rather than being aliases of the command's group. It's stored as an attribute + also named `root_aliases`. + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.root_aliases = kwargs.get("root_aliases", []) + + if not isinstance(self.root_aliases, (list, tuple)): + raise TypeError("Root aliases of a command must be a list or a tuple of strings.") -- cgit v1.2.3 From a3a6550b80543c3ce7da28cf00b450b561e57ca9 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 19 Feb 2021 23:25:08 +0000 Subject: Share common code. --- bot/exts/evergreen/pfp_modify.py | 239 ++++++++++++++++++++------------------- 1 file changed, 122 insertions(+), 117 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/pfp_modify.py b/bot/exts/evergreen/pfp_modify.py index 8a3eb77c..5fb3b89d 100644 --- a/bot/exts/evergreen/pfp_modify.py +++ b/bot/exts/evergreen/pfp_modify.py @@ -1,16 +1,15 @@ import asyncio import json import logging -import os import typing as t from concurrent.futures import ThreadPoolExecutor from io import BytesIO from pathlib import Path import aiofiles -import aiohttp import discord -from PIL import Image, ImageDraw, ImageOps, UnidentifiedImageError +from PIL import Image, ImageDraw, ImageOps +from aiohttp import client_exceptions from discord.ext import commands from bot.constants import Colours @@ -69,7 +68,7 @@ class PfpModify(commands.Cog): return (r, g, b) @staticmethod - def crop_avatar(avatar: Image) -> Image: + def crop_avatar_circle(avatar: Image) -> Image: """This crops the avatar given into a circle.""" mask = Image.new("L", avatar.size, 0) draw = ImageDraw.Draw(mask) @@ -87,58 +86,71 @@ class PfpModify(commands.Cog): ring.putalpha(mask) return ring - def process_options(self, option: str, pixels: int) -> t.Tuple[str, int, str]: - """Does some shared preprocessing for the prideavatar commands.""" - return option.lower(), max(0, min(512, pixels)), self.GENDER_OPTIONS.get(option) + @staticmethod + def _apply_effect(image_bytes: bytes, effect: t.Callable, *args) -> discord.File: + im = Image.open(BytesIO(image_bytes)) + im = im.convert("RGBA") + im = effect(im, *args) - def process_image( - self, - image_bytes: bytes, + bufferedio = BytesIO() + im.save(bufferedio, format="PNG") + bufferedio.seek(0) + + return discord.File(bufferedio, filename="modified_avatar.png") + + @staticmethod + def pridify_effect( + image: Image, pixels: int, flag: str - ) -> discord.File: - """Constructs and returns the final image. Used by the pride commands.""" - # This line can raise UnidentifiedImageError and must be handled by the calling func. - avatar = Image.open(BytesIO(image_bytes)) - avatar = avatar.convert("RGBA").resize((1024, 1024)) - - avatar = self.crop_avatar(avatar) + ) -> Image: + """Applies the pride effect to the given image.""" + image = image.resize((1024, 1024)) + image = PfpModify.crop_avatar_circle(image) ring = Image.open(Path(f"bot/resources/pride/flags/{flag}.png")).resize((1024, 1024)) ring = ring.convert("RGBA") - ring = self.crop_ring(ring, pixels) - - avatar.alpha_composite(ring, (0, 0)) - bufferedio = BytesIO() - avatar.save(bufferedio, format="PNG") - bufferedio.seek(0) + ring = PfpModify.crop_ring(ring, pixels) - return discord.File(bufferedio, filename="pride_avatar.png") # Creates file to be used in embed + image.alpha_composite(ring, (0, 0)) + return image - async def send_image( - self, - ctx: commands.Context, - image_bytes: bytes, - pixels: int, - flag: str, - option: str - ) -> None: - """Gets and sends the image in an embed. Used by the pride commands.""" - try: - file = await in_thread(self.process_image, image_bytes, pixels, flag) - except UnidentifiedImageError: - ctx.send("Cannot identify image from provided URL") - return + @staticmethod + def eight_bitify_effect(image: Image) -> Image: + """Applies the 8bit effect to the given image.""" + image = image.resize((32, 32), resample=Image.NEAREST).resize((1024, 1024), resample=Image.NEAREST) + return image.quantize() - embed = discord.Embed( - name="Your Lovely Pride Avatar", - description=f"Here is your lovely avatar, surrounded by\n a beautiful {option} flag. Enjoy :D" - ) - embed.set_image(url="attachment://pride_avatar.png") - embed.set_footer(text=f"Made by {ctx.author.display_name}", icon_url=ctx.author.avatar_url) - await ctx.send(file=file, embed=embed) + @staticmethod + def easterify_effect(image: Image, overlay_image: Image = None) -> Image: + """Applies the easter effect to the given image.""" + if overlay_image: + ratio = 64 / overlay_image.height + overlay_image = overlay_image.resize(( + round(overlay_image.width * ratio), + round(overlay_image.height * ratio) + )) + overlay_image = overlay_image.convert("RGBA") + else: + overlay_image = overlay_image = Image.open(Path("bot/resources/easter/chocolate_bunny.png")) + + alpha = image.getchannel("A").getdata() + image = image.convert("RGB") + image = ImageOps.posterize(image, 6) + + data = image.getdata() + setted_data = set(data) + new_d = {} + + for x in setted_data: + new_d[x] = PfpModify.closest(x) + new_data = [(*new_d[x], alpha[i]) if x in new_d else x for i, x in enumerate(data)] + + im = Image.new("RGBA", image.size) + im.putdata(new_data) + im.alpha_composite(overlay_image, (im.width - overlay_image.width, (im.height - overlay_image.height)//2)) + return im - @commands.max_concurrency(1, commands.BucketType.guild, wait=True) @commands.group() async def pfp_modify(self, ctx: commands.Context) -> None: """Groups all of the pfp modifing commands to allow a single concurrency limit.""" @@ -150,25 +162,18 @@ class PfpModify(commands.Cog): """Pixelates your avatar and changes the palette to an 8bit one.""" async with ctx.typing(): image_bytes = await ctx.author.avatar_url.read() - avatar = Image.open(BytesIO(image_bytes)) - avatar = avatar.convert("RGBA").resize((1024, 1024)) - - # Pixilate and quantize - eightbit = avatar.resize((32, 32), resample=Image.NEAREST).resize((1024, 1024), resample=Image.NEAREST) - eightbit = eightbit.quantize() - - bufferedio = BytesIO() - eightbit.save(bufferedio, format="PNG") - bufferedio.seek(0) - - file = discord.File(bufferedio, filename="8bitavatar.png") + file = await in_thread( + self._apply_effect, + image_bytes, + self.eight_bitify_effect + ) embed = discord.Embed( title="Your 8-bit avatar", description='Here is your avatar. I think it looks all cool and "retro"' ) - embed.set_image(url="attachment://8bitavatar.png") + embed.set_image(url="attachment://modified_avatar.png") embed.set_footer(text=f"Made by {ctx.author.display_name}", icon_url=ctx.author.avatar_url) await ctx.send(file=file, embed=embed) @@ -194,61 +199,59 @@ class PfpModify(commands.Cog): return args[0] async with ctx.typing(): - - # Grabs image of avatar - image_bytes = await ctx.author.avatar_url_as(size=256).read() - - old = Image.open(BytesIO(image_bytes)) - old = old.convert("RGBA") - - # Grabs alpha channel since posterize can't be used with an RGBA image. - alpha = old.getchannel("A").getdata() - old = old.convert("RGB") - old = ImageOps.posterize(old, 6) - - data = old.getdata() - setted_data = set(data) - new_d = {} - - for x in setted_data: - new_d[x] = self.closest(x) - await asyncio.sleep(0) # Ensures discord doesn't break in the background. - new_data = [(*new_d[x], alpha[i]) if x in new_d else x for i, x in enumerate(data)] - - im = Image.new("RGBA", old.size) - im.putdata(new_data) - + egg = None if colours: send_message = ctx.send ctx.send = send # Assigns ctx.send to a fake send egg = await ctx.invoke(self.bot.get_command("eggdecorate"), *colours) if isinstance(egg, str): # When an error message occurs in eggdecorate. - return await send_message(egg) - - ratio = 64 / egg.height - egg = egg.resize((round(egg.width * ratio), round(egg.height * ratio))) - egg = egg.convert("RGBA") - im.alpha_composite(egg, (im.width - egg.width, (im.height - egg.height)//2)) # Right centre. + await send_message(egg) + return ctx.send = send_message # Reassigns ctx.send - else: - bunny = Image.open(Path("bot/resources/easter/chocolate_bunny.png")) - im.alpha_composite(bunny, (im.width - bunny.width, (im.height - bunny.height)//2)) # Right centre. - bufferedio = BytesIO() - im.save(bufferedio, format="PNG") - - bufferedio.seek(0) + image_bytes = await ctx.author.avatar_url_as(size=256).read() + file = await in_thread( + self._apply_effect, + image_bytes, + self.easterify_effect, + egg + ) - file = discord.File(bufferedio, filename="easterified_avatar.png") # Creates file to be used in embed embed = discord.Embed( name="Your Lovely Easterified Avatar", description="Here is your lovely avatar, all bright and colourful\nwith Easter pastel colours. Enjoy :D" ) - embed.set_image(url="attachment://easterified_avatar.png") + embed.set_image(url="attachment://modified_avatar.png") embed.set_footer(text=f"Made by {ctx.author.display_name}", icon_url=ctx.author.avatar_url) await ctx.send(file=file, embed=embed) + async def send_pride_image( + self, + ctx: commands.Context, + image_bytes: bytes, + pixels: int, + flag: str, + option: str + ) -> None: + """Gets and sends the image in an embed. Used by the pride commands.""" + async with ctx.typing(): + file = await in_thread( + self._apply_effect, + image_bytes, + self.pridify_effect, + pixels, + flag + ) + + embed = discord.Embed( + name="Your Lovely Pride Avatar", + description=f"Here is your lovely avatar, surrounded by\n a beautiful {option} flag. Enjoy :D" + ) + embed.set_image(url="attachment://modified_avatar.png") + embed.set_footer(text=f"Made by {ctx.author.display_name}", icon_url=ctx.author.avatar_url) + await ctx.send(file=file, embed=embed) + @pfp_modify.group( aliases=["avatarpride", "pridepfp", "prideprofile"], root_aliases=("prideavatar", "avatarpride", "pridepfp", "prideprofile"), @@ -263,13 +266,15 @@ class PfpModify(commands.Cog): This has a maximum of 512px and defaults to a 64px border. The full image is 1024x1024. """ - option, pixels, flag = self.process_options(option, pixels) + option = option.lower() + pixels = max(0, min(512, pixels)) + flag = self.GENDER_OPTIONS.get(option) if flag is None: return await ctx.send("I don't have that flag!") async with ctx.typing(): image_bytes = await ctx.author.avatar_url.read() - await self.send_image(ctx, image_bytes, pixels, flag, option) + await self.send_pride_image(ctx, image_bytes, pixels, flag, option) @prideavatar.command() async def image(self, ctx: commands.Context, url: str, option: str = "lgbt", pixels: int = 64) -> None: @@ -281,22 +286,24 @@ class PfpModify(commands.Cog): This has a maximum of 512px and defaults to a 64px border. The full image is 1024x1024. """ - option, pixels, flag = self.process_options(option, pixels) + option = option.lower() + pixels = max(0, min(512, pixels)) + flag = self.GENDER_OPTIONS.get(option) if flag is None: return await ctx.send("I don't have that flag!") async with ctx.typing(): - async with aiohttp.ClientSession() as session: + async with self.bot.http_session as session: try: response = await session.get(url) - except aiohttp.client_exceptions.ClientConnectorError: + except client_exceptions.ClientConnectorError: return await ctx.send("Cannot connect to provided URL!") - except aiohttp.client_exceptions.InvalidURL: + except client_exceptions.InvalidURL: return await ctx.send("Invalid URL!") if response.status != 200: return await ctx.send("Bad response from provided URL!") image_bytes = await response.read() - await self.send_image(ctx, image_bytes, pixels, flag, option) + await self.send_pride_image(ctx, image_bytes, pixels, flag, option) @prideavatar.command() async def flags(self, ctx: commands.Context) -> None: @@ -308,7 +315,6 @@ class PfpModify(commands.Cog): description=options, colour=Colours.soft_red ) - await ctx.send(embed=embed) @pfp_modify.command( @@ -323,19 +329,18 @@ class PfpModify(commands.Cog): user = ctx.message.author async with ctx.typing(): - embed = discord.Embed(colour=0xFF0000) - embed.title = "Is this you or am I just really paranoid?" + image_bytes = await ctx.author.avatar_url.read() + file = await in_thread(self._apply_effect, image_bytes, spookifications.get_random_effect) + + embed = discord.Embed( + title="Is this you or am I just really paranoid?", + colour=0xFF0000 + ) embed.set_author(name=str(user.name), icon_url=user.avatar_url) + embed.set_image(url='attachment://modified_avatar.png') + embed.set_footer(text=f"Made by {ctx.author.display_name}", icon_url=ctx.author.avatar_url) - image_bytes = await ctx.author.avatar_url.read() - im = Image.open(BytesIO(image_bytes)) - modified_im = spookifications.get_random_effect(im) - modified_im.save(str(ctx.message.id)+'.png') - f = discord.File(str(ctx.message.id)+'.png') - embed.set_image(url='attachment://'+str(ctx.message.id)+'.png') - - await ctx.send(file=f, embed=embed) - os.remove(str(ctx.message.id)+'.png') + await ctx.send(file=file, embed=embed) def setup(bot: commands.Bot) -> None: -- cgit v1.2.3 From 326d50cdc039556e88e0b210a7124a4cc47a9275 Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 20 Feb 2021 15:44:47 +0000 Subject: Split effects from cog --- bot/exts/evergreen/pfp_modify.py | 348 --------------------- .../evergreen/profile_pic_modification/__init__.py | 0 .../evergreen/profile_pic_modification/_effects.py | 122 ++++++++ .../profile_pic_modification/pfp_modify.py | 234 ++++++++++++++ 4 files changed, 356 insertions(+), 348 deletions(-) delete mode 100644 bot/exts/evergreen/pfp_modify.py create mode 100644 bot/exts/evergreen/profile_pic_modification/__init__.py create mode 100644 bot/exts/evergreen/profile_pic_modification/_effects.py create mode 100644 bot/exts/evergreen/profile_pic_modification/pfp_modify.py (limited to 'bot') diff --git a/bot/exts/evergreen/pfp_modify.py b/bot/exts/evergreen/pfp_modify.py deleted file mode 100644 index 5fb3b89d..00000000 --- a/bot/exts/evergreen/pfp_modify.py +++ /dev/null @@ -1,348 +0,0 @@ -import asyncio -import json -import logging -import typing as t -from concurrent.futures import ThreadPoolExecutor -from io import BytesIO -from pathlib import Path - -import aiofiles -import discord -from PIL import Image, ImageDraw, ImageOps -from aiohttp import client_exceptions -from discord.ext import commands - -from bot.constants import Colours -from bot.utils.halloween import spookifications - -log = logging.getLogger(__name__) - -EASTER_COLOURS = [ - (255, 247, 0), (255, 255, 224), (0, 255, 127), (189, 252, 201), (255, 192, 203), - (255, 160, 122), (181, 115, 220), (221, 160, 221), (200, 162, 200), (238, 130, 238), - (135, 206, 235), (0, 204, 204), (64, 224, 208) -] # Pastel colours - Easter-like - -_EXECUTOR = ThreadPoolExecutor(10) - - -async def in_thread(func: t.Callable, *args) -> asyncio.Future: - """Allows non-async functions to work in async functions.""" - log.trace(f"Running {func.__name__} in an executor.") - loop = asyncio.get_event_loop() - return await loop.run_in_executor(_EXECUTOR, func, *args) - - -class PfpModify(commands.Cog): - """Various commands for users to change their own profile picture.""" - - def __init__(self, bot: commands.Bot) -> None: - self.bot = bot - self.bot.loop.create_task(self.init_cog()) - - async def init_cog(self) -> None: - """Initial load from resources asynchronously.""" - async with aiofiles.open('bot/resources/pride/gender_options.json') as f: - self.GENDER_OPTIONS = json.loads(await f.read()) - - @staticmethod - def closest(x: t.Tuple[int, int, int]) -> t.Tuple[int, int, int]: - """ - Finds the closest easter colour to a given pixel. - - Returns a merge between the original colour and the closest colour - """ - r1, g1, b1 = x - - def distance(point: t.Tuple[int, int, int]) -> t.Tuple[int, int, int]: - """Finds the difference between a pastel colour and the original pixel colour.""" - r2, g2, b2 = point - return ((r1 - r2)**2 + (g1 - g2)**2 + (b1 - b2)**2) - - closest_colours = sorted(EASTER_COLOURS, key=lambda point: distance(point)) - r2, g2, b2 = closest_colours[0] - r = (r1 + r2) // 2 - g = (g1 + g2) // 2 - b = (b1 + b2) // 2 - - return (r, g, b) - - @staticmethod - def crop_avatar_circle(avatar: Image) -> Image: - """This crops the avatar given into a circle.""" - mask = Image.new("L", avatar.size, 0) - draw = ImageDraw.Draw(mask) - draw.ellipse((0, 0) + avatar.size, fill=255) - avatar.putalpha(mask) - return avatar - - @staticmethod - def crop_ring(ring: Image, px: int) -> Image: - """This crops the given ring into a circle.""" - mask = Image.new("L", ring.size, 0) - draw = ImageDraw.Draw(mask) - draw.ellipse((0, 0) + ring.size, fill=255) - draw.ellipse((px, px, 1024-px, 1024-px), fill=0) - ring.putalpha(mask) - return ring - - @staticmethod - def _apply_effect(image_bytes: bytes, effect: t.Callable, *args) -> discord.File: - im = Image.open(BytesIO(image_bytes)) - im = im.convert("RGBA") - im = effect(im, *args) - - bufferedio = BytesIO() - im.save(bufferedio, format="PNG") - bufferedio.seek(0) - - return discord.File(bufferedio, filename="modified_avatar.png") - - @staticmethod - def pridify_effect( - image: Image, - pixels: int, - flag: str - ) -> Image: - """Applies the pride effect to the given image.""" - image = image.resize((1024, 1024)) - image = PfpModify.crop_avatar_circle(image) - - ring = Image.open(Path(f"bot/resources/pride/flags/{flag}.png")).resize((1024, 1024)) - ring = ring.convert("RGBA") - ring = PfpModify.crop_ring(ring, pixels) - - image.alpha_composite(ring, (0, 0)) - return image - - @staticmethod - def eight_bitify_effect(image: Image) -> Image: - """Applies the 8bit effect to the given image.""" - image = image.resize((32, 32), resample=Image.NEAREST).resize((1024, 1024), resample=Image.NEAREST) - return image.quantize() - - @staticmethod - def easterify_effect(image: Image, overlay_image: Image = None) -> Image: - """Applies the easter effect to the given image.""" - if overlay_image: - ratio = 64 / overlay_image.height - overlay_image = overlay_image.resize(( - round(overlay_image.width * ratio), - round(overlay_image.height * ratio) - )) - overlay_image = overlay_image.convert("RGBA") - else: - overlay_image = overlay_image = Image.open(Path("bot/resources/easter/chocolate_bunny.png")) - - alpha = image.getchannel("A").getdata() - image = image.convert("RGB") - image = ImageOps.posterize(image, 6) - - data = image.getdata() - setted_data = set(data) - new_d = {} - - for x in setted_data: - new_d[x] = PfpModify.closest(x) - new_data = [(*new_d[x], alpha[i]) if x in new_d else x for i, x in enumerate(data)] - - im = Image.new("RGBA", image.size) - im.putdata(new_data) - im.alpha_composite(overlay_image, (im.width - overlay_image.width, (im.height - overlay_image.height)//2)) - return im - - @commands.group() - async def pfp_modify(self, ctx: commands.Context) -> None: - """Groups all of the pfp modifing commands to allow a single concurrency limit.""" - if not ctx.invoked_subcommand: - await ctx.send_help(ctx.command) - - @pfp_modify.command(name="8bitify", root_aliases=("8bitify",)) - async def eightbit_command(self, ctx: commands.Context) -> None: - """Pixelates your avatar and changes the palette to an 8bit one.""" - async with ctx.typing(): - image_bytes = await ctx.author.avatar_url.read() - file = await in_thread( - self._apply_effect, - image_bytes, - self.eight_bitify_effect - ) - - embed = discord.Embed( - title="Your 8-bit avatar", - description='Here is your avatar. I think it looks all cool and "retro"' - ) - - embed.set_image(url="attachment://modified_avatar.png") - embed.set_footer(text=f"Made by {ctx.author.display_name}", icon_url=ctx.author.avatar_url) - - await ctx.send(file=file, embed=embed) - - @pfp_modify.command(pass_context=True, aliases=["easterify"], root_aliases=("easterify", "avatareasterify")) - async def avatareasterify(self, ctx: commands.Context, *colours: t.Union[discord.Colour, str]) -> None: - """ - This "Easterifies" the user's avatar. - - Given colours will produce a personalised egg in the corner, similar to the egg_decorate command. - If colours are not given, a nice little chocolate bunny will sit in the corner. - Colours are split by spaces, unless you wrap the colour name in double quotes. - Discord colour names, HTML colour names, XKCD colour names and hex values are accepted. - """ - async def send(*args, **kwargs) -> str: - """ - This replaces the original ctx.send. - - When invoking the egg decorating command, the egg itself doesn't print to to the channel. - Returns the message content so that if any errors occur, the error message can be output. - """ - if args: - return args[0] - - async with ctx.typing(): - egg = None - if colours: - send_message = ctx.send - ctx.send = send # Assigns ctx.send to a fake send - egg = await ctx.invoke(self.bot.get_command("eggdecorate"), *colours) - if isinstance(egg, str): # When an error message occurs in eggdecorate. - await send_message(egg) - return - ctx.send = send_message # Reassigns ctx.send - - image_bytes = await ctx.author.avatar_url_as(size=256).read() - file = await in_thread( - self._apply_effect, - image_bytes, - self.easterify_effect, - egg - ) - - embed = discord.Embed( - name="Your Lovely Easterified Avatar", - description="Here is your lovely avatar, all bright and colourful\nwith Easter pastel colours. Enjoy :D" - ) - embed.set_image(url="attachment://modified_avatar.png") - embed.set_footer(text=f"Made by {ctx.author.display_name}", icon_url=ctx.author.avatar_url) - - await ctx.send(file=file, embed=embed) - - async def send_pride_image( - self, - ctx: commands.Context, - image_bytes: bytes, - pixels: int, - flag: str, - option: str - ) -> None: - """Gets and sends the image in an embed. Used by the pride commands.""" - async with ctx.typing(): - file = await in_thread( - self._apply_effect, - image_bytes, - self.pridify_effect, - pixels, - flag - ) - - embed = discord.Embed( - name="Your Lovely Pride Avatar", - description=f"Here is your lovely avatar, surrounded by\n a beautiful {option} flag. Enjoy :D" - ) - embed.set_image(url="attachment://modified_avatar.png") - embed.set_footer(text=f"Made by {ctx.author.display_name}", icon_url=ctx.author.avatar_url) - await ctx.send(file=file, embed=embed) - - @pfp_modify.group( - aliases=["avatarpride", "pridepfp", "prideprofile"], - root_aliases=("prideavatar", "avatarpride", "pridepfp", "prideprofile"), - invoke_without_command=True - ) - async def prideavatar(self, ctx: commands.Context, option: str = "lgbt", pixels: int = 64) -> None: - """ - This surrounds an avatar with a border of a specified LGBT flag. - - This defaults to the LGBT rainbow flag if none is given. - The amount of pixels can be given which determines the thickness of the flag border. - This has a maximum of 512px and defaults to a 64px border. - The full image is 1024x1024. - """ - option = option.lower() - pixels = max(0, min(512, pixels)) - flag = self.GENDER_OPTIONS.get(option) - if flag is None: - return await ctx.send("I don't have that flag!") - - async with ctx.typing(): - image_bytes = await ctx.author.avatar_url.read() - await self.send_pride_image(ctx, image_bytes, pixels, flag, option) - - @prideavatar.command() - async def image(self, ctx: commands.Context, url: str, option: str = "lgbt", pixels: int = 64) -> None: - """ - This surrounds the image specified by the URL with a border of a specified LGBT flag. - - This defaults to the LGBT rainbow flag if none is given. - The amount of pixels can be given which determines the thickness of the flag border. - This has a maximum of 512px and defaults to a 64px border. - The full image is 1024x1024. - """ - option = option.lower() - pixels = max(0, min(512, pixels)) - flag = self.GENDER_OPTIONS.get(option) - if flag is None: - return await ctx.send("I don't have that flag!") - - async with ctx.typing(): - async with self.bot.http_session as session: - try: - response = await session.get(url) - except client_exceptions.ClientConnectorError: - return await ctx.send("Cannot connect to provided URL!") - except client_exceptions.InvalidURL: - return await ctx.send("Invalid URL!") - if response.status != 200: - return await ctx.send("Bad response from provided URL!") - image_bytes = await response.read() - await self.send_pride_image(ctx, image_bytes, pixels, flag, option) - - @prideavatar.command() - async def flags(self, ctx: commands.Context) -> None: - """This lists the flags that can be used with the prideavatar command.""" - choices = sorted(set(self.GENDER_OPTIONS.values())) - options = "• " + "\n• ".join(choices) - embed = discord.Embed( - title="I have the following flags:", - description=options, - colour=Colours.soft_red - ) - await ctx.send(embed=embed) - - @pfp_modify.command( - name='savatar', - aliases=('spookyavatar', 'spookify'), - root_aliases=('spookyavatar', 'spookify'), - brief='Spookify an user\'s avatar.' - ) - async def spooky_avatar(self, ctx: commands.Context, user: discord.Member = None) -> None: - """A command to print the user's spookified avatar.""" - if user is None: - user = ctx.message.author - - async with ctx.typing(): - image_bytes = await ctx.author.avatar_url.read() - file = await in_thread(self._apply_effect, image_bytes, spookifications.get_random_effect) - - embed = discord.Embed( - title="Is this you or am I just really paranoid?", - colour=0xFF0000 - ) - embed.set_author(name=str(user.name), icon_url=user.avatar_url) - embed.set_image(url='attachment://modified_avatar.png') - embed.set_footer(text=f"Made by {ctx.author.display_name}", icon_url=ctx.author.avatar_url) - - await ctx.send(file=file, embed=embed) - - -def setup(bot: commands.Bot) -> None: - """Load the PfpModify cog.""" - bot.add_cog(PfpModify(bot)) diff --git a/bot/exts/evergreen/profile_pic_modification/__init__.py b/bot/exts/evergreen/profile_pic_modification/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/bot/exts/evergreen/profile_pic_modification/_effects.py b/bot/exts/evergreen/profile_pic_modification/_effects.py new file mode 100644 index 00000000..fbe5f706 --- /dev/null +++ b/bot/exts/evergreen/profile_pic_modification/_effects.py @@ -0,0 +1,122 @@ +import typing as t +from io import BytesIO +from pathlib import Path + +import discord +from PIL import Image, ImageDraw, ImageOps + +EASTER_COLOURS = [ + (255, 247, 0), (255, 255, 224), (0, 255, 127), (189, 252, 201), (255, 192, 203), + (255, 160, 122), (181, 115, 220), (221, 160, 221), (200, 162, 200), (238, 130, 238), + (135, 206, 235), (0, 204, 204), (64, 224, 208) +] # Pastel colours - Easter-like + + +class PfpEffects(): + """Implements various image effects.""" + + @staticmethod + def closest(x: t.Tuple[int, int, int]) -> t.Tuple[int, int, int]: + """ + Finds the closest easter colour to a given pixel. + + Returns a merge between the original colour and the closest colour + """ + r1, g1, b1 = x + + def distance(point: t.Tuple[int, int, int]) -> t.Tuple[int, int, int]: + """Finds the difference between a pastel colour and the original pixel colour.""" + r2, g2, b2 = point + return ((r1 - r2)**2 + (g1 - g2)**2 + (b1 - b2)**2) + + closest_colours = sorted(EASTER_COLOURS, key=lambda point: distance(point)) + r2, g2, b2 = closest_colours[0] + r = (r1 + r2) // 2 + g = (g1 + g2) // 2 + b = (b1 + b2) // 2 + + return (r, g, b) + + @staticmethod + def crop_avatar_circle(avatar: Image) -> Image: + """This crops the avatar given into a circle.""" + mask = Image.new("L", avatar.size, 0) + draw = ImageDraw.Draw(mask) + draw.ellipse((0, 0) + avatar.size, fill=255) + avatar.putalpha(mask) + return avatar + + @staticmethod + def crop_ring(ring: Image, px: int) -> Image: + """This crops the given ring into a circle.""" + mask = Image.new("L", ring.size, 0) + draw = ImageDraw.Draw(mask) + draw.ellipse((0, 0) + ring.size, fill=255) + draw.ellipse((px, px, 1024-px, 1024-px), fill=0) + ring.putalpha(mask) + return ring + + @staticmethod + def _apply_effect(image_bytes: bytes, effect: t.Callable, *args) -> discord.File: + im = Image.open(BytesIO(image_bytes)) + im = im.convert("RGBA") + im = effect(im, *args) + + bufferedio = BytesIO() + im.save(bufferedio, format="PNG") + bufferedio.seek(0) + + return discord.File(bufferedio, filename="modified_avatar.png") + + @staticmethod + def pridify_effect( + image: Image, + pixels: int, + flag: str + ) -> Image: + """Applies the pride effect to the given image.""" + image = image.resize((1024, 1024)) + image = PfpEffects.crop_avatar_circle(image) + + ring = Image.open(Path(f"bot/resources/pride/flags/{flag}.png")).resize((1024, 1024)) + ring = ring.convert("RGBA") + ring = PfpEffects.crop_ring(ring, pixels) + + image.alpha_composite(ring, (0, 0)) + return image + + @staticmethod + def eight_bitify_effect(image: Image) -> Image: + """Applies the 8bit effect to the given image.""" + image = image.resize((32, 32), resample=Image.NEAREST).resize((1024, 1024), resample=Image.NEAREST) + return image.quantize() + + @staticmethod + def easterify_effect(image: Image, overlay_image: Image = None) -> Image: + """Applies the easter effect to the given image.""" + if overlay_image: + ratio = 64 / overlay_image.height + overlay_image = overlay_image.resize(( + round(overlay_image.width * ratio), + round(overlay_image.height * ratio) + )) + overlay_image = overlay_image.convert("RGBA") + else: + overlay_image = overlay_image = Image.open(Path("bot/resources/easter/chocolate_bunny.png")) + + alpha = image.getchannel("A").getdata() + image = image.convert("RGB") + image = ImageOps.posterize(image, 6) + + data = image.getdata() + setted_data = set(data) + new_d = {} + + for x in setted_data: + new_d[x] = PfpEffects.closest(x) + new_data = [(*new_d[x], alpha[i]) if x in new_d else x for i, x in enumerate(data)] + + im = Image.new("RGBA", image.size) + im.putdata(new_data) + im.alpha_composite(overlay_image, (im.width - overlay_image.width, (im.height - overlay_image.height)//2)) + return im diff --git a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py new file mode 100644 index 00000000..51742257 --- /dev/null +++ b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py @@ -0,0 +1,234 @@ +import asyncio +import json +import logging +import typing as t +from concurrent.futures import ThreadPoolExecutor + +import aiofiles +import discord +from aiohttp import client_exceptions +from discord.ext import commands + +from bot.constants import Colours +from bot.exts.evergreen.profile_pic_modification._effects import PfpEffects +from bot.utils.halloween import spookifications + +log = logging.getLogger(__name__) + +_EXECUTOR = ThreadPoolExecutor(10) + + +async def in_thread(func: t.Callable, *args) -> asyncio.Future: + """Allows non-async functions to work in async functions.""" + log.trace(f"Running {func.__name__} in an executor.") + loop = asyncio.get_event_loop() + return await loop.run_in_executor(_EXECUTOR, func, *args) + + +class PfpModify(commands.Cog): + """Various commands for users to change their own profile picture.""" + + def __init__(self, bot: commands.Bot) -> None: + self.bot = bot + self.bot.loop.create_task(self.init_cog()) + + async def init_cog(self) -> None: + """Initial load from resources asynchronously.""" + async with aiofiles.open('bot/resources/pride/gender_options.json') as f: + self.GENDER_OPTIONS = json.loads(await f.read()) + + @commands.group() + async def pfp_modify(self, ctx: commands.Context) -> None: + """Groups all of the pfp modifing commands to allow a single concurrency limit.""" + if not ctx.invoked_subcommand: + await ctx.send_help(ctx.command) + + @pfp_modify.command(name="8bitify", root_aliases=("8bitify",)) + async def eightbit_command(self, ctx: commands.Context) -> None: + """Pixelates your avatar and changes the palette to an 8bit one.""" + async with ctx.typing(): + image_bytes = await ctx.author.avatar_url.read() + file = await in_thread( + PfpEffects._apply_effect, + image_bytes, + PfpEffects.eight_bitify_effect + ) + + embed = discord.Embed( + title="Your 8-bit avatar", + description='Here is your avatar. I think it looks all cool and "retro"' + ) + + embed.set_image(url="attachment://modified_avatar.png") + embed.set_footer(text=f"Made by {ctx.author.display_name}", icon_url=ctx.author.avatar_url) + + await ctx.send(file=file, embed=embed) + + @pfp_modify.command(pass_context=True, aliases=["easterify"], root_aliases=("easterify", "avatareasterify")) + async def avatareasterify(self, ctx: commands.Context, *colours: t.Union[discord.Colour, str]) -> None: + """ + This "Easterifies" the user's avatar. + + Given colours will produce a personalised egg in the corner, similar to the egg_decorate command. + If colours are not given, a nice little chocolate bunny will sit in the corner. + Colours are split by spaces, unless you wrap the colour name in double quotes. + Discord colour names, HTML colour names, XKCD colour names and hex values are accepted. + """ + async def send(*args, **kwargs) -> str: + """ + This replaces the original ctx.send. + + When invoking the egg decorating command, the egg itself doesn't print to to the channel. + Returns the message content so that if any errors occur, the error message can be output. + """ + if args: + return args[0] + + async with ctx.typing(): + egg = None + if colours: + send_message = ctx.send + ctx.send = send # Assigns ctx.send to a fake send + egg = await ctx.invoke(self.bot.get_command("eggdecorate"), *colours) + if isinstance(egg, str): # When an error message occurs in eggdecorate. + await send_message(egg) + return + ctx.send = send_message # Reassigns ctx.send + + image_bytes = await ctx.author.avatar_url_as(size=256).read() + file = await in_thread( + PfpEffects._apply_effect, + image_bytes, + PfpEffects.easterify_effect, + egg + ) + + embed = discord.Embed( + name="Your Lovely Easterified Avatar", + description="Here is your lovely avatar, all bright and colourful\nwith Easter pastel colours. Enjoy :D" + ) + embed.set_image(url="attachment://modified_avatar.png") + embed.set_footer(text=f"Made by {ctx.author.display_name}", icon_url=ctx.author.avatar_url) + + await ctx.send(file=file, embed=embed) + + async def send_pride_image( + self, + ctx: commands.Context, + image_bytes: bytes, + pixels: int, + flag: str, + option: str + ) -> None: + """Gets and sends the image in an embed. Used by the pride commands.""" + async with ctx.typing(): + file = await in_thread( + PfpEffects._apply_effect, + image_bytes, + PfpEffects.pridify_effect, + pixels, + flag + ) + + embed = discord.Embed( + name="Your Lovely Pride Avatar", + description=f"Here is your lovely avatar, surrounded by\n a beautiful {option} flag. Enjoy :D" + ) + embed.set_image(url="attachment://modified_avatar.png") + embed.set_footer(text=f"Made by {ctx.author.display_name}", icon_url=ctx.author.avatar_url) + await ctx.send(file=file, embed=embed) + + @pfp_modify.group( + aliases=["avatarpride", "pridepfp", "prideprofile"], + root_aliases=("prideavatar", "avatarpride", "pridepfp", "prideprofile"), + invoke_without_command=True + ) + async def prideavatar(self, ctx: commands.Context, option: str = "lgbt", pixels: int = 64) -> None: + """ + This surrounds an avatar with a border of a specified LGBT flag. + + This defaults to the LGBT rainbow flag if none is given. + The amount of pixels can be given which determines the thickness of the flag border. + This has a maximum of 512px and defaults to a 64px border. + The full image is 1024x1024. + """ + option = option.lower() + pixels = max(0, min(512, pixels)) + flag = self.GENDER_OPTIONS.get(option) + if flag is None: + return await ctx.send("I don't have that flag!") + + async with ctx.typing(): + image_bytes = await ctx.author.avatar_url.read() + await self.send_pride_image(ctx, image_bytes, pixels, flag, option) + + @prideavatar.command() + async def image(self, ctx: commands.Context, url: str, option: str = "lgbt", pixels: int = 64) -> None: + """ + This surrounds the image specified by the URL with a border of a specified LGBT flag. + + This defaults to the LGBT rainbow flag if none is given. + The amount of pixels can be given which determines the thickness of the flag border. + This has a maximum of 512px and defaults to a 64px border. + The full image is 1024x1024. + """ + option = option.lower() + pixels = max(0, min(512, pixels)) + flag = self.GENDER_OPTIONS.get(option) + if flag is None: + return await ctx.send("I don't have that flag!") + + async with ctx.typing(): + async with self.bot.http_session as session: + try: + response = await session.get(url) + except client_exceptions.ClientConnectorError: + return await ctx.send("Cannot connect to provided URL!") + except client_exceptions.InvalidURL: + return await ctx.send("Invalid URL!") + if response.status != 200: + return await ctx.send("Bad response from provided URL!") + image_bytes = await response.read() + await self.send_pride_image(ctx, image_bytes, pixels, flag, option) + + @prideavatar.command() + async def flags(self, ctx: commands.Context) -> None: + """This lists the flags that can be used with the prideavatar command.""" + choices = sorted(set(self.GENDER_OPTIONS.values())) + options = "• " + "\n• ".join(choices) + embed = discord.Embed( + title="I have the following flags:", + description=options, + colour=Colours.soft_red + ) + await ctx.send(embed=embed) + + @pfp_modify.command( + name='spookyavatar', + aliases=('savatar', 'spookify'), + root_aliases=('spookyavatar', 'spookify', 'savatar'), + brief='Spookify an user\'s avatar.' + ) + async def spooky_avatar(self, ctx: commands.Context, user: discord.Member = None) -> None: + """A command to print the user's spookified avatar.""" + if user is None: + user = ctx.message.author + + async with ctx.typing(): + image_bytes = await ctx.author.avatar_url.read() + file = await in_thread(PfpEffects._apply_effect, image_bytes, spookifications.get_random_effect) + + embed = discord.Embed( + title="Is this you or am I just really paranoid?", + colour=0xFF0000 + ) + embed.set_author(name=str(user.name), icon_url=user.avatar_url) + embed.set_image(url='attachment://modified_avatar.png') + embed.set_footer(text=f"Made by {ctx.author.display_name}", icon_url=ctx.author.avatar_url) + + await ctx.send(file=file, embed=embed) + + +def setup(bot: commands.Bot) -> None: + """Load the PfpModify cog.""" + bot.add_cog(PfpModify(bot)) -- cgit v1.2.3 From e210354fc5d8e0970e1ea2e75bb28fa314fa72fa Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 20 Feb 2021 15:50:03 +0000 Subject: Hoist apply effect and remove private _. --- .../evergreen/profile_pic_modification/_effects.py | 25 +++++++++++----------- .../profile_pic_modification/pfp_modify.py | 8 +++---- 2 files changed, 17 insertions(+), 16 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/profile_pic_modification/_effects.py b/bot/exts/evergreen/profile_pic_modification/_effects.py index fbe5f706..a290ed3d 100644 --- a/bot/exts/evergreen/profile_pic_modification/_effects.py +++ b/bot/exts/evergreen/profile_pic_modification/_effects.py @@ -15,6 +15,19 @@ EASTER_COLOURS = [ class PfpEffects(): """Implements various image effects.""" + @staticmethod + def apply_effect(image_bytes: bytes, effect: t.Callable, *args) -> discord.File: + """Applies the given effect to the image passed to it.""" + im = Image.open(BytesIO(image_bytes)) + im = im.convert("RGBA") + im = effect(im, *args) + + bufferedio = BytesIO() + im.save(bufferedio, format="PNG") + bufferedio.seek(0) + + return discord.File(bufferedio, filename="modified_avatar.png") + @staticmethod def closest(x: t.Tuple[int, int, int]) -> t.Tuple[int, int, int]: """ @@ -56,18 +69,6 @@ class PfpEffects(): ring.putalpha(mask) return ring - @staticmethod - def _apply_effect(image_bytes: bytes, effect: t.Callable, *args) -> discord.File: - im = Image.open(BytesIO(image_bytes)) - im = im.convert("RGBA") - im = effect(im, *args) - - bufferedio = BytesIO() - im.save(bufferedio, format="PNG") - bufferedio.seek(0) - - return discord.File(bufferedio, filename="modified_avatar.png") - @staticmethod def pridify_effect( image: Image, diff --git a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py index 51742257..a58f44d2 100644 --- a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py +++ b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py @@ -49,7 +49,7 @@ class PfpModify(commands.Cog): async with ctx.typing(): image_bytes = await ctx.author.avatar_url.read() file = await in_thread( - PfpEffects._apply_effect, + PfpEffects.apply_effect, image_bytes, PfpEffects.eight_bitify_effect ) @@ -97,7 +97,7 @@ class PfpModify(commands.Cog): image_bytes = await ctx.author.avatar_url_as(size=256).read() file = await in_thread( - PfpEffects._apply_effect, + PfpEffects.apply_effect, image_bytes, PfpEffects.easterify_effect, egg @@ -123,7 +123,7 @@ class PfpModify(commands.Cog): """Gets and sends the image in an embed. Used by the pride commands.""" async with ctx.typing(): file = await in_thread( - PfpEffects._apply_effect, + PfpEffects.apply_effect, image_bytes, PfpEffects.pridify_effect, pixels, @@ -216,7 +216,7 @@ class PfpModify(commands.Cog): async with ctx.typing(): image_bytes = await ctx.author.avatar_url.read() - file = await in_thread(PfpEffects._apply_effect, image_bytes, spookifications.get_random_effect) + file = await in_thread(PfpEffects.apply_effect, image_bytes, spookifications.get_random_effect) embed = discord.Embed( title="Is this you or am I just really paranoid?", -- cgit v1.2.3 From a30f4448bfe411cf418e4de938870d89680d68df Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 20 Feb 2021 15:53:42 +0000 Subject: Don't return discord.Message from ctx.send calls --- bot/exts/evergreen/profile_pic_modification/pfp_modify.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py index a58f44d2..f3e8d426 100644 --- a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py +++ b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py @@ -156,7 +156,8 @@ class PfpModify(commands.Cog): pixels = max(0, min(512, pixels)) flag = self.GENDER_OPTIONS.get(option) if flag is None: - return await ctx.send("I don't have that flag!") + await ctx.send("I don't have that flag!") + return async with ctx.typing(): image_bytes = await ctx.author.avatar_url.read() @@ -176,7 +177,8 @@ class PfpModify(commands.Cog): pixels = max(0, min(512, pixels)) flag = self.GENDER_OPTIONS.get(option) if flag is None: - return await ctx.send("I don't have that flag!") + await ctx.send("I don't have that flag!") + return async with ctx.typing(): async with self.bot.http_session as session: -- cgit v1.2.3 From 6e7521c74e9c34159d8ba4873f09f5277f47908e Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 22 Feb 2021 18:35:10 +0000 Subject: Improve doc string. --- bot/exts/evergreen/profile_pic_modification/_effects.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/profile_pic_modification/_effects.py b/bot/exts/evergreen/profile_pic_modification/_effects.py index a290ed3d..1179100c 100644 --- a/bot/exts/evergreen/profile_pic_modification/_effects.py +++ b/bot/exts/evergreen/profile_pic_modification/_effects.py @@ -13,7 +13,11 @@ EASTER_COLOURS = [ class PfpEffects(): - """Implements various image effects.""" + """ + Implements various image effects. + + All of these methods are blocking, so should be ran in threads. + """ @staticmethod def apply_effect(image_bytes: bytes, effect: t.Callable, *args) -> discord.File: -- cgit v1.2.3 From 64576f56a69ec26406d7eaa11956d93db642fc3a Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 5 Mar 2021 22:05:56 +0000 Subject: Rename in_thread fuction for clarity. --- bot/exts/evergreen/profile_pic_modification/pfp_modify.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py index f3e8d426..df5005c9 100644 --- a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py +++ b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py @@ -18,7 +18,7 @@ log = logging.getLogger(__name__) _EXECUTOR = ThreadPoolExecutor(10) -async def in_thread(func: t.Callable, *args) -> asyncio.Future: +async def in_executor(func: t.Callable, *args) -> asyncio.Future: """Allows non-async functions to work in async functions.""" log.trace(f"Running {func.__name__} in an executor.") loop = asyncio.get_event_loop() @@ -48,7 +48,7 @@ class PfpModify(commands.Cog): """Pixelates your avatar and changes the palette to an 8bit one.""" async with ctx.typing(): image_bytes = await ctx.author.avatar_url.read() - file = await in_thread( + file = await in_executor( PfpEffects.apply_effect, image_bytes, PfpEffects.eight_bitify_effect @@ -96,7 +96,7 @@ class PfpModify(commands.Cog): ctx.send = send_message # Reassigns ctx.send image_bytes = await ctx.author.avatar_url_as(size=256).read() - file = await in_thread( + file = await in_executor( PfpEffects.apply_effect, image_bytes, PfpEffects.easterify_effect, @@ -122,7 +122,7 @@ class PfpModify(commands.Cog): ) -> None: """Gets and sends the image in an embed. Used by the pride commands.""" async with ctx.typing(): - file = await in_thread( + file = await in_executor( PfpEffects.apply_effect, image_bytes, PfpEffects.pridify_effect, @@ -218,7 +218,7 @@ class PfpModify(commands.Cog): async with ctx.typing(): image_bytes = await ctx.author.avatar_url.read() - file = await in_thread(PfpEffects.apply_effect, image_bytes, spookifications.get_random_effect) + file = await in_executor(PfpEffects.apply_effect, image_bytes, spookifications.get_random_effect) embed = discord.Embed( title="Is this you or am I just really paranoid?", -- cgit v1.2.3 From 013ce6da618ee9fdb7ec99d0c3a538f0aa8c745d Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 6 Mar 2021 12:12:22 +0000 Subject: Improve readibility of code, and fix grammar issues. --- .../evergreen/profile_pic_modification/_effects.py | 24 +++++++++++++--------- .../profile_pic_modification/pfp_modify.py | 18 ++++++++-------- 2 files changed, 23 insertions(+), 19 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/profile_pic_modification/_effects.py b/bot/exts/evergreen/profile_pic_modification/_effects.py index 1179100c..99010931 100644 --- a/bot/exts/evergreen/profile_pic_modification/_effects.py +++ b/bot/exts/evergreen/profile_pic_modification/_effects.py @@ -12,7 +12,7 @@ EASTER_COLOURS = [ ] # Pastel colours - Easter-like -class PfpEffects(): +class PfpEffects: """ Implements various image effects. @@ -37,14 +37,14 @@ class PfpEffects(): """ Finds the closest easter colour to a given pixel. - Returns a merge between the original colour and the closest colour + Returns a merge between the original colour and the closest colour. """ r1, g1, b1 = x def distance(point: t.Tuple[int, int, int]) -> t.Tuple[int, int, int]: """Finds the difference between a pastel colour and the original pixel colour.""" r2, g2, b2 = point - return ((r1 - r2)**2 + (g1 - g2)**2 + (b1 - b2)**2) + return (r1 - r2) ** 2 + (g1 - g2) ** 2 + (b1 - b2) ** 2 closest_colours = sorted(EASTER_COLOURS, key=lambda point: distance(point)) r2, g2, b2 = closest_colours[0] @@ -52,7 +52,7 @@ class PfpEffects(): g = (g1 + g2) // 2 b = (b1 + b2) // 2 - return (r, g, b) + return r, g, b @staticmethod def crop_avatar_circle(avatar: Image) -> Image: @@ -114,14 +114,18 @@ class PfpEffects(): image = ImageOps.posterize(image, 6) data = image.getdata() - setted_data = set(data) - new_d = {} + data_set = set(data) + easterified_data_set = {} - for x in setted_data: - new_d[x] = PfpEffects.closest(x) - new_data = [(*new_d[x], alpha[i]) if x in new_d else x for i, x in enumerate(data)] + for x in data_set: + easterified_data_set[x] = PfpEffects.closest(x) + new_pixel_data = [ + (*easterified_data_set[x], alpha[i]) + if x in easterified_data_set else x + for i, x in enumerate(data) + ] im = Image.new("RGBA", image.size) - im.putdata(new_data) + im.putdata(new_pixel_data) im.alpha_composite(overlay_image, (im.width - overlay_image.width, (im.height - overlay_image.height)//2)) return im diff --git a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py index df5005c9..1d5b7208 100644 --- a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py +++ b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py @@ -39,7 +39,7 @@ class PfpModify(commands.Cog): @commands.group() async def pfp_modify(self, ctx: commands.Context) -> None: - """Groups all of the pfp modifing commands to allow a single concurrency limit.""" + """Groups all of the pfp modifying commands to allow a single concurrency limit.""" if not ctx.invoked_subcommand: await ctx.send_help(ctx.command) @@ -56,11 +56,11 @@ class PfpModify(commands.Cog): embed = discord.Embed( title="Your 8-bit avatar", - description='Here is your avatar. I think it looks all cool and "retro"' + description="Here is your avatar. I think it looks all cool and 'retro'." ) embed.set_image(url="attachment://modified_avatar.png") - embed.set_footer(text=f"Made by {ctx.author.display_name}", icon_url=ctx.author.avatar_url) + embed.set_footer(text=f"Made by {ctx.author.display_name}.", icon_url=ctx.author.avatar_url) await ctx.send(file=file, embed=embed) @@ -104,11 +104,11 @@ class PfpModify(commands.Cog): ) embed = discord.Embed( - name="Your Lovely Easterified Avatar", + name="Your Lovely Easterified Avatar!", description="Here is your lovely avatar, all bright and colourful\nwith Easter pastel colours. Enjoy :D" ) embed.set_image(url="attachment://modified_avatar.png") - embed.set_footer(text=f"Made by {ctx.author.display_name}", icon_url=ctx.author.avatar_url) + embed.set_footer(text=f"Made by {ctx.author.display_name}.", icon_url=ctx.author.avatar_url) await ctx.send(file=file, embed=embed) @@ -131,11 +131,11 @@ class PfpModify(commands.Cog): ) embed = discord.Embed( - name="Your Lovely Pride Avatar", + name="Your Lovely Pride Avatar!", description=f"Here is your lovely avatar, surrounded by\n a beautiful {option} flag. Enjoy :D" ) embed.set_image(url="attachment://modified_avatar.png") - embed.set_footer(text=f"Made by {ctx.author.display_name}", icon_url=ctx.author.avatar_url) + embed.set_footer(text=f"Made by {ctx.author.display_name}.", icon_url=ctx.author.avatar_url) await ctx.send(file=file, embed=embed) @pfp_modify.group( @@ -222,11 +222,11 @@ class PfpModify(commands.Cog): embed = discord.Embed( title="Is this you or am I just really paranoid?", - colour=0xFF0000 + colour=Colours.soft_red ) embed.set_author(name=str(user.name), icon_url=user.avatar_url) embed.set_image(url='attachment://modified_avatar.png') - embed.set_footer(text=f"Made by {ctx.author.display_name}", icon_url=ctx.author.avatar_url) + embed.set_footer(text=f"Made by {ctx.author.display_name}.", icon_url=ctx.author.avatar_url) await ctx.send(file=file, embed=embed) -- cgit v1.2.3 From 67db0d13d35cda2fcaa7d3bb20e22b38bb264487 Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 6 Mar 2021 12:13:22 +0000 Subject: Convert method to static as it doesn't use class attributes. --- bot/exts/evergreen/profile_pic_modification/pfp_modify.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py index 1d5b7208..45a41e67 100644 --- a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py +++ b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py @@ -112,8 +112,8 @@ class PfpModify(commands.Cog): await ctx.send(file=file, embed=embed) + @staticmethod async def send_pride_image( - self, ctx: commands.Context, image_bytes: bytes, pixels: int, -- cgit v1.2.3 From 50a344d5caa006df1cbb033da4101b6cfa71529c Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 6 Mar 2021 12:14:23 +0000 Subject: Fix return type of in_executor. Its return type varies, based on the Callable given. --- bot/exts/evergreen/profile_pic_modification/pfp_modify.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py index 45a41e67..bba688aa 100644 --- a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py +++ b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py @@ -18,7 +18,7 @@ log = logging.getLogger(__name__) _EXECUTOR = ThreadPoolExecutor(10) -async def in_executor(func: t.Callable, *args) -> asyncio.Future: +async def in_executor(func: t.Callable, *args) -> t.Any: """Allows non-async functions to work in async functions.""" log.trace(f"Running {func.__name__} in an executor.") loop = asyncio.get_event_loop() -- cgit v1.2.3 From ea44e8ed8f5804cf5d80ab2318db6359c1e22c17 Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 6 Mar 2021 12:15:28 +0000 Subject: Don't return discord.Messages. --- bot/exts/evergreen/profile_pic_modification/pfp_modify.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py index bba688aa..7d4326a4 100644 --- a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py +++ b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py @@ -185,11 +185,16 @@ class PfpModify(commands.Cog): try: response = await session.get(url) except client_exceptions.ClientConnectorError: - return await ctx.send("Cannot connect to provided URL!") + await ctx.send("Cannot connect to provided URL!") + return except client_exceptions.InvalidURL: - return await ctx.send("Invalid URL!") + await ctx.send("Invalid URL!") + return + if response.status != 200: - return await ctx.send("Bad response from provided URL!") + await ctx.send("Bad response from provided URL!") + return + image_bytes = await response.read() await self.send_pride_image(ctx, image_bytes, pixels, flag, option) -- cgit v1.2.3 From 49ce88636124b826437c17a3c39c60c0840c1256 Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 6 Mar 2021 12:16:00 +0000 Subject: Remove superfluous str cast. --- bot/exts/evergreen/profile_pic_modification/pfp_modify.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py index 7d4326a4..11222563 100644 --- a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py +++ b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py @@ -229,7 +229,7 @@ class PfpModify(commands.Cog): title="Is this you or am I just really paranoid?", colour=Colours.soft_red ) - embed.set_author(name=str(user.name), icon_url=user.avatar_url) + embed.set_author(name=user.name, icon_url=user.avatar_url) embed.set_image(url='attachment://modified_avatar.png') embed.set_footer(text=f"Made by {ctx.author.display_name}.", icon_url=ctx.author.avatar_url) -- cgit v1.2.3 From 365a272ce235706006a36acd0eac09637205a1ae Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 6 Mar 2021 12:20:25 +0000 Subject: Split up complex line. --- bot/exts/evergreen/profile_pic_modification/_effects.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/profile_pic_modification/_effects.py b/bot/exts/evergreen/profile_pic_modification/_effects.py index 99010931..dda58006 100644 --- a/bot/exts/evergreen/profile_pic_modification/_effects.py +++ b/bot/exts/evergreen/profile_pic_modification/_effects.py @@ -127,5 +127,8 @@ class PfpEffects: im = Image.new("RGBA", image.size) im.putdata(new_pixel_data) - im.alpha_composite(overlay_image, (im.width - overlay_image.width, (im.height - overlay_image.height)//2)) + im.alpha_composite( + overlay_image, + (im.width - overlay_image.width, (im.height - overlay_image.height) // 2) + ) return im -- cgit v1.2.3 From 2ff509601fb97466ba6d980d226f6c0e481a1961 Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 10 Mar 2021 19:31:37 +0000 Subject: Move colours to constants --- bot/constants.py | 16 ++++++++++++++++ bot/exts/evergreen/profile_pic_modification/_effects.py | 8 ++------ 2 files changed, 18 insertions(+), 6 deletions(-) (limited to 'bot') diff --git a/bot/constants.py b/bot/constants.py index b8e30a7c..5c95d9c1 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -162,6 +162,22 @@ class Colours: python_yellow = 0xFFD43B grass_green = 0x66ff00 + easter_like_colours = [ + (255, 247, 0), + (255, 255, 224), + (0, 255, 127), + (189, 252, 201), + (255, 192, 203), + (255, 160, 122), + (181, 115, 220), + (221, 160, 221), + (200, 162, 200), + (238, 130, 238), + (135, 206, 235), + (0, 204, 204), + (64, 224, 208), + ] + class Emojis: star = "\u2B50" diff --git a/bot/exts/evergreen/profile_pic_modification/_effects.py b/bot/exts/evergreen/profile_pic_modification/_effects.py index dda58006..b0d50f4b 100644 --- a/bot/exts/evergreen/profile_pic_modification/_effects.py +++ b/bot/exts/evergreen/profile_pic_modification/_effects.py @@ -5,11 +5,7 @@ from pathlib import Path import discord from PIL import Image, ImageDraw, ImageOps -EASTER_COLOURS = [ - (255, 247, 0), (255, 255, 224), (0, 255, 127), (189, 252, 201), (255, 192, 203), - (255, 160, 122), (181, 115, 220), (221, 160, 221), (200, 162, 200), (238, 130, 238), - (135, 206, 235), (0, 204, 204), (64, 224, 208) -] # Pastel colours - Easter-like +from bot.constants import Colours class PfpEffects: @@ -46,7 +42,7 @@ class PfpEffects: r2, g2, b2 = point return (r1 - r2) ** 2 + (g1 - g2) ** 2 + (b1 - b2) ** 2 - closest_colours = sorted(EASTER_COLOURS, key=lambda point: distance(point)) + closest_colours = sorted(Colours.easter_like_colours, key=lambda point: distance(point)) r2, g2, b2 = closest_colours[0] r = (r1 + r2) // 2 g = (g1 + g2) // 2 -- cgit v1.2.3 From 3d54a88ee1d7fd5d9ece91ecea998e8da107ff22 Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 10 Mar 2021 19:33:30 +0000 Subject: Improve docstring and split line for readibility --- bot/exts/evergreen/profile_pic_modification/_effects.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/profile_pic_modification/_effects.py b/bot/exts/evergreen/profile_pic_modification/_effects.py index b0d50f4b..3f4e796d 100644 --- a/bot/exts/evergreen/profile_pic_modification/_effects.py +++ b/bot/exts/evergreen/profile_pic_modification/_effects.py @@ -88,8 +88,14 @@ class PfpEffects: @staticmethod def eight_bitify_effect(image: Image) -> Image: - """Applies the 8bit effect to the given image.""" - image = image.resize((32, 32), resample=Image.NEAREST).resize((1024, 1024), resample=Image.NEAREST) + """ + Applies the 8bit effect to the given image. + + This is done by reducing the image to 32x32 and then back up to 1024x1024. + We then quantize the image before returning too. + """ + image = image.resize((32, 32), resample=Image.NEAREST) + image = image.resize((1024, 1024), resample=Image.NEAREST) return image.quantize() @staticmethod -- cgit v1.2.3 From 33141d9fa9d3ffe2120fc4cf2fcba824d047fe7e Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 10 Mar 2021 19:34:00 +0000 Subject: Remove double assignment of a variable --- bot/exts/evergreen/profile_pic_modification/_effects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/profile_pic_modification/_effects.py b/bot/exts/evergreen/profile_pic_modification/_effects.py index 3f4e796d..ef0a3f37 100644 --- a/bot/exts/evergreen/profile_pic_modification/_effects.py +++ b/bot/exts/evergreen/profile_pic_modification/_effects.py @@ -109,7 +109,7 @@ class PfpEffects: )) overlay_image = overlay_image.convert("RGBA") else: - overlay_image = overlay_image = Image.open(Path("bot/resources/easter/chocolate_bunny.png")) + overlay_image = Image.open(Path("bot/resources/easter/chocolate_bunny.png")) alpha = image.getchannel("A").getdata() image = image.convert("RGB") -- cgit v1.2.3 From d90f735913b087104a24aa718b6473fa0b39384c Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 11 Mar 2021 17:55:20 +0000 Subject: Use a more specific filename for avatar modifiying commands --- .../evergreen/profile_pic_modification/_effects.py | 4 +- .../profile_pic_modification/pfp_modify.py | 44 ++++++++++++++++++---- 2 files changed, 39 insertions(+), 9 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/profile_pic_modification/_effects.py b/bot/exts/evergreen/profile_pic_modification/_effects.py index ef0a3f37..ae606dcb 100644 --- a/bot/exts/evergreen/profile_pic_modification/_effects.py +++ b/bot/exts/evergreen/profile_pic_modification/_effects.py @@ -16,7 +16,7 @@ class PfpEffects: """ @staticmethod - def apply_effect(image_bytes: bytes, effect: t.Callable, *args) -> discord.File: + def apply_effect(image_bytes: bytes, effect: t.Callable, filename: str, *args) -> discord.File: """Applies the given effect to the image passed to it.""" im = Image.open(BytesIO(image_bytes)) im = im.convert("RGBA") @@ -26,7 +26,7 @@ class PfpEffects: im.save(bufferedio, format="PNG") bufferedio.seek(0) - return discord.File(bufferedio, filename="modified_avatar.png") + return discord.File(bufferedio, filename=filename) @staticmethod def closest(x: t.Tuple[int, int, int]) -> t.Tuple[int, int, int]: diff --git a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py index 11222563..299ed107 100644 --- a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py +++ b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py @@ -17,6 +17,8 @@ log = logging.getLogger(__name__) _EXECUTOR = ThreadPoolExecutor(10) +FILENAME_STRING = "{effect}_{author}.png" + async def in_executor(func: t.Callable, *args) -> t.Any: """Allows non-async functions to work in async functions.""" @@ -48,10 +50,16 @@ class PfpModify(commands.Cog): """Pixelates your avatar and changes the palette to an 8bit one.""" async with ctx.typing(): image_bytes = await ctx.author.avatar_url.read() + file_name = FILENAME_STRING.format( + effect="eightbit_avatar", + author=ctx.author.display_name + ) + file = await in_executor( PfpEffects.apply_effect, image_bytes, - PfpEffects.eight_bitify_effect + PfpEffects.eight_bitify_effect, + file_name ) embed = discord.Embed( @@ -59,10 +67,10 @@ class PfpModify(commands.Cog): description="Here is your avatar. I think it looks all cool and 'retro'." ) - embed.set_image(url="attachment://modified_avatar.png") + embed.set_image(url=f"attachment://{file_name}") embed.set_footer(text=f"Made by {ctx.author.display_name}.", icon_url=ctx.author.avatar_url) - await ctx.send(file=file, embed=embed) + await ctx.send(embed=embed, file=file) @pfp_modify.command(pass_context=True, aliases=["easterify"], root_aliases=("easterify", "avatareasterify")) async def avatareasterify(self, ctx: commands.Context, *colours: t.Union[discord.Colour, str]) -> None: @@ -96,10 +104,16 @@ class PfpModify(commands.Cog): ctx.send = send_message # Reassigns ctx.send image_bytes = await ctx.author.avatar_url_as(size=256).read() + file_name = FILENAME_STRING.format( + effect="easterified_avatar", + author=ctx.author.display_name + ) + file = await in_executor( PfpEffects.apply_effect, image_bytes, PfpEffects.easterify_effect, + file_name, egg ) @@ -107,7 +121,7 @@ class PfpModify(commands.Cog): name="Your Lovely Easterified Avatar!", description="Here is your lovely avatar, all bright and colourful\nwith Easter pastel colours. Enjoy :D" ) - embed.set_image(url="attachment://modified_avatar.png") + embed.set_image(url=f"attachment://{file_name}") embed.set_footer(text=f"Made by {ctx.author.display_name}.", icon_url=ctx.author.avatar_url) await ctx.send(file=file, embed=embed) @@ -122,10 +136,16 @@ class PfpModify(commands.Cog): ) -> None: """Gets and sends the image in an embed. Used by the pride commands.""" async with ctx.typing(): + file_name = FILENAME_STRING.format( + effect="pride_avatar", + author=ctx.author.display_name + ) + file = await in_executor( PfpEffects.apply_effect, image_bytes, PfpEffects.pridify_effect, + file_name, pixels, flag ) @@ -134,7 +154,7 @@ class PfpModify(commands.Cog): name="Your Lovely Pride Avatar!", description=f"Here is your lovely avatar, surrounded by\n a beautiful {option} flag. Enjoy :D" ) - embed.set_image(url="attachment://modified_avatar.png") + embed.set_image(url=f"attachment://{file_name}") embed.set_footer(text=f"Made by {ctx.author.display_name}.", icon_url=ctx.author.avatar_url) await ctx.send(file=file, embed=embed) @@ -223,14 +243,24 @@ class PfpModify(commands.Cog): async with ctx.typing(): image_bytes = await ctx.author.avatar_url.read() - file = await in_executor(PfpEffects.apply_effect, image_bytes, spookifications.get_random_effect) + + file_name = FILENAME_STRING.format( + effect="pride_avatar", + author=ctx.author.display_name + ) + file = await in_executor( + PfpEffects.apply_effect, + image_bytes, + spookifications.get_random_effect, + file_name + ) embed = discord.Embed( title="Is this you or am I just really paranoid?", colour=Colours.soft_red ) embed.set_author(name=user.name, icon_url=user.avatar_url) - embed.set_image(url='attachment://modified_avatar.png') + embed.set_image(url=f"attachment://{file_name}") embed.set_footer(text=f"Made by {ctx.author.display_name}.", icon_url=ctx.author.avatar_url) await ctx.send(file=file, embed=embed) -- cgit v1.2.3 From 856cffbbfa2411fa246a2ff988b1199e0dcb4030 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 11 Mar 2021 17:59:39 +0000 Subject: Defer errors in pride image to error handler --- bot/exts/evergreen/profile_pic_modification/pfp_modify.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py index 299ed107..68750fe2 100644 --- a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py +++ b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py @@ -8,6 +8,7 @@ import aiofiles import discord from aiohttp import client_exceptions from discord.ext import commands +from discord.ext.commands.errors import BadArgument from bot.constants import Colours from bot.exts.evergreen.profile_pic_modification._effects import PfpEffects @@ -205,11 +206,9 @@ class PfpModify(commands.Cog): try: response = await session.get(url) except client_exceptions.ClientConnectorError: - await ctx.send("Cannot connect to provided URL!") - return + raise BadArgument("Cannot connect to provided URL!") except client_exceptions.InvalidURL: - await ctx.send("Invalid URL!") - return + raise BadArgument("Invalid URL!") if response.status != 200: await ctx.send("Bad response from provided URL!") -- cgit v1.2.3 From 6c1b715bae96980309af3b38a5ff6cad6ce49f95 Mon Sep 17 00:00:00 2001 From: ChrisJL Date: Thu, 11 Mar 2021 18:01:22 +0000 Subject: Update docstring to refer to groups rather than commands Co-authored-by: Shivansh-007 <69356296+Shivansh-007@users.noreply.github.com> --- bot/group.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/group.py b/bot/group.py index 77092adf..a7bc59b7 100644 --- a/bot/group.py +++ b/bot/group.py @@ -6,7 +6,7 @@ class Group(commands.Group): A `discord.ext.commands.Group` subclass which supports root aliases. A `root_aliases` keyword argument is added, which is a sequence of alias names that will act as - top-level commands rather than being aliases of the command's group. It's stored as an attribute + top-level groups rather than being aliases of the command's group. It's stored as an attribute also named `root_aliases`. """ @@ -15,4 +15,4 @@ class Group(commands.Group): self.root_aliases = kwargs.get("root_aliases", []) if not isinstance(self.root_aliases, (list, tuple)): - raise TypeError("Root aliases of a command must be a list or a tuple of strings.") + raise TypeError("Root aliases of a group must be a list or a tuple of strings.") -- cgit v1.2.3 From 335702f65429c6f1ebc9a9a50ffc528bcfd38581 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 11 Mar 2021 18:06:22 +0000 Subject: Fix filename for spooky effect avatars --- bot/exts/evergreen/profile_pic_modification/pfp_modify.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py index 68750fe2..9b874900 100644 --- a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py +++ b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py @@ -244,7 +244,7 @@ class PfpModify(commands.Cog): image_bytes = await ctx.author.avatar_url.read() file_name = FILENAME_STRING.format( - effect="pride_avatar", + effect="spooky_avatar", author=ctx.author.display_name ) file = await in_executor( -- cgit v1.2.3 From 88e7ab58ff6ad74a8323c828cc5411886bb57dbe Mon Sep 17 00:00:00 2001 From: ChrisJL Date: Sat, 13 Mar 2021 14:57:54 +0000 Subject: Use user reference passed via command The command takes in user as argument but doesn't use it while converting the image into bytes. Co-authored-by: Shivansh-007 <69356296+Shivansh-007@users.noreply.github.com> --- bot/exts/evergreen/profile_pic_modification/pfp_modify.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py index 9b874900..aa0f0404 100644 --- a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py +++ b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py @@ -241,11 +241,11 @@ class PfpModify(commands.Cog): user = ctx.message.author async with ctx.typing(): - image_bytes = await ctx.author.avatar_url.read() + image_bytes = await user.avatar_url.read() file_name = FILENAME_STRING.format( effect="spooky_avatar", - author=ctx.author.display_name + author=user.display_name ) file = await in_executor( PfpEffects.apply_effect, -- cgit v1.2.3 From d8b30f2a1a99f1c301c4d1e675dc708b90d0e652 Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 13 Mar 2021 15:54:15 +0000 Subject: Use new help command ext Since sir-lancebot#625 was merged, we now have bot.utils.extensions.invoke_help_command which sends a nicely formatted help embed. --- bot/exts/evergreen/profile_pic_modification/pfp_modify.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py index aa0f0404..b13ba574 100644 --- a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py +++ b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py @@ -12,6 +12,7 @@ from discord.ext.commands.errors import BadArgument from bot.constants import Colours from bot.exts.evergreen.profile_pic_modification._effects import PfpEffects +from bot.utils.extensions import invoke_help_command from bot.utils.halloween import spookifications log = logging.getLogger(__name__) @@ -44,7 +45,7 @@ class PfpModify(commands.Cog): async def pfp_modify(self, ctx: commands.Context) -> None: """Groups all of the pfp modifying commands to allow a single concurrency limit.""" if not ctx.invoked_subcommand: - await ctx.send_help(ctx.command) + await invoke_help_command(ctx) @pfp_modify.command(name="8bitify", root_aliases=("8bitify",)) async def eightbit_command(self, ctx: commands.Context) -> None: -- cgit v1.2.3 From 8c09ba1db566a6c1051479e353ad025392e7e5bb Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 14 Mar 2021 13:34:27 +0000 Subject: Remove aiofiles dep --- Pipfile | 1 - Pipfile.lock | 28 ++++++++-------------- .../profile_pic_modification/pfp_modify.py | 16 +++++-------- 3 files changed, 16 insertions(+), 29 deletions(-) (limited to 'bot') diff --git a/Pipfile b/Pipfile index 79a5dfc8..e7e01a31 100644 --- a/Pipfile +++ b/Pipfile @@ -15,7 +15,6 @@ PyYAML = "~=5.3.1" "discord.py" = {extras = ["voice"], version = "~=1.5.1"} async-rediscache = {extras = ["fakeredis"], version = "~=0.1.4"} emojis = "~=0.6.0" -aiofiles = "~=0.6" [dev-packages] flake8 = "~=3.8" diff --git a/Pipfile.lock b/Pipfile.lock index 9d0a988e..19bcb719 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "91281e9ed353fea748de3da19abd7bef402402b23fc78a1260dc8bf8bd2bd98c" + "sha256": "b4aaaacbab13179145e36d7b86c736db512286f6cce8e513cc30c48d68fe3810" }, "pipfile-spec": 6, "requires": { @@ -24,14 +24,6 @@ "index": "pypi", "version": "==2.0.0" }, - "aiofiles": { - "hashes": [ - "sha256:bd3019af67f83b739f8e4053c6c0512a7f545b9a8d91aaeab55e6e0f9d123c27", - "sha256:e0281b157d3d5d59d803e3f4557dcc9a3dff28a4dd4829a9ff478adae50ca092" - ], - "index": "pypi", - "version": "==0.6.0" - }, "aiohttp": { "hashes": [ "sha256:1a4160579ffbc1b69e88cb6ca8bb0fbd4947dfcbf9fb1e2a4fc4c7a4a986c1fe", @@ -531,11 +523,11 @@ }, "flake8-annotations": { "hashes": [ - "sha256:3a377140556aecf11fa9f3bb18c10db01f5ea56dc79a730e2ec9b4f1f49e2055", - "sha256:e17947a48a5b9f632fe0c72682fc797c385e451048e7dfb20139f448a074cb3e" + "sha256:8968ff12f296433028ad561c680ccc03a7cd62576d100c3f1475e058b3c11b43", + "sha256:bd0505616c0d85ebb45c6052d339c69f320d3f87fa079ab4e91a4f234a863d05" ], "index": "pypi", - "version": "==2.5.0" + "version": "==2.6.0" }, "flake8-bugbear": { "hashes": [ @@ -593,11 +585,11 @@ }, "identify": { "hashes": [ - "sha256:2179e7359471ab55729f201b3fdf7dc2778e221f868410fedcb0987b791ba552", - "sha256:2a5fdf2f5319cc357eda2550bea713a404392495961022cf2462624ce62f0f46" + "sha256:e3b7fd755b7ceee44fe22957005a92c2a085c863c2e65a6efdec35d0e06666db", + "sha256:fab0d3a3ab0d7d5f513985b0335ccccad9d61420c5216fb779237bf7edc3e5d1" ], "markers": "python_full_version >= '3.6.1'", - "version": "==2.1.0" + "version": "==2.1.2" }, "mccabe": { "hashes": [ @@ -623,11 +615,11 @@ }, "pre-commit": { "hashes": [ - "sha256:16212d1fde2bed88159287da88ff03796863854b04dc9f838a55979325a3d20e", - "sha256:399baf78f13f4de82a29b649afd74bef2c4e28eb4f021661fc7f29246e8c7a3a" + "sha256:94c82f1bf5899d56edb1d926732f4e75a7df29a0c8c092559c77420c9d62428b", + "sha256:de55c5c72ce80d79106e48beb1b54104d16495ce7f95b0c7b13d4784193a00af" ], "index": "pypi", - "version": "==2.10.1" + "version": "==2.11.1" }, "pycodestyle": { "hashes": [ diff --git a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py index b13ba574..d41fcaad 100644 --- a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py +++ b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py @@ -4,7 +4,6 @@ import logging import typing as t from concurrent.futures import ThreadPoolExecutor -import aiofiles import discord from aiohttp import client_exceptions from discord.ext import commands @@ -21,6 +20,9 @@ _EXECUTOR = ThreadPoolExecutor(10) FILENAME_STRING = "{effect}_{author}.png" +with open('bot/resources/pride/gender_options.json') as f: + GENDER_OPTIONS = json.loads(f.read()) + async def in_executor(func: t.Callable, *args) -> t.Any: """Allows non-async functions to work in async functions.""" @@ -34,12 +36,6 @@ class PfpModify(commands.Cog): def __init__(self, bot: commands.Bot) -> None: self.bot = bot - self.bot.loop.create_task(self.init_cog()) - - async def init_cog(self) -> None: - """Initial load from resources asynchronously.""" - async with aiofiles.open('bot/resources/pride/gender_options.json') as f: - self.GENDER_OPTIONS = json.loads(await f.read()) @commands.group() async def pfp_modify(self, ctx: commands.Context) -> None: @@ -176,7 +172,7 @@ class PfpModify(commands.Cog): """ option = option.lower() pixels = max(0, min(512, pixels)) - flag = self.GENDER_OPTIONS.get(option) + flag = GENDER_OPTIONS.get(option) if flag is None: await ctx.send("I don't have that flag!") return @@ -197,7 +193,7 @@ class PfpModify(commands.Cog): """ option = option.lower() pixels = max(0, min(512, pixels)) - flag = self.GENDER_OPTIONS.get(option) + flag = GENDER_OPTIONS.get(option) if flag is None: await ctx.send("I don't have that flag!") return @@ -221,7 +217,7 @@ class PfpModify(commands.Cog): @prideavatar.command() async def flags(self, ctx: commands.Context) -> None: """This lists the flags that can be used with the prideavatar command.""" - choices = sorted(set(self.GENDER_OPTIONS.values())) + choices = sorted(set(GENDER_OPTIONS.values())) options = "• " + "\n• ".join(choices) embed = discord.Embed( title="I have the following flags:", -- cgit v1.2.3 From 4dbd106f3febeadcde162bcafa163ae4d07f1b12 Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 14 Mar 2021 13:38:47 +0000 Subject: Remove unnecessary lambda in when getting closest colour --- bot/exts/evergreen/profile_pic_modification/_effects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/profile_pic_modification/_effects.py b/bot/exts/evergreen/profile_pic_modification/_effects.py index ae606dcb..2ff92000 100644 --- a/bot/exts/evergreen/profile_pic_modification/_effects.py +++ b/bot/exts/evergreen/profile_pic_modification/_effects.py @@ -42,7 +42,7 @@ class PfpEffects: r2, g2, b2 = point return (r1 - r2) ** 2 + (g1 - g2) ** 2 + (b1 - b2) ** 2 - closest_colours = sorted(Colours.easter_like_colours, key=lambda point: distance(point)) + closest_colours = sorted(Colours.easter_like_colours, key=distance) r2, g2, b2 = closest_colours[0] r = (r1 + r2) // 2 g = (g1 + g2) // 2 -- cgit v1.2.3 From 3eb23d5307bbf4abc03522e3ba9903e42131bb27 Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 14 Mar 2021 13:39:26 +0000 Subject: Pull the function signature back onto one line for readibility --- bot/exts/evergreen/profile_pic_modification/_effects.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/profile_pic_modification/_effects.py b/bot/exts/evergreen/profile_pic_modification/_effects.py index 2ff92000..9319a1b8 100644 --- a/bot/exts/evergreen/profile_pic_modification/_effects.py +++ b/bot/exts/evergreen/profile_pic_modification/_effects.py @@ -70,11 +70,7 @@ class PfpEffects: return ring @staticmethod - def pridify_effect( - image: Image, - pixels: int, - flag: str - ) -> Image: + def pridify_effect(image: Image, pixels: int, flag: str) -> Image: """Applies the pride effect to the given image.""" image = image.resize((1024, 1024)) image = PfpEffects.crop_avatar_circle(image) -- cgit v1.2.3 From 63ec14c47ef34fe48a32ecf6b51718c277714b88 Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 14 Mar 2021 13:50:37 +0000 Subject: Imrpoves docstrings within the profile pic modification functions --- bot/exts/evergreen/profile_pic_modification/_effects.py | 17 ++++++++++++----- .../evergreen/profile_pic_modification/pfp_modify.py | 9 +++++++-- 2 files changed, 19 insertions(+), 7 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/profile_pic_modification/_effects.py b/bot/exts/evergreen/profile_pic_modification/_effects.py index 9319a1b8..a1069db7 100644 --- a/bot/exts/evergreen/profile_pic_modification/_effects.py +++ b/bot/exts/evergreen/profile_pic_modification/_effects.py @@ -10,9 +10,9 @@ from bot.constants import Colours class PfpEffects: """ - Implements various image effects. + Implements various image modifying effects, for the PfpModify cog. - All of these methods are blocking, so should be ran in threads. + All of these fuctions are slow, and blocking, so should be ran in executors. """ @staticmethod @@ -31,7 +31,7 @@ class PfpEffects: @staticmethod def closest(x: t.Tuple[int, int, int]) -> t.Tuple[int, int, int]: """ - Finds the closest easter colour to a given pixel. + Finds the closest "easter" colour to a given pixel. Returns a merge between the original colour and the closest colour. """ @@ -71,7 +71,7 @@ class PfpEffects: @staticmethod def pridify_effect(image: Image, pixels: int, flag: str) -> Image: - """Applies the pride effect to the given image.""" + """Applies the given pride effect to the given image.""" image = image.resize((1024, 1024)) image = PfpEffects.crop_avatar_circle(image) @@ -96,7 +96,14 @@ class PfpEffects: @staticmethod def easterify_effect(image: Image, overlay_image: Image = None) -> Image: - """Applies the easter effect to the given image.""" + """ + Applies the easter effect to the given image. + + This is done by getting the closest "easter" colour to each pixel and changing the colour + to the half-way RGBvalue. + + We also then add an overlay image on top in middle right, a chocolate bunny by default. + """ if overlay_image: ratio = 64 / overlay_image.height overlay_image = overlay_image.resize(( diff --git a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py index d41fcaad..19f0bdf0 100644 --- a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py +++ b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py @@ -25,7 +25,12 @@ with open('bot/resources/pride/gender_options.json') as f: async def in_executor(func: t.Callable, *args) -> t.Any: - """Allows non-async functions to work in async functions.""" + """ + Runs the given synchronus function `func` in an executor. + + This is useful for running slow, blocking code within async + functions, so that they don't block the bot. + """ log.trace(f"Running {func.__name__} in an executor.") loop = asyncio.get_event_loop() return await loop.run_in_executor(_EXECUTOR, func, *args) @@ -233,7 +238,7 @@ class PfpModify(commands.Cog): brief='Spookify an user\'s avatar.' ) async def spooky_avatar(self, ctx: commands.Context, user: discord.Member = None) -> None: - """A command to print the user's spookified avatar.""" + """This "spookifies" the given user's avatar, with a random *spooky* effect.""" if user is None: user = ctx.message.author -- cgit v1.2.3 From eac2b1d79275e3bdc08fc2a8de7a28913b8ebd5b Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 21 Mar 2021 13:52:56 +0000 Subject: Don't close bot's http session when getting image --- .../profile_pic_modification/pfp_modify.py | 24 ++++++++++------------ 1 file changed, 11 insertions(+), 13 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py index 19f0bdf0..9242ff0c 100644 --- a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py +++ b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py @@ -204,20 +204,18 @@ class PfpModify(commands.Cog): return async with ctx.typing(): - async with self.bot.http_session as session: - try: - response = await session.get(url) - except client_exceptions.ClientConnectorError: - raise BadArgument("Cannot connect to provided URL!") - except client_exceptions.InvalidURL: - raise BadArgument("Invalid URL!") - - if response.status != 200: - await ctx.send("Bad response from provided URL!") - return + try: + async with self.bot.http_session.get(url) as response: + if response.status != 200: + await ctx.send("Bad response from provided URL!") + return + image_bytes = await response.read() + except client_exceptions.ClientConnectorError: + raise BadArgument("Cannot connect to provided URL!") + except client_exceptions.InvalidURL: + raise BadArgument("Invalid URL!") - image_bytes = await response.read() - await self.send_pride_image(ctx, image_bytes, pixels, flag, option) + await self.send_pride_image(ctx, image_bytes, pixels, flag, option) @prideavatar.command() async def flags(self, ctx: commands.Context) -> None: -- cgit v1.2.3 From bdfeef5d16ff64a7ca8a9a610d39a654f2e1fb99 Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 28 Mar 2021 23:01:55 +0100 Subject: Rework bookmark command to allow other users to bookmark the same message. --- bot/exts/evergreen/bookmark.py | 116 +++++++++++++++++++++++++++++++++-------- 1 file changed, 95 insertions(+), 21 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/bookmark.py b/bot/exts/evergreen/bookmark.py index 5fa05d2e..d26c4845 100644 --- a/bot/exts/evergreen/bookmark.py +++ b/bot/exts/evergreen/bookmark.py @@ -1,14 +1,18 @@ +import asyncio import logging import random import discord from discord.ext import commands -from bot.constants import Colours, ERROR_REPLIES, Emojis, Icons +from bot.constants import Colours, ERROR_REPLIES, Icons from bot.utils.converters import WrappedMessageConverter log = logging.getLogger(__name__) +# Number of seconds to wait for other users to bookmark the same message +TIMEOUT = 120 + class Bookmark(commands.Cog): """Creates personal bookmarks by relaying a message link to the user's DMs.""" @@ -16,6 +20,64 @@ class Bookmark(commands.Cog): def __init__(self, bot: commands.Bot): self.bot = bot + @staticmethod + def build_bookmark_dm(target_message: WrappedMessageConverter, title: str) -> discord.Embed: + """Builds the embed to DM the bookmark requestor.""" + embed = discord.Embed( + title=title, + description=target_message.content, + colour=Colours.soft_green + ) + embed.add_field(name="Wanna give it a visit?", value=f"[Visit original message]({target_message.jump_url})") + embed.set_author(name=target_message.author, icon_url=target_message.author.avatar_url) + embed.set_thumbnail(url=Icons.bookmark) + + return embed + + @staticmethod + def build_error_embed(user: discord.Member) -> discord.Embed: + """Builds an error embed for when a bookmark requestor has DMs disabled.""" + return discord.Embed( + title=random.choice(ERROR_REPLIES), + description=f"{user.mention}, please enable your DMs to receive the bookmark", + colour=Colours.soft_red + ) + + async def action_bookmark( + self, + channel: discord.TextChannel, + user: discord.Member, + target_message: WrappedMessageConverter, + title: str + ) -> None: + """Sends the bookmark DM, or sends an error embed when a user bookmarks a message.""" + try: + embed = self.build_bookmark_dm(target_message, title) + await user.send(embed=embed) + except discord.Forbidden: + error_embed = self.build_error_embed(user) + await channel.send(embed=error_embed) + else: + log.info(f"{user} bookmarked {target_message.jump_url} with title '{title}'") + + @staticmethod + async def send_reaction_embed( + channel: discord.TextChannel, + target_message: WrappedMessageConverter + ) -> discord.Message: + """Sends an embed, with a reaction, so users can react to bookmark the message too.""" + message = await channel.send( + embed=discord.Embed( + description=( + f"React with 📌 to be sent your very own bookmark to [this message]({target_message.jump_url})." + ), + colour=Colours.soft_green + ) + ) + + await message.add_reaction("📌") + return message + @commands.command(name="bookmark", aliases=("bm", "pin")) async def bookmark( self, @@ -37,27 +99,39 @@ class Bookmark(commands.Cog): await ctx.send(embed=embed) return - embed = discord.Embed( - title=title, - colour=Colours.soft_green, - description=target_message.content - ) - embed.add_field(name="Wanna give it a visit?", value=f"[Visit original message]({target_message.jump_url})") - embed.set_author(name=target_message.author, icon_url=target_message.author.avatar_url) - embed.set_thumbnail(url=Icons.bookmark) - - try: - await ctx.author.send(embed=embed) - except discord.Forbidden: - error_embed = discord.Embed( - title=random.choice(ERROR_REPLIES), - description=f"{ctx.author.mention}, please enable your DMs to receive the bookmark", - colour=Colours.soft_red + def event_check(reaction_: discord.Reaction, user_: discord.Member) -> bool: + """Make sure that this reaction is what we want to operate on.""" + return ( + # Conditions for a successful pagination: + all(( + # Reaction is on this message + reaction_.message.id == reaction_message.id, + # User has not already bookmarked this message + user_.id not in bookmarked_users, + # Reaction is the 📌 emoji + str(reaction_.emoji) == "📌", + # Reaction was not made by the Bot + user_.id != self.bot.user.id + )) ) - await ctx.send(embed=error_embed) - else: - log.info(f"{ctx.author} bookmarked {target_message.jump_url} with title '{title}'") - await ctx.message.add_reaction(Emojis.envelope) + await self.action_bookmark(ctx.channel, ctx.author, target_message, title) + + # Keep track of who has already bookmarked, so users can't spam reactions and cause loads of DMs + bookmarked_users = [ctx.author.id] + reaction_message = await self.send_reaction_embed(ctx.channel, target_message) + + while True: + try: + _, user = await self.bot.wait_for("reaction_add", timeout=TIMEOUT, check=event_check) + except asyncio.TimeoutError: + log.debug("Timed out waiting for a reaction") + break # We're done, no reactions for the last 5 minutes + log.trace(f"{user} has sucessfully bookmarked from a reaction, attempting to DM them.") + await self.action_bookmark(ctx.channel, user, target_message, title) + bookmarked_users.append(user.id) + + # Delete the message now that we're done with it. + await reaction_message.delete() def setup(bot: commands.Bot) -> None: -- cgit v1.2.3 From 99a0fccf221ae9e365a76d9b8c6c330de6264c90 Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 28 Mar 2021 23:30:32 +0100 Subject: Update phrasing of comments in bookmark command --- bot/exts/evergreen/bookmark.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/bookmark.py b/bot/exts/evergreen/bookmark.py index d26c4845..bfb1e700 100644 --- a/bot/exts/evergreen/bookmark.py +++ b/bot/exts/evergreen/bookmark.py @@ -125,12 +125,12 @@ class Bookmark(commands.Cog): _, user = await self.bot.wait_for("reaction_add", timeout=TIMEOUT, check=event_check) except asyncio.TimeoutError: log.debug("Timed out waiting for a reaction") - break # We're done, no reactions for the last 5 minutes + break # No reactions for the last `TIMEOUT` seconds log.trace(f"{user} has sucessfully bookmarked from a reaction, attempting to DM them.") await self.action_bookmark(ctx.channel, user, target_message, title) bookmarked_users.append(user.id) - # Delete the message now that we're done with it. + # Delete the message now that the bot isn't listening to it to save screen space await reaction_message.delete() -- cgit v1.2.3 From a308990d1432a1a29f54e74678630f374a5fac2c Mon Sep 17 00:00:00 2001 From: ChrisJL Date: Mon, 29 Mar 2021 12:28:40 +0100 Subject: Fix spelling and grammar errors in bookmark command Co-authored-by: Shivansh-007 <69356296+Shivansh-007@users.noreply.github.com> --- bot/exts/evergreen/bookmark.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/bookmark.py b/bot/exts/evergreen/bookmark.py index bfb1e700..9246825a 100644 --- a/bot/exts/evergreen/bookmark.py +++ b/bot/exts/evergreen/bookmark.py @@ -22,7 +22,7 @@ class Bookmark(commands.Cog): @staticmethod def build_bookmark_dm(target_message: WrappedMessageConverter, title: str) -> discord.Embed: - """Builds the embed to DM the bookmark requestor.""" + """Builds the embed to DM the bookmark requester.""" embed = discord.Embed( title=title, description=target_message.content, @@ -36,10 +36,10 @@ class Bookmark(commands.Cog): @staticmethod def build_error_embed(user: discord.Member) -> discord.Embed: - """Builds an error embed for when a bookmark requestor has DMs disabled.""" + """Builds an error embed for when a bookmark requester has DMs disabled.""" return discord.Embed( title=random.choice(ERROR_REPLIES), - description=f"{user.mention}, please enable your DMs to receive the bookmark", + description=f"{user.mention}, please enable your DMs to receive the bookmark.", colour=Colours.soft_red ) @@ -126,7 +126,7 @@ class Bookmark(commands.Cog): except asyncio.TimeoutError: log.debug("Timed out waiting for a reaction") break # No reactions for the last `TIMEOUT` seconds - log.trace(f"{user} has sucessfully bookmarked from a reaction, attempting to DM them.") + log.trace(f"{user} has successfully bookmarked from a reaction, attempting to DM them.") await self.action_bookmark(ctx.channel, user, target_message, title) bookmarked_users.append(user.id) -- cgit v1.2.3 From 6e71554c1604cb372415a505e4db7ca0f80f30fd Mon Sep 17 00:00:00 2001 From: ChrisJL Date: Mon, 29 Mar 2021 12:29:25 +0100 Subject: Split out an embed add field call for readability Co-authored-by: Shivansh-007 <69356296+Shivansh-007@users.noreply.github.com> --- bot/exts/evergreen/bookmark.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/bookmark.py b/bot/exts/evergreen/bookmark.py index 9246825a..c4c238c3 100644 --- a/bot/exts/evergreen/bookmark.py +++ b/bot/exts/evergreen/bookmark.py @@ -28,7 +28,10 @@ class Bookmark(commands.Cog): description=target_message.content, colour=Colours.soft_green ) - embed.add_field(name="Wanna give it a visit?", value=f"[Visit original message]({target_message.jump_url})") + embed.add_field( + name="Wanna give it a visit?", + value=f"[Visit original message]({target_message.jump_url})" + ) embed.set_author(name=target_message.author, icon_url=target_message.author.avatar_url) embed.set_thumbnail(url=Icons.bookmark) -- cgit v1.2.3 From 22e062ab679afd5b6aa1790617097ed45ffee1f5 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 29 Mar 2021 12:31:10 +0100 Subject: Make bookmark emoji a constant --- bot/exts/evergreen/bookmark.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/bookmark.py b/bot/exts/evergreen/bookmark.py index c4c238c3..59e2c9d0 100644 --- a/bot/exts/evergreen/bookmark.py +++ b/bot/exts/evergreen/bookmark.py @@ -12,6 +12,7 @@ log = logging.getLogger(__name__) # Number of seconds to wait for other users to bookmark the same message TIMEOUT = 120 +BOOKMARK_EMOJI = "📌" class Bookmark(commands.Cog): @@ -29,7 +30,7 @@ class Bookmark(commands.Cog): colour=Colours.soft_green ) embed.add_field( - name="Wanna give it a visit?", + name="Wanna give it a visit?", value=f"[Visit original message]({target_message.jump_url})" ) embed.set_author(name=target_message.author, icon_url=target_message.author.avatar_url) @@ -72,13 +73,14 @@ class Bookmark(commands.Cog): message = await channel.send( embed=discord.Embed( description=( - f"React with 📌 to be sent your very own bookmark to [this message]({target_message.jump_url})." + f"React with {BOOKMARK_EMOJI} to be sent your very own bookmark to " + f"[this message]({target_message.jump_url})." ), colour=Colours.soft_green ) ) - await message.add_reaction("📌") + await message.add_reaction(BOOKMARK_EMOJI) return message @commands.command(name="bookmark", aliases=("bm", "pin")) @@ -111,8 +113,8 @@ class Bookmark(commands.Cog): reaction_.message.id == reaction_message.id, # User has not already bookmarked this message user_.id not in bookmarked_users, - # Reaction is the 📌 emoji - str(reaction_.emoji) == "📌", + # Reaction is the `BOOKMARK_EMOJI` emoji + str(reaction_.emoji) == BOOKMARK_EMOJI, # Reaction was not made by the Bot user_.id != self.bot.user.id )) -- cgit v1.2.3 From c082ab627c4a4ecf5e497f85b1424f78c73c2120 Mon Sep 17 00:00:00 2001 From: ChrisJL Date: Tue, 30 Mar 2021 21:22:06 +0100 Subject: Consistent comments in bookmark command Co-authored-by: Xithrius <15021300+Xithrius@users.noreply.github.com> --- bot/exts/evergreen/bookmark.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/bookmark.py b/bot/exts/evergreen/bookmark.py index 59e2c9d0..2088c610 100644 --- a/bot/exts/evergreen/bookmark.py +++ b/bot/exts/evergreen/bookmark.py @@ -130,7 +130,8 @@ class Bookmark(commands.Cog): _, user = await self.bot.wait_for("reaction_add", timeout=TIMEOUT, check=event_check) except asyncio.TimeoutError: log.debug("Timed out waiting for a reaction") - break # No reactions for the last `TIMEOUT` seconds + # No reactions for the last `TIMEOUT` seconds + break log.trace(f"{user} has successfully bookmarked from a reaction, attempting to DM them.") await self.action_bookmark(ctx.channel, user, target_message, title) bookmarked_users.append(user.id) -- cgit v1.2.3 From fd57de601752db923f558b726e17af665a69df55 Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 30 Mar 2021 21:26:10 +0100 Subject: Remove unnecessary underscores from variables in the bm command --- bot/exts/evergreen/bookmark.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/bookmark.py b/bot/exts/evergreen/bookmark.py index 2088c610..16919d8c 100644 --- a/bot/exts/evergreen/bookmark.py +++ b/bot/exts/evergreen/bookmark.py @@ -104,19 +104,19 @@ class Bookmark(commands.Cog): await ctx.send(embed=embed) return - def event_check(reaction_: discord.Reaction, user_: discord.Member) -> bool: + def event_check(reaction: discord.Reaction, user: discord.Member) -> bool: """Make sure that this reaction is what we want to operate on.""" return ( # Conditions for a successful pagination: all(( # Reaction is on this message - reaction_.message.id == reaction_message.id, + reaction.message.id == reaction_message.id, # User has not already bookmarked this message - user_.id not in bookmarked_users, + user.id not in bookmarked_users, # Reaction is the `BOOKMARK_EMOJI` emoji - str(reaction_.emoji) == BOOKMARK_EMOJI, + str(reaction.emoji) == BOOKMARK_EMOJI, # Reaction was not made by the Bot - user_.id != self.bot.user.id + user.id != self.bot.user.id )) ) await self.action_bookmark(ctx.channel, ctx.author, target_message, title) -- cgit v1.2.3 From 2ad7b496c742d275d717b2de410caebdbeaa50d4 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 5 Apr 2021 17:16:52 +0100 Subject: Fetch member before modifying their pfp We do this as the member cache may have an outdated version of their pfp, which can lead to errors if it's removed from the Discord CDN. Co-authored-by: vcokltfre --- bot/constants.py | 1 + .../profile_pic_modification/pfp_modify.py | 65 +++++++++++++++++----- 2 files changed, 52 insertions(+), 14 deletions(-) (limited to 'bot') diff --git a/bot/constants.py b/bot/constants.py index 3ca2cda9..853ea340 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -165,6 +165,7 @@ class Colours: class Emojis: + cross_mark = "\u274C" star = "\u2B50" christmas_tree = "\U0001F384" check = "\u2611" diff --git a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py index 9242ff0c..c4b74d04 100644 --- a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py +++ b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py @@ -9,7 +9,7 @@ from aiohttp import client_exceptions from discord.ext import commands from discord.ext.commands.errors import BadArgument -from bot.constants import Colours +from bot.constants import Client, Colours, Emojis from bot.exts.evergreen.profile_pic_modification._effects import PfpEffects from bot.utils.extensions import invoke_help_command from bot.utils.halloween import spookifications @@ -42,6 +42,24 @@ class PfpModify(commands.Cog): def __init__(self, bot: commands.Bot) -> None: self.bot = bot + async def _fetch_member(self, member_id: int) -> t.Optional[discord.Member]: + """ + Fetches a member and handles errors. + + This helper funciton is required as the member cache doesn't always have the most up to date + profile picture. THis can lead to errors if the image is delted from the Discord CDN. + """ + try: + member = await self.bot.get_guild(Client.guild).fetch_member(member_id) + except discord.errors.NotFound: + log.debug(f"Member {member_id} left the guild before we could get their pfp.") + return None + except discord.HTTPException: + log.exception(f"Exception while trying to retrieve member {member_id} from Discord.") + return None + + return member + @commands.group() async def pfp_modify(self, ctx: commands.Context) -> None: """Groups all of the pfp modifying commands to allow a single concurrency limit.""" @@ -52,10 +70,15 @@ class PfpModify(commands.Cog): async def eightbit_command(self, ctx: commands.Context) -> None: """Pixelates your avatar and changes the palette to an 8bit one.""" async with ctx.typing(): - image_bytes = await ctx.author.avatar_url.read() + member = await self._fetch_member(ctx.author.id) + if not member: + ctx.send(f"{Emojis.cross_mark} Could not get member info.") + return + + image_bytes = await member.avatar_url.read() file_name = FILENAME_STRING.format( effect="eightbit_avatar", - author=ctx.author.display_name + author=member.display_name ) file = await in_executor( @@ -71,7 +94,7 @@ class PfpModify(commands.Cog): ) embed.set_image(url=f"attachment://{file_name}") - embed.set_footer(text=f"Made by {ctx.author.display_name}.", icon_url=ctx.author.avatar_url) + embed.set_footer(text=f"Made by {member.display_name}.", icon_url=member.avatar_url) await ctx.send(embed=embed, file=file) @@ -96,6 +119,11 @@ class PfpModify(commands.Cog): return args[0] async with ctx.typing(): + member = await self._fetch_member(ctx.author.id) + if not member: + ctx.send(f"{Emojis.cross_mark} Could not get member info.") + return + egg = None if colours: send_message = ctx.send @@ -106,10 +134,10 @@ class PfpModify(commands.Cog): return ctx.send = send_message # Reassigns ctx.send - image_bytes = await ctx.author.avatar_url_as(size=256).read() + image_bytes = await member.avatar_url_as(size=256).read() file_name = FILENAME_STRING.format( effect="easterified_avatar", - author=ctx.author.display_name + author=member.display_name ) file = await in_executor( @@ -125,7 +153,7 @@ class PfpModify(commands.Cog): description="Here is your lovely avatar, all bright and colourful\nwith Easter pastel colours. Enjoy :D" ) embed.set_image(url=f"attachment://{file_name}") - embed.set_footer(text=f"Made by {ctx.author.display_name}.", icon_url=ctx.author.avatar_url) + embed.set_footer(text=f"Made by {member.display_name}.", icon_url=member.avatar_url) await ctx.send(file=file, embed=embed) @@ -183,7 +211,11 @@ class PfpModify(commands.Cog): return async with ctx.typing(): - image_bytes = await ctx.author.avatar_url.read() + member = await self._fetch_member(ctx.author.id) + if not member: + ctx.send(f"{Emojis.cross_mark} Could not get member info.") + return + image_bytes = await member.avatar_url.read() await self.send_pride_image(ctx, image_bytes, pixels, flag, option) @prideavatar.command() @@ -235,17 +267,22 @@ class PfpModify(commands.Cog): root_aliases=('spookyavatar', 'spookify', 'savatar'), brief='Spookify an user\'s avatar.' ) - async def spooky_avatar(self, ctx: commands.Context, user: discord.Member = None) -> None: + async def spooky_avatar(self, ctx: commands.Context, member: discord.Member = None) -> None: """This "spookifies" the given user's avatar, with a random *spooky* effect.""" - if user is None: - user = ctx.message.author + if member is None: + member = ctx.author + + member = await self._fetch_member(member.id) + if not member: + ctx.send(f"{Emojis.cross_mark} Could not get member info.") + return async with ctx.typing(): - image_bytes = await user.avatar_url.read() + image_bytes = await member.avatar_url.read() file_name = FILENAME_STRING.format( effect="spooky_avatar", - author=user.display_name + author=member.display_name ) file = await in_executor( PfpEffects.apply_effect, @@ -258,7 +295,7 @@ class PfpModify(commands.Cog): title="Is this you or am I just really paranoid?", colour=Colours.soft_red ) - embed.set_author(name=user.name, icon_url=user.avatar_url) + embed.set_author(name=member.name, icon_url=member.avatar_url) embed.set_image(url=f"attachment://{file_name}") embed.set_footer(text=f"Made by {ctx.author.display_name}.", icon_url=ctx.author.avatar_url) -- cgit v1.2.3 From 422f6d50b8eeafdf2118f1d0e96c420c0b1391ce Mon Sep 17 00:00:00 2001 From: ChrisJL Date: Thu, 8 Apr 2021 10:04:14 +0100 Subject: Refactor pfp cog to remove unnecessary params and calls Co-authored-by: ToxicKidz <78174417+ToxicKidz@users.noreply.github.com> --- bot/exts/evergreen/profile_pic_modification/_effects.py | 2 +- bot/exts/evergreen/profile_pic_modification/pfp_modify.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/profile_pic_modification/_effects.py b/bot/exts/evergreen/profile_pic_modification/_effects.py index a1069db7..e415d700 100644 --- a/bot/exts/evergreen/profile_pic_modification/_effects.py +++ b/bot/exts/evergreen/profile_pic_modification/_effects.py @@ -12,7 +12,7 @@ class PfpEffects: """ Implements various image modifying effects, for the PfpModify cog. - All of these fuctions are slow, and blocking, so should be ran in executors. + All of these fuctions are slow, and blocking, so they should be ran in executors. """ @staticmethod diff --git a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py index c4b74d04..712631d1 100644 --- a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py +++ b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py @@ -21,7 +21,7 @@ _EXECUTOR = ThreadPoolExecutor(10) FILENAME_STRING = "{effect}_{author}.png" with open('bot/resources/pride/gender_options.json') as f: - GENDER_OPTIONS = json.loads(f.read()) + GENDER_OPTIONS = json.load(f) async def in_executor(func: t.Callable, *args) -> t.Any: @@ -98,7 +98,7 @@ class PfpModify(commands.Cog): await ctx.send(embed=embed, file=file) - @pfp_modify.command(pass_context=True, aliases=["easterify"], root_aliases=("easterify", "avatareasterify")) + @pfp_modify.command(aliases=["easterify"], root_aliases=("easterify", "avatareasterify")) async def avatareasterify(self, ctx: commands.Context, *colours: t.Union[discord.Colour, str]) -> None: """ This "Easterifies" the user's avatar. -- cgit v1.2.3 From c9e4916cb1cad8d43148030660575f5084710d38 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 8 Apr 2021 10:05:53 +0100 Subject: Ensure to await ctx.send() calls --- bot/exts/evergreen/profile_pic_modification/pfp_modify.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py index 712631d1..68d68927 100644 --- a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py +++ b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py @@ -72,7 +72,7 @@ class PfpModify(commands.Cog): async with ctx.typing(): member = await self._fetch_member(ctx.author.id) if not member: - ctx.send(f"{Emojis.cross_mark} Could not get member info.") + await ctx.send(f"{Emojis.cross_mark} Could not get member info.") return image_bytes = await member.avatar_url.read() @@ -121,7 +121,7 @@ class PfpModify(commands.Cog): async with ctx.typing(): member = await self._fetch_member(ctx.author.id) if not member: - ctx.send(f"{Emojis.cross_mark} Could not get member info.") + await ctx.send(f"{Emojis.cross_mark} Could not get member info.") return egg = None @@ -213,7 +213,7 @@ class PfpModify(commands.Cog): async with ctx.typing(): member = await self._fetch_member(ctx.author.id) if not member: - ctx.send(f"{Emojis.cross_mark} Could not get member info.") + await ctx.send(f"{Emojis.cross_mark} Could not get member info.") return image_bytes = await member.avatar_url.read() await self.send_pride_image(ctx, image_bytes, pixels, flag, option) @@ -274,7 +274,7 @@ class PfpModify(commands.Cog): member = await self._fetch_member(member.id) if not member: - ctx.send(f"{Emojis.cross_mark} Could not get member info.") + await ctx.send(f"{Emojis.cross_mark} Could not get member info.") return async with ctx.typing(): -- cgit v1.2.3 From 70fa9a8e36d586d6fbc1690a80a598e94506883b Mon Sep 17 00:00:00 2001 From: janine9vn Date: Fri, 9 Apr 2021 12:52:04 -0400 Subject: Cutover of rattlesnake to lancebot This is an initial cutover of the rattlesnake internal eval to Sir Lancebot. This commit by itself will not work. This is a simple drop in of rattlesnake code so there is context as to what has changed and why. --- bot/exts/internal_eval/__init__.py | 0 bot/exts/internal_eval/internal_eval.py | 152 ++++++++++++++++++++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 bot/exts/internal_eval/__init__.py create mode 100644 bot/exts/internal_eval/internal_eval.py (limited to 'bot') diff --git a/bot/exts/internal_eval/__init__.py b/bot/exts/internal_eval/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/bot/exts/internal_eval/internal_eval.py b/bot/exts/internal_eval/internal_eval.py new file mode 100644 index 00000000..f6812942 --- /dev/null +++ b/bot/exts/internal_eval/internal_eval.py @@ -0,0 +1,152 @@ +import logging +import re +import textwrap +import typing + +import discord +from discord.ext import commands + +from rattlesnake.bot import Rattlesnake +from rattlesnake.constants import ADMIN_ROLES +from rattlesnake.utils import in_whitelist +from .helpers import EvalContext + +__all__ = ["InternalEval"] + +log = logging.getLogger("rattlesnake.exts.admin_tools.internal_eval") + +CODEBLOCK_REGEX = re.compile(r"(^```(py(thon)?)?\n)|(```$)") + + +class InternalEval(commands.Cog): + """Top secret code evaluation for admins and owners.""" + + def __init__(self, bot: Rattlesnake): + self.bot = bot + self.locals = {} + + @staticmethod + def shorten_output( + output: str, + max_length: int = 1900, + placeholder: str = "\n[output truncated]" + ) -> str: + """ + Shorten the `output` so it's shorter than `max_length`. + There are three tactics for this, tried in the following order: + - Shorten the output on a line-by-line basis + - Shorten the output on any whitespace character + - Shorten the output solely on character count + """ + max_length = max_length - len(placeholder) + + shortened_output = [] + char_count = 0 + for line in output.split("\n"): + if char_count + len(line) > max_length: + break + shortened_output.append(line) + char_count += len(line) + 1 # account for (possible) line ending + + if shortened_output: + shortened_output.append(placeholder) + return "\n".join(shortened_output) + + shortened_output = textwrap.shorten(output, width=max_length, placeholder=placeholder) + + if shortened_output.strip() == placeholder.strip(): + # `textwrap` was unable to find whitespace to shorten on, so it has + # reduced the output to just the placeholder. Let's shorten based on + # characters instead. + shortened_output = output[:max_length] + placeholder + + return shortened_output + + async def _upload_output(self, output: str) -> typing.Optional[str]: + """Upload `internal eval` output to our pastebin and return the url.""" + try: + async with self.bot.http_session.post( + "https://paste.pythondiscord.com/documents", data=output, raise_for_status=True + ) as resp: + data = await resp.json() + + if "key" in data: + return f"https://paste.pythondiscord.com/{data['key']}" + except Exception: + # 400 (Bad Request) means there are too many characters + log.exception("Failed to upload `internal eval` output to paste service!") + + async def _send_output(self, ctx: commands.Context, output: str) -> None: + """Send the `internal eval` output to the command invocation context.""" + upload_message = "" + if len(output) >= 1980: + # The output is too long, let's truncate it for in-channel output and + # upload the complete output to the paste service. + url = await self._upload_output(output) + + if url: + upload_message = f"\nFull output here: {url}" + else: + upload_message = "\n:warning: Failed to upload full output!" + + output = self.shorten_output(output) + + await ctx.send(f"```py\n{output}```{upload_message}") + + async def _eval(self, ctx: commands.Context, code: str) -> None: + """Evaluate the `code` in the current evaluation context.""" + if code.startswith("exit"): + self.locals = {} + await ctx.send("The evaluation context was reset.") + return + + context_vars = { + "message": ctx.message, + "author": ctx.message.author, + "channel": ctx.channel, + "guild": ctx.guild, + "ctx": ctx, + "self": self, + "bot": self.bot, + "discord": discord, + } + + eval_context = EvalContext(context_vars, self.locals) + + log.trace("Preparing the evaluation by parsing the AST of the code") + error = eval_context.prepare_eval(code) + + if error: + log.trace("The code can't be evaluated due to an error") + await ctx.send(f"```py\n{error}\n```") + return + + log.trace("Evaluate the AST we've generated for the evaluation") + new_locals = await eval_context.run_eval() + + log.trace("Updating locals with those set during evaluation") + self.locals.update(new_locals) + + log.trace("Sending the formatted output back to the context") + await self._send_output(ctx, eval_context.format_output()) + + @commands.group(name='internal', aliases=('int',)) + @in_whitelist(roles=ADMIN_ROLES) + async def internal_group(self, ctx: commands.Context) -> None: + """Internal commands. Top secret!""" + if not ctx.invoked_subcommand: + await ctx.send_help(ctx.command) + + @internal_group.command(name='eval', aliases=('e',)) + @in_whitelist(roles=ADMIN_ROLES) + async def eval(self, ctx: commands.Context, *, code: str) -> None: + """Run eval in a REPL-like format.""" + code = CODEBLOCK_REGEX.sub("", code.strip()) + await self._eval(ctx, code) + + @internal_group.command(name='reset', aliases=("clear", "exit", "r", "c")) + @in_whitelist(roles=ADMIN_ROLES) + async def reset(self, ctx: commands.Context) -> None: + """Run eval in a REPL-like format.""" + self.locals = {} + await ctx.send("The evaluation context was reset.") -- cgit v1.2.3 From 8ca0fd85045bf9fbb9b78a117f3a2e30ee1d1fa5 Mon Sep 17 00:00:00 2001 From: janine9vn Date: Fri, 9 Apr 2021 12:54:59 -0400 Subject: Add helpers for internal eval Cutting over the rattlesnake helpers specifically for internal_eval. I am mirroring the rattlesnake structure as much as I can initially to ensure basic functionality before migrating functions to fit more within Sir Lancebot's file structure. --- bot/exts/internal_eval/helpers.py | 243 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 243 insertions(+) create mode 100644 bot/exts/internal_eval/helpers.py (limited to 'bot') diff --git a/bot/exts/internal_eval/helpers.py b/bot/exts/internal_eval/helpers.py new file mode 100644 index 00000000..5c602e4d --- /dev/null +++ b/bot/exts/internal_eval/helpers.py @@ -0,0 +1,243 @@ +import ast +import collections +import contextlib +import functools +import inspect +import io +import logging +import sys +import traceback +import types +import typing + + +log = logging.getLogger("rattlesnake.exts.admin_tools.internal_eval") + +# A type alias to annotate the tuples returned from `sys.exc_info()` +ExcInfo = typing.Tuple[typing.Type[Exception], Exception, types.TracebackType] +Namespace = typing.Dict[str, typing.Any] + +# This will be used as an coroutine function wrapper for the code +# to be evaluated. The wrapper contains one `pass` statement which +# will be replaced with `ast` with the code that we want to have +# evaluated. +# The function redirects output and captures exceptions that were +# raised in the code we evaluate. The latter is used to provide a +# meaningful traceback to the end user. +EVAL_WRAPPER = """ +async def _eval_wrapper_function(): + try: + with contextlib.redirect_stdout(_eval_context.stdout): + pass + if '_value_last_expression' in locals(): + if inspect.isawaitable(_value_last_expression): + _value_last_expression = await _value_last_expression + _eval_context._value_last_expression = _value_last_expression + else: + _eval_context._value_last_expression = None + except Exception: + _eval_context.exc_info = sys.exc_info() + finally: + _eval_context.locals = locals() +_eval_context.function = _eval_wrapper_function +""" + + +def format_internal_eval_exception(exc_info: ExcInfo, code: str) -> str: + """Format an exception caught while evaluation code by inserting lines.""" + exc_type, exc_value, tb = exc_info + stack_summary = traceback.StackSummary.extract(traceback.walk_tb(tb)) + code = code.split("\n") + + output = ["Traceback (most recent call last):"] + for frame in stack_summary: + if frame.filename == "": + line = code[frame.lineno - 1].lstrip() + + if frame.name == "_eval_wrapper_function": + name = "" + else: + name = frame.name + else: + line = frame.line + name = frame.name + + output.append( + f' File "{frame.filename}", line {frame.lineno}, in {name}\n' + f" {line}" + ) + + output.extend(traceback.format_exception_only(exc_type, exc_value)) + return "\n".join(output) + + +class EvalContext: + """ + Represents the current `internal eval` context. + The context remembers names set during earlier runs of `internal eval`. To + clear the context, use the `?internal clear` command. + """ + + def __init__(self, context_vars: Namespace, local_vars: Namespace) -> None: + self._locals = dict(local_vars) + self.context_vars = dict(context_vars) + + self.stdout = io.StringIO() + self._value_last_expression = None + self.exc_info = None + self.code = "" + self.function = None + self.eval_tree = None + + @property + def dependencies(self) -> typing.Dict[str, typing.Any]: + """ + Return a mapping of the dependencies for the wrapper function. + By using a property descriptor, the mapping can't be accidentally + mutated during evaluation. This ensures the dependencies are always + available. + """ + return { + "print": functools.partial(print, file=self.stdout), + "contextlib": contextlib, + "inspect": inspect, + "sys": sys, + "_eval_context": self, + "_": self._value_last_expression, + } + + @property + def locals(self) -> typing.Dict[str, typing.Any]: + """Return a mapping of names->values needed for evaluation.""" + return {**collections.ChainMap(self.dependencies, self.context_vars, self._locals)} + + @locals.setter + def locals(self, locals_: typing.Dict[str, typing.Any]) -> None: + """Update the contextual mapping of names to values.""" + log.trace(f"Updating {self._locals} with {locals_}") + self._locals.update(locals_) + + def prepare_eval(self, code: str) -> typing.Optional[str]: + """Prepare an evaluation by processing the code and setting up the context.""" + self.code = code + + if not self.code: + log.debug("No code was attached to the evaluation command") + return "[No code detected]" + + try: + code_tree = ast.parse(code, filename="") + except SyntaxError: + log.debug("Got a SyntaxError while parsing the eval code") + return "".join(traceback.format_exception(*sys.exc_info(), limit=0)) + + log.trace("Parsing the AST to see if there's a trailing expression we need to capture") + code_tree = CaptureLastExpression(code_tree).capture() + + log.trace("Wrapping the AST in the AST of the wrapper coroutine") + eval_tree = WrapEvalCodeTree(code_tree).wrap() + + self.eval_tree = eval_tree + return None + + async def run_eval(self) -> Namespace: + """Run the evaluation and return the updated locals.""" + log.trace("Compiling the AST to bytecode using `exec` mode") + compiled_code = compile(self.eval_tree, filename="", mode="exec") + + log.trace("Executing the compiled code with the desired namespace environment") + exec(compiled_code, self.locals) # noqa: B102,S102 + + log.trace("Awaiting the created evaluation wrapper coroutine.") + await self.function() + + log.trace("Returning the updated captured locals.") + return self._locals + + def format_output(self) -> str: + """Format the output of the most recent evaluation.""" + output = [] + + log.trace(f"Getting output from stdout `{id(self.stdout)}`") + stdout_text = self.stdout.getvalue() + if stdout_text: + log.trace("Appending output captured from stdout/print") + output.append(stdout_text) + + if self._value_last_expression is not None: + log.trace("Appending the output of a captured trialing expression") + output.append(f"[Captured] {self._value_last_expression!r}") + + if self.exc_info: + log.trace("Appending exception information") + output.append(format_internal_eval_exception(self.exc_info, self.code)) + + log.trace(f"Generated output: {output!r}") + return "\n".join(output) or "[No output]" + + +class WrapEvalCodeTree(ast.NodeTransformer): + """Wraps the AST of eval code with the wrapper function.""" + + def __init__(self, eval_code_tree: ast.AST, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + self.eval_code_tree = eval_code_tree + + # To avoid mutable aliasing, parse the WRAPPER_FUNC for each wrapping + self.wrapper = ast.parse(EVAL_WRAPPER, filename="") + + def wrap(self) -> ast.AST: + """Wrap the tree of the code by the tree of the wrapper function.""" + new_tree = self.visit(self.wrapper) + return ast.fix_missing_locations(new_tree) + + def visit_Pass(self, node: ast.Pass) -> typing.List[ast.AST]: # noqa: N802 + """ + Replace the `_ast.Pass` node in the wrapper function by the eval AST. + This method works on the assumption that there's a single `pass` + statement in the wrapper function. + """ + return list(ast.iter_child_nodes(self.eval_code_tree)) + + +class CaptureLastExpression(ast.NodeTransformer): + """Captures the return value from a loose expression.""" + + def __init__(self, tree: ast.AST, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + self.tree = tree + self.last_node = list(ast.iter_child_nodes(tree))[-1] + + def visit_Expr(self, node: ast.Expr) -> typing.Union[ast.Expr, ast.Assign]: # noqa: N802 + """ + Replace the Expr node that is last child node of Module with an assignment. + We use an assignment to capture the value of the last node, if it's a loose + Expr node. Normally, the value of an Expr node is lost, meaning we don't get + the output of such a last "loose" expression. By assigning it a name, we can + retrieve it for our output. + """ + if node is not self.last_node: + return node + + log.trace("Found a trailing last expression in the evaluation code") + + log.trace("Creating assignment statement with trailing expression as the right-hand side") + right_hand_side = list(ast.iter_child_nodes(node))[0] + + assignment = ast.Assign( + targets=[ast.Name(id='_value_last_expression', ctx=ast.Store())], + value=right_hand_side, + lineno=node.lineno, + col_offset=0, + ) + ast.fix_missing_locations(assignment) + return assignment + + def capture(self) -> ast.AST: + """Capture the value of the last expression with an assignment.""" + if not isinstance(self.last_node, ast.Expr): + # We only have to replace a node if the very last node is an Expr node + return self.tree + + new_tree = self.visit(self.tree) + return ast.fix_missing_locations(new_tree) -- cgit v1.2.3 From 80a9a40b3c27376657486cf183d4c0c7d4e9880f Mon Sep 17 00:00:00 2001 From: janine9vn Date: Fri, 9 Apr 2021 13:41:45 -0400 Subject: Realigned to SirLancebot Structure The code for rattlesnakes's internal eval was aligned to Sir Lancebot's structure. It was mostly renaming rattlesnake to bot and changing how some of the imports were setup as. It also included changing the __init__.py to match the Sir Lancebot cog structure. Additionally, the whitelist check has been significantly simplified to only be a role check for the admin role. The rattlesnake implementation had a more robust `in_whitelist` decorator, so it may be worth investigating adding that in if we see fit. For now, it's a simple `with_role` decorator check. The name of the cog file itself was changed to include an underscore to sidestep what I think was a namespace collision that would prevent the setup function from properly running. --- bot/exts/internal_eval/__init__.py | 10 ++ bot/exts/internal_eval/_helpers.py | 243 +++++++++++++++++++++++++++++++ bot/exts/internal_eval/_internal_eval.py | 152 +++++++++++++++++++ 3 files changed, 405 insertions(+) create mode 100644 bot/exts/internal_eval/_helpers.py create mode 100644 bot/exts/internal_eval/_internal_eval.py (limited to 'bot') diff --git a/bot/exts/internal_eval/__init__.py b/bot/exts/internal_eval/__init__.py index e69de29b..695fa74d 100644 --- a/bot/exts/internal_eval/__init__.py +++ b/bot/exts/internal_eval/__init__.py @@ -0,0 +1,10 @@ +from bot.bot import Bot + + +def setup(bot: Bot) -> None: + """Set up the Internal Eval extension.""" + # Import the Cog at runtime to prevent side effects like defining + # RedisCache instances too early. + from ._internal_eval import InternalEval + + bot.add_cog(InternalEval(bot)) diff --git a/bot/exts/internal_eval/_helpers.py b/bot/exts/internal_eval/_helpers.py new file mode 100644 index 00000000..5c602e4d --- /dev/null +++ b/bot/exts/internal_eval/_helpers.py @@ -0,0 +1,243 @@ +import ast +import collections +import contextlib +import functools +import inspect +import io +import logging +import sys +import traceback +import types +import typing + + +log = logging.getLogger("rattlesnake.exts.admin_tools.internal_eval") + +# A type alias to annotate the tuples returned from `sys.exc_info()` +ExcInfo = typing.Tuple[typing.Type[Exception], Exception, types.TracebackType] +Namespace = typing.Dict[str, typing.Any] + +# This will be used as an coroutine function wrapper for the code +# to be evaluated. The wrapper contains one `pass` statement which +# will be replaced with `ast` with the code that we want to have +# evaluated. +# The function redirects output and captures exceptions that were +# raised in the code we evaluate. The latter is used to provide a +# meaningful traceback to the end user. +EVAL_WRAPPER = """ +async def _eval_wrapper_function(): + try: + with contextlib.redirect_stdout(_eval_context.stdout): + pass + if '_value_last_expression' in locals(): + if inspect.isawaitable(_value_last_expression): + _value_last_expression = await _value_last_expression + _eval_context._value_last_expression = _value_last_expression + else: + _eval_context._value_last_expression = None + except Exception: + _eval_context.exc_info = sys.exc_info() + finally: + _eval_context.locals = locals() +_eval_context.function = _eval_wrapper_function +""" + + +def format_internal_eval_exception(exc_info: ExcInfo, code: str) -> str: + """Format an exception caught while evaluation code by inserting lines.""" + exc_type, exc_value, tb = exc_info + stack_summary = traceback.StackSummary.extract(traceback.walk_tb(tb)) + code = code.split("\n") + + output = ["Traceback (most recent call last):"] + for frame in stack_summary: + if frame.filename == "": + line = code[frame.lineno - 1].lstrip() + + if frame.name == "_eval_wrapper_function": + name = "" + else: + name = frame.name + else: + line = frame.line + name = frame.name + + output.append( + f' File "{frame.filename}", line {frame.lineno}, in {name}\n' + f" {line}" + ) + + output.extend(traceback.format_exception_only(exc_type, exc_value)) + return "\n".join(output) + + +class EvalContext: + """ + Represents the current `internal eval` context. + The context remembers names set during earlier runs of `internal eval`. To + clear the context, use the `?internal clear` command. + """ + + def __init__(self, context_vars: Namespace, local_vars: Namespace) -> None: + self._locals = dict(local_vars) + self.context_vars = dict(context_vars) + + self.stdout = io.StringIO() + self._value_last_expression = None + self.exc_info = None + self.code = "" + self.function = None + self.eval_tree = None + + @property + def dependencies(self) -> typing.Dict[str, typing.Any]: + """ + Return a mapping of the dependencies for the wrapper function. + By using a property descriptor, the mapping can't be accidentally + mutated during evaluation. This ensures the dependencies are always + available. + """ + return { + "print": functools.partial(print, file=self.stdout), + "contextlib": contextlib, + "inspect": inspect, + "sys": sys, + "_eval_context": self, + "_": self._value_last_expression, + } + + @property + def locals(self) -> typing.Dict[str, typing.Any]: + """Return a mapping of names->values needed for evaluation.""" + return {**collections.ChainMap(self.dependencies, self.context_vars, self._locals)} + + @locals.setter + def locals(self, locals_: typing.Dict[str, typing.Any]) -> None: + """Update the contextual mapping of names to values.""" + log.trace(f"Updating {self._locals} with {locals_}") + self._locals.update(locals_) + + def prepare_eval(self, code: str) -> typing.Optional[str]: + """Prepare an evaluation by processing the code and setting up the context.""" + self.code = code + + if not self.code: + log.debug("No code was attached to the evaluation command") + return "[No code detected]" + + try: + code_tree = ast.parse(code, filename="") + except SyntaxError: + log.debug("Got a SyntaxError while parsing the eval code") + return "".join(traceback.format_exception(*sys.exc_info(), limit=0)) + + log.trace("Parsing the AST to see if there's a trailing expression we need to capture") + code_tree = CaptureLastExpression(code_tree).capture() + + log.trace("Wrapping the AST in the AST of the wrapper coroutine") + eval_tree = WrapEvalCodeTree(code_tree).wrap() + + self.eval_tree = eval_tree + return None + + async def run_eval(self) -> Namespace: + """Run the evaluation and return the updated locals.""" + log.trace("Compiling the AST to bytecode using `exec` mode") + compiled_code = compile(self.eval_tree, filename="", mode="exec") + + log.trace("Executing the compiled code with the desired namespace environment") + exec(compiled_code, self.locals) # noqa: B102,S102 + + log.trace("Awaiting the created evaluation wrapper coroutine.") + await self.function() + + log.trace("Returning the updated captured locals.") + return self._locals + + def format_output(self) -> str: + """Format the output of the most recent evaluation.""" + output = [] + + log.trace(f"Getting output from stdout `{id(self.stdout)}`") + stdout_text = self.stdout.getvalue() + if stdout_text: + log.trace("Appending output captured from stdout/print") + output.append(stdout_text) + + if self._value_last_expression is not None: + log.trace("Appending the output of a captured trialing expression") + output.append(f"[Captured] {self._value_last_expression!r}") + + if self.exc_info: + log.trace("Appending exception information") + output.append(format_internal_eval_exception(self.exc_info, self.code)) + + log.trace(f"Generated output: {output!r}") + return "\n".join(output) or "[No output]" + + +class WrapEvalCodeTree(ast.NodeTransformer): + """Wraps the AST of eval code with the wrapper function.""" + + def __init__(self, eval_code_tree: ast.AST, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + self.eval_code_tree = eval_code_tree + + # To avoid mutable aliasing, parse the WRAPPER_FUNC for each wrapping + self.wrapper = ast.parse(EVAL_WRAPPER, filename="") + + def wrap(self) -> ast.AST: + """Wrap the tree of the code by the tree of the wrapper function.""" + new_tree = self.visit(self.wrapper) + return ast.fix_missing_locations(new_tree) + + def visit_Pass(self, node: ast.Pass) -> typing.List[ast.AST]: # noqa: N802 + """ + Replace the `_ast.Pass` node in the wrapper function by the eval AST. + This method works on the assumption that there's a single `pass` + statement in the wrapper function. + """ + return list(ast.iter_child_nodes(self.eval_code_tree)) + + +class CaptureLastExpression(ast.NodeTransformer): + """Captures the return value from a loose expression.""" + + def __init__(self, tree: ast.AST, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + self.tree = tree + self.last_node = list(ast.iter_child_nodes(tree))[-1] + + def visit_Expr(self, node: ast.Expr) -> typing.Union[ast.Expr, ast.Assign]: # noqa: N802 + """ + Replace the Expr node that is last child node of Module with an assignment. + We use an assignment to capture the value of the last node, if it's a loose + Expr node. Normally, the value of an Expr node is lost, meaning we don't get + the output of such a last "loose" expression. By assigning it a name, we can + retrieve it for our output. + """ + if node is not self.last_node: + return node + + log.trace("Found a trailing last expression in the evaluation code") + + log.trace("Creating assignment statement with trailing expression as the right-hand side") + right_hand_side = list(ast.iter_child_nodes(node))[0] + + assignment = ast.Assign( + targets=[ast.Name(id='_value_last_expression', ctx=ast.Store())], + value=right_hand_side, + lineno=node.lineno, + col_offset=0, + ) + ast.fix_missing_locations(assignment) + return assignment + + def capture(self) -> ast.AST: + """Capture the value of the last expression with an assignment.""" + if not isinstance(self.last_node, ast.Expr): + # We only have to replace a node if the very last node is an Expr node + return self.tree + + new_tree = self.visit(self.tree) + return ast.fix_missing_locations(new_tree) diff --git a/bot/exts/internal_eval/_internal_eval.py b/bot/exts/internal_eval/_internal_eval.py new file mode 100644 index 00000000..f7a0946b --- /dev/null +++ b/bot/exts/internal_eval/_internal_eval.py @@ -0,0 +1,152 @@ +import logging +import re +import textwrap +import typing + +import discord +from discord.ext import commands + +from bot.bot import Bot +from bot.constants import Roles +from bot.utils.decorators import with_role +from ._helpers import EvalContext + +__all__ = ["InternalEval"] + +log = logging.getLogger("rattlesnake.exts.admin_tools.internal_eval") + +CODEBLOCK_REGEX = re.compile(r"(^```(py(thon)?)?\n)|(```$)") + + +class InternalEval(commands.Cog): + """Top secret code evaluation for admins and owners.""" + + def __init__(self, bot: Bot): + self.bot = bot + self.locals = {} + + @staticmethod + def shorten_output( + output: str, + max_length: int = 1900, + placeholder: str = "\n[output truncated]" + ) -> str: + """ + Shorten the `output` so it's shorter than `max_length`. + There are three tactics for this, tried in the following order: + - Shorten the output on a line-by-line basis + - Shorten the output on any whitespace character + - Shorten the output solely on character count + """ + max_length = max_length - len(placeholder) + + shortened_output = [] + char_count = 0 + for line in output.split("\n"): + if char_count + len(line) > max_length: + break + shortened_output.append(line) + char_count += len(line) + 1 # account for (possible) line ending + + if shortened_output: + shortened_output.append(placeholder) + return "\n".join(shortened_output) + + shortened_output = textwrap.shorten(output, width=max_length, placeholder=placeholder) + + if shortened_output.strip() == placeholder.strip(): + # `textwrap` was unable to find whitespace to shorten on, so it has + # reduced the output to just the placeholder. Let's shorten based on + # characters instead. + shortened_output = output[:max_length] + placeholder + + return shortened_output + + async def _upload_output(self, output: str) -> typing.Optional[str]: + """Upload `internal eval` output to our pastebin and return the url.""" + try: + async with self.bot.http_session.post( + "https://paste.pythondiscord.com/documents", data=output, raise_for_status=True + ) as resp: + data = await resp.json() + + if "key" in data: + return f"https://paste.pythondiscord.com/{data['key']}" + except Exception: + # 400 (Bad Request) means there are too many characters + log.exception("Failed to upload `internal eval` output to paste service!") + + async def _send_output(self, ctx: commands.Context, output: str) -> None: + """Send the `internal eval` output to the command invocation context.""" + upload_message = "" + if len(output) >= 1980: + # The output is too long, let's truncate it for in-channel output and + # upload the complete output to the paste service. + url = await self._upload_output(output) + + if url: + upload_message = f"\nFull output here: {url}" + else: + upload_message = "\n:warning: Failed to upload full output!" + + output = self.shorten_output(output) + + await ctx.send(f"```py\n{output}```{upload_message}") + + async def _eval(self, ctx: commands.Context, code: str) -> None: + """Evaluate the `code` in the current evaluation context.""" + if code.startswith("exit"): + self.locals = {} + await ctx.send("The evaluation context was reset.") + return + + context_vars = { + "message": ctx.message, + "author": ctx.message.author, + "channel": ctx.channel, + "guild": ctx.guild, + "ctx": ctx, + "self": self, + "bot": self.bot, + "discord": discord, + } + + eval_context = EvalContext(context_vars, self.locals) + + log.trace("Preparing the evaluation by parsing the AST of the code") + error = eval_context.prepare_eval(code) + + if error: + log.trace("The code can't be evaluated due to an error") + await ctx.send(f"```py\n{error}\n```") + return + + log.trace("Evaluate the AST we've generated for the evaluation") + new_locals = await eval_context.run_eval() + + log.trace("Updating locals with those set during evaluation") + self.locals.update(new_locals) + + log.trace("Sending the formatted output back to the context") + await self._send_output(ctx, eval_context.format_output()) + + @commands.group(name='internal', aliases=('int',)) + @with_role(Roles.admin) + async def internal_group(self, ctx: commands.Context) -> None: + """Internal commands. Top secret!""" + if not ctx.invoked_subcommand: + await ctx.send_help(ctx.command) + + @internal_group.command(name='eval', aliases=('e',)) + @with_role(Roles.admin) + async def eval(self, ctx: commands.Context, *, code: str) -> None: + """Run eval in a REPL-like format.""" + code = CODEBLOCK_REGEX.sub("", code.strip()) + await self._eval(ctx, code) + + @internal_group.command(name='reset', aliases=("clear", "exit", "r", "c")) + @with_role(Roles.admin) + async def reset(self, ctx: commands.Context) -> None: + """Run eval in a REPL-like format.""" + self.locals = {} + await ctx.send("The evaluation context was reset.") -- cgit v1.2.3 From 0ac739dc029664332f1416ba85424c3b396e5660 Mon Sep 17 00:00:00 2001 From: janine9vn Date: Sat, 10 Apr 2021 20:17:47 -0400 Subject: Change names These were missed in a previous commit. It's a simple name change from the original files to better align with Sir Lancebot. --- bot/exts/internal_eval/helpers.py | 243 -------------------------------- bot/exts/internal_eval/internal_eval.py | 152 -------------------- 2 files changed, 395 deletions(-) delete mode 100644 bot/exts/internal_eval/helpers.py delete mode 100644 bot/exts/internal_eval/internal_eval.py (limited to 'bot') diff --git a/bot/exts/internal_eval/helpers.py b/bot/exts/internal_eval/helpers.py deleted file mode 100644 index 5c602e4d..00000000 --- a/bot/exts/internal_eval/helpers.py +++ /dev/null @@ -1,243 +0,0 @@ -import ast -import collections -import contextlib -import functools -import inspect -import io -import logging -import sys -import traceback -import types -import typing - - -log = logging.getLogger("rattlesnake.exts.admin_tools.internal_eval") - -# A type alias to annotate the tuples returned from `sys.exc_info()` -ExcInfo = typing.Tuple[typing.Type[Exception], Exception, types.TracebackType] -Namespace = typing.Dict[str, typing.Any] - -# This will be used as an coroutine function wrapper for the code -# to be evaluated. The wrapper contains one `pass` statement which -# will be replaced with `ast` with the code that we want to have -# evaluated. -# The function redirects output and captures exceptions that were -# raised in the code we evaluate. The latter is used to provide a -# meaningful traceback to the end user. -EVAL_WRAPPER = """ -async def _eval_wrapper_function(): - try: - with contextlib.redirect_stdout(_eval_context.stdout): - pass - if '_value_last_expression' in locals(): - if inspect.isawaitable(_value_last_expression): - _value_last_expression = await _value_last_expression - _eval_context._value_last_expression = _value_last_expression - else: - _eval_context._value_last_expression = None - except Exception: - _eval_context.exc_info = sys.exc_info() - finally: - _eval_context.locals = locals() -_eval_context.function = _eval_wrapper_function -""" - - -def format_internal_eval_exception(exc_info: ExcInfo, code: str) -> str: - """Format an exception caught while evaluation code by inserting lines.""" - exc_type, exc_value, tb = exc_info - stack_summary = traceback.StackSummary.extract(traceback.walk_tb(tb)) - code = code.split("\n") - - output = ["Traceback (most recent call last):"] - for frame in stack_summary: - if frame.filename == "": - line = code[frame.lineno - 1].lstrip() - - if frame.name == "_eval_wrapper_function": - name = "" - else: - name = frame.name - else: - line = frame.line - name = frame.name - - output.append( - f' File "{frame.filename}", line {frame.lineno}, in {name}\n' - f" {line}" - ) - - output.extend(traceback.format_exception_only(exc_type, exc_value)) - return "\n".join(output) - - -class EvalContext: - """ - Represents the current `internal eval` context. - The context remembers names set during earlier runs of `internal eval`. To - clear the context, use the `?internal clear` command. - """ - - def __init__(self, context_vars: Namespace, local_vars: Namespace) -> None: - self._locals = dict(local_vars) - self.context_vars = dict(context_vars) - - self.stdout = io.StringIO() - self._value_last_expression = None - self.exc_info = None - self.code = "" - self.function = None - self.eval_tree = None - - @property - def dependencies(self) -> typing.Dict[str, typing.Any]: - """ - Return a mapping of the dependencies for the wrapper function. - By using a property descriptor, the mapping can't be accidentally - mutated during evaluation. This ensures the dependencies are always - available. - """ - return { - "print": functools.partial(print, file=self.stdout), - "contextlib": contextlib, - "inspect": inspect, - "sys": sys, - "_eval_context": self, - "_": self._value_last_expression, - } - - @property - def locals(self) -> typing.Dict[str, typing.Any]: - """Return a mapping of names->values needed for evaluation.""" - return {**collections.ChainMap(self.dependencies, self.context_vars, self._locals)} - - @locals.setter - def locals(self, locals_: typing.Dict[str, typing.Any]) -> None: - """Update the contextual mapping of names to values.""" - log.trace(f"Updating {self._locals} with {locals_}") - self._locals.update(locals_) - - def prepare_eval(self, code: str) -> typing.Optional[str]: - """Prepare an evaluation by processing the code and setting up the context.""" - self.code = code - - if not self.code: - log.debug("No code was attached to the evaluation command") - return "[No code detected]" - - try: - code_tree = ast.parse(code, filename="") - except SyntaxError: - log.debug("Got a SyntaxError while parsing the eval code") - return "".join(traceback.format_exception(*sys.exc_info(), limit=0)) - - log.trace("Parsing the AST to see if there's a trailing expression we need to capture") - code_tree = CaptureLastExpression(code_tree).capture() - - log.trace("Wrapping the AST in the AST of the wrapper coroutine") - eval_tree = WrapEvalCodeTree(code_tree).wrap() - - self.eval_tree = eval_tree - return None - - async def run_eval(self) -> Namespace: - """Run the evaluation and return the updated locals.""" - log.trace("Compiling the AST to bytecode using `exec` mode") - compiled_code = compile(self.eval_tree, filename="", mode="exec") - - log.trace("Executing the compiled code with the desired namespace environment") - exec(compiled_code, self.locals) # noqa: B102,S102 - - log.trace("Awaiting the created evaluation wrapper coroutine.") - await self.function() - - log.trace("Returning the updated captured locals.") - return self._locals - - def format_output(self) -> str: - """Format the output of the most recent evaluation.""" - output = [] - - log.trace(f"Getting output from stdout `{id(self.stdout)}`") - stdout_text = self.stdout.getvalue() - if stdout_text: - log.trace("Appending output captured from stdout/print") - output.append(stdout_text) - - if self._value_last_expression is not None: - log.trace("Appending the output of a captured trialing expression") - output.append(f"[Captured] {self._value_last_expression!r}") - - if self.exc_info: - log.trace("Appending exception information") - output.append(format_internal_eval_exception(self.exc_info, self.code)) - - log.trace(f"Generated output: {output!r}") - return "\n".join(output) or "[No output]" - - -class WrapEvalCodeTree(ast.NodeTransformer): - """Wraps the AST of eval code with the wrapper function.""" - - def __init__(self, eval_code_tree: ast.AST, *args, **kwargs) -> None: - super().__init__(*args, **kwargs) - self.eval_code_tree = eval_code_tree - - # To avoid mutable aliasing, parse the WRAPPER_FUNC for each wrapping - self.wrapper = ast.parse(EVAL_WRAPPER, filename="") - - def wrap(self) -> ast.AST: - """Wrap the tree of the code by the tree of the wrapper function.""" - new_tree = self.visit(self.wrapper) - return ast.fix_missing_locations(new_tree) - - def visit_Pass(self, node: ast.Pass) -> typing.List[ast.AST]: # noqa: N802 - """ - Replace the `_ast.Pass` node in the wrapper function by the eval AST. - This method works on the assumption that there's a single `pass` - statement in the wrapper function. - """ - return list(ast.iter_child_nodes(self.eval_code_tree)) - - -class CaptureLastExpression(ast.NodeTransformer): - """Captures the return value from a loose expression.""" - - def __init__(self, tree: ast.AST, *args, **kwargs) -> None: - super().__init__(*args, **kwargs) - self.tree = tree - self.last_node = list(ast.iter_child_nodes(tree))[-1] - - def visit_Expr(self, node: ast.Expr) -> typing.Union[ast.Expr, ast.Assign]: # noqa: N802 - """ - Replace the Expr node that is last child node of Module with an assignment. - We use an assignment to capture the value of the last node, if it's a loose - Expr node. Normally, the value of an Expr node is lost, meaning we don't get - the output of such a last "loose" expression. By assigning it a name, we can - retrieve it for our output. - """ - if node is not self.last_node: - return node - - log.trace("Found a trailing last expression in the evaluation code") - - log.trace("Creating assignment statement with trailing expression as the right-hand side") - right_hand_side = list(ast.iter_child_nodes(node))[0] - - assignment = ast.Assign( - targets=[ast.Name(id='_value_last_expression', ctx=ast.Store())], - value=right_hand_side, - lineno=node.lineno, - col_offset=0, - ) - ast.fix_missing_locations(assignment) - return assignment - - def capture(self) -> ast.AST: - """Capture the value of the last expression with an assignment.""" - if not isinstance(self.last_node, ast.Expr): - # We only have to replace a node if the very last node is an Expr node - return self.tree - - new_tree = self.visit(self.tree) - return ast.fix_missing_locations(new_tree) diff --git a/bot/exts/internal_eval/internal_eval.py b/bot/exts/internal_eval/internal_eval.py deleted file mode 100644 index f6812942..00000000 --- a/bot/exts/internal_eval/internal_eval.py +++ /dev/null @@ -1,152 +0,0 @@ -import logging -import re -import textwrap -import typing - -import discord -from discord.ext import commands - -from rattlesnake.bot import Rattlesnake -from rattlesnake.constants import ADMIN_ROLES -from rattlesnake.utils import in_whitelist -from .helpers import EvalContext - -__all__ = ["InternalEval"] - -log = logging.getLogger("rattlesnake.exts.admin_tools.internal_eval") - -CODEBLOCK_REGEX = re.compile(r"(^```(py(thon)?)?\n)|(```$)") - - -class InternalEval(commands.Cog): - """Top secret code evaluation for admins and owners.""" - - def __init__(self, bot: Rattlesnake): - self.bot = bot - self.locals = {} - - @staticmethod - def shorten_output( - output: str, - max_length: int = 1900, - placeholder: str = "\n[output truncated]" - ) -> str: - """ - Shorten the `output` so it's shorter than `max_length`. - There are three tactics for this, tried in the following order: - - Shorten the output on a line-by-line basis - - Shorten the output on any whitespace character - - Shorten the output solely on character count - """ - max_length = max_length - len(placeholder) - - shortened_output = [] - char_count = 0 - for line in output.split("\n"): - if char_count + len(line) > max_length: - break - shortened_output.append(line) - char_count += len(line) + 1 # account for (possible) line ending - - if shortened_output: - shortened_output.append(placeholder) - return "\n".join(shortened_output) - - shortened_output = textwrap.shorten(output, width=max_length, placeholder=placeholder) - - if shortened_output.strip() == placeholder.strip(): - # `textwrap` was unable to find whitespace to shorten on, so it has - # reduced the output to just the placeholder. Let's shorten based on - # characters instead. - shortened_output = output[:max_length] + placeholder - - return shortened_output - - async def _upload_output(self, output: str) -> typing.Optional[str]: - """Upload `internal eval` output to our pastebin and return the url.""" - try: - async with self.bot.http_session.post( - "https://paste.pythondiscord.com/documents", data=output, raise_for_status=True - ) as resp: - data = await resp.json() - - if "key" in data: - return f"https://paste.pythondiscord.com/{data['key']}" - except Exception: - # 400 (Bad Request) means there are too many characters - log.exception("Failed to upload `internal eval` output to paste service!") - - async def _send_output(self, ctx: commands.Context, output: str) -> None: - """Send the `internal eval` output to the command invocation context.""" - upload_message = "" - if len(output) >= 1980: - # The output is too long, let's truncate it for in-channel output and - # upload the complete output to the paste service. - url = await self._upload_output(output) - - if url: - upload_message = f"\nFull output here: {url}" - else: - upload_message = "\n:warning: Failed to upload full output!" - - output = self.shorten_output(output) - - await ctx.send(f"```py\n{output}```{upload_message}") - - async def _eval(self, ctx: commands.Context, code: str) -> None: - """Evaluate the `code` in the current evaluation context.""" - if code.startswith("exit"): - self.locals = {} - await ctx.send("The evaluation context was reset.") - return - - context_vars = { - "message": ctx.message, - "author": ctx.message.author, - "channel": ctx.channel, - "guild": ctx.guild, - "ctx": ctx, - "self": self, - "bot": self.bot, - "discord": discord, - } - - eval_context = EvalContext(context_vars, self.locals) - - log.trace("Preparing the evaluation by parsing the AST of the code") - error = eval_context.prepare_eval(code) - - if error: - log.trace("The code can't be evaluated due to an error") - await ctx.send(f"```py\n{error}\n```") - return - - log.trace("Evaluate the AST we've generated for the evaluation") - new_locals = await eval_context.run_eval() - - log.trace("Updating locals with those set during evaluation") - self.locals.update(new_locals) - - log.trace("Sending the formatted output back to the context") - await self._send_output(ctx, eval_context.format_output()) - - @commands.group(name='internal', aliases=('int',)) - @in_whitelist(roles=ADMIN_ROLES) - async def internal_group(self, ctx: commands.Context) -> None: - """Internal commands. Top secret!""" - if not ctx.invoked_subcommand: - await ctx.send_help(ctx.command) - - @internal_group.command(name='eval', aliases=('e',)) - @in_whitelist(roles=ADMIN_ROLES) - async def eval(self, ctx: commands.Context, *, code: str) -> None: - """Run eval in a REPL-like format.""" - code = CODEBLOCK_REGEX.sub("", code.strip()) - await self._eval(ctx, code) - - @internal_group.command(name='reset', aliases=("clear", "exit", "r", "c")) - @in_whitelist(roles=ADMIN_ROLES) - async def reset(self, ctx: commands.Context) -> None: - """Run eval in a REPL-like format.""" - self.locals = {} - await ctx.send("The evaluation context was reset.") -- cgit v1.2.3 From 13be6262e6048790062be9dd7daf178fb8b8d0e5 Mon Sep 17 00:00:00 2001 From: janine9vn Date: Sat, 10 Apr 2021 20:23:45 -0400 Subject: Update codeblock regex The snekbox implementation of the codeblock regex was incorporated. This now correctly parses the `code` and ``code`` markdown discord allows. You can also use multiple code blocks with text interrupting it and it will process the different code blocks as one continuous code block. --- bot/exts/internal_eval/_internal_eval.py | 37 +++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/internal_eval/_internal_eval.py b/bot/exts/internal_eval/_internal_eval.py index f7a0946b..45bfbdc3 100644 --- a/bot/exts/internal_eval/_internal_eval.py +++ b/bot/exts/internal_eval/_internal_eval.py @@ -17,6 +17,23 @@ log = logging.getLogger("rattlesnake.exts.admin_tools.internal_eval") CODEBLOCK_REGEX = re.compile(r"(^```(py(thon)?)?\n)|(```$)") +FORMATTED_CODE_REGEX = re.compile( + r"(?P(?P```)|``?)" # code delimiter: 1-3 backticks; (?P=block) only matches if it's a block + r"(?(block)(?:(?P[a-z]+)\n)?)" # if we're in a block, match optional language (only letters plus newline) + r"(?:[ \t]*\n)*" # any blank (empty or tabs/spaces only) lines before the code + r"(?P.*?)" # extract all code inside the markup + r"\s*" # any more whitespace before the end of the code markup + r"(?P=delim)", # match the exact same delimiter from the start again + re.DOTALL | re.IGNORECASE # "." also matches newlines, case insensitive +) + +RAW_CODE_REGEX = re.compile( + r"^(?:[ \t]*\n)*" # any blank (empty or tabs/spaces only) lines before the code + r"(?P.*?)" # extract all the rest as code + r"\s*$", # any trailing whitespace until the end of the string + re.DOTALL # "." also matches newlines +) + class InternalEval(commands.Cog): """Top secret code evaluation for admins and owners.""" @@ -141,7 +158,25 @@ class InternalEval(commands.Cog): @with_role(Roles.admin) async def eval(self, ctx: commands.Context, *, code: str) -> None: """Run eval in a REPL-like format.""" - code = CODEBLOCK_REGEX.sub("", code.strip()) + + if match := list(FORMATTED_CODE_REGEX.finditer(code)): + blocks = [block for block in match if block.group("block")] + + if len(blocks) > 1: + code = '\n'.join(block.group("code") for block in blocks) + info = "several code blocks" + else: + match = match[0] if len(blocks) == 0 else blocks[0] + code, block, lang, delim = match.group("code", "block", "lang", "delim") + if block: + info = (f"'{lang}' highlighted" if lang else "plain") + " code block" + else: + info = f"{delim}-enclosed inline code" + else: + code = RAW_CODE_REGEX.fullmatch(code).group("code") + info = "unformatted or badly formatted code" + + code = textwrap.dedent(code) await self._eval(ctx, code) @internal_group.command(name='reset', aliases=("clear", "exit", "r", "c")) -- cgit v1.2.3 From e946b50c42412a08c823a89e087bd8c67c20a85b Mon Sep 17 00:00:00 2001 From: janine9vn Date: Sat, 10 Apr 2021 20:49:41 -0400 Subject: Removed rogue variable I'm better than this I swear. I can lint before I commit. Don't tell lemon. --- bot/exts/internal_eval/_internal_eval.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) (limited to 'bot') diff --git a/bot/exts/internal_eval/_internal_eval.py b/bot/exts/internal_eval/_internal_eval.py index 45bfbdc3..6f29a661 100644 --- a/bot/exts/internal_eval/_internal_eval.py +++ b/bot/exts/internal_eval/_internal_eval.py @@ -164,17 +164,12 @@ class InternalEval(commands.Cog): if len(blocks) > 1: code = '\n'.join(block.group("code") for block in blocks) - info = "several code blocks" else: match = match[0] if len(blocks) == 0 else blocks[0] code, block, lang, delim = match.group("code", "block", "lang", "delim") - if block: - info = (f"'{lang}' highlighted" if lang else "plain") + " code block" - else: - info = f"{delim}-enclosed inline code" + else: code = RAW_CODE_REGEX.fullmatch(code).group("code") - info = "unformatted or badly formatted code" code = textwrap.dedent(code) await self._eval(ctx, code) -- cgit v1.2.3 From 341908df150b6129055c970c7d2a8d36d76a4bfe Mon Sep 17 00:00:00 2001 From: janine9vn Date: Sat, 10 Apr 2021 21:07:48 -0400 Subject: Blind Fixes at Linting Both my pre-commit and flake8 runs are telling me everything is fine and it's all passed. Github actions is saying otherwise but isn't saying *where*. So here I am with useless linting commits. --- bot/exts/internal_eval/_helpers.py | 4 ++++ bot/exts/internal_eval/_internal_eval.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/internal_eval/_helpers.py b/bot/exts/internal_eval/_helpers.py index 5c602e4d..a8ae5bef 100644 --- a/bot/exts/internal_eval/_helpers.py +++ b/bot/exts/internal_eval/_helpers.py @@ -74,6 +74,7 @@ def format_internal_eval_exception(exc_info: ExcInfo, code: str) -> str: class EvalContext: """ Represents the current `internal eval` context. + The context remembers names set during earlier runs of `internal eval`. To clear the context, use the `?internal clear` command. """ @@ -93,6 +94,7 @@ class EvalContext: def dependencies(self) -> typing.Dict[str, typing.Any]: """ Return a mapping of the dependencies for the wrapper function. + By using a property descriptor, the mapping can't be accidentally mutated during evaluation. This ensures the dependencies are always available. @@ -194,6 +196,7 @@ class WrapEvalCodeTree(ast.NodeTransformer): def visit_Pass(self, node: ast.Pass) -> typing.List[ast.AST]: # noqa: N802 """ Replace the `_ast.Pass` node in the wrapper function by the eval AST. + This method works on the assumption that there's a single `pass` statement in the wrapper function. """ @@ -211,6 +214,7 @@ class CaptureLastExpression(ast.NodeTransformer): def visit_Expr(self, node: ast.Expr) -> typing.Union[ast.Expr, ast.Assign]: # noqa: N802 """ Replace the Expr node that is last child node of Module with an assignment. + We use an assignment to capture the value of the last node, if it's a loose Expr node. Normally, the value of an Expr node is lost, meaning we don't get the output of such a last "loose" expression. By assigning it a name, we can diff --git a/bot/exts/internal_eval/_internal_eval.py b/bot/exts/internal_eval/_internal_eval.py index 6f29a661..ee438724 100644 --- a/bot/exts/internal_eval/_internal_eval.py +++ b/bot/exts/internal_eval/_internal_eval.py @@ -50,6 +50,7 @@ class InternalEval(commands.Cog): ) -> str: """ Shorten the `output` so it's shorter than `max_length`. + There are three tactics for this, tried in the following order: - Shorten the output on a line-by-line basis - Shorten the output on any whitespace character @@ -158,7 +159,6 @@ class InternalEval(commands.Cog): @with_role(Roles.admin) async def eval(self, ctx: commands.Context, *, code: str) -> None: """Run eval in a REPL-like format.""" - if match := list(FORMATTED_CODE_REGEX.finditer(code)): blocks = [block for block in match if block.group("block")] -- cgit v1.2.3 From 87e459836b8d3b0d624ec97fe293f994ba9c8c22 Mon Sep 17 00:00:00 2001 From: Janine vN Date: Sun, 11 Apr 2021 11:18:28 -0400 Subject: Correct prefix usage in a doctstring Corrects the prefix for the a command in the docstring to use Lancebot's prefix. Co-authored-by: Matteo Bertucci --- bot/exts/internal_eval/_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/internal_eval/_helpers.py b/bot/exts/internal_eval/_helpers.py index a8ae5bef..8b991d98 100644 --- a/bot/exts/internal_eval/_helpers.py +++ b/bot/exts/internal_eval/_helpers.py @@ -76,7 +76,7 @@ class EvalContext: Represents the current `internal eval` context. The context remembers names set during earlier runs of `internal eval`. To - clear the context, use the `?internal clear` command. + clear the context, use the `.internal clear` command. """ def __init__(self, context_vars: Namespace, local_vars: Namespace) -> None: -- cgit v1.2.3 From 2cc2a2e618ed019de00054c768613e3e6ba2470c Mon Sep 17 00:00:00 2001 From: Janine vN Date: Sun, 11 Apr 2021 11:36:56 -0400 Subject: Correct logger name Changed the initialization of the logging to pull dynamically so it can actually log correctly. --- bot/exts/internal_eval/_helpers.py | 2 +- bot/exts/internal_eval/_internal_eval.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/exts/internal_eval/_helpers.py b/bot/exts/internal_eval/_helpers.py index 8b991d98..bd36520d 100644 --- a/bot/exts/internal_eval/_helpers.py +++ b/bot/exts/internal_eval/_helpers.py @@ -11,7 +11,7 @@ import types import typing -log = logging.getLogger("rattlesnake.exts.admin_tools.internal_eval") +log = logging.getLogger(__name__) # A type alias to annotate the tuples returned from `sys.exc_info()` ExcInfo = typing.Tuple[typing.Type[Exception], Exception, types.TracebackType] diff --git a/bot/exts/internal_eval/_internal_eval.py b/bot/exts/internal_eval/_internal_eval.py index ee438724..198c1312 100644 --- a/bot/exts/internal_eval/_internal_eval.py +++ b/bot/exts/internal_eval/_internal_eval.py @@ -13,7 +13,7 @@ from ._helpers import EvalContext __all__ = ["InternalEval"] -log = logging.getLogger("rattlesnake.exts.admin_tools.internal_eval") +log = logging.getLogger(__name__) CODEBLOCK_REGEX = re.compile(r"(^```(py(thon)?)?\n)|(```$)") -- cgit v1.2.3 From fa67eebb08ec9d71d94cdeaf757cc84f33053691 Mon Sep 17 00:00:00 2001 From: Janine vN Date: Sun, 11 Apr 2021 13:58:39 -0400 Subject: Remove unused codeblock regex With the regex sufficiently stolen from snekbox and confirmed to work, the original codeblock regex has been removed. --- bot/exts/internal_eval/_internal_eval.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'bot') diff --git a/bot/exts/internal_eval/_internal_eval.py b/bot/exts/internal_eval/_internal_eval.py index 198c1312..4746c6c9 100644 --- a/bot/exts/internal_eval/_internal_eval.py +++ b/bot/exts/internal_eval/_internal_eval.py @@ -15,8 +15,6 @@ __all__ = ["InternalEval"] log = logging.getLogger(__name__) -CODEBLOCK_REGEX = re.compile(r"(^```(py(thon)?)?\n)|(```$)") - FORMATTED_CODE_REGEX = re.compile( r"(?P(?P```)|``?)" # code delimiter: 1-3 backticks; (?P=block) only matches if it's a block r"(?(block)(?:(?P[a-z]+)\n)?)" # if we're in a block, match optional language (only letters plus newline) -- cgit v1.2.3 From e4829ffe61d8eda83ff89e7b86de35fe930bb793 Mon Sep 17 00:00:00 2001 From: Janine vN Date: Sun, 11 Apr 2021 14:06:39 -0400 Subject: Update docstring for reset Updated the docstring for `reset` to provide accurate information as to what the command does. --- bot/exts/internal_eval/_internal_eval.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/internal_eval/_internal_eval.py b/bot/exts/internal_eval/_internal_eval.py index 4746c6c9..8e474b7d 100644 --- a/bot/exts/internal_eval/_internal_eval.py +++ b/bot/exts/internal_eval/_internal_eval.py @@ -175,6 +175,6 @@ class InternalEval(commands.Cog): @internal_group.command(name='reset', aliases=("clear", "exit", "r", "c")) @with_role(Roles.admin) async def reset(self, ctx: commands.Context) -> None: - """Run eval in a REPL-like format.""" + """Reset the context and locals of the eval session.""" self.locals = {} await ctx.send("The evaluation context was reset.") -- cgit v1.2.3 From af97f53e2db9494dcf934d8b646b7d65c6713924 Mon Sep 17 00:00:00 2001 From: Janine vN Date: Sun, 11 Apr 2021 14:12:48 -0400 Subject: Change command help format `.int` with nothing else now uses the `invoke_help_command()` utility that formats the help command much more nicely than the default version --- bot/exts/internal_eval/_internal_eval.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/internal_eval/_internal_eval.py b/bot/exts/internal_eval/_internal_eval.py index 8e474b7d..06626b69 100644 --- a/bot/exts/internal_eval/_internal_eval.py +++ b/bot/exts/internal_eval/_internal_eval.py @@ -9,6 +9,7 @@ from discord.ext import commands from bot.bot import Bot from bot.constants import Roles from bot.utils.decorators import with_role +from bot.utils.extensions import invoke_help_command from ._helpers import EvalContext __all__ = ["InternalEval"] @@ -151,7 +152,7 @@ class InternalEval(commands.Cog): async def internal_group(self, ctx: commands.Context) -> None: """Internal commands. Top secret!""" if not ctx.invoked_subcommand: - await ctx.send_help(ctx.command) + await invoke_help_command(ctx) @internal_group.command(name='eval', aliases=('e',)) @with_role(Roles.admin) -- cgit v1.2.3 From 652f428347d2108d6c70df28c8c8130545ab9029 Mon Sep 17 00:00:00 2001 From: Janine vN Date: Sun, 11 Apr 2021 14:16:43 -0400 Subject: Ensure output will be robust for discord markdown Added in an extra `\n` at the end of the output. Sometimes discord won't properly format the codeblock in the triple ` is not on a newline. This changes ensures that it should. --- bot/exts/internal_eval/_internal_eval.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/internal_eval/_internal_eval.py b/bot/exts/internal_eval/_internal_eval.py index 06626b69..a62a7899 100644 --- a/bot/exts/internal_eval/_internal_eval.py +++ b/bot/exts/internal_eval/_internal_eval.py @@ -108,7 +108,7 @@ class InternalEval(commands.Cog): output = self.shorten_output(output) - await ctx.send(f"```py\n{output}```{upload_message}") + await ctx.send(f"```py\n{output}\n```{upload_message}") async def _eval(self, ctx: commands.Context, code: str) -> None: """Evaluate the `code` in the current evaluation context.""" -- cgit v1.2.3 From 442391dfbafa3b3848a030301702031f01320fdd Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 12 Apr 2021 21:40:05 +0100 Subject: Request the pfp as the right size from discord --- bot/exts/evergreen/profile_pic_modification/pfp_modify.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py index 68d68927..51999fb0 100644 --- a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py +++ b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py @@ -215,7 +215,7 @@ class PfpModify(commands.Cog): if not member: await ctx.send(f"{Emojis.cross_mark} Could not get member info.") return - image_bytes = await member.avatar_url.read() + image_bytes = await member.avatar_url_as(size=1024).read() await self.send_pride_image(ctx, image_bytes, pixels, flag, option) @prideavatar.command() -- cgit v1.2.3 From b411a4522422879eee3e821f149f21636dfb56bf Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 12 Apr 2021 22:21:44 +0100 Subject: Silence matplotlib's logger --- bot/__init__.py | 1 + 1 file changed, 1 insertion(+) (limited to 'bot') diff --git a/bot/__init__.py b/bot/__init__.py index d0992912..71b7c8a3 100644 --- a/bot/__init__.py +++ b/bot/__init__.py @@ -60,6 +60,7 @@ if root.handlers: logging.getLogger("discord").setLevel(logging.ERROR) logging.getLogger("websockets").setLevel(logging.ERROR) logging.getLogger("PIL").setLevel(logging.ERROR) +logging.getLogger("matplotlib").setLevel(logging.ERROR) # Setup new logging configuration logging.basicConfig( -- cgit v1.2.3 From c45e26621f9ea4e6209a33541f5db996e3279ea0 Mon Sep 17 00:00:00 2001 From: Janine vN Date: Mon, 12 Apr 2021 18:20:28 -0400 Subject: Add constants for common string filenames Added a constant for the same filenames used in several locations. Because the now-a-constant string is used in several locations this will allow for it to be updated more easily down the line. --- bot/exts/internal_eval/_helpers.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'bot') diff --git a/bot/exts/internal_eval/_helpers.py b/bot/exts/internal_eval/_helpers.py index bd36520d..3a50b9f3 100644 --- a/bot/exts/internal_eval/_helpers.py +++ b/bot/exts/internal_eval/_helpers.py @@ -41,6 +41,8 @@ async def _eval_wrapper_function(): _eval_context.locals = locals() _eval_context.function = _eval_wrapper_function """ +INTERNAL_EVAL_FRAMENAME = "" +EVAL_WRAPPER_FUNCTION_FRAMENAME = "_eval_wrapper_function" def format_internal_eval_exception(exc_info: ExcInfo, code: str) -> str: @@ -51,11 +53,11 @@ def format_internal_eval_exception(exc_info: ExcInfo, code: str) -> str: output = ["Traceback (most recent call last):"] for frame in stack_summary: - if frame.filename == "": + if frame.filename == INTERNAL_EVAL_FRAMENAME: line = code[frame.lineno - 1].lstrip() - if frame.name == "_eval_wrapper_function": - name = "" + if frame.name == EVAL_WRAPPER_FUNCTION_FRAMENAME: + name = INTERNAL_EVAL_FRAMENAME else: name = frame.name else: @@ -128,7 +130,7 @@ class EvalContext: return "[No code detected]" try: - code_tree = ast.parse(code, filename="") + code_tree = ast.parse(code, filename=INTERNAL_EVAL_FRAMENAME) except SyntaxError: log.debug("Got a SyntaxError while parsing the eval code") return "".join(traceback.format_exception(*sys.exc_info(), limit=0)) @@ -145,7 +147,7 @@ class EvalContext: async def run_eval(self) -> Namespace: """Run the evaluation and return the updated locals.""" log.trace("Compiling the AST to bytecode using `exec` mode") - compiled_code = compile(self.eval_tree, filename="", mode="exec") + compiled_code = compile(self.eval_tree, filename=INTERNAL_EVAL_FRAMENAME, mode="exec") log.trace("Executing the compiled code with the desired namespace environment") exec(compiled_code, self.locals) # noqa: B102,S102 @@ -186,7 +188,7 @@ class WrapEvalCodeTree(ast.NodeTransformer): self.eval_code_tree = eval_code_tree # To avoid mutable aliasing, parse the WRAPPER_FUNC for each wrapping - self.wrapper = ast.parse(EVAL_WRAPPER, filename="") + self.wrapper = ast.parse(EVAL_WRAPPER, filename=INTERNAL_EVAL_FRAMENAME) def wrap(self) -> ast.AST: """Wrap the tree of the code by the tree of the wrapper function.""" -- cgit v1.2.3 From c9a3cdf1e71525c307ee3385a375724a861a7b74 Mon Sep 17 00:00:00 2001 From: Janine vN Date: Mon, 12 Apr 2021 18:37:01 -0400 Subject: Remove check for `exit` to reset context This commit removes the `exit` check if someone were to use this: `.int e exit` to clear the context. The check would prevent `.int e exit()` from restarting the bot container. With the `.int reset` and `.int exit` ability to clear the context the check for `exit` to clear the context isn't necessary. --- bot/exts/internal_eval/_internal_eval.py | 5 ----- 1 file changed, 5 deletions(-) (limited to 'bot') diff --git a/bot/exts/internal_eval/_internal_eval.py b/bot/exts/internal_eval/_internal_eval.py index a62a7899..757a2a1e 100644 --- a/bot/exts/internal_eval/_internal_eval.py +++ b/bot/exts/internal_eval/_internal_eval.py @@ -112,11 +112,6 @@ class InternalEval(commands.Cog): async def _eval(self, ctx: commands.Context, code: str) -> None: """Evaluate the `code` in the current evaluation context.""" - if code.startswith("exit"): - self.locals = {} - await ctx.send("The evaluation context was reset.") - return - context_vars = { "message": ctx.message, "author": ctx.message.author, -- cgit v1.2.3 From b2ec5813ddc4e7abc38c4143d79a0165fa591cd7 Mon Sep 17 00:00:00 2001 From: Rohan Date: Thu, 15 Apr 2021 19:16:42 +0530 Subject: Use custom help command util for sending command help. --- bot/exts/evergreen/reddit.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/reddit.py b/bot/exts/evergreen/reddit.py index 1a4f9add..7f4ce6a0 100644 --- a/bot/exts/evergreen/reddit.py +++ b/bot/exts/evergreen/reddit.py @@ -15,6 +15,7 @@ from discord.utils import escape_markdown, sleep_until from bot.bot import Bot from bot.constants import Channels, ERROR_REPLIES, Emojis, Reddit as RedditConfig, STAFF_ROLES from bot.utils.converters import Subreddit +from bot.utils.extensions import invoke_help_command from bot.utils.messages import sub_clyde from bot.utils.pagination import ImagePaginator, LinePaginator @@ -300,7 +301,7 @@ class Reddit(Cog): @group(name="reddit", invoke_without_command=True) async def reddit_group(self, ctx: Context) -> None: """View the top posts from various subreddits.""" - await ctx.send_help(ctx.command) + await invoke_help_command(ctx) @reddit_group.command(name="top") async def top_command(self, ctx: Context, subreddit: Subreddit = "r/Python") -> None: -- cgit v1.2.3 From 1670e5a48efb50324096df859e673d30528380e4 Mon Sep 17 00:00:00 2001 From: Rohan Date: Thu, 15 Apr 2021 20:39:50 +0530 Subject: Add reddit channel ID to constants. --- bot/constants.py | 1 + 1 file changed, 1 insertion(+) (limited to 'bot') diff --git a/bot/constants.py b/bot/constants.py index dfb39364..f5baec35 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -111,6 +111,7 @@ class Channels(NamedTuple): voice_chat_0 = 412357430186344448 voice_chat_1 = 799647045886541885 staff_voice = 541638762007101470 + reddit = int(environ.get("CHANNEL_REDDIT", 458224812528238616)) class Categories(NamedTuple): -- cgit v1.2.3 From 541efecc44fffec87f7e9346619dcae0710e2a08 Mon Sep 17 00:00:00 2001 From: Rohan Date: Thu, 15 Apr 2021 20:42:14 +0530 Subject: Apply code review suggestions. --- bot/exts/evergreen/reddit.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/reddit.py b/bot/exts/evergreen/reddit.py index 7f4ce6a0..916563ac 100644 --- a/bot/exts/evergreen/reddit.py +++ b/bot/exts/evergreen/reddit.py @@ -86,11 +86,11 @@ class Reddit(Cog): author = data["author"] content_type = Emojis.reddit_post_text - if data["is_video"] is True or {"youtube", "youtu.be"}.issubset(set(data["url"].split("."))): + if data["is_video"] or {"youtube", "youtu.be"}.issubset(set(data["url"].split("."))): # This means the content type in the post is a video. content_type = f"{Emojis.reddit_post_video}" - elif any(data["url"].endswith(pic_format) for pic_format in ("jpg", "png", "gif")): + elif data["url"].endswith(("jpg", "png", "gif")): # This means the content type in the post is an image. content_type = f"{Emojis.reddit_post_photo}" image_url = data["url"] -- cgit v1.2.3 From e43d311ffdc8378cc0ad7095c765c76aeea145d5 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 16 Apr 2021 14:01:17 +0100 Subject: Remove un-used sound resources --- .../spookysounds/109710__tomlija__horror-gate.mp3 | Bin 118125 -> 0 bytes .../spookysounds/126113__klankbeeld__laugh.mp3 | Bin 112365 -> 0 bytes ...ugh-original-132802-nanakisan-evil-laugh-08.mp3 | Bin 137385 -> 0 bytes .../spookysounds/14570__oscillator__ghost-fx.mp3 | Bin 135405 -> 0 bytes .../spookysounds/168650__0xmusex0__doorcreak.mp3 | Bin 162421 -> 0 bytes ...71078__klankbeeld__horror-scream-woman-long.mp3 | Bin 131625 -> 0 bytes .../193812__geoneo0__four-voices-whispering-6.mp3 | Bin 163257 -> 0 bytes ...37282__devilfish101__frantic-violin-screech.mp3 | Bin 131566 -> 0 bytes .../249686__cylon8472__cthulhu-growl.mp3 | Bin 153226 -> 0 bytes .../spookysounds/35716__analogchill__scream.mp3 | Bin 114773 -> 0 bytes ...15__inspectorj__something-evil-approaches-a.mp3 | Bin 298717 -> 0 bytes .../60571__gabemiller74__breathofdeath.mp3 | Bin 177049 -> 0 bytes .../spookysounds/Female_Monster_Growls_.mp3 | Bin 148276 -> 0 bytes .../halloween/spookysounds/Male_Zombie_Roar_.mp3 | Bin 62171 -> 0 bytes .../spookysounds/Monster_Alien_Growl_Calm_.mp3 | Bin 133651 -> 0 bytes .../spookysounds/Monster_Alien_Grunt_Hiss_.mp3 | Bin 74718 -> 0 bytes bot/resources/halloween/spookysounds/sources.txt | 41 --------------------- 17 files changed, 41 deletions(-) delete mode 100644 bot/resources/halloween/spookysounds/109710__tomlija__horror-gate.mp3 delete mode 100644 bot/resources/halloween/spookysounds/126113__klankbeeld__laugh.mp3 delete mode 100644 bot/resources/halloween/spookysounds/133674__klankbeeld__horror-laugh-original-132802-nanakisan-evil-laugh-08.mp3 delete mode 100644 bot/resources/halloween/spookysounds/14570__oscillator__ghost-fx.mp3 delete mode 100644 bot/resources/halloween/spookysounds/168650__0xmusex0__doorcreak.mp3 delete mode 100644 bot/resources/halloween/spookysounds/171078__klankbeeld__horror-scream-woman-long.mp3 delete mode 100644 bot/resources/halloween/spookysounds/193812__geoneo0__four-voices-whispering-6.mp3 delete mode 100644 bot/resources/halloween/spookysounds/237282__devilfish101__frantic-violin-screech.mp3 delete mode 100644 bot/resources/halloween/spookysounds/249686__cylon8472__cthulhu-growl.mp3 delete mode 100644 bot/resources/halloween/spookysounds/35716__analogchill__scream.mp3 delete mode 100644 bot/resources/halloween/spookysounds/413315__inspectorj__something-evil-approaches-a.mp3 delete mode 100644 bot/resources/halloween/spookysounds/60571__gabemiller74__breathofdeath.mp3 delete mode 100644 bot/resources/halloween/spookysounds/Female_Monster_Growls_.mp3 delete mode 100644 bot/resources/halloween/spookysounds/Male_Zombie_Roar_.mp3 delete mode 100644 bot/resources/halloween/spookysounds/Monster_Alien_Growl_Calm_.mp3 delete mode 100644 bot/resources/halloween/spookysounds/Monster_Alien_Grunt_Hiss_.mp3 delete mode 100644 bot/resources/halloween/spookysounds/sources.txt (limited to 'bot') diff --git a/bot/resources/halloween/spookysounds/109710__tomlija__horror-gate.mp3 b/bot/resources/halloween/spookysounds/109710__tomlija__horror-gate.mp3 deleted file mode 100644 index 495f2bd1..00000000 Binary files a/bot/resources/halloween/spookysounds/109710__tomlija__horror-gate.mp3 and /dev/null differ diff --git a/bot/resources/halloween/spookysounds/126113__klankbeeld__laugh.mp3 b/bot/resources/halloween/spookysounds/126113__klankbeeld__laugh.mp3 deleted file mode 100644 index 538feabc..00000000 Binary files a/bot/resources/halloween/spookysounds/126113__klankbeeld__laugh.mp3 and /dev/null differ diff --git a/bot/resources/halloween/spookysounds/133674__klankbeeld__horror-laugh-original-132802-nanakisan-evil-laugh-08.mp3 b/bot/resources/halloween/spookysounds/133674__klankbeeld__horror-laugh-original-132802-nanakisan-evil-laugh-08.mp3 deleted file mode 100644 index 17f66698..00000000 Binary files a/bot/resources/halloween/spookysounds/133674__klankbeeld__horror-laugh-original-132802-nanakisan-evil-laugh-08.mp3 and /dev/null differ diff --git a/bot/resources/halloween/spookysounds/14570__oscillator__ghost-fx.mp3 b/bot/resources/halloween/spookysounds/14570__oscillator__ghost-fx.mp3 deleted file mode 100644 index 5670657c..00000000 Binary files a/bot/resources/halloween/spookysounds/14570__oscillator__ghost-fx.mp3 and /dev/null differ diff --git a/bot/resources/halloween/spookysounds/168650__0xmusex0__doorcreak.mp3 b/bot/resources/halloween/spookysounds/168650__0xmusex0__doorcreak.mp3 deleted file mode 100644 index 42f9e9fd..00000000 Binary files a/bot/resources/halloween/spookysounds/168650__0xmusex0__doorcreak.mp3 and /dev/null differ diff --git a/bot/resources/halloween/spookysounds/171078__klankbeeld__horror-scream-woman-long.mp3 b/bot/resources/halloween/spookysounds/171078__klankbeeld__horror-scream-woman-long.mp3 deleted file mode 100644 index 1cdb0f4d..00000000 Binary files a/bot/resources/halloween/spookysounds/171078__klankbeeld__horror-scream-woman-long.mp3 and /dev/null differ diff --git a/bot/resources/halloween/spookysounds/193812__geoneo0__four-voices-whispering-6.mp3 b/bot/resources/halloween/spookysounds/193812__geoneo0__four-voices-whispering-6.mp3 deleted file mode 100644 index 89150d57..00000000 Binary files a/bot/resources/halloween/spookysounds/193812__geoneo0__four-voices-whispering-6.mp3 and /dev/null differ diff --git a/bot/resources/halloween/spookysounds/237282__devilfish101__frantic-violin-screech.mp3 b/bot/resources/halloween/spookysounds/237282__devilfish101__frantic-violin-screech.mp3 deleted file mode 100644 index b5f85f8d..00000000 Binary files a/bot/resources/halloween/spookysounds/237282__devilfish101__frantic-violin-screech.mp3 and /dev/null differ diff --git a/bot/resources/halloween/spookysounds/249686__cylon8472__cthulhu-growl.mp3 b/bot/resources/halloween/spookysounds/249686__cylon8472__cthulhu-growl.mp3 deleted file mode 100644 index d141f68e..00000000 Binary files a/bot/resources/halloween/spookysounds/249686__cylon8472__cthulhu-growl.mp3 and /dev/null differ diff --git a/bot/resources/halloween/spookysounds/35716__analogchill__scream.mp3 b/bot/resources/halloween/spookysounds/35716__analogchill__scream.mp3 deleted file mode 100644 index a0614b53..00000000 Binary files a/bot/resources/halloween/spookysounds/35716__analogchill__scream.mp3 and /dev/null differ diff --git a/bot/resources/halloween/spookysounds/413315__inspectorj__something-evil-approaches-a.mp3 b/bot/resources/halloween/spookysounds/413315__inspectorj__something-evil-approaches-a.mp3 deleted file mode 100644 index 38374316..00000000 Binary files a/bot/resources/halloween/spookysounds/413315__inspectorj__something-evil-approaches-a.mp3 and /dev/null differ diff --git a/bot/resources/halloween/spookysounds/60571__gabemiller74__breathofdeath.mp3 b/bot/resources/halloween/spookysounds/60571__gabemiller74__breathofdeath.mp3 deleted file mode 100644 index f769d9d8..00000000 Binary files a/bot/resources/halloween/spookysounds/60571__gabemiller74__breathofdeath.mp3 and /dev/null differ diff --git a/bot/resources/halloween/spookysounds/Female_Monster_Growls_.mp3 b/bot/resources/halloween/spookysounds/Female_Monster_Growls_.mp3 deleted file mode 100644 index 8b04f0f5..00000000 Binary files a/bot/resources/halloween/spookysounds/Female_Monster_Growls_.mp3 and /dev/null differ diff --git a/bot/resources/halloween/spookysounds/Male_Zombie_Roar_.mp3 b/bot/resources/halloween/spookysounds/Male_Zombie_Roar_.mp3 deleted file mode 100644 index 964d685e..00000000 Binary files a/bot/resources/halloween/spookysounds/Male_Zombie_Roar_.mp3 and /dev/null differ diff --git a/bot/resources/halloween/spookysounds/Monster_Alien_Growl_Calm_.mp3 b/bot/resources/halloween/spookysounds/Monster_Alien_Growl_Calm_.mp3 deleted file mode 100644 index 9e643773..00000000 Binary files a/bot/resources/halloween/spookysounds/Monster_Alien_Growl_Calm_.mp3 and /dev/null differ diff --git a/bot/resources/halloween/spookysounds/Monster_Alien_Grunt_Hiss_.mp3 b/bot/resources/halloween/spookysounds/Monster_Alien_Grunt_Hiss_.mp3 deleted file mode 100644 index ad99cf76..00000000 Binary files a/bot/resources/halloween/spookysounds/Monster_Alien_Grunt_Hiss_.mp3 and /dev/null differ diff --git a/bot/resources/halloween/spookysounds/sources.txt b/bot/resources/halloween/spookysounds/sources.txt deleted file mode 100644 index 7df03c2e..00000000 --- a/bot/resources/halloween/spookysounds/sources.txt +++ /dev/null @@ -1,41 +0,0 @@ -Female_Monster_Growls_ -Male_Zombie_Roar_ -Monster_Alien_Growl_Calm_ -Monster_Alien_Grunt_Hiss_ -https://www.youtube.com/audiolibrary/soundeffects - -413315__inspectorj__something-evil-approaches-a -https://freesound.org/people/InspectorJ/sounds/413315/ - -133674__klankbeeld__horror-laugh-original-132802-nanakisan-evil-laugh-08 -https://freesound.org/people/klankbeeld/sounds/133674/ - -35716__analogchill__scream -https://freesound.org/people/analogchill/sounds/35716/ - -249686__cylon8472__cthulhu-growl -https://freesound.org/people/cylon8472/sounds/249686/ - -126113__klankbeeld__laugh -https://freesound.org/people/klankbeeld/sounds/126113/ - -14570__oscillator__ghost-fx -https://freesound.org/people/oscillator/sounds/14570/ - -60571__gabemiller74__breathofdeath -https://freesound.org/people/gabemiller74/sounds/60571/ - -168650__0xmusex0__doorcreak -https://freesound.org/people/0XMUSEX0/sounds/168650/ - -193812__geoneo0__four-voices-whispering-6 -https://freesound.org/people/geoneo0/sounds/193812/ - -109710__tomlija__horror-gate -https://freesound.org/people/Tomlija/sounds/109710/ - -171078__klankbeeld__horror-scream-woman-long -https://freesound.org/people/klankbeeld/sounds/171078/ - -237282__devilfish101__frantic-violin-screech -https://freesound.org/people/devilfish101/sounds/237282/ -- cgit v1.2.3 From 2d3e1cd56eaf4902ca49f0a47a6c933484f0f1b5 Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 17 Apr 2021 12:24:17 +0100 Subject: Fix captitilization in docstring Co-authored-by: LXNN --- bot/exts/evergreen/profile_pic_modification/pfp_modify.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py index 51999fb0..77c937de 100644 --- a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py +++ b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py @@ -47,7 +47,7 @@ class PfpModify(commands.Cog): Fetches a member and handles errors. This helper funciton is required as the member cache doesn't always have the most up to date - profile picture. THis can lead to errors if the image is delted from the Discord CDN. + profile picture. This can lead to errors if the image is delted from the Discord CDN. """ try: member = await self.bot.get_guild(Client.guild).fetch_member(member_id) -- cgit v1.2.3 From 1ed857d9093481387720ac1dc4041a64a2c9c593 Mon Sep 17 00:00:00 2001 From: vcokltfre Date: Mon, 19 Apr 2021 20:20:48 +0100 Subject: chore: switch commands.Bot typehints to bot.bot's Bot --- bot/exts/evergreen/8bitify.py | 6 ++++-- bot/exts/evergreen/battleship.py | 7 ++++--- bot/exts/evergreen/bookmark.py | 5 +++-- bot/exts/evergreen/cheatsheet.py | 5 +++-- bot/exts/evergreen/connect_four.py | 9 +++++---- bot/exts/evergreen/conversationstarters.py | 5 +++-- bot/exts/evergreen/emoji.py | 5 +++-- bot/exts/evergreen/error_handler.py | 5 +++-- bot/exts/evergreen/fun.py | 5 +++-- bot/exts/evergreen/githubinfo.py | 5 +++-- bot/exts/evergreen/issues.py | 5 +++-- bot/exts/evergreen/latex.py | 4 +++- bot/exts/evergreen/magic_8ball.py | 6 ++++-- bot/exts/evergreen/minesweeper.py | 5 +++-- bot/exts/evergreen/movie.py | 3 ++- bot/exts/evergreen/ping.py | 5 +++-- bot/exts/evergreen/pythonfacts.py | 5 +++-- bot/exts/evergreen/recommend_game.py | 6 ++++-- bot/exts/evergreen/reddit.py | 5 +++-- bot/exts/evergreen/source.py | 5 +++-- bot/exts/evergreen/speedrun.py | 6 ++++-- bot/exts/evergreen/status_codes.py | 5 +++-- bot/exts/evergreen/timed.py | 4 +++- bot/exts/evergreen/trivia_quiz.py | 5 +++-- bot/exts/evergreen/uptime.py | 5 +++-- bot/exts/evergreen/wolfram.py | 7 ++++--- bot/exts/evergreen/wonder_twins.py | 4 +++- 27 files changed, 88 insertions(+), 54 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/8bitify.py b/bot/exts/evergreen/8bitify.py index 7eb4d313..621aa875 100644 --- a/bot/exts/evergreen/8bitify.py +++ b/bot/exts/evergreen/8bitify.py @@ -4,11 +4,13 @@ import discord from PIL import Image from discord.ext import commands +from bot.bot import Bot + class EightBitify(commands.Cog): """Make your avatar 8bit!""" - def __init__(self, bot: commands.Bot) -> None: + def __init__(self, bot: Bot) -> None: self.bot = bot @staticmethod @@ -50,6 +52,6 @@ class EightBitify(commands.Cog): await ctx.send(file=file, embed=embed) -def setup(bot: commands.Bot) -> None: +def setup(bot: Bot) -> None: """Cog load.""" bot.add_cog(EightBitify(bot)) diff --git a/bot/exts/evergreen/battleship.py b/bot/exts/evergreen/battleship.py index 1681434f..f255afb9 100644 --- a/bot/exts/evergreen/battleship.py +++ b/bot/exts/evergreen/battleship.py @@ -9,6 +9,7 @@ from functools import partial import discord from discord.ext import commands +from bot.bot import Bot from bot.constants import Colours log = logging.getLogger(__name__) @@ -95,7 +96,7 @@ class Game: def __init__( self, - bot: commands.Bot, + bot: Bot, channel: discord.TextChannel, player1: discord.Member, player2: discord.Member @@ -321,7 +322,7 @@ class Game: class Battleship(commands.Cog): """Play the classic game Battleship!""" - def __init__(self, bot: commands.Bot) -> None: + def __init__(self, bot: Bot) -> None: self.bot = bot self.games: typing.List[Game] = [] self.waiting: typing.List[discord.Member] = [] @@ -438,6 +439,6 @@ class Battleship(commands.Cog): await ctx.send(embed=embed) -def setup(bot: commands.Bot) -> None: +def setup(bot: Bot) -> None: """Cog load.""" bot.add_cog(Battleship(bot)) diff --git a/bot/exts/evergreen/bookmark.py b/bot/exts/evergreen/bookmark.py index 5fa05d2e..329838b9 100644 --- a/bot/exts/evergreen/bookmark.py +++ b/bot/exts/evergreen/bookmark.py @@ -4,6 +4,7 @@ import random import discord from discord.ext import commands +from bot.bot import Bot from bot.constants import Colours, ERROR_REPLIES, Emojis, Icons from bot.utils.converters import WrappedMessageConverter @@ -13,7 +14,7 @@ log = logging.getLogger(__name__) class Bookmark(commands.Cog): """Creates personal bookmarks by relaying a message link to the user's DMs.""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Bot): self.bot = bot @commands.command(name="bookmark", aliases=("bm", "pin")) @@ -60,6 +61,6 @@ class Bookmark(commands.Cog): await ctx.message.add_reaction(Emojis.envelope) -def setup(bot: commands.Bot) -> None: +def setup(bot: Bot) -> None: """Load the Bookmark cog.""" bot.add_cog(Bookmark(bot)) diff --git a/bot/exts/evergreen/cheatsheet.py b/bot/exts/evergreen/cheatsheet.py index 3fe709d5..57c6d0b0 100644 --- a/bot/exts/evergreen/cheatsheet.py +++ b/bot/exts/evergreen/cheatsheet.py @@ -8,6 +8,7 @@ from discord.ext import commands from discord.ext.commands import BucketType, Context from bot import constants +from bot.bot import Bot from bot.constants import Categories, Channels, Colours, ERROR_REPLIES from bot.utils.decorators import whitelist_override @@ -33,7 +34,7 @@ HEADERS = {'User-Agent': 'curl/7.68.0'} class CheatSheet(commands.Cog): """Commands that sends a result of a cht.sh search in code blocks.""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Bot): self.bot = bot @staticmethod @@ -102,6 +103,6 @@ class CheatSheet(commands.Cog): await ctx.send(content=description) -def setup(bot: commands.Bot) -> None: +def setup(bot: Bot) -> None: """Load the CheatSheet cog.""" bot.add_cog(CheatSheet(bot)) diff --git a/bot/exts/evergreen/connect_four.py b/bot/exts/evergreen/connect_four.py index 7e3ec42b..fbb13780 100644 --- a/bot/exts/evergreen/connect_four.py +++ b/bot/exts/evergreen/connect_four.py @@ -8,6 +8,7 @@ import emojis from discord.ext import commands from discord.ext.commands import guild_only +from bot.bot import Bot from bot.constants import Emojis NUMBERS = list(Emojis.number_emojis.values()) @@ -22,7 +23,7 @@ class Game: def __init__( self, - bot: commands.Bot, + bot: Bot, channel: discord.TextChannel, player1: discord.Member, player2: typing.Optional[discord.Member], @@ -180,7 +181,7 @@ class Game: class AI: """The Computer Player for Single-Player games.""" - def __init__(self, bot: commands.Bot, game: Game) -> None: + def __init__(self, bot: Bot, game: Game) -> None: self.game = game self.mention = bot.user.mention @@ -255,7 +256,7 @@ class AI: class ConnectFour(commands.Cog): """Connect Four. The Classic Vertical Four-in-a-row Game!""" - def __init__(self, bot: commands.Bot) -> None: + def __init__(self, bot: Bot) -> None: self.bot = bot self.games: typing.List[Game] = [] self.waiting: typing.List[discord.Member] = [] @@ -445,6 +446,6 @@ class ConnectFour(commands.Cog): await self._play_game(ctx, None, board_size, str(emoji1), str(emoji2)) -def setup(bot: commands.Bot) -> None: +def setup(bot: Bot) -> None: """Load ConnectFour Cog.""" bot.add_cog(ConnectFour(bot)) diff --git a/bot/exts/evergreen/conversationstarters.py b/bot/exts/evergreen/conversationstarters.py index e7058961..aed76bcf 100644 --- a/bot/exts/evergreen/conversationstarters.py +++ b/bot/exts/evergreen/conversationstarters.py @@ -4,6 +4,7 @@ import yaml from discord import Color, Embed from discord.ext import commands +from bot.bot import Bot from bot.constants import WHITELISTED_CHANNELS from bot.utils.decorators import whitelist_override from bot.utils.randomization import RandomCycle @@ -34,7 +35,7 @@ TOPICS = { class ConvoStarters(commands.Cog): """Evergreen conversation topics.""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Bot): self.bot = bot @commands.command() @@ -66,6 +67,6 @@ class ConvoStarters(commands.Cog): await ctx.send(embed=embed) -def setup(bot: commands.Bot) -> None: +def setup(bot: Bot) -> None: """Conversation starters Cog load.""" bot.add_cog(ConvoStarters(bot)) diff --git a/bot/exts/evergreen/emoji.py b/bot/exts/evergreen/emoji.py index fa3044e3..58d9be03 100644 --- a/bot/exts/evergreen/emoji.py +++ b/bot/exts/evergreen/emoji.py @@ -8,6 +8,7 @@ from typing import List, Optional, Tuple from discord import Color, Embed, Emoji from discord.ext import commands +from bot.bot import Bot from bot.constants import Colours, ERROR_REPLIES from bot.utils.extensions import invoke_help_command from bot.utils.pagination import LinePaginator @@ -19,7 +20,7 @@ log = logging.getLogger(__name__) class Emojis(commands.Cog): """A collection of commands related to emojis in the server.""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Bot): self.bot = bot @staticmethod @@ -120,6 +121,6 @@ class Emojis(commands.Cog): await ctx.send(embed=emoji_information) -def setup(bot: commands.Bot) -> None: +def setup(bot: Bot) -> None: """Add the Emojis cog into the bot.""" bot.add_cog(Emojis(bot)) diff --git a/bot/exts/evergreen/error_handler.py b/bot/exts/evergreen/error_handler.py index 8db49748..053e3866 100644 --- a/bot/exts/evergreen/error_handler.py +++ b/bot/exts/evergreen/error_handler.py @@ -7,6 +7,7 @@ from discord import Embed, Message from discord.ext import commands from sentry_sdk import push_scope +from bot.bot import Bot from bot.constants import Channels, Colours, ERROR_REPLIES, NEGATIVE_REPLIES from bot.utils.decorators import InChannelCheckFailure, InMonthCheckFailure from bot.utils.exceptions import UserNotPlayingError @@ -17,7 +18,7 @@ log = logging.getLogger(__name__) class CommandErrorHandler(commands.Cog): """A error handler for the PythonDiscord server.""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Bot): self.bot = bot @staticmethod @@ -135,6 +136,6 @@ class CommandErrorHandler(commands.Cog): log.exception(f"Unhandled command error: {str(error)}", exc_info=error) -def setup(bot: commands.Bot) -> None: +def setup(bot: Bot) -> None: """Error handler Cog load.""" bot.add_cog(CommandErrorHandler(bot)) diff --git a/bot/exts/evergreen/fun.py b/bot/exts/evergreen/fun.py index 101725da..cde37895 100644 --- a/bot/exts/evergreen/fun.py +++ b/bot/exts/evergreen/fun.py @@ -7,9 +7,10 @@ from typing import Callable, Iterable, Tuple, Union from discord import Embed, Message from discord.ext import commands -from discord.ext.commands import BadArgument, Bot, Cog, Context, MessageConverter, clean_content +from discord.ext.commands import BadArgument, Cog, Context, MessageConverter, clean_content from bot import utils +from bot.bot import Bot from bot.constants import Client, Colours, Emojis log = logging.getLogger(__name__) @@ -239,6 +240,6 @@ class Fun(Cog): return Embed.from_dict(embed_dict) -def setup(bot: commands.Bot) -> None: +def setup(bot: Bot) -> None: """Fun Cog load.""" bot.add_cog(Fun(bot)) diff --git a/bot/exts/evergreen/githubinfo.py b/bot/exts/evergreen/githubinfo.py index c8a6b3f7..da6eba5c 100644 --- a/bot/exts/evergreen/githubinfo.py +++ b/bot/exts/evergreen/githubinfo.py @@ -7,6 +7,7 @@ import discord from discord.ext import commands from discord.ext.commands.cooldowns import BucketType +from bot.bot import Bot from bot.constants import Colours, NEGATIVE_REPLIES from bot.exts.utils.extensions import invoke_help_command @@ -18,7 +19,7 @@ GITHUB_API_URL = "https://api.github.com" class GithubInfo(commands.Cog): """Fetches info from GitHub.""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Bot): self.bot = bot async def fetch_data(self, url: str) -> dict: @@ -170,6 +171,6 @@ class GithubInfo(commands.Cog): await ctx.send(embed=embed) -def setup(bot: commands.Bot) -> None: +def setup(bot: Bot) -> None: """Adding the cog to the bot.""" bot.add_cog(GithubInfo(bot)) diff --git a/bot/exts/evergreen/issues.py b/bot/exts/evergreen/issues.py index a0316080..692e0b43 100644 --- a/bot/exts/evergreen/issues.py +++ b/bot/exts/evergreen/issues.py @@ -7,6 +7,7 @@ from dataclasses import dataclass import discord from discord.ext import commands +from bot.bot import Bot from bot.constants import ( Categories, Channels, @@ -91,7 +92,7 @@ class IssueState: class Issues(commands.Cog): """Cog that allows users to retrieve issues from GitHub.""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Bot): self.bot = bot self.repos = [] @@ -269,6 +270,6 @@ class Issues(commands.Cog): await message.channel.send(embed=resp) -def setup(bot: commands.Bot) -> None: +def setup(bot: Bot) -> None: """Cog Retrieves Issues From Github.""" bot.add_cog(Issues(bot)) diff --git a/bot/exts/evergreen/latex.py b/bot/exts/evergreen/latex.py index c4a8597c..3a93907a 100644 --- a/bot/exts/evergreen/latex.py +++ b/bot/exts/evergreen/latex.py @@ -9,6 +9,8 @@ import discord import matplotlib.pyplot as plt from discord.ext import commands +from bot.bot import Bot + # configure fonts and colors for matplotlib plt.rcParams.update( { @@ -89,6 +91,6 @@ class Latex(commands.Cog): await ctx.send(file=discord.File(image, "latex.png")) -def setup(bot: commands.Bot) -> None: +def setup(bot: Bot) -> None: """Load the Latex Cog.""" bot.add_cog(Latex(bot)) diff --git a/bot/exts/evergreen/magic_8ball.py b/bot/exts/evergreen/magic_8ball.py index f974e487..2dfe237a 100644 --- a/bot/exts/evergreen/magic_8ball.py +++ b/bot/exts/evergreen/magic_8ball.py @@ -5,13 +5,15 @@ from pathlib import Path from discord.ext import commands +from bot.bot import Bot + log = logging.getLogger(__name__) class Magic8ball(commands.Cog): """A Magic 8ball command to respond to a user's question.""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Bot): self.bot = bot with open(Path("bot/resources/evergreen/magic8ball.json"), "r", encoding="utf8") as file: self.answers = json.load(file) @@ -26,6 +28,6 @@ class Magic8ball(commands.Cog): await ctx.send("Usage: .8ball (minimum length of 3 eg: `will I win?`)") -def setup(bot: commands.Bot) -> None: +def setup(bot: Bot) -> None: """Magic 8ball Cog load.""" bot.add_cog(Magic8ball(bot)) diff --git a/bot/exts/evergreen/minesweeper.py b/bot/exts/evergreen/minesweeper.py index 3031debc..7c1bac3b 100644 --- a/bot/exts/evergreen/minesweeper.py +++ b/bot/exts/evergreen/minesweeper.py @@ -6,6 +6,7 @@ from random import randint, random import discord from discord.ext import commands +from bot.bot import Bot from bot.constants import Client from bot.utils.exceptions import UserNotPlayingError from bot.utils.extensions import invoke_help_command @@ -78,7 +79,7 @@ GamesDict = typing.Dict[int, Game] class Minesweeper(commands.Cog): """Play a game of Minesweeper.""" - def __init__(self, bot: commands.Bot) -> None: + def __init__(self, bot: Bot) -> None: self.games: GamesDict = {} # Store the currently running games @commands.group(name='minesweeper', aliases=('ms',), invoke_without_command=True) @@ -292,6 +293,6 @@ class Minesweeper(commands.Cog): del self.games[ctx.author.id] -def setup(bot: commands.Bot) -> None: +def setup(bot: Bot) -> None: """Load the Minesweeper cog.""" bot.add_cog(Minesweeper(bot)) diff --git a/bot/exts/evergreen/movie.py b/bot/exts/evergreen/movie.py index b3bfe998..f4356f33 100644 --- a/bot/exts/evergreen/movie.py +++ b/bot/exts/evergreen/movie.py @@ -6,8 +6,9 @@ from urllib.parse import urlencode from aiohttp import ClientSession from discord import Embed -from discord.ext.commands import Bot, Cog, Context, group +from discord.ext.commands import Cog, Context, group +from bot.bot import Bot from bot.constants import Tokens from bot.utils.extensions import invoke_help_command from bot.utils.pagination import ImagePaginator diff --git a/bot/exts/evergreen/ping.py b/bot/exts/evergreen/ping.py index 97f8b34d..1332e3e6 100644 --- a/bot/exts/evergreen/ping.py +++ b/bot/exts/evergreen/ping.py @@ -1,13 +1,14 @@ from discord import Embed from discord.ext import commands +from bot.bot import Bot from bot.constants import Colours class Ping(commands.Cog): """Ping the bot to see its latency and state.""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Bot): self.bot = bot @commands.command(name="ping") @@ -22,6 +23,6 @@ class Ping(commands.Cog): await ctx.send(embed=embed) -def setup(bot: commands.Bot) -> None: +def setup(bot: Bot) -> None: """Cog load.""" bot.add_cog(Ping(bot)) diff --git a/bot/exts/evergreen/pythonfacts.py b/bot/exts/evergreen/pythonfacts.py index 457c2fd3..bbc4eb17 100644 --- a/bot/exts/evergreen/pythonfacts.py +++ b/bot/exts/evergreen/pythonfacts.py @@ -3,6 +3,7 @@ import itertools import discord from discord.ext import commands +from bot.bot import Bot from bot.constants import Colours with open('bot/resources/evergreen/python_facts.txt') as file: @@ -14,7 +15,7 @@ COLORS = itertools.cycle([Colours.python_blue, Colours.python_yellow]) class PythonFacts(commands.Cog): """Sends a random fun fact about Python.""" - def __init__(self, bot: commands.Bot) -> None: + def __init__(self, bot: Bot) -> None: self.bot = bot @commands.command(name='pythonfact', aliases=['pyfact']) @@ -28,6 +29,6 @@ class PythonFacts(commands.Cog): await ctx.send(embed=embed) -def setup(bot: commands.Bot) -> None: +def setup(bot: Bot) -> None: """Load PythonFacts Cog.""" bot.add_cog(PythonFacts(bot)) diff --git a/bot/exts/evergreen/recommend_game.py b/bot/exts/evergreen/recommend_game.py index 5e262a5b..be329f44 100644 --- a/bot/exts/evergreen/recommend_game.py +++ b/bot/exts/evergreen/recommend_game.py @@ -6,6 +6,8 @@ from random import shuffle import discord from discord.ext import commands +from bot.bot import Bot + log = logging.getLogger(__name__) game_recs = [] @@ -20,7 +22,7 @@ shuffle(game_recs) class RecommendGame(commands.Cog): """Commands related to recommending games.""" - def __init__(self, bot: commands.Bot) -> None: + def __init__(self, bot: Bot) -> None: self.bot = bot self.index = 0 @@ -45,6 +47,6 @@ class RecommendGame(commands.Cog): await ctx.send(embed=embed) -def setup(bot: commands.Bot) -> None: +def setup(bot: Bot) -> None: """Loads the RecommendGame cog.""" bot.add_cog(RecommendGame(bot)) diff --git a/bot/exts/evergreen/reddit.py b/bot/exts/evergreen/reddit.py index 49127bea..ea77123e 100644 --- a/bot/exts/evergreen/reddit.py +++ b/bot/exts/evergreen/reddit.py @@ -5,6 +5,7 @@ import discord from discord.ext import commands from discord.ext.commands.cooldowns import BucketType +from bot.bot import Bot from bot.utils.pagination import ImagePaginator log = logging.getLogger(__name__) @@ -13,7 +14,7 @@ log = logging.getLogger(__name__) class Reddit(commands.Cog): """Fetches reddit posts.""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Bot): self.bot = bot async def fetch(self, url: str) -> dict: @@ -123,6 +124,6 @@ class Reddit(commands.Cog): await ImagePaginator.paginate(pages, ctx, embed) -def setup(bot: commands.Bot) -> None: +def setup(bot: Bot) -> None: """Load the Cog.""" bot.add_cog(Reddit(bot)) diff --git a/bot/exts/evergreen/source.py b/bot/exts/evergreen/source.py index 45752bf9..3e6031a5 100644 --- a/bot/exts/evergreen/source.py +++ b/bot/exts/evergreen/source.py @@ -5,6 +5,7 @@ from typing import Optional, Tuple, Union from discord import Embed from discord.ext import commands +from bot.bot import Bot from bot.constants import Source SourceType = Union[commands.Command, commands.Cog, str, commands.ExtensionNotLoaded] @@ -31,7 +32,7 @@ class SourceConverter(commands.Converter): class BotSource(commands.Cog): """Displays information about the bot's source code.""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Bot): self.bot = bot @commands.command(name="source", aliases=("src",)) @@ -104,6 +105,6 @@ class BotSource(commands.Cog): return embed -def setup(bot: commands.Bot) -> None: +def setup(bot: Bot) -> None: """Load the BotSource cog.""" bot.add_cog(BotSource(bot)) diff --git a/bot/exts/evergreen/speedrun.py b/bot/exts/evergreen/speedrun.py index 21aad5aa..27d944ca 100644 --- a/bot/exts/evergreen/speedrun.py +++ b/bot/exts/evergreen/speedrun.py @@ -5,6 +5,8 @@ from random import choice from discord.ext import commands +from bot.bot import Bot + log = logging.getLogger(__name__) with Path('bot/resources/evergreen/speedrun_links.json').open(encoding="utf8") as file: LINKS = json.load(file) @@ -13,7 +15,7 @@ with Path('bot/resources/evergreen/speedrun_links.json').open(encoding="utf8") a class Speedrun(commands.Cog): """Commands about the video game speedrunning community.""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Bot): self.bot = bot @commands.command(name="speedrun") @@ -22,6 +24,6 @@ class Speedrun(commands.Cog): await ctx.send(choice(LINKS)) -def setup(bot: commands.Bot) -> None: +def setup(bot: Bot) -> None: """Load the Speedrun cog.""" bot.add_cog(Speedrun(bot)) diff --git a/bot/exts/evergreen/status_codes.py b/bot/exts/evergreen/status_codes.py index 7c00fe20..635eef3d 100644 --- a/bot/exts/evergreen/status_codes.py +++ b/bot/exts/evergreen/status_codes.py @@ -3,6 +3,7 @@ from http import HTTPStatus import discord from discord.ext import commands +from bot.bot import Bot from bot.utils.extensions import invoke_help_command HTTP_DOG_URL = "https://httpstatusdogs.com/img/{code}.jpg" @@ -12,7 +13,7 @@ HTTP_CAT_URL = "https://http.cat/{code}.jpg" class HTTPStatusCodes(commands.Cog): """Commands that give HTTP statuses described and visualized by cats and dogs.""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Bot): self.bot = bot @commands.group(name="http_status", aliases=("status", "httpstatus")) @@ -68,6 +69,6 @@ class HTTPStatusCodes(commands.Cog): await ctx.send(embed=embed) -def setup(bot: commands.Bot) -> None: +def setup(bot: Bot) -> None: """Load the HTTPStatusCodes cog.""" bot.add_cog(HTTPStatusCodes(bot)) diff --git a/bot/exts/evergreen/timed.py b/bot/exts/evergreen/timed.py index 5f177fd6..35ca807c 100644 --- a/bot/exts/evergreen/timed.py +++ b/bot/exts/evergreen/timed.py @@ -4,6 +4,8 @@ from time import perf_counter from discord import Message from discord.ext import commands +from bot.bot import Bot + class TimedCommands(commands.Cog): """Time the command execution of a command.""" @@ -41,6 +43,6 @@ class TimedCommands(commands.Cog): await ctx.send(f"Command execution for `{new_ctx.command}` finished in {(t_end - t_start):.4f} seconds.") -def setup(bot: commands.Bot) -> None: +def setup(bot: Bot) -> None: """Cog load.""" bot.add_cog(TimedCommands(bot)) diff --git a/bot/exts/evergreen/trivia_quiz.py b/bot/exts/evergreen/trivia_quiz.py index fe692c2a..080f5b6f 100644 --- a/bot/exts/evergreen/trivia_quiz.py +++ b/bot/exts/evergreen/trivia_quiz.py @@ -8,6 +8,7 @@ import discord from discord.ext import commands from fuzzywuzzy import fuzz +from bot.bot import Bot from bot.constants import Roles @@ -23,7 +24,7 @@ WRONG_ANS_RESPONSE = [ class TriviaQuiz(commands.Cog): """A cog for all quiz commands.""" - def __init__(self, bot: commands.Bot) -> None: + def __init__(self, bot: Bot) -> None: self.bot = bot self.questions = self.load_questions() self.game_status = {} # A variable to store the game status: either running or not running. @@ -299,6 +300,6 @@ class TriviaQuiz(commands.Cog): await channel.send(embed=embed) -def setup(bot: commands.Bot) -> None: +def setup(bot: Bot) -> None: """Load the cog.""" bot.add_cog(TriviaQuiz(bot)) diff --git a/bot/exts/evergreen/uptime.py b/bot/exts/evergreen/uptime.py index a9ad9dfb..b8813bc7 100644 --- a/bot/exts/evergreen/uptime.py +++ b/bot/exts/evergreen/uptime.py @@ -5,6 +5,7 @@ from dateutil.relativedelta import relativedelta from discord.ext import commands from bot import start_time +from bot.bot import Bot log = logging.getLogger(__name__) @@ -12,7 +13,7 @@ log = logging.getLogger(__name__) class Uptime(commands.Cog): """A cog for posting the bot's uptime.""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Bot): self.bot = bot @commands.command(name="uptime") @@ -28,6 +29,6 @@ class Uptime(commands.Cog): await ctx.send(f"I started up {uptime_string}.") -def setup(bot: commands.Bot) -> None: +def setup(bot: Bot) -> None: """Uptime Cog load.""" bot.add_cog(Uptime(bot)) diff --git a/bot/exts/evergreen/wolfram.py b/bot/exts/evergreen/wolfram.py index 14ec1041..c4c69070 100644 --- a/bot/exts/evergreen/wolfram.py +++ b/bot/exts/evergreen/wolfram.py @@ -9,6 +9,7 @@ from discord import Embed from discord.ext import commands from discord.ext.commands import BucketType, Cog, Context, check, group +from bot.bot import Bot from bot.constants import Colours, STAFF_ROLES, Wolfram from bot.utils.pagination import ImagePaginator @@ -102,7 +103,7 @@ def custom_cooldown(*ignore: List[int]) -> Callable: return check(predicate) -async def get_pod_pages(ctx: Context, bot: commands.Bot, query: str) -> Optional[List[Tuple]]: +async def get_pod_pages(ctx: Context, bot: Bot, query: str) -> Optional[List[Tuple]]: """Get the Wolfram API pod pages for the provided query.""" async with ctx.channel.typing(): url_str = parse.urlencode({ @@ -162,7 +163,7 @@ async def get_pod_pages(ctx: Context, bot: commands.Bot, query: str) -> Optional class Wolfram(Cog): """Commands for interacting with the Wolfram|Alpha API.""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Bot): self.bot = bot @group(name="wolfram", aliases=("wolf", "wa"), invoke_without_command=True) @@ -283,6 +284,6 @@ class Wolfram(Cog): await send_embed(ctx, message, color) -def setup(bot: commands.Bot) -> None: +def setup(bot: Bot) -> None: """Load the Wolfram cog.""" bot.add_cog(Wolfram(bot)) diff --git a/bot/exts/evergreen/wonder_twins.py b/bot/exts/evergreen/wonder_twins.py index afc5346e..80b9bb4d 100644 --- a/bot/exts/evergreen/wonder_twins.py +++ b/bot/exts/evergreen/wonder_twins.py @@ -2,7 +2,9 @@ import random from pathlib import Path import yaml -from discord.ext.commands import Bot, Cog, Context, command +from discord.ext.commands import Cog, Context, command + +from bot.bot import Bot class WonderTwins(Cog): -- cgit v1.2.3 From 713396a91859d914478e417ee6ab9e0a26ee6cf5 Mon Sep 17 00:00:00 2001 From: vcokltfre Date: Mon, 19 Apr 2021 20:56:53 +0100 Subject: chore(evergreen): format each cog load docstring the same way --- bot/exts/evergreen/8bitify.py | 2 +- bot/exts/evergreen/battleship.py | 2 +- bot/exts/evergreen/conversationstarters.py | 2 +- bot/exts/evergreen/emoji.py | 2 +- bot/exts/evergreen/error_handler.py | 2 +- bot/exts/evergreen/fun.py | 2 +- bot/exts/evergreen/game.py | 2 +- bot/exts/evergreen/githubinfo.py | 2 +- bot/exts/evergreen/issues.py | 2 +- bot/exts/evergreen/magic_8ball.py | 2 +- bot/exts/evergreen/movie.py | 2 +- bot/exts/evergreen/ping.py | 2 +- bot/exts/evergreen/pythonfacts.py | 2 +- bot/exts/evergreen/reddit.py | 2 +- bot/exts/evergreen/space.py | 2 +- bot/exts/evergreen/tic_tac_toe.py | 2 +- bot/exts/evergreen/timed.py | 2 +- bot/exts/evergreen/trivia_quiz.py | 2 +- bot/exts/evergreen/uptime.py | 2 +- bot/exts/evergreen/wikipedia.py | 2 +- bot/exts/evergreen/xkcd.py | 2 +- 21 files changed, 21 insertions(+), 21 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/8bitify.py b/bot/exts/evergreen/8bitify.py index 621aa875..09a3eb5c 100644 --- a/bot/exts/evergreen/8bitify.py +++ b/bot/exts/evergreen/8bitify.py @@ -53,5 +53,5 @@ class EightBitify(commands.Cog): def setup(bot: Bot) -> None: - """Cog load.""" + """Cog the EightBitify load.""" bot.add_cog(EightBitify(bot)) diff --git a/bot/exts/evergreen/battleship.py b/bot/exts/evergreen/battleship.py index f255afb9..ca0e3881 100644 --- a/bot/exts/evergreen/battleship.py +++ b/bot/exts/evergreen/battleship.py @@ -440,5 +440,5 @@ class Battleship(commands.Cog): def setup(bot: Bot) -> None: - """Cog load.""" + """Load the Battleship cog""" bot.add_cog(Battleship(bot)) diff --git a/bot/exts/evergreen/conversationstarters.py b/bot/exts/evergreen/conversationstarters.py index aed76bcf..04194c01 100644 --- a/bot/exts/evergreen/conversationstarters.py +++ b/bot/exts/evergreen/conversationstarters.py @@ -68,5 +68,5 @@ class ConvoStarters(commands.Cog): def setup(bot: Bot) -> None: - """Conversation starters Cog load.""" + """Load the ConvoStarters cog.""" bot.add_cog(ConvoStarters(bot)) diff --git a/bot/exts/evergreen/emoji.py b/bot/exts/evergreen/emoji.py index 58d9be03..45b77e16 100644 --- a/bot/exts/evergreen/emoji.py +++ b/bot/exts/evergreen/emoji.py @@ -122,5 +122,5 @@ class Emojis(commands.Cog): def setup(bot: Bot) -> None: - """Add the Emojis cog into the bot.""" + """Load the Emojis cog.""" bot.add_cog(Emojis(bot)) diff --git a/bot/exts/evergreen/error_handler.py b/bot/exts/evergreen/error_handler.py index 053e3866..64460822 100644 --- a/bot/exts/evergreen/error_handler.py +++ b/bot/exts/evergreen/error_handler.py @@ -137,5 +137,5 @@ class CommandErrorHandler(commands.Cog): def setup(bot: Bot) -> None: - """Error handler Cog load.""" + """Load the ErrorHandler cog.""" bot.add_cog(CommandErrorHandler(bot)) diff --git a/bot/exts/evergreen/fun.py b/bot/exts/evergreen/fun.py index cde37895..c7b0d7d9 100644 --- a/bot/exts/evergreen/fun.py +++ b/bot/exts/evergreen/fun.py @@ -241,5 +241,5 @@ class Fun(Cog): def setup(bot: Bot) -> None: - """Fun Cog load.""" + """Load the Fun cog.""" bot.add_cog(Fun(bot)) diff --git a/bot/exts/evergreen/game.py b/bot/exts/evergreen/game.py index 068d3f68..24872e76 100644 --- a/bot/exts/evergreen/game.py +++ b/bot/exts/evergreen/game.py @@ -471,7 +471,7 @@ class Games(Cog): def setup(bot: Bot) -> None: - """Add/Load Games cog.""" + """Load the Games cog.""" # Check does IGDB API key exist, if not, log warning and don't load cog if not Tokens.igdb_client_id: logger.warning("No IGDB client ID. Not loading Games cog.") diff --git a/bot/exts/evergreen/githubinfo.py b/bot/exts/evergreen/githubinfo.py index da6eba5c..65fcc7cf 100644 --- a/bot/exts/evergreen/githubinfo.py +++ b/bot/exts/evergreen/githubinfo.py @@ -172,5 +172,5 @@ class GithubInfo(commands.Cog): def setup(bot: Bot) -> None: - """Adding the cog to the bot.""" + """Load the GithubInfo cog.""" bot.add_cog(GithubInfo(bot)) diff --git a/bot/exts/evergreen/issues.py b/bot/exts/evergreen/issues.py index 692e0b43..d7ee99c0 100644 --- a/bot/exts/evergreen/issues.py +++ b/bot/exts/evergreen/issues.py @@ -271,5 +271,5 @@ class Issues(commands.Cog): def setup(bot: Bot) -> None: - """Cog Retrieves Issues From Github.""" + """Load the Issues cog.""" bot.add_cog(Issues(bot)) diff --git a/bot/exts/evergreen/magic_8ball.py b/bot/exts/evergreen/magic_8ball.py index 2dfe237a..61c68693 100644 --- a/bot/exts/evergreen/magic_8ball.py +++ b/bot/exts/evergreen/magic_8ball.py @@ -29,5 +29,5 @@ class Magic8ball(commands.Cog): def setup(bot: Bot) -> None: - """Magic 8ball Cog load.""" + """Load the Magic8Ball cog.""" bot.add_cog(Magic8ball(bot)) diff --git a/bot/exts/evergreen/movie.py b/bot/exts/evergreen/movie.py index f4356f33..0d535b03 100644 --- a/bot/exts/evergreen/movie.py +++ b/bot/exts/evergreen/movie.py @@ -199,5 +199,5 @@ class Movie(Cog): def setup(bot: Bot) -> None: - """Load Movie Cog.""" + """Load the Movie Cog.""" bot.add_cog(Movie(bot)) diff --git a/bot/exts/evergreen/ping.py b/bot/exts/evergreen/ping.py index 1332e3e6..71152d15 100644 --- a/bot/exts/evergreen/ping.py +++ b/bot/exts/evergreen/ping.py @@ -24,5 +24,5 @@ class Ping(commands.Cog): def setup(bot: Bot) -> None: - """Cog load.""" + """Load the Ping cog.""" bot.add_cog(Ping(bot)) diff --git a/bot/exts/evergreen/pythonfacts.py b/bot/exts/evergreen/pythonfacts.py index bbc4eb17..1dd9b40d 100644 --- a/bot/exts/evergreen/pythonfacts.py +++ b/bot/exts/evergreen/pythonfacts.py @@ -30,5 +30,5 @@ class PythonFacts(commands.Cog): def setup(bot: Bot) -> None: - """Load PythonFacts Cog.""" + """Load the PythonFacts Cog.""" bot.add_cog(PythonFacts(bot)) diff --git a/bot/exts/evergreen/reddit.py b/bot/exts/evergreen/reddit.py index ea77123e..ce22a864 100644 --- a/bot/exts/evergreen/reddit.py +++ b/bot/exts/evergreen/reddit.py @@ -125,5 +125,5 @@ class Reddit(commands.Cog): def setup(bot: Bot) -> None: - """Load the Cog.""" + """Load the Reddit cog.""" bot.add_cog(Reddit(bot)) diff --git a/bot/exts/evergreen/space.py b/bot/exts/evergreen/space.py index 323ff659..b3f0016b 100644 --- a/bot/exts/evergreen/space.py +++ b/bot/exts/evergreen/space.py @@ -242,7 +242,7 @@ class Space(Cog): def setup(bot: Bot) -> None: - """Load Space Cog.""" + """Load the Space cog.""" if not Tokens.nasa: logger.warning("Can't find NASA API key. Not loading Space Cog.") return diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index 6e21528e..15cc1ef3 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -323,5 +323,5 @@ class TicTacToe(Cog): def setup(bot: Bot) -> None: - """Load TicTacToe Cog.""" + """Load the TicTacToe cog.""" bot.add_cog(TicTacToe(bot)) diff --git a/bot/exts/evergreen/timed.py b/bot/exts/evergreen/timed.py index 35ca807c..42a77346 100644 --- a/bot/exts/evergreen/timed.py +++ b/bot/exts/evergreen/timed.py @@ -44,5 +44,5 @@ class TimedCommands(commands.Cog): def setup(bot: Bot) -> None: - """Cog load.""" + """Load the Timed cog.""" bot.add_cog(TimedCommands(bot)) diff --git a/bot/exts/evergreen/trivia_quiz.py b/bot/exts/evergreen/trivia_quiz.py index 080f5b6f..f40375a6 100644 --- a/bot/exts/evergreen/trivia_quiz.py +++ b/bot/exts/evergreen/trivia_quiz.py @@ -301,5 +301,5 @@ class TriviaQuiz(commands.Cog): def setup(bot: Bot) -> None: - """Load the cog.""" + """Load the TriviaQuiz cog.""" bot.add_cog(TriviaQuiz(bot)) diff --git a/bot/exts/evergreen/uptime.py b/bot/exts/evergreen/uptime.py index b8813bc7..c22af2c9 100644 --- a/bot/exts/evergreen/uptime.py +++ b/bot/exts/evergreen/uptime.py @@ -30,5 +30,5 @@ class Uptime(commands.Cog): def setup(bot: Bot) -> None: - """Uptime Cog load.""" + """Load the Uptime cog.""" bot.add_cog(Uptime(bot)) diff --git a/bot/exts/evergreen/wikipedia.py b/bot/exts/evergreen/wikipedia.py index 068c4f43..e2172fc3 100644 --- a/bot/exts/evergreen/wikipedia.py +++ b/bot/exts/evergreen/wikipedia.py @@ -90,5 +90,5 @@ class WikipediaSearch(commands.Cog): def setup(bot: Bot) -> None: - """Wikipedia Cog load.""" + """Load the WikipediaSearch cog.""" bot.add_cog(WikipediaSearch(bot)) diff --git a/bot/exts/evergreen/xkcd.py b/bot/exts/evergreen/xkcd.py index 1ff98ca2..ba9e46e0 100644 --- a/bot/exts/evergreen/xkcd.py +++ b/bot/exts/evergreen/xkcd.py @@ -87,5 +87,5 @@ class XKCD(Cog): def setup(bot: Bot) -> None: - """Loading the XKCD cog.""" + """Load the XKCD cog.""" bot.add_cog(XKCD(bot)) -- cgit v1.2.3 From 227d0a7705e08b74deb9b95ec4f643b1d0a0fcdd Mon Sep 17 00:00:00 2001 From: vcokltfre Date: Mon, 19 Apr 2021 21:09:10 +0100 Subject: chore(evergreen): make usage of . at sentence ends consistent --- bot/exts/evergreen/battleship.py | 6 +++--- bot/exts/evergreen/bookmark.py | 4 ++-- bot/exts/evergreen/connect_four.py | 2 +- bot/exts/evergreen/emoji.py | 2 +- bot/exts/evergreen/githubinfo.py | 2 +- bot/exts/evergreen/minesweeper.py | 10 +++++----- bot/exts/evergreen/wolfram.py | 12 ++++++------ 7 files changed, 19 insertions(+), 19 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/battleship.py b/bot/exts/evergreen/battleship.py index ca0e3881..813f998e 100644 --- a/bot/exts/evergreen/battleship.py +++ b/bot/exts/evergreen/battleship.py @@ -238,7 +238,7 @@ class Game: square = None turn_message = await self.turn.user.send( "It's your turn! Type the square you want to fire at. Format it like this: A1\n" - "Type `surrender` to give up" + "Type `surrender` to give up." ) await self.next.user.send("Their turn", delete_after=3.0) while True: @@ -382,7 +382,7 @@ class Battleship(commands.Cog): return await ctx.send("You're already playing a game!") if ctx.author in self.waiting: - return await ctx.send("You've already sent out a request for a player 2") + return await ctx.send("You've already sent out a request for a player 2.") announcement = await ctx.send( "**Battleship**: A new game is about to start!\n" @@ -426,7 +426,7 @@ class Battleship(commands.Cog): self.games.remove(game) except Exception: # End the game in the event of an unforseen error so the players aren't stuck in a game - await ctx.send(f"{ctx.author.mention} {user.mention} An error occurred. Game failed") + await ctx.send(f"{ctx.author.mention} {user.mention} An error occurred. Game failed.") self.games.remove(game) raise diff --git a/bot/exts/evergreen/bookmark.py b/bot/exts/evergreen/bookmark.py index 329838b9..ec25e7a7 100644 --- a/bot/exts/evergreen/bookmark.py +++ b/bot/exts/evergreen/bookmark.py @@ -29,7 +29,7 @@ class Bookmark(commands.Cog): # Prevent users from bookmarking a message in a channel they don't have access to permissions = ctx.author.permissions_in(target_message.channel) if not permissions.read_messages: - log.info(f"{ctx.author} tried to bookmark a message in #{target_message.channel} but has no permissions") + log.info(f"{ctx.author} tried to bookmark a message in #{target_message.channel} but has no permissions.") embed = discord.Embed( title=random.choice(ERROR_REPLIES), color=Colours.soft_red, @@ -52,7 +52,7 @@ class Bookmark(commands.Cog): except discord.Forbidden: error_embed = discord.Embed( title=random.choice(ERROR_REPLIES), - description=f"{ctx.author.mention}, please enable your DMs to receive the bookmark", + description=f"{ctx.author.mention}, please enable your DMs to receive the bookmark.", colour=Colours.soft_red ) await ctx.send(embed=error_embed) diff --git a/bot/exts/evergreen/connect_four.py b/bot/exts/evergreen/connect_four.py index fbb13780..df2a913a 100644 --- a/bot/exts/evergreen/connect_four.py +++ b/bot/exts/evergreen/connect_four.py @@ -355,7 +355,7 @@ class ConnectFour(commands.Cog): self.games.remove(game) except Exception: # End the game in the event of an unforeseen error so the players aren't stuck in a game - await ctx.send(f"{ctx.author.mention} {user.mention if user else ''} An error occurred. Game failed") + await ctx.send(f"{ctx.author.mention} {user.mention if user else ''} An error occurred. Game failed.") if game in self.games: self.games.remove(game) raise diff --git a/bot/exts/evergreen/emoji.py b/bot/exts/evergreen/emoji.py index 45b77e16..7ceb6c8d 100644 --- a/bot/exts/evergreen/emoji.py +++ b/bot/exts/evergreen/emoji.py @@ -87,7 +87,7 @@ class Emojis(commands.Cog): if not ctx.guild.emojis: await ctx.send("No emojis found.") return - log.trace(f"Emoji Category {'' if category_query else 'not '}provided by the user") + log.trace(f"Emoji Category {'' if category_query else 'not '}provided by the user.") for emoji in ctx.guild.emojis: emoji_category = emoji.name.split("_")[0] diff --git a/bot/exts/evergreen/githubinfo.py b/bot/exts/evergreen/githubinfo.py index 65fcc7cf..fd100a7c 100644 --- a/bot/exts/evergreen/githubinfo.py +++ b/bot/exts/evergreen/githubinfo.py @@ -96,7 +96,7 @@ class GithubInfo(commands.Cog): embed.add_field( name=f"Organization{'s' if len(orgs)!=1 else ''}", - value=orgs_to_add if orgs else "No organizations" + value=orgs_to_add if orgs else "No organizations." ) embed.add_field(name="Website", value=blog) diff --git a/bot/exts/evergreen/minesweeper.py b/bot/exts/evergreen/minesweeper.py index 7c1bac3b..bba349fa 100644 --- a/bot/exts/evergreen/minesweeper.py +++ b/bot/exts/evergreen/minesweeper.py @@ -38,7 +38,7 @@ class CoordinateConverter(commands.Converter): async def convert(self, ctx: commands.Context, coordinate: str) -> typing.Tuple[int, int]: """Take in a coordinate string and turn it into an (x, y) tuple.""" if not 2 <= len(coordinate) <= 3: - raise commands.BadArgument('Invalid co-ordinate provided') + raise commands.BadArgument('Invalid co-ordinate provided.') coordinate = coordinate.lower() if coordinate[0].isalpha(): @@ -149,7 +149,7 @@ class Minesweeper(commands.Cog): f"Close the game with `{Client.prefix}ms end`\n" ) except discord.errors.Forbidden: - log.debug(f"{ctx.author.name} ({ctx.author.id}) has disabled DMs from server members") + log.debug(f"{ctx.author.name} ({ctx.author.id}) has disabled DMs from server members.") await ctx.send(f":x: {ctx.author.mention}, please enable DMs to play minesweeper.") return @@ -159,7 +159,7 @@ class Minesweeper(commands.Cog): dm_msg = await ctx.author.send(f"Here's your board!\n{self.format_for_discord(revealed_board)}") if ctx.guild: - await ctx.send(f"{ctx.author.mention} is playing Minesweeper") + await ctx.send(f"{ctx.author.mention} is playing Minesweeper.") chat_msg = await ctx.send(f"Here's their board!\n{self.format_for_discord(revealed_board)}") else: chat_msg = None @@ -248,7 +248,7 @@ class Minesweeper(commands.Cog): """ Reveal one square. - return is True if the game ended, breaking the loop in `reveal_command` and deleting the game + return is True if the game ended, breaking the loop in `reveal_command` and deleting the game. """ revealed[y][x] = board[y][x] if board[y][x] == "bomb": @@ -286,7 +286,7 @@ class Minesweeper(commands.Cog): game = self.games[ctx.author.id] game.revealed = game.board await self.update_boards(ctx) - new_msg = f":no_entry: Game canceled :no_entry:\n{game.dm_msg.content}" + new_msg = f":no_entry: Game canceled. :no_entry:\n{game.dm_msg.content}" await game.dm_msg.edit(content=new_msg) if game.activated_on_server: await game.chat_msg.edit(content=new_msg) diff --git a/bot/exts/evergreen/wolfram.py b/bot/exts/evergreen/wolfram.py index c4c69070..c57a8d7a 100644 --- a/bot/exts/evergreen/wolfram.py +++ b/bot/exts/evergreen/wolfram.py @@ -56,7 +56,7 @@ def custom_cooldown(*ignore: List[int]) -> Callable: """ Implement per-user and per-guild cooldowns for requests to the Wolfram API. - A list of roles may be provided to ignore the per-user cooldown + A list of roles may be provided to ignore the per-user cooldown. """ async def predicate(ctx: Context) -> bool: if ctx.invoked_with == 'help': @@ -189,11 +189,11 @@ class Wolfram(Cog): image_url = "attachment://image.png" if status == 501: - message = "Failed to get response" + message = "Failed to get response." footer = "" color = Colours.soft_red elif status == 400: - message = "No input found" + message = "No input found." footer = "" color = Colours.soft_red elif status == 403: @@ -269,12 +269,12 @@ class Wolfram(Cog): response_text = await response.text() if status == 501: - message = "Failed to get response" + message = "Failed to get response." color = Colours.soft_red elif status == 400: - message = "No input found" + message = "No input found." color = Colours.soft_red - elif response_text == "Error 1: Invalid appid": + elif response_text == "Error 1: Invalid appid.": message = "Wolfram API key is invalid or missing." color = Colours.soft_red else: -- cgit v1.2.3 From f919e0cdb9cff71c187f55ea4447ef4934c5b9ab Mon Sep 17 00:00:00 2001 From: vcokltfre Date: Mon, 19 Apr 2021 21:17:10 +0100 Subject: chore(evergreen): remove unneeded cog constructors --- bot/exts/evergreen/bookmark.py | 3 --- bot/exts/evergreen/conversationstarters.py | 3 --- bot/exts/evergreen/emoji.py | 3 --- bot/exts/evergreen/error_handler.py | 3 --- bot/exts/evergreen/magic_8ball.py | 3 +-- bot/exts/evergreen/minesweeper.py | 2 +- bot/exts/evergreen/movie.py | 1 - bot/exts/evergreen/pythonfacts.py | 3 --- bot/exts/evergreen/source.py | 3 --- bot/exts/evergreen/space.py | 1 - bot/exts/evergreen/speedrun.py | 3 --- bot/exts/evergreen/tic_tac_toe.py | 3 +-- bot/exts/evergreen/uptime.py | 3 --- bot/exts/evergreen/wonder_twins.py | 4 +--- 14 files changed, 4 insertions(+), 34 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/bookmark.py b/bot/exts/evergreen/bookmark.py index ec25e7a7..6a272784 100644 --- a/bot/exts/evergreen/bookmark.py +++ b/bot/exts/evergreen/bookmark.py @@ -14,9 +14,6 @@ log = logging.getLogger(__name__) class Bookmark(commands.Cog): """Creates personal bookmarks by relaying a message link to the user's DMs.""" - def __init__(self, bot: Bot): - self.bot = bot - @commands.command(name="bookmark", aliases=("bm", "pin")) async def bookmark( self, diff --git a/bot/exts/evergreen/conversationstarters.py b/bot/exts/evergreen/conversationstarters.py index 04194c01..54fea0b3 100644 --- a/bot/exts/evergreen/conversationstarters.py +++ b/bot/exts/evergreen/conversationstarters.py @@ -35,9 +35,6 @@ TOPICS = { class ConvoStarters(commands.Cog): """Evergreen conversation topics.""" - def __init__(self, bot: Bot): - self.bot = bot - @commands.command() @whitelist_override(channels=ALL_ALLOWED_CHANNELS) async def topic(self, ctx: commands.Context) -> None: diff --git a/bot/exts/evergreen/emoji.py b/bot/exts/evergreen/emoji.py index 7ceb6c8d..8e540712 100644 --- a/bot/exts/evergreen/emoji.py +++ b/bot/exts/evergreen/emoji.py @@ -20,9 +20,6 @@ log = logging.getLogger(__name__) class Emojis(commands.Cog): """A collection of commands related to emojis in the server.""" - def __init__(self, bot: Bot): - self.bot = bot - @staticmethod def embed_builder(emoji: dict) -> Tuple[Embed, List[str]]: """Generates an embed with the emoji names and count.""" diff --git a/bot/exts/evergreen/error_handler.py b/bot/exts/evergreen/error_handler.py index 64460822..dabd0ab5 100644 --- a/bot/exts/evergreen/error_handler.py +++ b/bot/exts/evergreen/error_handler.py @@ -18,9 +18,6 @@ log = logging.getLogger(__name__) class CommandErrorHandler(commands.Cog): """A error handler for the PythonDiscord server.""" - def __init__(self, bot: Bot): - self.bot = bot - @staticmethod def revert_cooldown_counter(command: commands.Command, message: Message) -> None: """Undoes the last cooldown counter for user-error cases.""" diff --git a/bot/exts/evergreen/magic_8ball.py b/bot/exts/evergreen/magic_8ball.py index 61c68693..708aa6d2 100644 --- a/bot/exts/evergreen/magic_8ball.py +++ b/bot/exts/evergreen/magic_8ball.py @@ -13,8 +13,7 @@ log = logging.getLogger(__name__) class Magic8ball(commands.Cog): """A Magic 8ball command to respond to a user's question.""" - def __init__(self, bot: Bot): - self.bot = bot + def __init__(self, _bot: Bot): with open(Path("bot/resources/evergreen/magic8ball.json"), "r", encoding="utf8") as file: self.answers = json.load(file) diff --git a/bot/exts/evergreen/minesweeper.py b/bot/exts/evergreen/minesweeper.py index bba349fa..d0cc28c5 100644 --- a/bot/exts/evergreen/minesweeper.py +++ b/bot/exts/evergreen/minesweeper.py @@ -79,7 +79,7 @@ GamesDict = typing.Dict[int, Game] class Minesweeper(commands.Cog): """Play a game of Minesweeper.""" - def __init__(self, bot: Bot) -> None: + def __init__(self, _bot: Bot) -> None: self.games: GamesDict = {} # Store the currently running games @commands.group(name='minesweeper', aliases=('ms',), invoke_without_command=True) diff --git a/bot/exts/evergreen/movie.py b/bot/exts/evergreen/movie.py index 0d535b03..488e5142 100644 --- a/bot/exts/evergreen/movie.py +++ b/bot/exts/evergreen/movie.py @@ -51,7 +51,6 @@ class Movie(Cog): """Movie Cog contains movies command that grab random movies from TMDB.""" def __init__(self, bot: Bot): - self.bot = bot self.http_session: ClientSession = bot.http_session @group(name='movies', aliases=['movie'], invoke_without_command=True) diff --git a/bot/exts/evergreen/pythonfacts.py b/bot/exts/evergreen/pythonfacts.py index 1dd9b40d..73234d55 100644 --- a/bot/exts/evergreen/pythonfacts.py +++ b/bot/exts/evergreen/pythonfacts.py @@ -15,9 +15,6 @@ COLORS = itertools.cycle([Colours.python_blue, Colours.python_yellow]) class PythonFacts(commands.Cog): """Sends a random fun fact about Python.""" - def __init__(self, bot: Bot) -> None: - self.bot = bot - @commands.command(name='pythonfact', aliases=['pyfact']) async def get_python_fact(self, ctx: commands.Context) -> None: """Sends a Random fun fact about Python.""" diff --git a/bot/exts/evergreen/source.py b/bot/exts/evergreen/source.py index 3e6031a5..14fd02f3 100644 --- a/bot/exts/evergreen/source.py +++ b/bot/exts/evergreen/source.py @@ -32,9 +32,6 @@ class SourceConverter(commands.Converter): class BotSource(commands.Cog): """Displays information about the bot's source code.""" - def __init__(self, bot: Bot): - self.bot = bot - @commands.command(name="source", aliases=("src",)) async def source_command(self, ctx: commands.Context, *, source_item: SourceConverter = None) -> None: """Display information and a GitHub link to the source code of a command, tag, or cog.""" diff --git a/bot/exts/evergreen/space.py b/bot/exts/evergreen/space.py index b3f0016b..ee9ad38c 100644 --- a/bot/exts/evergreen/space.py +++ b/bot/exts/evergreen/space.py @@ -39,7 +39,6 @@ class Space(Cog): """Space Cog contains commands, that show images, facts or other information about space.""" def __init__(self, bot: Bot): - self.bot = bot self.http_session = bot.http_session self.rovers = {} diff --git a/bot/exts/evergreen/speedrun.py b/bot/exts/evergreen/speedrun.py index 27d944ca..bf6f2117 100644 --- a/bot/exts/evergreen/speedrun.py +++ b/bot/exts/evergreen/speedrun.py @@ -15,9 +15,6 @@ with Path('bot/resources/evergreen/speedrun_links.json').open(encoding="utf8") a class Speedrun(commands.Cog): """Commands about the video game speedrunning community.""" - def __init__(self, bot: Bot): - self.bot = bot - @commands.command(name="speedrun") async def get_speedrun(self, ctx: commands.Context) -> None: """Sends a link to a video of a random speedrun.""" diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index 15cc1ef3..1fef427a 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -246,8 +246,7 @@ def is_requester_free() -> t.Callable: class TicTacToe(Cog): """TicTacToe cog contains tic-tac-toe game commands.""" - def __init__(self, bot: Bot): - self.bot = bot + def __init__(self, _bot: Bot): self.games: t.List[Game] = [] @guild_only() diff --git a/bot/exts/evergreen/uptime.py b/bot/exts/evergreen/uptime.py index c22af2c9..d2509e6f 100644 --- a/bot/exts/evergreen/uptime.py +++ b/bot/exts/evergreen/uptime.py @@ -13,9 +13,6 @@ log = logging.getLogger(__name__) class Uptime(commands.Cog): """A cog for posting the bot's uptime.""" - def __init__(self, bot: Bot): - self.bot = bot - @commands.command(name="uptime") async def uptime(self, ctx: commands.Context) -> None: """Responds with the uptime of the bot.""" diff --git a/bot/exts/evergreen/wonder_twins.py b/bot/exts/evergreen/wonder_twins.py index 80b9bb4d..9fa2d7f8 100644 --- a/bot/exts/evergreen/wonder_twins.py +++ b/bot/exts/evergreen/wonder_twins.py @@ -10,9 +10,7 @@ from bot.bot import Bot class WonderTwins(Cog): """Cog for a Wonder Twins inspired command.""" - def __init__(self, bot: Bot): - self.bot = bot - + def __init__(self, _bot: Bot): with open(Path.cwd() / "bot" / "resources" / "evergreen" / "wonder_twins.yaml", "r", encoding="utf-8") as f: info = yaml.load(f, Loader=yaml.FullLoader) self.water_types = info["water_types"] -- cgit v1.2.3 From ae64ddd3e7d731a44a1684c93d67ea6334b120d2 Mon Sep 17 00:00:00 2001 From: ToxicKidz <78174417+ToxicKidz@users.noreply.github.com> Date: Mon, 19 Apr 2021 17:04:11 -0400 Subject: Clean Up the Halloween Season - Change commands.Bot type hints to bot.Bot - Remove the setting of Cog.bot if it isn't being used - Use Bot.http_session instead of creating new ones - Keep string quotes and Doc-Strings consistant - Remove redundant/outdated code Ignored spookyavatar.py as it is being moved in another PR. --- bot/exts/halloween/8ball.py | 17 ++--- bot/exts/halloween/candy_collection.py | 35 +++++---- bot/exts/halloween/hacktober-issue-finder.py | 73 +++++++++--------- bot/exts/halloween/hacktoberstats.py | 108 ++++++++++++--------------- bot/exts/halloween/halloween_facts.py | 15 ++-- bot/exts/halloween/halloweenify.py | 13 ++-- bot/exts/halloween/monsterbio.py | 10 +-- bot/exts/halloween/monstersurvey.py | 6 +- bot/exts/halloween/scarymovie.py | 74 +++++++++--------- bot/exts/halloween/spookygif.py | 23 +++--- bot/exts/halloween/spookynamerate.py | 21 +++--- bot/exts/halloween/spookyrating.py | 14 ++-- bot/exts/halloween/spookyreact.py | 29 +++---- bot/exts/halloween/timeleft.py | 8 +- 14 files changed, 216 insertions(+), 230 deletions(-) (limited to 'bot') diff --git a/bot/exts/halloween/8ball.py b/bot/exts/halloween/8ball.py index 1df48fbf..59d4acc5 100644 --- a/bot/exts/halloween/8ball.py +++ b/bot/exts/halloween/8ball.py @@ -6,28 +6,27 @@ from pathlib import Path from discord.ext import commands +from bot.bot import Bot + log = logging.getLogger(__name__) -with open(Path("bot/resources/halloween/responses.json"), "r", encoding="utf8") as f: - responses = json.load(f) +with Path("bot/resources/halloween/responses.json").open("r", encoding="utf8") as f: + RESPONSES = json.load(f) class SpookyEightBall(commands.Cog): """Spooky Eightball answers.""" - def __init__(self, bot: commands.Bot): - self.bot = bot - @commands.command(aliases=('spooky8ball',)) async def spookyeightball(self, ctx: commands.Context, *, question: str) -> None: """Responds with a random response to a question.""" - choice = random.choice(responses['responses']) + choice = random.choice(RESPONSES["responses"]) msg = await ctx.send(choice[0]) if len(choice) > 1: await asyncio.sleep(random.randint(2, 5)) await msg.edit(content=f"{choice[0]} \n{choice[1]}") -def setup(bot: commands.Bot) -> None: - """Spooky Eight Ball Cog Load.""" - bot.add_cog(SpookyEightBall(bot)) +def setup(bot: Bot) -> None: + """Load the Spooky Eight Ball Cog.""" + bot.add_cog(SpookyEightBall()) diff --git a/bot/exts/halloween/candy_collection.py b/bot/exts/halloween/candy_collection.py index 40e21f40..5441d8a5 100644 --- a/bot/exts/halloween/candy_collection.py +++ b/bot/exts/halloween/candy_collection.py @@ -6,6 +6,7 @@ import discord from async_rediscache import RedisCache from discord.ext import commands +from bot.bot import Bot from bot.constants import Channels, Month from bot.utils.decorators import in_month @@ -40,7 +41,7 @@ class CandyCollection(commands.Cog): candy_messages = RedisCache() skull_messages = RedisCache() - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Bot): self.bot = bot @in_month(Month.OCTOBER) @@ -60,15 +61,15 @@ class CandyCollection(commands.Cog): # do random check for skull first as it has the lower chance if random.randint(1, ADD_SKULL_REACTION_CHANCE) == 1: await self.skull_messages.set(message.id, "skull") - return await message.add_reaction(EMOJIS['SKULL']) + await message.add_reaction(EMOJIS["SKULL"]) # check for the candy chance next - if random.randint(1, ADD_CANDY_REACTION_CHANCE) == 1: + elif random.randint(1, ADD_CANDY_REACTION_CHANCE) == 1: await self.candy_messages.set(message.id, "candy") - return await message.add_reaction(EMOJIS['CANDY']) + await message.add_reaction(EMOJIS["CANDY"]) @in_month(Month.OCTOBER) @commands.Cog.listener() - async def on_reaction_add(self, reaction: discord.Reaction, user: discord.Member) -> None: + async def on_reaction_add(self, reaction: discord.Reaction, user: Union[discord.User, discord.Member]) -> None: """Add/remove candies from a person if the reaction satisfies criteria.""" message = reaction.message # check to ensure the reactor is human @@ -81,7 +82,7 @@ class CandyCollection(commands.Cog): # if its not a candy or skull, and it is one of 10 most recent messages, # proceed to add a skull/candy with higher chance - if str(reaction.emoji) not in (EMOJIS['SKULL'], EMOJIS['CANDY']): + if str(reaction.emoji) not in (EMOJIS["SKULL"], EMOJIS["CANDY"]): recent_message_ids = map( lambda m: m.id, await self.hacktober_channel.history(limit=10).flatten() @@ -90,14 +91,14 @@ class CandyCollection(commands.Cog): await self.reacted_msg_chance(message) return - if await self.candy_messages.get(message.id) == "candy" and str(reaction.emoji) == EMOJIS['CANDY']: + if await self.candy_messages.get(message.id) == "candy" and str(reaction.emoji) == EMOJIS["CANDY"]: await self.candy_messages.delete(message.id) if await self.candy_records.contains(user.id): await self.candy_records.increment(user.id) else: await self.candy_records.set(user.id, 1) - elif await self.skull_messages.get(message.id) == "skull" and str(reaction.emoji) == EMOJIS['SKULL']: + elif await self.skull_messages.get(message.id) == "skull" and str(reaction.emoji) == EMOJIS["SKULL"]: await self.skull_messages.delete(message.id) if prev_record := await self.candy_records.get(user.id): @@ -124,11 +125,11 @@ class CandyCollection(commands.Cog): """ if random.randint(1, ADD_SKULL_EXISTING_REACTION_CHANCE) == 1: await self.skull_messages.set(message.id, "skull") - return await message.add_reaction(EMOJIS['SKULL']) + await message.add_reaction(EMOJIS['SKULL']) - if random.randint(1, ADD_CANDY_EXISTING_REACTION_CHANCE) == 1: + elif random.randint(1, ADD_CANDY_EXISTING_REACTION_CHANCE) == 1: await self.candy_messages.set(message.id, "candy") - return await message.add_reaction(EMOJIS['CANDY']) + await message.add_reaction(EMOJIS["CANDY"]) @property def hacktober_channel(self) -> discord.TextChannel: @@ -141,8 +142,10 @@ class CandyCollection(commands.Cog): ) -> None: """Send a spooky message.""" e = discord.Embed(colour=author.colour) - e.set_author(name="Ghosts and Ghouls and Jack o' lanterns at night; " - f"I took {candies} candies and quickly took flight.") + e.set_author( + name="Ghosts and Ghouls and Jack o' lanterns at night; " + f"I took {candies} candies and quickly took flight." + ) await channel.send(embed=e) @staticmethod @@ -173,7 +176,7 @@ class CandyCollection(commands.Cog): return '\n'.join( f"{EMOJIS['MEDALS'][index]} <@{record[0]}>: {record[1]}" for index, record in enumerate(top_five) - ) if top_five else 'No Candies' + ) if top_five else "No Candies" e = discord.Embed(colour=discord.Colour.blurple()) e.add_field( @@ -191,6 +194,6 @@ class CandyCollection(commands.Cog): await ctx.send(embed=e) -def setup(bot: commands.Bot) -> None: - """Candy Collection game Cog load.""" +def setup(bot: Bot) -> None: + """Load the Candy Collection Cog.""" bot.add_cog(CandyCollection(bot)) diff --git a/bot/exts/halloween/hacktober-issue-finder.py b/bot/exts/halloween/hacktober-issue-finder.py index 9deadde9..c88e2b6f 100644 --- a/bot/exts/halloween/hacktober-issue-finder.py +++ b/bot/exts/halloween/hacktober-issue-finder.py @@ -3,10 +3,10 @@ import logging import random from typing import Dict, Optional -import aiohttp import discord from discord.ext import commands +from bot.bot import Bot from bot.constants import Month, Tokens from bot.utils.decorators import in_month @@ -25,7 +25,7 @@ if GITHUB_TOKEN := Tokens.github: class HacktoberIssues(commands.Cog): """Find a random hacktober python issue on GitHub.""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Bot): self.bot = bot self.cache_normal = None self.cache_timer_normal = datetime.datetime(1, 1, 1) @@ -41,7 +41,7 @@ class HacktoberIssues(commands.Cog): If the command is run with beginner (`.hacktoberissues beginner`): It will also narrow it down to the "first good issue" label. """ - with ctx.typing(): + async with ctx.typing(): issues = await self.get_issues(ctx, option) if issues is None: return @@ -59,40 +59,39 @@ class HacktoberIssues(commands.Cog): log.debug("using cache") return self.cache_normal - async with aiohttp.ClientSession() as session: + if option == "beginner": + url = URL + '+label:"good first issue"' + if self.cache_beginner is not None: + page = random.randint(1, min(1000, self.cache_beginner["total_count"]) // 100) + url += f"&page={page}" + else: + url = URL + if self.cache_normal is not None: + page = random.randint(1, min(1000, self.cache_normal["total_count"]) // 100) + url += f"&page={page}" + + log.debug(f"making api request to url: {url}") + async with self.bot.http_session.get(url, headers=REQUEST_HEADERS) as response: + if response.status != 200: + log.error(f"expected 200 status (got {response.status}) from the GitHub api.") + await ctx.send(f"ERROR: expected 200 status (got {response.status}) from the GitHub api.") + await ctx.send(await response.text()) + return None + data = await response.json() + + if len(data["items"]) == 0: + log.error(f"no issues returned from GitHub api. with url: {response.url}") + await ctx.send(f"ERROR: no issues returned from GitHub api. with url: {response.url}") + return None + if option == "beginner": - url = URL + '+label:"good first issue"' - if self.cache_beginner is not None: - page = random.randint(1, min(1000, self.cache_beginner["total_count"]) // 100) - url += f"&page={page}" + self.cache_beginner = data + self.cache_timer_beginner = ctx.message.created_at else: - url = URL - if self.cache_normal is not None: - page = random.randint(1, min(1000, self.cache_normal["total_count"]) // 100) - url += f"&page={page}" - - log.debug(f"making api request to url: {url}") - async with session.get(url, headers=REQUEST_HEADERS) as response: - if response.status != 200: - log.error(f"expected 200 status (got {response.status}) from the GitHub api.") - await ctx.send(f"ERROR: expected 200 status (got {response.status}) from the GitHub api.") - await ctx.send(await response.text()) - return None - data = await response.json() - - if len(data["items"]) == 0: - log.error(f"no issues returned from GitHub api. with url: {response.url}") - await ctx.send(f"ERROR: no issues returned from GitHub api. with url: {response.url}") - return None - - if option == "beginner": - self.cache_beginner = data - self.cache_timer_beginner = ctx.message.created_at - else: - self.cache_normal = data - self.cache_timer_normal = ctx.message.created_at - - return data + self.cache_normal = data + self.cache_timer_normal = ctx.message.created_at + + return data @staticmethod def format_embed(issue: Dict) -> discord.Embed: @@ -111,6 +110,6 @@ class HacktoberIssues(commands.Cog): return embed -def setup(bot: commands.Bot) -> None: - """Hacktober issue finder Cog Load.""" +def setup(bot: Bot) -> None: + """Load the HacktoberIssue finder.""" bot.add_cog(HacktoberIssues(bot)) diff --git a/bot/exts/halloween/hacktoberstats.py b/bot/exts/halloween/hacktoberstats.py index d9fc0e8a..9695ba2a 100644 --- a/bot/exts/halloween/hacktoberstats.py +++ b/bot/exts/halloween/hacktoberstats.py @@ -5,11 +5,11 @@ from collections import Counter from datetime import datetime, timedelta from typing import List, Optional, Tuple, Union -import aiohttp import discord from async_rediscache import RedisCache from discord.ext import commands +from bot.bot import Bot from bot.constants import Channels, Month, NEGATIVE_REPLIES, Tokens, WHITELISTED_CHANNELS from bot.utils.decorators import in_month, whitelist_override @@ -39,7 +39,7 @@ class HacktoberStats(commands.Cog): # Stores mapping of user IDs and GitHub usernames linked_accounts = RedisCache() - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Bot): self.bot = bot @in_month(Month.SEPTEMBER, Month.OCTOBER, Month.NOVEMBER) @@ -83,15 +83,15 @@ class HacktoberStats(commands.Cog): if github_username: if await self.linked_accounts.contains(author_id): old_username = await self.linked_accounts.get(author_id) - logging.info(f"{author_id} has changed their github link from '{old_username}' to '{github_username}'") + log.info(f"{author_id} has changed their github link from '{old_username}' to '{github_username}'") await ctx.send(f"{author_mention}, your GitHub username has been updated to: '{github_username}'") else: - logging.info(f"{author_id} has added a github link to '{github_username}'") + log.info(f"{author_id} has added a github link to '{github_username}'") await ctx.send(f"{author_mention}, your GitHub username has been added") await self.linked_accounts.set(author_id, github_username) else: - logging.info(f"{author_id} tried to link a GitHub account but didn't provide a username") + log.info(f"{author_id} tried to link a GitHub account but didn't provide a username") await ctx.send(f"{author_mention}, a GitHub username is required to link your account") @in_month(Month.SEPTEMBER, Month.OCTOBER, Month.NOVEMBER) @@ -157,7 +157,7 @@ class HacktoberStats(commands.Cog): stats_embed = discord.Embed( title=f"{github_username}'s Hacktoberfest", - color=discord.Color(0x9c4af7), + color=0x9c4af7, description=( f"{github_username} has made {n} valid " f"{self._contributionator(n)} in " @@ -188,8 +188,7 @@ class HacktoberStats(commands.Cog): logging.info(f"Hacktoberfest PR built for GitHub user '{github_username}'") return stats_embed - @staticmethod - async def get_october_prs(github_username: str) -> Optional[List[dict]]: + async def get_october_prs(self, github_username: str) -> Optional[List[dict]]: """ Query GitHub's API for PRs created during the month of October by github_username. @@ -212,7 +211,7 @@ class HacktoberStats(commands.Cog): Otherwise, return empty list. None will be returned when the GitHub user was not found. """ - logging.info(f"Fetching Hacktoberfest Stats for GitHub user: '{github_username}'") + log.info(f"Fetching Hacktoberfest Stats for GitHub user: '{github_username}'") base_url = "https://api.github.com/search/issues?q=" action_type = "pr" is_query = "public" @@ -228,24 +227,24 @@ class HacktoberStats(commands.Cog): f"+created:{date_range}" f"&per_page={per_page}" ) - logging.debug(f"GitHub query URL generated: {query_url}") + log.logProcesses.debug(f"GitHub query URL generated: {query_url}") - jsonresp = await HacktoberStats._fetch_url(query_url, REQUEST_HEADERS) - if "message" in jsonresp.keys(): + jsonresp = await self._fetch_url(query_url, REQUEST_HEADERS) + if "message" in jsonresp: # One of the parameters is invalid, short circuit for now api_message = jsonresp["errors"][0]["message"] # Ignore logging non-existent users or users we do not have permission to see if api_message == GITHUB_NONEXISTENT_USER_MESSAGE: - logging.debug(f"No GitHub user found named '{github_username}'") + log.debug(f"No GitHub user found named '{github_username}'") return else: - logging.error(f"GitHub API request for '{github_username}' failed with message: {api_message}") + log.error(f"GitHub API request for '{github_username}' failed with message: {api_message}") return [] # No October PRs were found due to error if jsonresp["total_count"] == 0: # Short circuit if there aren't any PRs - logging.info(f"No October PRs found for GitHub user: '{github_username}'") + log.info(f"No October PRs found for GitHub user: '{github_username}'") return [] logging.info(f"Found {len(jsonresp['items'])} Hacktoberfest PRs for GitHub user: '{github_username}'") @@ -253,20 +252,20 @@ class HacktoberStats(commands.Cog): oct3 = datetime(int(CURRENT_YEAR), 10, 3, 23, 59, 59, tzinfo=None) hackto_topics = {} # cache whether each repo has the appropriate topic (bool values) for item in jsonresp["items"]: - shortname = HacktoberStats._get_shortname(item["repository_url"]) + shortname = self._get_shortname(item["repository_url"]) itemdict = { "repo_url": f"https://www.github.com/{shortname}", "repo_shortname": shortname, "created_at": datetime.strptime( - item["created_at"], r"%Y-%m-%dT%H:%M:%SZ" + item["created_at"], "%Y-%m-%dT%H:%M:%SZ" ), "number": item["number"] } # If the PR has 'invalid' or 'spam' labels, the PR must be # either merged or approved for it to be included - if HacktoberStats._has_label(item, ["invalid", "spam"]): - if not await HacktoberStats._is_accepted(itemdict): + if self._has_label(item, ["invalid", "spam"]): + if not await self._is_accepted(itemdict): continue # PRs before oct 3 no need to check for topics @@ -277,21 +276,20 @@ class HacktoberStats(commands.Cog): continue # Checking PR's labels for "hacktoberfest-accepted" - if HacktoberStats._has_label(item, "hacktoberfest-accepted"): + if self._has_label(item, "hacktoberfest-accepted"): outlist.append(itemdict) continue # No need to query GitHub if repo topics are fetched before already - if shortname in hackto_topics.keys(): - if hackto_topics[shortname]: - outlist.append(itemdict) - continue + if hackto_topics.get(shortname): + outlist.append(itemdict) + continue # Fetch topics for the PR's repo topics_query_url = f"https://api.github.com/repos/{shortname}/topics" - logging.debug(f"Fetching repo topics for {shortname} with url: {topics_query_url}") - jsonresp2 = await HacktoberStats._fetch_url(topics_query_url, GITHUB_TOPICS_ACCEPT_HEADER) + log.debug(f"Fetching repo topics for {shortname} with url: {topics_query_url}") + jsonresp2 = await self._fetch_url(topics_query_url, GITHUB_TOPICS_ACCEPT_HEADER) if jsonresp2.get("names") is None: - logging.error(f"Error fetching topics for {shortname}: {jsonresp2['message']}") + log.error(f"Error fetching topics for {shortname}: {jsonresp2['message']}") continue # Assume the repo doesn't have the `hacktoberfest` topic if API request errored # PRs after oct 3 that doesn't have 'hacktoberfest-accepted' label @@ -301,12 +299,10 @@ class HacktoberStats(commands.Cog): outlist.append(itemdict) return outlist - @staticmethod - async def _fetch_url(url: str, headers: dict) -> dict: + async def _fetch_url(self, url: str, headers: dict) -> dict: """Retrieve API response from URL.""" - async with aiohttp.ClientSession() as session: - async with session.get(url, headers=headers) as resp: - jsonresp = await resp.json() + async with self.bot.http_session.get(url, headers=headers) as resp: + jsonresp = await resp.json() return jsonresp @staticmethod @@ -319,40 +315,36 @@ class HacktoberStats(commands.Cog): """ if not pr.get("labels"): # if PR has no labels return False - if (isinstance(labels, str)) and (any(label["name"].casefold() == labels for label in pr["labels"])): + if isinstance(labels, str) and any(label["name"].casefold() == labels for label in pr["labels"]): return True for item in labels: if any(label["name"].casefold() == item for label in pr["labels"]): return True return False - @staticmethod - async def _is_accepted(pr: dict) -> bool: + async def _is_accepted(self, pr: dict) -> bool: """Check if a PR is merged, approved, or labelled hacktoberfest-accepted.""" # checking for merge status - query_url = f"https://api.github.com/repos/{pr['repo_shortname']}/pulls/" - query_url += str(pr["number"]) - jsonresp = await HacktoberStats._fetch_url(query_url, REQUEST_HEADERS) - - if "message" in jsonresp.keys(): - logging.error( - f"Error fetching PR stats for #{pr['number']} in repo {pr['repo_shortname']}:\n" - f"{jsonresp['message']}" - ) + query_url = f"https://api.github.com/repos/{pr['repo_shortname']}/pulls/{pr['number']}" + jsonresp = await self._fetch_url(query_url, REQUEST_HEADERS) + + if message := jsonresp.get("message"): + log.error(f"Error fetching PR stats for #{pr['number']} in repo {pr['repo_shortname']}:\n{message}") return False - if ("merged" in jsonresp.keys()) and jsonresp["merged"]: + + if jsonresp.get("merged"): return True # checking for the label, using `jsonresp` which has the label information - if HacktoberStats._has_label(jsonresp, "hacktoberfest-accepted"): + if self._has_label(jsonresp, "hacktoberfest-accepted"): return True # checking approval query_url += "/reviews" - jsonresp2 = await HacktoberStats._fetch_url(query_url, REQUEST_HEADERS) + jsonresp2 = await self._fetch_url(query_url, REQUEST_HEADERS) if isinstance(jsonresp2, dict): # if API request is unsuccessful it will be a dict with the error in 'message' - logging.error( + log.error( f"Error fetching PR reviews for #{pr['number']} in repo {pr['repo_shortname']}:\n" f"{jsonresp2['message']}" ) @@ -363,9 +355,8 @@ class HacktoberStats(commands.Cog): # loop through reviews and check for approval for item in jsonresp2: - if "status" in item.keys(): - if item['status'] == "APPROVED": - return True + if item.get('status') == "APPROVED": + return True return False @staticmethod @@ -381,8 +372,7 @@ class HacktoberStats(commands.Cog): exp = r"https?:\/\/api.github.com\/repos\/([/\-\_\.\w]+)" return re.findall(exp, in_url)[0] - @staticmethod - async def _categorize_prs(prs: List[dict]) -> tuple: + async def _categorize_prs(self, prs: List[dict]) -> tuple: """ Categorize PRs into 'in_review' and 'accepted' and returns as a tuple. @@ -399,7 +389,7 @@ class HacktoberStats(commands.Cog): for pr in prs: if (pr['created_at'] + timedelta(REVIEW_DAYS)) > now: in_review.append(pr) - elif (pr['created_at'] <= oct3) or await HacktoberStats._is_accepted(pr): + elif (pr['created_at'] <= oct3) or await self._is_accepted(pr): accepted.append(pr) return in_review, accepted @@ -438,14 +428,14 @@ class HacktoberStats(commands.Cog): return "contributions" @staticmethod - def _author_mention_from_context(ctx: commands.Context) -> Tuple: + def _author_mention_from_context(ctx: commands.Context) -> Tuple[str, str]: """Return stringified Message author ID and mentionable string from commands.Context.""" - author_id = str(ctx.message.author.id) - author_mention = ctx.message.author.mention + author_id = str(ctx.author.id) + author_mention = ctx.author.mention return author_id, author_mention -def setup(bot: commands.Bot) -> None: - """Hacktoberstats Cog load.""" +def setup(bot: Bot) -> None: + """Load the Hacktober Stats Cog.""" bot.add_cog(HacktoberStats(bot)) diff --git a/bot/exts/halloween/halloween_facts.py b/bot/exts/halloween/halloween_facts.py index 7eb6d56f..139e0810 100644 --- a/bot/exts/halloween/halloween_facts.py +++ b/bot/exts/halloween/halloween_facts.py @@ -8,6 +8,8 @@ from typing import Tuple import discord from discord.ext import commands +from bot.bot import Bot + log = logging.getLogger(__name__) SPOOKY_EMOJIS = [ @@ -20,16 +22,15 @@ SPOOKY_EMOJIS = [ "\N{SKULL AND CROSSBONES}", "\N{SPIDER WEB}", ] -PUMPKIN_ORANGE = discord.Color(0xFF7518) +PUMPKIN_ORANGE = 0xFF7518 INTERVAL = timedelta(hours=6).total_seconds() class HalloweenFacts(commands.Cog): """A Cog for displaying interesting facts about Halloween.""" - def __init__(self, bot: commands.Bot): - self.bot = bot - with open(Path("bot/resources/halloween/halloween_facts.json"), "r", encoding="utf8") as file: + def __init__(self): + with Path("bot/resources/halloween/halloween_facts.json").open("r", encoding="utf8") as file: self.halloween_facts = json.load(file) self.facts = list(enumerate(self.halloween_facts)) random.shuffle(self.facts) @@ -53,6 +54,6 @@ class HalloweenFacts(commands.Cog): return discord.Embed(title=title, description=fact, color=PUMPKIN_ORANGE) -def setup(bot: commands.Bot) -> None: - """Halloween facts Cog load.""" - bot.add_cog(HalloweenFacts(bot)) +def setup(bot: Bot) -> None: + """Load the Halloween Facts Cog.""" + bot.add_cog(HalloweenFacts()) diff --git a/bot/exts/halloween/halloweenify.py b/bot/exts/halloween/halloweenify.py index 596c6682..5a8f4ecc 100644 --- a/bot/exts/halloween/halloweenify.py +++ b/bot/exts/halloween/halloweenify.py @@ -6,7 +6,9 @@ from random import choice import discord from discord.errors import Forbidden from discord.ext import commands -from discord.ext.commands.cooldowns import BucketType +from discord.ext.commands import BucketType + +from bot.bot import Bot log = logging.getLogger(__name__) @@ -14,9 +16,6 @@ log = logging.getLogger(__name__) class Halloweenify(commands.Cog): """A cog to change a invokers nickname to a spooky one!""" - def __init__(self, bot: commands.Bot): - self.bot = bot - @commands.cooldown(1, 300, BucketType.user) @commands.command() async def halloweenify(self, ctx: commands.Context) -> None: @@ -61,6 +60,6 @@ class Halloweenify(commands.Cog): await ctx.send(embed=embed) -def setup(bot: commands.Bot) -> None: - """Halloweenify Cog load.""" - bot.add_cog(Halloweenify(bot)) +def setup(bot: Bot) -> None: + """Load the Halloweenify Cog.""" + bot.add_cog(Halloweenify()) diff --git a/bot/exts/halloween/monsterbio.py b/bot/exts/halloween/monsterbio.py index 016a66d1..f484305d 100644 --- a/bot/exts/halloween/monsterbio.py +++ b/bot/exts/halloween/monsterbio.py @@ -6,6 +6,7 @@ from pathlib import Path import discord from discord.ext import commands +from bot.bot import Bot from bot.constants import Colours log = logging.getLogger(__name__) @@ -17,9 +18,6 @@ with open(Path("bot/resources/halloween/monster.json"), "r", encoding="utf8") as class MonsterBio(commands.Cog): """A cog that generates a spooky monster biography.""" - def __init__(self, bot: commands.Bot): - self.bot = bot - def generate_name(self, seeded_random: random.Random) -> str: """Generates a name (for either monster species or monster name).""" n_candidate_strings = seeded_random.randint(2, len(TEXT_OPTIONS["monster_type"])) @@ -50,6 +48,6 @@ class MonsterBio(commands.Cog): await ctx.send(embed=embed) -def setup(bot: commands.Bot) -> None: - """Monster bio Cog load.""" - bot.add_cog(MonsterBio(bot)) +def setup(bot: Bot) -> None: + """Load the Monster Bio Cog.""" + bot.add_cog(MonsterBio()) diff --git a/bot/exts/halloween/monstersurvey.py b/bot/exts/halloween/monstersurvey.py index 80196825..0610503d 100644 --- a/bot/exts/halloween/monstersurvey.py +++ b/bot/exts/halloween/monstersurvey.py @@ -23,9 +23,8 @@ class MonsterSurvey(Cog): Users may change their vote, but only their current vote will be counted. """ - def __init__(self, bot: Bot): + def __init__(self): """Initializes values for the bot to use within the voting commands.""" - self.bot = bot self.registry_location = os.path.join(os.getcwd(), 'bot', 'resources', 'halloween', 'monstersurvey.json') with open(self.registry_location, 'r', encoding="utf8") as jason: self.voter_registry = json.load(jason) @@ -201,4 +200,5 @@ class MonsterSurvey(Cog): def setup(bot: Bot) -> None: - """Monster survey Cog load.""" + """Load the Monster Survey Cog.""" + bot.add_cog(MonsterSurvey()) diff --git a/bot/exts/halloween/scarymovie.py b/bot/exts/halloween/scarymovie.py index 0807eca6..48c9f53d 100644 --- a/bot/exts/halloween/scarymovie.py +++ b/bot/exts/halloween/scarymovie.py @@ -2,24 +2,25 @@ import logging import random from os import environ -import aiohttp from discord import Embed from discord.ext import commands +from bot.bot import Bot + log = logging.getLogger(__name__) -TMDB_API_KEY = environ.get('TMDB_API_KEY') -TMDB_TOKEN = environ.get('TMDB_TOKEN') +TMDB_API_KEY = environ.get("TMDB_API_KEY") +TMDB_TOKEN = environ.get("TMDB_TOKEN") class ScaryMovie(commands.Cog): """Selects a random scary movie and embeds info into Discord chat.""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Bot): self.bot = bot - @commands.command(name='scarymovie', alias=['smovie']) + @commands.command(name="scarymovie", alias=["smovie"]) async def random_movie(self, ctx: commands.Context) -> None: """Randomly select a scary movie and display information about it.""" async with ctx.typing(): @@ -28,36 +29,34 @@ class ScaryMovie(commands.Cog): await ctx.send(embed=movie_details) - @staticmethod - async def select_movie() -> dict: + async def select_movie(self) -> dict: """Selects a random movie and returns a JSON of movie details from TMDb.""" - url = 'https://api.themoviedb.org/4/discover/movie' + url = "https://api.themoviedb.org/4/discover/movie" params = { - 'with_genres': '27', - 'vote_count.gte': '5' + "with_genres": "27", + "vote_count.gte": "5" } headers = { - 'Authorization': 'Bearer ' + TMDB_TOKEN, - 'Content-Type': 'application/json;charset=utf-8' + "Authorization": "Bearer " + TMDB_TOKEN, + "Content-Type": "application/json;charset=utf-8" } # Get total page count of horror movies - async with aiohttp.ClientSession() as session: - response = await session.get(url=url, params=params, headers=headers) - total_pages = await response.json() - total_pages = total_pages.get('total_pages') - - # Get movie details from one random result on a random page - params['page'] = random.randint(1, total_pages) - response = await session.get(url=url, params=params, headers=headers) - response = await response.json() - selection_id = random.choice(response.get('results')).get('id') - - # Get full details and credits - selection = await session.get( - url='https://api.themoviedb.org/3/movie/' + str(selection_id), - params={'api_key': TMDB_API_KEY, 'append_to_response': 'credits'} - ) + async with self.bot.http_session.get(url=url, params=params, headers=headers) as response: + data = await response.json() + total_pages = data.get("total_pages") + + # Get movie details from one random result on a random page + params['page'] = random.randint(1, total_pages) + async with self.bot.http_session.get(url=url, params=params, headers=headers) as response: + data = await response.json() + selection_id = random.choice(data.get("results")).get("id") + + # Get full details and credits + async with self.bot.http_session.get( + url=f"https://api.themoviedb.org/3/movie/{selection_id}", + params={"api_key": TMDB_API_KEY, "append_to_response": "credits"} + ) as selection: return await selection.json() @@ -67,8 +66,8 @@ class ScaryMovie(commands.Cog): # Build the relevant URLs. movie_id = movie.get("id") poster_path = movie.get("poster_path") - tmdb_url = f'https://www.themoviedb.org/movie/{movie_id}' if movie_id else None - poster = f'https://image.tmdb.org/t/p/original{poster_path}' if poster_path else None + tmdb_url = f"https://www.themoviedb.org/movie/{movie_id}" if movie_id else None + poster = f"https://image.tmdb.org/t/p/original{poster_path}" if poster_path else None # Get cast names cast = [] @@ -82,10 +81,7 @@ class ScaryMovie(commands.Cog): # Determine the spookiness rating rating = '' - rating_count = movie.get('vote_average', 0) - - if rating_count: - rating_count /= 2 + rating_count = movie.get('vote_average', 0) / 2 for _ in range(int(rating_count)): rating += ':skull:' @@ -100,7 +96,7 @@ class ScaryMovie(commands.Cog): # Not all these attributes will always be present movie_attributes = { "Directed by": director, - "Starring": ', '.join(cast), + "Starring": ", ".join(cast), "Running time": runtime, "Release year": year, "Spookiness rating": rating, @@ -108,9 +104,9 @@ class ScaryMovie(commands.Cog): embed = Embed( colour=0x01d277, - title='**' + movie.get('title') + '**', + title=f"**{movie.get('title')}**", url=tmdb_url, - description=movie.get('overview') + description=movie.get("overview") ) if poster: @@ -127,6 +123,6 @@ class ScaryMovie(commands.Cog): return embed -def setup(bot: commands.Bot) -> None: - """Scary movie Cog load.""" +def setup(bot: Bot) -> None: + """Load the Scary Movie Cog.""" bot.add_cog(ScaryMovie(bot)) diff --git a/bot/exts/halloween/spookygif.py b/bot/exts/halloween/spookygif.py index f402437f..bfdf2128 100644 --- a/bot/exts/halloween/spookygif.py +++ b/bot/exts/halloween/spookygif.py @@ -1,9 +1,9 @@ import logging -import aiohttp import discord from discord.ext import commands +from bot.bot import Bot from bot.constants import Tokens log = logging.getLogger(__name__) @@ -12,27 +12,26 @@ log = logging.getLogger(__name__) class SpookyGif(commands.Cog): """A cog to fetch a random spooky gif from the web!""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Bot): self.bot = bot @commands.command(name="spookygif", aliases=("sgif", "scarygif")) async def spookygif(self, ctx: commands.Context) -> None: """Fetches a random gif from the GIPHY API and responds with it.""" async with ctx.typing(): - async with aiohttp.ClientSession() as session: - params = {'api_key': Tokens.giphy, 'tag': 'halloween', 'rating': 'g'} - # Make a GET request to the Giphy API to get a random halloween gif. - async with session.get('http://api.giphy.com/v1/gifs/random', params=params) as resp: - data = await resp.json() - url = data['data']['image_url'] + params = {'api_key': Tokens.giphy, 'tag': 'halloween', 'rating': 'g'} + # Make a GET request to the Giphy API to get a random halloween gif. + async with self.bot.http_session.get('http://api.giphy.com/v1/gifs/random', params=params) as resp: + data = await resp.json() + url = data['data']['image_url'] - embed = discord.Embed(colour=0x9b59b6) - embed.title = "A spooooky gif!" - embed.set_image(url=url) + embed = discord.Embed(colour=0x9b59b6) + embed.title = "A spooooky gif!" + embed.set_image(url=url) await ctx.send(embed=embed) -def setup(bot: commands.Bot) -> None: +def setup(bot: Bot) -> None: """Spooky GIF Cog load.""" bot.add_cog(SpookyGif(bot)) diff --git a/bot/exts/halloween/spookynamerate.py b/bot/exts/halloween/spookynamerate.py index e2950343..9191f5f6 100644 --- a/bot/exts/halloween/spookynamerate.py +++ b/bot/exts/halloween/spookynamerate.py @@ -12,8 +12,9 @@ from async_rediscache import RedisCache from discord import Embed, Reaction, TextChannel, User from discord.colour import Colour from discord.ext import tasks -from discord.ext.commands import Bot, Cog, Context, group +from discord.ext.commands import Cog, Context, group +from bot.bot import Bot from bot.constants import Channels, Client, Colours, Month from bot.utils.decorators import InMonthCheckFailure @@ -34,7 +35,7 @@ ADDED_MESSAGES = [ ] PING = "<@{id}>" -EMOJI_MESSAGE = "\n".join([f"- {emoji} {val}" for emoji, val in EMOJIS_VAL.items()]) +EMOJI_MESSAGE = "\n".join(f"- {emoji} {val}" for emoji, val in EMOJIS_VAL.items()) HELP_MESSAGE_DICT = { "title": "Spooky Name Rate", "description": f"Help for the `{Client.prefix}spookynamerate` command", @@ -137,14 +138,12 @@ class SpookyNameRate(Cog): async def add_name(self, ctx: Context, *, name: str) -> None: """Use this command to add/register your spookified name.""" if self.poll: - logger.info(f"{ctx.message.author} tried to add a name, but the poll had already started.") + logger.info(f"{ctx.author} tried to add a name, but the poll had already started.") await ctx.send("Sorry, the poll has started! You can try and participate in the next round though!") return - message = ctx.message - for data in (json.loads(user_data) for _, user_data in await self.messages.items()): - if data["author"] == message.author.id: + if data["author"] == ctx.author.id: await ctx.send( "But you have already added an entry! Type " f"`{self.bot.command_prefix}spookynamerate " @@ -156,14 +155,14 @@ class SpookyNameRate(Cog): await ctx.send("TOO LATE. Someone has already added this name.") return - msg = await (await self.get_channel()).send(f"{message.author.mention} added the name {name!r}!") + msg = await (await self.get_channel()).send(f"{ctx.author.mention} added the name {name!r}!") await self.messages.set( msg.id, json.dumps( { "name": name, - "author": message.author.id, + "author": ctx.author.id, "score": 0, } ), @@ -172,7 +171,7 @@ class SpookyNameRate(Cog): for emoji in EMOJIS_VAL: await msg.add_reaction(emoji) - logger.info(f"{message.author} added the name {name!r}") + logger.info(f"{ctx.author} added the name {name!r}") @spooky_name_rate.command(name="delete") async def delete_name(self, ctx: Context) -> None: @@ -185,7 +184,7 @@ class SpookyNameRate(Cog): if ctx.author.id == data["author"]: await self.messages.delete(message_id) - await ctx.send(f'Name deleted successfully ({data["name"]!r})!') + await ctx.send(f"Name deleted successfully ({data['name']!r})!") return await ctx.send( @@ -397,5 +396,5 @@ class SpookyNameRate(Cog): def setup(bot: Bot) -> None: - """Loads the SpookyNameRate Cog.""" + """Load the SpookyNameRate Cog.""" bot.add_cog(SpookyNameRate(bot)) diff --git a/bot/exts/halloween/spookyrating.py b/bot/exts/halloween/spookyrating.py index 6f069f8c..dc398e2e 100644 --- a/bot/exts/halloween/spookyrating.py +++ b/bot/exts/halloween/spookyrating.py @@ -7,20 +7,20 @@ from pathlib import Path import discord from discord.ext import commands +from bot.bot import Bot from bot.constants import Colours log = logging.getLogger(__name__) with Path("bot/resources/halloween/spooky_rating.json").open(encoding="utf8") as file: - SPOOKY_DATA = json.load(file) - SPOOKY_DATA = sorted((int(key), value) for key, value in SPOOKY_DATA.items()) + data = json.load(file) + SPOOKY_DATA = sorted((int(key), value) for key, value in data.items()) class SpookyRating(commands.Cog): """A cog for calculating one's spooky rating.""" - def __init__(self, bot: commands.Bot): - self.bot = bot + def __init__(self): self.local_random = random.Random() @commands.command() @@ -61,6 +61,6 @@ class SpookyRating(commands.Cog): await ctx.send(embed=embed) -def setup(bot: commands.Bot) -> None: - """Spooky Rating Cog load.""" - bot.add_cog(SpookyRating(bot)) +def setup(bot: Bot) -> None: + """Load the Spooky Rating Cog.""" + bot.add_cog(SpookyRating()) diff --git a/bot/exts/halloween/spookyreact.py b/bot/exts/halloween/spookyreact.py index b335df75..dabc3c1f 100644 --- a/bot/exts/halloween/spookyreact.py +++ b/bot/exts/halloween/spookyreact.py @@ -2,8 +2,9 @@ import logging import re import discord -from discord.ext.commands import Bot, Cog +from discord.ext.commands import Cog +from bot.bot import Bot from bot.constants import Month from bot.utils.decorators import in_month @@ -28,20 +29,20 @@ class SpookyReact(Cog): @in_month(Month.OCTOBER) @Cog.listener() - async def on_message(self, ctx: discord.Message) -> None: + async def on_message(self, message: discord.Message) -> None: """Triggered when the bot sees a message in October.""" - for trigger in SPOOKY_TRIGGERS.keys(): - trigger_test = re.search(SPOOKY_TRIGGERS[trigger][0], ctx.content.lower()) + for name, trigger in SPOOKY_TRIGGERS.items(): + trigger_test = re.search(trigger[0], message.content.lower()) if trigger_test: # Check message for bot replies and/or command invocations # Short circuit if they're found, logging is handled in _short_circuit_check - if await self._short_circuit_check(ctx): + if await self._short_circuit_check(message): return else: - await ctx.add_reaction(SPOOKY_TRIGGERS[trigger][1]) - logging.info(f"Added '{trigger}' reaction to message ID: {ctx.id}") + await message.add_reaction(trigger[1]) + log.info(f"Added {name!r} reaction to message ID: {message.id}") - async def _short_circuit_check(self, ctx: discord.Message) -> bool: + async def _short_circuit_check(self, message: discord.Message) -> bool: """ Short-circuit helper check. @@ -50,20 +51,20 @@ class SpookyReact(Cog): * prefix is not None """ # Check for self reaction - if ctx.author == self.bot.user: - logging.debug(f"Ignoring reactions on self message. Message ID: {ctx.id}") + if message.author == self.bot.user: + log.debug(f"Ignoring reactions on self message. Message ID: {message.id}") return True # Check for command invocation # Because on_message doesn't give a full Context object, generate one first - tmp_ctx = await self.bot.get_context(ctx) - if tmp_ctx.prefix: - logging.debug(f"Ignoring reactions on command invocation. Message ID: {ctx.id}") + ctx = await self.bot.get_context(message) + if ctx.prefix: + log.debug(f"Ignoring reactions on command invocation. Message ID: {message.id}") return True return False def setup(bot: Bot) -> None: - """Spooky reaction Cog load.""" + """Load the Spooky Reaction Cog.""" bot.add_cog(SpookyReact(bot)) diff --git a/bot/exts/halloween/timeleft.py b/bot/exts/halloween/timeleft.py index 47adb09b..f4ab9284 100644 --- a/bot/exts/halloween/timeleft.py +++ b/bot/exts/halloween/timeleft.py @@ -4,13 +4,15 @@ from typing import Tuple from discord.ext import commands +from bot.bot import Bot + log = logging.getLogger(__name__) class TimeLeft(commands.Cog): """A Cog that tells you how long left until Hacktober is over!""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Bot): self.bot = bot def in_hacktober(self) -> bool: @@ -64,6 +66,6 @@ class TimeLeft(commands.Cog): ) -def setup(bot: commands.Bot) -> None: - """Cog load.""" +def setup(bot: Bot) -> None: + """Load the Time Left Cog.""" bot.add_cog(TimeLeft(bot)) -- cgit v1.2.3 From c1658ed8a37532bcdcbc6567e35a4ecb7578b237 Mon Sep 17 00:00:00 2001 From: Rohan Date: Tue, 20 Apr 2021 13:42:20 +0530 Subject: Fix Bug:Paginated embed image gets carried to next page --- bot/utils/pagination.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'bot') diff --git a/bot/utils/pagination.py b/bot/utils/pagination.py index a4d0cc56..917275c0 100644 --- a/bot/utils/pagination.py +++ b/bot/utils/pagination.py @@ -4,6 +4,7 @@ from typing import Iterable, List, Optional, Tuple from discord import Embed, Member, Reaction from discord.abc import User +from discord.embeds import EmptyEmbed from discord.ext.commands import Context, Paginator from bot.constants import Emojis @@ -417,9 +418,8 @@ class ImagePaginator(Paginator): await message.edit(embed=embed) embed.description = paginator.pages[current_page] - image = paginator.images[current_page] - if image: - embed.set_image(url=image) + image = paginator.images[current_page] or EmptyEmbed + embed.set_image(url=image) embed.set_footer(text=f"Page {current_page + 1}/{len(paginator.pages)}") log.debug(f"Got {reaction_type} page reaction - changing to page {current_page + 1}/{len(paginator.pages)}") -- cgit v1.2.3 From 8662541fc08037558f68e15dcc3fd72ef491dced Mon Sep 17 00:00:00 2001 From: vcokltfre Date: Tue, 20 Apr 2021 17:01:02 +0100 Subject: chore: ctx.channel.send -> ctx.send --- bot/exts/evergreen/snakes/_snakes_cog.py | 22 +++++++++++----------- bot/exts/valentines/myvalenstate.py | 2 +- bot/exts/valentines/whoisvalentine.py | 4 ++-- 3 files changed, 14 insertions(+), 14 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/snakes/_snakes_cog.py b/bot/exts/evergreen/snakes/_snakes_cog.py index 3732b559..70093912 100644 --- a/bot/exts/evergreen/snakes/_snakes_cog.py +++ b/bot/exts/evergreen/snakes/_snakes_cog.py @@ -423,7 +423,7 @@ class Snakes(Cog): try: reaction, user = await ctx.bot.wait_for("reaction_add", timeout=45.0, check=predicate) except asyncio.TimeoutError: - await ctx.channel.send(f"You took too long. The correct answer was **{options[answer]}**.") + await ctx.send(f"You took too long. The correct answer was **{options[answer]}**.") await message.clear_reactions() return @@ -720,7 +720,7 @@ class Snakes(Cog): snake_image = utils.snakes[snake_name] # Hatch the snake - message = await ctx.channel.send(embed=Embed(description="Hatching your snake :snake:...")) + message = await ctx.send(embed=Embed(description="Hatching your snake :snake:...")) await asyncio.sleep(1) for stage in utils.stages: @@ -737,7 +737,7 @@ class Snakes(Cog): text=" Owner: {0}#{1}".format(ctx.message.author.name, ctx.message.author.discriminator) ) - await ctx.channel.send(embed=my_snake_embed) + await ctx.send(embed=my_snake_embed) @snakes_group.command(name='movie') async def movie_command(self, ctx: Context) -> None: @@ -800,9 +800,9 @@ class Snakes(Cog): embed.set_thumbnail(url="https://i.imgur.com/LtFtC8H.png") try: - await ctx.channel.send(embed=embed) + await ctx.send(embed=embed) except HTTPException as err: - await ctx.channel.send("An error occurred while fetching a snake-related movie!") + await ctx.send("An error occurred while fetching a snake-related movie!") raise err from None @snakes_group.command(name='quiz') @@ -828,7 +828,7 @@ class Snakes(Cog): ) ) - quiz = await ctx.channel.send("", embed=embed) + quiz = await ctx.send("", embed=embed) await self._validate_answer(ctx, quiz, answer, options) @snakes_group.command(name='name', aliases=('name_gen',)) @@ -964,7 +964,7 @@ class Snakes(Cog): ) ) - await ctx.channel.send(embed=embed) + await ctx.send(embed=embed) @snakes_group.command(name='card') async def card_command(self, ctx: Context, *, name: Snake = None) -> None: @@ -1018,7 +1018,7 @@ class Snakes(Cog): color=SNAKE_COLOR, description=question ) - await ctx.channel.send(embed=embed) + await ctx.send(embed=embed) @snakes_group.command(name='snakify') async def snakify_command(self, ctx: Context, *, message: str = None) -> None: @@ -1059,7 +1059,7 @@ class Snakes(Cog): ) embed.description = f"*{self._snakify(message)}*" - await ctx.channel.send(embed=embed) + await ctx.send(embed=embed) @snakes_group.command(name='video', aliases=('get_video',)) async def video_command(self, ctx: Context, *, search: str = None) -> None: @@ -1095,7 +1095,7 @@ class Snakes(Cog): if len(data) > 0: num = random.randint(0, len(data) - 1) youtube_base_url = 'https://www.youtube.com/watch?v=' - await ctx.channel.send( + await ctx.send( content=f"{youtube_base_url}{data[num]['id']['videoId']}" ) else: @@ -1120,7 +1120,7 @@ class Snakes(Cog): # Embed and send embed.description = zen_quote - await ctx.channel.send( + await ctx.send( embed=embed ) # endregion diff --git a/bot/exts/valentines/myvalenstate.py b/bot/exts/valentines/myvalenstate.py index 01801847..041af7af 100644 --- a/bot/exts/valentines/myvalenstate.py +++ b/bot/exts/valentines/myvalenstate.py @@ -78,7 +78,7 @@ class MyValenstate(commands.Cog): ) embed.add_field(name=embed_title, value=embed_text) embed.set_image(url=STATES[valenstate]["flag"]) - await ctx.channel.send(embed=embed) + await ctx.send(embed=embed) def setup(bot: commands.Bot) -> None: diff --git a/bot/exts/valentines/whoisvalentine.py b/bot/exts/valentines/whoisvalentine.py index 0ff9186c..ab6b1ca1 100644 --- a/bot/exts/valentines/whoisvalentine.py +++ b/bot/exts/valentines/whoisvalentine.py @@ -33,7 +33,7 @@ class ValentineFacts(commands.Cog): 'facial_reconstruction.jpg/1024px-Saint_Valentine_-_facial_reconstruction.jpg' ) - await ctx.channel.send(embed=embed) + await ctx.send(embed=embed) @commands.command() async def valentine_fact(self, ctx: commands.Context) -> None: @@ -44,7 +44,7 @@ class ValentineFacts(commands.Cog): color=Colours.pink ) - await ctx.channel.send(embed=embed) + await ctx.send(embed=embed) def setup(bot: commands.Bot) -> None: -- cgit v1.2.3 From 9524620ddbb7d3e707258e1ba3b84b23b5e3b54a Mon Sep 17 00:00:00 2001 From: Dillon Runke <44979306+Kronifer@users.noreply.github.com> Date: Tue, 20 Apr 2021 11:12:17 -0500 Subject: Add Catify command (#694) Co-authored-by: Joe Banks Co-authored-by: hypergamer80 <79152594+hypergamer80@users.noreply.github.com> --- bot/constants.py | 5 +++ bot/exts/evergreen/catify.py | 78 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 bot/exts/evergreen/catify.py (limited to 'bot') diff --git a/bot/constants.py b/bot/constants.py index a64882db..bcbdcba0 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -8,6 +8,7 @@ from typing import Dict, NamedTuple __all__ = ( "AdventOfCode", "Branding", + "Cats", "Channels", "Categories", "Client", @@ -93,6 +94,10 @@ class Branding: cycle_frequency = int(environ.get("CYCLE_FREQUENCY", 3)) # 0: never, 1: every day, 2: every other day, ... +class Cats: + cats = ["ᓚᘏᗢ", "ᘡᘏᗢ", "🐈", "ᓕᘏᗢ", "ᓇᘏᗢ", "ᓂᘏᗢ", "ᘣᘏᗢ", "ᕦᘏᗢ", "ᕂᘏᗢ"] + + class Channels(NamedTuple): advent_of_code = int(environ.get("AOC_CHANNEL_ID", 782715290437943306)) advent_of_code_commands = int(environ.get("AOC_COMMANDS_CHANNEL_ID", 607247579608121354)) diff --git a/bot/exts/evergreen/catify.py b/bot/exts/evergreen/catify.py new file mode 100644 index 00000000..a0121403 --- /dev/null +++ b/bot/exts/evergreen/catify.py @@ -0,0 +1,78 @@ +import random +from typing import Optional + +from discord import AllowedMentions, Embed +from discord.ext import commands + +from bot.constants import Cats, Colours, NEGATIVE_REPLIES + + +class Catify(commands.Cog): + """Cog for the catify command.""" + + def __init__(self, bot: commands.Bot): + self.bot = bot + + @commands.command(aliases=["ᓚᘏᗢify", "ᓚᘏᗢ"]) + async def catify(self, ctx: commands.Context, *, text: Optional[str]) -> None: + """ + Convert the provided text into a cat themed sentence by interspercing cats throughout text. + + If no text is given then the users nickname is edited. + """ + if not text: + display_name = ctx.author.display_name + + if len(display_name) > 26: + embed = Embed( + title=random.choice(NEGATIVE_REPLIES), + description="Your nickname is too long to be catified! Please change it to be under 26 characters.", + color=Colours.soft_red + ) + await ctx.send(embed=embed) + return + + else: + display_name += f" | {random.choice(Cats.cats)}" + await ctx.send(f"Your catified username is: `{display_name}`") + await ctx.author.edit(nick=display_name) + else: + if len(text) >= 1500: + embed = Embed( + title=random.choice(NEGATIVE_REPLIES), + description="Submitted text was too large! Please submit something under 1500 characters.", + color=Colours.soft_red + ) + await ctx.send(embed=embed) + return + + string_list = text.split() + for index, name in enumerate(string_list): + if "cat" in name: + if random.randint(0, 5) == 5: + string_list[index] = string_list[index].replace("cat", f"**{random.choice(Cats.cats)}**") + else: + string_list[index] = string_list[index].replace("cat", random.choice(Cats.cats)) + for element in Cats.cats: + if element in name: + string_list[index] = string_list[index].replace(element, "cat") + + string_len = len(string_list) // 3 or len(string_list) + + for _ in range(random.randint(1, string_len)): + # insert cat at random index + if random.randint(0, 5) == 5: + string_list.insert(random.randint(0, len(string_list)), f"**{random.choice(Cats.cats)}**") + else: + string_list.insert(random.randint(0, len(string_list)), random.choice(Cats.cats)) + + text = " ".join(string_list) + await ctx.send( + f">>> {text}", + allowed_mentions=AllowedMentions.none() + ) + + +def setup(bot: commands.Bot) -> None: + """Loads the catify cog.""" + bot.add_cog(Catify(bot)) -- cgit v1.2.3 From 97048ce3634875e875f4a66e786b49d39bfdb17f Mon Sep 17 00:00:00 2001 From: ToxicKidz <78174417+ToxicKidz@users.noreply.github.com> Date: Tue, 20 Apr 2021 12:22:41 -0400 Subject: Clean Up Christmas Season --- bot/exts/christmas/advent_of_code/_cog.py | 4 ++-- bot/exts/christmas/hanukkah_embed.py | 16 +++++++++------- bot/exts/pride/drag_queen_name.py | 9 +++++---- bot/exts/pride/pride_anthem.py | 14 ++++++++------ bot/exts/pride/pride_facts.py | 18 ++++++++---------- 5 files changed, 32 insertions(+), 29 deletions(-) (limited to 'bot') diff --git a/bot/exts/christmas/advent_of_code/_cog.py b/bot/exts/christmas/advent_of_code/_cog.py index 8376987d..da1cf28d 100644 --- a/bot/exts/christmas/advent_of_code/_cog.py +++ b/bot/exts/christmas/advent_of_code/_cog.py @@ -124,7 +124,7 @@ class AdventOfCode(commands.Cog): @whitelist_override(channels=AOC_WHITELIST) async def about_aoc(self, ctx: commands.Context) -> None: """Respond with an explanation of all things Advent of Code.""" - await ctx.send("", embed=self.cached_about_aoc) + await ctx.send(embed=self.cached_about_aoc) @adventofcode_group.command(name="join", aliases=("j",), brief="Learn how to join the leaderboard (via DM)") @whitelist_override(channels=AOC_WHITELIST) @@ -135,7 +135,7 @@ class AdventOfCode(commands.Cog): await ctx.send(f"The Python Discord leaderboard for {current_year} is not yet available!") return - author = ctx.message.author + author = ctx.author log.info(f"{author.name} ({author.id}) has requested a PyDis AoC leaderboard code") if AocConfig.staff_leaderboard_id and any(r.id == Roles.helpers for r in author.roles): diff --git a/bot/exts/christmas/hanukkah_embed.py b/bot/exts/christmas/hanukkah_embed.py index 4f470a34..cd8a9192 100644 --- a/bot/exts/christmas/hanukkah_embed.py +++ b/bot/exts/christmas/hanukkah_embed.py @@ -5,6 +5,7 @@ from typing import List from discord import Embed from discord.ext import commands +from bot.bot import Bot from bot.constants import Colours, Month from bot.utils.decorators import in_month @@ -14,7 +15,7 @@ log = logging.getLogger(__name__) class HanukkahEmbed(commands.Cog): """A cog that returns information about Hanukkah festival.""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Bot): self.bot = bot self.url = ("https://www.hebcal.com/hebcal/?v=1&cfg=json&maj=on&min=on&mod=on&nx=on&" "year=now&month=x&ss=on&mf=on&c=on&geo=geoname&geonameid=3448439&m=50&s=on") @@ -60,17 +61,18 @@ class HanukkahEmbed(commands.Cog): if day in self.hanukkah_days and month in self.hanukkah_months and year in self.hanukkah_years: if int(day) == hanukkah_start_day: now = datetime.datetime.utcnow() - now = str(now) - hours = int(now[11:13]) + 4 # using only hours + hours = now.hour + 4 # using only hours hanukkah_start_hour = 18 if hours < hanukkah_start_hour: embed.description = (f"Hanukkah hasnt started yet, " f"it will start in about {hanukkah_start_hour-hours} hour/s.") - return await ctx.send(embed=embed) + await ctx.send(embed=embed) + return elif hours > hanukkah_start_hour: embed.description = (f'It is the starting day of Hanukkah ! ' f'Its been {hours-hanukkah_start_hour} hours hanukkah started !') - return await ctx.send(embed=embed) + await ctx.send(embed=embed) + return festival_day = self.hanukkah_days.index(day) number_suffixes = ['st', 'nd', 'rd', 'th'] suffix = '' @@ -108,6 +110,6 @@ class HanukkahEmbed(commands.Cog): self.hanukkah_years.append(date[0:4]) -def setup(bot: commands.Bot) -> None: - """Cog load.""" +def setup(bot: Bot) -> None: + """Load the Hanukkah Embed Cog.""" bot.add_cog(HanukkahEmbed(bot)) diff --git a/bot/exts/pride/drag_queen_name.py b/bot/exts/pride/drag_queen_name.py index fca9750f..32ead1bc 100644 --- a/bot/exts/pride/drag_queen_name.py +++ b/bot/exts/pride/drag_queen_name.py @@ -5,14 +5,15 @@ from pathlib import Path from discord.ext import commands +from bot.bot import Bot + log = logging.getLogger(__name__) class DragNames(commands.Cog): """Gives a random drag queen name!""" - def __init__(self, bot: commands.Bot): - self.bot = bot + def __init__(self): self.names = self.load_names() @staticmethod @@ -27,6 +28,6 @@ class DragNames(commands.Cog): await ctx.send(random.choice(self.names)) -def setup(bot: commands.Bot) -> None: - """Cog loader for drag queen name generator.""" +def setup(bot: Bot) -> None: + """Load the Drag Queen Cog.""" bot.add_cog(DragNames(bot)) diff --git a/bot/exts/pride/pride_anthem.py b/bot/exts/pride/pride_anthem.py index 33cb2a9d..a7f8d7ef 100644 --- a/bot/exts/pride/pride_anthem.py +++ b/bot/exts/pride/pride_anthem.py @@ -2,20 +2,22 @@ import json import logging import random from pathlib import Path +from typing import Optional from discord.ext import commands +from bot.bot import Bot + log = logging.getLogger(__name__) class PrideAnthem(commands.Cog): """Embed a random youtube video for a gay anthem!""" - def __init__(self, bot: commands.Bot): - self.bot = bot + def __init__(self): self.anthems = self.load_vids() - def get_video(self, genre: str = None) -> dict: + def get_video(self, genre: Optional[str] = None) -> dict: """ Picks a random anthem from the list. @@ -52,6 +54,6 @@ class PrideAnthem(commands.Cog): await ctx.send("I couldn't find a video, sorry!") -def setup(bot: commands.Bot) -> None: - """Cog loader for pride anthem.""" - bot.add_cog(PrideAnthem(bot)) +def setup(bot: Bot) -> None: + """Load the Pride Anthem Cog.""" + bot.add_cog(PrideAnthem()) diff --git a/bot/exts/pride/pride_facts.py b/bot/exts/pride/pride_facts.py index 5bd5d0ce..b2daaab7 100644 --- a/bot/exts/pride/pride_facts.py +++ b/bot/exts/pride/pride_facts.py @@ -15,8 +15,6 @@ from bot.utils.decorators import seasonal_task log = logging.getLogger(__name__) -Sendable = Union[commands.Context, discord.TextChannel] - class PrideFacts(commands.Cog): """Provides a new fact every day during the Pride season!""" @@ -44,7 +42,7 @@ class PrideFacts(commands.Cog): async def send_random_fact(self, ctx: commands.Context) -> None: """Provides a fact from any previous day, or today.""" now = datetime.utcnow() - previous_years_facts = (self.facts[x] for x in self.facts.keys() if int(x) < now.year) + previous_years_facts = (y for x, y in self.facts.items() if int(x) < now.year) current_year_facts = self.facts.get(str(now.year), [])[:now.day] previous_facts = current_year_facts + [x for y in previous_years_facts for x in y] try: @@ -52,7 +50,7 @@ class PrideFacts(commands.Cog): except IndexError: await ctx.send("No facts available") - async def send_select_fact(self, target: Sendable, _date: Union[str, datetime]) -> None: + async def send_select_fact(self, target: discord.abc.Messageable, _date: Union[str, datetime]) -> None: """Provides the fact for the specified day, if the day is today, or is in the past.""" now = datetime.utcnow() if isinstance(_date, str): @@ -76,7 +74,7 @@ class PrideFacts(commands.Cog): await target.send("The fact for the selected day is not yet available.") @commands.command(name="pridefact", aliases=["pridefacts"]) - async def pridefact(self, ctx: commands.Context) -> None: + async def pridefact(self, ctx: commands.Context, option: str = None) -> None: """ Sends a message with a pride fact of the day. @@ -85,15 +83,15 @@ class PrideFacts(commands.Cog): If a date is given as an argument, and the date is in the past, the fact from that day will be provided. """ - message_body = ctx.message.content[len(ctx.invoked_with) + 2:] - if message_body == "": + if not option: await self.send_select_fact(ctx, datetime.utcnow()) - elif message_body.lower().startswith("rand"): + elif option.lower().startswith("rand"): await self.send_random_fact(ctx) else: - await self.send_select_fact(ctx, message_body) + await self.send_select_fact(ctx, option) - def make_embed(self, fact: str) -> discord.Embed: + @staticmethod + def make_embed(fact: str) -> discord.Embed: """Makes a nice embed for the fact to be sent.""" return discord.Embed( colour=Colours.pink, -- cgit v1.2.3 From dc7923a78a4020a4020c42a392ff38bf3f5c35f3 Mon Sep 17 00:00:00 2001 From: ToxicKidz <78174417+ToxicKidz@users.noreply.github.com> Date: Tue, 20 Apr 2021 12:30:27 -0400 Subject: chore: Fix UnboundLocalError and discord.ForbiddenErrors in the catify command --- bot/exts/evergreen/catify.py | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/catify.py b/bot/exts/evergreen/catify.py index a0121403..c409ce6c 100644 --- a/bot/exts/evergreen/catify.py +++ b/bot/exts/evergreen/catify.py @@ -1,7 +1,8 @@ import random +from contextlib import suppress from typing import Optional -from discord import AllowedMentions, Embed +from discord import AllowedMentions, Embed, Forbidden from discord.ext import commands from bot.constants import Cats, Colours, NEGATIVE_REPLIES @@ -34,8 +35,11 @@ class Catify(commands.Cog): else: display_name += f" | {random.choice(Cats.cats)}" + await ctx.send(f"Your catified username is: `{display_name}`") - await ctx.author.edit(nick=display_name) + + with suppress(Forbidden): + await ctx.author.edit(nick=display_name) else: if len(text) >= 1500: embed = Embed( @@ -50,27 +54,27 @@ class Catify(commands.Cog): for index, name in enumerate(string_list): if "cat" in name: if random.randint(0, 5) == 5: - string_list[index] = string_list[index].replace("cat", f"**{random.choice(Cats.cats)}**") + string_list[index] = name.replace("cat", f"**{random.choice(Cats.cats)}**") else: - string_list[index] = string_list[index].replace("cat", random.choice(Cats.cats)) + string_list[index] = name.replace("cat", random.choice(Cats.cats)) for element in Cats.cats: if element in name: - string_list[index] = string_list[index].replace(element, "cat") + string_list[index] = name.replace(element, "cat") - string_len = len(string_list) // 3 or len(string_list) + string_len = len(string_list) // 3 or len(string_list) - for _ in range(random.randint(1, string_len)): - # insert cat at random index - if random.randint(0, 5) == 5: - string_list.insert(random.randint(0, len(string_list)), f"**{random.choice(Cats.cats)}**") - else: - string_list.insert(random.randint(0, len(string_list)), random.choice(Cats.cats)) + for _ in range(random.randint(1, string_len)): + # insert cat at random index + if random.randint(0, 5) == 5: + string_list.insert(random.randint(0, len(string_list)), f"**{random.choice(Cats.cats)}**") + else: + string_list.insert(random.randint(0, len(string_list)), random.choice(Cats.cats)) - text = " ".join(string_list) - await ctx.send( - f">>> {text}", - allowed_mentions=AllowedMentions.none() - ) + text = " ".join(string_list) + await ctx.send( + f">>> {text}", + allowed_mentions=AllowedMentions.none() + ) def setup(bot: commands.Bot) -> None: -- cgit v1.2.3 From 3477069a3a2e0b5e4030602afa4a4c0e7411a7e1 Mon Sep 17 00:00:00 2001 From: ToxicKidz <78174417+ToxicKidz@users.noreply.github.com> Date: Tue, 20 Apr 2021 12:59:46 -0400 Subject: chore: lower the input to fine more cats --- bot/exts/evergreen/catify.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/catify.py b/bot/exts/evergreen/catify.py index c409ce6c..262c75bd 100644 --- a/bot/exts/evergreen/catify.py +++ b/bot/exts/evergreen/catify.py @@ -52,11 +52,12 @@ class Catify(commands.Cog): string_list = text.split() for index, name in enumerate(string_list): - if "cat" in name: + name = name.lower() + if "cat" in text: if random.randint(0, 5) == 5: - string_list[index] = name.replace("cat", f"**{random.choice(Cats.cats)}**") + string_list[index] = text.replace("cat", f"**{random.choice(Cats.cats)}**") else: - string_list[index] = name.replace("cat", random.choice(Cats.cats)) + string_list[index] = text.replace("cat", random.choice(Cats.cats)) for element in Cats.cats: if element in name: string_list[index] = name.replace(element, "cat") -- cgit v1.2.3 From 5f889e4d3f0712c0005dbbc7c3ee820cc786ec30 Mon Sep 17 00:00:00 2001 From: ToxicKidz <78174417+ToxicKidz@users.noreply.github.com> Date: Tue, 20 Apr 2021 13:05:03 -0400 Subject: fix: Use name.replace not text.replace --- bot/exts/evergreen/catify.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/catify.py b/bot/exts/evergreen/catify.py index 262c75bd..88c63202 100644 --- a/bot/exts/evergreen/catify.py +++ b/bot/exts/evergreen/catify.py @@ -53,11 +53,11 @@ class Catify(commands.Cog): string_list = text.split() for index, name in enumerate(string_list): name = name.lower() - if "cat" in text: + if "cat" in name: if random.randint(0, 5) == 5: - string_list[index] = text.replace("cat", f"**{random.choice(Cats.cats)}**") + string_list[index] = name.replace("cat", f"**{random.choice(Cats.cats)}**") else: - string_list[index] = text.replace("cat", random.choice(Cats.cats)) + string_list[index] = name.replace("cat", random.choice(Cats.cats)) for element in Cats.cats: if element in name: string_list[index] = name.replace(element, "cat") -- cgit v1.2.3 From daf7dc79753d0d4482f58ddcad0ee2a7f7a244c1 Mon Sep 17 00:00:00 2001 From: ToxicKidz <78174417+ToxicKidz@users.noreply.github.com> Date: Tue, 20 Apr 2021 16:13:57 -0400 Subject: chore: use 'nickname' and 'display name' in the right places and use allowed_mentions --- bot/exts/evergreen/catify.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/catify.py b/bot/exts/evergreen/catify.py index 88c63202..ae8d54b6 100644 --- a/bot/exts/evergreen/catify.py +++ b/bot/exts/evergreen/catify.py @@ -27,7 +27,10 @@ class Catify(commands.Cog): if len(display_name) > 26: embed = Embed( title=random.choice(NEGATIVE_REPLIES), - description="Your nickname is too long to be catified! Please change it to be under 26 characters.", + description=( + "Your display name is too long to be catified! " + "Please change it to be under 26 characters." + ), color=Colours.soft_red ) await ctx.send(embed=embed) @@ -36,7 +39,7 @@ class Catify(commands.Cog): else: display_name += f" | {random.choice(Cats.cats)}" - await ctx.send(f"Your catified username is: `{display_name}`") + await ctx.send(f"Your catified nickname is: `{display_name}`", allowed_mentions=AllowedMentions.none()) with suppress(Forbidden): await ctx.author.edit(nick=display_name) -- cgit v1.2.3 From 29084d1576e1435a5a4a32071a79b6af28acd362 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 22 Apr 2021 20:25:38 +0100 Subject: Fix errors when a subreddit has <5 posts. If a subreddit has <2 posts, the posts[1] check would fail with an IndexError. If the subreddit had less that 5 posts, then the k=5 check would also error. These changes harden the command for these edge cases. --- bot/exts/evergreen/reddit.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/reddit.py b/bot/exts/evergreen/reddit.py index 49127bea..4fdb6fca 100644 --- a/bot/exts/evergreen/reddit.py +++ b/bot/exts/evergreen/reddit.py @@ -54,7 +54,7 @@ class Reddit(commands.Cog): if not posts: return await ctx.send('No posts available!') - if posts[1]["data"]["over_18"] is True: + if posts[0]["data"]["over_18"] is True: return await ctx.send( "You cannot access this Subreddit as it is ment for those who " "are 18 years or older." @@ -63,7 +63,7 @@ class Reddit(commands.Cog): embed_titles = "" # Chooses k unique random elements from a population sequence or set. - random_posts = random.sample(posts, k=5) + random_posts = random.sample(posts, k=min(len(posts), 5)) # ----------------------------------------------------------- # This code below is bound of change when the emojis are added. -- cgit v1.2.3 From 9b9ab546090dc2513ac00f6b476b8b494eb5428a Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 22 Apr 2021 21:02:31 +0100 Subject: Escape invalid filename chars before saving. This would cause an issue with the embed not embedding the image due to a filename mismatch. This has been implemented by escaping all invalid filename characters before saving the file. --- .../profile_pic_modification/pfp_modify.py | 40 +++++++++++++--------- 1 file changed, 24 insertions(+), 16 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py index 77c937de..f6b1d394 100644 --- a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py +++ b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py @@ -1,7 +1,9 @@ import asyncio import json import logging +import string import typing as t +import unicodedata from concurrent.futures import ThreadPoolExecutor import discord @@ -36,6 +38,23 @@ async def in_executor(func: t.Callable, *args) -> t.Any: return await loop.run_in_executor(_EXECUTOR, func, *args) +def file_safe_name(effect: str, display_name: str) -> str: + """Returns a file safe filename based on the given effect and display name.""" + valid_filename_chars = f"-_.() {string.ascii_letters}{string.digits}" + + file_name = FILENAME_STRING.format(effect=effect, author=display_name) + + # Replace spaces + file_name = file_name.replace(" ", "_") + + # Normalize unicode characters + cleaned_filename = unicodedata.normalize('NFKD', file_name).encode('ASCII', 'ignore').decode() + + # Remove invalid filename characters + cleaned_filename = ''.join(c for c in cleaned_filename if c in valid_filename_chars) + return cleaned_filename + + class PfpModify(commands.Cog): """Various commands for users to change their own profile picture.""" @@ -76,10 +95,7 @@ class PfpModify(commands.Cog): return image_bytes = await member.avatar_url.read() - file_name = FILENAME_STRING.format( - effect="eightbit_avatar", - author=member.display_name - ) + file_name = file_safe_name("eightbit_avatar", member.display_name) file = await in_executor( PfpEffects.apply_effect, @@ -135,10 +151,7 @@ class PfpModify(commands.Cog): ctx.send = send_message # Reassigns ctx.send image_bytes = await member.avatar_url_as(size=256).read() - file_name = FILENAME_STRING.format( - effect="easterified_avatar", - author=member.display_name - ) + file_name = file_safe_name("easterified_avatar", member.display_name) file = await in_executor( PfpEffects.apply_effect, @@ -167,10 +180,7 @@ class PfpModify(commands.Cog): ) -> None: """Gets and sends the image in an embed. Used by the pride commands.""" async with ctx.typing(): - file_name = FILENAME_STRING.format( - effect="pride_avatar", - author=ctx.author.display_name - ) + file_name = file_safe_name("pride_avatar", ctx.author.display_name) file = await in_executor( PfpEffects.apply_effect, @@ -280,10 +290,8 @@ class PfpModify(commands.Cog): async with ctx.typing(): image_bytes = await member.avatar_url.read() - file_name = FILENAME_STRING.format( - effect="spooky_avatar", - author=member.display_name - ) + file_name = file_safe_name("spooky_avatar", member.display_name) + file = await in_executor( PfpEffects.apply_effect, image_bytes, -- cgit v1.2.3 From c837894af55deafff478bf8aa3b115d4589b6e07 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 22 Apr 2021 21:59:29 +0100 Subject: Only use WrappedMessageConverter in the actual command signature --- bot/exts/evergreen/bookmark.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/bookmark.py b/bot/exts/evergreen/bookmark.py index 16919d8c..f0b828f8 100644 --- a/bot/exts/evergreen/bookmark.py +++ b/bot/exts/evergreen/bookmark.py @@ -22,7 +22,7 @@ class Bookmark(commands.Cog): self.bot = bot @staticmethod - def build_bookmark_dm(target_message: WrappedMessageConverter, title: str) -> discord.Embed: + def build_bookmark_dm(target_message: discord.Message, title: str) -> discord.Embed: """Builds the embed to DM the bookmark requester.""" embed = discord.Embed( title=title, @@ -51,7 +51,7 @@ class Bookmark(commands.Cog): self, channel: discord.TextChannel, user: discord.Member, - target_message: WrappedMessageConverter, + target_message: discord.Message, title: str ) -> None: """Sends the bookmark DM, or sends an error embed when a user bookmarks a message.""" @@ -67,7 +67,7 @@ class Bookmark(commands.Cog): @staticmethod async def send_reaction_embed( channel: discord.TextChannel, - target_message: WrappedMessageConverter + target_message: discord.Message ) -> discord.Message: """Sends an embed, with a reaction, so users can react to bookmark the message too.""" message = await channel.send( -- cgit v1.2.3 From e858a3682ffe36675570508802f633046b39ebd8 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela Date: Fri, 23 Apr 2021 02:55:03 +0300 Subject: Adds Link Suppressing Helper Adds a helper to find and escape links in a message. Signed-off-by: Hassan Abouelela --- bot/utils/helpers.py | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 bot/utils/helpers.py (limited to 'bot') diff --git a/bot/utils/helpers.py b/bot/utils/helpers.py new file mode 100644 index 00000000..74c2ccd0 --- /dev/null +++ b/bot/utils/helpers.py @@ -0,0 +1,8 @@ +import re + + +def suppress_links(message: str) -> str: + """Accepts a message that may contain links, suppresses them, and returns them.""" + for link in set(re.findall(r"https?://[^\s]+", message, re.IGNORECASE)): + message = message.replace(link, f"<{link}>") + return message -- cgit v1.2.3 From 9e03064e9c116b0dd2bcc65b149b7ac9ee389ff3 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela Date: Fri, 23 Apr 2021 02:59:30 +0300 Subject: Suppresses Links In Commands Suppresses links in certain commands that can echo back user input. Signed-off-by: Hassan Abouelela --- bot/exts/easter/egg_decorating.py | 4 +++- bot/exts/evergreen/catify.py | 3 ++- bot/exts/evergreen/fun.py | 3 +++ 3 files changed, 8 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/exts/easter/egg_decorating.py b/bot/exts/easter/egg_decorating.py index b18e6636..a847388d 100644 --- a/bot/exts/easter/egg_decorating.py +++ b/bot/exts/easter/egg_decorating.py @@ -10,6 +10,8 @@ import discord from PIL import Image from discord.ext import commands +from bot.utils import helpers + log = logging.getLogger(__name__) with open(Path("bot/resources/evergreen/html_colours.json"), encoding="utf8") as f: @@ -65,7 +67,7 @@ class EggDecorating(commands.Cog): if value: colours[idx] = discord.Colour(value) else: - invalid.append(colour) + invalid.append(helpers.suppress_links(colour)) if len(invalid) > 1: return await ctx.send(f"Sorry, I don't know these colours: {' '.join(invalid)}") diff --git a/bot/exts/evergreen/catify.py b/bot/exts/evergreen/catify.py index ae8d54b6..d8a7442d 100644 --- a/bot/exts/evergreen/catify.py +++ b/bot/exts/evergreen/catify.py @@ -6,6 +6,7 @@ from discord import AllowedMentions, Embed, Forbidden from discord.ext import commands from bot.constants import Cats, Colours, NEGATIVE_REPLIES +from bot.utils import helpers class Catify(commands.Cog): @@ -74,7 +75,7 @@ class Catify(commands.Cog): else: string_list.insert(random.randint(0, len(string_list)), random.choice(Cats.cats)) - text = " ".join(string_list) + text = helpers.suppress_links(" ".join(string_list)) await ctx.send( f">>> {text}", allowed_mentions=AllowedMentions.none() diff --git a/bot/exts/evergreen/fun.py b/bot/exts/evergreen/fun.py index 101725da..7152d0cb 100644 --- a/bot/exts/evergreen/fun.py +++ b/bot/exts/evergreen/fun.py @@ -11,6 +11,7 @@ from discord.ext.commands import BadArgument, Bot, Cog, Context, MessageConverte from bot import utils from bot.constants import Client, Colours, Emojis +from bot.utils import helpers log = logging.getLogger(__name__) @@ -83,6 +84,7 @@ class Fun(Cog): if embed is not None: embed = Fun._convert_embed(conversion_func, embed) converted_text = conversion_func(text) + converted_text = helpers.suppress_links(converted_text) # Don't put >>> if only embed present if converted_text: converted_text = f">>> {converted_text.lstrip('> ')}" @@ -101,6 +103,7 @@ class Fun(Cog): if embed is not None: embed = Fun._convert_embed(conversion_func, embed) converted_text = conversion_func(text) + converted_text = helpers.suppress_links(converted_text) # Don't put >>> if only embed present if converted_text: converted_text = f">>> {converted_text.lstrip('> ')}" -- cgit v1.2.3 From d77986e528b977170c30efb6afc41e53425ad6df Mon Sep 17 00:00:00 2001 From: ChrisJL Date: Fri, 23 Apr 2021 12:28:37 +0100 Subject: Fix spelling of a user facing message in reddit cog Co-authored-by: Xithrius <15021300+Xithrius@users.noreply.github.com> --- bot/exts/evergreen/reddit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/reddit.py b/bot/exts/evergreen/reddit.py index 4fdb6fca..2be511c8 100644 --- a/bot/exts/evergreen/reddit.py +++ b/bot/exts/evergreen/reddit.py @@ -56,7 +56,7 @@ class Reddit(commands.Cog): if posts[0]["data"]["over_18"] is True: return await ctx.send( - "You cannot access this Subreddit as it is ment for those who " + "You cannot access this Subreddit as it is meant for those who " "are 18 years or older." ) -- cgit v1.2.3 From e666fb7b80a9f590257d1426c7604ff180b35e6e Mon Sep 17 00:00:00 2001 From: Salil Chincholikar Date: Tue, 27 Apr 2021 14:39:58 +0530 Subject: Reworded/fixed grammatical error --- bot/utils/exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/utils/exceptions.py b/bot/utils/exceptions.py index 2b1c1b31..5e4d6330 100644 --- a/bot/utils/exceptions.py +++ b/bot/utils/exceptions.py @@ -1,4 +1,4 @@ class UserNotPlayingError(Exception): - """Will raised when user try to use game commands when not playing.""" + """Will be raised when the users try to use game commands when they are not playing.""" pass -- cgit v1.2.3 From 4e0210922ef8d25eeb18f080c9baab022d70d0d2 Mon Sep 17 00:00:00 2001 From: Salil Chincholikar <31334826+chincholikarsalil@users.noreply.github.com> Date: Tue, 27 Apr 2021 19:12:28 +0530 Subject: Updated bot/utils/exceptions.py Co-authored-by: Joe Banks --- bot/utils/exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/utils/exceptions.py b/bot/utils/exceptions.py index 5e4d6330..9e080759 100644 --- a/bot/utils/exceptions.py +++ b/bot/utils/exceptions.py @@ -1,4 +1,4 @@ class UserNotPlayingError(Exception): - """Will be raised when the users try to use game commands when they are not playing.""" + """Raised when users try to use game commands when they are not playing.""" pass -- cgit v1.2.3 From dade71a609d298e8bfb16fef8c8ecbd2faffe51d Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 30 Apr 2021 10:00:51 +0100 Subject: Rename pfp_modify cog and group to avatar_mod for clarity Added in aliases for ease of use too. --- bot/exts/evergreen/avatar_modification/__init__.py | 0 bot/exts/evergreen/avatar_modification/_effects.py | 139 +++++++++ .../evergreen/avatar_modification/avatar_modify.py | 315 +++++++++++++++++++++ .../evergreen/profile_pic_modification/__init__.py | 0 .../evergreen/profile_pic_modification/_effects.py | 139 --------- .../profile_pic_modification/pfp_modify.py | 315 --------------------- 6 files changed, 454 insertions(+), 454 deletions(-) create mode 100644 bot/exts/evergreen/avatar_modification/__init__.py create mode 100644 bot/exts/evergreen/avatar_modification/_effects.py create mode 100644 bot/exts/evergreen/avatar_modification/avatar_modify.py delete mode 100644 bot/exts/evergreen/profile_pic_modification/__init__.py delete mode 100644 bot/exts/evergreen/profile_pic_modification/_effects.py delete mode 100644 bot/exts/evergreen/profile_pic_modification/pfp_modify.py (limited to 'bot') diff --git a/bot/exts/evergreen/avatar_modification/__init__.py b/bot/exts/evergreen/avatar_modification/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/bot/exts/evergreen/avatar_modification/_effects.py b/bot/exts/evergreen/avatar_modification/_effects.py new file mode 100644 index 00000000..e415d700 --- /dev/null +++ b/bot/exts/evergreen/avatar_modification/_effects.py @@ -0,0 +1,139 @@ +import typing as t +from io import BytesIO +from pathlib import Path + +import discord +from PIL import Image, ImageDraw, ImageOps + +from bot.constants import Colours + + +class PfpEffects: + """ + Implements various image modifying effects, for the PfpModify cog. + + All of these fuctions are slow, and blocking, so they should be ran in executors. + """ + + @staticmethod + def apply_effect(image_bytes: bytes, effect: t.Callable, filename: str, *args) -> discord.File: + """Applies the given effect to the image passed to it.""" + im = Image.open(BytesIO(image_bytes)) + im = im.convert("RGBA") + im = effect(im, *args) + + bufferedio = BytesIO() + im.save(bufferedio, format="PNG") + bufferedio.seek(0) + + return discord.File(bufferedio, filename=filename) + + @staticmethod + def closest(x: t.Tuple[int, int, int]) -> t.Tuple[int, int, int]: + """ + Finds the closest "easter" colour to a given pixel. + + Returns a merge between the original colour and the closest colour. + """ + r1, g1, b1 = x + + def distance(point: t.Tuple[int, int, int]) -> t.Tuple[int, int, int]: + """Finds the difference between a pastel colour and the original pixel colour.""" + r2, g2, b2 = point + return (r1 - r2) ** 2 + (g1 - g2) ** 2 + (b1 - b2) ** 2 + + closest_colours = sorted(Colours.easter_like_colours, key=distance) + r2, g2, b2 = closest_colours[0] + r = (r1 + r2) // 2 + g = (g1 + g2) // 2 + b = (b1 + b2) // 2 + + return r, g, b + + @staticmethod + def crop_avatar_circle(avatar: Image) -> Image: + """This crops the avatar given into a circle.""" + mask = Image.new("L", avatar.size, 0) + draw = ImageDraw.Draw(mask) + draw.ellipse((0, 0) + avatar.size, fill=255) + avatar.putalpha(mask) + return avatar + + @staticmethod + def crop_ring(ring: Image, px: int) -> Image: + """This crops the given ring into a circle.""" + mask = Image.new("L", ring.size, 0) + draw = ImageDraw.Draw(mask) + draw.ellipse((0, 0) + ring.size, fill=255) + draw.ellipse((px, px, 1024-px, 1024-px), fill=0) + ring.putalpha(mask) + return ring + + @staticmethod + def pridify_effect(image: Image, pixels: int, flag: str) -> Image: + """Applies the given pride effect to the given image.""" + image = image.resize((1024, 1024)) + image = PfpEffects.crop_avatar_circle(image) + + ring = Image.open(Path(f"bot/resources/pride/flags/{flag}.png")).resize((1024, 1024)) + ring = ring.convert("RGBA") + ring = PfpEffects.crop_ring(ring, pixels) + + image.alpha_composite(ring, (0, 0)) + return image + + @staticmethod + def eight_bitify_effect(image: Image) -> Image: + """ + Applies the 8bit effect to the given image. + + This is done by reducing the image to 32x32 and then back up to 1024x1024. + We then quantize the image before returning too. + """ + image = image.resize((32, 32), resample=Image.NEAREST) + image = image.resize((1024, 1024), resample=Image.NEAREST) + return image.quantize() + + @staticmethod + def easterify_effect(image: Image, overlay_image: Image = None) -> Image: + """ + Applies the easter effect to the given image. + + This is done by getting the closest "easter" colour to each pixel and changing the colour + to the half-way RGBvalue. + + We also then add an overlay image on top in middle right, a chocolate bunny by default. + """ + if overlay_image: + ratio = 64 / overlay_image.height + overlay_image = overlay_image.resize(( + round(overlay_image.width * ratio), + round(overlay_image.height * ratio) + )) + overlay_image = overlay_image.convert("RGBA") + else: + overlay_image = Image.open(Path("bot/resources/easter/chocolate_bunny.png")) + + alpha = image.getchannel("A").getdata() + image = image.convert("RGB") + image = ImageOps.posterize(image, 6) + + data = image.getdata() + data_set = set(data) + easterified_data_set = {} + + for x in data_set: + easterified_data_set[x] = PfpEffects.closest(x) + new_pixel_data = [ + (*easterified_data_set[x], alpha[i]) + if x in easterified_data_set else x + for i, x in enumerate(data) + ] + + im = Image.new("RGBA", image.size) + im.putdata(new_pixel_data) + im.alpha_composite( + overlay_image, + (im.width - overlay_image.width, (im.height - overlay_image.height) // 2) + ) + return im diff --git a/bot/exts/evergreen/avatar_modification/avatar_modify.py b/bot/exts/evergreen/avatar_modification/avatar_modify.py new file mode 100644 index 00000000..e1078fa0 --- /dev/null +++ b/bot/exts/evergreen/avatar_modification/avatar_modify.py @@ -0,0 +1,315 @@ +import asyncio +import json +import logging +import string +import typing as t +import unicodedata +from concurrent.futures import ThreadPoolExecutor + +import discord +from aiohttp import client_exceptions +from discord.ext import commands +from discord.ext.commands.errors import BadArgument + +from bot.constants import Client, Colours, Emojis +from bot.exts.evergreen.avatar_modification._effects import PfpEffects +from bot.utils.extensions import invoke_help_command +from bot.utils.halloween import spookifications + +log = logging.getLogger(__name__) + +_EXECUTOR = ThreadPoolExecutor(10) + +FILENAME_STRING = "{effect}_{author}.png" + +with open('bot/resources/pride/gender_options.json') as f: + GENDER_OPTIONS = json.load(f) + + +async def in_executor(func: t.Callable, *args) -> t.Any: + """ + Runs the given synchronus function `func` in an executor. + + This is useful for running slow, blocking code within async + functions, so that they don't block the bot. + """ + log.trace(f"Running {func.__name__} in an executor.") + loop = asyncio.get_event_loop() + return await loop.run_in_executor(_EXECUTOR, func, *args) + + +def file_safe_name(effect: str, display_name: str) -> str: + """Returns a file safe filename based on the given effect and display name.""" + valid_filename_chars = f"-_.() {string.ascii_letters}{string.digits}" + + file_name = FILENAME_STRING.format(effect=effect, author=display_name) + + # Replace spaces + file_name = file_name.replace(" ", "_") + + # Normalize unicode characters + cleaned_filename = unicodedata.normalize('NFKD', file_name).encode('ASCII', 'ignore').decode() + + # Remove invalid filename characters + cleaned_filename = ''.join(c for c in cleaned_filename if c in valid_filename_chars) + return cleaned_filename + + +class AvatarModify(commands.Cog): + """Various commands for users to apply affects to their own avatars.""" + + def __init__(self, bot: commands.Bot) -> None: + self.bot = bot + + async def _fetch_member(self, member_id: int) -> t.Optional[discord.Member]: + """ + Fetches a member and handles errors. + + This helper funciton is required as the member cache doesn't always have the most up to date + profile picture. This can lead to errors if the image is delted from the Discord CDN. + """ + try: + member = await self.bot.get_guild(Client.guild).fetch_member(member_id) + except discord.errors.NotFound: + log.debug(f"Member {member_id} left the guild before we could get their pfp.") + return None + except discord.HTTPException: + log.exception(f"Exception while trying to retrieve member {member_id} from Discord.") + return None + + return member + + @commands.group(aliases=('avatar_mod', 'pfp_mod')) + async def avatar_modify(self, ctx: commands.Context) -> None: + """Groups all of the pfp modifying commands to allow a single concurrency limit.""" + if not ctx.invoked_subcommand: + await invoke_help_command(ctx) + + @avatar_modify.command(name="8bitify", root_aliases=("8bitify",)) + async def eightbit_command(self, ctx: commands.Context) -> None: + """Pixelates your avatar and changes the palette to an 8bit one.""" + async with ctx.typing(): + member = await self._fetch_member(ctx.author.id) + if not member: + await ctx.send(f"{Emojis.cross_mark} Could not get member info.") + return + + image_bytes = await member.avatar_url.read() + file_name = file_safe_name("eightbit_avatar", member.display_name) + + file = await in_executor( + PfpEffects.apply_effect, + image_bytes, + PfpEffects.eight_bitify_effect, + file_name + ) + + embed = discord.Embed( + title="Your 8-bit avatar", + description="Here is your avatar. I think it looks all cool and 'retro'." + ) + + embed.set_image(url=f"attachment://{file_name}") + embed.set_footer(text=f"Made by {member.display_name}.", icon_url=member.avatar_url) + + await ctx.send(embed=embed, file=file) + + @avatar_modify.command(aliases=["easterify"], root_aliases=("easterify", "avatareasterify")) + async def avatareasterify(self, ctx: commands.Context, *colours: t.Union[discord.Colour, str]) -> None: + """ + This "Easterifies" the user's avatar. + + Given colours will produce a personalised egg in the corner, similar to the egg_decorate command. + If colours are not given, a nice little chocolate bunny will sit in the corner. + Colours are split by spaces, unless you wrap the colour name in double quotes. + Discord colour names, HTML colour names, XKCD colour names and hex values are accepted. + """ + async def send(*args, **kwargs) -> str: + """ + This replaces the original ctx.send. + + When invoking the egg decorating command, the egg itself doesn't print to to the channel. + Returns the message content so that if any errors occur, the error message can be output. + """ + if args: + return args[0] + + async with ctx.typing(): + member = await self._fetch_member(ctx.author.id) + if not member: + await ctx.send(f"{Emojis.cross_mark} Could not get member info.") + return + + egg = None + if colours: + send_message = ctx.send + ctx.send = send # Assigns ctx.send to a fake send + egg = await ctx.invoke(self.bot.get_command("eggdecorate"), *colours) + if isinstance(egg, str): # When an error message occurs in eggdecorate. + await send_message(egg) + return + ctx.send = send_message # Reassigns ctx.send + + image_bytes = await member.avatar_url_as(size=256).read() + file_name = file_safe_name("easterified_avatar", member.display_name) + + file = await in_executor( + PfpEffects.apply_effect, + image_bytes, + PfpEffects.easterify_effect, + file_name, + egg + ) + + embed = discord.Embed( + name="Your Lovely Easterified Avatar!", + description="Here is your lovely avatar, all bright and colourful\nwith Easter pastel colours. Enjoy :D" + ) + embed.set_image(url=f"attachment://{file_name}") + embed.set_footer(text=f"Made by {member.display_name}.", icon_url=member.avatar_url) + + await ctx.send(file=file, embed=embed) + + @staticmethod + async def send_pride_image( + ctx: commands.Context, + image_bytes: bytes, + pixels: int, + flag: str, + option: str + ) -> None: + """Gets and sends the image in an embed. Used by the pride commands.""" + async with ctx.typing(): + file_name = file_safe_name("pride_avatar", ctx.author.display_name) + + file = await in_executor( + PfpEffects.apply_effect, + image_bytes, + PfpEffects.pridify_effect, + file_name, + pixels, + flag + ) + + embed = discord.Embed( + name="Your Lovely Pride Avatar!", + description=f"Here is your lovely avatar, surrounded by\n a beautiful {option} flag. Enjoy :D" + ) + embed.set_image(url=f"attachment://{file_name}") + embed.set_footer(text=f"Made by {ctx.author.display_name}.", icon_url=ctx.author.avatar_url) + await ctx.send(file=file, embed=embed) + + @avatar_modify.group( + aliases=["avatarpride", "pridepfp", "prideprofile"], + root_aliases=("prideavatar", "avatarpride", "pridepfp", "prideprofile"), + invoke_without_command=True + ) + async def prideavatar(self, ctx: commands.Context, option: str = "lgbt", pixels: int = 64) -> None: + """ + This surrounds an avatar with a border of a specified LGBT flag. + + This defaults to the LGBT rainbow flag if none is given. + The amount of pixels can be given which determines the thickness of the flag border. + This has a maximum of 512px and defaults to a 64px border. + The full image is 1024x1024. + """ + option = option.lower() + pixels = max(0, min(512, pixels)) + flag = GENDER_OPTIONS.get(option) + if flag is None: + await ctx.send("I don't have that flag!") + return + + async with ctx.typing(): + member = await self._fetch_member(ctx.author.id) + if not member: + await ctx.send(f"{Emojis.cross_mark} Could not get member info.") + return + image_bytes = await member.avatar_url_as(size=1024).read() + await self.send_pride_image(ctx, image_bytes, pixels, flag, option) + + @prideavatar.command() + async def image(self, ctx: commands.Context, url: str, option: str = "lgbt", pixels: int = 64) -> None: + """ + This surrounds the image specified by the URL with a border of a specified LGBT flag. + + This defaults to the LGBT rainbow flag if none is given. + The amount of pixels can be given which determines the thickness of the flag border. + This has a maximum of 512px and defaults to a 64px border. + The full image is 1024x1024. + """ + option = option.lower() + pixels = max(0, min(512, pixels)) + flag = GENDER_OPTIONS.get(option) + if flag is None: + await ctx.send("I don't have that flag!") + return + + async with ctx.typing(): + try: + async with self.bot.http_session.get(url) as response: + if response.status != 200: + await ctx.send("Bad response from provided URL!") + return + image_bytes = await response.read() + except client_exceptions.ClientConnectorError: + raise BadArgument("Cannot connect to provided URL!") + except client_exceptions.InvalidURL: + raise BadArgument("Invalid URL!") + + await self.send_pride_image(ctx, image_bytes, pixels, flag, option) + + @prideavatar.command() + async def flags(self, ctx: commands.Context) -> None: + """This lists the flags that can be used with the prideavatar command.""" + choices = sorted(set(GENDER_OPTIONS.values())) + options = "• " + "\n• ".join(choices) + embed = discord.Embed( + title="I have the following flags:", + description=options, + colour=Colours.soft_red + ) + await ctx.send(embed=embed) + + @avatar_modify.command( + name='spookyavatar', + aliases=('savatar', 'spookify'), + root_aliases=('spookyavatar', 'spookify', 'savatar'), + brief='Spookify an user\'s avatar.' + ) + async def spooky_avatar(self, ctx: commands.Context, member: discord.Member = None) -> None: + """This "spookifies" the given user's avatar, with a random *spooky* effect.""" + if member is None: + member = ctx.author + + member = await self._fetch_member(member.id) + if not member: + await ctx.send(f"{Emojis.cross_mark} Could not get member info.") + return + + async with ctx.typing(): + image_bytes = await member.avatar_url.read() + + file_name = file_safe_name("spooky_avatar", member.display_name) + + file = await in_executor( + PfpEffects.apply_effect, + image_bytes, + spookifications.get_random_effect, + file_name + ) + + embed = discord.Embed( + title="Is this you or am I just really paranoid?", + colour=Colours.soft_red + ) + embed.set_author(name=member.name, icon_url=member.avatar_url) + embed.set_image(url=f"attachment://{file_name}") + embed.set_footer(text=f"Made by {ctx.author.display_name}.", icon_url=ctx.author.avatar_url) + + await ctx.send(file=file, embed=embed) + + +def setup(bot: commands.Bot) -> None: + """Load the PfpModify cog.""" + bot.add_cog(AvatarModify(bot)) diff --git a/bot/exts/evergreen/profile_pic_modification/__init__.py b/bot/exts/evergreen/profile_pic_modification/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/bot/exts/evergreen/profile_pic_modification/_effects.py b/bot/exts/evergreen/profile_pic_modification/_effects.py deleted file mode 100644 index e415d700..00000000 --- a/bot/exts/evergreen/profile_pic_modification/_effects.py +++ /dev/null @@ -1,139 +0,0 @@ -import typing as t -from io import BytesIO -from pathlib import Path - -import discord -from PIL import Image, ImageDraw, ImageOps - -from bot.constants import Colours - - -class PfpEffects: - """ - Implements various image modifying effects, for the PfpModify cog. - - All of these fuctions are slow, and blocking, so they should be ran in executors. - """ - - @staticmethod - def apply_effect(image_bytes: bytes, effect: t.Callable, filename: str, *args) -> discord.File: - """Applies the given effect to the image passed to it.""" - im = Image.open(BytesIO(image_bytes)) - im = im.convert("RGBA") - im = effect(im, *args) - - bufferedio = BytesIO() - im.save(bufferedio, format="PNG") - bufferedio.seek(0) - - return discord.File(bufferedio, filename=filename) - - @staticmethod - def closest(x: t.Tuple[int, int, int]) -> t.Tuple[int, int, int]: - """ - Finds the closest "easter" colour to a given pixel. - - Returns a merge between the original colour and the closest colour. - """ - r1, g1, b1 = x - - def distance(point: t.Tuple[int, int, int]) -> t.Tuple[int, int, int]: - """Finds the difference between a pastel colour and the original pixel colour.""" - r2, g2, b2 = point - return (r1 - r2) ** 2 + (g1 - g2) ** 2 + (b1 - b2) ** 2 - - closest_colours = sorted(Colours.easter_like_colours, key=distance) - r2, g2, b2 = closest_colours[0] - r = (r1 + r2) // 2 - g = (g1 + g2) // 2 - b = (b1 + b2) // 2 - - return r, g, b - - @staticmethod - def crop_avatar_circle(avatar: Image) -> Image: - """This crops the avatar given into a circle.""" - mask = Image.new("L", avatar.size, 0) - draw = ImageDraw.Draw(mask) - draw.ellipse((0, 0) + avatar.size, fill=255) - avatar.putalpha(mask) - return avatar - - @staticmethod - def crop_ring(ring: Image, px: int) -> Image: - """This crops the given ring into a circle.""" - mask = Image.new("L", ring.size, 0) - draw = ImageDraw.Draw(mask) - draw.ellipse((0, 0) + ring.size, fill=255) - draw.ellipse((px, px, 1024-px, 1024-px), fill=0) - ring.putalpha(mask) - return ring - - @staticmethod - def pridify_effect(image: Image, pixels: int, flag: str) -> Image: - """Applies the given pride effect to the given image.""" - image = image.resize((1024, 1024)) - image = PfpEffects.crop_avatar_circle(image) - - ring = Image.open(Path(f"bot/resources/pride/flags/{flag}.png")).resize((1024, 1024)) - ring = ring.convert("RGBA") - ring = PfpEffects.crop_ring(ring, pixels) - - image.alpha_composite(ring, (0, 0)) - return image - - @staticmethod - def eight_bitify_effect(image: Image) -> Image: - """ - Applies the 8bit effect to the given image. - - This is done by reducing the image to 32x32 and then back up to 1024x1024. - We then quantize the image before returning too. - """ - image = image.resize((32, 32), resample=Image.NEAREST) - image = image.resize((1024, 1024), resample=Image.NEAREST) - return image.quantize() - - @staticmethod - def easterify_effect(image: Image, overlay_image: Image = None) -> Image: - """ - Applies the easter effect to the given image. - - This is done by getting the closest "easter" colour to each pixel and changing the colour - to the half-way RGBvalue. - - We also then add an overlay image on top in middle right, a chocolate bunny by default. - """ - if overlay_image: - ratio = 64 / overlay_image.height - overlay_image = overlay_image.resize(( - round(overlay_image.width * ratio), - round(overlay_image.height * ratio) - )) - overlay_image = overlay_image.convert("RGBA") - else: - overlay_image = Image.open(Path("bot/resources/easter/chocolate_bunny.png")) - - alpha = image.getchannel("A").getdata() - image = image.convert("RGB") - image = ImageOps.posterize(image, 6) - - data = image.getdata() - data_set = set(data) - easterified_data_set = {} - - for x in data_set: - easterified_data_set[x] = PfpEffects.closest(x) - new_pixel_data = [ - (*easterified_data_set[x], alpha[i]) - if x in easterified_data_set else x - for i, x in enumerate(data) - ] - - im = Image.new("RGBA", image.size) - im.putdata(new_pixel_data) - im.alpha_composite( - overlay_image, - (im.width - overlay_image.width, (im.height - overlay_image.height) // 2) - ) - return im diff --git a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py b/bot/exts/evergreen/profile_pic_modification/pfp_modify.py deleted file mode 100644 index f6b1d394..00000000 --- a/bot/exts/evergreen/profile_pic_modification/pfp_modify.py +++ /dev/null @@ -1,315 +0,0 @@ -import asyncio -import json -import logging -import string -import typing as t -import unicodedata -from concurrent.futures import ThreadPoolExecutor - -import discord -from aiohttp import client_exceptions -from discord.ext import commands -from discord.ext.commands.errors import BadArgument - -from bot.constants import Client, Colours, Emojis -from bot.exts.evergreen.profile_pic_modification._effects import PfpEffects -from bot.utils.extensions import invoke_help_command -from bot.utils.halloween import spookifications - -log = logging.getLogger(__name__) - -_EXECUTOR = ThreadPoolExecutor(10) - -FILENAME_STRING = "{effect}_{author}.png" - -with open('bot/resources/pride/gender_options.json') as f: - GENDER_OPTIONS = json.load(f) - - -async def in_executor(func: t.Callable, *args) -> t.Any: - """ - Runs the given synchronus function `func` in an executor. - - This is useful for running slow, blocking code within async - functions, so that they don't block the bot. - """ - log.trace(f"Running {func.__name__} in an executor.") - loop = asyncio.get_event_loop() - return await loop.run_in_executor(_EXECUTOR, func, *args) - - -def file_safe_name(effect: str, display_name: str) -> str: - """Returns a file safe filename based on the given effect and display name.""" - valid_filename_chars = f"-_.() {string.ascii_letters}{string.digits}" - - file_name = FILENAME_STRING.format(effect=effect, author=display_name) - - # Replace spaces - file_name = file_name.replace(" ", "_") - - # Normalize unicode characters - cleaned_filename = unicodedata.normalize('NFKD', file_name).encode('ASCII', 'ignore').decode() - - # Remove invalid filename characters - cleaned_filename = ''.join(c for c in cleaned_filename if c in valid_filename_chars) - return cleaned_filename - - -class PfpModify(commands.Cog): - """Various commands for users to change their own profile picture.""" - - def __init__(self, bot: commands.Bot) -> None: - self.bot = bot - - async def _fetch_member(self, member_id: int) -> t.Optional[discord.Member]: - """ - Fetches a member and handles errors. - - This helper funciton is required as the member cache doesn't always have the most up to date - profile picture. This can lead to errors if the image is delted from the Discord CDN. - """ - try: - member = await self.bot.get_guild(Client.guild).fetch_member(member_id) - except discord.errors.NotFound: - log.debug(f"Member {member_id} left the guild before we could get their pfp.") - return None - except discord.HTTPException: - log.exception(f"Exception while trying to retrieve member {member_id} from Discord.") - return None - - return member - - @commands.group() - async def pfp_modify(self, ctx: commands.Context) -> None: - """Groups all of the pfp modifying commands to allow a single concurrency limit.""" - if not ctx.invoked_subcommand: - await invoke_help_command(ctx) - - @pfp_modify.command(name="8bitify", root_aliases=("8bitify",)) - async def eightbit_command(self, ctx: commands.Context) -> None: - """Pixelates your avatar and changes the palette to an 8bit one.""" - async with ctx.typing(): - member = await self._fetch_member(ctx.author.id) - if not member: - await ctx.send(f"{Emojis.cross_mark} Could not get member info.") - return - - image_bytes = await member.avatar_url.read() - file_name = file_safe_name("eightbit_avatar", member.display_name) - - file = await in_executor( - PfpEffects.apply_effect, - image_bytes, - PfpEffects.eight_bitify_effect, - file_name - ) - - embed = discord.Embed( - title="Your 8-bit avatar", - description="Here is your avatar. I think it looks all cool and 'retro'." - ) - - embed.set_image(url=f"attachment://{file_name}") - embed.set_footer(text=f"Made by {member.display_name}.", icon_url=member.avatar_url) - - await ctx.send(embed=embed, file=file) - - @pfp_modify.command(aliases=["easterify"], root_aliases=("easterify", "avatareasterify")) - async def avatareasterify(self, ctx: commands.Context, *colours: t.Union[discord.Colour, str]) -> None: - """ - This "Easterifies" the user's avatar. - - Given colours will produce a personalised egg in the corner, similar to the egg_decorate command. - If colours are not given, a nice little chocolate bunny will sit in the corner. - Colours are split by spaces, unless you wrap the colour name in double quotes. - Discord colour names, HTML colour names, XKCD colour names and hex values are accepted. - """ - async def send(*args, **kwargs) -> str: - """ - This replaces the original ctx.send. - - When invoking the egg decorating command, the egg itself doesn't print to to the channel. - Returns the message content so that if any errors occur, the error message can be output. - """ - if args: - return args[0] - - async with ctx.typing(): - member = await self._fetch_member(ctx.author.id) - if not member: - await ctx.send(f"{Emojis.cross_mark} Could not get member info.") - return - - egg = None - if colours: - send_message = ctx.send - ctx.send = send # Assigns ctx.send to a fake send - egg = await ctx.invoke(self.bot.get_command("eggdecorate"), *colours) - if isinstance(egg, str): # When an error message occurs in eggdecorate. - await send_message(egg) - return - ctx.send = send_message # Reassigns ctx.send - - image_bytes = await member.avatar_url_as(size=256).read() - file_name = file_safe_name("easterified_avatar", member.display_name) - - file = await in_executor( - PfpEffects.apply_effect, - image_bytes, - PfpEffects.easterify_effect, - file_name, - egg - ) - - embed = discord.Embed( - name="Your Lovely Easterified Avatar!", - description="Here is your lovely avatar, all bright and colourful\nwith Easter pastel colours. Enjoy :D" - ) - embed.set_image(url=f"attachment://{file_name}") - embed.set_footer(text=f"Made by {member.display_name}.", icon_url=member.avatar_url) - - await ctx.send(file=file, embed=embed) - - @staticmethod - async def send_pride_image( - ctx: commands.Context, - image_bytes: bytes, - pixels: int, - flag: str, - option: str - ) -> None: - """Gets and sends the image in an embed. Used by the pride commands.""" - async with ctx.typing(): - file_name = file_safe_name("pride_avatar", ctx.author.display_name) - - file = await in_executor( - PfpEffects.apply_effect, - image_bytes, - PfpEffects.pridify_effect, - file_name, - pixels, - flag - ) - - embed = discord.Embed( - name="Your Lovely Pride Avatar!", - description=f"Here is your lovely avatar, surrounded by\n a beautiful {option} flag. Enjoy :D" - ) - embed.set_image(url=f"attachment://{file_name}") - embed.set_footer(text=f"Made by {ctx.author.display_name}.", icon_url=ctx.author.avatar_url) - await ctx.send(file=file, embed=embed) - - @pfp_modify.group( - aliases=["avatarpride", "pridepfp", "prideprofile"], - root_aliases=("prideavatar", "avatarpride", "pridepfp", "prideprofile"), - invoke_without_command=True - ) - async def prideavatar(self, ctx: commands.Context, option: str = "lgbt", pixels: int = 64) -> None: - """ - This surrounds an avatar with a border of a specified LGBT flag. - - This defaults to the LGBT rainbow flag if none is given. - The amount of pixels can be given which determines the thickness of the flag border. - This has a maximum of 512px and defaults to a 64px border. - The full image is 1024x1024. - """ - option = option.lower() - pixels = max(0, min(512, pixels)) - flag = GENDER_OPTIONS.get(option) - if flag is None: - await ctx.send("I don't have that flag!") - return - - async with ctx.typing(): - member = await self._fetch_member(ctx.author.id) - if not member: - await ctx.send(f"{Emojis.cross_mark} Could not get member info.") - return - image_bytes = await member.avatar_url_as(size=1024).read() - await self.send_pride_image(ctx, image_bytes, pixels, flag, option) - - @prideavatar.command() - async def image(self, ctx: commands.Context, url: str, option: str = "lgbt", pixels: int = 64) -> None: - """ - This surrounds the image specified by the URL with a border of a specified LGBT flag. - - This defaults to the LGBT rainbow flag if none is given. - The amount of pixels can be given which determines the thickness of the flag border. - This has a maximum of 512px and defaults to a 64px border. - The full image is 1024x1024. - """ - option = option.lower() - pixels = max(0, min(512, pixels)) - flag = GENDER_OPTIONS.get(option) - if flag is None: - await ctx.send("I don't have that flag!") - return - - async with ctx.typing(): - try: - async with self.bot.http_session.get(url) as response: - if response.status != 200: - await ctx.send("Bad response from provided URL!") - return - image_bytes = await response.read() - except client_exceptions.ClientConnectorError: - raise BadArgument("Cannot connect to provided URL!") - except client_exceptions.InvalidURL: - raise BadArgument("Invalid URL!") - - await self.send_pride_image(ctx, image_bytes, pixels, flag, option) - - @prideavatar.command() - async def flags(self, ctx: commands.Context) -> None: - """This lists the flags that can be used with the prideavatar command.""" - choices = sorted(set(GENDER_OPTIONS.values())) - options = "• " + "\n• ".join(choices) - embed = discord.Embed( - title="I have the following flags:", - description=options, - colour=Colours.soft_red - ) - await ctx.send(embed=embed) - - @pfp_modify.command( - name='spookyavatar', - aliases=('savatar', 'spookify'), - root_aliases=('spookyavatar', 'spookify', 'savatar'), - brief='Spookify an user\'s avatar.' - ) - async def spooky_avatar(self, ctx: commands.Context, member: discord.Member = None) -> None: - """This "spookifies" the given user's avatar, with a random *spooky* effect.""" - if member is None: - member = ctx.author - - member = await self._fetch_member(member.id) - if not member: - await ctx.send(f"{Emojis.cross_mark} Could not get member info.") - return - - async with ctx.typing(): - image_bytes = await member.avatar_url.read() - - file_name = file_safe_name("spooky_avatar", member.display_name) - - file = await in_executor( - PfpEffects.apply_effect, - image_bytes, - spookifications.get_random_effect, - file_name - ) - - embed = discord.Embed( - title="Is this you or am I just really paranoid?", - colour=Colours.soft_red - ) - embed.set_author(name=member.name, icon_url=member.avatar_url) - embed.set_image(url=f"attachment://{file_name}") - embed.set_footer(text=f"Made by {ctx.author.display_name}.", icon_url=ctx.author.avatar_url) - - await ctx.send(file=file, embed=embed) - - -def setup(bot: commands.Bot) -> None: - """Load the PfpModify cog.""" - bot.add_cog(PfpModify(bot)) -- cgit v1.2.3 From 1e1e8aec0d943cecda46fa22300bab3cae108263 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 30 Apr 2021 10:04:07 +0100 Subject: Consistant use of double quotes and tuples in avatar_mod cog --- .../evergreen/avatar_modification/avatar_modify.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/avatar_modification/avatar_modify.py b/bot/exts/evergreen/avatar_modification/avatar_modify.py index e1078fa0..095de306 100644 --- a/bot/exts/evergreen/avatar_modification/avatar_modify.py +++ b/bot/exts/evergreen/avatar_modification/avatar_modify.py @@ -22,7 +22,7 @@ _EXECUTOR = ThreadPoolExecutor(10) FILENAME_STRING = "{effect}_{author}.png" -with open('bot/resources/pride/gender_options.json') as f: +with open("bot/resources/pride/gender_options.json") as f: GENDER_OPTIONS = json.load(f) @@ -48,10 +48,10 @@ def file_safe_name(effect: str, display_name: str) -> str: file_name = file_name.replace(" ", "_") # Normalize unicode characters - cleaned_filename = unicodedata.normalize('NFKD', file_name).encode('ASCII', 'ignore').decode() + cleaned_filename = unicodedata.normalize("NFKD", file_name).encode("ASCII", "ignore").decode() # Remove invalid filename characters - cleaned_filename = ''.join(c for c in cleaned_filename if c in valid_filename_chars) + cleaned_filename = "".join(c for c in cleaned_filename if c in valid_filename_chars) return cleaned_filename @@ -79,7 +79,7 @@ class AvatarModify(commands.Cog): return member - @commands.group(aliases=('avatar_mod', 'pfp_mod')) + @commands.group(aliases=("avatar_mod", "pfp_mod")) async def avatar_modify(self, ctx: commands.Context) -> None: """Groups all of the pfp modifying commands to allow a single concurrency limit.""" if not ctx.invoked_subcommand: @@ -114,7 +114,7 @@ class AvatarModify(commands.Cog): await ctx.send(embed=embed, file=file) - @avatar_modify.command(aliases=["easterify"], root_aliases=("easterify", "avatareasterify")) + @avatar_modify.command(aliases=("easterify",), root_aliases=("easterify", "avatareasterify")) async def avatareasterify(self, ctx: commands.Context, *colours: t.Union[discord.Colour, str]) -> None: """ This "Easterifies" the user's avatar. @@ -200,7 +200,7 @@ class AvatarModify(commands.Cog): await ctx.send(file=file, embed=embed) @avatar_modify.group( - aliases=["avatarpride", "pridepfp", "prideprofile"], + aliases=("avatarpride", "pridepfp", "prideprofile"), root_aliases=("prideavatar", "avatarpride", "pridepfp", "prideprofile"), invoke_without_command=True ) @@ -272,12 +272,11 @@ class AvatarModify(commands.Cog): await ctx.send(embed=embed) @avatar_modify.command( - name='spookyavatar', - aliases=('savatar', 'spookify'), - root_aliases=('spookyavatar', 'spookify', 'savatar'), - brief='Spookify an user\'s avatar.' + aliases=("savatar", "spookify"), + root_aliases=("spookyavatar", "spookify", "savatar"), + brief="Spookify an user's avatar." ) - async def spooky_avatar(self, ctx: commands.Context, member: discord.Member = None) -> None: + async def spookyavatar(self, ctx: commands.Context, member: discord.Member = None) -> None: """This "spookifies" the given user's avatar, with a random *spooky* effect.""" if member is None: member = ctx.author -- cgit v1.2.3 From 2bbbef35ebbb01e7671250f58c4a8e7543942b08 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 30 Apr 2021 10:09:37 +0100 Subject: Add non-underscore aliases for lazy people like me :) --- bot/exts/evergreen/avatar_modification/avatar_modify.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/avatar_modification/avatar_modify.py b/bot/exts/evergreen/avatar_modification/avatar_modify.py index 095de306..a111c480 100644 --- a/bot/exts/evergreen/avatar_modification/avatar_modify.py +++ b/bot/exts/evergreen/avatar_modification/avatar_modify.py @@ -79,7 +79,7 @@ class AvatarModify(commands.Cog): return member - @commands.group(aliases=("avatar_mod", "pfp_mod")) + @commands.group(aliases=("avatar_mod", "pfp_mod", "avatarmod", "pfpmod")) async def avatar_modify(self, ctx: commands.Context) -> None: """Groups all of the pfp modifying commands to allow a single concurrency limit.""" if not ctx.invoked_subcommand: -- cgit v1.2.3 From 477a4d7e095c2cb5ca51339b3e75b287338e3e92 Mon Sep 17 00:00:00 2001 From: vcokltfre Date: Fri, 30 Apr 2021 17:32:53 +0100 Subject: fix: remove () from list of safe filename chars --- bot/exts/evergreen/avatar_modification/avatar_modify.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/avatar_modification/avatar_modify.py b/bot/exts/evergreen/avatar_modification/avatar_modify.py index a111c480..0baee8b2 100644 --- a/bot/exts/evergreen/avatar_modification/avatar_modify.py +++ b/bot/exts/evergreen/avatar_modification/avatar_modify.py @@ -40,7 +40,7 @@ async def in_executor(func: t.Callable, *args) -> t.Any: def file_safe_name(effect: str, display_name: str) -> str: """Returns a file safe filename based on the given effect and display name.""" - valid_filename_chars = f"-_.() {string.ascii_letters}{string.digits}" + valid_filename_chars = f"-_. {string.ascii_letters}{string.digits}" file_name = FILENAME_STRING.format(effect=effect, author=display_name) -- cgit v1.2.3 From cf110bc4c8b0713291174ac7cfb3730d0ff226a7 Mon Sep 17 00:00:00 2001 From: ToxicKidz <78174417+ToxicKidz@users.noreply.github.com> Date: Fri, 30 Apr 2021 14:08:14 -0400 Subject: feat: Add the .mosaic command --- bot/exts/evergreen/avatar_modification/_effects.py | 158 ++++++++++++++++++++- .../evergreen/avatar_modification/avatar_modify.py | 50 ++++++- 2 files changed, 202 insertions(+), 6 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/avatar_modification/_effects.py b/bot/exts/evergreen/avatar_modification/_effects.py index e415d700..d2370b4b 100644 --- a/bot/exts/evergreen/avatar_modification/_effects.py +++ b/bot/exts/evergreen/avatar_modification/_effects.py @@ -1,3 +1,5 @@ +import math +import random import typing as t from io import BytesIO from pathlib import Path @@ -51,7 +53,7 @@ class PfpEffects: return r, g, b @staticmethod - def crop_avatar_circle(avatar: Image) -> Image: + def crop_avatar_circle(avatar: Image.Image) -> Image.Image: """This crops the avatar given into a circle.""" mask = Image.new("L", avatar.size, 0) draw = ImageDraw.Draw(mask) @@ -60,7 +62,7 @@ class PfpEffects: return avatar @staticmethod - def crop_ring(ring: Image, px: int) -> Image: + def crop_ring(ring: Image.Image, px: int) -> Image.Image: """This crops the given ring into a circle.""" mask = Image.new("L", ring.size, 0) draw = ImageDraw.Draw(mask) @@ -70,7 +72,7 @@ class PfpEffects: return ring @staticmethod - def pridify_effect(image: Image, pixels: int, flag: str) -> Image: + def pridify_effect(image: Image.Image, pixels: int, flag: str) -> Image.Image: """Applies the given pride effect to the given image.""" image = image.resize((1024, 1024)) image = PfpEffects.crop_avatar_circle(image) @@ -83,7 +85,7 @@ class PfpEffects: return image @staticmethod - def eight_bitify_effect(image: Image) -> Image: + def eight_bitify_effect(image: Image.Image) -> Image.Image: """ Applies the 8bit effect to the given image. @@ -95,7 +97,7 @@ class PfpEffects: return image.quantize() @staticmethod - def easterify_effect(image: Image, overlay_image: Image = None) -> Image: + def easterify_effect(image: Image.Image, overlay_image: t.Optional[Image.Image] = None) -> Image.Image: """ Applies the easter effect to the given image. @@ -137,3 +139,149 @@ class PfpEffects: (im.width - overlay_image.width, (im.height - overlay_image.height) // 2) ) return im + + @staticmethod + def split_image(img: Image.Image, squares: int) -> list: + """ + Split an image into a selection of squares, specified by the squares argument. + + Explanation: + + 1. It gets the width and the height of the Image passed to the function. + + 2. It gets the root of a number of squares (number of squares) passed, which is called xy. Reason: if let's say + 25 squares (number of squares) were passed, that is the total squares (split pieces) that the image is supposed + to be split into. As it is known, a 2D shape has a height and a width, and in this case the program thinks of it + as rows and columns. Rows multiplied by columns is equal to the passed squares (number of squares). To get rows + and columns, since in this case, each square (split piece) is identical, rows are equal to columns and the + program treats the image as a square-shaped, it gets the root out of the squares (number of squares) passed. + + 3. Now width and height are both of the original Image, Discord PFP, so when it comes to forming the squares, + the program divides the original image's height and width by the xy. In a case of 25 squares (number of squares) + passed, xy would be 5, so if an image was 250x300, x_frac would be 50 and y_frac - 60. Note: + x_frac stands for a fracture of width. The reason it's called that is because it is shorter to use x for width + in mind and then it's just half of the word fracture, same applies to y_frac, just height instead of width. + x_frac and y_frac are width and height of a single square (split piece). + + 4. With left, top, right, bottom, = 0, 0, x_frac, y_frac, the program sets these variables to create the initial + square (split piece). Explanation: all of these 4 variables start at the top left corner of the Image, by adding + value to right and bottom, it's creating the initial square (split piece). + + 5. In the for loop, it keeps adding those squares (split pieces) in a row and once (index + 1) % xy == 0 is + True, it adds to top and bottom to lower them and reset right and left to recreate the initial space between + them, forming a square (split piece), it also adds the newly created square (split piece) into the new_imgs list + where it stores them. The program keeps repeating this process till all 25 squares get added to the list. + + 6. It returns new_imgs, a list of squares (split pieces). + """ + width, heigth = img.size + + xy = math.sqrt(squares) + + x_frac = width // xy + y_frac = heigth // xy + + left, top, right, bottom, = 0, 0, x_frac, y_frac + + new_imgs = [] + + for index in range(squares): + new_img = img.crop((left, top, right, bottom)) + new_imgs.append(new_img) + + if (index + 1) % xy == 0: + top += y_frac + bottom += y_frac + left = 0 + right = x_frac + else: + left += x_frac + right += x_frac + + return new_imgs + + @staticmethod + def join_images(images: t.List[Image.Image]) -> Image.Image: + """ + Stitches all the image squares into a new image. + + Explanation: + + 1. Shuffles the passed images to randomize the pieces. + + 2. The program gets a single square (split piece) out of the list and defines single_width as the square's width + and single_height as the square's height. + + 3. It gets the root of type integer of the number of images (split pieces) in the list and calls it multiplier. + Program then proceeds to calculate total height and width of the new image that it's creating using the same + multiplier. + + 4. The program then defines new_image as the image that it's creating, using the previously obtained total_width + and total_height. + + 5. Now it defines width_multiplier as well as height with values of 0. These will be used to correctly position + squares (split pieces) onto the new_image canvas. + + 6. Similar to how in the split_image function, the program gets the root of number of images in the list. + In split_image function, it was the passed squares (number of squares) instead of a number of imgs in the + list that it got the square of here. + + 7. In the for loop, as it iterates, the program multiplies single_width by width_multiplier to correctly + position a square (split piece) width wise. It then proceeds to paste the newly positioned square (split piece) + onto the new_image. The program increases the width_multiplier by 1 every iteration so the image wouldn't get + pasted in the same spot and the positioning would move accordingly. It makes sure to increase the + width_multiplier before the check, which checks if the end of a row has been reached, - + (index + 1) % pieces == 0, so after it, if it was True, width_multiplier would have been reset to 0 (start of + the row). If the check returns True, the height gets increased by a single square's (split piece) height to + lower the positioning height wise and, as mentioned, the width_multiplier gets reset to 0 and width will + then be calculated from the start of the new row. The for loop finishes once all the squares (split pieces) were + positioned accordingly. + + 8. Finally, it returns the new_image, the randomized squares (split pieces) stitched back into the format of the + original image - user's PFP. + """ + random.shuffle(images) + single_img = images[0] + + single_wdith = single_img.size[0] + single_height = single_img.size[1] + + multiplier = int(math.sqrt(len(images))) + + total_width = multiplier * single_wdith + total_height = multiplier * single_height + + new_image = Image.new('RGBA', (total_width, total_height), (250, 250, 250)) + + width_multiplier = 0 + height = 0 + + squares = math.sqrt(len(images)) + + for index, image in enumerate(images): + width = single_wdith * width_multiplier + + new_image.paste(image, (width, height)) + + width_multiplier += 1 + + if (index + 1) % squares == 0: + width_multiplier = 0 + height += single_height + + return new_image + + @staticmethod + def mosaic_effect(img_bytes: bytes, squares: int, file_name: str) -> discord.File: + """Seperate function run from an executor which turns an image into a mosaic.""" + avatar = Image.open(BytesIO(img_bytes)) + avatar = avatar.convert('RGBA').resize((1024, 1024)) + + img_squares = PfpEffects.split_image(avatar, squares) + new_img = PfpEffects.join_images(img_squares) + + bufferedio = BytesIO() + new_img.save(bufferedio, format='PNG') + bufferedio.seek(0) + + return discord.File(bufferedio, filename=file_name) diff --git a/bot/exts/evergreen/avatar_modification/avatar_modify.py b/bot/exts/evergreen/avatar_modification/avatar_modify.py index 0baee8b2..afff125f 100644 --- a/bot/exts/evergreen/avatar_modification/avatar_modify.py +++ b/bot/exts/evergreen/avatar_modification/avatar_modify.py @@ -1,6 +1,7 @@ import asyncio import json import logging +import math import string import typing as t import unicodedata @@ -22,11 +23,15 @@ _EXECUTOR = ThreadPoolExecutor(10) FILENAME_STRING = "{effect}_{author}.png" +MAX_SQUARES = 10_000 + +T = t.TypeVar("T") + with open("bot/resources/pride/gender_options.json") as f: GENDER_OPTIONS = json.load(f) -async def in_executor(func: t.Callable, *args) -> t.Any: +async def in_executor(func: t.Callable[..., T], *args) -> T: """ Runs the given synchronus function `func` in an executor. @@ -308,6 +313,49 @@ class AvatarModify(commands.Cog): await ctx.send(file=file, embed=embed) + @avatar_modify.command(name='mosaic', root_aliases=('mosaic',)) + async def mosaic_command(self, ctx: commands.Context, squares: int = 16) -> None: + """Splits your avatar into x squares, randomizes them and stitches them back into a new image!""" + async with ctx.typing(): + if squares < 1: + raise commands.BadArgument("Squares must be a positive number") + + if not math.sqrt(squares).is_integer(): + raise commands.BadArgument("Squares must be a perfect square") + + if squares > MAX_SQUARES: + raise commands.BadArgument(f"Number of squares cannot be higher than {MAX_SQUARES:,}.") + + file_name = file_safe_name("mosaic_avatar", ctx.author.display_name) + img_bytes = await ctx.author.avatar_url.read() + + file = await in_executor( + PfpEffects.mosaic_effect, + img_bytes, + squares, + file_name + ) + + if squares == 1: + title = 'Hooh... that was a lot of work' + description = 'I present to you... Yourself!' + elif squares == MAX_SQUARES: + title = 'Testing the limits I see...' + description = 'What a masterpiece. :star:' + else: + title = 'Your mosaic avatar' + description = 'Here is your avatar. I think it looks a bit *puzzling*' + + embed = discord.Embed( + title=title, + description=description + ) + + embed.set_image(url=f'attachment://{file_name}') + embed.set_footer(text=f'Made by {ctx.author.display_name}', icon_url=ctx.author.avatar_url) + + await ctx.send(file=file, embed=embed) + def setup(bot: commands.Bot) -> None: """Load the PfpModify cog.""" -- cgit v1.2.3 From a1aa1a8ff0e9970cbb492282d10b6cb73aa17b28 Mon Sep 17 00:00:00 2001 From: ToxicKidz <78174417+ToxicKidz@users.noreply.github.com> Date: Mon, 3 May 2021 12:28:47 -0400 Subject: fix: Don't pass bot to __init__ --- bot/exts/pride/drag_queen_name.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/pride/drag_queen_name.py b/bot/exts/pride/drag_queen_name.py index 32ead1bc..9839f089 100644 --- a/bot/exts/pride/drag_queen_name.py +++ b/bot/exts/pride/drag_queen_name.py @@ -30,4 +30,4 @@ class DragNames(commands.Cog): def setup(bot: Bot) -> None: """Load the Drag Queen Cog.""" - bot.add_cog(DragNames(bot)) + bot.add_cog(DragNames()) -- cgit v1.2.3 From 5838d291ff56bac0aaf7a0cfc669c065ddda50b1 Mon Sep 17 00:00:00 2001 From: ToxicKidz <78174417+ToxicKidz@users.noreply.github.com> Date: Mon, 3 May 2021 13:18:25 -0400 Subject: chore: Clean Up the Valentines Season - Change commands.Bot type hints to bot.Bot - Remove the setting of Cog.bot if it isn't being used - Change ctx.author -> ctx.message.author --- bot/exts/valentines/be_my_valentine.py | 11 ++++++----- bot/exts/valentines/lovecalculator.py | 9 ++++----- bot/exts/valentines/movie_generator.py | 4 +++- bot/exts/valentines/myvalenstate.py | 7 ++++--- bot/exts/valentines/pickuplines.py | 8 +++----- bot/exts/valentines/savethedate.py | 8 +++----- bot/exts/valentines/valentine_zodiac.py | 8 ++++---- bot/exts/valentines/whoisvalentine.py | 8 +++----- 8 files changed, 30 insertions(+), 33 deletions(-) (limited to 'bot') diff --git a/bot/exts/valentines/be_my_valentine.py b/bot/exts/valentines/be_my_valentine.py index 09591cf8..051f09b8 100644 --- a/bot/exts/valentines/be_my_valentine.py +++ b/bot/exts/valentines/be_my_valentine.py @@ -8,6 +8,7 @@ import discord from discord.ext import commands from discord.ext.commands.cooldowns import BucketType +from bot.bot import Bot from bot.constants import Channels, Colours, Lovefest, Month from bot.utils.decorators import in_month from bot.utils.extensions import invoke_help_command @@ -50,7 +51,7 @@ class BeMyValentine(commands.Cog): async def add_role(self, ctx: commands.Context) -> None: """Adds the lovefest role.""" user = ctx.author - role = discord.utils.get(ctx.guild.roles, id=Lovefest.role_id) + role = ctx.guild.get_role(Lovefest.role_id) if Lovefest.role_id not in [role.id for role in ctx.message.author.roles]: await user.add_roles(role) await ctx.send("The Lovefest role has been added !") @@ -61,12 +62,12 @@ class BeMyValentine(commands.Cog): async def remove_role(self, ctx: commands.Context) -> None: """Removes the lovefest role.""" user = ctx.author - role = discord.utils.get(ctx.guild.roles, id=Lovefest.role_id) - if Lovefest.role_id not in [role.id for role in ctx.message.author.roles]: + role = ctx.guild.get_role(Lovefest.role_id) + if role not in ctx.author.roles: await ctx.send("You dont have the lovefest role.") else: await user.remove_roles(role) - await ctx.send("The lovefest role has been successfully removed !") + await ctx.send("The lovefest role has been successfully removed!") @commands.cooldown(1, 1800, BucketType.user) @commands.group(name='bemyvalentine', invoke_without_command=True) @@ -196,6 +197,6 @@ class BeMyValentine(commands.Cog): return valentine_compliment -def setup(bot: commands.Bot) -> None: +def setup(bot: Bot) -> None: """Be my Valentine Cog load.""" bot.add_cog(BeMyValentine(bot)) diff --git a/bot/exts/valentines/lovecalculator.py b/bot/exts/valentines/lovecalculator.py index 966acc82..c2a5da26 100644 --- a/bot/exts/valentines/lovecalculator.py +++ b/bot/exts/valentines/lovecalculator.py @@ -11,6 +11,8 @@ from discord import Member from discord.ext import commands from discord.ext.commands import BadArgument, Cog, clean_content +from bot.bot import Bot + log = logging.getLogger(__name__) with Path("bot/resources/valentines/love_matches.json").open(encoding="utf8") as file: @@ -21,9 +23,6 @@ with Path("bot/resources/valentines/love_matches.json").open(encoding="utf8") as class LoveCalculator(Cog): """A cog for calculating the love between two people.""" - def __init__(self, bot: commands.Bot): - self.bot = bot - @commands.command(aliases=('love_calculator', 'love_calc')) @commands.cooldown(rate=1, per=5, type=commands.BucketType.user) async def love(self, ctx: commands.Context, who: Union[Member, str], whom: Union[Member, str] = None) -> None: @@ -93,6 +92,6 @@ class LoveCalculator(Cog): await ctx.send(embed=embed) -def setup(bot: commands.Bot) -> None: +def setup(bot: Bot) -> None: """Love calculator Cog load.""" - bot.add_cog(LoveCalculator(bot)) + bot.add_cog(LoveCalculator()) diff --git a/bot/exts/valentines/movie_generator.py b/bot/exts/valentines/movie_generator.py index 4df9e0d5..461255ff 100644 --- a/bot/exts/valentines/movie_generator.py +++ b/bot/exts/valentines/movie_generator.py @@ -6,6 +6,8 @@ from urllib import parse import discord from discord.ext import commands +from bot.bot import Bot + TMDB_API_KEY = environ.get("TMDB_API_KEY") log = logging.getLogger(__name__) @@ -59,6 +61,6 @@ class RomanceMovieFinder(commands.Cog): await ctx.send(embed=embed) -def setup(bot: commands.Bot) -> None: +def setup(bot: Bot) -> None: """Romance movie Cog load.""" bot.add_cog(RomanceMovieFinder(bot)) diff --git a/bot/exts/valentines/myvalenstate.py b/bot/exts/valentines/myvalenstate.py index 041af7af..19e6b57f 100644 --- a/bot/exts/valentines/myvalenstate.py +++ b/bot/exts/valentines/myvalenstate.py @@ -7,6 +7,7 @@ from random import choice import discord from discord.ext import commands +from bot.bot import Bot from bot.constants import Colours log = logging.getLogger(__name__) @@ -46,7 +47,7 @@ class MyValenstate(commands.Cog): """Find the vacation spot(s) with the most matching characters to the invoking user.""" eq_chars = collections.defaultdict(int) if name is None: - author = ctx.message.author.name.lower().replace(' ', '') + author = ctx.author.name.lower().replace(' ', '') else: author = name.lower().replace(' ', '') @@ -60,7 +61,7 @@ class MyValenstate(commands.Cog): embed_title = "But there are more!" if len(matches) > 1: - leftovers = f"{', '.join(matches[:len(matches)-2])}, and {matches[len(matches)-1]}" + leftovers = f"{', '.join(matches[:-2])}, and {matches[-1]}" embed_text = f"You have {len(matches)} more matches, these being {leftovers}." elif len(matches) == 1: embed_title = "But there's another one!" @@ -81,6 +82,6 @@ class MyValenstate(commands.Cog): await ctx.send(embed=embed) -def setup(bot: commands.Bot) -> None: +def setup(bot: Bot) -> None: """Valenstate Cog load.""" bot.add_cog(MyValenstate(bot)) diff --git a/bot/exts/valentines/pickuplines.py b/bot/exts/valentines/pickuplines.py index 74c7e68b..bb322016 100644 --- a/bot/exts/valentines/pickuplines.py +++ b/bot/exts/valentines/pickuplines.py @@ -6,6 +6,7 @@ from pathlib import Path import discord from discord.ext import commands +from bot.bot import Bot from bot.constants import Colours log = logging.getLogger(__name__) @@ -17,9 +18,6 @@ with open(Path("bot/resources/valentines/pickup_lines.json"), "r", encoding="utf class PickupLine(commands.Cog): """A cog that gives random cheesy pickup lines.""" - def __init__(self, bot: commands.Bot): - self.bot = bot - @commands.command() async def pickupline(self, ctx: commands.Context) -> None: """ @@ -39,6 +37,6 @@ class PickupLine(commands.Cog): await ctx.send(embed=embed) -def setup(bot: commands.Bot) -> None: +def setup(bot: Bot) -> None: """Pickup lines Cog load.""" - bot.add_cog(PickupLine(bot)) + bot.add_cog(PickupLine()) diff --git a/bot/exts/valentines/savethedate.py b/bot/exts/valentines/savethedate.py index ac38d279..bda5d8c6 100644 --- a/bot/exts/valentines/savethedate.py +++ b/bot/exts/valentines/savethedate.py @@ -6,6 +6,7 @@ from pathlib import Path import discord from discord.ext import commands +from bot.bot import Bot from bot.constants import Colours log = logging.getLogger(__name__) @@ -19,9 +20,6 @@ with open(Path("bot/resources/valentines/date_ideas.json"), "r", encoding="utf8" class SaveTheDate(commands.Cog): """A cog that gives random suggestion for a Valentine's date.""" - def __init__(self, bot: commands.Bot): - self.bot = bot - @commands.command() async def savethedate(self, ctx: commands.Context) -> None: """Gives you ideas for what to do on a date with your valentine.""" @@ -36,6 +34,6 @@ class SaveTheDate(commands.Cog): await ctx.send(embed=embed) -def setup(bot: commands.Bot) -> None: +def setup(bot: Bot) -> None: """Save the date Cog Load.""" - bot.add_cog(SaveTheDate(bot)) + bot.add_cog(SaveTheDate()) diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index 2696999f..237fe5db 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -9,6 +9,7 @@ from typing import Tuple, Union import discord from discord.ext import commands +from bot.bot import Bot from bot.constants import Colours log = logging.getLogger(__name__) @@ -20,8 +21,7 @@ HEART_EMOJIS = [":heart:", ":gift_heart:", ":revolving_hearts:", ":sparkling_hea class ValentineZodiac(commands.Cog): """A Cog that returns a counter compatible zodiac sign to the given user's zodiac sign.""" - def __init__(self, bot: commands.Bot): - self.bot = bot + def __init__(self): self.zodiacs, self.zodiac_fact = self.load_comp_json() @staticmethod @@ -141,6 +141,6 @@ class ValentineZodiac(commands.Cog): log.trace("Embed from date successfully sent.") -def setup(bot: commands.Bot) -> None: +def setup(bot: Bot) -> None: """Valentine zodiac Cog load.""" - bot.add_cog(ValentineZodiac(bot)) + bot.add_cog(ValentineZodiac()) diff --git a/bot/exts/valentines/whoisvalentine.py b/bot/exts/valentines/whoisvalentine.py index ab6b1ca1..73cdcf52 100644 --- a/bot/exts/valentines/whoisvalentine.py +++ b/bot/exts/valentines/whoisvalentine.py @@ -6,6 +6,7 @@ from random import choice import discord from discord.ext import commands +from bot.bot import Bot from bot.constants import Colours log = logging.getLogger(__name__) @@ -17,9 +18,6 @@ with open(Path("bot/resources/valentines/valentine_facts.json"), "r", encoding=" class ValentineFacts(commands.Cog): """A Cog for displaying facts about Saint Valentine.""" - def __init__(self, bot: commands.Bot): - self.bot = bot - @commands.command(aliases=('whoisvalentine', 'saint_valentine')) async def who_is_valentine(self, ctx: commands.Context) -> None: """Displays info about Saint Valentine.""" @@ -47,6 +45,6 @@ class ValentineFacts(commands.Cog): await ctx.send(embed=embed) -def setup(bot: commands.Bot) -> None: +def setup(bot: Bot) -> None: """Who is Valentine Cog load.""" - bot.add_cog(ValentineFacts(bot)) + bot.add_cog(ValentineFacts()) -- cgit v1.2.3 From e9f33306b5344c5d7e0877b0bf63ff9c914e6f85 Mon Sep 17 00:00:00 2001 From: ToxicKidz <78174417+ToxicKidz@users.noreply.github.com> Date: Mon, 3 May 2021 13:36:26 -0400 Subject: Clean Up the Easter Season - Keep Consistent setup docstrings - Type hint to bot.Bot instead of commands.Bot - Don't set Cog.bot if it isn't used --- bot/exts/easter/april_fools_vids.py | 8 +++++--- bot/exts/easter/bunny_name_generator.py | 9 ++++----- bot/exts/easter/earth_photos.py | 5 +++-- bot/exts/easter/easter_riddle.py | 5 +++-- bot/exts/easter/egg_decorating.py | 8 +++----- bot/exts/easter/egg_facts.py | 2 +- bot/exts/easter/egghead_quiz.py | 10 +++++----- bot/exts/easter/save_the_planet.py | 10 ++++------ bot/exts/easter/traditions.py | 11 +++++------ 9 files changed, 33 insertions(+), 35 deletions(-) (limited to 'bot') diff --git a/bot/exts/easter/april_fools_vids.py b/bot/exts/easter/april_fools_vids.py index c7a3c014..84aa2913 100644 --- a/bot/exts/easter/april_fools_vids.py +++ b/bot/exts/easter/april_fools_vids.py @@ -4,6 +4,8 @@ from json import load from discord.ext import commands +from bot.bot import Bot + log = logging.getLogger(__name__) with open("bot/resources/easter/april_fools_vids.json", encoding="utf-8") as f: @@ -23,6 +25,6 @@ class AprilFoolVideos(commands.Cog): await ctx.send(f"Check out this April Fools' video by {channel}.\n\n{url}") -def setup(bot: commands.Bot) -> None: - """April Fools' Cog load.""" - bot.add_cog(AprilFoolVideos(bot)) +def setup(bot: Bot) -> None: + """Load the April Fools' Cog.""" + bot.add_cog(AprilFoolVideos()) diff --git a/bot/exts/easter/bunny_name_generator.py b/bot/exts/easter/bunny_name_generator.py index 3ecf9be9..6d9e2a57 100644 --- a/bot/exts/easter/bunny_name_generator.py +++ b/bot/exts/easter/bunny_name_generator.py @@ -7,6 +7,8 @@ from typing import List, Union from discord.ext import commands +from bot.bot import Bot + log = logging.getLogger(__name__) with Path("bot/resources/easter/bunny_names.json").open("r", encoding="utf8") as f: @@ -16,9 +18,6 @@ with Path("bot/resources/easter/bunny_names.json").open("r", encoding="utf8") as class BunnyNameGenerator(commands.Cog): """Generate a random bunny name, or bunnify your Discord username!""" - def __init__(self, bot: commands.Bot): - self.bot = bot - def find_separators(self, displayname: str) -> Union[List[str], None]: """Check if Discord name contains spaces so we can bunnify an individual word in the name.""" new_name = re.split(r'[_.\s]', displayname) @@ -87,6 +86,6 @@ class BunnyNameGenerator(commands.Cog): await ctx.send(bunnified_name) -def setup(bot: commands.Bot) -> None: - """Bunny Name Generator Cog load.""" +def setup(bot: Bot) -> None: + """Load the Bunny Name Generator Cog.""" bot.add_cog(BunnyNameGenerator(bot)) diff --git a/bot/exts/easter/earth_photos.py b/bot/exts/easter/earth_photos.py index bf658391..d7e7ccc3 100644 --- a/bot/exts/easter/earth_photos.py +++ b/bot/exts/easter/earth_photos.py @@ -3,6 +3,7 @@ import logging import discord from discord.ext import commands +from bot.bot import Bot from bot.constants import Colours from bot.constants import Tokens @@ -12,7 +13,7 @@ log = logging.getLogger(__name__) class EarthPhotos(commands.Cog): """This cog contains the command for earth photos.""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Bot): self.bot = bot @commands.command(aliases=["earth"]) @@ -55,7 +56,7 @@ class EarthPhotos(commands.Cog): await ctx.send(embed=embed) -def setup(bot: commands.Bot) -> None: +def setup(bot: Bot) -> None: """Load the Earth Photos cog.""" if not Tokens.unsplash_access_key: log.warning("No Unsplash access key found. Cog not loading.") diff --git a/bot/exts/easter/easter_riddle.py b/bot/exts/easter/easter_riddle.py index a93b3745..da66edf5 100644 --- a/bot/exts/easter/easter_riddle.py +++ b/bot/exts/easter/easter_riddle.py @@ -7,6 +7,7 @@ from pathlib import Path import discord from discord.ext import commands +from bot.bot import Bot from bot.constants import Colours, NEGATIVE_REPLIES log = logging.getLogger(__name__) @@ -20,7 +21,7 @@ TIMELIMIT = 10 class EasterRiddle(commands.Cog): """This cog contains the command for the Easter quiz!""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Bot): self.bot = bot self.winners = set() self.correct = "" @@ -106,6 +107,6 @@ class EasterRiddle(commands.Cog): self.winners.add(message.author.mention) -def setup(bot: commands.Bot) -> None: +def setup(bot: Bot) -> None: """Easter Riddle Cog load.""" bot.add_cog(EasterRiddle(bot)) diff --git a/bot/exts/easter/egg_decorating.py b/bot/exts/easter/egg_decorating.py index a847388d..b8a8c6a7 100644 --- a/bot/exts/easter/egg_decorating.py +++ b/bot/exts/easter/egg_decorating.py @@ -10,6 +10,7 @@ import discord from PIL import Image from discord.ext import commands +from bot.bot import Bot from bot.utils import helpers log = logging.getLogger(__name__) @@ -33,9 +34,6 @@ IRREPLACEABLE = [ class EggDecorating(commands.Cog): """Decorate some easter eggs!""" - def __init__(self, bot: commands.Bot) -> None: - self.bot = bot - @staticmethod def replace_invalid(colour: str) -> Union[int, None]: """Attempts to match with HTML or XKCD colour names, returning the int value.""" @@ -115,6 +113,6 @@ class EggDecorating(commands.Cog): return new_im -def setup(bot: commands.bot) -> None: - """Egg decorating Cog load.""" +def setup(bot: Bot) -> None: + """Load the Egg decorating Cog.""" bot.add_cog(EggDecorating(bot)) diff --git a/bot/exts/easter/egg_facts.py b/bot/exts/easter/egg_facts.py index 761e9059..78a5e592 100644 --- a/bot/exts/easter/egg_facts.py +++ b/bot/exts/easter/egg_facts.py @@ -57,5 +57,5 @@ class EasterFacts(commands.Cog): def setup(bot: Bot) -> None: - """Easter Egg facts cog load.""" + """Load the Easter Egg facts Cog.""" bot.add_cog(EasterFacts(bot)) diff --git a/bot/exts/easter/egghead_quiz.py b/bot/exts/easter/egghead_quiz.py index 0498d9db..e950bc2e 100644 --- a/bot/exts/easter/egghead_quiz.py +++ b/bot/exts/easter/egghead_quiz.py @@ -8,6 +8,7 @@ from typing import Union import discord from discord.ext import commands +from bot.bot import Bot from bot.constants import Colours log = logging.getLogger(__name__) @@ -31,8 +32,7 @@ TIMELIMIT = 30 class EggheadQuiz(commands.Cog): """This cog contains the command for the Easter quiz!""" - def __init__(self, bot: commands.Bot) -> None: - self.bot = bot + def __init__(self) -> None: self.quiz_messages = {} @commands.command(aliases=["eggheadquiz", "easterquiz"]) @@ -114,6 +114,6 @@ class EggheadQuiz(commands.Cog): return await reaction.message.remove_reaction(reaction, user) -def setup(bot: commands.Bot) -> None: - """Egghead Quiz Cog load.""" - bot.add_cog(EggheadQuiz(bot)) +def setup(bot: Bot) -> None: + """Load the Egghead Quiz Cog.""" + bot.add_cog(EggheadQuiz()) diff --git a/bot/exts/easter/save_the_planet.py b/bot/exts/easter/save_the_planet.py index 8f644259..db9d3498 100644 --- a/bot/exts/easter/save_the_planet.py +++ b/bot/exts/easter/save_the_planet.py @@ -4,6 +4,7 @@ from pathlib import Path from discord import Embed from discord.ext import commands +from bot.bot import Bot from bot.utils.randomization import RandomCycle @@ -14,9 +15,6 @@ with Path("bot/resources/easter/save_the_planet.json").open('r', encoding='utf8' class SaveThePlanet(commands.Cog): """A cog that teaches users how they can help our planet.""" - def __init__(self, bot: commands.Bot) -> None: - self.bot = bot - @commands.command(aliases=('savetheearth', 'saveplanet', 'saveearth')) async def savetheplanet(self, ctx: commands.Context) -> None: """Responds with a random tip on how to be eco-friendly and help our planet.""" @@ -24,6 +22,6 @@ class SaveThePlanet(commands.Cog): await ctx.send(embed=return_embed) -def setup(bot: commands.Bot) -> None: - """Save the Planet Cog load.""" - bot.add_cog(SaveThePlanet(bot)) +def setup(bot: Bot) -> None: + """Load the Save the Planet Cog.""" + bot.add_cog(SaveThePlanet()) diff --git a/bot/exts/easter/traditions.py b/bot/exts/easter/traditions.py index 85b4adfb..19e69b98 100644 --- a/bot/exts/easter/traditions.py +++ b/bot/exts/easter/traditions.py @@ -5,6 +5,8 @@ from pathlib import Path from discord.ext import commands +from bot.bot import Bot + log = logging.getLogger(__name__) with open(Path("bot/resources/easter/traditions.json"), "r", encoding="utf8") as f: @@ -14,9 +16,6 @@ with open(Path("bot/resources/easter/traditions.json"), "r", encoding="utf8") as class Traditions(commands.Cog): """A cog which allows users to get a random easter tradition or custom from a random country.""" - def __init__(self, bot: commands.Bot): - self.bot = bot - @commands.command(aliases=('eastercustoms',)) async def easter_tradition(self, ctx: commands.Context) -> None: """Responds with a random tradition or custom.""" @@ -25,6 +24,6 @@ class Traditions(commands.Cog): await ctx.send(f"{random_country}:\n{traditions[random_country]}") -def setup(bot: commands.Bot) -> None: - """Traditions Cog load.""" - bot.add_cog(Traditions(bot)) +def setup(bot: Bot) -> None: + """Load the Traditions Cog.""" + bot.add_cog(Traditions()) -- cgit v1.2.3 From 5189259ea3332bffa61863452f7f07bec0f4b533 Mon Sep 17 00:00:00 2001 From: ToxicKidz <78174417+ToxicKidz@users.noreply.github.com> Date: Mon, 3 May 2021 14:06:05 -0400 Subject: chore: End the sentence with a period --- bot/exts/evergreen/battleship.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/battleship.py b/bot/exts/evergreen/battleship.py index 813f998e..78fb0937 100644 --- a/bot/exts/evergreen/battleship.py +++ b/bot/exts/evergreen/battleship.py @@ -440,5 +440,5 @@ class Battleship(commands.Cog): def setup(bot: Bot) -> None: - """Load the Battleship cog""" + """Load the Battleship Cog.""" bot.add_cog(Battleship(bot)) -- cgit v1.2.3 From a7279c624b093ffe826edadbc999103083710953 Mon Sep 17 00:00:00 2001 From: ToxicKidz <78174417+ToxicKidz@users.noreply.github.com> Date: Mon, 3 May 2021 19:23:52 -0400 Subject: chore: Don't return a Message object when the return annotation is None --- bot/exts/easter/easter_riddle.py | 3 ++- bot/exts/easter/egg_decorating.py | 9 ++++++--- bot/exts/evergreen/battleship.py | 12 ++++++++---- bot/exts/evergreen/reddit.py | 9 ++++++--- bot/exts/evergreen/snakes/_snakes_cog.py | 6 ++++-- bot/exts/evergreen/trivia_quiz.py | 3 ++- bot/utils/pagination.py | 10 ++++++---- 7 files changed, 34 insertions(+), 18 deletions(-) (limited to 'bot') diff --git a/bot/exts/easter/easter_riddle.py b/bot/exts/easter/easter_riddle.py index da66edf5..9a253a6a 100644 --- a/bot/exts/easter/easter_riddle.py +++ b/bot/exts/easter/easter_riddle.py @@ -35,7 +35,8 @@ class EasterRiddle(commands.Cog): The duration of the hint interval can be configured by changing the TIMELIMIT constant in this file. """ if self.current_channel: - return await ctx.send(f"A riddle is already being solved in {self.current_channel.mention}!") + await ctx.send(f"A riddle is already being solved in {self.current_channel.mention}!") + return # Don't let users start in a DM if not ctx.guild: diff --git a/bot/exts/easter/egg_decorating.py b/bot/exts/easter/egg_decorating.py index b8a8c6a7..1432fa31 100644 --- a/bot/exts/easter/egg_decorating.py +++ b/bot/exts/easter/egg_decorating.py @@ -54,7 +54,8 @@ class EggDecorating(commands.Cog): Discord colour names, HTML colour names, XKCD colour names and hex values are accepted. """ if len(colours) < 2: - return await ctx.send("You must include at least 2 colours!") + await ctx.send("You must include at least 2 colours!") + return invalid = [] colours = list(colours) @@ -68,9 +69,11 @@ class EggDecorating(commands.Cog): invalid.append(helpers.suppress_links(colour)) if len(invalid) > 1: - return await ctx.send(f"Sorry, I don't know these colours: {' '.join(invalid)}") + await ctx.send(f"Sorry, I don't know these colours: {' '.join(invalid)}") + return elif len(invalid) == 1: - return await ctx.send(f"Sorry, I don't know the colour {invalid[0]}!") + await ctx.send(f"Sorry, I don't know the colour {invalid[0]}!") + return async with ctx.typing(): # Expand list to 8 colours diff --git a/bot/exts/evergreen/battleship.py b/bot/exts/evergreen/battleship.py index 78fb0937..d4584ae8 100644 --- a/bot/exts/evergreen/battleship.py +++ b/bot/exts/evergreen/battleship.py @@ -379,10 +379,12 @@ class Battleship(commands.Cog): Make sure you have your DMs open so that the bot can message you. """ if self.already_playing(ctx.author): - return await ctx.send("You're already playing a game!") + await ctx.send("You're already playing a game!") + return if ctx.author in self.waiting: - return await ctx.send("You've already sent out a request for a player 2.") + await ctx.send("You've already sent out a request for a player 2.") + return announcement = await ctx.send( "**Battleship**: A new game is about to start!\n" @@ -402,12 +404,14 @@ class Battleship(commands.Cog): except asyncio.TimeoutError: self.waiting.remove(ctx.author) await announcement.delete() - return await ctx.send(f"{ctx.author.mention} Seems like there's no one here to play...") + await ctx.send(f"{ctx.author.mention} Seems like there's no one here to play...") + return if str(reaction.emoji) == CROSS_EMOJI: self.waiting.remove(ctx.author) await announcement.delete() - return await ctx.send(f"{ctx.author.mention} Game cancelled.") + await ctx.send(f"{ctx.author.mention} Game cancelled.") + return await announcement.delete() self.waiting.remove(ctx.author) diff --git a/bot/exts/evergreen/reddit.py b/bot/exts/evergreen/reddit.py index 518ffeb7..51a360b3 100644 --- a/bot/exts/evergreen/reddit.py +++ b/bot/exts/evergreen/reddit.py @@ -51,15 +51,18 @@ class Reddit(commands.Cog): try: posts = data["data"]["children"] except KeyError: - return await ctx.send('Subreddit not found!') + await ctx.send('Subreddit not found!') + return if not posts: - return await ctx.send('No posts available!') + await ctx.send('No posts available!') + return if posts[0]["data"]["over_18"] is True: - return await ctx.send( + await ctx.send( "You cannot access this Subreddit as it is meant for those who " "are 18 years or older." ) + return embed_titles = "" diff --git a/bot/exts/evergreen/snakes/_snakes_cog.py b/bot/exts/evergreen/snakes/_snakes_cog.py index 70093912..c8633ce7 100644 --- a/bot/exts/evergreen/snakes/_snakes_cog.py +++ b/bot/exts/evergreen/snakes/_snakes_cog.py @@ -643,7 +643,8 @@ class Snakes(Cog): data = await self._get_snek(name) if data.get('error'): - return await ctx.send('Could not fetch data from Wikipedia.') + await ctx.send('Could not fetch data from Wikipedia.') + return description = data["info"] @@ -900,7 +901,8 @@ class Snakes(Cog): color=SNAKE_COLOR ) - return await ctx.send(embed=embed) + await ctx.send(embed=embed) + return @snakes_group.command(name='sal') @locked() diff --git a/bot/exts/evergreen/trivia_quiz.py b/bot/exts/evergreen/trivia_quiz.py index f40375a6..bfd7d357 100644 --- a/bot/exts/evergreen/trivia_quiz.py +++ b/bot/exts/evergreen/trivia_quiz.py @@ -62,10 +62,11 @@ class TriviaQuiz(commands.Cog): # Stop game if running. if self.game_status[ctx.channel.id] is True: - return await ctx.send( + await ctx.send( f"Game is already running..." f"do `{self.bot.command_prefix}quiz stop`" ) + return # Send embed showing available categories if inputted category is invalid. if category is None: diff --git a/bot/utils/pagination.py b/bot/utils/pagination.py index a4d0cc56..a97dd023 100644 --- a/bot/utils/pagination.py +++ b/bot/utils/pagination.py @@ -79,7 +79,7 @@ class LinePaginator(Paginator): prefix: str = "", suffix: str = "", max_lines: Optional[int] = None, max_size: int = 500, empty: bool = True, restrict_to_user: User = None, timeout: int = 300, footer_text: str = None, url: str = None, - exception_on_empty_embed: bool = False): + exception_on_empty_embed: bool = False) -> None: """ Use a paginator and set of reactions to provide pagination over a set of lines. @@ -157,7 +157,8 @@ class LinePaginator(Paginator): log.trace(f"Setting embed url to '{url}'") log.debug("There's less than two pages, so we won't paginate - sending single page on its own") - return await ctx.send(embed=embed) + await ctx.send(embed=embed) + return else: if footer_text: embed.set_footer(text=f"{footer_text} (Page {current_page + 1}/{len(paginator.pages)})") @@ -302,7 +303,7 @@ class ImagePaginator(Paginator): @classmethod async def paginate(cls, pages: List[Tuple[str, str]], ctx: Context, embed: Embed, prefix: str = "", suffix: str = "", timeout: int = 300, - exception_on_empty_embed: bool = False): + exception_on_empty_embed: bool = False) -> None: """ Use a paginator and set of reactions to provide pagination over a set of title/image pairs. @@ -352,7 +353,8 @@ class ImagePaginator(Paginator): embed.set_image(url=image) if len(paginator.pages) <= 1: - return await ctx.send(embed=embed) + await ctx.send(embed=embed) + return embed.set_footer(text=f"Page {current_page + 1}/{len(paginator.pages)}") message = await ctx.send(embed=embed) -- cgit v1.2.3 From 8769624eac7e86e634fcef39c8fb81c4abc2f48e Mon Sep 17 00:00:00 2001 From: ToxicKidz <78174417+ToxicKidz@users.noreply.github.com> Date: Tue, 4 May 2021 11:30:19 -0400 Subject: chore: Don't make content an empty string Co-authored-by: Matteo Bertucci --- bot/exts/evergreen/snakes/_snakes_cog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/snakes/_snakes_cog.py b/bot/exts/evergreen/snakes/_snakes_cog.py index c8633ce7..e75f958c 100644 --- a/bot/exts/evergreen/snakes/_snakes_cog.py +++ b/bot/exts/evergreen/snakes/_snakes_cog.py @@ -829,7 +829,7 @@ class Snakes(Cog): ) ) - quiz = await ctx.send("", embed=embed) + quiz = await ctx.send(embed=embed) await self._validate_answer(ctx, quiz, answer, options) @snakes_group.command(name='name', aliases=('name_gen',)) -- cgit v1.2.3 From a6cc40ff3b323dff112d7f8c339e124f3a6d9980 Mon Sep 17 00:00:00 2001 From: ToxicKidz <78174417+ToxicKidz@users.noreply.github.com> Date: Tue, 4 May 2021 12:57:03 -0400 Subject: chore: Prefer double quotes over single quotes --- bot/bot.py | 2 +- bot/exts/christmas/advent_of_code/_helpers.py | 8 +- bot/exts/christmas/hanukkah_embed.py | 28 ++--- bot/exts/easter/april_fools_vids.py | 2 +- bot/exts/easter/bunny_name_generator.py | 16 +-- bot/exts/easter/earth_photos.py | 2 +- bot/exts/easter/egg_facts.py | 2 +- bot/exts/easter/egghead_quiz.py | 12 +- bot/exts/easter/save_the_planet.py | 4 +- bot/exts/easter/traditions.py | 2 +- bot/exts/evergreen/cheatsheet.py | 4 +- bot/exts/evergreen/connect_four.py | 6 +- bot/exts/evergreen/conversationstarters.py | 12 +- bot/exts/evergreen/emoji.py | 6 +- bot/exts/evergreen/error_handler.py | 4 +- bot/exts/evergreen/game.py | 4 +- bot/exts/evergreen/githubinfo.py | 42 +++---- bot/exts/evergreen/help.py | 74 ++++++------- bot/exts/evergreen/issues.py | 4 +- bot/exts/evergreen/minesweeper.py | 6 +- bot/exts/evergreen/movie.py | 20 ++-- bot/exts/evergreen/pythonfacts.py | 8 +- bot/exts/evergreen/recommend_game.py | 10 +- bot/exts/evergreen/reddit.py | 20 ++-- bot/exts/evergreen/snakes/_converter.py | 12 +- bot/exts/evergreen/snakes/_snakes_cog.py | 152 +++++++++++++------------- bot/exts/evergreen/snakes/_utils.py | 34 +++--- bot/exts/evergreen/source.py | 2 +- bot/exts/evergreen/speedrun.py | 2 +- bot/exts/evergreen/status_codes.py | 16 +-- bot/exts/evergreen/tic_tac_toe.py | 2 +- bot/exts/evergreen/trivia_quiz.py | 2 +- bot/exts/evergreen/wikipedia.py | 10 +- bot/exts/evergreen/wolfram.py | 4 +- bot/exts/evergreen/xkcd.py | 4 +- bot/exts/halloween/8ball.py | 2 +- bot/exts/halloween/candy_collection.py | 18 +-- bot/exts/halloween/hacktober-issue-finder.py | 2 +- bot/exts/halloween/hacktoberstats.py | 8 +- bot/exts/halloween/halloweenify.py | 4 +- bot/exts/halloween/monsterbio.py | 2 +- bot/exts/halloween/monstersurvey.py | 84 +++++++------- bot/exts/halloween/scarymovie.py | 22 ++-- bot/exts/halloween/spookygif.py | 6 +- bot/exts/halloween/spookynamerate.py | 2 +- bot/exts/halloween/spookyrating.py | 10 +- bot/exts/halloween/spookyreact.py | 14 +-- bot/exts/internal_eval/_internal_eval.py | 8 +- bot/exts/valentines/be_my_valentine.py | 34 +++--- bot/exts/valentines/myvalenstate.py | 10 +- bot/exts/valentines/pickuplines.py | 8 +- bot/exts/valentines/savethedate.py | 2 +- bot/exts/valentines/valentine_zodiac.py | 32 +++--- bot/exts/valentines/whoisvalentine.py | 12 +- bot/utils/__init__.py | 14 +-- bot/utils/checks.py | 4 +- bot/utils/decorators.py | 2 +- bot/utils/extensions.py | 4 +- bot/utils/halloween/spookifications.py | 10 +- bot/utils/pagination.py | 8 +- 60 files changed, 430 insertions(+), 430 deletions(-) (limited to 'bot') diff --git a/bot/bot.py b/bot/bot.py index 7e495940..b8de97aa 100644 --- a/bot/bot.py +++ b/bot/bot.py @@ -101,7 +101,7 @@ class Bot(commands.Bot): all_channels_ids = [channel.id for channel in self.get_all_channels()] for name, channel_id in vars(constants.Channels).items(): - if name.startswith('_'): + if name.startswith("_"): continue if channel_id not in all_channels_ids: log.error(f'Channel "{name}" with ID {channel_id} missing') diff --git a/bot/exts/christmas/advent_of_code/_helpers.py b/bot/exts/christmas/advent_of_code/_helpers.py index a16a4871..f4a258c0 100644 --- a/bot/exts/christmas/advent_of_code/_helpers.py +++ b/bot/exts/christmas/advent_of_code/_helpers.py @@ -108,7 +108,7 @@ def _parse_raw_leaderboard_data(raw_leaderboard_data: dict) -> dict: # star view. We need that per star view to compute rank scores per star. for member in raw_leaderboard_data.values(): name = member["name"] if member["name"] else f"Anonymous #{member['id']}" - member_id = member['id'] + member_id = member["id"] leaderboard[member_id] = {"name": name, "score": 0, "star_1": 0, "star_2": 0} # Iterate over all days for this participant @@ -119,7 +119,7 @@ def _parse_raw_leaderboard_data(raw_leaderboard_data: dict) -> dict: leaderboard[member_id][f"star_{star}"] += 1 # Record completion datetime for this participant for this day/star - completion_time = datetime.datetime.fromtimestamp(int(data['get_star_ts'])) + completion_time = datetime.datetime.fromtimestamp(int(data["get_star_ts"])) star_results[(day, star)].append( StarResult(member_id=member_id, completion_time=completion_time) ) @@ -133,7 +133,7 @@ def _parse_raw_leaderboard_data(raw_leaderboard_data: dict) -> dict: if day in AdventOfCode.ignored_days: continue - sorted_result = sorted(results, key=operator.attrgetter('completion_time')) + sorted_result = sorted(results, key=operator.attrgetter("completion_time")) for rank, star_result in enumerate(sorted_result): leaderboard[star_result.member_id]["score"] += max_score - rank @@ -307,7 +307,7 @@ async def fetch_leaderboard(invalidate_cache: bool = False) -> dict: def get_summary_embed(leaderboard: dict) -> discord.Embed: """Get an embed with the current summary stats of the leaderboard.""" - leaderboard_url = leaderboard['full_leaderboard_url'] + leaderboard_url = leaderboard["full_leaderboard_url"] refresh_minutes = AdventOfCode.leaderboard_cache_expiry_seconds // 60 aoc_embed = discord.Embed( diff --git a/bot/exts/christmas/hanukkah_embed.py b/bot/exts/christmas/hanukkah_embed.py index cd8a9192..32002f76 100644 --- a/bot/exts/christmas/hanukkah_embed.py +++ b/bot/exts/christmas/hanukkah_embed.py @@ -28,15 +28,15 @@ class HanukkahEmbed(commands.Cog): hanukkah_dates = [] async with self.bot.http_session.get(self.url) as response: json_data = await response.json() - festivals = json_data['items'] + festivals = json_data["items"] for festival in festivals: - if festival['title'].startswith('Chanukah'): - date = festival['date'] + if festival["title"].startswith("Chanukah"): + date = festival["date"] hanukkah_dates.append(date) return hanukkah_dates @in_month(Month.DECEMBER) - @commands.command(name='hanukkah', aliases=['chanukah']) + @commands.command(name="hanukkah", aliases=["chanukah"]) async def hanukkah_festival(self, ctx: commands.Context) -> None: """Tells you about the Hanukkah Festivaltime of festival, festival day, etc).""" hanukkah_dates = await self.get_hanukkah_dates() @@ -56,7 +56,7 @@ class HanukkahEmbed(commands.Cog): month = str(today.month) year = str(today.year) embed = Embed() - embed.title = 'Hanukkah' + embed.title = "Hanukkah" embed.colour = Colours.blue if day in self.hanukkah_days and month in self.hanukkah_months and year in self.hanukkah_years: if int(day) == hanukkah_start_day: @@ -69,13 +69,13 @@ class HanukkahEmbed(commands.Cog): await ctx.send(embed=embed) return elif hours > hanukkah_start_hour: - embed.description = (f'It is the starting day of Hanukkah ! ' - f'Its been {hours-hanukkah_start_hour} hours hanukkah started !') + embed.description = (f"It is the starting day of Hanukkah ! " + f"Its been {hours-hanukkah_start_hour} hours hanukkah started !") await ctx.send(embed=embed) return festival_day = self.hanukkah_days.index(day) - number_suffixes = ['st', 'nd', 'rd', 'th'] - suffix = '' + number_suffixes = ["st", "nd", "rd", "th"] + suffix = "" if int(festival_day) == 1: suffix = number_suffixes[0] if int(festival_day) == 2: @@ -84,19 +84,19 @@ class HanukkahEmbed(commands.Cog): suffix = number_suffixes[2] if int(festival_day) > 3: suffix = number_suffixes[3] - message = '' + message = "" for _ in range(1, festival_day + 1): - message += ':menorah:' - embed.description = f'It is the {festival_day}{suffix} day of Hanukkah ! \n {message}' + message += ":menorah:" + embed.description = f"It is the {festival_day}{suffix} day of Hanukkah ! \n {message}" await ctx.send(embed=embed) else: if today < hanukkah_start: - festival_starting_month = hanukkah_start.strftime('%B') + festival_starting_month = hanukkah_start.strftime("%B") embed.description = (f"Hanukkah has not started yet. " f"Hanukkah will start at sundown on {hanukkah_start_day}th " f"of {festival_starting_month}.") else: - festival_end_month = hanukkah_end.strftime('%B') + festival_end_month = hanukkah_end.strftime("%B") embed.description = (f"Looks like you missed Hanukkah !" f"Hanukkah ended on {hanukkah_end_day}th of {festival_end_month}.") diff --git a/bot/exts/easter/april_fools_vids.py b/bot/exts/easter/april_fools_vids.py index 84aa2913..3ce1f72a 100644 --- a/bot/exts/easter/april_fools_vids.py +++ b/bot/exts/easter/april_fools_vids.py @@ -15,7 +15,7 @@ with open("bot/resources/easter/april_fools_vids.json", encoding="utf-8") as f: class AprilFoolVideos(commands.Cog): """A cog for April Fools' that gets a random April Fools' video from Youtube.""" - @commands.command(name='fool') + @commands.command(name="fool") async def april_fools(self, ctx: commands.Context) -> None: """Get a random April Fools' video from Youtube.""" video = random.choice(ALL_VIDS) diff --git a/bot/exts/easter/bunny_name_generator.py b/bot/exts/easter/bunny_name_generator.py index 6d9e2a57..23f85226 100644 --- a/bot/exts/easter/bunny_name_generator.py +++ b/bot/exts/easter/bunny_name_generator.py @@ -20,7 +20,7 @@ class BunnyNameGenerator(commands.Cog): def find_separators(self, displayname: str) -> Union[List[str], None]: """Check if Discord name contains spaces so we can bunnify an individual word in the name.""" - new_name = re.split(r'[_.\s]', displayname) + new_name = re.split(r"[_.\s]", displayname) if displayname not in new_name: return new_name @@ -33,11 +33,11 @@ class BunnyNameGenerator(commands.Cog): Only the most recently matched pattern will apply the changes. """ expressions = [ - (r'a.+y', 'patchy'), - (r'e.+y', 'ears'), - (r'i.+y', 'ditsy'), - (r'o.+y', 'oofy'), - (r'u.+y', 'uffy'), + ("a.+y", "patchy"), + ("e.+y", "ears"), + ("i.+y", "ditsy"), + ("o.+y", "oofy"), + ("u.+y", "uffy"), ] for exp, vowel_sub in expressions: @@ -47,7 +47,7 @@ class BunnyNameGenerator(commands.Cog): def append_name(self, displayname: str) -> str: """Adds a suffix to the end of the Discord name.""" - extensions = ['foot', 'ear', 'nose', 'tail'] + extensions = ["foot", "ear", "nose", "tail"] suffix = random.choice(extensions) appended_name = displayname + suffix @@ -74,7 +74,7 @@ class BunnyNameGenerator(commands.Cog): unmatched_name = self.append_name(username) if spaces_in_name is not None: - replacements = ['Cotton', 'Fluff', 'Floof' 'Bounce', 'Snuffle', 'Nibble', 'Cuddle', 'Velvetpaw', 'Carrot'] + replacements = ["Cotton", "Fluff", "Floof" "Bounce", "Snuffle", "Nibble", "Cuddle", "Velvetpaw", "Carrot"] word_to_replace = random.choice(spaces_in_name) substitute = random.choice(replacements) bunnified_name = username.replace(word_to_replace, substitute) diff --git a/bot/exts/easter/earth_photos.py b/bot/exts/easter/earth_photos.py index d7e7ccc3..0e82e99a 100644 --- a/bot/exts/easter/earth_photos.py +++ b/bot/exts/easter/earth_photos.py @@ -21,7 +21,7 @@ class EarthPhotos(commands.Cog): """Returns a random photo of earth, sourced from Unsplash.""" async with ctx.typing(): async with self.bot.http_session.get( - 'https://api.unsplash.com/photos/random', + "https://api.unsplash.com/photos/random", params={"query": "planet_earth", "client_id": Tokens.unsplash_access_key} ) as r: jsondata = await r.json() diff --git a/bot/exts/easter/egg_facts.py b/bot/exts/easter/egg_facts.py index 78a5e592..8c93ca7b 100644 --- a/bot/exts/easter/egg_facts.py +++ b/bot/exts/easter/egg_facts.py @@ -41,7 +41,7 @@ class EasterFacts(commands.Cog): channel = self.bot.get_channel(Channels.community_bot_commands) await channel.send(embed=self.make_embed()) - @commands.command(name='eggfact', aliases=['fact']) + @commands.command(name="eggfact", aliases=["fact"]) async def easter_facts(self, ctx: commands.Context) -> None: """Get easter egg facts.""" embed = self.make_embed() diff --git a/bot/exts/easter/egghead_quiz.py b/bot/exts/easter/egghead_quiz.py index e950bc2e..59c1f6f8 100644 --- a/bot/exts/easter/egghead_quiz.py +++ b/bot/exts/easter/egghead_quiz.py @@ -18,12 +18,12 @@ with open(Path("bot/resources/easter/egghead_questions.json"), "r", encoding="ut EMOJIS = [ - '\U0001f1e6', '\U0001f1e7', '\U0001f1e8', '\U0001f1e9', '\U0001f1ea', - '\U0001f1eb', '\U0001f1ec', '\U0001f1ed', '\U0001f1ee', '\U0001f1ef', - '\U0001f1f0', '\U0001f1f1', '\U0001f1f2', '\U0001f1f3', '\U0001f1f4', - '\U0001f1f5', '\U0001f1f6', '\U0001f1f7', '\U0001f1f8', '\U0001f1f9', - '\U0001f1fa', '\U0001f1fb', '\U0001f1fc', '\U0001f1fd', '\U0001f1fe', - '\U0001f1ff' + "\U0001f1e6", "\U0001f1e7", "\U0001f1e8", "\U0001f1e9", "\U0001f1ea", + "\U0001f1eb", "\U0001f1ec", "\U0001f1ed", "\U0001f1ee", "\U0001f1ef", + "\U0001f1f0", "\U0001f1f1", "\U0001f1f2", "\U0001f1f3", "\U0001f1f4", + "\U0001f1f5", "\U0001f1f6", "\U0001f1f7", "\U0001f1f8", "\U0001f1f9", + "\U0001f1fa", "\U0001f1fb", "\U0001f1fc", "\U0001f1fd", "\U0001f1fe", + "\U0001f1ff" ] # Regional Indicators A-Z (used for voting) TIMELIMIT = 30 diff --git a/bot/exts/easter/save_the_planet.py b/bot/exts/easter/save_the_planet.py index db9d3498..444bb030 100644 --- a/bot/exts/easter/save_the_planet.py +++ b/bot/exts/easter/save_the_planet.py @@ -8,14 +8,14 @@ from bot.bot import Bot from bot.utils.randomization import RandomCycle -with Path("bot/resources/easter/save_the_planet.json").open('r', encoding='utf8') as f: +with Path("bot/resources/easter/save_the_planet.json").open("r", encoding="utf8") as f: EMBED_DATA = RandomCycle(json.load(f)) class SaveThePlanet(commands.Cog): """A cog that teaches users how they can help our planet.""" - @commands.command(aliases=('savetheearth', 'saveplanet', 'saveearth')) + @commands.command(aliases=("savetheearth", "saveplanet", "saveearth")) async def savetheplanet(self, ctx: commands.Context) -> None: """Responds with a random tip on how to be eco-friendly and help our planet.""" return_embed = Embed.from_dict(next(EMBED_DATA)) diff --git a/bot/exts/easter/traditions.py b/bot/exts/easter/traditions.py index 19e69b98..cb70f2d0 100644 --- a/bot/exts/easter/traditions.py +++ b/bot/exts/easter/traditions.py @@ -16,7 +16,7 @@ with open(Path("bot/resources/easter/traditions.json"), "r", encoding="utf8") as class Traditions(commands.Cog): """A cog which allows users to get a random easter tradition or custom from a random country.""" - @commands.command(aliases=('eastercustoms',)) + @commands.command(aliases=("eastercustoms",)) async def easter_tradition(self, ctx: commands.Context) -> None: """Responds with a random tradition or custom.""" random_country = random.choice(list(traditions)) diff --git a/bot/exts/evergreen/cheatsheet.py b/bot/exts/evergreen/cheatsheet.py index 57c6d0b0..86fae167 100644 --- a/bot/exts/evergreen/cheatsheet.py +++ b/bot/exts/evergreen/cheatsheet.py @@ -24,11 +24,11 @@ Unknown cheat sheet. Please try to reformulate your query. If the problem persists send a message in <#{Channels.dev_contrib}> """ -URL = 'https://cheat.sh/python/{search}' +URL = "https://cheat.sh/python/{search}" ESCAPE_TT = str.maketrans({"`": "\\`"}) ANSI_RE = re.compile(r"\x1b\[.*?m") # We need to pass headers as curl otherwise it would default to aiohttp which would return raw html. -HEADERS = {'User-Agent': 'curl/7.68.0'} +HEADERS = {"User-Agent": "curl/7.68.0"} class CheatSheet(commands.Cog): diff --git a/bot/exts/evergreen/connect_four.py b/bot/exts/evergreen/connect_four.py index df2a913a..929a15d8 100644 --- a/bot/exts/evergreen/connect_four.py +++ b/bot/exts/evergreen/connect_four.py @@ -55,8 +55,8 @@ class Game: async def print_grid(self) -> None: """Formats and outputs the Connect Four grid to the channel.""" title = ( - f'Connect 4: {self.player1.display_name}' - f' VS {self.bot.user.display_name if isinstance(self.player2, AI) else self.player2.display_name}' + f"Connect 4: {self.player1.display_name}" + f" VS {self.bot.user.display_name if isinstance(self.player2, AI) else self.player2.display_name}" ) rows = [" ".join(self.tokens[s] for s in row) for row in self.grid] @@ -67,7 +67,7 @@ class Game: if self.message: await self.message.edit(embed=embed) else: - self.message = await self.channel.send(content='Loading...') + self.message = await self.channel.send(content="Loading...") for emoji in self.unicode_numbers: await self.message.add_reaction(emoji) await self.message.add_reaction(CROSS_EMOJI) diff --git a/bot/exts/evergreen/conversationstarters.py b/bot/exts/evergreen/conversationstarters.py index 54fea0b3..4fe8c47c 100644 --- a/bot/exts/evergreen/conversationstarters.py +++ b/bot/exts/evergreen/conversationstarters.py @@ -9,7 +9,7 @@ from bot.constants import WHITELISTED_CHANNELS from bot.utils.decorators import whitelist_override from bot.utils.randomization import RandomCycle -SUGGESTION_FORM = 'https://forms.gle/zw6kkJqv8U43Nfjg9' +SUGGESTION_FORM = "https://forms.gle/zw6kkJqv8U43Nfjg9" with Path("bot/resources/evergreen/starter.yaml").open("r", encoding="utf8") as f: STARTERS = yaml.load(f, Loader=yaml.FullLoader) @@ -25,9 +25,9 @@ with Path("bot/resources/evergreen/py_topics.yaml").open("r", encoding="utf8") a ALL_ALLOWED_CHANNELS = list(PY_TOPICS.keys()) + list(WHITELISTED_CHANNELS) # Putting all topics into one dictionary and shuffling lists to reduce same-topic repetitions. -ALL_TOPICS = {'default': STARTERS, **PY_TOPICS} +ALL_TOPICS = {"default": STARTERS, **PY_TOPICS} TOPICS = { - channel: RandomCycle(topics or ['No topics found for this channel.']) + channel: RandomCycle(topics or ["No topics found for this channel."]) for channel, topics in ALL_TOPICS.items() } @@ -46,7 +46,7 @@ class ConvoStarters(commands.Cog): Otherwise, a random conversation topic will be received by the user. """ # No matter what, the form will be shown. - embed = Embed(description=f'Suggest more topics [here]({SUGGESTION_FORM})!', color=Color.blurple()) + embed = Embed(description=f"Suggest more topics [here]({SUGGESTION_FORM})!", color=Color.blurple()) try: # Fetching topics. @@ -54,11 +54,11 @@ class ConvoStarters(commands.Cog): # If the channel isn't Python-related. except KeyError: - embed.title = f'**{next(TOPICS["default"])}**' + embed.title = f"**{next(TOPICS['default'])}**" # If the channel ID doesn't have any topics. else: - embed.title = f'**{next(channel_topics)}**' + embed.title = f"**{next(channel_topics)}**" finally: await ctx.send(embed=embed) diff --git a/bot/exts/evergreen/emoji.py b/bot/exts/evergreen/emoji.py index 8e540712..e7452a15 100644 --- a/bot/exts/evergreen/emoji.py +++ b/bot/exts/evergreen/emoji.py @@ -46,9 +46,9 @@ class Emojis(commands.Cog): else: emoji_info = f"There is **{len(category_emojis)}** emoji in the **{category_name}** category." if emoji_choice.animated: - msg.append(f' {emoji_info}') + msg.append(f" {emoji_info}") else: - msg.append(f'<:{emoji_choice.name}:{emoji_choice.id}> {emoji_info}') + msg.append(f"<:{emoji_choice.name}:{emoji_choice.id}> {emoji_info}") return embed, msg @staticmethod @@ -64,7 +64,7 @@ class Emojis(commands.Cog): for emoji in emojis: emoji_dict[emoji.name.split("_")[0]].append(emoji) - error_comp = ', '.join(emoji_dict) + error_comp = ", ".join(emoji_dict) msg.append(f"These are the valid emoji categories:\n```{error_comp}```") return embed, msg diff --git a/bot/exts/evergreen/error_handler.py b/bot/exts/evergreen/error_handler.py index dabd0ab5..5cd8d28d 100644 --- a/bot/exts/evergreen/error_handler.py +++ b/bot/exts/evergreen/error_handler.py @@ -40,7 +40,7 @@ class CommandErrorHandler(commands.Cog): @commands.Cog.listener() async def on_command_error(self, ctx: commands.Context, error: commands.CommandError) -> None: """Activates when a command opens an error.""" - if getattr(error, 'handled', False): + if getattr(error, "handled", False): logging.debug(f"Command {ctx.command} had its error already handled locally; ignoring.") return @@ -49,7 +49,7 @@ class CommandErrorHandler(commands.Cog): parent_command = f"{ctx.command} " ctx = subctx - error = getattr(error, 'original', error) + error = getattr(error, "original", error) logging.debug( f"Error Encountered: {type(error).__name__} - {str(error)}, " f"Command: {ctx.command}, " diff --git a/bot/exts/evergreen/game.py b/bot/exts/evergreen/game.py index 24872e76..7abbadcd 100644 --- a/bot/exts/evergreen/game.py +++ b/bot/exts/evergreen/game.py @@ -176,7 +176,7 @@ class Games(Cog): "Invalid OAuth credentials. Unloading Games cog. " f"OAuth response message: {result['message']}" ) - self.bot.remove_cog('Games') + self.bot.remove_cog("Games") return @@ -260,7 +260,7 @@ class Games(Cog): display_possibilities = "`, `".join(p[1] for p in possibilities) await ctx.send( f"Invalid genre `{genre}`. " - f"{f'Maybe you meant `{display_possibilities}`?' if display_possibilities else ''}" + f"Maybe you meant `{display_possibilities}`?" if display_possibilities else '' ) return elif len(possibilities) == 1: diff --git a/bot/exts/evergreen/githubinfo.py b/bot/exts/evergreen/githubinfo.py index fd100a7c..24479c79 100644 --- a/bot/exts/evergreen/githubinfo.py +++ b/bot/exts/evergreen/githubinfo.py @@ -27,14 +27,14 @@ class GithubInfo(commands.Cog): async with self.bot.http_session.get(url) as r: return await r.json() - @commands.group(name='github', aliases=('gh', 'git')) + @commands.group(name="github", aliases=("gh", "git")) @commands.cooldown(1, 10, BucketType.user) async def github_group(self, ctx: commands.Context) -> None: """Commands for finding information related to GitHub.""" if ctx.invoked_subcommand is None: await invoke_help_command(ctx) - @github_group.command(name='user', aliases=('userinfo',)) + @github_group.command(name="user", aliases=("userinfo",)) async def github_user_info(self, ctx: commands.Context, username: str) -> None: """Fetches a user's GitHub information.""" async with ctx.typing(): @@ -51,31 +51,31 @@ class GithubInfo(commands.Cog): await ctx.send(embed=embed) return - org_data = await self.fetch_data(user_data['organizations_url']) + org_data = await self.fetch_data(user_data["organizations_url"]) orgs = [f"[{org['login']}](https://github.com/{org['login']})" for org in org_data] - orgs_to_add = ' | '.join(orgs) + orgs_to_add = " | ".join(orgs) - gists = user_data['public_gists'] + gists = user_data["public_gists"] # Forming blog link - if user_data['blog'].startswith("http"): # Blog link is complete - blog = user_data['blog'] - elif user_data['blog']: # Blog exists but the link is not complete + if user_data["blog"].startswith("http"): # Blog link is complete + blog = user_data["blog"] + elif user_data["blog"]: # Blog exists but the link is not complete blog = f"https://{user_data['blog']}" else: blog = "No website link available" embed = discord.Embed( title=f"`{user_data['login']}`'s GitHub profile info", - description=f"```{user_data['bio']}```\n" if user_data['bio'] is not None else "", + description=f"```{user_data['bio']}```\n" if user_data["bio"] is not None else "", colour=discord.Colour.blurple(), - url=user_data['html_url'], - timestamp=datetime.strptime(user_data['created_at'], "%Y-%m-%dT%H:%M:%SZ") + url=user_data["html_url"], + timestamp=datetime.strptime(user_data["created_at"], "%Y-%m-%dT%H:%M:%SZ") ) - embed.set_thumbnail(url=user_data['avatar_url']) + embed.set_thumbnail(url=user_data["avatar_url"]) embed.set_footer(text="Account created at") - if user_data['type'] == "User": + if user_data["type"] == "User": embed.add_field( name="Followers", @@ -91,7 +91,7 @@ class GithubInfo(commands.Cog): value=f"[{user_data['public_repos']}]({user_data['html_url']}?tab=repositories)" ) - if user_data['type'] == "User": + if user_data["type"] == "User": embed.add_field(name="Gists", value=f"[{gists}](https://gist.github.com/{quote(username, safe='')})") embed.add_field( @@ -109,8 +109,8 @@ class GithubInfo(commands.Cog): The repository should look like `user/reponame` or `user reponame`. """ - repo = '/'.join(repo) - if repo.count('/') != 1: + repo = "/".join(repo) + if repo.count("/") != 1: embed = discord.Embed( title=random.choice(NEGATIVE_REPLIES), description="The repository should look like `user/reponame` or `user reponame`.", @@ -135,10 +135,10 @@ class GithubInfo(commands.Cog): return embed = discord.Embed( - title=repo_data['name'], + title=repo_data["name"], description=repo_data["description"], colour=discord.Colour.blurple(), - url=repo_data['html_url'] + url=repo_data["html_url"] ) # If it's a fork, then it will have a parent key @@ -148,7 +148,7 @@ class GithubInfo(commands.Cog): except KeyError: log.debug("Repository is not a fork.") - repo_owner = repo_data['owner'] + repo_owner = repo_data["owner"] embed.set_author( name=repo_owner["login"], @@ -156,8 +156,8 @@ class GithubInfo(commands.Cog): icon_url=repo_owner["avatar_url"] ) - repo_created_at = datetime.strptime(repo_data['created_at'], "%Y-%m-%dT%H:%M:%SZ").strftime("%d/%m/%Y") - last_pushed = datetime.strptime(repo_data['pushed_at'], "%Y-%m-%dT%H:%M:%SZ").strftime("%d/%m/%Y at %H:%M") + repo_created_at = datetime.strptime(repo_data["created_at"], "%Y-%m-%dT%H:%M:%SZ").strftime("%d/%m/%Y") + last_pushed = datetime.strptime(repo_data["pushed_at"], "%Y-%m-%dT%H:%M:%SZ").strftime("%d/%m/%Y at %H:%M") embed.set_footer( text=( diff --git a/bot/exts/evergreen/help.py b/bot/exts/evergreen/help.py index f557e42e..bfaf25f1 100644 --- a/bot/exts/evergreen/help.py +++ b/bot/exts/evergreen/help.py @@ -22,14 +22,14 @@ from bot.utils.pagination import ( DELETE_EMOJI = Emojis.trashcan REACTIONS = { - FIRST_EMOJI: 'first', - LEFT_EMOJI: 'back', - RIGHT_EMOJI: 'next', - LAST_EMOJI: 'end', - DELETE_EMOJI: 'stop', + FIRST_EMOJI: "first", + LEFT_EMOJI: "back", + RIGHT_EMOJI: "next", + LAST_EMOJI: "end", + DELETE_EMOJI: "stop", } -Cog = namedtuple('Cog', ['name', 'description', 'commands']) +Cog = namedtuple("Cog", ["name", "description", "commands"]) log = logging.getLogger(__name__) @@ -87,7 +87,7 @@ class HelpSession: # set the query details for the session if command: - query_str = ' '.join(command) + query_str = " ".join(command) self.query = self._get_query(query_str) self.description = self.query.description or self.query.help else: @@ -191,7 +191,7 @@ class HelpSession: self.reset_timeout() # Run relevant action method - action = getattr(self, f'do_{REACTIONS[emoji]}', None) + action = getattr(self, f"do_{REACTIONS[emoji]}", None) if action: await action() @@ -234,11 +234,11 @@ class HelpSession: if cmd.cog: try: if cmd.cog.category: - return f'**{cmd.cog.category}**' + return f"**{cmd.cog.category}**" except AttributeError: pass - return f'**{cmd.cog_name}**' + return f"**{cmd.cog_name}**" else: return "**\u200bNo Category:**" @@ -262,47 +262,47 @@ class HelpSession: # if default is not an empty string or None if show_default: - results.append(f'[{name}={param.default}]') + results.append(f"[{name}={param.default}]") else: - results.append(f'[{name}]') + results.append(f"[{name}]") # if variable length argument elif param.kind == param.VAR_POSITIONAL: - results.append(f'[{name}...]') + results.append(f"[{name}...]") # if required else: - results.append(f'<{name}>') + results.append(f"<{name}>") return f"{cmd.name} {' '.join(results)}" async def build_pages(self) -> None: """Builds the list of content pages to be paginated through in the help message, as a list of str.""" # Use LinePaginator to restrict embed line height - paginator = LinePaginator(prefix='', suffix='', max_lines=self._max_lines) + paginator = LinePaginator(prefix="", suffix="", max_lines=self._max_lines) prefix = constants.Client.prefix # show signature if query is a command if isinstance(self.query, commands.Command): signature = self._get_command_params(self.query) - parent = self.query.full_parent_name + ' ' if self.query.parent else '' - paginator.add_line(f'**```{prefix}{parent}{signature}```**') + parent = self.query.full_parent_name + " " if self.query.parent else "" + paginator.add_line(f"**```{prefix}{parent}{signature}```**") aliases = [f"`{alias}`" if not parent else f"`{parent} {alias}`" for alias in self.query.aliases] aliases += [f"`{alias}`" for alias in getattr(self.query, "root_aliases", ())] aliases = ", ".join(sorted(aliases)) if aliases: - paginator.add_line(f'**Can also use:** {aliases}\n') + paginator.add_line(f"**Can also use:** {aliases}\n") if not await self.query.can_run(self._ctx): - paginator.add_line('***You cannot run this command.***\n') + paginator.add_line("***You cannot run this command.***\n") if isinstance(self.query, Cog): - paginator.add_line(f'**{self.query.name}**') + paginator.add_line(f"**{self.query.name}**") if self.description: - paginator.add_line(f'*{self.description}*') + paginator.add_line(f"*{self.description}*") # list all children commands of the queried object if isinstance(self.query, (commands.GroupMixin, Cog)): @@ -319,13 +319,13 @@ class HelpSession: return if isinstance(self.query, Cog): - grouped = (('**Commands:**', self.query.commands),) + grouped = (("**Commands:**", self.query.commands),) elif isinstance(self.query, commands.Command): - grouped = (('**Subcommands:**', self.query.commands),) + grouped = (("**Subcommands:**", self.query.commands),) # don't show prefix for subcommands - prefix = '' + prefix = "" # otherwise sort and organise all commands into categories else: @@ -347,7 +347,7 @@ class HelpSession: continue # see if the user can run the command - strikeout = '' + strikeout = "" # Patch to make the !help command work outside of #bot-commands again # This probably needs a proper rewrite, but this will make it work in @@ -361,16 +361,16 @@ class HelpSession: # skip if we don't show commands they can't run if self._only_can_run: continue - strikeout = '~~' + strikeout = "~~" signature = self._get_command_params(command) info = f"{strikeout}**`{prefix}{signature}`**{strikeout}" # handle if the command has no docstring if command.short_doc: - cat_cmds.append(f'{info}\n*{command.short_doc}*') + cat_cmds.append(f"{info}\n*{command.short_doc}*") else: - cat_cmds.append(f'{info}\n*No details provided.*') + cat_cmds.append(f"{info}\n*No details provided.*") # state var for if the category should be added next print_cat = 1 @@ -379,7 +379,7 @@ class HelpSession: for details in cat_cmds: # keep details together, paginating early if it won't fit - lines_adding = len(details.split('\n')) + print_cat + lines_adding = len(details.split("\n")) + print_cat if paginator._linecount + lines_adding > self._max_lines: paginator._linecount = 0 new_page = True @@ -390,7 +390,7 @@ class HelpSession: if print_cat: if new_page: - paginator.add_line('') + paginator.add_line("") paginator.add_line(category) print_cat = 0 @@ -412,7 +412,7 @@ class HelpSession: page_count = len(self._pages) if page_count > 1: - embed.set_footer(text=f'Page {self._current_page+1} / {page_count}') + embed.set_footer(text=f"Page {self._current_page+1} / {page_count}") return embed @@ -496,7 +496,7 @@ class HelpSession: class Help(DiscordCog): """Custom Embed Pagination Help feature.""" - @commands.command('help') + @commands.command("help") async def new_help(self, ctx: Context, *commands) -> None: """Shows Command Help.""" try: @@ -507,8 +507,8 @@ class Help(DiscordCog): embed.title = str(error) if error.possible_matches: - matches = '\n'.join(error.possible_matches.keys()) - embed.description = f'**Did you mean:**\n`{matches}`' + matches = "\n".join(error.possible_matches.keys()) + embed.description = f"**Did you mean:**\n`{matches}`" await ctx.send(embed=embed) @@ -519,7 +519,7 @@ def unload(bot: Bot) -> None: This is run if the cog raises an exception on load, or if the extension is unloaded. """ - bot.remove_command('help') + bot.remove_command("help") bot.add_command(bot._old_help) @@ -534,8 +534,8 @@ def setup(bot: Bot) -> None: If an exception is raised during the loading of the cog, `unload` will be called in order to reinstate the original help command. """ - bot._old_help = bot.get_command('help') - bot.remove_command('help') + bot._old_help = bot.get_command("help") + bot.remove_command("help") try: bot.add_cog(Help()) diff --git a/bot/exts/evergreen/issues.py b/bot/exts/evergreen/issues.py index d7ee99c0..5bbc57c6 100644 --- a/bot/exts/evergreen/issues.py +++ b/bot/exts/evergreen/issues.py @@ -158,7 +158,7 @@ class Issues(commands.Cog): issue_url = json_data.get("html_url") - return IssueState(repository, number, issue_url, json_data.get('title', ''), emoji) + return IssueState(repository, number, issue_url, json_data.get("title", ""), emoji) @staticmethod def format_embed( @@ -177,7 +177,7 @@ class Issues(commands.Cog): resp = discord.Embed( colour=Colours.bright_green, - description='\n'.join(description_list) + description="\n".join(description_list) ) embed_url = f"https://github.com/{user}/{repository}" if repository else f"https://github.com/{user}" diff --git a/bot/exts/evergreen/minesweeper.py b/bot/exts/evergreen/minesweeper.py index d0cc28c5..f2c5e656 100644 --- a/bot/exts/evergreen/minesweeper.py +++ b/bot/exts/evergreen/minesweeper.py @@ -38,7 +38,7 @@ class CoordinateConverter(commands.Converter): async def convert(self, ctx: commands.Context, coordinate: str) -> typing.Tuple[int, int]: """Take in a coordinate string and turn it into an (x, y) tuple.""" if not 2 <= len(coordinate) <= 3: - raise commands.BadArgument('Invalid co-ordinate provided.') + raise commands.BadArgument("Invalid co-ordinate provided.") coordinate = coordinate.lower() if coordinate[0].isalpha(): @@ -51,7 +51,7 @@ class CoordinateConverter(commands.Converter): if not digit.isdigit(): raise commands.BadArgument - x = ord(letter) - ord('a') + x = ord(letter) - ord("a") y = int(digit) - 1 if (not 0 <= x <= 9) or (not 0 <= y <= 9): @@ -82,7 +82,7 @@ class Minesweeper(commands.Cog): def __init__(self, _bot: Bot) -> None: self.games: GamesDict = {} # Store the currently running games - @commands.group(name='minesweeper', aliases=('ms',), invoke_without_command=True) + @commands.group(name="minesweeper", aliases=("ms",), invoke_without_command=True) async def minesweeper_group(self, ctx: commands.Context) -> None: """Commands for Playing Minesweeper.""" await invoke_help_command(ctx) diff --git a/bot/exts/evergreen/movie.py b/bot/exts/evergreen/movie.py index 488e5142..e67f8d04 100644 --- a/bot/exts/evergreen/movie.py +++ b/bot/exts/evergreen/movie.py @@ -53,7 +53,7 @@ class Movie(Cog): def __init__(self, bot: Bot): self.http_session: ClientSession = bot.http_session - @group(name='movies', aliases=['movie'], invoke_without_command=True) + @group(name="movies", aliases=["movie"], invoke_without_command=True) async def movies(self, ctx: Context, genre: str = "", amount: int = 5) -> None: """ Get random movies by specifying genre. Also support amount parameter, that define how much movies will be shown. @@ -89,7 +89,7 @@ class Movie(Cog): # Get movies list from TMDB, check if results key in result. When not, raise error. movies = await self.get_movies_list(self.http_session, MovieGenres[genre].value, page) - if 'results' not in movies.keys(): + if "results" not in movies.keys(): err_msg = f"There is problem while making TMDB API request. Response Code: {result['status_code']}, " \ f"{result['status_message']}." await ctx.send(err_msg) @@ -101,7 +101,7 @@ class Movie(Cog): await ImagePaginator.paginate(pages, ctx, embed) - @movies.command(name='genres', aliases=['genre', 'g']) + @movies.command(name="genres", aliases=["genre", "g"]) async def genres(self, ctx: Context) -> None: """Show all currently available genres for .movies command.""" await ctx.send(f"Current available genres: {', '.join('`' + genre.name + '`' for genre in MovieGenres)}") @@ -130,7 +130,7 @@ class Movie(Cog): pages = [] for i in range(amount): - movie_id = movies['results'][i]['id'] + movie_id = movies["results"][i]["id"] movie = await self.get_movie(client, movie_id) page, img = await self.create_page(movie) @@ -151,7 +151,7 @@ class Movie(Cog): # Add title + tagline (if not empty) text += f"**{movie['title']}**\n" - if movie['tagline']: + if movie["tagline"]: text += f"{movie['tagline']}\n\n" else: text += "\n" @@ -162,8 +162,8 @@ class Movie(Cog): text += "__**Production Information**__\n" - companies = movie['production_companies'] - countries = movie['production_countries'] + companies = movie["production_companies"] + countries = movie["production_countries"] text += f"**Made by:** {', '.join(company['name'] for company in companies)}\n" text += f"**Made in:** {', '.join(country['name'] for country in countries)}\n\n" @@ -173,8 +173,8 @@ class Movie(Cog): budget = f"{movie['budget']:,d}" if movie['budget'] else "?" revenue = f"{movie['revenue']:,d}" if movie['revenue'] else "?" - if movie['runtime'] is not None: - duration = divmod(movie['runtime'], 60) + if movie["runtime"] is not None: + duration = divmod(movie["runtime"], 60) else: duration = ("?", "?") @@ -182,7 +182,7 @@ class Movie(Cog): text += f"**Revenue:** ${revenue}\n" text += f"**Duration:** {f'{duration[0]} hour(s) {duration[1]} minute(s)'}\n\n" - text += movie['overview'] + text += movie["overview"] img = f"http://image.tmdb.org/t/p/w200{movie['poster_path']}" diff --git a/bot/exts/evergreen/pythonfacts.py b/bot/exts/evergreen/pythonfacts.py index 73234d55..2dc4996f 100644 --- a/bot/exts/evergreen/pythonfacts.py +++ b/bot/exts/evergreen/pythonfacts.py @@ -6,7 +6,7 @@ from discord.ext import commands from bot.bot import Bot from bot.constants import Colours -with open('bot/resources/evergreen/python_facts.txt') as file: +with open("bot/resources/evergreen/python_facts.txt") as file: FACTS = itertools.cycle(list(file)) COLORS = itertools.cycle([Colours.python_blue, Colours.python_yellow]) @@ -15,13 +15,13 @@ COLORS = itertools.cycle([Colours.python_blue, Colours.python_yellow]) class PythonFacts(commands.Cog): """Sends a random fun fact about Python.""" - @commands.command(name='pythonfact', aliases=['pyfact']) + @commands.command(name="pythonfact", aliases=["pyfact"]) async def get_python_fact(self, ctx: commands.Context) -> None: """Sends a Random fun fact about Python.""" - embed = discord.Embed(title='Python Facts', + embed = discord.Embed(title="Python Facts", description=next(FACTS), colour=next(COLORS)) - embed.add_field(name='Suggestions', + embed.add_field(name="Suggestions", value="Suggest more facts [here!](https://github.com/python-discord/meta/discussions/93)") await ctx.send(embed=embed) diff --git a/bot/exts/evergreen/recommend_game.py b/bot/exts/evergreen/recommend_game.py index be329f44..340a42d3 100644 --- a/bot/exts/evergreen/recommend_game.py +++ b/bot/exts/evergreen/recommend_game.py @@ -13,7 +13,7 @@ game_recs = [] # Populate the list `game_recs` with resource files for rec_path in Path("bot/resources/evergreen/game_recs").glob("*.json"): - with rec_path.open(encoding='utf8') as file: + with rec_path.open(encoding="utf8") as file: data = json.load(file) game_recs.append(data) shuffle(game_recs) @@ -26,7 +26,7 @@ class RecommendGame(commands.Cog): self.bot = bot self.index = 0 - @commands.command(name="recommendgame", aliases=['gamerec']) + @commands.command(name="recommendgame", aliases=["gamerec"]) async def recommend_game(self, ctx: commands.Context) -> None: """Sends an Embed of a random game recommendation.""" if self.index >= len(game_recs): @@ -35,14 +35,14 @@ class RecommendGame(commands.Cog): game = game_recs[self.index] self.index += 1 - author = self.bot.get_user(int(game['author'])) + author = self.bot.get_user(int(game["author"])) # Creating and formatting Embed embed = discord.Embed(color=discord.Colour.blue()) if author is not None: embed.set_author(name=author.name, icon_url=author.avatar_url) - embed.set_image(url=game['image']) - embed.add_field(name='Recommendation: ' + game['title'] + '\n' + game['link'], value=game['description']) + embed.set_image(url=game["image"]) + embed.add_field(name="Recommendation: " + game["title"] + "\n" + game["link"], value=game["description"]) await ctx.send(embed=embed) diff --git a/bot/exts/evergreen/reddit.py b/bot/exts/evergreen/reddit.py index 51a360b3..82af6ce9 100644 --- a/bot/exts/evergreen/reddit.py +++ b/bot/exts/evergreen/reddit.py @@ -21,18 +21,18 @@ class Reddit(commands.Cog): """Send a get request to the reddit API and get json response.""" session = self.bot.http_session params = { - 'limit': 50 + "limit": 50 } headers = { - 'User-Agent': 'Iceman' + "User-Agent": "Iceman" } async with session.get(url=url, params=params, headers=headers) as response: return await response.json() - @commands.command(name='reddit') + @commands.command(name="reddit") @commands.cooldown(1, 10, BucketType.user) - async def get_reddit(self, ctx: commands.Context, subreddit: str = 'python', sort: str = "hot") -> None: + async def get_reddit(self, ctx: commands.Context, subreddit: str = "python", sort: str = "hot") -> None: """ Fetch reddit posts by using this command. @@ -46,15 +46,15 @@ class Reddit(commands.Cog): await ctx.send(f"Invalid sorting: {sort}\nUsing default sorting: `Hot`") sort = "hot" - data = await self.fetch(f'https://www.reddit.com/r/{subreddit}/{sort}/.json') + data = await self.fetch(f"https://www.reddit.com/r/{subreddit}/{sort}/.json") try: posts = data["data"]["children"] except KeyError: - await ctx.send('Subreddit not found!') + await ctx.send("Subreddit not found!") return if not posts: - await ctx.send('No posts available!') + await ctx.send("No posts available!") return if posts[0]["data"]["over_18"] is True: @@ -106,12 +106,12 @@ class Reddit(commands.Cog): post_stats = f"{image_emoji} " image_url = post_url - votes = f'{upvote_emoji}{post["data"]["ups"]}' - comments = f'{comment_emoji}\u2002{ post["data"]["num_comments"]}' + votes = f"{upvote_emoji}{post['data']['ups']}" + comments = f"{comment_emoji}\u2002{ post['data']['num_comments']}" post_stats += ( f"\u2002{votes}\u2003" f"{comments}" - f'\u2003{user_emoji}\u2002{post["data"]["author"]}\n' + f"\u2003{user_emoji}\u2002{post['data']['author']}\n" ) embed_titles += f"{post_stats}\n" page_text = f"**[{post_title}]({post_url})**\n{post_stats}\n{post['data']['selftext'][0:200]}" diff --git a/bot/exts/evergreen/snakes/_converter.py b/bot/exts/evergreen/snakes/_converter.py index eee248cf..0ca10d6c 100644 --- a/bot/exts/evergreen/snakes/_converter.py +++ b/bot/exts/evergreen/snakes/_converter.py @@ -24,8 +24,8 @@ class Snake(Converter): await self.build_list() name = name.lower() - if name == 'python': - return 'Python (programming language)' + if name == "python": + return "Python (programming language)" def get_potential(iterable: Iterable, *, threshold: int = 80) -> List[str]: nonlocal name @@ -47,12 +47,12 @@ class Snake(Converter): if name.lower() in self.special_cases: return self.special_cases.get(name.lower(), name.lower()) - names = {snake['name']: snake['scientific'] for snake in self.snakes} + names = {snake["name"]: snake["scientific"] for snake in self.snakes} all_names = names.keys() | names.values() timeout = len(all_names) * (3 / 4) embed = discord.Embed( - title='Found multiple choices. Please choose the correct one.', colour=0x59982F) + title="Found multiple choices. Please choose the correct one.", colour=0x59982F) embed.set_author(name=ctx.author.display_name, icon_url=ctx.author.avatar_url) name = await disambiguate(ctx, get_potential(all_names), timeout=timeout, embed=embed) @@ -70,7 +70,7 @@ class Snake(Converter): if cls.special_cases is None: with (SNAKE_RESOURCES / "special_snakes.json").open(encoding="utf8") as snakefile: special_cases = json.load(snakefile) - cls.special_cases = {snake['name'].lower(): snake for snake in special_cases} + cls.special_cases = {snake["name"].lower(): snake for snake in special_cases} @classmethod async def random(cls) -> str: @@ -81,5 +81,5 @@ class Snake(Converter): so I can get it from here. """ await cls.build_list() - names = [snake['scientific'] for snake in cls.snakes] + names = [snake["scientific"] for snake in cls.snakes] return random.choice(names) diff --git a/bot/exts/evergreen/snakes/_snakes_cog.py b/bot/exts/evergreen/snakes/_snakes_cog.py index c8633ce7..d95970da 100644 --- a/bot/exts/evergreen/snakes/_snakes_cog.py +++ b/bot/exts/evergreen/snakes/_snakes_cog.py @@ -143,8 +143,8 @@ class Snakes(Cog): https://github.com/python-discord/code-jam-1 """ - wiki_brief = re.compile(r'(.*?)(=+ (.*?) =+)', flags=re.DOTALL) - valid_image_extensions = ('gif', 'png', 'jpeg', 'jpg', 'webp') + wiki_brief = re.compile(r"(.*?)(=+ (.*?) =+)", flags=re.DOTALL) + valid_image_extensions = ("gif", "png", "jpeg", "jpg", "webp") def __init__(self, bot: Bot): self.active_sal = {} @@ -183,28 +183,28 @@ class Snakes(Cog): # Get the size of the snake icon, configure the height of the image box (yes, it changes) icon_width = 347 # Hardcoded, not much i can do about that icon_height = int((icon_width / snake.width) * snake.height) - frame_copies = icon_height // CARD['frame'].height + 1 + frame_copies = icon_height // CARD["frame"].height + 1 snake.thumbnail((icon_width, icon_height)) # Get the dimensions of the final image - main_height = icon_height + CARD['top'].height + CARD['bottom'].height - main_width = CARD['frame'].width + main_height = icon_height + CARD["top"].height + CARD["bottom"].height + main_width = CARD["frame"].width # Start creating the foreground foreground = Image.new("RGBA", (main_width, main_height), (0, 0, 0, 0)) - foreground.paste(CARD['top'], (0, 0)) + foreground.paste(CARD["top"], (0, 0)) # Generate the frame borders to the correct height for offset in range(frame_copies): - position = (0, CARD['top'].height + offset * CARD['frame'].height) - foreground.paste(CARD['frame'], position) + position = (0, CARD["top"].height + offset * CARD["frame"].height) + foreground.paste(CARD["frame"], position) # Add the image and bottom part of the image - foreground.paste(snake, (36, CARD['top'].height)) # Also hardcoded :( - foreground.paste(CARD['bottom'], (0, CARD['top'].height + icon_height)) + foreground.paste(snake, (36, CARD["top"].height)) # Also hardcoded :( + foreground.paste(CARD["bottom"], (0, CARD["top"].height + icon_height)) # Setup the background - back = random.choice(CARD['backs']) + back = random.choice(CARD["backs"]) back_copies = main_height // back.height + 1 full_image = Image.new("RGBA", (main_width, main_height), (0, 0, 0, 0)) @@ -216,11 +216,11 @@ class Snakes(Cog): full_image.paste(foreground, (0, 0), foreground) # Get the first two sentences of the info - description = '.'.join(content['info'].split(".")[:2]) + '.' + description = ".".join(content["info"].split(".")[:2]) + "." # Setup positioning variables margin = 36 - offset = CARD['top'].height + icon_height + margin + offset = CARD["top"].height + icon_height + margin # Create blank rectangle image which will be behind the text rectangle = Image.new( @@ -242,12 +242,12 @@ class Snakes(Cog): # Draw the text onto the final image draw = ImageDraw.Draw(full_image) for line in textwrap.wrap(description, 36): - draw.text([margin + 4, offset], line, font=CARD['font']) - offset += CARD['font'].getsize(line)[1] + draw.text([margin + 4, offset], line, font=CARD["font"]) + offset += CARD["font"].getsize(line)[1] # Get the image contents as a BufferIO object buffer = BytesIO() - full_image.save(buffer, 'PNG') + full_image.save(buffer, "PNG") buffer.seek(0) return buffer @@ -311,12 +311,12 @@ class Snakes(Cog): async with aiohttp.ClientSession() as session: params = { - 'format': 'json', - 'action': 'query', - 'list': 'search', - 'srsearch': name, - 'utf8': '', - 'srlimit': '1', + "format": "json", + "action": "query", + "list": "search", + "srsearch": name, + "utf8": "", + "srlimit": "1", } json = await self._fetch(session, URL, params=params) @@ -331,13 +331,13 @@ class Snakes(Cog): return None params = { - 'format': 'json', - 'action': 'query', - 'prop': 'extracts|images|info', - 'exlimit': 'max', - 'explaintext': '', - 'inprop': 'url', - 'pageids': pageid + "format": "json", + "action": "query", + "prop": "extracts|images|info", + "exlimit": "max", + "explaintext": "", + "inprop": "url", + "pageids": pageid } json = await self._fetch(session, URL, params=params) @@ -353,32 +353,32 @@ class Snakes(Cog): snake_info["error"] = True if snake_info["images"]: - i_url = 'https://commons.wikimedia.org/wiki/Special:FilePath/' + i_url = "https://commons.wikimedia.org/wiki/Special:FilePath/" image_list = [] map_list = [] thumb_list = [] # Wikipedia has arbitrary images that are not snakes banned = [ - 'Commons-logo.svg', - 'Red%20Pencil%20Icon.png', - 'distribution', - 'The%20Death%20of%20Cleopatra%20arthur.jpg', - 'Head%20of%20holotype', - 'locator', - 'Woma.png', - '-map.', - '.svg', - 'ange.', - 'Adder%20(PSF).png' + "Commons-logo.svg", + "Red%20Pencil%20Icon.png", + "distribution", + "The%20Death%20of%20Cleopatra%20arthur.jpg", + "Head%20of%20holotype", + "locator", + "Woma.png", + "-map.", + ".svg", + "ange.", + "Adder%20(PSF).png" ] for image in snake_info["images"]: # Images come in the format of `File:filename.extension` - file, sep, filename = image["title"].partition(':') + file, sep, filename = image["title"].partition(":") filename = filename.replace(" ", "%20") # Wikipedia returns good data! - if not filename.startswith('Map'): + if not filename.startswith("Map"): if any(ban in filename for ban in banned): pass else: @@ -392,7 +392,7 @@ class Snakes(Cog): snake_info["thumb_list"] = thumb_list snake_info["name"] = name - match = self.wiki_brief.match(snake_info['extract']) + match = self.wiki_brief.match(snake_info["extract"]) info = match.group(1) if match else None if info: @@ -438,13 +438,13 @@ class Snakes(Cog): # endregion # region: Commands - @group(name='snakes', aliases=('snake',), invoke_without_command=True) + @group(name="snakes", aliases=("snake",), invoke_without_command=True) async def snakes_group(self, ctx: Context) -> None: """Commands from our first code jam.""" await invoke_help_command(ctx) @bot_has_permissions(manage_messages=True) - @snakes_group.command(name='antidote') + @snakes_group.command(name="antidote") @locked() async def antidote_command(self, ctx: Context) -> None: """ @@ -586,7 +586,7 @@ class Snakes(Cog): log.debug("Ending pagination and removing all reactions...") await board_id.clear_reactions() - @snakes_group.command(name='draw') + @snakes_group.command(name="draw") async def draw_command(self, ctx: Context) -> None: """ Draws a random snek using Perlin noise. @@ -621,10 +621,10 @@ class Snakes(Cog): bg_color=bg_color ) png_bytes = utils.frame_to_png_bytes(image_frame) - file = File(png_bytes, filename='snek.png') + file = File(png_bytes, filename="snek.png") await ctx.send(file=file) - @snakes_group.command(name='get') + @snakes_group.command(name="get") @bot_has_permissions(manage_messages=True) @locked() async def get_command(self, ctx: Context, *, name: Snake = None) -> None: @@ -642,8 +642,8 @@ class Snakes(Cog): else: data = await self._get_snek(name) - if data.get('error'): - await ctx.send('Could not fetch data from Wikipedia.') + if data.get("error"): + await ctx.send("Could not fetch data from Wikipedia.") return description = data["info"] @@ -662,19 +662,19 @@ class Snakes(Cog): # Build and send the embed. embed = Embed( - title=data.get("title", data.get('name')), + title=data.get("title", data.get("name")), description=description, colour=0x59982F, ) - emoji = 'https://emojipedia-us.s3.amazonaws.com/thumbs/60/google/3/snake_1f40d.png' - image = next((url for url in data['image_list'] + emoji = "https://emojipedia-us.s3.amazonaws.com/thumbs/60/google/3/snake_1f40d.png" + image = next((url for url in data["image_list"] if url.endswith(self.valid_image_extensions)), emoji) embed.set_image(url=image) await ctx.send(embed=embed) - @snakes_group.command(name='guess', aliases=('identify',)) + @snakes_group.command(name="guess", aliases=("identify",)) @locked() async def guess_command(self, ctx: Context) -> None: """ @@ -694,11 +694,11 @@ class Snakes(Cog): data = await self._get_snek(snake) - image = next((url for url in data['image_list'] + image = next((url for url in data["image_list"] if url.endswith(self.valid_image_extensions)), None) embed = Embed( - title='Which of the following is the snake in the image?', + title="Which of the following is the snake in the image?", description="\n".join( f"{'ABCD'[snakes.index(snake)]}: {snake}" for snake in snakes), colour=SNAKE_COLOR @@ -709,7 +709,7 @@ class Snakes(Cog): options = {f"{'abcd'[snakes.index(snake)]}": snake for snake in snakes} await self._validate_answer(ctx, guess, answer, options) - @snakes_group.command(name='hatch') + @snakes_group.command(name="hatch") async def hatch_command(self, ctx: Context) -> None: """ Hatches your personal snake. @@ -740,7 +740,7 @@ class Snakes(Cog): await ctx.send(embed=my_snake_embed) - @snakes_group.command(name='movie') + @snakes_group.command(name="movie") async def movie_command(self, ctx: Context) -> None: """ Gets a random snake-related movie from TMDB. @@ -806,7 +806,7 @@ class Snakes(Cog): await ctx.send("An error occurred while fetching a snake-related movie!") raise err from None - @snakes_group.command(name='quiz') + @snakes_group.command(name="quiz") @locked() async def quiz_command(self, ctx: Context) -> None: """ @@ -832,7 +832,7 @@ class Snakes(Cog): quiz = await ctx.send("", embed=embed) await self._validate_answer(ctx, quiz, answer, options) - @snakes_group.command(name='name', aliases=('name_gen',)) + @snakes_group.command(name="name", aliases=("name_gen",)) async def name_command(self, ctx: Context, *, name: str = None) -> None: """ Snakifies a username. @@ -856,7 +856,7 @@ class Snakes(Cog): This was written by Iceman, and modified for inclusion into the bot by lemon. """ snake_name = await self._get_snake_name() - snake_name = snake_name['name'] + snake_name = snake_name["name"] snake_prefix = "" # Set aside every word in the snake name except the last. @@ -904,7 +904,7 @@ class Snakes(Cog): await ctx.send(embed=embed) return - @snakes_group.command(name='sal') + @snakes_group.command(name="sal") @locked() async def sal_command(self, ctx: Context) -> None: """ @@ -923,7 +923,7 @@ class Snakes(Cog): await game.open_game() - @snakes_group.command(name='about') + @snakes_group.command(name="about") async def about_command(self, ctx: Context) -> None: """Show an embed with information about the event, its participants, and its winners.""" contributors = [ @@ -968,7 +968,7 @@ class Snakes(Cog): await ctx.send(embed=embed) - @snakes_group.command(name='card') + @snakes_group.command(name="card") async def card_command(self, ctx: Context, *, name: Snake = None) -> None: """ Create an interesting little card from a snake. @@ -978,7 +978,7 @@ class Snakes(Cog): # Get the snake data we need if not name: name_obj = await self._get_snake_name() - name = name_obj['scientific'] + name = name_obj["scientific"] content = await self._get_snek(name) elif isinstance(name, dict): @@ -992,7 +992,7 @@ class Snakes(Cog): stream = BytesIO() async with async_timeout.timeout(10): - async with self.bot.http_session.get(content['image_list'][0]) as response: + async with self.bot.http_session.get(content["image_list"][0]) as response: stream.write(await response.read()) stream.seek(0) @@ -1003,10 +1003,10 @@ class Snakes(Cog): # Send it! await ctx.send( f"A wild {content['name'].title()} appears!", - file=File(final_buffer, filename=content['name'].replace(" ", "") + ".png") + file=File(final_buffer, filename=content["name"].replace(" ", "") + ".png") ) - @snakes_group.command(name='fact') + @snakes_group.command(name="fact") async def fact_command(self, ctx: Context) -> None: """ Gets a snake-related fact. @@ -1022,7 +1022,7 @@ class Snakes(Cog): ) await ctx.send(embed=embed) - @snakes_group.command(name='snakify') + @snakes_group.command(name="snakify") async def snakify_command(self, ctx: Context, *, message: str = None) -> None: """ How would I talk if I were a snake? @@ -1063,7 +1063,7 @@ class Snakes(Cog): await ctx.send(embed=embed) - @snakes_group.command(name='video', aliases=('get_video',)) + @snakes_group.command(name="video", aliases=("get_video",)) async def video_command(self, ctx: Context, *, search: str = None) -> None: """ Gets a YouTube video about snakes. @@ -1074,13 +1074,13 @@ class Snakes(Cog): """ # Are we searching for anything specific? if search: - query = search + ' snake' + query = search + " snake" else: snake = await self._get_snake_name() - query = snake['name'] + query = snake["name"] # Build the URL and make the request - url = 'https://www.googleapis.com/youtube/v3/search' + url = "https://www.googleapis.com/youtube/v3/search" response = await self.bot.http_session.get( url, params={ @@ -1096,14 +1096,14 @@ class Snakes(Cog): # Send the user a video if len(data) > 0: num = random.randint(0, len(data) - 1) - youtube_base_url = 'https://www.youtube.com/watch?v=' + youtube_base_url = "https://www.youtube.com/watch?v=" await ctx.send( content=f"{youtube_base_url}{data[num]['id']['videoId']}" ) else: log.warning(f"YouTube API error. Full response looks like {response}") - @snakes_group.command(name='zen') + @snakes_group.command(name="zen") async def zen_command(self, ctx: Context) -> None: """ Gets a random quote from the Zen of Python, except as if spoken by a snake. diff --git a/bot/exts/evergreen/snakes/_utils.py b/bot/exts/evergreen/snakes/_utils.py index 7d6caf04..d58ee279 100644 --- a/bot/exts/evergreen/snakes/_utils.py +++ b/bot/exts/evergreen/snakes/_utils.py @@ -321,7 +321,7 @@ def create_snek_frame( image_dimensions[Y] / 2 - (dimension_range[Y] / 2 + min_dimensions[Y]) ) - image = Image.new(mode='RGB', size=image_dimensions, color=bg_color) + image = Image.new(mode="RGB", size=image_dimensions, color=bg_color) draw = ImageDraw(image) for index in range(1, len(points)): point = points[index] @@ -345,7 +345,7 @@ def create_snek_frame( def frame_to_png_bytes(image: Image) -> io.BytesIO: """Convert image to byte stream.""" stream = io.BytesIO() - image.save(stream, format='PNG') + image.save(stream, format="PNG") stream.seek(0) return stream @@ -373,7 +373,7 @@ class SnakeAndLaddersGame: self.snakes = snakes self.ctx = context self.channel = self.ctx.channel - self.state = 'booting' + self.state = "booting" self.started = False self.author = self.ctx.author self.players = [] @@ -413,7 +413,7 @@ class SnakeAndLaddersGame: "**Snakes and Ladders**: A new game is about to start!", file=File( str(SNAKE_RESOURCES / "snakes_and_ladders" / "banner.jpg"), - filename='Snakes and Ladders.jpg' + filename="Snakes and Ladders.jpg" ) ) startup = await self.channel.send( @@ -423,7 +423,7 @@ class SnakeAndLaddersGame: for emoji in STARTUP_SCREEN_EMOJI: await startup.add_reaction(emoji) - self.state = 'waiting' + self.state = "waiting" while not self.started: try: @@ -460,7 +460,7 @@ class SnakeAndLaddersGame: self.players.append(user) self.player_tiles[user.id] = 1 - avatar_bytes = await user.avatar_url_as(format='jpeg', size=PLAYER_ICON_IMAGE_SIZE).read() + avatar_bytes = await user.avatar_url_as(format="jpeg", size=PLAYER_ICON_IMAGE_SIZE).read() im = Image.open(io.BytesIO(avatar_bytes)).resize((BOARD_PLAYER_SIZE, BOARD_PLAYER_SIZE)) self.avatar_images[user.id] = im @@ -475,7 +475,7 @@ class SnakeAndLaddersGame: if user == p: await self.channel.send(user.mention + " You are already in the game.", delete_after=10) return - if self.state != 'waiting': + if self.state != "waiting": await self.channel.send(user.mention + " You cannot join at this time.", delete_after=10) return if len(self.players) is MAX_PLAYERS: @@ -510,7 +510,7 @@ class SnakeAndLaddersGame: delete_after=10 ) - if self.state != 'waiting' and len(self.players) == 0: + if self.state != "waiting" and len(self.players) == 0: await self.channel.send("**Snakes and Ladders**: The game has been surrendered!") is_surrendered = True self._destruct() @@ -535,12 +535,12 @@ class SnakeAndLaddersGame: await self.channel.send(user.mention + " Only the author of the game can start it.", delete_after=10) return - if not self.state == 'waiting': + if not self.state == "waiting": await self.channel.send(user.mention + " The game cannot be started at this time.", delete_after=10) return - self.state = 'starting' - player_list = ', '.join(user.mention for user in self.players) + self.state = "starting" + player_list = ", ".join(user.mention for user in self.players) await self.channel.send("**Snakes and Ladders**: The game is starting!\nPlayers: " + player_list) await self.start_round() @@ -556,7 +556,7 @@ class SnakeAndLaddersGame: )) ) - self.state = 'roll' + self.state = "roll" for user in self.players: self.round_has_rolled[user.id] = False board_img = Image.open(str(SNAKE_RESOURCES / "snakes_and_ladders" / "board.jpg")) @@ -574,8 +574,8 @@ class SnakeAndLaddersGame: board_img.paste(self.avatar_images[player.id], box=(x_offset, y_offset)) - board_file = File(frame_to_png_bytes(board_img), filename='Board.jpg') - player_list = '\n'.join((user.mention + ": Tile " + str(self.player_tiles[user.id])) for user in self.players) + board_file = File(frame_to_png_bytes(board_img), filename="Board.jpg") + player_list = "\n".join((user.mention + ": Tile " + str(self.player_tiles[user.id])) for user in self.players) # Store and send new messages temp_board = await self.channel.send( @@ -644,7 +644,7 @@ class SnakeAndLaddersGame: if user.id not in self.player_tiles: await self.channel.send(user.mention + " You are not in the match.", delete_after=10) return - if self.state != 'roll': + if self.state != "roll": await self.channel.send(user.mention + " You may not roll at this time.", delete_after=10) return if self.round_has_rolled[user.id]: @@ -673,7 +673,7 @@ class SnakeAndLaddersGame: async def _complete_round(self) -> None: """At the conclusion of a round check to see if there's been a winner.""" - self.state = 'post_round' + self.state = "post_round" # check for winner winner = self._check_winner() @@ -688,7 +688,7 @@ class SnakeAndLaddersGame: def _check_winner(self) -> Member: """Return a winning member if we're in the post-round state and there's a winner.""" - if self.state != 'post_round': + if self.state != "post_round": return None return next((player for player in self.players if self.player_tiles[player.id] == 100), None) diff --git a/bot/exts/evergreen/source.py b/bot/exts/evergreen/source.py index 14fd02f3..2f25e4cb 100644 --- a/bot/exts/evergreen/source.py +++ b/bot/exts/evergreen/source.py @@ -83,7 +83,7 @@ class BotSource(commands.Cog): url, location, first_line = self.get_source_link(source_object) if isinstance(source_object, commands.Command): - if source_object.cog_name == 'Help': + if source_object.cog_name == "Help": title = "Help Command" description = source_object.__doc__.splitlines()[1] else: diff --git a/bot/exts/evergreen/speedrun.py b/bot/exts/evergreen/speedrun.py index bf6f2117..110d5c13 100644 --- a/bot/exts/evergreen/speedrun.py +++ b/bot/exts/evergreen/speedrun.py @@ -8,7 +8,7 @@ from discord.ext import commands from bot.bot import Bot log = logging.getLogger(__name__) -with Path('bot/resources/evergreen/speedrun_links.json').open(encoding="utf8") as file: +with Path("bot/resources/evergreen/speedrun_links.json").open(encoding="utf8") as file: LINKS = json.load(file) diff --git a/bot/exts/evergreen/status_codes.py b/bot/exts/evergreen/status_codes.py index 635eef3d..a866692e 100644 --- a/bot/exts/evergreen/status_codes.py +++ b/bot/exts/evergreen/status_codes.py @@ -22,10 +22,10 @@ class HTTPStatusCodes(commands.Cog): if not ctx.invoked_subcommand: await invoke_help_command(ctx) - @http_status_group.command(name='cat') + @http_status_group.command(name="cat") async def http_cat(self, ctx: commands.Context, code: int) -> None: """Sends an embed with an image of a cat, portraying the status code.""" - embed = discord.Embed(title=f'**Status: {code}**') + embed = discord.Embed(title=f"**Status: {code}**") url = HTTP_CAT_URL.format(code=code) try: @@ -37,18 +37,18 @@ class HTTPStatusCodes(commands.Cog): raise NotImplementedError except ValueError: - embed.set_footer(text='Inputted status code does not exist.') + embed.set_footer(text="Inputted status code does not exist.") except NotImplementedError: - embed.set_footer(text='Inputted status code is not implemented by http.cat yet.') + embed.set_footer(text="Inputted status code is not implemented by http.cat yet.") finally: await ctx.send(embed=embed) - @http_status_group.command(name='dog') + @http_status_group.command(name="dog") async def http_dog(self, ctx: commands.Context, code: int) -> None: """Sends an embed with an image of a dog, portraying the status code.""" - embed = discord.Embed(title=f'**Status: {code}**') + embed = discord.Embed(title=f"**Status: {code}**") url = HTTP_DOG_URL.format(code=code) try: @@ -60,10 +60,10 @@ class HTTPStatusCodes(commands.Cog): raise NotImplementedError except ValueError: - embed.set_footer(text='Inputted status code does not exist.') + embed.set_footer(text="Inputted status code does not exist.") except NotImplementedError: - embed.set_footer(text='Inputted status code is not implemented by httpstatusdogs.com yet.') + embed.set_footer(text="Inputted status code is not implemented by httpstatusdogs.com yet.") finally: await ctx.send(embed=embed) diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index 1fef427a..7b387c0a 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -58,7 +58,7 @@ class Player: ) try: - react, _ = await self.ctx.bot.wait_for('reaction_add', timeout=30.0, check=check_for_move) + react, _ = await self.ctx.bot.wait_for("reaction_add", timeout=30.0, check=check_for_move) except asyncio.TimeoutError: return True, None else: diff --git a/bot/exts/evergreen/trivia_quiz.py b/bot/exts/evergreen/trivia_quiz.py index bfd7d357..1953253b 100644 --- a/bot/exts/evergreen/trivia_quiz.py +++ b/bot/exts/evergreen/trivia_quiz.py @@ -129,7 +129,7 @@ class TriviaQuiz(commands.Cog): ) try: - msg = await self.bot.wait_for('message', check=check, timeout=10) + msg = await self.bot.wait_for("message", check=check, timeout=10) except asyncio.TimeoutError: # In case of TimeoutError and the game has been stopped, then do nothing. if self.game_status[ctx.channel.id] is False: diff --git a/bot/exts/evergreen/wikipedia.py b/bot/exts/evergreen/wikipedia.py index e2172fc3..fa21b916 100644 --- a/bot/exts/evergreen/wikipedia.py +++ b/bot/exts/evergreen/wikipedia.py @@ -20,7 +20,7 @@ WIKI_THUMBNAIL = ( "https://upload.wikimedia.org/wikipedia/en/thumb/8/80/Wikipedia-logo-v2.svg" "/330px-Wikipedia-logo-v2.svg.png" ) -WIKI_SNIPPET_REGEX = r'(|<[^>]*>)' +WIKI_SNIPPET_REGEX = r"(|<[^>]*>)" WIKI_SEARCH_RESULT = ( "**[{name}]({url})**\n" "{description}\n" @@ -39,18 +39,18 @@ class WikipediaSearch(commands.Cog): async with self.bot.http_session.get(url=url) as resp: if resp.status == 200: raw_data = await resp.json() - number_of_results = raw_data['query']['searchinfo']['totalhits'] + number_of_results = raw_data["query"]["searchinfo"]["totalhits"] if number_of_results: - results = raw_data['query']['search'] + results = raw_data["query"]["search"] lines = [] for article in results: line = WIKI_SEARCH_RESULT.format( - name=article['title'], + name=article["title"], description=unescape( re.sub( - WIKI_SNIPPET_REGEX, '', article['snippet'] + WIKI_SNIPPET_REGEX, "", article["snippet"] ) ), url=f"https://en.wikipedia.org/?curid={article['pageid']}" diff --git a/bot/exts/evergreen/wolfram.py b/bot/exts/evergreen/wolfram.py index c57a8d7a..3cc12c03 100644 --- a/bot/exts/evergreen/wolfram.py +++ b/bot/exts/evergreen/wolfram.py @@ -59,7 +59,7 @@ def custom_cooldown(*ignore: List[int]) -> Callable: A list of roles may be provided to ignore the per-user cooldown. """ async def predicate(ctx: Context) -> bool: - if ctx.invoked_with == 'help': + if ctx.invoked_with == "help": # if the invoked command is help we don't want to increase the ratelimits since it's not actually # invoking the command/making a request, so instead just check if the user/guild are on cooldown. guild_cooldown = not guildcd.get_bucket(ctx.message).get_tokens() == 0 # if guild is on cooldown @@ -118,7 +118,7 @@ async def get_pod_pages(ctx: Context, bot: Bot, query: str) -> Optional[List[Tup request_url = QUERY.format(request="query", data=url_str) async with bot.http_session.get(request_url) as response: - json = await response.json(content_type='text/plain') + json = await response.json(content_type="text/plain") result = json["queryresult"] diff --git a/bot/exts/evergreen/xkcd.py b/bot/exts/evergreen/xkcd.py index ba9e46e0..c98830bc 100644 --- a/bot/exts/evergreen/xkcd.py +++ b/bot/exts/evergreen/xkcd.py @@ -53,7 +53,7 @@ class XKCD(Cog): await ctx.send(embed=embed) return - comic = randint(1, self.latest_comic_info['num']) if comic is None else comic.group(0) + comic = randint(1, self.latest_comic_info["num"]) if comic is None else comic.group(0) if comic == "latest": info = self.latest_comic_info @@ -69,7 +69,7 @@ class XKCD(Cog): return embed.title = f"XKCD comic #{info['num']}" - embed.description = info['alt'] + embed.description = info["alt"] embed.url = f"{BASE_URL}/{info['num']}" if info["img"][-3:] in ("jpg", "png", "gif"): diff --git a/bot/exts/halloween/8ball.py b/bot/exts/halloween/8ball.py index 59d4acc5..d6c5a299 100644 --- a/bot/exts/halloween/8ball.py +++ b/bot/exts/halloween/8ball.py @@ -17,7 +17,7 @@ with Path("bot/resources/halloween/responses.json").open("r", encoding="utf8") a class SpookyEightBall(commands.Cog): """Spooky Eightball answers.""" - @commands.command(aliases=('spooky8ball',)) + @commands.command(aliases=("spooky8ball",)) async def spookyeightball(self, ctx: commands.Context, *, question: str) -> None: """Responds with a random response to a question.""" choice = random.choice(RESPONSES["responses"]) diff --git a/bot/exts/halloween/candy_collection.py b/bot/exts/halloween/candy_collection.py index 5441d8a5..14efa1fb 100644 --- a/bot/exts/halloween/candy_collection.py +++ b/bot/exts/halloween/candy_collection.py @@ -22,11 +22,11 @@ EMOJIS = dict( CANDY="\N{CANDY}", SKULL="\N{SKULL}", MEDALS=( - '\N{FIRST PLACE MEDAL}', - '\N{SECOND PLACE MEDAL}', - '\N{THIRD PLACE MEDAL}', - '\N{SPORTS MEDAL}', - '\N{SPORTS MEDAL}', + "\N{FIRST PLACE MEDAL}", + "\N{SECOND PLACE MEDAL}", + "\N{THIRD PLACE MEDAL}", + "\N{SPORTS MEDAL}", + "\N{SPORTS MEDAL}", ), ) @@ -106,7 +106,7 @@ class CandyCollection(commands.Cog): await self.candy_records.decrement(user.id, lost) if lost == prev_record: - await CandyCollection.send_spook_msg(user, message.channel, 'all of your') + await CandyCollection.send_spook_msg(user, message.channel, "all of your") else: await CandyCollection.send_spook_msg(user, message.channel, lost) else: @@ -125,7 +125,7 @@ class CandyCollection(commands.Cog): """ if random.randint(1, ADD_SKULL_EXISTING_REACTION_CHANCE) == 1: await self.skull_messages.set(message.id, "skull") - await message.add_reaction(EMOJIS['SKULL']) + await message.add_reaction(EMOJIS["SKULL"]) elif random.randint(1, ADD_CANDY_EXISTING_REACTION_CHANCE) == 1: await self.candy_messages.set(message.id, "candy") @@ -173,7 +173,7 @@ class CandyCollection(commands.Cog): ) top_five = top_sorted[:5] - return '\n'.join( + return "\n".join( f"{EMOJIS['MEDALS'][index]} <@{record[0]}>: {record[1]}" for index, record in enumerate(top_five) ) if top_five else "No Candies" @@ -185,7 +185,7 @@ class CandyCollection(commands.Cog): inline=False ) e.add_field( - name='\u200b', + name="\u200b", value="Candies will randomly appear on messages sent. " "\nHit the candy when it appears as fast as possible to get the candy! " "\nBut beware the ghosts...", diff --git a/bot/exts/halloween/hacktober-issue-finder.py b/bot/exts/halloween/hacktober-issue-finder.py index c88e2b6f..baee9612 100644 --- a/bot/exts/halloween/hacktober-issue-finder.py +++ b/bot/exts/halloween/hacktober-issue-finder.py @@ -102,7 +102,7 @@ class HacktoberIssues(commands.Cog): labels = [label["name"] for label in issue["labels"]] embed = discord.Embed(title=title) - embed.description = body[:500] + '...' if len(body) > 500 else body + embed.description = body[:500] + "..." if len(body) > 500 else body embed.add_field(name="labels", value="\n".join(labels)) embed.url = issue_url embed.set_footer(text=issue_url) diff --git a/bot/exts/halloween/hacktoberstats.py b/bot/exts/halloween/hacktoberstats.py index 9695ba2a..25da9ad5 100644 --- a/bot/exts/halloween/hacktoberstats.py +++ b/bot/exts/halloween/hacktoberstats.py @@ -138,7 +138,7 @@ class HacktoberStats(commands.Cog): if prs: stats_embed = await self.build_embed(github_username, prs) - await ctx.send('Here are some stats!', embed=stats_embed) + await ctx.send("Here are some stats!", embed=stats_embed) else: await ctx.send(f"No valid Hacktoberfest PRs found for '{github_username}'") @@ -355,7 +355,7 @@ class HacktoberStats(commands.Cog): # loop through reviews and check for approval for item in jsonresp2: - if item.get('status') == "APPROVED": + if item.get("status") == "APPROVED": return True return False @@ -387,9 +387,9 @@ class HacktoberStats(commands.Cog): in_review = [] accepted = [] for pr in prs: - if (pr['created_at'] + timedelta(REVIEW_DAYS)) > now: + if (pr["created_at"] + timedelta(REVIEW_DAYS)) > now: in_review.append(pr) - elif (pr['created_at'] <= oct3) or await self._is_accepted(pr): + elif (pr["created_at"] <= oct3) or await self._is_accepted(pr): accepted.append(pr) return in_review, accepted diff --git a/bot/exts/halloween/halloweenify.py b/bot/exts/halloween/halloweenify.py index 5a8f4ecc..47b20a2a 100644 --- a/bot/exts/halloween/halloweenify.py +++ b/bot/exts/halloween/halloweenify.py @@ -34,8 +34,8 @@ class Halloweenify(commands.Cog): embed.colour = discord.Colour.dark_orange() embed.title = "Not spooky enough?" embed.description = ( - f"**{ctx.author.display_name}** wasn\'t spooky enough for you? That\'s understandable, " - f"{ctx.author.display_name} isn\'t scary at all! " + f"**{ctx.author.display_name}** wasn't spooky enough for you? That's understandable, " + f"{ctx.author.display_name} isn't scary at all! " "Let me think of something better. Hmm... I got it!\n\n " ) embed.set_image(url=image) diff --git a/bot/exts/halloween/monsterbio.py b/bot/exts/halloween/monsterbio.py index f484305d..dbafa43f 100644 --- a/bot/exts/halloween/monsterbio.py +++ b/bot/exts/halloween/monsterbio.py @@ -37,7 +37,7 @@ class MonsterBio(commands.Cog): continue options = seeded_random.sample(TEXT_OPTIONS[key], value) - words[key] = ' '.join(options) + words[key] = " ".join(options) embed = discord.Embed( title=f"{name}'s Biography", diff --git a/bot/exts/halloween/monstersurvey.py b/bot/exts/halloween/monstersurvey.py index 0610503d..486e8937 100644 --- a/bot/exts/halloween/monstersurvey.py +++ b/bot/exts/halloween/monstersurvey.py @@ -9,8 +9,8 @@ from discord.ext.commands import Bot, Cog, Context log = logging.getLogger(__name__) EMOJIS = { - 'SUCCESS': u'\u2705', - 'ERROR': u'\u274C' + "SUCCESS": u"\u2705", + "ERROR": u"\u274C" } @@ -25,63 +25,63 @@ class MonsterSurvey(Cog): def __init__(self): """Initializes values for the bot to use within the voting commands.""" - self.registry_location = os.path.join(os.getcwd(), 'bot', 'resources', 'halloween', 'monstersurvey.json') - with open(self.registry_location, 'r', encoding="utf8") as jason: + self.registry_location = os.path.join(os.getcwd(), "bot", "resources", "halloween", "monstersurvey.json") + with open(self.registry_location, "r", encoding="utf8") as jason: self.voter_registry = json.load(jason) def json_write(self) -> None: """Write voting results to a local JSON file.""" log.info("Saved Monster Survey Results") - with open(self.registry_location, 'w', encoding="utf8") as jason: + with open(self.registry_location, "w", encoding="utf8") as jason: json.dump(self.voter_registry, jason, indent=2) def cast_vote(self, id: int, monster: str) -> None: """ - Cast a user's vote for the specified monster. + Cast a user"s vote for the specified monster. If the user has already voted, their existing vote is removed. """ vr = self.voter_registry for m in vr.keys(): - if id not in vr[m]['votes'] and m == monster: - vr[m]['votes'].append(id) + if id not in vr[m]["votes"] and m == monster: + vr[m]["votes"].append(id) else: - if id in vr[m]['votes'] and m != monster: - vr[m]['votes'].remove(id) + if id in vr[m]["votes"] and m != monster: + vr[m]["votes"].remove(id) def get_name_by_leaderboard_index(self, n: int) -> str: """Return the monster at the specified leaderboard index.""" n = n - 1 vr = self.voter_registry - top = sorted(vr, key=lambda k: len(vr[k]['votes']), reverse=True) + top = sorted(vr, key=lambda k: len(vr[k]["votes"]), reverse=True) name = top[n] if n >= 0 else None return name @commands.group( - name='monster', - aliases=('mon',) + name="monster", + aliases=("mon",) ) async def monster_group(self, ctx: Context) -> None: """The base voting command. If nothing is called, then it will return an embed.""" if ctx.invoked_subcommand is None: async with ctx.typing(): default_embed = Embed( - title='Monster Voting', + title="Monster Voting", color=0xFF6800, - description='Vote for your favorite monster!' + description="Vote for your favorite monster!" ) default_embed.add_field( - name='.monster show monster_name(optional)', - value='Show a specific monster. If none is listed, it will give you an error with valid choices.', + name=".monster show monster_name(optional)", + value="Show a specific monster. If none is listed, it will give you an error with valid choices.", inline=False) default_embed.add_field( - name='.monster vote monster_name', - value='Vote for a specific monster. You get one vote, but can change it at any time.', + name=".monster vote monster_name", + value="Vote for a specific monster. You get one vote, but can change it at any time.", inline=False ) default_embed.add_field( - name='.monster leaderboard', - value='Which monster has the most votes? This command will tell you.', + name=".monster leaderboard", + value="Which monster has the most votes? This command will tell you.", inline=False ) default_embed.set_footer(text=f"Monsters choices are: {', '.join(self.voter_registry.keys())}") @@ -89,7 +89,7 @@ class MonsterSurvey(Cog): await ctx.send(embed=default_embed) @monster_group.command( - name='vote' + name="vote" ) async def monster_vote(self, ctx: Context, name: str = None) -> None: """ @@ -110,37 +110,37 @@ class MonsterSurvey(Cog): name = name.lower() vote_embed = Embed( - name='Monster Voting', + name="Monster Voting", color=0xFF6800 ) m = self.voter_registry.get(name) if m is None: - vote_embed.description = f'You cannot vote for {name} because it\'s not in the running.' + vote_embed.description = f"You cannot vote for {name} because it's not in the running." vote_embed.add_field( - name='Use `.monster show {monster_name}` for more information on a specific monster', - value='or use `.monster vote {monster}` to cast your vote for said monster.', + name="Use `.monster show {monster_name}` for more information on a specific monster", + value="or use `.monster vote {monster}` to cast your vote for said monster.", inline=False ) vote_embed.add_field( - name='You may vote for or show the following monsters:', - value=f"{', '.join(self.voter_registry.keys())}" + name="You may vote for or show the following monsters:", + value=", ".join(self.voter_registry.keys()) ) else: self.cast_vote(ctx.author.id, name) vote_embed.add_field( - name='Vote successful!', - value=f'You have successfully voted for {m["full_name"]}!', + name="Vote successful!", + value=f"You have successfully voted for {m['full_name']}!", inline=False ) - vote_embed.set_thumbnail(url=m['image']) + vote_embed.set_thumbnail(url=m["image"]) vote_embed.set_footer(text="Please note that any previous votes have been removed.") self.json_write() await ctx.send(embed=vote_embed) @monster_group.command( - name='show' + name="show" ) async def monster_show(self, ctx: Context, name: str = None) -> None: """Shows the named monster. If one is not named, it sends the default voting embed instead.""" @@ -158,31 +158,31 @@ class MonsterSurvey(Cog): m = self.voter_registry.get(name) if not m: - await ctx.send('That monster does not exist.') + await ctx.send("That monster does not exist.") await ctx.invoke(self.monster_vote) return - embed = Embed(title=m['full_name'], color=0xFF6800) - embed.add_field(name='Summary', value=m['summary']) - embed.set_image(url=m['image']) - embed.set_footer(text=f'To vote for this monster, type .monster vote {name}') + embed = Embed(title=m["full_name"], color=0xFF6800) + embed.add_field(name="Summary", value=m["summary"]) + embed.set_image(url=m["image"]) + embed.set_footer(text=f"To vote for this monster, type .monster vote {name}") await ctx.send(embed=embed) @monster_group.command( - name='leaderboard', - aliases=('lb',) + name="leaderboard", + aliases=("lb",) ) async def monster_leaderboard(self, ctx: Context) -> None: """Shows the current standings.""" async with ctx.typing(): vr = self.voter_registry - top = sorted(vr, key=lambda k: len(vr[k]['votes']), reverse=True) - total_votes = sum(len(m['votes']) for m in self.voter_registry.values()) + top = sorted(vr, key=lambda k: len(vr[k]["votes"]), reverse=True) + total_votes = sum(len(m["votes"]) for m in self.voter_registry.values()) embed = Embed(title="Monster Survey Leader Board", color=0xFF6800) for rank, m in enumerate(top): - votes = len(vr[m]['votes']) + votes = len(vr[m]["votes"]) percentage = ((votes / total_votes) * 100) if total_votes > 0 else 0 embed.add_field(name=f"{rank+1}. {vr[m]['full_name']}", value=( diff --git a/bot/exts/halloween/scarymovie.py b/bot/exts/halloween/scarymovie.py index 48c9f53d..f4cf41db 100644 --- a/bot/exts/halloween/scarymovie.py +++ b/bot/exts/halloween/scarymovie.py @@ -47,7 +47,7 @@ class ScaryMovie(commands.Cog): total_pages = data.get("total_pages") # Get movie details from one random result on a random page - params['page'] = random.randint(1, total_pages) + params["page"] = random.randint(1, total_pages) async with self.bot.http_session.get(url=url, params=params, headers=headers) as response: data = await response.json() selection_id = random.choice(data.get("results")).get("id") @@ -71,26 +71,26 @@ class ScaryMovie(commands.Cog): # Get cast names cast = [] - for actor in movie.get('credits', {}).get('cast', [])[:3]: - cast.append(actor.get('name')) + for actor in movie.get("credits", {}).get("cast", [])[:3]: + cast.append(actor.get("name")) # Get director name - director = movie.get('credits', {}).get('crew', []) + director = movie.get("credits", {}).get("crew", []) if director: - director = director[0].get('name') + director = director[0].get("name") # Determine the spookiness rating - rating = '' - rating_count = movie.get('vote_average', 0) / 2 + rating = "" + rating_count = movie.get("vote_average", 0) / 2 for _ in range(int(rating_count)): - rating += ':skull:' + rating += ":skull:" if (rating_count % 1) >= .5: - rating += ':bat:' + rating += ":bat:" # Try to get year of release and runtime - year = movie.get('release_date', [])[:4] - runtime = movie.get('runtime') + year = movie.get("release_date", [])[:4] + runtime = movie.get("runtime") runtime = f"{runtime} minutes" if runtime else None # Not all these attributes will always be present diff --git a/bot/exts/halloween/spookygif.py b/bot/exts/halloween/spookygif.py index bfdf2128..ffb91b1b 100644 --- a/bot/exts/halloween/spookygif.py +++ b/bot/exts/halloween/spookygif.py @@ -19,11 +19,11 @@ class SpookyGif(commands.Cog): async def spookygif(self, ctx: commands.Context) -> None: """Fetches a random gif from the GIPHY API and responds with it.""" async with ctx.typing(): - params = {'api_key': Tokens.giphy, 'tag': 'halloween', 'rating': 'g'} + params = {"api_key": Tokens.giphy, "tag": "halloween", "rating": "g"} # Make a GET request to the Giphy API to get a random halloween gif. - async with self.bot.http_session.get('http://api.giphy.com/v1/gifs/random', params=params) as resp: + async with self.bot.http_session.get("http://api.giphy.com/v1/gifs/random", params=params) as resp: data = await resp.json() - url = data['data']['image_url'] + url = data["data"]["image_url"] embed = discord.Embed(colour=0x9b59b6) embed.title = "A spooooky gif!" diff --git a/bot/exts/halloween/spookynamerate.py b/bot/exts/halloween/spookynamerate.py index 9191f5f6..87172922 100644 --- a/bot/exts/halloween/spookynamerate.py +++ b/bot/exts/halloween/spookynamerate.py @@ -81,7 +81,7 @@ class SpookyNameRate(Cog): # The data cache stores small information such as the current name that is going on and whether it is the first time # the bot is running data = RedisCache() - debug = getenv('SPOOKYNAMERATE_DEBUG', False) # Enable if you do not want to limit the commands to October or if + debug = getenv("SPOOKYNAMERATE_DEBUG", False) # Enable if you do not want to limit the commands to October or if # you do not want to wait till 12 UTC. Note: if debug is enabled and you run `.cogs reload spookynamerate`, it # will automatically start the scoring and announcing the result (without waiting for 12, so do not expect it to.). # Also, it won't wait for the two hours (when the poll closes). diff --git a/bot/exts/halloween/spookyrating.py b/bot/exts/halloween/spookyrating.py index dc398e2e..6c79fbed 100644 --- a/bot/exts/halloween/spookyrating.py +++ b/bot/exts/halloween/spookyrating.py @@ -46,16 +46,16 @@ class SpookyRating(commands.Cog): _, data = SPOOKY_DATA[index] embed = discord.Embed( - title=data['title'], - description=f'{who} scored {spooky_percent}%!', + title=data["title"], + description=f"{who} scored {spooky_percent}%!", color=Colours.orange ) embed.add_field( - name='A whisper from Satan', - value=data['text'] + name="A whisper from Satan", + value=data["text"] ) embed.set_thumbnail( - url=data['image'] + url=data["image"] ) await ctx.send(embed=embed) diff --git a/bot/exts/halloween/spookyreact.py b/bot/exts/halloween/spookyreact.py index dabc3c1f..25e783f4 100644 --- a/bot/exts/halloween/spookyreact.py +++ b/bot/exts/halloween/spookyreact.py @@ -11,13 +11,13 @@ from bot.utils.decorators import in_month log = logging.getLogger(__name__) SPOOKY_TRIGGERS = { - 'spooky': (r"\bspo{2,}ky\b", "\U0001F47B"), - 'skeleton': (r"\bskeleton\b", "\U0001F480"), - 'doot': (r"\bdo{2,}t\b", "\U0001F480"), - 'pumpkin': (r"\bpumpkin\b", "\U0001F383"), - 'halloween': (r"\bhalloween\b", "\U0001F383"), - 'jack-o-lantern': (r"\bjack-o-lantern\b", "\U0001F383"), - 'danger': (r"\bdanger\b", "\U00002620") + "spooky": (r"\bspo{2,}ky\b", "\U0001F47B"), + "skeleton": (r"\bskeleton\b", "\U0001F480"), + "doot": (r"\bdo{2,}t\b", "\U0001F480"), + "pumpkin": (r"\bpumpkin\b", "\U0001F383"), + "halloween": (r"\bhalloween\b", "\U0001F383"), + "jack-o-lantern": (r"\bjack-o-lantern\b", "\U0001F383"), + "danger": (r"\bdanger\b", "\U00002620") } diff --git a/bot/exts/internal_eval/_internal_eval.py b/bot/exts/internal_eval/_internal_eval.py index 757a2a1e..4fe3bc09 100644 --- a/bot/exts/internal_eval/_internal_eval.py +++ b/bot/exts/internal_eval/_internal_eval.py @@ -142,14 +142,14 @@ class InternalEval(commands.Cog): log.trace("Sending the formatted output back to the context") await self._send_output(ctx, eval_context.format_output()) - @commands.group(name='internal', aliases=('int',)) + @commands.group(name="internal", aliases=("int",)) @with_role(Roles.admin) async def internal_group(self, ctx: commands.Context) -> None: """Internal commands. Top secret!""" if not ctx.invoked_subcommand: await invoke_help_command(ctx) - @internal_group.command(name='eval', aliases=('e',)) + @internal_group.command(name="eval", aliases=("e",)) @with_role(Roles.admin) async def eval(self, ctx: commands.Context, *, code: str) -> None: """Run eval in a REPL-like format.""" @@ -157,7 +157,7 @@ class InternalEval(commands.Cog): blocks = [block for block in match if block.group("block")] if len(blocks) > 1: - code = '\n'.join(block.group("code") for block in blocks) + code = "\n".join(block.group("code") for block in blocks) else: match = match[0] if len(blocks) == 0 else blocks[0] code, block, lang, delim = match.group("code", "block", "lang", "delim") @@ -168,7 +168,7 @@ class InternalEval(commands.Cog): code = textwrap.dedent(code) await self._eval(ctx, code) - @internal_group.command(name='reset', aliases=("clear", "exit", "r", "c")) + @internal_group.command(name="reset", aliases=("clear", "exit", "r", "c")) @with_role(Roles.admin) async def reset(self, ctx: commands.Context) -> None: """Reset the context and locals of the eval session.""" diff --git a/bot/exts/valentines/be_my_valentine.py b/bot/exts/valentines/be_my_valentine.py index 051f09b8..e94efda0 100644 --- a/bot/exts/valentines/be_my_valentine.py +++ b/bot/exts/valentines/be_my_valentine.py @@ -70,7 +70,7 @@ class BeMyValentine(commands.Cog): await ctx.send("The lovefest role has been successfully removed!") @commands.cooldown(1, 1800, BucketType.user) - @commands.group(name='bemyvalentine', invoke_without_command=True) + @commands.group(name="bemyvalentine", invoke_without_command=True) async def send_valentine( self, ctx: commands.Context, user: discord.Member, *, valentine_type: str = None ) -> None: @@ -102,14 +102,14 @@ class BeMyValentine(commands.Cog): valentine, title = self.valentine_check(valentine_type) embed = discord.Embed( - title=f'{emoji_1} {title} {user.display_name} {emoji_2}', - description=f'{valentine} \n **{emoji_2}From {ctx.author}{emoji_1}**', + title=f"{emoji_1} {title} {user.display_name} {emoji_2}", + description=f"{valentine} \n **{emoji_2}From {ctx.author}{emoji_1}**", color=Colours.pink ) await channel.send(user.mention, embed=embed) @commands.cooldown(1, 1800, BucketType.user) - @send_valentine.command(name='secret') + @send_valentine.command(name="secret") async def anonymous( self, ctx: commands.Context, user: discord.Member, *, valentine_type: str = None ) -> None: @@ -137,8 +137,8 @@ class BeMyValentine(commands.Cog): valentine, title = self.valentine_check(valentine_type) embed = discord.Embed( - title=f'{emoji_1}{title} {user.display_name}{emoji_2}', - description=f'{valentine} \n **{emoji_2}From anonymous{emoji_1}**', + title=f"{emoji_1}{title} {user.display_name}{emoji_2}", + description=f"{valentine} \n **{emoji_2}From anonymous{emoji_1}**", color=Colours.pink ) await ctx.message.delete() @@ -154,18 +154,18 @@ class BeMyValentine(commands.Cog): if valentine_type is None: valentine, title = self.random_valentine() - elif valentine_type.lower() in ['p', 'poem']: + elif valentine_type.lower() in ["p", "poem"]: valentine = self.valentine_poem() - title = 'A poem dedicated to' + title = "A poem dedicated to" - elif valentine_type.lower() in ['c', 'compliment']: + elif valentine_type.lower() in ["c", "compliment"]: valentine = self.valentine_compliment() - title = 'A compliment for' + title = "A compliment for" else: # in this case, the user decides to type his own valentine. valentine = valentine_type - title = 'A message for' + title = "A message for" return valentine, title @staticmethod @@ -177,23 +177,23 @@ class BeMyValentine(commands.Cog): def random_valentine(self) -> Tuple[str, str]: """Grabs a random poem or a compliment (any message).""" - valentine_poem = random.choice(self.valentines['valentine_poems']) - valentine_compliment = random.choice(self.valentines['valentine_compliments']) + valentine_poem = random.choice(self.valentines["valentine_poems"]) + valentine_compliment = random.choice(self.valentines["valentine_compliments"]) random_valentine = random.choice([valentine_compliment, valentine_poem]) if random_valentine == valentine_poem: - title = 'A poem dedicated to' + title = "A poem dedicated to" else: - title = 'A compliment for ' + title = "A compliment for " return random_valentine, title def valentine_poem(self) -> str: """Grabs a random poem.""" - valentine_poem = random.choice(self.valentines['valentine_poems']) + valentine_poem = random.choice(self.valentines["valentine_poems"]) return valentine_poem def valentine_compliment(self) -> str: """Grabs a random compliment.""" - valentine_compliment = random.choice(self.valentines['valentine_compliments']) + valentine_compliment = random.choice(self.valentines["valentine_compliments"]) return valentine_compliment diff --git a/bot/exts/valentines/myvalenstate.py b/bot/exts/valentines/myvalenstate.py index 19e6b57f..7a0f8318 100644 --- a/bot/exts/valentines/myvalenstate.py +++ b/bot/exts/valentines/myvalenstate.py @@ -47,12 +47,12 @@ class MyValenstate(commands.Cog): """Find the vacation spot(s) with the most matching characters to the invoking user.""" eq_chars = collections.defaultdict(int) if name is None: - author = ctx.author.name.lower().replace(' ', '') + author = ctx.author.name.lower().replace(" ", "") else: - author = name.lower().replace(' ', '') + author = name.lower().replace(" ", "") for state in STATES.keys(): - lower_state = state.lower().replace(' ', '') + lower_state = state.lower().replace(" ", "") eq_chars[state] = self.levenshtein(author, lower_state) matches = [x for x, y in eq_chars.items() if y == min(eq_chars.values())] @@ -73,8 +73,8 @@ class MyValenstate(commands.Cog): " you better" embed = discord.Embed( - title=f'Your Valenstate is {valenstate} \u2764', - description=f'{STATES[valenstate]["text"]}', + title=f"Your Valenstate is {valenstate} \u2764", + description=f"{STATES[valenstate]['text']}", colour=Colours.pink ) embed.add_field(name=embed_title, value=embed_text) diff --git a/bot/exts/valentines/pickuplines.py b/bot/exts/valentines/pickuplines.py index bb322016..216ee13b 100644 --- a/bot/exts/valentines/pickuplines.py +++ b/bot/exts/valentines/pickuplines.py @@ -25,14 +25,14 @@ class PickupLine(commands.Cog): Note that most of them are very cheesy. """ - random_line = random.choice(pickup_lines['lines']) + random_line = random.choice(pickup_lines["lines"]) embed = discord.Embed( - title=':cheese: Your pickup line :cheese:', - description=random_line['line'], + title=":cheese: Your pickup line :cheese:", + description=random_line["line"], color=Colours.pink ) embed.set_thumbnail( - url=random_line.get('image', pickup_lines['placeholder']) + url=random_line.get("image", pickup_lines["placeholder"]) ) await ctx.send(embed=embed) diff --git a/bot/exts/valentines/savethedate.py b/bot/exts/valentines/savethedate.py index bda5d8c6..ed2d2c5f 100644 --- a/bot/exts/valentines/savethedate.py +++ b/bot/exts/valentines/savethedate.py @@ -23,7 +23,7 @@ class SaveTheDate(commands.Cog): @commands.command() async def savethedate(self, ctx: commands.Context) -> None: """Gives you ideas for what to do on a date with your valentine.""" - random_date = random.choice(VALENTINES_DATES['ideas']) + random_date = random.choice(VALENTINES_DATES["ideas"]) emoji_1 = random.choice(HEART_EMOJIS) emoji_2 = random.choice(HEART_EMOJIS) embed = discord.Embed( diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index 237fe5db..72fd93fe 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -14,7 +14,7 @@ from bot.constants import Colours log = logging.getLogger(__name__) -LETTER_EMOJI = ':love_letter:' +LETTER_EMOJI = ":love_letter:" HEART_EMOJIS = [":heart:", ":gift_heart:", ":revolving_hearts:", ":sparkling_heart:", ":two_hearts:"] @@ -32,8 +32,8 @@ class ValentineZodiac(commands.Cog): with explanation_file.open(encoding="utf8") as json_data: zodiac_fact = json.load(json_data) for zodiac_data in zodiac_fact.values(): - zodiac_data['start_at'] = datetime.fromisoformat(zodiac_data['start_at']) - zodiac_data['end_at'] = datetime.fromisoformat(zodiac_data['end_at']) + zodiac_data["start_at"] = datetime.fromisoformat(zodiac_data["start_at"]) + zodiac_data["end_at"] = datetime.fromisoformat(zodiac_data["end_at"]) with compatibility_file.open(encoding="utf8") as json_data: zodiacs = json.load(json_data) @@ -62,10 +62,10 @@ class ValentineZodiac(commands.Cog): log.trace("Making zodiac embed.") embed.title = f"__{zodiac}__" embed.description = self.zodiac_fact[zodiac]["About"] - embed.add_field(name='__Motto__', value=self.zodiac_fact[zodiac]["Motto"], inline=False) - embed.add_field(name='__Strengths__', value=self.zodiac_fact[zodiac]["Strengths"], inline=False) - embed.add_field(name='__Weaknesses__', value=self.zodiac_fact[zodiac]["Weaknesses"], inline=False) - embed.add_field(name='__Full form__', value=self.zodiac_fact[zodiac]["full_form"], inline=False) + embed.add_field(name="__Motto__", value=self.zodiac_fact[zodiac]["Motto"], inline=False) + embed.add_field(name="__Strengths__", value=self.zodiac_fact[zodiac]["Strengths"], inline=False) + embed.add_field(name="__Weaknesses__", value=self.zodiac_fact[zodiac]["Weaknesses"], inline=False) + embed.add_field(name="__Full form__", value=self.zodiac_fact[zodiac]["full_form"], inline=False) embed.set_thumbnail(url=self.zodiac_fact[zodiac]["url"]) else: embed = self.generate_invalidname_embed(zodiac) @@ -79,7 +79,7 @@ class ValentineZodiac(commands.Cog): log.trace("Zodiac name sent.") return zodiac_name - @commands.group(name='zodiac', invoke_without_command=True) + @commands.group(name="zodiac", invoke_without_command=True) async def zodiac(self, ctx: commands.Context, zodiac_sign: str) -> None: """Provides information about zodiac sign by taking zodiac sign name as input.""" final_embed = self.zodiac_build_embed(zodiac_sign) @@ -93,9 +93,9 @@ class ValentineZodiac(commands.Cog): month = month.capitalize() try: month = list(calendar.month_abbr).index(month[:3]) - log.trace('Valid month name entered by user') + log.trace("Valid month name entered by user") except ValueError: - log.info('Invalid month name entered by user') + log.info("Invalid month name entered by user") await ctx.send(f"Sorry, but `{month}` is not a valid month name.") return if (month == 1 and 1 <= date <= 19) or (month == 12 and 22 <= date <= 31): @@ -109,14 +109,14 @@ class ValentineZodiac(commands.Cog): final_embed = discord.Embed() final_embed.color = Colours.soft_red final_embed.description = f"Zodiac sign could not be found because.\n```{e}```" - log.info(f'Error in "zodiac date" command:\n{e}.') + log.info(f"Error in 'zodiac date' command:\n{e}.") else: final_embed = self.zodiac_build_embed(zodiac_sign_based_on_date) await ctx.send(embed=final_embed) log.trace("Embed from date successfully sent.") - @zodiac.command(name="partnerzodiac", aliases=['partner']) + @zodiac.command(name="partnerzodiac", aliases=["partner"]) async def partner_zodiac(self, ctx: commands.Context, zodiac_sign: str) -> None: """Provides a random counter compatible zodiac sign to the given user's zodiac sign.""" embed = discord.Embed() @@ -128,12 +128,12 @@ class ValentineZodiac(commands.Cog): emoji2 = random.choice(HEART_EMOJIS) embed.title = "Zodiac Compatibility" embed.description = ( - f'{zodiac_sign.capitalize()}{emoji1}{compatible_zodiac["Zodiac"]}\n' - f'{emoji2}Compatibility meter : {compatible_zodiac["compatibility_score"]}{emoji2}' + f"{zodiac_sign.capitalize()}{emoji1}{compatible_zodiac['Zodiac']}\n" + f"{emoji2}Compatibility meter : {compatible_zodiac['compatibility_score']}{emoji2}" ) embed.add_field( - name=f'A letter from Dr.Zodiac {LETTER_EMOJI}', - value=compatible_zodiac['description'] + name=f"A letter from Dr.Zodiac {LETTER_EMOJI}", + value=compatible_zodiac["description"] ) else: embed = self.generate_invalidname_embed(zodiac_sign) diff --git a/bot/exts/valentines/whoisvalentine.py b/bot/exts/valentines/whoisvalentine.py index 73cdcf52..3789fad5 100644 --- a/bot/exts/valentines/whoisvalentine.py +++ b/bot/exts/valentines/whoisvalentine.py @@ -18,17 +18,17 @@ with open(Path("bot/resources/valentines/valentine_facts.json"), "r", encoding=" class ValentineFacts(commands.Cog): """A Cog for displaying facts about Saint Valentine.""" - @commands.command(aliases=('whoisvalentine', 'saint_valentine')) + @commands.command(aliases=("whoisvalentine", "saint_valentine")) async def who_is_valentine(self, ctx: commands.Context) -> None: """Displays info about Saint Valentine.""" embed = discord.Embed( title="Who is Saint Valentine?", - description=FACTS['whois'], + description=FACTS["whois"], color=Colours.pink ) embed.set_thumbnail( - url='https://upload.wikimedia.org/wikipedia/commons/thumb/f/f1/Saint_Valentine_-_' - 'facial_reconstruction.jpg/1024px-Saint_Valentine_-_facial_reconstruction.jpg' + url="https://upload.wikimedia.org/wikipedia/commons/thumb/f/f1/Saint_Valentine_-_" + "facial_reconstruction.jpg/1024px-Saint_Valentine_-_facial_reconstruction.jpg" ) await ctx.send(embed=embed) @@ -37,8 +37,8 @@ class ValentineFacts(commands.Cog): async def valentine_fact(self, ctx: commands.Context) -> None: """Shows a random fact about Valentine's Day.""" embed = discord.Embed( - title=choice(FACTS['titles']), - description=choice(FACTS['text']), + title=choice(FACTS["titles"]), + description=choice(FACTS["text"]), color=Colours.pink ) diff --git a/bot/utils/__init__.py b/bot/utils/__init__.py index 35ef0a7b..2fac2086 100644 --- a/bot/utils/__init__.py +++ b/bot/utils/__init__.py @@ -43,12 +43,12 @@ async def disambiguate( or if the user makes an invalid choice. """ if len(entries) == 0: - raise BadArgument('No matches found.') + raise BadArgument("No matches found.") if len(entries) == 1: return entries[0] - choices = (f'{index}: {entry}' for index, entry in enumerate(entries, start=1)) + choices = (f"{index}: {entry}" for index, entry in enumerate(entries, start=1)) def check(message: discord.Message) -> bool: return (message.content.isdigit() @@ -59,7 +59,7 @@ async def disambiguate( if embed is None: embed = discord.Embed() - coro1 = ctx.bot.wait_for('message', check=check, timeout=timeout) + coro1 = ctx.bot.wait_for("message", check=check, timeout=timeout) coro2 = LinePaginator.paginate(choices, ctx, embed=embed, max_lines=entries_per_page, empty=empty, max_size=6000, timeout=9000) @@ -74,7 +74,7 @@ async def disambiguate( if result is None: for coro in pending: coro.cancel() - raise BadArgument('Canceled.') + raise BadArgument("Canceled.") # Pagination was not initiated, only one page if result.author == ctx.bot.user: @@ -85,7 +85,7 @@ async def disambiguate( for coro in pending: coro.cancel() except asyncio.TimeoutError: - raise BadArgument('Timed out.') + raise BadArgument("Timed out.") # Guaranteed to not error because of isdigit() in check index = int(result.content) @@ -93,7 +93,7 @@ async def disambiguate( try: return entries[index - 1] except IndexError: - raise BadArgument('Invalid choice.') + raise BadArgument("Invalid choice.") def replace_many( @@ -139,7 +139,7 @@ def replace_many( return replacement # Clean punctuation from word so string methods work - cleaned_word = word.translate(str.maketrans('', '', string.punctuation)) + cleaned_word = word.translate(str.maketrans("", "", string.punctuation)) if cleaned_word.isupper(): return replacement.upper() elif cleaned_word[0].isupper(): diff --git a/bot/utils/checks.py b/bot/utils/checks.py index 9dd4dde0..3783dd38 100644 --- a/bot/utils/checks.py +++ b/bot/utils/checks.py @@ -154,8 +154,8 @@ def cooldown_with_role_bypass(rate: int, per: float, type: BucketType = BucketTy # # If the `before_invoke` detail is ever a problem then I can quickly just swap over. if not isinstance(command, Command): - raise TypeError('Decorator `cooldown_with_role_bypass` must be applied after the command decorator. ' - 'This means it has to be above the command decorator in the code.') + raise TypeError("Decorator `cooldown_with_role_bypass` must be applied after the command decorator. " + "This means it has to be above the command decorator in the code.") command._before_invoke = predicate diff --git a/bot/utils/decorators.py b/bot/utils/decorators.py index 60066dc4..c0783144 100644 --- a/bot/utils/decorators.py +++ b/bot/utils/decorators.py @@ -269,7 +269,7 @@ def whitelist_check(**default_kwargs: t.Container[int]) -> t.Callable[[Context], channels.update(channel.id for channel in category.text_channels) if channels: - channels_str = ', '.join(f"<#{c_id}>" for c_id in channels) + channels_str = ", ".join(f"<#{c_id}>" for c_id in channels) message = f"Sorry, but you may only use this command within {channels_str}." else: message = "Sorry, but you may not use this command." diff --git a/bot/utils/extensions.py b/bot/utils/extensions.py index 459588a1..cd491c4b 100644 --- a/bot/utils/extensions.py +++ b/bot/utils/extensions.py @@ -35,8 +35,8 @@ def walk_extensions() -> Iterator[str]: async def invoke_help_command(ctx: Context) -> None: """Invoke the help command or default help command if help extensions is not loaded.""" - if 'bot.exts.evergreen.help' in ctx.bot.extensions: - help_command = ctx.bot.get_command('help') + if "bot.exts.evergreen.help" in ctx.bot.extensions: + help_command = ctx.bot.get_command("help") await ctx.invoke(help_command, ctx.command.qualified_name) return await ctx.send_help(ctx.command) diff --git a/bot/utils/halloween/spookifications.py b/bot/utils/halloween/spookifications.py index 11f69850..f69dd6fd 100644 --- a/bot/utils/halloween/spookifications.py +++ b/bot/utils/halloween/spookifications.py @@ -13,16 +13,16 @@ def inversion(im: Image) -> Image: Returns an inverted image when supplied with an Image object. """ - im = im.convert('RGB') + im = im.convert("RGB") inv = ImageOps.invert(im) return inv def pentagram(im: Image) -> Image: """Adds pentagram to the image.""" - im = im.convert('RGB') + im = im.convert("RGB") wt, ht = im.size - penta = Image.open('bot/resources/halloween/bloody-pentagram.png') + penta = Image.open("bot/resources/halloween/bloody-pentagram.png") penta = penta.resize((wt, ht)) im.paste(penta, (0, 0), penta) return im @@ -35,9 +35,9 @@ def bat(im: Image) -> Image: The bat silhoutte is of a size at least one-fifths that of the original image and may be rotated up to 90 degrees anti-clockwise. """ - im = im.convert('RGB') + im = im.convert("RGB") wt, ht = im.size - bat = Image.open('bot/resources/halloween/bat-clipart.png') + bat = Image.open("bot/resources/halloween/bat-clipart.png") bat_size = randint(wt//10, wt//7) rot = randint(0, 90) bat = bat.resize((bat_size, bat_size)) diff --git a/bot/utils/pagination.py b/bot/utils/pagination.py index a97dd023..a073a00b 100644 --- a/bot/utils/pagination.py +++ b/bot/utils/pagination.py @@ -26,7 +26,7 @@ class EmptyPaginatorEmbed(Exception): class LinePaginator(Paginator): """A class that aids in paginating code blocks for Discord messages.""" - def __init__(self, prefix: str = '```', suffix: str = '```', max_size: int = 2000, max_lines: int = None): + def __init__(self, prefix: str = "```", suffix: str = "```", max_size: int = 2000, max_lines: int = None): """ Overrides the Paginator.__init__ from inside discord.ext.commands. @@ -44,7 +44,7 @@ class LinePaginator(Paginator): self._count = len(prefix) + 1 # prefix + newline self._pages = [] - def add_line(self, line: str = '', *, empty: bool = False) -> None: + def add_line(self, line: str = "", *, empty: bool = False) -> None: """ Adds a line to the current page. @@ -56,7 +56,7 @@ class LinePaginator(Paginator): If `empty` is True, an empty line will be placed after the a given `line`. """ if len(line) > self.max_size - len(self.prefix) - 2: - raise RuntimeError('Line exceeds maximum page size %s' % (self.max_size - len(self.prefix) - 2)) + raise RuntimeError("Line exceeds maximum page size %s" % (self.max_size - len(self.prefix) - 2)) if self.max_lines is not None: if self._linecount >= self.max_lines: @@ -71,7 +71,7 @@ class LinePaginator(Paginator): self._current_page.append(line) if empty: - self._current_page.append('') + self._current_page.append("") self._count += 1 @classmethod -- cgit v1.2.3 From c051abf49ce5bb82d127c8ddfad7ece431bb028f Mon Sep 17 00:00:00 2001 From: ToxicKidz <78174417+ToxicKidz@users.noreply.github.com> Date: Tue, 4 May 2021 13:15:55 -0400 Subject: chore: Apply suggested changes --- bot/exts/evergreen/magic_8ball.py | 6 +++--- bot/exts/evergreen/minesweeper.py | 4 ++-- bot/exts/evergreen/tic_tac_toe.py | 4 ++-- bot/exts/evergreen/wonder_twins.py | 4 ++-- bot/exts/halloween/hacktoberstats.py | 6 +++--- 5 files changed, 12 insertions(+), 12 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/magic_8ball.py b/bot/exts/evergreen/magic_8ball.py index 708aa6d2..7c9b929d 100644 --- a/bot/exts/evergreen/magic_8ball.py +++ b/bot/exts/evergreen/magic_8ball.py @@ -13,7 +13,7 @@ log = logging.getLogger(__name__) class Magic8ball(commands.Cog): """A Magic 8ball command to respond to a user's question.""" - def __init__(self, _bot: Bot): + def __init__(self): with open(Path("bot/resources/evergreen/magic8ball.json"), "r", encoding="utf8") as file: self.answers = json.load(file) @@ -28,5 +28,5 @@ class Magic8ball(commands.Cog): def setup(bot: Bot) -> None: - """Load the Magic8Ball cog.""" - bot.add_cog(Magic8ball(bot)) + """Load the Magic8Ball Cog.""" + bot.add_cog(Magic8ball()) diff --git a/bot/exts/evergreen/minesweeper.py b/bot/exts/evergreen/minesweeper.py index f2c5e656..bd28d365 100644 --- a/bot/exts/evergreen/minesweeper.py +++ b/bot/exts/evergreen/minesweeper.py @@ -79,7 +79,7 @@ GamesDict = typing.Dict[int, Game] class Minesweeper(commands.Cog): """Play a game of Minesweeper.""" - def __init__(self, _bot: Bot) -> None: + def __init__(self) -> None: self.games: GamesDict = {} # Store the currently running games @commands.group(name="minesweeper", aliases=("ms",), invoke_without_command=True) @@ -295,4 +295,4 @@ class Minesweeper(commands.Cog): def setup(bot: Bot) -> None: """Load the Minesweeper cog.""" - bot.add_cog(Minesweeper(bot)) + bot.add_cog(Minesweeper()) diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index 7b387c0a..bd5e0102 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -246,7 +246,7 @@ def is_requester_free() -> t.Callable: class TicTacToe(Cog): """TicTacToe cog contains tic-tac-toe game commands.""" - def __init__(self, _bot: Bot): + def __init__(self): self.games: t.List[Game] = [] @guild_only() @@ -323,4 +323,4 @@ class TicTacToe(Cog): def setup(bot: Bot) -> None: """Load the TicTacToe cog.""" - bot.add_cog(TicTacToe(bot)) + bot.add_cog(TicTacToe()) diff --git a/bot/exts/evergreen/wonder_twins.py b/bot/exts/evergreen/wonder_twins.py index 9fa2d7f8..437d69f1 100644 --- a/bot/exts/evergreen/wonder_twins.py +++ b/bot/exts/evergreen/wonder_twins.py @@ -10,7 +10,7 @@ from bot.bot import Bot class WonderTwins(Cog): """Cog for a Wonder Twins inspired command.""" - def __init__(self, _bot: Bot): + def __init__(self): with open(Path.cwd() / "bot" / "resources" / "evergreen" / "wonder_twins.yaml", "r", encoding="utf-8") as f: info = yaml.load(f, Loader=yaml.FullLoader) self.water_types = info["water_types"] @@ -46,4 +46,4 @@ class WonderTwins(Cog): def setup(bot: Bot) -> None: """Load the WonderTwins cog.""" - bot.add_cog(WonderTwins(bot)) + bot.add_cog(WonderTwins()) diff --git a/bot/exts/halloween/hacktoberstats.py b/bot/exts/halloween/hacktoberstats.py index 25da9ad5..33e9ca31 100644 --- a/bot/exts/halloween/hacktoberstats.py +++ b/bot/exts/halloween/hacktoberstats.py @@ -10,7 +10,7 @@ from async_rediscache import RedisCache from discord.ext import commands from bot.bot import Bot -from bot.constants import Channels, Month, NEGATIVE_REPLIES, Tokens, WHITELISTED_CHANNELS +from bot.constants import Channels, Colours, Month, NEGATIVE_REPLIES, Tokens, WHITELISTED_CHANNELS from bot.utils.decorators import in_month, whitelist_override log = logging.getLogger(__name__) @@ -157,7 +157,7 @@ class HacktoberStats(commands.Cog): stats_embed = discord.Embed( title=f"{github_username}'s Hacktoberfest", - color=0x9c4af7, + color=Colours.purple, description=( f"{github_username} has made {n} valid " f"{self._contributionator(n)} in " @@ -227,7 +227,7 @@ class HacktoberStats(commands.Cog): f"+created:{date_range}" f"&per_page={per_page}" ) - log.logProcesses.debug(f"GitHub query URL generated: {query_url}") + log.debug(f"GitHub query URL generated: {query_url}") jsonresp = await self._fetch_url(query_url, REQUEST_HEADERS) if "message" in jsonresp: -- cgit v1.2.3 From dd20f59b9fb12288f286d479b5719697ab1a30f4 Mon Sep 17 00:00:00 2001 From: ToxicKidz <78174417+ToxicKidz@users.noreply.github.com> Date: Tue, 4 May 2021 15:04:18 -0400 Subject: Apply suggested changes to hanukkah_embed.py --- bot/exts/christmas/hanukkah_embed.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'bot') diff --git a/bot/exts/christmas/hanukkah_embed.py b/bot/exts/christmas/hanukkah_embed.py index 32002f76..214044b8 100644 --- a/bot/exts/christmas/hanukkah_embed.py +++ b/bot/exts/christmas/hanukkah_embed.py @@ -65,24 +65,24 @@ class HanukkahEmbed(commands.Cog): hanukkah_start_hour = 18 if hours < hanukkah_start_hour: embed.description = (f"Hanukkah hasnt started yet, " - f"it will start in about {hanukkah_start_hour-hours} hour/s.") + f"it will start in about {hanukkah_start_hour - hours} hour/s.") await ctx.send(embed=embed) return elif hours > hanukkah_start_hour: embed.description = (f"It is the starting day of Hanukkah ! " - f"Its been {hours-hanukkah_start_hour} hours hanukkah started !") + f"Its been {hours - hanukkah_start_hour} hours hanukkah started !") await ctx.send(embed=embed) return festival_day = self.hanukkah_days.index(day) number_suffixes = ["st", "nd", "rd", "th"] suffix = "" - if int(festival_day) == 1: + if festival_day == 1: suffix = number_suffixes[0] - if int(festival_day) == 2: + if festival_day == 2: suffix = number_suffixes[1] - if int(festival_day) == 3: + if festival_day == 3: suffix = number_suffixes[2] - if int(festival_day) > 3: + if festival_day > 3: suffix = number_suffixes[3] message = "" for _ in range(1, festival_day + 1): -- cgit v1.2.3 From 6d31315fa72ca9e6ccd7553e78548fed6e5e4829 Mon Sep 17 00:00:00 2001 From: ToxicKidz <78174417+ToxicKidz@users.noreply.github.com> Date: Tue, 4 May 2021 15:44:31 -0400 Subject: fix: Pass bot only when __init__ is defined --- bot/exts/easter/bunny_name_generator.py | 2 +- bot/exts/easter/egg_decorating.py | 2 +- bot/exts/evergreen/avatar_modification/avatar_modify.py | 5 +++-- bot/exts/evergreen/bookmark.py | 2 +- bot/exts/evergreen/catify.py | 8 +++----- bot/exts/evergreen/conversationstarters.py | 2 +- bot/exts/evergreen/emoji.py | 2 +- bot/exts/evergreen/error_handler.py | 2 +- bot/exts/evergreen/latex.py | 2 +- bot/exts/evergreen/pythonfacts.py | 2 +- bot/exts/evergreen/snakes/__init__.py | 5 ++--- bot/exts/evergreen/source.py | 2 +- bot/exts/evergreen/speedrun.py | 2 +- bot/exts/evergreen/timed.py | 2 +- bot/exts/evergreen/uptime.py | 2 +- bot/exts/halloween/timeleft.py | 5 +---- bot/exts/valentines/be_my_valentine.py | 4 ++-- bot/exts/valentines/lovecalculator.py | 2 +- bot/exts/valentines/movie_generator.py | 4 ++-- bot/exts/valentines/myvalenstate.py | 7 ++----- bot/exts/valentines/pickuplines.py | 2 +- bot/exts/valentines/savethedate.py | 2 +- bot/exts/valentines/valentine_zodiac.py | 2 +- bot/exts/valentines/whoisvalentine.py | 2 +- 24 files changed, 32 insertions(+), 40 deletions(-) (limited to 'bot') diff --git a/bot/exts/easter/bunny_name_generator.py b/bot/exts/easter/bunny_name_generator.py index 23f85226..19a0b0f6 100644 --- a/bot/exts/easter/bunny_name_generator.py +++ b/bot/exts/easter/bunny_name_generator.py @@ -88,4 +88,4 @@ class BunnyNameGenerator(commands.Cog): def setup(bot: Bot) -> None: """Load the Bunny Name Generator Cog.""" - bot.add_cog(BunnyNameGenerator(bot)) + bot.add_cog(BunnyNameGenerator()) diff --git a/bot/exts/easter/egg_decorating.py b/bot/exts/easter/egg_decorating.py index 1432fa31..3744989d 100644 --- a/bot/exts/easter/egg_decorating.py +++ b/bot/exts/easter/egg_decorating.py @@ -118,4 +118,4 @@ class EggDecorating(commands.Cog): def setup(bot: Bot) -> None: """Load the Egg decorating Cog.""" - bot.add_cog(EggDecorating(bot)) + bot.add_cog(EggDecorating()) diff --git a/bot/exts/evergreen/avatar_modification/avatar_modify.py b/bot/exts/evergreen/avatar_modification/avatar_modify.py index 0baee8b2..2304e459 100644 --- a/bot/exts/evergreen/avatar_modification/avatar_modify.py +++ b/bot/exts/evergreen/avatar_modification/avatar_modify.py @@ -11,6 +11,7 @@ from aiohttp import client_exceptions from discord.ext import commands from discord.ext.commands.errors import BadArgument +from bot.bot import Bot from bot.constants import Client, Colours, Emojis from bot.exts.evergreen.avatar_modification._effects import PfpEffects from bot.utils.extensions import invoke_help_command @@ -58,7 +59,7 @@ def file_safe_name(effect: str, display_name: str) -> str: class AvatarModify(commands.Cog): """Various commands for users to apply affects to their own avatars.""" - def __init__(self, bot: commands.Bot) -> None: + def __init__(self, bot: Bot) -> None: self.bot = bot async def _fetch_member(self, member_id: int) -> t.Optional[discord.Member]: @@ -309,6 +310,6 @@ class AvatarModify(commands.Cog): await ctx.send(file=file, embed=embed) -def setup(bot: commands.Bot) -> None: +def setup(bot: Bot) -> None: """Load the PfpModify cog.""" bot.add_cog(AvatarModify(bot)) diff --git a/bot/exts/evergreen/bookmark.py b/bot/exts/evergreen/bookmark.py index 6a272784..f0fad0ea 100644 --- a/bot/exts/evergreen/bookmark.py +++ b/bot/exts/evergreen/bookmark.py @@ -60,4 +60,4 @@ class Bookmark(commands.Cog): def setup(bot: Bot) -> None: """Load the Bookmark cog.""" - bot.add_cog(Bookmark(bot)) + bot.add_cog(Bookmark()) diff --git a/bot/exts/evergreen/catify.py b/bot/exts/evergreen/catify.py index d8a7442d..b4ae4a25 100644 --- a/bot/exts/evergreen/catify.py +++ b/bot/exts/evergreen/catify.py @@ -5,6 +5,7 @@ from typing import Optional from discord import AllowedMentions, Embed, Forbidden from discord.ext import commands +from bot.bot import Bot from bot.constants import Cats, Colours, NEGATIVE_REPLIES from bot.utils import helpers @@ -12,9 +13,6 @@ from bot.utils import helpers class Catify(commands.Cog): """Cog for the catify command.""" - def __init__(self, bot: commands.Bot): - self.bot = bot - @commands.command(aliases=["ᓚᘏᗢify", "ᓚᘏᗢ"]) async def catify(self, ctx: commands.Context, *, text: Optional[str]) -> None: """ @@ -82,6 +80,6 @@ class Catify(commands.Cog): ) -def setup(bot: commands.Bot) -> None: +def setup(bot: Bot) -> None: """Loads the catify cog.""" - bot.add_cog(Catify(bot)) + bot.add_cog(Catify()) diff --git a/bot/exts/evergreen/conversationstarters.py b/bot/exts/evergreen/conversationstarters.py index 4fe8c47c..fdc4467a 100644 --- a/bot/exts/evergreen/conversationstarters.py +++ b/bot/exts/evergreen/conversationstarters.py @@ -66,4 +66,4 @@ class ConvoStarters(commands.Cog): def setup(bot: Bot) -> None: """Load the ConvoStarters cog.""" - bot.add_cog(ConvoStarters(bot)) + bot.add_cog(ConvoStarters()) diff --git a/bot/exts/evergreen/emoji.py b/bot/exts/evergreen/emoji.py index e7452a15..11615214 100644 --- a/bot/exts/evergreen/emoji.py +++ b/bot/exts/evergreen/emoji.py @@ -120,4 +120,4 @@ class Emojis(commands.Cog): def setup(bot: Bot) -> None: """Load the Emojis cog.""" - bot.add_cog(Emojis(bot)) + bot.add_cog(Emojis()) diff --git a/bot/exts/evergreen/error_handler.py b/bot/exts/evergreen/error_handler.py index 5cd8d28d..62529f52 100644 --- a/bot/exts/evergreen/error_handler.py +++ b/bot/exts/evergreen/error_handler.py @@ -135,4 +135,4 @@ class CommandErrorHandler(commands.Cog): def setup(bot: Bot) -> None: """Load the ErrorHandler cog.""" - bot.add_cog(CommandErrorHandler(bot)) + bot.add_cog(CommandErrorHandler()) diff --git a/bot/exts/evergreen/latex.py b/bot/exts/evergreen/latex.py index 3a93907a..54d25267 100644 --- a/bot/exts/evergreen/latex.py +++ b/bot/exts/evergreen/latex.py @@ -93,4 +93,4 @@ class Latex(commands.Cog): def setup(bot: Bot) -> None: """Load the Latex Cog.""" - bot.add_cog(Latex(bot)) + bot.add_cog(Latex()) diff --git a/bot/exts/evergreen/pythonfacts.py b/bot/exts/evergreen/pythonfacts.py index 2dc4996f..c0086c20 100644 --- a/bot/exts/evergreen/pythonfacts.py +++ b/bot/exts/evergreen/pythonfacts.py @@ -28,4 +28,4 @@ class PythonFacts(commands.Cog): def setup(bot: Bot) -> None: """Load the PythonFacts Cog.""" - bot.add_cog(PythonFacts(bot)) + bot.add_cog(PythonFacts()) diff --git a/bot/exts/evergreen/snakes/__init__.py b/bot/exts/evergreen/snakes/__init__.py index bc42f0c2..049bc964 100644 --- a/bot/exts/evergreen/snakes/__init__.py +++ b/bot/exts/evergreen/snakes/__init__.py @@ -1,12 +1,11 @@ import logging -from discord.ext import commands - +from bot.bot import Bot from bot.exts.evergreen.snakes._snakes_cog import Snakes log = logging.getLogger(__name__) -def setup(bot: commands.Bot) -> None: +def setup(bot: Bot) -> None: """Snakes Cog load.""" bot.add_cog(Snakes(bot)) diff --git a/bot/exts/evergreen/source.py b/bot/exts/evergreen/source.py index 2f25e4cb..685b3111 100644 --- a/bot/exts/evergreen/source.py +++ b/bot/exts/evergreen/source.py @@ -104,4 +104,4 @@ class BotSource(commands.Cog): def setup(bot: Bot) -> None: """Load the BotSource cog.""" - bot.add_cog(BotSource(bot)) + bot.add_cog(BotSource()) diff --git a/bot/exts/evergreen/speedrun.py b/bot/exts/evergreen/speedrun.py index 110d5c13..d9820287 100644 --- a/bot/exts/evergreen/speedrun.py +++ b/bot/exts/evergreen/speedrun.py @@ -23,4 +23,4 @@ class Speedrun(commands.Cog): def setup(bot: Bot) -> None: """Load the Speedrun cog.""" - bot.add_cog(Speedrun(bot)) + bot.add_cog(Speedrun()) diff --git a/bot/exts/evergreen/timed.py b/bot/exts/evergreen/timed.py index 42a77346..491231cc 100644 --- a/bot/exts/evergreen/timed.py +++ b/bot/exts/evergreen/timed.py @@ -45,4 +45,4 @@ class TimedCommands(commands.Cog): def setup(bot: Bot) -> None: """Load the Timed cog.""" - bot.add_cog(TimedCommands(bot)) + bot.add_cog(TimedCommands()) diff --git a/bot/exts/evergreen/uptime.py b/bot/exts/evergreen/uptime.py index d2509e6f..b390e7f7 100644 --- a/bot/exts/evergreen/uptime.py +++ b/bot/exts/evergreen/uptime.py @@ -28,4 +28,4 @@ class Uptime(commands.Cog): def setup(bot: Bot) -> None: """Load the Uptime cog.""" - bot.add_cog(Uptime(bot)) + bot.add_cog(Uptime()) diff --git a/bot/exts/halloween/timeleft.py b/bot/exts/halloween/timeleft.py index f4ab9284..e80025dc 100644 --- a/bot/exts/halloween/timeleft.py +++ b/bot/exts/halloween/timeleft.py @@ -12,9 +12,6 @@ log = logging.getLogger(__name__) class TimeLeft(commands.Cog): """A Cog that tells you how long left until Hacktober is over!""" - def __init__(self, bot: Bot): - self.bot = bot - def in_hacktober(self) -> bool: """Return True if the current time is within Hacktoberfest.""" _, end, start = self.load_date() @@ -68,4 +65,4 @@ class TimeLeft(commands.Cog): def setup(bot: Bot) -> None: """Load the Time Left Cog.""" - bot.add_cog(TimeLeft(bot)) + bot.add_cog(TimeLeft()) diff --git a/bot/exts/valentines/be_my_valentine.py b/bot/exts/valentines/be_my_valentine.py index e94efda0..cf6099d2 100644 --- a/bot/exts/valentines/be_my_valentine.py +++ b/bot/exts/valentines/be_my_valentine.py @@ -21,7 +21,7 @@ HEART_EMOJIS = [":heart:", ":gift_heart:", ":revolving_hearts:", ":sparkling_hea class BeMyValentine(commands.Cog): """A cog that sends Valentines to other users!""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Bot): self.bot = bot self.valentines = self.load_json() @@ -198,5 +198,5 @@ class BeMyValentine(commands.Cog): def setup(bot: Bot) -> None: - """Be my Valentine Cog load.""" + """Load the Be my Valentine Cog.""" bot.add_cog(BeMyValentine(bot)) diff --git a/bot/exts/valentines/lovecalculator.py b/bot/exts/valentines/lovecalculator.py index c2a5da26..e55dc128 100644 --- a/bot/exts/valentines/lovecalculator.py +++ b/bot/exts/valentines/lovecalculator.py @@ -93,5 +93,5 @@ class LoveCalculator(Cog): def setup(bot: Bot) -> None: - """Love calculator Cog load.""" + """Load the Love calculator Cog.""" bot.add_cog(LoveCalculator()) diff --git a/bot/exts/valentines/movie_generator.py b/bot/exts/valentines/movie_generator.py index 461255ff..4508c3b2 100644 --- a/bot/exts/valentines/movie_generator.py +++ b/bot/exts/valentines/movie_generator.py @@ -16,7 +16,7 @@ log = logging.getLogger(__name__) class RomanceMovieFinder(commands.Cog): """A Cog that returns a random romance movie suggestion to a user.""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Bot): self.bot = bot @commands.command(name="romancemovie") @@ -62,5 +62,5 @@ class RomanceMovieFinder(commands.Cog): def setup(bot: Bot) -> None: - """Romance movie Cog load.""" + """Load the Romance movie Cog.""" bot.add_cog(RomanceMovieFinder(bot)) diff --git a/bot/exts/valentines/myvalenstate.py b/bot/exts/valentines/myvalenstate.py index 7a0f8318..1c67984b 100644 --- a/bot/exts/valentines/myvalenstate.py +++ b/bot/exts/valentines/myvalenstate.py @@ -19,9 +19,6 @@ with open(Path("bot/resources/valentines/valenstates.json"), "r", encoding="utf8 class MyValenstate(commands.Cog): """A Cog to find your most likely Valentine's vacation destination.""" - def __init__(self, bot: commands.Bot): - self.bot = bot - def levenshtein(self, source: str, goal: str) -> int: """Calculates the Levenshtein Distance between source and goal.""" if len(source) < len(goal): @@ -83,5 +80,5 @@ class MyValenstate(commands.Cog): def setup(bot: Bot) -> None: - """Valenstate Cog load.""" - bot.add_cog(MyValenstate(bot)) + """Load the Valenstate Cog.""" + bot.add_cog(MyValenstate()) diff --git a/bot/exts/valentines/pickuplines.py b/bot/exts/valentines/pickuplines.py index 216ee13b..909169e6 100644 --- a/bot/exts/valentines/pickuplines.py +++ b/bot/exts/valentines/pickuplines.py @@ -38,5 +38,5 @@ class PickupLine(commands.Cog): def setup(bot: Bot) -> None: - """Pickup lines Cog load.""" + """Load the Pickup lines Cog.""" bot.add_cog(PickupLine()) diff --git a/bot/exts/valentines/savethedate.py b/bot/exts/valentines/savethedate.py index ed2d2c5f..cc16f5c9 100644 --- a/bot/exts/valentines/savethedate.py +++ b/bot/exts/valentines/savethedate.py @@ -35,5 +35,5 @@ class SaveTheDate(commands.Cog): def setup(bot: Bot) -> None: - """Save the date Cog Load.""" + """Load the Save the date Cog.""" bot.add_cog(SaveTheDate()) diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index 72fd93fe..a444a355 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -142,5 +142,5 @@ class ValentineZodiac(commands.Cog): def setup(bot: Bot) -> None: - """Valentine zodiac Cog load.""" + """Load the Valentine zodiac Cog.""" bot.add_cog(ValentineZodiac()) diff --git a/bot/exts/valentines/whoisvalentine.py b/bot/exts/valentines/whoisvalentine.py index 3789fad5..3f23201f 100644 --- a/bot/exts/valentines/whoisvalentine.py +++ b/bot/exts/valentines/whoisvalentine.py @@ -46,5 +46,5 @@ class ValentineFacts(commands.Cog): def setup(bot: Bot) -> None: - """Who is Valentine Cog load.""" + """Load the Who is Valentine Cog.""" bot.add_cog(ValentineFacts()) -- cgit v1.2.3 From d73eaf3d4d0724444161d0232533b2daaf7baedd Mon Sep 17 00:00:00 2001 From: ToxicKidz <78174417+ToxicKidz@users.noreply.github.com> Date: Tue, 4 May 2021 16:03:25 -0400 Subject: chore: Change back to the original string --- bot/exts/evergreen/game.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/game.py b/bot/exts/evergreen/game.py index 7abbadcd..f3d4e9a2 100644 --- a/bot/exts/evergreen/game.py +++ b/bot/exts/evergreen/game.py @@ -260,7 +260,7 @@ class Games(Cog): display_possibilities = "`, `".join(p[1] for p in possibilities) await ctx.send( f"Invalid genre `{genre}`. " - f"Maybe you meant `{display_possibilities}`?" if display_possibilities else '' + f"{f'Maybe you meant `{display_possibilities}`?' if display_possibilities else ''}" ) return elif len(possibilities) == 1: -- cgit v1.2.3 From f7c9fecf5dbab7320ff91eecd322c5521034b628 Mon Sep 17 00:00:00 2001 From: ToxicKidz <78174417+ToxicKidz@users.noreply.github.com> Date: Tue, 4 May 2021 16:15:57 -0400 Subject: chore: Replace the remaining double quotes with double quotes --- bot/__init__.py | 2 +- bot/exts/evergreen/reddit.py | 2 +- bot/exts/halloween/halloweenify.py | 4 ++-- bot/exts/valentines/lovecalculator.py | 12 ++++++------ bot/utils/pagination.py | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) (limited to 'bot') diff --git a/bot/__init__.py b/bot/__init__.py index 71b7c8a3..669f9f5d 100644 --- a/bot/__init__.py +++ b/bot/__init__.py @@ -64,7 +64,7 @@ logging.getLogger("matplotlib").setLevel(logging.ERROR) # Setup new logging configuration logging.basicConfig( - format='%(asctime)s - %(name)s %(levelname)s: %(message)s', + format="%(asctime)s - %(name)s %(levelname)s: %(message)s", datefmt="%D %H:%M:%S", level=logging.TRACE if Client.debug else logging.DEBUG, handlers=[console_handler, file_handler], diff --git a/bot/exts/evergreen/reddit.py b/bot/exts/evergreen/reddit.py index 82af6ce9..f2b95fe2 100644 --- a/bot/exts/evergreen/reddit.py +++ b/bot/exts/evergreen/reddit.py @@ -84,7 +84,7 @@ class Reddit(commands.Cog): for i, post in enumerate(random_posts, start=1): post_title = post["data"]["title"][0:50] - post_url = post['data']['url'] + post_url = post["data"]["url"] if post_title == "": post_title = "No Title." elif post_title == post_url: diff --git a/bot/exts/halloween/halloweenify.py b/bot/exts/halloween/halloweenify.py index 47b20a2a..df55b55d 100644 --- a/bot/exts/halloween/halloweenify.py +++ b/bot/exts/halloween/halloweenify.py @@ -26,8 +26,8 @@ class Halloweenify(commands.Cog): # Choose a random character from our list we loaded above and set apart the nickname and image url. character = choice(data["characters"]) - nickname = ''.join([nickname for nickname in character]) - image = ''.join([character[nickname] for nickname in character]) + nickname = "".join([nickname for nickname in character]) + image = "".join([character[nickname] for nickname in character]) # Build up a Embed embed = discord.Embed() diff --git a/bot/exts/valentines/lovecalculator.py b/bot/exts/valentines/lovecalculator.py index e55dc128..9096d994 100644 --- a/bot/exts/valentines/lovecalculator.py +++ b/bot/exts/valentines/lovecalculator.py @@ -23,7 +23,7 @@ with Path("bot/resources/valentines/love_matches.json").open(encoding="utf8") as class LoveCalculator(Cog): """A cog for calculating the love between two people.""" - @commands.command(aliases=('love_calculator', 'love_calc')) + @commands.command(aliases=("love_calculator", "love_calc")) @commands.cooldown(rate=1, per=5, type=commands.BucketType.user) async def love(self, ctx: commands.Context, who: Union[Member, str], whom: Union[Member, str] = None) -> None: """ @@ -61,7 +61,7 @@ class LoveCalculator(Cog): # Make sure user didn't provide something silly such as 10 spaces if not (who and whom): - raise BadArgument('Arguments be non-empty strings.') + raise BadArgument("Arguments be non-empty strings.") # Hash inputs to guarantee consistent results (hashing algorithm choice arbitrary) # @@ -78,15 +78,15 @@ class LoveCalculator(Cog): # We only need the dict, so we can ditch the first element _, data = LOVE_DATA[index] - status = random.choice(data['titles']) + status = random.choice(data["titles"]) embed = discord.Embed( title=status, - description=f'{who} \N{HEAVY BLACK HEART} {whom} scored {love_percent}%!\n\u200b', + description=f"{who} \N{HEAVY BLACK HEART} {whom} scored {love_percent}%!\n\u200b", color=discord.Color.dark_magenta() ) embed.add_field( - name='A letter from Dr. Love:', - value=data['text'] + name="A letter from Dr. Love:", + value=data["text"] ) await ctx.send(embed=embed) diff --git a/bot/utils/pagination.py b/bot/utils/pagination.py index a073a00b..e0dc25b4 100644 --- a/bot/utils/pagination.py +++ b/bot/utils/pagination.py @@ -283,7 +283,7 @@ class ImagePaginator(Paginator): self.images = [] self._pages = [] - def add_line(self, line: str = '', *, empty: bool = False) -> None: + def add_line(self, line: str = "", *, empty: bool = False) -> None: """ Adds a line to each page, usually just 1 line in this context. -- cgit v1.2.3 From ccb7cd7bc4ab43e4d777093a14c921a64c884f6e Mon Sep 17 00:00:00 2001 From: ToxicKidz <78174417+ToxicKidz@users.noreply.github.com> Date: Wed, 5 May 2021 11:28:01 -0400 Subject: chore: Add a 5 second cooldown per user to .catify --- bot/exts/evergreen/catify.py | 1 + 1 file changed, 1 insertion(+) (limited to 'bot') diff --git a/bot/exts/evergreen/catify.py b/bot/exts/evergreen/catify.py index d8a7442d..a175602f 100644 --- a/bot/exts/evergreen/catify.py +++ b/bot/exts/evergreen/catify.py @@ -16,6 +16,7 @@ class Catify(commands.Cog): self.bot = bot @commands.command(aliases=["ᓚᘏᗢify", "ᓚᘏᗢ"]) + @commands.cooldown(1, 5, commands.BucketType.user) async def catify(self, ctx: commands.Context, *, text: Optional[str]) -> None: """ Convert the provided text into a cat themed sentence by interspercing cats throughout text. -- cgit v1.2.3 From 6ae760663b7055f9b74089591b4f46ac75c6f8e5 Mon Sep 17 00:00:00 2001 From: ToxicKidz <78174417+ToxicKidz@users.noreply.github.com> Date: Wed, 5 May 2021 12:10:18 -0400 Subject: chore: Don't have defaults for typing.Optional[...] in commands --- bot/exts/evergreen/game.py | 2 +- bot/exts/evergreen/space.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/game.py b/bot/exts/evergreen/game.py index f3d4e9a2..4da33259 100644 --- a/bot/exts/evergreen/game.py +++ b/bot/exts/evergreen/game.py @@ -225,7 +225,7 @@ class Games(Cog): self.genres[genre_name] = genre @group(name="games", aliases=["game"], invoke_without_command=True) - async def games(self, ctx: Context, amount: Optional[int] = 5, *, genre: Optional[str] = None) -> None: + async def games(self, ctx: Context, amount: Optional[int] = 5, *, genre: Optional[str]) -> None: """ Get random game(s) by genre from IGDB. Use .games genres command to get all available genres. diff --git a/bot/exts/evergreen/space.py b/bot/exts/evergreen/space.py index ee9ad38c..7d83dbfb 100644 --- a/bot/exts/evergreen/space.py +++ b/bot/exts/evergreen/space.py @@ -66,7 +66,7 @@ class Space(Cog): await invoke_help_command(ctx) @space.command(name="apod") - async def apod(self, ctx: Context, date: Optional[str] = None) -> None: + async def apod(self, ctx: Context, date: Optional[str]) -> None: """ Get Astronomy Picture of Day from NASA API. Date is optional parameter, what formatting is YYYY-MM-DD. @@ -99,7 +99,7 @@ class Space(Cog): ) @space.command(name="nasa") - async def nasa(self, ctx: Context, *, search_term: Optional[str] = None) -> None: + async def nasa(self, ctx: Context, *, search_term: Optional[str]) -> None: """Get random NASA information/facts + image. Support `search_term` parameter for more specific search.""" params = { "media_type": "image" @@ -124,7 +124,7 @@ class Space(Cog): ) @space.command(name="epic") - async def epic(self, ctx: Context, date: Optional[str] = None) -> None: + async def epic(self, ctx: Context, date: Optional[str]) -> None: """Get one of latest random image of earth from NASA EPIC API. Support date parameter, format is YYYY-MM-DD.""" if date: try: @@ -160,8 +160,8 @@ class Space(Cog): async def mars( self, ctx: Context, - date: Optional[DateConverter] = None, - rover: Optional[str] = "curiosity" + date: Optional[DateConverter], + rover: str = "curiosity" ) -> None: """ Get random Mars image by date. Support both SOL (martian solar day) and earth date and rovers. -- cgit v1.2.3 From 7a18b365f3ffe43fe3de6bb342d98b0414e0283c Mon Sep 17 00:00:00 2001 From: ToxicKidz <78174417+ToxicKidz@users.noreply.github.com> Date: Wed, 5 May 2021 13:23:59 -0400 Subject: chore: Don't use "is not None" Co-authored-by: Anand Krishna <40204976+anand2312@users.noreply.github.com> --- bot/exts/evergreen/githubinfo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/githubinfo.py b/bot/exts/evergreen/githubinfo.py index 24479c79..fe126aa2 100644 --- a/bot/exts/evergreen/githubinfo.py +++ b/bot/exts/evergreen/githubinfo.py @@ -67,7 +67,7 @@ class GithubInfo(commands.Cog): embed = discord.Embed( title=f"`{user_data['login']}`'s GitHub profile info", - description=f"```{user_data['bio']}```\n" if user_data["bio"] is not None else "", + description=f"```{user_data['bio']}```\n" if user_data["bio"] else "", colour=discord.Colour.blurple(), url=user_data["html_url"], timestamp=datetime.strptime(user_data["created_at"], "%Y-%m-%dT%H:%M:%SZ") -- cgit v1.2.3 From 888f83170bce384315a0a5c47b87b7ffbfb5d8ea Mon Sep 17 00:00:00 2001 From: ToxicKidz <78174417+ToxicKidz@users.noreply.github.com> Date: Wed, 5 May 2021 13:46:08 -0400 Subject: chore: Add all of the converters into bot/utils/converters.py --- bot/exts/evergreen/minesweeper.py | 28 +------------- bot/exts/evergreen/source.py | 23 +----------- bot/exts/evergreen/space.py | 19 ++-------- bot/utils/converters.py | 77 +++++++++++++++++++++++++++++++++++++-- 4 files changed, 80 insertions(+), 67 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/minesweeper.py b/bot/exts/evergreen/minesweeper.py index bd28d365..7a31dfde 100644 --- a/bot/exts/evergreen/minesweeper.py +++ b/bot/exts/evergreen/minesweeper.py @@ -8,6 +8,7 @@ from discord.ext import commands from bot.bot import Bot from bot.constants import Client +from bot.utils.converters import CoordinateConverter from bot.utils.exceptions import UserNotPlayingError from bot.utils.extensions import invoke_help_command @@ -32,33 +33,6 @@ MESSAGE_MAPPING = { log = logging.getLogger(__name__) -class CoordinateConverter(commands.Converter): - """Converter for Coordinates.""" - - async def convert(self, ctx: commands.Context, coordinate: str) -> typing.Tuple[int, int]: - """Take in a coordinate string and turn it into an (x, y) tuple.""" - if not 2 <= len(coordinate) <= 3: - raise commands.BadArgument("Invalid co-ordinate provided.") - - coordinate = coordinate.lower() - if coordinate[0].isalpha(): - digit = coordinate[1:] - letter = coordinate[0] - else: - digit = coordinate[:-1] - letter = coordinate[-1] - - if not digit.isdigit(): - raise commands.BadArgument - - x = ord(letter) - ord("a") - y = int(digit) - 1 - - if (not 0 <= x <= 9) or (not 0 <= y <= 9): - raise commands.BadArgument - return x, y - - GameBoard = typing.List[typing.List[typing.Union[str, int]]] diff --git a/bot/exts/evergreen/source.py b/bot/exts/evergreen/source.py index 685b3111..8fb72143 100644 --- a/bot/exts/evergreen/source.py +++ b/bot/exts/evergreen/source.py @@ -1,32 +1,13 @@ import inspect from pathlib import Path -from typing import Optional, Tuple, Union +from typing import Optional, Tuple from discord import Embed from discord.ext import commands from bot.bot import Bot from bot.constants import Source - -SourceType = Union[commands.Command, commands.Cog, str, commands.ExtensionNotLoaded] - - -class SourceConverter(commands.Converter): - """Convert an argument into a help command, tag, command, or cog.""" - - async def convert(self, ctx: commands.Context, argument: str) -> SourceType: - """Convert argument into source object.""" - cog = ctx.bot.get_cog(argument) - if cog: - return cog - - cmd = ctx.bot.get_command(argument) - if cmd: - return cmd - - raise commands.BadArgument( - f"Unable to convert `{argument}` to valid command or Cog." - ) +from bot.utils.converters import SourceConverter, SourceType class BotSource(commands.Cog): diff --git a/bot/exts/evergreen/space.py b/bot/exts/evergreen/space.py index 7d83dbfb..77b63946 100644 --- a/bot/exts/evergreen/space.py +++ b/bot/exts/evergreen/space.py @@ -1,15 +1,16 @@ import logging import random from datetime import date, datetime -from typing import Any, Dict, Optional, Union +from typing import Any, Dict, Optional from urllib.parse import urlencode from discord import Embed from discord.ext import tasks -from discord.ext.commands import BadArgument, Cog, Context, Converter, group +from discord.ext.commands import Cog, Context, group from bot.bot import Bot from bot.constants import Tokens +from bot.utils.converters import DateConverter from bot.utils.extensions import invoke_help_command logger = logging.getLogger(__name__) @@ -21,20 +22,6 @@ NASA_EPIC_BASE_URL = "https://epic.gsfc.nasa.gov" APOD_MIN_DATE = date(1995, 6, 16) -class DateConverter(Converter): - """Parse SOL or earth date (in format YYYY-MM-DD) into `int` or `datetime`. When invalid input, raise error.""" - - async def convert(self, ctx: Context, argument: str) -> Union[int, datetime]: - """Parse date (SOL or earth) into `datetime` or `int`. When invalid value, raise error.""" - if argument.isdigit(): - return int(argument) - try: - date = datetime.strptime(argument, "%Y-%m-%d") - except ValueError: - raise BadArgument(f"Can't convert `{argument}` to `datetime` in format `YYYY-MM-DD` or `int` in SOL.") - return date - - class Space(Cog): """Space Cog contains commands, that show images, facts or other information about space.""" diff --git a/bot/utils/converters.py b/bot/utils/converters.py index 228714c9..98607087 100644 --- a/bot/utils/converters.py +++ b/bot/utils/converters.py @@ -1,11 +1,15 @@ +from datetime import datetime +from typing import Tuple, Union + import discord -from discord.ext.commands.converter import MessageConverter +from discord.ext import commands -class WrappedMessageConverter(MessageConverter): +class WrappedMessageConverter(commands.MessageConverter): """A converter that handles embed-suppressed links like .""" - async def convert(self, ctx: discord.ext.commands.Context, argument: str) -> discord.Message: + @staticmethod + async def convert(ctx: commands.Context, argument: str) -> discord.Message: """Wrap the commands.MessageConverter to handle <> delimited message links.""" # It's possible to wrap a message in [<>] as well, and it's supported because its easy if argument.startswith("[") and argument.endswith("]"): @@ -14,3 +18,70 @@ class WrappedMessageConverter(MessageConverter): argument = argument[1:-1] return await super().convert(ctx, argument) + + +class CoordinateConverter(commands.Converter): + """Converter for Coordinates.""" + + @staticmethod + async def convert(ctx: commands.Context, coordinate: str) -> Tuple[int, int]: + """Take in a coordinate string and turn it into an (x, y) tuple.""" + if len(coordinate) not in (2, 3): + raise commands.BadArgument("Invalid co-ordinate provided.") + + coordinate = coordinate.lower() + if coordinate[0].isalpha(): + digit = coordinate[1:] + letter = coordinate[0] + else: + digit = coordinate[:-1] + letter = coordinate[-1] + + if not digit.isdigit(): + raise commands.BadArgument + + x = ord(letter) - ord("a") + y = int(digit) - 1 + + if (not 0 <= x <= 9) or (not 0 <= y <= 9): + raise commands.BadArgument + return x, y + + +SourceType = Union[commands.Command, commands.Cog] + + +class SourceConverter(commands.Converter): + """Convert an argument into a command or cog.""" + + @staticmethod + async def convert(ctx: commands.Context, argument: str) -> SourceType: + """Convert argument into source object.""" + cog = ctx.bot.get_cog(argument) + if cog: + return cog + + cmd = ctx.bot.get_command(argument) + if cmd: + return cmd + + raise commands.BadArgument( + f"Unable to convert `{argument}` to valid command or Cog." + ) + + +class DateConverter(commands.Converter): + """Parse SOL or earth date (in format YYYY-MM-DD) into `int` or `datetime`. When invalid input, raise error.""" + + @staticmethod + async def convert(ctx: commands.Context, argument: str) -> Union[int, datetime]: + """Parse date (SOL or earth) into `datetime` or `int`. When invalid value, raise error.""" + if argument.isdigit(): + return int(argument) + try: + date = datetime.strptime(argument, "%Y-%m-%d") + except ValueError: + raise commands.BadArgument( + f"Can't convert `{argument}` to `datetime` in format `YYYY-MM-DD` or `int` in SOL." + ) + return date -- cgit v1.2.3 From 780aaeb5c272f7a9fd9fe9e8cdc1c041beb90a25 Mon Sep 17 00:00:00 2001 From: ToxicKidz <78174417+ToxicKidz@users.noreply.github.com> Date: Wed, 5 May 2021 14:20:40 -0400 Subject: chore: Apply anand's suggested changes --- bot/exts/evergreen/error_handler.py | 2 +- bot/exts/evergreen/githubinfo.py | 3 +- bot/exts/evergreen/movie.py | 10 +- bot/exts/evergreen/reddit.py | 3 +- bot/exts/evergreen/snakes/__init__.py | 2 +- bot/exts/evergreen/snakes/_snakes_cog.py | 175 +++++++++++++++---------------- 6 files changed, 96 insertions(+), 99 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/error_handler.py b/bot/exts/evergreen/error_handler.py index 62529f52..faaf1386 100644 --- a/bot/exts/evergreen/error_handler.py +++ b/bot/exts/evergreen/error_handler.py @@ -39,7 +39,7 @@ class CommandErrorHandler(commands.Cog): @commands.Cog.listener() async def on_command_error(self, ctx: commands.Context, error: commands.CommandError) -> None: - """Activates when a command opens an error.""" + """Activates when a command raises an error.""" if getattr(error, "handled", False): logging.debug(f"Command {ctx.command} had its error already handled locally; ignoring.") return diff --git a/bot/exts/evergreen/githubinfo.py b/bot/exts/evergreen/githubinfo.py index fe126aa2..27e607e5 100644 --- a/bot/exts/evergreen/githubinfo.py +++ b/bot/exts/evergreen/githubinfo.py @@ -5,7 +5,6 @@ from urllib.parse import quote import discord from discord.ext import commands -from discord.ext.commands.cooldowns import BucketType from bot.bot import Bot from bot.constants import Colours, NEGATIVE_REPLIES @@ -28,7 +27,7 @@ class GithubInfo(commands.Cog): return await r.json() @commands.group(name="github", aliases=("gh", "git")) - @commands.cooldown(1, 10, BucketType.user) + @commands.cooldown(1, 10, commands.BucketType.user) async def github_group(self, ctx: commands.Context) -> None: """Commands for finding information related to GitHub.""" if ctx.invoked_subcommand is None: diff --git a/bot/exts/evergreen/movie.py b/bot/exts/evergreen/movie.py index e67f8d04..fa284417 100644 --- a/bot/exts/evergreen/movie.py +++ b/bot/exts/evergreen/movie.py @@ -72,13 +72,13 @@ class Movie(Cog): # Capitalize genre for getting data from Enum, get random page, send help when genre don't exist. genre = genre.capitalize() try: - result = await self.get_movies_list(self.http_session, MovieGenres[genre].value, 1) + result = await self.get_movies_data(self.http_session, MovieGenres[genre].value, 1) except KeyError: await invoke_help_command(ctx) return # Check if "results" is in result. If not, throw error. - if "results" not in result.keys(): + if "results" not in result: err_msg = f"There is problem while making TMDB API request. Response Code: {result['status_code']}, " \ f"{result['status_message']}." await ctx.send(err_msg) @@ -88,8 +88,8 @@ class Movie(Cog): page = random.randint(1, result["total_pages"]) # Get movies list from TMDB, check if results key in result. When not, raise error. - movies = await self.get_movies_list(self.http_session, MovieGenres[genre].value, page) - if "results" not in movies.keys(): + movies = await self.get_movies_data(self.http_session, MovieGenres[genre].value, page) + if "results" not in movies: err_msg = f"There is problem while making TMDB API request. Response Code: {result['status_code']}, " \ f"{result['status_message']}." await ctx.send(err_msg) @@ -106,7 +106,7 @@ class Movie(Cog): """Show all currently available genres for .movies command.""" await ctx.send(f"Current available genres: {', '.join('`' + genre.name + '`' for genre in MovieGenres)}") - async def get_movies_list(self, client: ClientSession, genre_id: str, page: int) -> Dict[str, Any]: + async def get_movies_data(self, client: ClientSession, genre_id: str, page: int) -> List[Dict[str, Any]]: """Return JSON of TMDB discover request.""" # Define params of request params = { diff --git a/bot/exts/evergreen/reddit.py b/bot/exts/evergreen/reddit.py index f2b95fe2..bda155c3 100644 --- a/bot/exts/evergreen/reddit.py +++ b/bot/exts/evergreen/reddit.py @@ -3,7 +3,6 @@ import random import discord from discord.ext import commands -from discord.ext.commands.cooldowns import BucketType from bot.bot import Bot from bot.utils.pagination import ImagePaginator @@ -31,7 +30,7 @@ class Reddit(commands.Cog): return await response.json() @commands.command(name="reddit") - @commands.cooldown(1, 10, BucketType.user) + @commands.cooldown(1, 10, commands.BucketType.user) async def get_reddit(self, ctx: commands.Context, subreddit: str = "python", sort: str = "hot") -> None: """ Fetch reddit posts by using this command. diff --git a/bot/exts/evergreen/snakes/__init__.py b/bot/exts/evergreen/snakes/__init__.py index 049bc964..7740429b 100644 --- a/bot/exts/evergreen/snakes/__init__.py +++ b/bot/exts/evergreen/snakes/__init__.py @@ -7,5 +7,5 @@ log = logging.getLogger(__name__) def setup(bot: Bot) -> None: - """Snakes Cog load.""" + """Load the Snakes Cog.""" bot.add_cog(Snakes(bot)) diff --git a/bot/exts/evergreen/snakes/_snakes_cog.py b/bot/exts/evergreen/snakes/_snakes_cog.py index 353bcd66..62795aef 100644 --- a/bot/exts/evergreen/snakes/_snakes_cog.py +++ b/bot/exts/evergreen/snakes/_snakes_cog.py @@ -9,15 +9,15 @@ import textwrap import urllib from functools import partial from io import BytesIO -from typing import Any, Dict, List +from typing import Any, Dict, List, Optional -import aiohttp import async_timeout from PIL import Image, ImageDraw, ImageFont from discord import Colour, Embed, File, Member, Message, Reaction from discord.errors import HTTPException -from discord.ext.commands import Bot, Cog, CommandError, Context, bot_has_permissions, group +from discord.ext.commands import Cog, CommandError, Context, bot_has_permissions, group +from bot.bot import Bot from bot.constants import ERROR_REPLIES, Tokens from bot.exts.evergreen.snakes import _utils as utils from bot.exts.evergreen.snakes._converter import Snake @@ -275,13 +275,13 @@ class Snakes(Cog): return message - async def _fetch(self, session: aiohttp.ClientSession, url: str, params: dict = None) -> dict: + async def _fetch(self, url: str, params: Optional[dict] = None) -> dict: """Asynchronous web request helper method.""" if params is None: params = {} async with async_timeout.timeout(10): - async with session.get(url, params=params) as response: + async with self.bot.http_session.get(url, params=params) as response: return await response.json() def _get_random_long_message(self, messages: List[str], retries: int = 10) -> str: @@ -309,96 +309,95 @@ class Snakes(Cog): """ snake_info = {} - async with aiohttp.ClientSession() as session: - params = { - "format": "json", - "action": "query", - "list": "search", - "srsearch": name, - "utf8": "", - "srlimit": "1", - } - - json = await self._fetch(session, URL, params=params) - - # Wikipedia does have a error page - try: - pageid = json["query"]["search"][0]["pageid"] - except KeyError: - # Wikipedia error page ID(?) - pageid = 41118 - except IndexError: - return None - - params = { - "format": "json", - "action": "query", - "prop": "extracts|images|info", - "exlimit": "max", - "explaintext": "", - "inprop": "url", - "pageids": pageid - } + params = { + "format": "json", + "action": "query", + "list": "search", + "srsearch": name, + "utf8": "", + "srlimit": "1", + } - json = await self._fetch(session, URL, params=params) + json = await self._fetch(URL, params=params) - # Constructing dict - handle exceptions later - try: - snake_info["title"] = json["query"]["pages"][f"{pageid}"]["title"] - snake_info["extract"] = json["query"]["pages"][f"{pageid}"]["extract"] - snake_info["images"] = json["query"]["pages"][f"{pageid}"]["images"] - snake_info["fullurl"] = json["query"]["pages"][f"{pageid}"]["fullurl"] - snake_info["pageid"] = json["query"]["pages"][f"{pageid}"]["pageid"] - except KeyError: - snake_info["error"] = True - - if snake_info["images"]: - i_url = "https://commons.wikimedia.org/wiki/Special:FilePath/" - image_list = [] - map_list = [] - thumb_list = [] - - # Wikipedia has arbitrary images that are not snakes - banned = [ - "Commons-logo.svg", - "Red%20Pencil%20Icon.png", - "distribution", - "The%20Death%20of%20Cleopatra%20arthur.jpg", - "Head%20of%20holotype", - "locator", - "Woma.png", - "-map.", - ".svg", - "ange.", - "Adder%20(PSF).png" - ] - - for image in snake_info["images"]: - # Images come in the format of `File:filename.extension` - file, sep, filename = image["title"].partition(":") - filename = filename.replace(" ", "%20") # Wikipedia returns good data! - - if not filename.startswith("Map"): - if any(ban in filename for ban in banned): - pass - else: - image_list.append(f"{i_url}{filename}") - thumb_list.append(f"{i_url}{filename}?width=100") + # Wikipedia does have a error page + try: + pageid = json["query"]["search"][0]["pageid"] + except KeyError: + # Wikipedia error page ID(?) + pageid = 41118 + except IndexError: + return None + + params = { + "format": "json", + "action": "query", + "prop": "extracts|images|info", + "exlimit": "max", + "explaintext": "", + "inprop": "url", + "pageids": pageid + } + + json = await self._fetch(URL, params=params) + + # Constructing dict - handle exceptions later + try: + snake_info["title"] = json["query"]["pages"][f"{pageid}"]["title"] + snake_info["extract"] = json["query"]["pages"][f"{pageid}"]["extract"] + snake_info["images"] = json["query"]["pages"][f"{pageid}"]["images"] + snake_info["fullurl"] = json["query"]["pages"][f"{pageid}"]["fullurl"] + snake_info["pageid"] = json["query"]["pages"][f"{pageid}"]["pageid"] + except KeyError: + snake_info["error"] = True + + if snake_info["images"]: + i_url = "https://commons.wikimedia.org/wiki/Special:FilePath/" + image_list = [] + map_list = [] + thumb_list = [] + + # Wikipedia has arbitrary images that are not snakes + banned = [ + "Commons-logo.svg", + "Red%20Pencil%20Icon.png", + "distribution", + "The%20Death%20of%20Cleopatra%20arthur.jpg", + "Head%20of%20holotype", + "locator", + "Woma.png", + "-map.", + ".svg", + "ange.", + "Adder%20(PSF).png" + ] + + for image in snake_info["images"]: + # Images come in the format of `File:filename.extension` + file, sep, filename = image["title"].partition(":") + filename = filename.replace(" ", "%20") # Wikipedia returns good data! + + if not filename.startswith("Map"): + if any(ban in filename for ban in banned): + pass else: - map_list.append(f"{i_url}{filename}") + image_list.append(f"{i_url}{filename}") + thumb_list.append(f"{i_url}{filename}?width=100") + else: + map_list.append(f"{i_url}{filename}") - snake_info["image_list"] = image_list - snake_info["map_list"] = map_list - snake_info["thumb_list"] = thumb_list - snake_info["name"] = name + snake_info["image_list"] = image_list + snake_info["map_list"] = map_list + snake_info["thumb_list"] = thumb_list + snake_info["name"] = name - match = self.wiki_brief.match(snake_info["extract"]) - info = match.group(1) if match else None + match = self.wiki_brief.match(snake_info["extract"]) + info = match.group(1) if match else None - if info: - info = info.replace("\n", "\n\n") # Give us some proper paragraphs. + if info: + info = info.replace("\n", "\n\n") # Give us some proper paragraphs. - snake_info["info"] = info + snake_info["info"] = info return snake_info -- cgit v1.2.3 From 353264e53f54ba32d6f71fc027bf81aca871fdf7 Mon Sep 17 00:00:00 2001 From: ToxicKidz <78174417+ToxicKidz@users.noreply.github.com> Date: Wed, 5 May 2021 14:25:59 -0400 Subject: chore: Improve .space epic's docstring --- bot/exts/evergreen/space.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/space.py b/bot/exts/evergreen/space.py index 77b63946..6c991d26 100644 --- a/bot/exts/evergreen/space.py +++ b/bot/exts/evergreen/space.py @@ -112,7 +112,7 @@ class Space(Cog): @space.command(name="epic") async def epic(self, ctx: Context, date: Optional[str]) -> None: - """Get one of latest random image of earth from NASA EPIC API. Support date parameter, format is YYYY-MM-DD.""" + """Get a random image of the Earth from the NASA EPIC API. Support date parameter, format is YYYY-MM-DD.""" if date: try: show_date = datetime.strptime(date, "%Y-%m-%d").date().isoformat() -- cgit v1.2.3 From 19e2efaf5572a0edb4595bead6877aaae078adae Mon Sep 17 00:00:00 2001 From: ToxicKidz <78174417+ToxicKidz@users.noreply.github.com> Date: Wed, 5 May 2021 14:46:29 -0400 Subject: chore: Apply suggested changes --- bot/exts/halloween/hacktober-issue-finder.py | 12 +++++++----- bot/exts/halloween/hacktoberstats.py | 3 +-- 2 files changed, 8 insertions(+), 7 deletions(-) (limited to 'bot') diff --git a/bot/exts/halloween/hacktober-issue-finder.py b/bot/exts/halloween/hacktober-issue-finder.py index baee9612..20a06770 100644 --- a/bot/exts/halloween/hacktober-issue-finder.py +++ b/bot/exts/halloween/hacktober-issue-finder.py @@ -73,15 +73,17 @@ class HacktoberIssues(commands.Cog): log.debug(f"making api request to url: {url}") async with self.bot.http_session.get(url, headers=REQUEST_HEADERS) as response: if response.status != 200: - log.error(f"expected 200 status (got {response.status}) from the GitHub api.") - await ctx.send(f"ERROR: expected 200 status (got {response.status}) from the GitHub api.") - await ctx.send(await response.text()) + log.error(f"expected 200 status (got {response.status}) by the GitHub api.") + await ctx.send( + f"ERROR: expected 200 status (got {response.status}) by the GitHub api.\n" + f"{await response.text()}" + ) return None data = await response.json() if len(data["items"]) == 0: - log.error(f"no issues returned from GitHub api. with url: {response.url}") - await ctx.send(f"ERROR: no issues returned from GitHub api. with url: {response.url}") + log.error(f"no issues returned by GitHub API, with url: {response.url}") + await ctx.send(f"ERROR: no issues returned by GitHub API, with url: {response.url}") return None if option == "beginner": diff --git a/bot/exts/halloween/hacktoberstats.py b/bot/exts/halloween/hacktoberstats.py index 33e9ca31..b74e680b 100644 --- a/bot/exts/halloween/hacktoberstats.py +++ b/bot/exts/halloween/hacktoberstats.py @@ -302,8 +302,7 @@ class HacktoberStats(commands.Cog): async def _fetch_url(self, url: str, headers: dict) -> dict: """Retrieve API response from URL.""" async with self.bot.http_session.get(url, headers=headers) as resp: - jsonresp = await resp.json() - return jsonresp + return await resp.json() @staticmethod def _has_label(pr: dict, labels: Union[List[str], str]) -> bool: -- cgit v1.2.3 From e22c03395b83d81b4c5de90378bc09fae9dce4e1 Mon Sep 17 00:00:00 2001 From: ToxicKidz <78174417+ToxicKidz@users.noreply.github.com> Date: Wed, 5 May 2021 15:33:08 -0400 Subject: chore: Use pathlib more often and apply other changes --- bot/exts/halloween/halloween_facts.py | 1 - bot/exts/halloween/monstersurvey.py | 16 ++++++++-------- bot/exts/pride/drag_queen_name.py | 4 ++-- 3 files changed, 10 insertions(+), 11 deletions(-) (limited to 'bot') diff --git a/bot/exts/halloween/halloween_facts.py b/bot/exts/halloween/halloween_facts.py index 139e0810..3a89b5aa 100644 --- a/bot/exts/halloween/halloween_facts.py +++ b/bot/exts/halloween/halloween_facts.py @@ -33,7 +33,6 @@ class HalloweenFacts(commands.Cog): with Path("bot/resources/halloween/halloween_facts.json").open("r", encoding="utf8") as file: self.halloween_facts = json.load(file) self.facts = list(enumerate(self.halloween_facts)) - random.shuffle(self.facts) def random_fact(self) -> Tuple[int, str]: """Return a random fact from the loaded facts.""" diff --git a/bot/exts/halloween/monstersurvey.py b/bot/exts/halloween/monstersurvey.py index 486e8937..38e3850e 100644 --- a/bot/exts/halloween/monstersurvey.py +++ b/bot/exts/halloween/monstersurvey.py @@ -1,6 +1,6 @@ import json import logging -import os +import pathlib from discord import Embed from discord.ext import commands @@ -25,24 +25,24 @@ class MonsterSurvey(Cog): def __init__(self): """Initializes values for the bot to use within the voting commands.""" - self.registry_location = os.path.join(os.getcwd(), "bot", "resources", "halloween", "monstersurvey.json") - with open(self.registry_location, "r", encoding="utf8") as jason: - self.voter_registry = json.load(jason) + self.registry_path = pathlib.Path("bot", "resources", "halloween", "monstersurvey.json") + with self.registry_path.open(encoding="utf8") as data: + self.voter_registry = json.load(data) def json_write(self) -> None: """Write voting results to a local JSON file.""" log.info("Saved Monster Survey Results") - with open(self.registry_location, "w", encoding="utf8") as jason: - json.dump(self.voter_registry, jason, indent=2) + with self.registry_path.open("w", encoding="utf8") as data: + json.dump(self.voter_registry, data, indent=2) def cast_vote(self, id: int, monster: str) -> None: """ - Cast a user"s vote for the specified monster. + Cast a user's vote for the specified monster. If the user has already voted, their existing vote is removed. """ vr = self.voter_registry - for m in vr.keys(): + for m in vr: if id not in vr[m]["votes"] and m == monster: vr[m]["votes"].append(id) else: diff --git a/bot/exts/pride/drag_queen_name.py b/bot/exts/pride/drag_queen_name.py index 9839f089..d9424001 100644 --- a/bot/exts/pride/drag_queen_name.py +++ b/bot/exts/pride/drag_queen_name.py @@ -19,7 +19,7 @@ class DragNames(commands.Cog): @staticmethod def load_names() -> list: """Loads a list of drag queen names.""" - with open(Path("bot/resources/pride/drag_queen_names.json"), "r", encoding="utf8") as f: + with Path("bot/resources/pride/drag_queen_names.json").open(encoding="utf8") as f: return json.load(f) @commands.command(name="dragname", aliases=["dragqueenname", "queenme"]) @@ -29,5 +29,5 @@ class DragNames(commands.Cog): def setup(bot: Bot) -> None: - """Load the Drag Queen Cog.""" + """Load the Drag Names Cog.""" bot.add_cog(DragNames()) -- cgit v1.2.3 From b486ed47478adf2916291497243e760026e686bd Mon Sep 17 00:00:00 2001 From: ToxicKidz <78174417+ToxicKidz@users.noreply.github.com> Date: Wed, 5 May 2021 15:36:39 -0400 Subject: chore: Remove useless call of `dict.keys()` Co-authored-by: Anand Krishna <40204976+anand2312@users.noreply.github.com> --- bot/exts/halloween/monstersurvey.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/halloween/monstersurvey.py b/bot/exts/halloween/monstersurvey.py index 486e8937..42a316e6 100644 --- a/bot/exts/halloween/monstersurvey.py +++ b/bot/exts/halloween/monstersurvey.py @@ -84,7 +84,7 @@ class MonsterSurvey(Cog): value="Which monster has the most votes? This command will tell you.", inline=False ) - default_embed.set_footer(text=f"Monsters choices are: {', '.join(self.voter_registry.keys())}") + default_embed.set_footer(text=f"Monsters choices are: {', '.join(self.voter_registry)}") await ctx.send(embed=default_embed) -- cgit v1.2.3 From 544a05b00758583f4594569e032c9e661406a72f Mon Sep 17 00:00:00 2001 From: ToxicKidz <78174417+ToxicKidz@users.noreply.github.com> Date: Wed, 5 May 2021 16:50:59 -0400 Subject: chore: Fetch the member and use Colours.blue in the embed for the .mosaic command --- bot/exts/evergreen/avatar_modification/avatar_modify.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/avatar_modification/avatar_modify.py b/bot/exts/evergreen/avatar_modification/avatar_modify.py index afff125f..bd151d30 100644 --- a/bot/exts/evergreen/avatar_modification/avatar_modify.py +++ b/bot/exts/evergreen/avatar_modification/avatar_modify.py @@ -317,6 +317,11 @@ class AvatarModify(commands.Cog): async def mosaic_command(self, ctx: commands.Context, squares: int = 16) -> None: """Splits your avatar into x squares, randomizes them and stitches them back into a new image!""" async with ctx.typing(): + member = await self._fetch_member(ctx.author.id) + if not member: + await ctx.send(f"{Emojis.cross_mark} Could not get member info.") + return + if squares < 1: raise commands.BadArgument("Squares must be a positive number") @@ -327,7 +332,8 @@ class AvatarModify(commands.Cog): raise commands.BadArgument(f"Number of squares cannot be higher than {MAX_SQUARES:,}.") file_name = file_safe_name("mosaic_avatar", ctx.author.display_name) - img_bytes = await ctx.author.avatar_url.read() + + img_bytes = await member.avatar_url.read() file = await in_executor( PfpEffects.mosaic_effect, @@ -348,7 +354,8 @@ class AvatarModify(commands.Cog): embed = discord.Embed( title=title, - description=description + description=description, + colour=Colours.blue ) embed.set_image(url=f'attachment://{file_name}') -- cgit v1.2.3 From 390c05383e42ba60197f5fb888cd93e867c00038 Mon Sep 17 00:00:00 2001 From: ToxicKidz <78174417+ToxicKidz@users.noreply.github.com> Date: Wed, 5 May 2021 17:17:08 -0400 Subject: chore: Prefer double quotes over single quotes --- .../evergreen/avatar_modification/avatar_modify.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/avatar_modification/avatar_modify.py b/bot/exts/evergreen/avatar_modification/avatar_modify.py index bd151d30..ca048134 100644 --- a/bot/exts/evergreen/avatar_modification/avatar_modify.py +++ b/bot/exts/evergreen/avatar_modification/avatar_modify.py @@ -313,7 +313,7 @@ class AvatarModify(commands.Cog): await ctx.send(file=file, embed=embed) - @avatar_modify.command(name='mosaic', root_aliases=('mosaic',)) + @avatar_modify.command(name="mosaic", root_aliases=("mosaic",)) async def mosaic_command(self, ctx: commands.Context, squares: int = 16) -> None: """Splits your avatar into x squares, randomizes them and stitches them back into a new image!""" async with ctx.typing(): @@ -343,14 +343,14 @@ class AvatarModify(commands.Cog): ) if squares == 1: - title = 'Hooh... that was a lot of work' - description = 'I present to you... Yourself!' + title = "Hooh... that was a lot of work" + description = "I present to you... Yourself!" elif squares == MAX_SQUARES: - title = 'Testing the limits I see...' - description = 'What a masterpiece. :star:' + title = "Testing the limits I see..." + description = "What a masterpiece. :star:" else: - title = 'Your mosaic avatar' - description = 'Here is your avatar. I think it looks a bit *puzzling*' + title = "Your mosaic avatar" + description = "Here is your avatar. I think it looks a bit *puzzling*" embed = discord.Embed( title=title, @@ -358,8 +358,8 @@ class AvatarModify(commands.Cog): colour=Colours.blue ) - embed.set_image(url=f'attachment://{file_name}') - embed.set_footer(text=f'Made by {ctx.author.display_name}', icon_url=ctx.author.avatar_url) + embed.set_image(url=f"attachment://{file_name}") + embed.set_footer(text=f"Made by {ctx.author.display_name}", icon_url=ctx.author.avatar_url) await ctx.send(file=file, embed=embed) -- cgit v1.2.3 From f6175b5cf842c7052089c05f387770fc9d91b6d0 Mon Sep 17 00:00:00 2001 From: ToxicKidz <78174417+ToxicKidz@users.noreply.github.com> Date: Wed, 5 May 2021 17:19:32 -0400 Subject: chore: Use the name AvatarModify instead of PfpModify --- bot/exts/evergreen/avatar_modification/avatar_modify.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/avatar_modification/avatar_modify.py b/bot/exts/evergreen/avatar_modification/avatar_modify.py index ca048134..221bd809 100644 --- a/bot/exts/evergreen/avatar_modification/avatar_modify.py +++ b/bot/exts/evergreen/avatar_modification/avatar_modify.py @@ -365,5 +365,5 @@ class AvatarModify(commands.Cog): def setup(bot: commands.Bot) -> None: - """Load the PfpModify cog.""" + """Load the AvatarModify cog.""" bot.add_cog(AvatarModify(bot)) -- cgit v1.2.3 From df65a5091100a59424bedc14144f37c43954d0b3 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 6 May 2021 14:45:29 +0100 Subject: Use bot.bot.Bot for type hint, over commands.Bot --- bot/exts/evergreen/bookmark.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/bookmark.py b/bot/exts/evergreen/bookmark.py index f0b828f8..5f550a4a 100644 --- a/bot/exts/evergreen/bookmark.py +++ b/bot/exts/evergreen/bookmark.py @@ -5,6 +5,7 @@ import random import discord from discord.ext import commands +from bot.bot import Bot from bot.constants import Colours, ERROR_REPLIES, Icons from bot.utils.converters import WrappedMessageConverter @@ -18,7 +19,7 @@ BOOKMARK_EMOJI = "📌" class Bookmark(commands.Cog): """Creates personal bookmarks by relaying a message link to the user's DMs.""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Bot): self.bot = bot @staticmethod @@ -140,6 +141,6 @@ class Bookmark(commands.Cog): await reaction_message.delete() -def setup(bot: commands.Bot) -> None: +def setup(bot: Bot) -> None: """Load the Bookmark cog.""" bot.add_cog(Bookmark(bot)) -- cgit v1.2.3 From 16b43597050b8dad922c8d93aa07c061e0512c1f Mon Sep 17 00:00:00 2001 From: ToxicKidz <78174417+ToxicKidz@users.noreply.github.com> Date: Thu, 6 May 2021 13:05:37 -0400 Subject: chore: Check if the number of squares first is bigger than the max first Co-authored-by: Anand Krishna <40204976+anand2312@users.noreply.github.com> --- bot/exts/evergreen/avatar_modification/avatar_modify.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/avatar_modification/avatar_modify.py b/bot/exts/evergreen/avatar_modification/avatar_modify.py index 221bd809..dffe43ce 100644 --- a/bot/exts/evergreen/avatar_modification/avatar_modify.py +++ b/bot/exts/evergreen/avatar_modification/avatar_modify.py @@ -322,14 +322,11 @@ class AvatarModify(commands.Cog): await ctx.send(f"{Emojis.cross_mark} Could not get member info.") return - if squares < 1: - raise commands.BadArgument("Squares must be a positive number") + if 1 <= squares <= MAX_SQUARES: + raise commands.BadArgument(f"Squares must be a positive number less than or equal to {MAX_SQUARES:,}.") if not math.sqrt(squares).is_integer(): - raise commands.BadArgument("Squares must be a perfect square") - - if squares > MAX_SQUARES: - raise commands.BadArgument(f"Number of squares cannot be higher than {MAX_SQUARES:,}.") + raise commands.BadArgument("The number of squares must be a perfect square.") file_name = file_safe_name("mosaic_avatar", ctx.author.display_name) -- cgit v1.2.3 From 40d5a00f1b609b23a6cd77a3b7f1e4814ed7df82 Mon Sep 17 00:00:00 2001 From: ToxicKidz Date: Fri, 7 May 2021 11:01:24 -0400 Subject: chore: Get the next perfect square If the amount of squares is not a perfect square, get the next highest perfect square --- bot/exts/evergreen/avatar_modification/avatar_modify.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/avatar_modification/avatar_modify.py b/bot/exts/evergreen/avatar_modification/avatar_modify.py index dffe43ce..6418eaee 100644 --- a/bot/exts/evergreen/avatar_modification/avatar_modify.py +++ b/bot/exts/evergreen/avatar_modification/avatar_modify.py @@ -325,8 +325,10 @@ class AvatarModify(commands.Cog): if 1 <= squares <= MAX_SQUARES: raise commands.BadArgument(f"Squares must be a positive number less than or equal to {MAX_SQUARES:,}.") - if not math.sqrt(squares).is_integer(): - raise commands.BadArgument("The number of squares must be a perfect square.") + sqrt = math.sqrt(squares) + + if not sqrt.is_integer(): + squares = math.ceil(sqrt) ** 2 # Get the next perfect square file_name = file_safe_name("mosaic_avatar", ctx.author.display_name) -- cgit v1.2.3 From 14fc010db6a8ac06eb356617146e9deaa540177b Mon Sep 17 00:00:00 2001 From: ToxicKidz Date: Fri, 7 May 2021 11:34:35 -0400 Subject: fix: Add a missing 'not' --- bot/exts/evergreen/avatar_modification/avatar_modify.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/avatar_modification/avatar_modify.py b/bot/exts/evergreen/avatar_modification/avatar_modify.py index 6418eaee..2afc3b74 100644 --- a/bot/exts/evergreen/avatar_modification/avatar_modify.py +++ b/bot/exts/evergreen/avatar_modification/avatar_modify.py @@ -322,7 +322,7 @@ class AvatarModify(commands.Cog): await ctx.send(f"{Emojis.cross_mark} Could not get member info.") return - if 1 <= squares <= MAX_SQUARES: + if not 1 <= squares <= MAX_SQUARES: raise commands.BadArgument(f"Squares must be a positive number less than or equal to {MAX_SQUARES:,}.") sqrt = math.sqrt(squares) -- cgit v1.2.3 From 39350fb471d9dff954cbecceebf3113b28dad6d9 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 7 May 2021 17:34:25 +0100 Subject: Switch from fetch_member to fetch_user This is because discord.py would populate the user portion of the fetched member with cached information which would cause the avatar url not to be updated as we had intended with that function --- .../evergreen/avatar_modification/avatar_modify.py | 68 +++++++++++----------- 1 file changed, 35 insertions(+), 33 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/avatar_modification/avatar_modify.py b/bot/exts/evergreen/avatar_modification/avatar_modify.py index 2afc3b74..a84983e0 100644 --- a/bot/exts/evergreen/avatar_modification/avatar_modify.py +++ b/bot/exts/evergreen/avatar_modification/avatar_modify.py @@ -12,7 +12,7 @@ from aiohttp import client_exceptions from discord.ext import commands from discord.ext.commands.errors import BadArgument -from bot.constants import Client, Colours, Emojis +from bot.constants import Colours, Emojis from bot.exts.evergreen.avatar_modification._effects import PfpEffects from bot.utils.extensions import invoke_help_command from bot.utils.halloween import spookifications @@ -66,23 +66,25 @@ class AvatarModify(commands.Cog): def __init__(self, bot: commands.Bot) -> None: self.bot = bot - async def _fetch_member(self, member_id: int) -> t.Optional[discord.Member]: + async def _fetch_user(self, user_id: int) -> t.Optional[discord.Member]: """ - Fetches a member and handles errors. + Fetches a user and handles errors. - This helper funciton is required as the member cache doesn't always have the most up to date + This helper function is required as the member cache doesn't always have the most up to date profile picture. This can lead to errors if the image is delted from the Discord CDN. + fetch_member can't be used due to the avatar url being part of the user object, and + some weird caching that D.py does """ try: - member = await self.bot.get_guild(Client.guild).fetch_member(member_id) + user = await self.bot.fetch_user(user_id) except discord.errors.NotFound: - log.debug(f"Member {member_id} left the guild before we could get their pfp.") + log.debug(f"User {user_id} could not be found.") return None except discord.HTTPException: - log.exception(f"Exception while trying to retrieve member {member_id} from Discord.") + log.exception(f"Exception while trying to retrieve user {user_id} from Discord.") return None - return member + return user @commands.group(aliases=("avatar_mod", "pfp_mod", "avatarmod", "pfpmod")) async def avatar_modify(self, ctx: commands.Context) -> None: @@ -94,13 +96,13 @@ class AvatarModify(commands.Cog): async def eightbit_command(self, ctx: commands.Context) -> None: """Pixelates your avatar and changes the palette to an 8bit one.""" async with ctx.typing(): - member = await self._fetch_member(ctx.author.id) - if not member: - await ctx.send(f"{Emojis.cross_mark} Could not get member info.") + user = await self._fetch_user(ctx.author.id) + if not user: + await ctx.send(f"{Emojis.cross_mark} Could not get user info.") return - image_bytes = await member.avatar_url.read() - file_name = file_safe_name("eightbit_avatar", member.display_name) + image_bytes = await user.avatar_url.read() + file_name = file_safe_name("eightbit_avatar", ctx.author.display_name) file = await in_executor( PfpEffects.apply_effect, @@ -115,7 +117,7 @@ class AvatarModify(commands.Cog): ) embed.set_image(url=f"attachment://{file_name}") - embed.set_footer(text=f"Made by {member.display_name}.", icon_url=member.avatar_url) + embed.set_footer(text=f"Made by {ctx.author.display_name}.", icon_url=user.avatar_url) await ctx.send(embed=embed, file=file) @@ -140,9 +142,9 @@ class AvatarModify(commands.Cog): return args[0] async with ctx.typing(): - member = await self._fetch_member(ctx.author.id) - if not member: - await ctx.send(f"{Emojis.cross_mark} Could not get member info.") + user = await self._fetch_user(ctx.author.id) + if not user: + await ctx.send(f"{Emojis.cross_mark} Could not get user info.") return egg = None @@ -155,8 +157,8 @@ class AvatarModify(commands.Cog): return ctx.send = send_message # Reassigns ctx.send - image_bytes = await member.avatar_url_as(size=256).read() - file_name = file_safe_name("easterified_avatar", member.display_name) + image_bytes = await user.avatar_url_as(size=256).read() + file_name = file_safe_name("easterified_avatar", ctx.author.display_name) file = await in_executor( PfpEffects.apply_effect, @@ -171,7 +173,7 @@ class AvatarModify(commands.Cog): description="Here is your lovely avatar, all bright and colourful\nwith Easter pastel colours. Enjoy :D" ) embed.set_image(url=f"attachment://{file_name}") - embed.set_footer(text=f"Made by {member.display_name}.", icon_url=member.avatar_url) + embed.set_footer(text=f"Made by {ctx.author.display_name}.", icon_url=user.avatar_url) await ctx.send(file=file, embed=embed) @@ -226,11 +228,11 @@ class AvatarModify(commands.Cog): return async with ctx.typing(): - member = await self._fetch_member(ctx.author.id) - if not member: - await ctx.send(f"{Emojis.cross_mark} Could not get member info.") + user = await self._fetch_user(ctx.author.id) + if not user: + await ctx.send(f"{Emojis.cross_mark} Could not get user info.") return - image_bytes = await member.avatar_url_as(size=1024).read() + image_bytes = await user.avatar_url.read() await self.send_pride_image(ctx, image_bytes, pixels, flag, option) @prideavatar.command() @@ -286,13 +288,13 @@ class AvatarModify(commands.Cog): if member is None: member = ctx.author - member = await self._fetch_member(member.id) - if not member: - await ctx.send(f"{Emojis.cross_mark} Could not get member info.") + user = await self._fetch_user(member.id) + if not user: + await ctx.send(f"{Emojis.cross_mark} Could not get user info.") return async with ctx.typing(): - image_bytes = await member.avatar_url.read() + image_bytes = await user.avatar_url.read() file_name = file_safe_name("spooky_avatar", member.display_name) @@ -317,9 +319,9 @@ class AvatarModify(commands.Cog): async def mosaic_command(self, ctx: commands.Context, squares: int = 16) -> None: """Splits your avatar into x squares, randomizes them and stitches them back into a new image!""" async with ctx.typing(): - member = await self._fetch_member(ctx.author.id) - if not member: - await ctx.send(f"{Emojis.cross_mark} Could not get member info.") + user = await self._fetch_user(ctx.author.id) + if not user: + await ctx.send(f"{Emojis.cross_mark} Could not get user info.") return if not 1 <= squares <= MAX_SQUARES: @@ -332,7 +334,7 @@ class AvatarModify(commands.Cog): file_name = file_safe_name("mosaic_avatar", ctx.author.display_name) - img_bytes = await member.avatar_url.read() + img_bytes = await user.avatar_url.read() file = await in_executor( PfpEffects.mosaic_effect, @@ -358,7 +360,7 @@ class AvatarModify(commands.Cog): ) embed.set_image(url=f"attachment://{file_name}") - embed.set_footer(text=f"Made by {ctx.author.display_name}", icon_url=ctx.author.avatar_url) + embed.set_footer(text=f"Made by {ctx.author.display_name}", icon_url=user.avatar_url) await ctx.send(file=file, embed=embed) -- cgit v1.2.3 From 7725a022675c6bddad4354283fbad8e56a364eb6 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 7 May 2021 17:35:54 +0100 Subject: Download avatars as size 1024 to avoid very large avatars affecting infra --- bot/exts/evergreen/avatar_modification/avatar_modify.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/avatar_modification/avatar_modify.py b/bot/exts/evergreen/avatar_modification/avatar_modify.py index a84983e0..1eceaf03 100644 --- a/bot/exts/evergreen/avatar_modification/avatar_modify.py +++ b/bot/exts/evergreen/avatar_modification/avatar_modify.py @@ -101,7 +101,7 @@ class AvatarModify(commands.Cog): await ctx.send(f"{Emojis.cross_mark} Could not get user info.") return - image_bytes = await user.avatar_url.read() + image_bytes = await user.avatar_url_as(size=1024).read() file_name = file_safe_name("eightbit_avatar", ctx.author.display_name) file = await in_executor( @@ -232,7 +232,7 @@ class AvatarModify(commands.Cog): if not user: await ctx.send(f"{Emojis.cross_mark} Could not get user info.") return - image_bytes = await user.avatar_url.read() + image_bytes = await user.avatar_url_as(size=1024).read() await self.send_pride_image(ctx, image_bytes, pixels, flag, option) @prideavatar.command() @@ -294,7 +294,7 @@ class AvatarModify(commands.Cog): return async with ctx.typing(): - image_bytes = await user.avatar_url.read() + image_bytes = await user.avatar_url_as(size=1024).read() file_name = file_safe_name("spooky_avatar", member.display_name) @@ -334,7 +334,7 @@ class AvatarModify(commands.Cog): file_name = file_safe_name("mosaic_avatar", ctx.author.display_name) - img_bytes = await user.avatar_url.read() + img_bytes = await user.avatar_url_as(size=1024).read() file = await in_executor( PfpEffects.mosaic_effect, -- cgit v1.2.3 From 94a9550f3729d7c266c3437554b36d8b52fc84fb Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 7 May 2021 17:36:56 +0100 Subject: Add the number of squares that were used by .mosaic This is useful for when we round up the number of squares used, users can still see how many were actually used. --- bot/exts/evergreen/avatar_modification/avatar_modify.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/avatar_modification/avatar_modify.py b/bot/exts/evergreen/avatar_modification/avatar_modify.py index 1eceaf03..b604b3e4 100644 --- a/bot/exts/evergreen/avatar_modification/avatar_modify.py +++ b/bot/exts/evergreen/avatar_modification/avatar_modify.py @@ -351,7 +351,7 @@ class AvatarModify(commands.Cog): description = "What a masterpiece. :star:" else: title = "Your mosaic avatar" - description = "Here is your avatar. I think it looks a bit *puzzling*" + description = f"Here is your avatar. I think it looks a bit *puzzling*\nMade with {squares} squares" embed = discord.Embed( title=title, -- cgit v1.2.3 From 28f0c10eaf8880995d4eccfe3df92e3b707d95d5 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 7 May 2021 17:43:56 +0100 Subject: Correct grammer in the new mosaic command output --- bot/exts/evergreen/avatar_modification/avatar_modify.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/avatar_modification/avatar_modify.py b/bot/exts/evergreen/avatar_modification/avatar_modify.py index b604b3e4..9cc2e6b4 100644 --- a/bot/exts/evergreen/avatar_modification/avatar_modify.py +++ b/bot/exts/evergreen/avatar_modification/avatar_modify.py @@ -351,7 +351,7 @@ class AvatarModify(commands.Cog): description = "What a masterpiece. :star:" else: title = "Your mosaic avatar" - description = f"Here is your avatar. I think it looks a bit *puzzling*\nMade with {squares} squares" + description = f"Here is your avatar. I think it looks a bit *puzzling*\nMade with {squares} squares." embed = discord.Embed( title=title, -- cgit v1.2.3 From e929d6b07ee5676d6f73fe7fc9d86db8214f6d22 Mon Sep 17 00:00:00 2001 From: ChrisJL Date: Fri, 7 May 2021 17:51:40 +0100 Subject: Update return type hint to reflect new behaviour. Co-authored-by: ToxicKidz <78174417+ToxicKidz@users.noreply.github.com> --- bot/exts/evergreen/avatar_modification/avatar_modify.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/avatar_modification/avatar_modify.py b/bot/exts/evergreen/avatar_modification/avatar_modify.py index 9cc2e6b4..693d15c7 100644 --- a/bot/exts/evergreen/avatar_modification/avatar_modify.py +++ b/bot/exts/evergreen/avatar_modification/avatar_modify.py @@ -66,7 +66,7 @@ class AvatarModify(commands.Cog): def __init__(self, bot: commands.Bot) -> None: self.bot = bot - async def _fetch_user(self, user_id: int) -> t.Optional[discord.Member]: + async def _fetch_user(self, user_id: int) -> t.Optional[discord.User]: """ Fetches a user and handles errors. -- cgit v1.2.3 From d5f01153aac59cfbc043d894ebf6e1173c1efb48 Mon Sep 17 00:00:00 2001 From: ToxicKidz Date: Fri, 7 May 2021 13:41:23 -0400 Subject: chore: Reformat the code to follow the style guild --- bot/__init__.py | 2 +- bot/exts/christmas/advent_of_code/_cog.py | 18 ++++++++++------ bot/exts/christmas/hanukkah_embed.py | 34 ++++++++++++++++++++----------- bot/exts/evergreen/cheatsheet.py | 20 ++++++++++-------- bot/exts/evergreen/connect_four.py | 6 ++++-- bot/exts/evergreen/game.py | 15 +++++++------- bot/exts/evergreen/pythonfacts.py | 14 ++++++++----- bot/exts/evergreen/snakes/_snakes_cog.py | 26 ++++++++++++++++------- bot/exts/evergreen/snakes/_utils.py | 7 +++++-- 9 files changed, 92 insertions(+), 50 deletions(-) (limited to 'bot') diff --git a/bot/__init__.py b/bot/__init__.py index 669f9f5d..e5ed9d92 100644 --- a/bot/__init__.py +++ b/bot/__init__.py @@ -69,7 +69,7 @@ logging.basicConfig( level=logging.TRACE if Client.debug else logging.DEBUG, handlers=[console_handler, file_handler], ) -logging.getLogger().info('Logging initialization complete') +logging.getLogger().info("Logging initialization complete") # On Windows, the selector event loop is required for aiodns. diff --git a/bot/exts/christmas/advent_of_code/_cog.py b/bot/exts/christmas/advent_of_code/_cog.py index da1cf28d..863d2a21 100644 --- a/bot/exts/christmas/advent_of_code/_cog.py +++ b/bot/exts/christmas/advent_of_code/_cog.py @@ -72,11 +72,15 @@ class AdventOfCode(commands.Cog): if role not in ctx.author.roles: await ctx.author.add_roles(role) - await ctx.send("Okay! You have been __subscribed__ to notifications about new Advent of Code tasks. " - f"You can run `{unsubscribe_command}` to disable them again for you.") + await ctx.send( + "Okay! You have been __subscribed__ to notifications about new Advent of Code tasks. " + f"You can run `{unsubscribe_command}` to disable them again for you." + ) else: - await ctx.send("Hey, you already are receiving notifications about new Advent of Code tasks. " - f"If you don't want them any more, run `{unsubscribe_command}` instead.") + await ctx.send( + "Hey, you already are receiving notifications about new Advent of Code tasks. " + f"If you don't want them any more, run `{unsubscribe_command}` instead." + ) @in_month(Month.DECEMBER) @adventofcode_group.command(name="unsubscribe", aliases=("unsub",), brief="Notifications for new days") @@ -110,8 +114,10 @@ class AdventOfCode(commands.Cog): else: delta_str = f"{delta.days} days" - await ctx.send(f"The Advent of Code event is not currently running. " - f"The next event will start in {delta_str}.") + await ctx.send( + f"The Advent of Code event is not currently running. " + f"The next event will start in {delta_str}." + ) return tomorrow, time_left = _helpers.time_left_to_est_midnight() diff --git a/bot/exts/christmas/hanukkah_embed.py b/bot/exts/christmas/hanukkah_embed.py index 214044b8..af5cfccf 100644 --- a/bot/exts/christmas/hanukkah_embed.py +++ b/bot/exts/christmas/hanukkah_embed.py @@ -17,8 +17,10 @@ class HanukkahEmbed(commands.Cog): def __init__(self, bot: Bot): self.bot = bot - self.url = ("https://www.hebcal.com/hebcal/?v=1&cfg=json&maj=on&min=on&mod=on&nx=on&" - "year=now&month=x&ss=on&mf=on&c=on&geo=geoname&geonameid=3448439&m=50&s=on") + self.url = ( + "https://www.hebcal.com/hebcal/?v=1&cfg=json&maj=on&min=on&mod=on&nx=on&" + "year=now&month=x&ss=on&mf=on&c=on&geo=geoname&geonameid=3448439&m=50&s=on" + ) self.hanukkah_days = [] self.hanukkah_months = [] self.hanukkah_years = [] @@ -64,13 +66,17 @@ class HanukkahEmbed(commands.Cog): hours = now.hour + 4 # using only hours hanukkah_start_hour = 18 if hours < hanukkah_start_hour: - embed.description = (f"Hanukkah hasnt started yet, " - f"it will start in about {hanukkah_start_hour - hours} hour/s.") + embed.description = ( + f"Hanukkah hasnt started yet, " + f"it will start in about {hanukkah_start_hour - hours} hour/s." + ) await ctx.send(embed=embed) return elif hours > hanukkah_start_hour: - embed.description = (f"It is the starting day of Hanukkah ! " - f"Its been {hours - hanukkah_start_hour} hours hanukkah started !") + embed.description = ( + f"It is the starting day of Hanukkah ! " + f"Its been {hours - hanukkah_start_hour} hours hanukkah started !" + ) await ctx.send(embed=embed) return festival_day = self.hanukkah_days.index(day) @@ -87,18 +93,22 @@ class HanukkahEmbed(commands.Cog): message = "" for _ in range(1, festival_day + 1): message += ":menorah:" - embed.description = f"It is the {festival_day}{suffix} day of Hanukkah ! \n {message}" + embed.description = f"It is the {festival_day}{suffix} day of Hanukkah!\n{message}" await ctx.send(embed=embed) else: if today < hanukkah_start: festival_starting_month = hanukkah_start.strftime("%B") - embed.description = (f"Hanukkah has not started yet. " - f"Hanukkah will start at sundown on {hanukkah_start_day}th " - f"of {festival_starting_month}.") + embed.description = ( + f"Hanukkah has not started yet. " + f"Hanukkah will start at sundown on {hanukkah_start_day}th " + f"of {festival_starting_month}." + ) else: festival_end_month = hanukkah_end.strftime("%B") - embed.description = (f"Looks like you missed Hanukkah !" - f"Hanukkah ended on {hanukkah_end_day}th of {festival_end_month}.") + embed.description = ( + f"Looks like you missed Hanukkah !" + f"Hanukkah ended on {hanukkah_end_day}th of {festival_end_month}." + ) await ctx.send(embed=embed) diff --git a/bot/exts/evergreen/cheatsheet.py b/bot/exts/evergreen/cheatsheet.py index 86fae167..7ee34b1d 100644 --- a/bot/exts/evergreen/cheatsheet.py +++ b/bot/exts/evergreen/cheatsheet.py @@ -12,7 +12,7 @@ from bot.bot import Bot from bot.constants import Categories, Channels, Colours, ERROR_REPLIES from bot.utils.decorators import whitelist_override -ERROR_MESSAGE = f""" +ERROR_MESSAGE = f"""\ Unknown cheat sheet. Please try to reformulate your query. **Examples**: @@ -61,14 +61,18 @@ class CheatSheet(commands.Cog): body_space = min(1986 - len(url), 1000) if len(body_text) > body_space: - description = (f"**Result Of cht.sh**\n" - f"```python\n{body_text[:body_space]}\n" - f"... (truncated - too many lines)```\n" - f"Full results: {url} ") + description = ( + f"**Result Of cht.sh**\n" + f"```python\n{body_text[:body_space]}\n" + f"... (truncated - too many lines)```\n" + f"Full results: {url} " + ) else: - description = (f"**Result Of cht.sh**\n" - f"```python\n{body_text}```\n" - f"{url}") + description = ( + f"**Result Of cht.sh**\n" + f"```python\n{body_text}```\n" + f"{url}" + ) return False, description @commands.command( diff --git a/bot/exts/evergreen/connect_four.py b/bot/exts/evergreen/connect_four.py index 929a15d8..dc365a70 100644 --- a/bot/exts/evergreen/connect_four.py +++ b/bot/exts/evergreen/connect_four.py @@ -277,8 +277,10 @@ class ConnectFour(commands.Cog): return False if not self.min_board_size <= board_size <= self.max_board_size: - await ctx.send(f"{board_size} is not a valid board size. A valid board size is " - f"between `{self.min_board_size}` and `{self.max_board_size}`.") + await ctx.send( + f"{board_size} is not a valid board size. A valid board size is " + f"between `{self.min_board_size}` and `{self.max_board_size}`." + ) return False return True diff --git a/bot/exts/evergreen/game.py b/bot/exts/evergreen/game.py index 4da33259..6f01d81c 100644 --- a/bot/exts/evergreen/game.py +++ b/bot/exts/evergreen/game.py @@ -335,13 +335,14 @@ class Games(Cog): return await ctx.send("Successfully refreshed genres.") - async def get_games_list(self, - amount: int, - genre: Optional[str] = None, - sort: Optional[str] = None, - additional_body: str = "", - offset: int = 0 - ) -> List[Dict[str, Any]]: + async def get_games_list( + self, + amount: int, + genre: Optional[str] = None, + sort: Optional[str] = None, + additional_body: str = "", + offset: int = 0 + ) -> List[Dict[str, Any]]: """ Get list of games from IGDB API by parameters that is provided. diff --git a/bot/exts/evergreen/pythonfacts.py b/bot/exts/evergreen/pythonfacts.py index c0086c20..e162c9bd 100644 --- a/bot/exts/evergreen/pythonfacts.py +++ b/bot/exts/evergreen/pythonfacts.py @@ -18,11 +18,15 @@ class PythonFacts(commands.Cog): @commands.command(name="pythonfact", aliases=["pyfact"]) async def get_python_fact(self, ctx: commands.Context) -> None: """Sends a Random fun fact about Python.""" - embed = discord.Embed(title="Python Facts", - description=next(FACTS), - colour=next(COLORS)) - embed.add_field(name="Suggestions", - value="Suggest more facts [here!](https://github.com/python-discord/meta/discussions/93)") + embed = discord.Embed( + title="Python Facts", + description=next(FACTS), + colour=next(COLORS) + ) + embed.add_field( + name="Suggestions", + value="Suggest more facts [here!](https://github.com/python-discord/meta/discussions/93)" + ) await ctx.send(embed=embed) diff --git a/bot/exts/evergreen/snakes/_snakes_cog.py b/bot/exts/evergreen/snakes/_snakes_cog.py index 62795aef..6278c883 100644 --- a/bot/exts/evergreen/snakes/_snakes_cog.py +++ b/bot/exts/evergreen/snakes/_snakes_cog.py @@ -497,9 +497,11 @@ class Snakes(Cog): for i in range(0, 10): page_guess_list.append(f"{HOLE_EMOJI} {HOLE_EMOJI} {HOLE_EMOJI} {HOLE_EMOJI}") page_result_list.append(f"{CROSS_EMOJI} {CROSS_EMOJI} {CROSS_EMOJI} {CROSS_EMOJI}") - board.append(f"`{i+1:02d}` " - f"{page_guess_list[i]} - " - f"{page_result_list[i]}") + board.append( + f"`{i+1:02d}` " + f"{page_guess_list[i]} - " + f"{page_result_list[i]}" + ) board.append(EMPTY_UNICODE) antidote_embed.add_field(name="10 guesses remaining", value="\n".join(board)) board_id = await ctx.send(embed=antidote_embed) # Display board @@ -667,8 +669,14 @@ class Snakes(Cog): ) emoji = "https://emojipedia-us.s3.amazonaws.com/thumbs/60/google/3/snake_1f40d.png" - image = next((url for url in data["image_list"] - if url.endswith(self.valid_image_extensions)), emoji) + + _iter = ( + url + for url in data["image_list"] + if url.endswith(self.valid_image_extensions) + ) + image = next(_iter, emoji) + embed.set_image(url=image) await ctx.send(embed=embed) @@ -693,8 +701,12 @@ class Snakes(Cog): data = await self._get_snek(snake) - image = next((url for url in data["image_list"] - if url.endswith(self.valid_image_extensions)), None) + _iter = ( + url + for url in data["image_list"] + if url.endswith(self.valid_image_extensions) + ) + image = next(_iter, None) embed = Embed( title="Which of the following is the snake in the image?", diff --git a/bot/exts/evergreen/snakes/_utils.py b/bot/exts/evergreen/snakes/_utils.py index d58ee279..998c13a9 100644 --- a/bot/exts/evergreen/snakes/_utils.py +++ b/bot/exts/evergreen/snakes/_utils.py @@ -191,8 +191,11 @@ class PerlinNoiseFactory(object): def get_plain_noise(self, *point) -> float: """Get plain noise for a single point, without taking into account either octaves or tiling.""" if len(point) != self.dimension: - raise ValueError("Expected {0} values, got {1}".format( - self.dimension, len(point))) + raise ValueError( + "Expected {0} values, got {1}".format( + self.dimension, len(point) + ) + ) # Build a list of the (min, max) bounds in each dimension grid_coords = [] -- cgit v1.2.3 From 0017b76e914e4963a8556ac768e2b4a176111636 Mon Sep 17 00:00:00 2001 From: ToxicKidz Date: Fri, 7 May 2021 13:45:57 -0400 Subject: chore: Apply anand's suggested changes --- bot/exts/pride/pride_facts.py | 2 +- bot/exts/valentines/be_my_valentine.py | 5 ++--- bot/exts/valentines/lovecalculator.py | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) (limited to 'bot') diff --git a/bot/exts/pride/pride_facts.py b/bot/exts/pride/pride_facts.py index b2daaab7..6d06cf64 100644 --- a/bot/exts/pride/pride_facts.py +++ b/bot/exts/pride/pride_facts.py @@ -101,5 +101,5 @@ class PrideFacts(commands.Cog): def setup(bot: Bot) -> None: - """Cog loader for pride facts.""" + """Load the Pride Facts Cog.""" bot.add_cog(PrideFacts(bot)) diff --git a/bot/exts/valentines/be_my_valentine.py b/bot/exts/valentines/be_my_valentine.py index cf6099d2..1ce92203 100644 --- a/bot/exts/valentines/be_my_valentine.py +++ b/bot/exts/valentines/be_my_valentine.py @@ -6,7 +6,6 @@ from typing import Tuple import discord from discord.ext import commands -from discord.ext.commands.cooldowns import BucketType from bot.bot import Bot from bot.constants import Channels, Colours, Lovefest, Month @@ -69,7 +68,7 @@ class BeMyValentine(commands.Cog): await user.remove_roles(role) await ctx.send("The lovefest role has been successfully removed!") - @commands.cooldown(1, 1800, BucketType.user) + @commands.cooldown(1, 1800, commands.BucketType.user) @commands.group(name="bemyvalentine", invoke_without_command=True) async def send_valentine( self, ctx: commands.Context, user: discord.Member, *, valentine_type: str = None @@ -108,7 +107,7 @@ class BeMyValentine(commands.Cog): ) await channel.send(user.mention, embed=embed) - @commands.cooldown(1, 1800, BucketType.user) + @commands.cooldown(1, 1800, commands.BucketType.user) @send_valentine.command(name="secret") async def anonymous( self, ctx: commands.Context, user: discord.Member, *, valentine_type: str = None diff --git a/bot/exts/valentines/lovecalculator.py b/bot/exts/valentines/lovecalculator.py index 9096d994..8a4c71eb 100644 --- a/bot/exts/valentines/lovecalculator.py +++ b/bot/exts/valentines/lovecalculator.py @@ -61,7 +61,7 @@ class LoveCalculator(Cog): # Make sure user didn't provide something silly such as 10 spaces if not (who and whom): - raise BadArgument("Arguments be non-empty strings.") + raise BadArgument("Arguments must be non-empty strings.") # Hash inputs to guarantee consistent results (hashing algorithm choice arbitrary) # -- cgit v1.2.3 From ae3ecb3ac46d90ebab1cb556fdc357848f45abb5 Mon Sep 17 00:00:00 2001 From: ToxicKidz <78174417+ToxicKidz@users.noreply.github.com> Date: Fri, 7 May 2021 13:47:43 -0400 Subject: chore: Simplify the if statement Co-authored-by: Anand Krishna <40204976+anand2312@users.noreply.github.com> --- bot/exts/valentines/be_my_valentine.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/valentines/be_my_valentine.py b/bot/exts/valentines/be_my_valentine.py index 1ce92203..b9cf6738 100644 --- a/bot/exts/valentines/be_my_valentine.py +++ b/bot/exts/valentines/be_my_valentine.py @@ -51,7 +51,7 @@ class BeMyValentine(commands.Cog): """Adds the lovefest role.""" user = ctx.author role = ctx.guild.get_role(Lovefest.role_id) - if Lovefest.role_id not in [role.id for role in ctx.message.author.roles]: + if role not in ctx.author.roles: await user.add_roles(role) await ctx.send("The Lovefest role has been added !") else: -- cgit v1.2.3 From fb556c6407bab3ef078bc23620074ba206fb7ad8 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela Date: Mon, 10 May 2021 00:27:25 +0300 Subject: Fixes Expected Token Revoke Status Code --- bot/exts/evergreen/reddit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/reddit.py b/bot/exts/evergreen/reddit.py index 916563ac..e57fa2c0 100644 --- a/bot/exts/evergreen/reddit.py +++ b/bot/exts/evergreen/reddit.py @@ -174,7 +174,7 @@ class Reddit(Cog): } ) - if response.status == 204 and response.content_type == "application/json": + if response.status in [200, 204] and response.content_type == "application/json": self.access_token = None else: log.warning(f"Unable to revoke access token: status {response.status}.") -- cgit v1.2.3 From 4503a3307b989b251826f36e9370bda7471640b3 Mon Sep 17 00:00:00 2001 From: ToxicKidz Date: Sun, 9 May 2021 17:49:53 -0400 Subject: fix: Use str.isdecimal instead of str.isdigit --- bot/utils/__init__.py | 4 ++-- bot/utils/converters.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'bot') diff --git a/bot/utils/__init__.py b/bot/utils/__init__.py index 2fac2086..09a4dfc3 100644 --- a/bot/utils/__init__.py +++ b/bot/utils/__init__.py @@ -51,7 +51,7 @@ async def disambiguate( choices = (f"{index}: {entry}" for index, entry in enumerate(entries, start=1)) def check(message: discord.Message) -> bool: - return (message.content.isdigit() + return (message.content.isdecimal() and message.author == ctx.author and message.channel == ctx.channel) @@ -87,7 +87,7 @@ async def disambiguate( except asyncio.TimeoutError: raise BadArgument("Timed out.") - # Guaranteed to not error because of isdigit() in check + # Guaranteed to not error because of isdecimal() in check index = int(result.content) try: diff --git a/bot/utils/converters.py b/bot/utils/converters.py index 98607087..72b64848 100644 --- a/bot/utils/converters.py +++ b/bot/utils/converters.py @@ -37,7 +37,7 @@ class CoordinateConverter(commands.Converter): digit = coordinate[:-1] letter = coordinate[-1] - if not digit.isdigit(): + if not digit.isdecimal(): raise commands.BadArgument x = ord(letter) - ord("a") @@ -76,7 +76,7 @@ class DateConverter(commands.Converter): @staticmethod async def convert(ctx: commands.Context, argument: str) -> Union[int, datetime]: """Parse date (SOL or earth) into `datetime` or `int`. When invalid value, raise error.""" - if argument.isdigit(): + if argument.isdecimal(): return int(argument) try: date = datetime.strptime(argument, "%Y-%m-%d") -- cgit v1.2.3 From 4ba9c941fc8ceb50cf61cfcb5ae15afc6216c7b2 Mon Sep 17 00:00:00 2001 From: ToxicKidz Date: Mon, 10 May 2021 09:28:43 -0400 Subject: chore: Apply more suggested changes --- bot/exts/easter/bunny_name_generator.py | 1 + bot/exts/evergreen/help.py | 12 +++++++++--- bot/exts/evergreen/snakes/_utils.py | 4 +--- bot/exts/valentines/be_my_valentine.py | 18 ++++++------------ 4 files changed, 17 insertions(+), 18 deletions(-) (limited to 'bot') diff --git a/bot/exts/easter/bunny_name_generator.py b/bot/exts/easter/bunny_name_generator.py index 19a0b0f6..b6523ff6 100644 --- a/bot/exts/easter/bunny_name_generator.py +++ b/bot/exts/easter/bunny_name_generator.py @@ -23,6 +23,7 @@ class BunnyNameGenerator(commands.Cog): new_name = re.split(r"[_.\s]", displayname) if displayname not in new_name: return new_name + return None def find_vowels(self, displayname: str) -> str: """ diff --git a/bot/exts/evergreen/help.py b/bot/exts/evergreen/help.py index bfaf25f1..7e3fdad4 100644 --- a/bot/exts/evergreen/help.py +++ b/bot/exts/evergreen/help.py @@ -2,9 +2,8 @@ import asyncio import itertools import logging -from collections import namedtuple from contextlib import suppress -from typing import Union +from typing import List, NamedTuple, Union from discord import Colour, Embed, HTTPException, Message, Reaction, User from discord.ext import commands @@ -29,7 +28,14 @@ REACTIONS = { DELETE_EMOJI: "stop", } -Cog = namedtuple("Cog", ["name", "description", "commands"]) + +class Cog(NamedTuple): + """Show information about a Cog's name, description and commands.""" + + name: str + description: str + commands: List[Command] + log = logging.getLogger(__name__) diff --git a/bot/exts/evergreen/snakes/_utils.py b/bot/exts/evergreen/snakes/_utils.py index 998c13a9..9b38ffa2 100644 --- a/bot/exts/evergreen/snakes/_utils.py +++ b/bot/exts/evergreen/snakes/_utils.py @@ -192,9 +192,7 @@ class PerlinNoiseFactory(object): """Get plain noise for a single point, without taking into account either octaves or tiling.""" if len(point) != self.dimension: raise ValueError( - "Expected {0} values, got {1}".format( - self.dimension, len(point) - ) + f"Expected {self.dimension} values, got {len(point)}" ) # Build a list of the (min, max) bounds in each dimension diff --git a/bot/exts/valentines/be_my_valentine.py b/bot/exts/valentines/be_my_valentine.py index b9cf6738..d1eea388 100644 --- a/bot/exts/valentines/be_my_valentine.py +++ b/bot/exts/valentines/be_my_valentine.py @@ -151,21 +151,17 @@ class BeMyValentine(commands.Cog): def valentine_check(self, valentine_type: str) -> Tuple[str, str]: """Return the appropriate Valentine type & title based on the invoking user's input.""" if valentine_type is None: - valentine, title = self.random_valentine() + return self.random_valentine() elif valentine_type.lower() in ["p", "poem"]: - valentine = self.valentine_poem() - title = "A poem dedicated to" + return self.valentine_poem(), "A poem dedicated to" elif valentine_type.lower() in ["c", "compliment"]: - valentine = self.valentine_compliment() - title = "A compliment for" + return self.valentine_compliment(), "A compliment for" else: # in this case, the user decides to type his own valentine. - valentine = valentine_type - title = "A message for" - return valentine, title + return valentine_type, "A message for" @staticmethod def random_emoji() -> Tuple[str, str]: @@ -187,13 +183,11 @@ class BeMyValentine(commands.Cog): def valentine_poem(self) -> str: """Grabs a random poem.""" - valentine_poem = random.choice(self.valentines["valentine_poems"]) - return valentine_poem + return random.choice(self.valentines["valentine_poems"]) def valentine_compliment(self) -> str: """Grabs a random compliment.""" - valentine_compliment = random.choice(self.valentines["valentine_compliments"]) - return valentine_compliment + return random.choice(self.valentines["valentine_compliments"]) def setup(bot: Bot) -> None: -- cgit v1.2.3 From 52aa6dcd499fc3e757a226c1192755888a6d88ef Mon Sep 17 00:00:00 2001 From: ToxicKidz Date: Mon, 10 May 2021 09:30:46 -0400 Subject: chore: ctx.message.author -> ctx.author --- bot/exts/easter/bunny_name_generator.py | 2 +- bot/exts/evergreen/snakes/_snakes_cog.py | 6 +++--- bot/exts/halloween/monsterbio.py | 2 +- bot/exts/internal_eval/_internal_eval.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) (limited to 'bot') diff --git a/bot/exts/easter/bunny_name_generator.py b/bot/exts/easter/bunny_name_generator.py index b6523ff6..5e3b014d 100644 --- a/bot/exts/easter/bunny_name_generator.py +++ b/bot/exts/easter/bunny_name_generator.py @@ -62,7 +62,7 @@ class BunnyNameGenerator(commands.Cog): @commands.command() async def bunnifyme(self, ctx: commands.Context) -> None: """Gets your Discord username and bunnifies it.""" - username = ctx.message.author.display_name + username = ctx.author.display_name # If name contains spaces or other separators, get the individual words to randomly bunnify spaces_in_name = self.find_separators(username) diff --git a/bot/exts/evergreen/snakes/_snakes_cog.py b/bot/exts/evergreen/snakes/_snakes_cog.py index 6278c883..b844960a 100644 --- a/bot/exts/evergreen/snakes/_snakes_cog.py +++ b/bot/exts/evergreen/snakes/_snakes_cog.py @@ -746,7 +746,7 @@ class Snakes(Cog): my_snake_embed = Embed(description=":tada: Congrats! You hatched: **{0}**".format(snake_name)) my_snake_embed.set_thumbnail(url=snake_image) my_snake_embed.set_footer( - text=" Owner: {0}#{1}".format(ctx.message.author.name, ctx.message.author.discriminator) + text=" Owner: {0}#{1}".format(ctx.author.name, ctx.author.discriminator) ) await ctx.send(embed=my_snake_embed) @@ -1046,14 +1046,14 @@ class Snakes(Cog): """ with ctx.typing(): embed = Embed() - user = ctx.message.author + user = ctx.author if not message: # Get a random message from the users history messages = [] async for message in ctx.channel.history(limit=500).filter( - lambda msg: msg.author == ctx.message.author # Message was sent by author. + lambda msg: msg.author == ctx.author # Message was sent by author. ): messages.append(message.content) diff --git a/bot/exts/halloween/monsterbio.py b/bot/exts/halloween/monsterbio.py index dbafa43f..1aaba7bb 100644 --- a/bot/exts/halloween/monsterbio.py +++ b/bot/exts/halloween/monsterbio.py @@ -26,7 +26,7 @@ class MonsterBio(commands.Cog): @commands.command(brief="Sends your monster bio!") async def monsterbio(self, ctx: commands.Context) -> None: """Sends a description of a monster.""" - seeded_random = random.Random(ctx.message.author.id) # Seed a local Random instance rather than the system one + seeded_random = random.Random(ctx.author.id) # Seed a local Random instance rather than the system one name = self.generate_name(seeded_random) species = self.generate_name(seeded_random) diff --git a/bot/exts/internal_eval/_internal_eval.py b/bot/exts/internal_eval/_internal_eval.py index 4fe3bc09..56bf5add 100644 --- a/bot/exts/internal_eval/_internal_eval.py +++ b/bot/exts/internal_eval/_internal_eval.py @@ -114,7 +114,7 @@ class InternalEval(commands.Cog): """Evaluate the `code` in the current evaluation context.""" context_vars = { "message": ctx.message, - "author": ctx.message.author, + "author": ctx.author, "channel": ctx.channel, "guild": ctx.guild, "ctx": ctx, -- cgit v1.2.3 From b17a60a83edc98405e75ae84de23bbd1c7f4bf6c Mon Sep 17 00:00:00 2001 From: ToxicKidz Date: Mon, 10 May 2021 09:35:48 -0400 Subject: chore: Use ctx instead of ctx.channel --- bot/exts/easter/egghead_quiz.py | 2 +- bot/exts/evergreen/error_handler.py | 5 +---- bot/exts/evergreen/snakes/_snakes_cog.py | 2 +- bot/exts/evergreen/wolfram.py | 6 +++--- 4 files changed, 6 insertions(+), 9 deletions(-) (limited to 'bot') diff --git a/bot/exts/easter/egghead_quiz.py b/bot/exts/easter/egghead_quiz.py index 59c1f6f8..b6b1593d 100644 --- a/bot/exts/easter/egghead_quiz.py +++ b/bot/exts/easter/egghead_quiz.py @@ -64,7 +64,7 @@ class EggheadQuiz(commands.Cog): del self.quiz_messages[msg.id] - msg = await ctx.channel.fetch_message(msg.id) # Refreshes message + msg = await ctx.fetch_message(msg.id) # Refreshes message total_no = sum([len(await r.users().flatten()) for r in msg.reactions]) - len(valid_emojis) # - bot's reactions diff --git a/bot/exts/evergreen/error_handler.py b/bot/exts/evergreen/error_handler.py index faaf1386..de8e53d0 100644 --- a/bot/exts/evergreen/error_handler.py +++ b/bot/exts/evergreen/error_handler.py @@ -125,10 +125,7 @@ class CommandErrorHandler(commands.Cog): scope.set_extra("full_message", ctx.message.content) if ctx.guild is not None: - scope.set_extra( - "jump_to", - f"https://discordapp.com/channels/{ctx.guild.id}/{ctx.channel.id}/{ctx.message.id}" - ) + scope.set_extra("jump_to", ctx.message.jump_url) log.exception(f"Unhandled command error: {str(error)}", exc_info=error) diff --git a/bot/exts/evergreen/snakes/_snakes_cog.py b/bot/exts/evergreen/snakes/_snakes_cog.py index b844960a..c50b23c5 100644 --- a/bot/exts/evergreen/snakes/_snakes_cog.py +++ b/bot/exts/evergreen/snakes/_snakes_cog.py @@ -1052,7 +1052,7 @@ class Snakes(Cog): # Get a random message from the users history messages = [] - async for message in ctx.channel.history(limit=500).filter( + async for message in ctx.history(limit=500).filter( lambda msg: msg.author == ctx.author # Message was sent by author. ): messages.append(message.content) diff --git a/bot/exts/evergreen/wolfram.py b/bot/exts/evergreen/wolfram.py index 3cc12c03..ca7d8454 100644 --- a/bot/exts/evergreen/wolfram.py +++ b/bot/exts/evergreen/wolfram.py @@ -105,7 +105,7 @@ def custom_cooldown(*ignore: List[int]) -> Callable: async def get_pod_pages(ctx: Context, bot: Bot, query: str) -> Optional[List[Tuple]]: """Get the Wolfram API pod pages for the provided query.""" - async with ctx.channel.typing(): + async with ctx.typing(): url_str = parse.urlencode({ "input": query, "appid": APPID, @@ -180,7 +180,7 @@ class Wolfram(Cog): query = QUERY.format(request="simple", data=url_str) # Give feedback that the bot is working. - async with ctx.channel.typing(): + async with ctx.typing(): async with self.bot.http_session.get(query) as response: status = response.status image_bytes = await response.read() @@ -263,7 +263,7 @@ class Wolfram(Cog): query = QUERY.format(request="result", data=url_str) # Give feedback that the bot is working. - async with ctx.channel.typing(): + async with ctx.typing(): async with self.bot.http_session.get(query) as response: status = response.status response_text = await response.text() -- cgit v1.2.3 From 8c9298495a86da7c7b67b0a5d7557819ce0057e7 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 10 May 2021 20:52:13 +0100 Subject: Remove redundant comments in bookmark cog --- bot/exts/evergreen/bookmark.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/bookmark.py b/bot/exts/evergreen/bookmark.py index 5f550a4a..7a97a40d 100644 --- a/bot/exts/evergreen/bookmark.py +++ b/bot/exts/evergreen/bookmark.py @@ -24,7 +24,7 @@ class Bookmark(commands.Cog): @staticmethod def build_bookmark_dm(target_message: discord.Message, title: str) -> discord.Embed: - """Builds the embed to DM the bookmark requester.""" + """Build the embed to DM the bookmark requester.""" embed = discord.Embed( title=title, description=target_message.content, @@ -131,13 +131,11 @@ class Bookmark(commands.Cog): _, user = await self.bot.wait_for("reaction_add", timeout=TIMEOUT, check=event_check) except asyncio.TimeoutError: log.debug("Timed out waiting for a reaction") - # No reactions for the last `TIMEOUT` seconds break log.trace(f"{user} has successfully bookmarked from a reaction, attempting to DM them.") await self.action_bookmark(ctx.channel, user, target_message, title) bookmarked_users.append(user.id) - # Delete the message now that the bot isn't listening to it to save screen space await reaction_message.delete() -- cgit v1.2.3 From d821b4e2bd908e38cf3476dc5adb43211ccbcd3a Mon Sep 17 00:00:00 2001 From: vcokltfre Date: Wed, 12 May 2021 19:26:41 +0100 Subject: feat: merge ping and uptime cogs (#722) --- bot/exts/evergreen/ping.py | 21 +++++++++++++++++++-- bot/exts/evergreen/uptime.py | 33 --------------------------------- 2 files changed, 19 insertions(+), 35 deletions(-) delete mode 100644 bot/exts/evergreen/uptime.py (limited to 'bot') diff --git a/bot/exts/evergreen/ping.py b/bot/exts/evergreen/ping.py index 97f8b34d..07c13524 100644 --- a/bot/exts/evergreen/ping.py +++ b/bot/exts/evergreen/ping.py @@ -1,11 +1,14 @@ +import arrow +from dateutil.relativedelta import relativedelta from discord import Embed from discord.ext import commands +from bot import start_time from bot.constants import Colours class Ping(commands.Cog): - """Ping the bot to see its latency and state.""" + """Get info about the bot's ping and uptime.""" def __init__(self, bot: commands.Bot): self.bot = bot @@ -21,7 +24,21 @@ class Ping(commands.Cog): await ctx.send(embed=embed) + # Originally made in 70d2170a0a6594561d59c7d080c4280f1ebcd70b by lemon & gdude2002 + @commands.command(name="uptime") + async def uptime(self, ctx: commands.Context) -> None: + """Get the current uptime of the bot.""" + difference = relativedelta(start_time - arrow.utcnow()) + uptime_string = start_time.shift( + seconds=-difference.seconds, + minutes=-difference.minutes, + hours=-difference.hours, + days=-difference.days + ).humanize() + + await ctx.send(f"I started up {uptime_string}.") + def setup(bot: commands.Bot) -> None: - """Cog load.""" + """Load the Ping cog.""" bot.add_cog(Ping(bot)) diff --git a/bot/exts/evergreen/uptime.py b/bot/exts/evergreen/uptime.py deleted file mode 100644 index a9ad9dfb..00000000 --- a/bot/exts/evergreen/uptime.py +++ /dev/null @@ -1,33 +0,0 @@ -import logging - -import arrow -from dateutil.relativedelta import relativedelta -from discord.ext import commands - -from bot import start_time - -log = logging.getLogger(__name__) - - -class Uptime(commands.Cog): - """A cog for posting the bot's uptime.""" - - def __init__(self, bot: commands.Bot): - self.bot = bot - - @commands.command(name="uptime") - async def uptime(self, ctx: commands.Context) -> None: - """Responds with the uptime of the bot.""" - difference = relativedelta(start_time - arrow.utcnow()) - uptime_string = start_time.shift( - seconds=-difference.seconds, - minutes=-difference.minutes, - hours=-difference.hours, - days=-difference.days - ).humanize() - await ctx.send(f"I started up {uptime_string}.") - - -def setup(bot: commands.Bot) -> None: - """Uptime Cog load.""" - bot.add_cog(Uptime(bot)) -- cgit v1.2.3 From 2aa1916d5c8e4832f26f6da4094238e9a0021d1c Mon Sep 17 00:00:00 2001 From: ToxicKidz Date: Thu, 13 May 2021 13:34:06 -0400 Subject: chore: Use pathlib.Path.read_text & write_text over open --- bot/exts/christmas/advent_of_code/_cog.py | 3 +-- bot/exts/easter/april_fools_vids.py | 6 +++--- bot/exts/easter/bunny_name_generator.py | 3 +-- bot/exts/easter/easter_riddle.py | 5 ++--- bot/exts/easter/egg_decorating.py | 6 ++---- bot/exts/easter/egg_facts.py | 5 ++--- bot/exts/easter/egghead_quiz.py | 5 ++--- bot/exts/easter/save_the_planet.py | 4 +--- bot/exts/easter/traditions.py | 3 +-- bot/exts/evergreen/avatar_modification/avatar_modify.py | 4 ++-- bot/exts/evergreen/fun.py | 3 +-- bot/exts/evergreen/magic_8ball.py | 3 +-- bot/exts/evergreen/recommend_game.py | 3 +-- bot/exts/evergreen/snakes/_converter.py | 7 ++----- bot/exts/evergreen/snakes/_utils.py | 5 ++--- bot/exts/evergreen/speedrun.py | 4 ++-- bot/exts/evergreen/trivia_quiz.py | 4 +--- bot/exts/halloween/8ball.py | 3 +-- bot/exts/halloween/halloween_facts.py | 3 +-- bot/exts/halloween/halloweenify.py | 5 ++--- bot/exts/halloween/monsterbio.py | 5 +++-- bot/exts/halloween/monstersurvey.py | 6 ++---- bot/exts/halloween/spookynamerate.py | 3 +-- bot/exts/halloween/spookyrating.py | 6 +++--- bot/exts/pride/drag_queen_name.py | 3 +-- bot/exts/pride/pride_anthem.py | 4 +--- bot/exts/pride/pride_facts.py | 3 +-- bot/exts/valentines/be_my_valentine.py | 6 ++---- bot/exts/valentines/lovecalculator.py | 5 ++--- bot/exts/valentines/myvalenstate.py | 3 +-- bot/exts/valentines/pickuplines.py | 9 ++++----- bot/exts/valentines/savethedate.py | 5 ++--- bot/exts/valentines/valentine_zodiac.py | 16 ++++++++-------- bot/exts/valentines/whoisvalentine.py | 3 +-- 34 files changed, 63 insertions(+), 98 deletions(-) (limited to 'bot') diff --git a/bot/exts/christmas/advent_of_code/_cog.py b/bot/exts/christmas/advent_of_code/_cog.py index 863d2a21..ce6a4cb6 100644 --- a/bot/exts/christmas/advent_of_code/_cog.py +++ b/bot/exts/christmas/advent_of_code/_cog.py @@ -279,8 +279,7 @@ class AdventOfCode(commands.Cog): def _build_about_embed(self) -> discord.Embed: """Build and return the informational "About AoC" embed from the resources file.""" - with self.about_aoc_filepath.open("r", encoding="utf8") as f: - embed_fields = json.load(f) + embed_fields = json.loads(self.about_aoc_filepath.read_text("utf8")) about_embed = discord.Embed( title=self._base_url, diff --git a/bot/exts/easter/april_fools_vids.py b/bot/exts/easter/april_fools_vids.py index 3ce1f72a..5ef40704 100644 --- a/bot/exts/easter/april_fools_vids.py +++ b/bot/exts/easter/april_fools_vids.py @@ -1,6 +1,7 @@ import logging import random -from json import load +from json import loads +from pathlib import Path from discord.ext import commands @@ -8,8 +9,7 @@ from bot.bot import Bot log = logging.getLogger(__name__) -with open("bot/resources/easter/april_fools_vids.json", encoding="utf-8") as f: - ALL_VIDS = load(f) +ALL_VIDS = loads(Path("bot/resources/easter/april_fools_vids.json").read_text("utf-8")) class AprilFoolVideos(commands.Cog): diff --git a/bot/exts/easter/bunny_name_generator.py b/bot/exts/easter/bunny_name_generator.py index 5e3b014d..adde8704 100644 --- a/bot/exts/easter/bunny_name_generator.py +++ b/bot/exts/easter/bunny_name_generator.py @@ -11,8 +11,7 @@ from bot.bot import Bot log = logging.getLogger(__name__) -with Path("bot/resources/easter/bunny_names.json").open("r", encoding="utf8") as f: - BUNNY_NAMES = json.load(f) +BUNNY_NAMES = json.loads(Path("bot/resources/easter/bunny_names.json").read_text("utf8")) class BunnyNameGenerator(commands.Cog): diff --git a/bot/exts/easter/easter_riddle.py b/bot/exts/easter/easter_riddle.py index 9a253a6a..01b956f1 100644 --- a/bot/exts/easter/easter_riddle.py +++ b/bot/exts/easter/easter_riddle.py @@ -1,7 +1,7 @@ import asyncio import logging import random -from json import load +from json import loads from pathlib import Path import discord @@ -12,8 +12,7 @@ from bot.constants import Colours, NEGATIVE_REPLIES log = logging.getLogger(__name__) -with Path("bot/resources/easter/easter_riddle.json").open("r", encoding="utf8") as f: - RIDDLE_QUESTIONS = load(f) +RIDDLE_QUESTIONS = loads(Path("bot/resources/easter/easter_riddle.json").read_text("utf8")) TIMELIMIT = 10 diff --git a/bot/exts/easter/egg_decorating.py b/bot/exts/easter/egg_decorating.py index 3744989d..7448f702 100644 --- a/bot/exts/easter/egg_decorating.py +++ b/bot/exts/easter/egg_decorating.py @@ -15,11 +15,9 @@ from bot.utils import helpers log = logging.getLogger(__name__) -with open(Path("bot/resources/evergreen/html_colours.json"), encoding="utf8") as f: - HTML_COLOURS = json.load(f) +HTML_COLOURS = json.loads(Path("bot/resources/evergreen/html_colours.json").read_text("utf8")) -with open(Path("bot/resources/evergreen/xkcd_colours.json"), encoding="utf8") as f: - XKCD_COLOURS = json.load(f) +XKCD_COLOURS = json.loads(Path("bot/resources/evergreen/xkcd_colours.json").read_text("utf8")) COLOURS = [ (255, 0, 0, 255), (255, 128, 0, 255), (255, 255, 0, 255), (0, 255, 0, 255), diff --git a/bot/exts/easter/egg_facts.py b/bot/exts/easter/egg_facts.py index 8c93ca7b..c1c43f80 100644 --- a/bot/exts/easter/egg_facts.py +++ b/bot/exts/easter/egg_facts.py @@ -1,6 +1,6 @@ import logging import random -from json import load +from json import loads from pathlib import Path import discord @@ -30,8 +30,7 @@ class EasterFacts(commands.Cog): def load_json() -> dict: """Load a list of easter egg facts from the resource JSON file.""" p = Path("bot/resources/easter/easter_egg_facts.json") - with p.open(encoding="utf8") as f: - return load(f) + return loads(p.read_text("utf8")) @seasonal_task(Month.APRIL) async def send_egg_fact_daily(self) -> None: diff --git a/bot/exts/easter/egghead_quiz.py b/bot/exts/easter/egghead_quiz.py index b6b1593d..4b67310f 100644 --- a/bot/exts/easter/egghead_quiz.py +++ b/bot/exts/easter/egghead_quiz.py @@ -1,7 +1,7 @@ import asyncio import logging import random -from json import load +from json import loads from pathlib import Path from typing import Union @@ -13,8 +13,7 @@ from bot.constants import Colours log = logging.getLogger(__name__) -with open(Path("bot/resources/easter/egghead_questions.json"), "r", encoding="utf8") as f: - EGGHEAD_QUESTIONS = load(f) +EGGHEAD_QUESTIONS = loads(Path("bot/resources/easter/egghead_questions.json").read_text("utf8")) EMOJIS = [ diff --git a/bot/exts/easter/save_the_planet.py b/bot/exts/easter/save_the_planet.py index 444bb030..1bd515f2 100644 --- a/bot/exts/easter/save_the_planet.py +++ b/bot/exts/easter/save_the_planet.py @@ -7,9 +7,7 @@ from discord.ext import commands from bot.bot import Bot from bot.utils.randomization import RandomCycle - -with Path("bot/resources/easter/save_the_planet.json").open("r", encoding="utf8") as f: - EMBED_DATA = RandomCycle(json.load(f)) +EMBED_DATA = RandomCycle(json.loads(Path("bot/resources/easter/save_the_planet.json").read_text("utf8"))) class SaveThePlanet(commands.Cog): diff --git a/bot/exts/easter/traditions.py b/bot/exts/easter/traditions.py index cb70f2d0..93404f3e 100644 --- a/bot/exts/easter/traditions.py +++ b/bot/exts/easter/traditions.py @@ -9,8 +9,7 @@ from bot.bot import Bot log = logging.getLogger(__name__) -with open(Path("bot/resources/easter/traditions.json"), "r", encoding="utf8") as f: - traditions = json.load(f) +traditions = json.loads(Path("bot/resources/easter/traditions.json").read_text("utf8")) class Traditions(commands.Cog): diff --git a/bot/exts/evergreen/avatar_modification/avatar_modify.py b/bot/exts/evergreen/avatar_modification/avatar_modify.py index 5685b2c1..199b6dcb 100644 --- a/bot/exts/evergreen/avatar_modification/avatar_modify.py +++ b/bot/exts/evergreen/avatar_modification/avatar_modify.py @@ -6,6 +6,7 @@ import string import typing as t import unicodedata from concurrent.futures import ThreadPoolExecutor +from pathlib import Path import discord from aiohttp import client_exceptions @@ -27,8 +28,7 @@ MAX_SQUARES = 10_000 T = t.TypeVar("T") -with open("bot/resources/pride/gender_options.json") as f: - GENDER_OPTIONS = json.load(f) +GENDER_OPTIONS = json.loads(Path("bot/resources/pride/gender_options.json").read_text("utf8")) async def in_executor(func: t.Callable[..., T], *args) -> T: diff --git a/bot/exts/evergreen/fun.py b/bot/exts/evergreen/fun.py index ed51358c..3b266e1b 100644 --- a/bot/exts/evergreen/fun.py +++ b/bot/exts/evergreen/fun.py @@ -56,8 +56,7 @@ class Fun(Cog): def __init__(self, bot: Bot) -> None: self.bot = bot - with Path("bot/resources/evergreen/caesar_info.json").open("r", encoding="UTF-8") as f: - self._caesar_cipher_embed = json.load(f) + self._caesar_cipher_embed = json.loads(Path("bot/resources/evergreen/caesar_info.json").read_text("UTF-8")) @staticmethod def _get_random_die() -> str: diff --git a/bot/exts/evergreen/magic_8ball.py b/bot/exts/evergreen/magic_8ball.py index 7c9b929d..4b3ed2a4 100644 --- a/bot/exts/evergreen/magic_8ball.py +++ b/bot/exts/evergreen/magic_8ball.py @@ -14,8 +14,7 @@ class Magic8ball(commands.Cog): """A Magic 8ball command to respond to a user's question.""" def __init__(self): - with open(Path("bot/resources/evergreen/magic8ball.json"), "r", encoding="utf8") as file: - self.answers = json.load(file) + self.answers = json.loads(Path("bot/resources/evergreen/magic8ball.json").read_text("utf8")) @commands.command(name="8ball") async def output_answer(self, ctx: commands.Context, *, question: str) -> None: diff --git a/bot/exts/evergreen/recommend_game.py b/bot/exts/evergreen/recommend_game.py index 340a42d3..56596020 100644 --- a/bot/exts/evergreen/recommend_game.py +++ b/bot/exts/evergreen/recommend_game.py @@ -13,8 +13,7 @@ game_recs = [] # Populate the list `game_recs` with resource files for rec_path in Path("bot/resources/evergreen/game_recs").glob("*.json"): - with rec_path.open(encoding="utf8") as file: - data = json.load(file) + data = json.loads(rec_path.read_text("utf8")) game_recs.append(data) shuffle(game_recs) diff --git a/bot/exts/evergreen/snakes/_converter.py b/bot/exts/evergreen/snakes/_converter.py index 0ca10d6c..26bde611 100644 --- a/bot/exts/evergreen/snakes/_converter.py +++ b/bot/exts/evergreen/snakes/_converter.py @@ -63,13 +63,10 @@ class Snake(Converter): """Build list of snakes from the static snake resources.""" # Get all the snakes if cls.snakes is None: - with (SNAKE_RESOURCES / "snake_names.json").open(encoding="utf8") as snakefile: - cls.snakes = json.load(snakefile) - + cls.snakes = json.loads((SNAKE_RESOURCES / "snake_names.json").read_text("utf8")) # Get the special cases if cls.special_cases is None: - with (SNAKE_RESOURCES / "special_snakes.json").open(encoding="utf8") as snakefile: - special_cases = json.load(snakefile) + special_cases = json.loads((SNAKE_RESOURCES / "special_snakes.json").read_text("utf8")) cls.special_cases = {snake["name"].lower(): snake for snake in special_cases} @classmethod diff --git a/bot/exts/evergreen/snakes/_utils.py b/bot/exts/evergreen/snakes/_utils.py index 9b38ffa2..8b39f217 100644 --- a/bot/exts/evergreen/snakes/_utils.py +++ b/bot/exts/evergreen/snakes/_utils.py @@ -114,8 +114,7 @@ ANGLE_RANGE = math.pi * 2 def get_resource(file: str) -> List[dict]: """Load Snake resources JSON.""" - with (SNAKE_RESOURCES / f"{file}.json").open(encoding="utf-8") as snakefile: - return json.load(snakefile) + return json.loads((SNAKE_RESOURCES / f"{file}.json").read_text("utf-8")) def smoothstep(t: float) -> float: @@ -560,7 +559,7 @@ class SnakeAndLaddersGame: self.state = "roll" for user in self.players: self.round_has_rolled[user.id] = False - board_img = Image.open(str(SNAKE_RESOURCES / "snakes_and_ladders" / "board.jpg")) + board_img = Image.open(SNAKE_RESOURCES / "snakes_and_ladders" / "board.jpg") player_row_size = math.ceil(MAX_PLAYERS / 2) for i, player in enumerate(self.players): diff --git a/bot/exts/evergreen/speedrun.py b/bot/exts/evergreen/speedrun.py index d9820287..774eff81 100644 --- a/bot/exts/evergreen/speedrun.py +++ b/bot/exts/evergreen/speedrun.py @@ -8,8 +8,8 @@ from discord.ext import commands from bot.bot import Bot log = logging.getLogger(__name__) -with Path("bot/resources/evergreen/speedrun_links.json").open(encoding="utf8") as file: - LINKS = json.load(file) + +LINKS = json.loads(Path("bot/resources/evergreen/speedrun_links.json").read_text("utf8")) class Speedrun(commands.Cog): diff --git a/bot/exts/evergreen/trivia_quiz.py b/bot/exts/evergreen/trivia_quiz.py index 1953253b..9db201ef 100644 --- a/bot/exts/evergreen/trivia_quiz.py +++ b/bot/exts/evergreen/trivia_quiz.py @@ -41,9 +41,7 @@ class TriviaQuiz(commands.Cog): def load_questions() -> dict: """Load the questions from the JSON file.""" p = Path("bot", "resources", "evergreen", "trivia_quiz.json") - with p.open(encoding="utf8") as json_data: - questions = json.load(json_data) - return questions + return json.loads(p.read_text("utf8")) @commands.group(name="quiz", aliases=["trivia"], invoke_without_command=True) async def quiz_game(self, ctx: commands.Context, category: str = None) -> None: diff --git a/bot/exts/halloween/8ball.py b/bot/exts/halloween/8ball.py index d6c5a299..a2431190 100644 --- a/bot/exts/halloween/8ball.py +++ b/bot/exts/halloween/8ball.py @@ -10,8 +10,7 @@ from bot.bot import Bot log = logging.getLogger(__name__) -with Path("bot/resources/halloween/responses.json").open("r", encoding="utf8") as f: - RESPONSES = json.load(f) +RESPONSES = json.loads(Path("bot/resources/halloween/responses.json").read_text("utf8")) class SpookyEightBall(commands.Cog): diff --git a/bot/exts/halloween/halloween_facts.py b/bot/exts/halloween/halloween_facts.py index 3a89b5aa..98cc2db0 100644 --- a/bot/exts/halloween/halloween_facts.py +++ b/bot/exts/halloween/halloween_facts.py @@ -30,8 +30,7 @@ class HalloweenFacts(commands.Cog): """A Cog for displaying interesting facts about Halloween.""" def __init__(self): - with Path("bot/resources/halloween/halloween_facts.json").open("r", encoding="utf8") as file: - self.halloween_facts = json.load(file) + self.halloween_facts = json.loads(Path("bot/resources/halloween/halloween_facts.json").read_text("utf8")) self.facts = list(enumerate(self.halloween_facts)) def random_fact(self) -> Tuple[int, str]: diff --git a/bot/exts/halloween/halloweenify.py b/bot/exts/halloween/halloweenify.py index df55b55d..e839950a 100644 --- a/bot/exts/halloween/halloweenify.py +++ b/bot/exts/halloween/halloweenify.py @@ -1,5 +1,5 @@ import logging -from json import load +from json import loads from pathlib import Path from random import choice @@ -21,8 +21,7 @@ class Halloweenify(commands.Cog): async def halloweenify(self, ctx: commands.Context) -> None: """Change your nickname into a much spookier one!""" async with ctx.typing(): - with open(Path("bot/resources/halloween/halloweenify.json"), "r", encoding="utf8") as f: - data = load(f) + data = loads(Path("bot/resources/halloween/halloweenify.json").read_text("utf8")) # Choose a random character from our list we loaded above and set apart the nickname and image url. character = choice(data["characters"]) diff --git a/bot/exts/halloween/monsterbio.py b/bot/exts/halloween/monsterbio.py index 1aaba7bb..69e898cb 100644 --- a/bot/exts/halloween/monsterbio.py +++ b/bot/exts/halloween/monsterbio.py @@ -11,8 +11,9 @@ from bot.constants import Colours log = logging.getLogger(__name__) -with open(Path("bot/resources/halloween/monster.json"), "r", encoding="utf8") as f: - TEXT_OPTIONS = json.load(f) # Data for a mad-lib style generation of text +TEXT_OPTIONS = json.loads( + Path("bot/resources/halloween/monster.json").read_text("utf8") +) # Data for a mad-lib style generation of text class MonsterBio(commands.Cog): diff --git a/bot/exts/halloween/monstersurvey.py b/bot/exts/halloween/monstersurvey.py index 231454ea..240a97db 100644 --- a/bot/exts/halloween/monstersurvey.py +++ b/bot/exts/halloween/monstersurvey.py @@ -26,14 +26,12 @@ class MonsterSurvey(Cog): def __init__(self): """Initializes values for the bot to use within the voting commands.""" self.registry_path = pathlib.Path("bot", "resources", "halloween", "monstersurvey.json") - with self.registry_path.open(encoding="utf8") as data: - self.voter_registry = json.load(data) + self.voter_registry = json.loads(self.registry_path.read_text("utf8")) def json_write(self) -> None: """Write voting results to a local JSON file.""" log.info("Saved Monster Survey Results") - with self.registry_path.open("w", encoding="utf8") as data: - json.dump(self.voter_registry, data, indent=2) + self.registry_path.write_text(json.dumps(self.voter_registry, indent=2)) def cast_vote(self, id: int, monster: str) -> None: """ diff --git a/bot/exts/halloween/spookynamerate.py b/bot/exts/halloween/spookynamerate.py index 87172922..63040289 100644 --- a/bot/exts/halloween/spookynamerate.py +++ b/bot/exts/halloween/spookynamerate.py @@ -371,8 +371,7 @@ class SpookyNameRate(Cog): @staticmethod def load_json(file: Path) -> Dict[str, str]: """Loads a JSON file and returns its contents.""" - with file.open("r", encoding="utf-8") as f: - return json.load(f) + return json.loads(file.read_text("utf8")) @staticmethod def in_allowed_month() -> bool: diff --git a/bot/exts/halloween/spookyrating.py b/bot/exts/halloween/spookyrating.py index 6c79fbed..105d2164 100644 --- a/bot/exts/halloween/spookyrating.py +++ b/bot/exts/halloween/spookyrating.py @@ -3,6 +3,7 @@ import json import logging import random from pathlib import Path +from typing import Dict import discord from discord.ext import commands @@ -12,9 +13,8 @@ from bot.constants import Colours log = logging.getLogger(__name__) -with Path("bot/resources/halloween/spooky_rating.json").open(encoding="utf8") as file: - data = json.load(file) - SPOOKY_DATA = sorted((int(key), value) for key, value in data.items()) +data: Dict[str, Dict[str, str]] = json.loads(Path("bot/resources/halloween/spooky_rating.json").read_text("utf8")) +SPOOKY_DATA = sorted((int(key), value) for key, value in data.items()) class SpookyRating(commands.Cog): diff --git a/bot/exts/pride/drag_queen_name.py b/bot/exts/pride/drag_queen_name.py index d9424001..6bf43913 100644 --- a/bot/exts/pride/drag_queen_name.py +++ b/bot/exts/pride/drag_queen_name.py @@ -19,8 +19,7 @@ class DragNames(commands.Cog): @staticmethod def load_names() -> list: """Loads a list of drag queen names.""" - with Path("bot/resources/pride/drag_queen_names.json").open(encoding="utf8") as f: - return json.load(f) + return json.loads(Path("bot/resources/pride/drag_queen_names.json").read_text("utf8")) @commands.command(name="dragname", aliases=["dragqueenname", "queenme"]) async def dragname(self, ctx: commands.Context) -> None: diff --git a/bot/exts/pride/pride_anthem.py b/bot/exts/pride/pride_anthem.py index a7f8d7ef..21b7e468 100644 --- a/bot/exts/pride/pride_anthem.py +++ b/bot/exts/pride/pride_anthem.py @@ -36,9 +36,7 @@ class PrideAnthem(commands.Cog): @staticmethod def load_vids() -> list: """Loads a list of videos from the resources folder as dictionaries.""" - with open(Path("bot/resources/pride/anthems.json"), "r", encoding="utf8") as f: - anthems = json.load(f) - return anthems + return json.loads(Path("bot/resources/pride/anthems.json").read_text("utf8")) @commands.command(name="prideanthem", aliases=["anthem", "pridesong"]) async def prideanthem(self, ctx: commands.Context, genre: str = None) -> None: diff --git a/bot/exts/pride/pride_facts.py b/bot/exts/pride/pride_facts.py index 6d06cf64..47e69a03 100644 --- a/bot/exts/pride/pride_facts.py +++ b/bot/exts/pride/pride_facts.py @@ -28,8 +28,7 @@ class PrideFacts(commands.Cog): @staticmethod def load_facts() -> dict: """Loads a dictionary of years mapping to lists of facts.""" - with open(Path("bot/resources/pride/facts.json"), "r", encoding="utf8") as f: - return json.load(f) + return json.loads(Path("bot/resources/pride/facts.json").read_text("utf8")) @seasonal_task(Month.JUNE) async def send_pride_fact_daily(self) -> None: diff --git a/bot/exts/valentines/be_my_valentine.py b/bot/exts/valentines/be_my_valentine.py index d1eea388..8b522a72 100644 --- a/bot/exts/valentines/be_my_valentine.py +++ b/bot/exts/valentines/be_my_valentine.py @@ -1,6 +1,6 @@ import logging import random -from json import load +from json import loads from pathlib import Path from typing import Tuple @@ -28,9 +28,7 @@ class BeMyValentine(commands.Cog): def load_json() -> dict: """Load Valentines messages from the static resources.""" p = Path("bot/resources/valentines/bemyvalentine_valentines.json") - with p.open(encoding="utf8") as json_data: - valentines = load(json_data) - return valentines + return loads(p.read_text("utf8")) @in_month(Month.FEBRUARY) @commands.group(name="lovefest") diff --git a/bot/exts/valentines/lovecalculator.py b/bot/exts/valentines/lovecalculator.py index 8a4c71eb..b10b7bca 100644 --- a/bot/exts/valentines/lovecalculator.py +++ b/bot/exts/valentines/lovecalculator.py @@ -15,9 +15,8 @@ from bot.bot import Bot log = logging.getLogger(__name__) -with Path("bot/resources/valentines/love_matches.json").open(encoding="utf8") as file: - LOVE_DATA = json.load(file) - LOVE_DATA = sorted((int(key), value) for key, value in LOVE_DATA.items()) +LOVE_DATA = json.loads(Path("bot/resources/valentines/love_matches.json").read_text("utf8")) +LOVE_DATA = sorted((int(key), value) for key, value in LOVE_DATA.items()) class LoveCalculator(Cog): diff --git a/bot/exts/valentines/myvalenstate.py b/bot/exts/valentines/myvalenstate.py index 1c67984b..d2409dcc 100644 --- a/bot/exts/valentines/myvalenstate.py +++ b/bot/exts/valentines/myvalenstate.py @@ -12,8 +12,7 @@ from bot.constants import Colours log = logging.getLogger(__name__) -with open(Path("bot/resources/valentines/valenstates.json"), "r", encoding="utf8") as file: - STATES = json.load(file) +STATES = json.loads(Path("bot/resources/valentines/valenstates.json").read_text("utf8")) class MyValenstate(commands.Cog): diff --git a/bot/exts/valentines/pickuplines.py b/bot/exts/valentines/pickuplines.py index 909169e6..00741a72 100644 --- a/bot/exts/valentines/pickuplines.py +++ b/bot/exts/valentines/pickuplines.py @@ -1,6 +1,6 @@ import logging import random -from json import load +from json import loads from pathlib import Path import discord @@ -11,8 +11,7 @@ from bot.constants import Colours log = logging.getLogger(__name__) -with open(Path("bot/resources/valentines/pickup_lines.json"), "r", encoding="utf8") as f: - pickup_lines = load(f) +PICKUP_LINES = loads(Path("bot/resources/valentines/pickup_lines.json").read_text("utf8")) class PickupLine(commands.Cog): @@ -25,14 +24,14 @@ class PickupLine(commands.Cog): Note that most of them are very cheesy. """ - random_line = random.choice(pickup_lines["lines"]) + random_line = random.choice(PICKUP_LINES["lines"]) embed = discord.Embed( title=":cheese: Your pickup line :cheese:", description=random_line["line"], color=Colours.pink ) embed.set_thumbnail( - url=random_line.get("image", pickup_lines["placeholder"]) + url=random_line.get("image", PICKUP_LINES["placeholder"]) ) await ctx.send(embed=embed) diff --git a/bot/exts/valentines/savethedate.py b/bot/exts/valentines/savethedate.py index cc16f5c9..ffe559d6 100644 --- a/bot/exts/valentines/savethedate.py +++ b/bot/exts/valentines/savethedate.py @@ -1,6 +1,6 @@ import logging import random -from json import load +from json import loads from pathlib import Path import discord @@ -13,8 +13,7 @@ log = logging.getLogger(__name__) HEART_EMOJIS = [":heart:", ":gift_heart:", ":revolving_hearts:", ":sparkling_heart:", ":two_hearts:"] -with open(Path("bot/resources/valentines/date_ideas.json"), "r", encoding="utf8") as f: - VALENTINES_DATES = load(f) +VALENTINES_DATES = loads(Path("bot/resources/valentines/date_ideas.json").read_text("utf8")) class SaveTheDate(commands.Cog): diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index a444a355..45d1edd5 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -29,14 +29,14 @@ class ValentineZodiac(commands.Cog): """Load zodiac compatibility from static JSON resource.""" explanation_file = Path("bot/resources/valentines/zodiac_explanation.json") compatibility_file = Path("bot/resources/valentines/zodiac_compatibility.json") - with explanation_file.open(encoding="utf8") as json_data: - zodiac_fact = json.load(json_data) - for zodiac_data in zodiac_fact.values(): - zodiac_data["start_at"] = datetime.fromisoformat(zodiac_data["start_at"]) - zodiac_data["end_at"] = datetime.fromisoformat(zodiac_data["end_at"]) - - with compatibility_file.open(encoding="utf8") as json_data: - zodiacs = json.load(json_data) + + zodiac_fact = json.loads(explanation_file.read_text("utf8")) + + for zodiac_data in zodiac_fact.values(): + zodiac_data["start_at"] = datetime.fromisoformat(zodiac_data["start_at"]) + zodiac_data["end_at"] = datetime.fromisoformat(zodiac_data["end_at"]) + + zodiacs = json.loads(compatibility_file.read_text("utf8")) return zodiacs, zodiac_fact diff --git a/bot/exts/valentines/whoisvalentine.py b/bot/exts/valentines/whoisvalentine.py index 3f23201f..211b1f27 100644 --- a/bot/exts/valentines/whoisvalentine.py +++ b/bot/exts/valentines/whoisvalentine.py @@ -11,8 +11,7 @@ from bot.constants import Colours log = logging.getLogger(__name__) -with open(Path("bot/resources/valentines/valentine_facts.json"), "r", encoding="utf8") as file: - FACTS = json.load(file) +FACTS = json.loads(Path("bot/resources/valentines/valentine_facts.json").read_text("utf8")) class ValentineFacts(commands.Cog): -- cgit v1.2.3 From 42002ea00706732f5a67c9bbad194c104b046f25 Mon Sep 17 00:00:00 2001 From: ToxicKidz Date: Thu, 13 May 2021 17:54:49 -0400 Subject: chore: Break up the HelpSession.build_pages method --- bot/exts/evergreen/help.py | 172 +++++++++++++++++++++++---------------------- 1 file changed, 87 insertions(+), 85 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/help.py b/bot/exts/evergreen/help.py index 7e3fdad4..3c9ba4d2 100644 --- a/bot/exts/evergreen/help.py +++ b/bot/exts/evergreen/help.py @@ -287,22 +287,9 @@ class HelpSession: # Use LinePaginator to restrict embed line height paginator = LinePaginator(prefix="", suffix="", max_lines=self._max_lines) - prefix = constants.Client.prefix - # show signature if query is a command if isinstance(self.query, commands.Command): - signature = self._get_command_params(self.query) - parent = self.query.full_parent_name + " " if self.query.parent else "" - paginator.add_line(f"**```{prefix}{parent}{signature}```**") - - aliases = [f"`{alias}`" if not parent else f"`{parent} {alias}`" for alias in self.query.aliases] - aliases += [f"`{alias}`" for alias in getattr(self.query, "root_aliases", ())] - aliases = ", ".join(sorted(aliases)) - if aliases: - paginator.add_line(f"**Can also use:** {aliases}\n") - - if not await self.query.can_run(self._ctx): - paginator.add_line("***You cannot run this command.***\n") + await self._add_command_signature(paginator) if isinstance(self.query, Cog): paginator.add_line(f"**{self.query.name}**") @@ -312,97 +299,112 @@ class HelpSession: # list all children commands of the queried object if isinstance(self.query, (commands.GroupMixin, Cog)): + await self._list_child_commands(paginator) - # remove hidden commands if session is not wanting hiddens - if not self._show_hidden: - filtered = [c for c in self.query.commands if not c.hidden] - else: - filtered = self.query.commands - - # if after filter there are no commands, finish up - if not filtered: - self._pages = paginator.pages - return - - if isinstance(self.query, Cog): - grouped = (("**Commands:**", self.query.commands),) - - elif isinstance(self.query, commands.Command): - grouped = (("**Subcommands:**", self.query.commands),) - - # don't show prefix for subcommands - prefix = "" + self._pages = paginator.pages - # otherwise sort and organise all commands into categories - else: - cat_sort = sorted(filtered, key=self._category_key) - grouped = itertools.groupby(cat_sort, key=self._category_key) + async def _add_command_signature(self, paginator: LinePaginator) -> None: + prefix = constants.Client.prefix - for category, cmds in grouped: - cmds = sorted(cmds, key=lambda c: c.name) + signature = self._get_command_params(self.query) + parent = self.query.full_parent_name + " " if self.query.parent else "" + paginator.add_line(f"**```{prefix}{parent}{signature}```**") + aliases = [f"`{alias}`" if not parent else f"`{parent} {alias}`" for alias in self.query.aliases] + aliases += [f"`{alias}`" for alias in getattr(self.query, "root_aliases", ())] + aliases = ", ".join(sorted(aliases)) + if aliases: + paginator.add_line(f"**Can also use:** {aliases}\n") + if not await self.query.can_run(self._ctx): + paginator.add_line("***You cannot run this command.***\n") + + async def _list_child_commands(self, paginator: LinePaginator) -> None: + # remove hidden commands if session is not wanting hiddens + if not self._show_hidden: + filtered = [c for c in self.query.commands if not c.hidden] + else: + filtered = self.query.commands - if len(cmds) == 0: - continue + # if after filter there are no commands, finish up + if not filtered: + self._pages = paginator.pages + return - cat_cmds = [] + if isinstance(self.query, Cog): + grouped = (("**Commands:**", self.query.commands),) - for command in cmds: + elif isinstance(self.query, commands.Command): + grouped = (("**Subcommands:**", self.query.commands),) - # skip if hidden and hide if session is set to - if command.hidden and not self._show_hidden: - continue + # otherwise sort and organise all commands into categories + else: + cat_sort = sorted(filtered, key=self._category_key) + grouped = itertools.groupby(cat_sort, key=self._category_key) - # see if the user can run the command - strikeout = "" + for category, cmds in grouped: + await self._format_command_category(paginator, category, list(cmds)) - # Patch to make the !help command work outside of #bot-commands again - # This probably needs a proper rewrite, but this will make it work in - # the mean time. - try: - can_run = await command.can_run(self._ctx) - except CheckFailure: - can_run = False + async def _format_command_category(self, paginator: LinePaginator, category: str, cmds: List[Command]) -> None: + cmds = sorted(cmds, key=lambda c: c.name) + cat_cmds = [] + for command in cmds: + cat_cmds += await self._format_command(command) - if not can_run: - # skip if we don't show commands they can't run - if self._only_can_run: - continue - strikeout = "~~" + # state var for if the category should be added next + print_cat = 1 + new_page = True - signature = self._get_command_params(command) - info = f"{strikeout}**`{prefix}{signature}`**{strikeout}" + for details in cat_cmds: - # handle if the command has no docstring - if command.short_doc: - cat_cmds.append(f"{info}\n*{command.short_doc}*") - else: - cat_cmds.append(f"{info}\n*No details provided.*") + # keep details together, paginating early if it won"t fit + lines_adding = len(details.split("\n")) + print_cat + if paginator._linecount + lines_adding > self._max_lines: + paginator._linecount = 0 + new_page = True + paginator.close_page() - # state var for if the category should be added next + # new page so print category title again print_cat = 1 - new_page = True - for details in cat_cmds: + if print_cat: + if new_page: + paginator.add_line("") + paginator.add_line(category) + print_cat = 0 - # keep details together, paginating early if it won't fit - lines_adding = len(details.split("\n")) + print_cat - if paginator._linecount + lines_adding > self._max_lines: - paginator._linecount = 0 - new_page = True - paginator.close_page() + paginator.add_line(details) - # new page so print category title again - print_cat = 1 + async def _format_command(self, command: Command) -> List[str]: + # skip if hidden and hide if session is set to + if command.hidden and not self._show_hidden: + return [] - if print_cat: - if new_page: - paginator.add_line("") - paginator.add_line(category) - print_cat = 0 + # Patch to make the !help command work outside of #bot-commands again + # This probably needs a proper rewrite, but this will make it work in + # the mean time. + try: + can_run = await command.can_run(self._ctx) + except CheckFailure: + can_run = False + + # see if the user can run the command + strikeout = "" + if not can_run: + # skip if we don't show commands they can't run + if self._only_can_run: + return [] + strikeout = "~~" - paginator.add_line(details) + if isinstance(self.query, commands.Command): + prefix = "" + else: + prefix = constants.Client.prefix - self._pages = paginator.pages + signature = self._get_command_params(command) + info = f"{strikeout}**`{prefix}{signature}`**{strikeout}" + + # handle if the command has no docstring + short_doc = command.short_doc or "No details provided" + return [f"{info}\n*{short_doc}*"] def embed_page(self, page_number: int = 0) -> Embed: """Returns an Embed with the requested page formatted within.""" -- cgit v1.2.3 From 92c33d625068b9b39564dbf4fb9f6c1102711955 Mon Sep 17 00:00:00 2001 From: ToxicKidz Date: Fri, 14 May 2021 12:34:30 -0400 Subject: chore: Refactor more code to follow our style guide --- bot/exts/evergreen/avatar_modification/_effects.py | 6 +- bot/exts/evergreen/connect_four.py | 70 +++++++++++----------- bot/exts/evergreen/game.py | 6 +- bot/exts/evergreen/issues.py | 16 ++--- bot/exts/evergreen/minesweeper.py | 12 ++-- bot/exts/evergreen/movie.py | 6 +- bot/exts/evergreen/snakes/_snakes_cog.py | 10 +++- bot/exts/evergreen/snakes/_utils.py | 16 ++--- bot/exts/evergreen/wolfram.py | 16 +++-- bot/exts/halloween/candy_collection.py | 8 ++- bot/exts/halloween/monstersurvey.py | 23 +++---- bot/exts/valentines/movie_generator.py | 6 +- bot/utils/__init__.py | 11 +++- bot/utils/checks.py | 30 ++++++---- 14 files changed, 136 insertions(+), 100 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/avatar_modification/_effects.py b/bot/exts/evergreen/avatar_modification/_effects.py index d2370b4b..9339ecc4 100644 --- a/bot/exts/evergreen/avatar_modification/_effects.py +++ b/bot/exts/evergreen/avatar_modification/_effects.py @@ -251,7 +251,7 @@ class PfpEffects: total_width = multiplier * single_wdith total_height = multiplier * single_height - new_image = Image.new('RGBA', (total_width, total_height), (250, 250, 250)) + new_image = Image.new("RGBA", (total_width, total_height), (250, 250, 250)) width_multiplier = 0 height = 0 @@ -275,13 +275,13 @@ class PfpEffects: def mosaic_effect(img_bytes: bytes, squares: int, file_name: str) -> discord.File: """Seperate function run from an executor which turns an image into a mosaic.""" avatar = Image.open(BytesIO(img_bytes)) - avatar = avatar.convert('RGBA').resize((1024, 1024)) + avatar = avatar.convert("RGBA").resize((1024, 1024)) img_squares = PfpEffects.split_image(avatar, squares) new_img = PfpEffects.join_images(img_squares) bufferedio = BytesIO() - new_img.save(bufferedio, format='PNG') + new_img.save(bufferedio, format="PNG") bufferedio.seek(0) return discord.File(bufferedio, filename=file_name) diff --git a/bot/exts/evergreen/connect_four.py b/bot/exts/evergreen/connect_four.py index dc365a70..7a39d442 100644 --- a/bot/exts/evergreen/connect_four.py +++ b/bot/exts/evergreen/connect_four.py @@ -22,13 +22,13 @@ class Game: """A Connect 4 Game.""" def __init__( - self, - bot: Bot, - channel: discord.TextChannel, - player1: discord.Member, - player2: typing.Optional[discord.Member], - tokens: typing.List[str], - size: int = 7 + self, + bot: Bot, + channel: discord.TextChannel, + player1: discord.Member, + player2: typing.Optional[discord.Member], + tokens: typing.List[str], + size: int = 7 ) -> None: self.bot = bot @@ -286,20 +286,20 @@ class ConnectFour(commands.Cog): return True def get_player( - self, - ctx: commands.Context, - announcement: discord.Message, - reaction: discord.Reaction, - user: discord.Member + self, + ctx: commands.Context, + announcement: discord.Message, + reaction: discord.Reaction, + user: discord.Member ) -> bool: """Predicate checking the criteria for the announcement message.""" if self.already_playing(ctx.author): # If they've joined a game since requesting a player 2 return True # Is dealt with later on if ( - user.id not in (ctx.me.id, ctx.author.id) - and str(reaction.emoji) == Emojis.hand_raised - and reaction.message.id == announcement.id + user.id not in (ctx.me.id, ctx.author.id) + and str(reaction.emoji) == Emojis.hand_raised + and reaction.message.id == announcement.id ): if self.already_playing(user): self.bot.loop.create_task(ctx.send(f"{user.mention} You're already playing a game!")) @@ -316,9 +316,9 @@ class ConnectFour(commands.Cog): return True if ( - user.id == ctx.author.id - and str(reaction.emoji) == CROSS_EMOJI - and reaction.message.id == announcement.id + user.id == ctx.author.id + and str(reaction.emoji) == CROSS_EMOJI + and reaction.message.id == announcement.id ): return True return False @@ -329,7 +329,7 @@ class ConnectFour(commands.Cog): @staticmethod def check_emojis( - e1: EMOJI_CHECK, e2: EMOJI_CHECK + e1: EMOJI_CHECK, e2: EMOJI_CHECK ) -> typing.Tuple[bool, typing.Optional[str]]: """Validate the emojis, the user put.""" if isinstance(e1, str) and emojis.count(e1) != 1: @@ -339,12 +339,12 @@ class ConnectFour(commands.Cog): return True, None async def _play_game( - self, - ctx: commands.Context, - user: typing.Optional[discord.Member], - board_size: int, - emoji1: str, - emoji2: str + self, + ctx: commands.Context, + user: typing.Optional[discord.Member], + board_size: int, + emoji1: str, + emoji2: str ) -> None: """Helper for playing a game of connect four.""" self.tokens = [":white_circle:", str(emoji1), str(emoji2)] @@ -369,11 +369,11 @@ class ConnectFour(commands.Cog): case_insensitive=True ) async def connect_four( - self, - ctx: commands.Context, - board_size: int = 7, - emoji1: EMOJI_CHECK = "\U0001f535", - emoji2: EMOJI_CHECK = "\U0001f534" + self, + ctx: commands.Context, + board_size: int = 7, + emoji1: EMOJI_CHECK = "\U0001f535", + emoji2: EMOJI_CHECK = "\U0001f534" ) -> None: """ Play the classic game of Connect Four with someone! @@ -430,11 +430,11 @@ class ConnectFour(commands.Cog): @guild_only() @connect_four.command(aliases=["bot", "computer", "cpu"]) async def ai( - self, - ctx: commands.Context, - board_size: int = 7, - emoji1: EMOJI_CHECK = "\U0001f535", - emoji2: EMOJI_CHECK = "\U0001f534" + self, + ctx: commands.Context, + board_size: int = 7, + emoji1: EMOJI_CHECK = "\U0001f535", + emoji2: EMOJI_CHECK = "\U0001f534" ) -> None: """Play Connect Four against a computer player.""" check, emoji = self.check_emojis(emoji1, emoji2) diff --git a/bot/exts/evergreen/game.py b/bot/exts/evergreen/game.py index 6f01d81c..43f64be7 100644 --- a/bot/exts/evergreen/game.py +++ b/bot/exts/evergreen/game.py @@ -374,8 +374,10 @@ class Games(Cog): release_date = dt.utcfromtimestamp(data["first_release_date"]).date() if "first_release_date" in data else "?" # Create Age Ratings value - rating = ", ".join(f"{AgeRatingCategories(age['category']).name} {AgeRatings(age['rating']).name}" - for age in data["age_ratings"]) if "age_ratings" in data else "?" + rating = ", ".join( + f"{AgeRatingCategories(age['category']).name} {AgeRatings(age['rating']).name}" + for age in data["age_ratings"] + ) if "age_ratings" in data else "?" companies = [c["company"]["name"] for c in data["involved_companies"]] if "involved_companies" in data else "?" diff --git a/bot/exts/evergreen/issues.py b/bot/exts/evergreen/issues.py index 5bbc57c6..b67aa4a6 100644 --- a/bot/exts/evergreen/issues.py +++ b/bot/exts/evergreen/issues.py @@ -162,9 +162,9 @@ class Issues(commands.Cog): @staticmethod def format_embed( - results: t.List[t.Union[IssueState, FetchError]], - user: str, - repository: t.Optional[str] = None + results: t.List[t.Union[IssueState, FetchError]], + user: str, + repository: t.Optional[str] = None ) -> discord.Embed: """Take a list of IssueState or FetchError and format a Discord embed for them.""" description_list = [] @@ -187,11 +187,11 @@ class Issues(commands.Cog): @whitelist_override(channels=WHITELISTED_CHANNELS, categories=WHITELISTED_CATEGORIES) @commands.command(aliases=("pr",)) async def issue( - self, - ctx: commands.Context, - numbers: commands.Greedy[int], - repository: str = "sir-lancebot", - user: str = "python-discord" + self, + ctx: commands.Context, + numbers: commands.Greedy[int], + repository: str = "sir-lancebot", + user: str = "python-discord" ) -> None: """Command to retrieve issue(s) from a GitHub repository.""" # Remove duplicates diff --git a/bot/exts/evergreen/minesweeper.py b/bot/exts/evergreen/minesweeper.py index 7a31dfde..932358f9 100644 --- a/bot/exts/evergreen/minesweeper.py +++ b/bot/exts/evergreen/minesweeper.py @@ -212,12 +212,12 @@ class Minesweeper(commands.Cog): return True async def reveal_one( - self, - ctx: commands.Context, - revealed: GameBoard, - board: GameBoard, - x: int, - y: int + self, + ctx: commands.Context, + revealed: GameBoard, + board: GameBoard, + x: int, + y: int ) -> bool: """ Reveal one square. diff --git a/bot/exts/evergreen/movie.py b/bot/exts/evergreen/movie.py index fa284417..ff23df4c 100644 --- a/bot/exts/evergreen/movie.py +++ b/bot/exts/evergreen/movie.py @@ -79,8 +79,10 @@ class Movie(Cog): # Check if "results" is in result. If not, throw error. if "results" not in result: - err_msg = f"There is problem while making TMDB API request. Response Code: {result['status_code']}, " \ - f"{result['status_message']}." + err_msg = ( + f"There is problem while making TMDB API request. Response Code: {result['status_code']}, " + f"{result['status_message']}." + ) await ctx.send(err_msg) logger.warning(err_msg) diff --git a/bot/exts/evergreen/snakes/_snakes_cog.py b/bot/exts/evergreen/snakes/_snakes_cog.py index c50b23c5..07d3c363 100644 --- a/bot/exts/evergreen/snakes/_snakes_cog.py +++ b/bot/exts/evergreen/snakes/_snakes_cog.py @@ -579,9 +579,13 @@ class Snakes(Cog): antidote_embed = Embed(color=SNAKE_COLOR, title="Antidote") antidote_embed.set_author(name=ctx.author.name, icon_url=ctx.author.avatar_url) antidote_embed.set_image(url="https://media.giphy.com/media/ceeN6U57leAhi/giphy.gif") - antidote_embed.add_field(name=EMPTY_UNICODE, - value=f"Sorry you didnt make the antidote in time.\n" - f"The formula was {' '.join(antidote_answer)}") + antidote_embed.add_field( + name=EMPTY_UNICODE, + value=( + f"Sorry you didnt make the antidote in time.\n" + f"The formula was {' '.join(antidote_answer)}" + ) + ) await board_id.edit(embed=antidote_embed) log.debug("Ending pagination and removing all reactions...") diff --git a/bot/exts/evergreen/snakes/_utils.py b/bot/exts/evergreen/snakes/_utils.py index 8b39f217..0a5894b7 100644 --- a/bot/exts/evergreen/snakes/_utils.py +++ b/bot/exts/evergreen/snakes/_utils.py @@ -17,38 +17,38 @@ from bot.constants import Roles SNAKE_RESOURCES = Path("bot/resources/snakes").absolute() -h1 = r'''``` +h1 = r"""``` ---- ------ /--------\ |--------| |--------| \------/ - ----```''' -h2 = r'''``` + ----```""" +h2 = r"""``` ---- ------ /---\-/--\ |-----\--| |--------| \------/ - ----```''' -h3 = r'''``` + ----```""" +h3 = r"""``` ---- ------ /---\-/--\ |-----\--| |-----/--| \----\-/ - ----```''' -h4 = r'''``` + ----```""" +h4 = r"""``` ----- ----- \ /--| /---\ |--\ -\---| |--\--/-- / \------- / - ------```''' + ------```""" stages = [h1, h2, h3, h4] snakes = { "Baby Python": "https://i.imgur.com/SYOcmSa.png", diff --git a/bot/exts/evergreen/wolfram.py b/bot/exts/evergreen/wolfram.py index ca7d8454..d23afd6f 100644 --- a/bot/exts/evergreen/wolfram.py +++ b/bot/exts/evergreen/wolfram.py @@ -40,9 +40,11 @@ async def send_embed( """Generate & send a response embed with Wolfram as the author.""" embed = Embed(colour=colour) embed.description = message_txt - embed.set_author(name="Wolfram Alpha", - icon_url=WOLF_IMAGE, - url="https://www.wolframalpha.com/") + embed.set_author( + name="Wolfram Alpha", + icon_url=WOLF_IMAGE, + url="https://www.wolframalpha.com/" + ) if footer: embed.set_footer(text=footer) @@ -222,9 +224,11 @@ class Wolfram(Cog): return embed = Embed() - embed.set_author(name="Wolfram Alpha", - icon_url=WOLF_IMAGE, - url="https://www.wolframalpha.com/") + embed.set_author( + name="Wolfram Alpha", + icon_url=WOLF_IMAGE, + url="https://www.wolframalpha.com/" + ) embed.colour = Colours.soft_orange await ImagePaginator.paginate(pages, ctx, embed) diff --git a/bot/exts/halloween/candy_collection.py b/bot/exts/halloween/candy_collection.py index 14efa1fb..4afd5913 100644 --- a/bot/exts/halloween/candy_collection.py +++ b/bot/exts/halloween/candy_collection.py @@ -155,8 +155,12 @@ class CandyCollection(commands.Cog): ) -> None: """An alternative spooky message sent when user has no candies in the collection.""" embed = discord.Embed(color=author.color) - embed.set_author(name="Ghosts and Ghouls and Jack o' lanterns at night; " - "I tried to take your candies but you had none to begin with!") + embed.set_author( + name=( + "Ghosts and Ghouls and Jack o' lanterns at night; " + "I tried to take your candies but you had none to begin with!" + ) + ) await channel.send(embed=embed) @in_month(Month.OCTOBER) diff --git a/bot/exts/halloween/monstersurvey.py b/bot/exts/halloween/monstersurvey.py index 240a97db..96cda11e 100644 --- a/bot/exts/halloween/monstersurvey.py +++ b/bot/exts/halloween/monstersurvey.py @@ -71,7 +71,8 @@ class MonsterSurvey(Cog): default_embed.add_field( name=".monster show monster_name(optional)", value="Show a specific monster. If none is listed, it will give you an error with valid choices.", - inline=False) + inline=False + ) default_embed.add_field( name=".monster vote monster_name", value="Vote for a specific monster. You get one vote, but can change it at any time.", @@ -182,15 +183,17 @@ class MonsterSurvey(Cog): for rank, m in enumerate(top): votes = len(vr[m]["votes"]) percentage = ((votes / total_votes) * 100) if total_votes > 0 else 0 - embed.add_field(name=f"{rank+1}. {vr[m]['full_name']}", - value=( - f"{votes} votes. {percentage:.1f}% of total votes.\n" - f"Vote for this monster by typing " - f"'.monster vote {m}'\n" - f"Get more information on this monster by typing " - f"'.monster show {m}'" - ), - inline=False) + embed.add_field( + name=f"{rank+1}. {vr[m]['full_name']}", + value=( + f"{votes} votes. {percentage:.1f}% of total votes.\n" + f"Vote for this monster by typing " + f"'.monster vote {m}'\n" + f"Get more information on this monster by typing " + f"'.monster show {m}'" + ), + inline=False + ) embed.set_footer(text="You can also vote by their rank number. '.monster vote {number}' ") diff --git a/bot/exts/valentines/movie_generator.py b/bot/exts/valentines/movie_generator.py index 4508c3b2..0fc5edb4 100644 --- a/bot/exts/valentines/movie_generator.py +++ b/bot/exts/valentines/movie_generator.py @@ -54,8 +54,10 @@ class RomanceMovieFinder(commands.Cog): embed.set_thumbnail(url="https://i.imgur.com/LtFtC8H.png") await ctx.send(embed=embed) except KeyError: - warning_message = "A KeyError was raised while fetching information on the movie. The API service" \ - " could be unavailable or the API key could be set incorrectly." + warning_message = ( + "A KeyError was raised while fetching information on the movie. The API service" + " could be unavailable or the API key could be set incorrectly." + ) embed = discord.Embed(title=warning_message) log.warning(warning_message) await ctx.send(embed=embed) diff --git a/bot/utils/__init__.py b/bot/utils/__init__.py index 09a4dfc3..a1985019 100644 --- a/bot/utils/__init__.py +++ b/bot/utils/__init__.py @@ -3,7 +3,7 @@ import contextlib import re import string from datetime import datetime -from typing import Iterable, List +from typing import Iterable, List, Optional import discord from discord.ext.commands import BadArgument, Context @@ -31,8 +31,13 @@ def resolve_current_month() -> Month: async def disambiguate( - ctx: Context, entries: List[str], *, timeout: float = 30, - entries_per_page: int = 20, empty: bool = False, embed: discord.Embed = None + ctx: Context, + entries: List[str], + *, + timeout: float = 30, + entries_per_page: int = 20, + empty: bool = False, + embed: Optional[discord.Embed] = None ) -> str: """ Has the user choose between multiple entries in case one could not be chosen automatically. diff --git a/bot/utils/checks.py b/bot/utils/checks.py index 3783dd38..c06b6870 100644 --- a/bot/utils/checks.py +++ b/bot/utils/checks.py @@ -92,8 +92,10 @@ def in_whitelist_check( def with_role_check(ctx: Context, *role_ids: int) -> bool: """Returns True if the user has any one of the roles in role_ids.""" if not ctx.guild: # Return False in a DM - log.trace(f"{ctx.author} tried to use the '{ctx.command.name}'command from a DM. " - "This command is restricted by the with_role decorator. Rejecting request.") + log.trace( + f"{ctx.author} tried to use the '{ctx.command.name}'command from a DM. " + "This command is restricted by the with_role decorator. Rejecting request." + ) return False for role in ctx.author.roles: @@ -101,22 +103,28 @@ def with_role_check(ctx: Context, *role_ids: int) -> bool: log.trace(f"{ctx.author} has the '{role.name}' role, and passes the check.") return True - log.trace(f"{ctx.author} does not have the required role to use " - f"the '{ctx.command.name}' command, so the request is rejected.") + log.trace( + f"{ctx.author} does not have the required role to use " + f"the '{ctx.command.name}' command, so the request is rejected." + ) return False def without_role_check(ctx: Context, *role_ids: int) -> bool: """Returns True if the user does not have any of the roles in role_ids.""" if not ctx.guild: # Return False in a DM - log.trace(f"{ctx.author} tried to use the '{ctx.command.name}' command from a DM. " - "This command is restricted by the without_role decorator. Rejecting request.") + log.trace( + f"{ctx.author} tried to use the '{ctx.command.name}' command from a DM. " + "This command is restricted by the without_role decorator. Rejecting request." + ) return False author_roles = [role.id for role in ctx.author.roles] check = all(role not in author_roles for role in role_ids) - log.trace(f"{ctx.author} tried to call the '{ctx.command.name}' command. " - f"The result of the without_role check was {check}.") + log.trace( + f"{ctx.author} tried to call the '{ctx.command.name}' command. " + f"The result of the without_role check was {check}." + ) return check @@ -154,8 +162,10 @@ def cooldown_with_role_bypass(rate: int, per: float, type: BucketType = BucketTy # # If the `before_invoke` detail is ever a problem then I can quickly just swap over. if not isinstance(command, Command): - raise TypeError("Decorator `cooldown_with_role_bypass` must be applied after the command decorator. " - "This means it has to be above the command decorator in the code.") + raise TypeError( + "Decorator `cooldown_with_role_bypass` must be applied after the command decorator. " + "This means it has to be above the command decorator in the code." + ) command._before_invoke = predicate -- cgit v1.2.3 From 95e3bcbee2a68b1379b35c74872df70977cae592 Mon Sep 17 00:00:00 2001 From: ToxicKidz Date: Sat, 15 May 2021 11:43:11 -0400 Subject: chore: Remove redundant f-strings --- bot/exts/christmas/advent_of_code/_cog.py | 2 +- bot/exts/christmas/hanukkah_embed.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'bot') diff --git a/bot/exts/christmas/advent_of_code/_cog.py b/bot/exts/christmas/advent_of_code/_cog.py index ce6a4cb6..ead84544 100644 --- a/bot/exts/christmas/advent_of_code/_cog.py +++ b/bot/exts/christmas/advent_of_code/_cog.py @@ -115,7 +115,7 @@ class AdventOfCode(commands.Cog): delta_str = f"{delta.days} days" await ctx.send( - f"The Advent of Code event is not currently running. " + "The Advent of Code event is not currently running. " f"The next event will start in {delta_str}." ) return diff --git a/bot/exts/christmas/hanukkah_embed.py b/bot/exts/christmas/hanukkah_embed.py index af5cfccf..77ef4684 100644 --- a/bot/exts/christmas/hanukkah_embed.py +++ b/bot/exts/christmas/hanukkah_embed.py @@ -67,14 +67,14 @@ class HanukkahEmbed(commands.Cog): hanukkah_start_hour = 18 if hours < hanukkah_start_hour: embed.description = ( - f"Hanukkah hasnt started yet, " + "Hanukkah hasnt started yet, " f"it will start in about {hanukkah_start_hour - hours} hour/s." ) await ctx.send(embed=embed) return elif hours > hanukkah_start_hour: embed.description = ( - f"It is the starting day of Hanukkah ! " + "It is the starting day of Hanukkah ! " f"Its been {hours - hanukkah_start_hour} hours hanukkah started !" ) await ctx.send(embed=embed) -- cgit v1.2.3 From c09ab5c5b8a2d682c320229271e75f21d7b33256 Mon Sep 17 00:00:00 2001 From: ToxicKidz Date: Sat, 15 May 2021 11:57:14 -0400 Subject: chore: Reformat bot/utils/__init__.py --- bot/utils/__init__.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) (limited to 'bot') diff --git a/bot/utils/__init__.py b/bot/utils/__init__.py index a1985019..bef12d25 100644 --- a/bot/utils/__init__.py +++ b/bot/utils/__init__.py @@ -56,17 +56,21 @@ async def disambiguate( choices = (f"{index}: {entry}" for index, entry in enumerate(entries, start=1)) def check(message: discord.Message) -> bool: - return (message.content.isdecimal() - and message.author == ctx.author - and message.channel == ctx.channel) + return ( + message.content.isdecimal() + and message.author == ctx.author + and message.channel == ctx.channel + ) try: if embed is None: embed = discord.Embed() coro1 = ctx.bot.wait_for("message", check=check, timeout=timeout) - coro2 = LinePaginator.paginate(choices, ctx, embed=embed, max_lines=entries_per_page, - empty=empty, max_size=6000, timeout=9000) + coro2 = LinePaginator.paginate( + choices, ctx, embed=embed, max_lines=entries_per_page, + empty=empty, max_size=6000, timeout=9000 + ) # wait_for timeout will go to except instead of the wait_for thing as I expected futures = [asyncio.ensure_future(coro1), asyncio.ensure_future(coro2)] @@ -102,7 +106,7 @@ async def disambiguate( def replace_many( - sentence: str, replacements: dict, *, ignore_case: bool = False, match_case: bool = False + sentence: str, replacements: dict, *, ignore_case: bool = False, match_case: bool = False ) -> str: """ Replaces multiple substrings in a string given a mapping of strings. -- cgit v1.2.3 From e1361d03fac450365d4bdc8d10d8bcf2c99b479f Mon Sep 17 00:00:00 2001 From: ToxicKidz Date: Sat, 15 May 2021 12:32:10 -0400 Subject: chore: Update myvalenstate.py Removes a redundant f string, and when is one leftover match, get the first item of the match list instead of having [''] --- bot/exts/valentines/myvalenstate.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'bot') diff --git a/bot/exts/valentines/myvalenstate.py b/bot/exts/valentines/myvalenstate.py index d2409dcc..c699de94 100644 --- a/bot/exts/valentines/myvalenstate.py +++ b/bot/exts/valentines/myvalenstate.py @@ -61,8 +61,7 @@ class MyValenstate(commands.Cog): embed_text = f"You have {len(matches)} more matches, these being {leftovers}." elif len(matches) == 1: embed_title = "But there's another one!" - leftovers = str(matches) - embed_text = f"You have another match, this being {leftovers}." + embed_text = f"You have another match, this being {matches[0]}." else: embed_title = "You have a true match!" embed_text = "This state is your true Valenstate! There are no states that would suit" \ @@ -70,7 +69,7 @@ class MyValenstate(commands.Cog): embed = discord.Embed( title=f"Your Valenstate is {valenstate} \u2764", - description=f"{STATES[valenstate]['text']}", + description=STATES[valenstate]['text'], colour=Colours.pink ) embed.add_field(name=embed_title, value=embed_text) -- cgit v1.2.3 From 55d472a97efea392b4eb2b198ffa6f5f5026072d Mon Sep 17 00:00:00 2001 From: ToxicKidz Date: Sat, 15 May 2021 12:53:00 -0400 Subject: chore: Make all aliases in commands tuples --- bot/exts/christmas/hanukkah_embed.py | 2 +- bot/exts/easter/earth_photos.py | 2 +- bot/exts/easter/easter_riddle.py | 2 +- bot/exts/easter/egg_decorating.py | 2 +- bot/exts/easter/egg_facts.py | 2 +- bot/exts/easter/egghead_quiz.py | 2 +- bot/exts/evergreen/battleship.py | 2 +- bot/exts/evergreen/catify.py | 2 +- bot/exts/evergreen/connect_four.py | 4 ++-- bot/exts/evergreen/game.py | 12 ++++++------ bot/exts/evergreen/movie.py | 4 ++-- bot/exts/evergreen/pythonfacts.py | 2 +- bot/exts/evergreen/recommend_game.py | 2 +- bot/exts/evergreen/space.py | 2 +- bot/exts/evergreen/timed.py | 2 +- bot/exts/evergreen/trivia_quiz.py | 2 +- bot/exts/evergreen/wikipedia.py | 2 +- bot/exts/evergreen/wonder_twins.py | 2 +- bot/exts/halloween/spookynamerate.py | 4 ++-- bot/exts/pride/drag_queen_name.py | 2 +- bot/exts/pride/pride_anthem.py | 2 +- bot/exts/pride/pride_facts.py | 2 +- bot/exts/valentines/valentine_zodiac.py | 2 +- 23 files changed, 31 insertions(+), 31 deletions(-) (limited to 'bot') diff --git a/bot/exts/christmas/hanukkah_embed.py b/bot/exts/christmas/hanukkah_embed.py index 77ef4684..7a06b558 100644 --- a/bot/exts/christmas/hanukkah_embed.py +++ b/bot/exts/christmas/hanukkah_embed.py @@ -38,7 +38,7 @@ class HanukkahEmbed(commands.Cog): return hanukkah_dates @in_month(Month.DECEMBER) - @commands.command(name="hanukkah", aliases=["chanukah"]) + @commands.command(name="hanukkah", aliases=("chanukah",)) async def hanukkah_festival(self, ctx: commands.Context) -> None: """Tells you about the Hanukkah Festivaltime of festival, festival day, etc).""" hanukkah_dates = await self.get_hanukkah_dates() diff --git a/bot/exts/easter/earth_photos.py b/bot/exts/easter/earth_photos.py index 0e82e99a..9df4f9ca 100644 --- a/bot/exts/easter/earth_photos.py +++ b/bot/exts/easter/earth_photos.py @@ -16,7 +16,7 @@ class EarthPhotos(commands.Cog): def __init__(self, bot: Bot): self.bot = bot - @commands.command(aliases=["earth"]) + @commands.command(aliases=("earth",)) async def earth_photos(self, ctx: commands.Context) -> None: """Returns a random photo of earth, sourced from Unsplash.""" async with ctx.typing(): diff --git a/bot/exts/easter/easter_riddle.py b/bot/exts/easter/easter_riddle.py index 01b956f1..a87f93d1 100644 --- a/bot/exts/easter/easter_riddle.py +++ b/bot/exts/easter/easter_riddle.py @@ -26,7 +26,7 @@ class EasterRiddle(commands.Cog): self.correct = "" self.current_channel = None - @commands.command(aliases=["riddlemethis", "riddleme"]) + @commands.command(aliases=("riddlemethis", "riddleme")) async def riddle(self, ctx: commands.Context) -> None: """ Gives a random riddle, then provides 2 hints at certain intervals before revealing the answer. diff --git a/bot/exts/easter/egg_decorating.py b/bot/exts/easter/egg_decorating.py index 7448f702..d4b27b20 100644 --- a/bot/exts/easter/egg_decorating.py +++ b/bot/exts/easter/egg_decorating.py @@ -41,7 +41,7 @@ class EggDecorating(commands.Cog): return int(XKCD_COLOURS[colour], 16) return None - @commands.command(aliases=["decorateegg"]) + @commands.command(aliases=("decorateegg",)) async def eggdecorate( self, ctx: commands.Context, *colours: Union[discord.Colour, str] ) -> Union[Image.Image, discord.Message]: diff --git a/bot/exts/easter/egg_facts.py b/bot/exts/easter/egg_facts.py index c1c43f80..d60032f0 100644 --- a/bot/exts/easter/egg_facts.py +++ b/bot/exts/easter/egg_facts.py @@ -40,7 +40,7 @@ class EasterFacts(commands.Cog): channel = self.bot.get_channel(Channels.community_bot_commands) await channel.send(embed=self.make_embed()) - @commands.command(name="eggfact", aliases=["fact"]) + @commands.command(name="eggfact", aliases=("fact",)) async def easter_facts(self, ctx: commands.Context) -> None: """Get easter egg facts.""" embed = self.make_embed() diff --git a/bot/exts/easter/egghead_quiz.py b/bot/exts/easter/egghead_quiz.py index 4b67310f..7c4960cd 100644 --- a/bot/exts/easter/egghead_quiz.py +++ b/bot/exts/easter/egghead_quiz.py @@ -34,7 +34,7 @@ class EggheadQuiz(commands.Cog): def __init__(self) -> None: self.quiz_messages = {} - @commands.command(aliases=["eggheadquiz", "easterquiz"]) + @commands.command(aliases=("eggheadquiz", "easterquiz")) async def eggquiz(self, ctx: commands.Context) -> None: """ Gives a random quiz question, waits 30 seconds and then outputs the answer. diff --git a/bot/exts/evergreen/battleship.py b/bot/exts/evergreen/battleship.py index d4584ae8..27c50bdb 100644 --- a/bot/exts/evergreen/battleship.py +++ b/bot/exts/evergreen/battleship.py @@ -434,7 +434,7 @@ class Battleship(commands.Cog): self.games.remove(game) raise - @battleship.command(name="ships", aliases=["boats"]) + @battleship.command(name="ships", aliases=("boats",)) async def battleship_ships(self, ctx: commands.Context) -> None: """Lists the ships that are found on the battleship grid.""" embed = discord.Embed(colour=Colours.blue) diff --git a/bot/exts/evergreen/catify.py b/bot/exts/evergreen/catify.py index 3182f69e..32dfae09 100644 --- a/bot/exts/evergreen/catify.py +++ b/bot/exts/evergreen/catify.py @@ -13,7 +13,7 @@ from bot.utils import helpers class Catify(commands.Cog): """Cog for the catify command.""" - @commands.command(aliases=["ᓚᘏᗢify", "ᓚᘏᗢ"]) + @commands.command(aliases=("ᓚᘏᗢify", "ᓚᘏᗢ")) @commands.cooldown(1, 5, commands.BucketType.user) async def catify(self, ctx: commands.Context, *, text: Optional[str]) -> None: """ diff --git a/bot/exts/evergreen/connect_four.py b/bot/exts/evergreen/connect_four.py index 7a39d442..5c82ffee 100644 --- a/bot/exts/evergreen/connect_four.py +++ b/bot/exts/evergreen/connect_four.py @@ -365,7 +365,7 @@ class ConnectFour(commands.Cog): @guild_only() @commands.group( invoke_without_command=True, - aliases=["4inarow", "connect4", "connectfour", "c4"], + aliases=("4inarow", "connect4", "connectfour", "c4"), case_insensitive=True ) async def connect_four( @@ -428,7 +428,7 @@ class ConnectFour(commands.Cog): await self._play_game(ctx, user, board_size, str(emoji1), str(emoji2)) @guild_only() - @connect_four.command(aliases=["bot", "computer", "cpu"]) + @connect_four.command(aliases=("bot", "computer", "cpu")) async def ai( self, ctx: commands.Context, diff --git a/bot/exts/evergreen/game.py b/bot/exts/evergreen/game.py index 43f64be7..32fe9263 100644 --- a/bot/exts/evergreen/game.py +++ b/bot/exts/evergreen/game.py @@ -224,7 +224,7 @@ class Games(Cog): else: self.genres[genre_name] = genre - @group(name="games", aliases=["game"], invoke_without_command=True) + @group(name="games", aliases=("game",), invoke_without_command=True) async def games(self, ctx: Context, amount: Optional[int] = 5, *, genre: Optional[str]) -> None: """ Get random game(s) by genre from IGDB. Use .games genres command to get all available genres. @@ -277,7 +277,7 @@ class Games(Cog): await ImagePaginator.paginate(pages, ctx, Embed(title=f"Random {genre.title()} Games")) - @games.command(name="top", aliases=["t"]) + @games.command(name="top", aliases=("t",)) async def top(self, ctx: Context, amount: int = 10) -> None: """ Get current Top games in IGDB. @@ -294,19 +294,19 @@ class Games(Cog): pages = [await self.create_page(game) for game in games] await ImagePaginator.paginate(pages, ctx, Embed(title=f"Top {amount} Games")) - @games.command(name="genres", aliases=["genre", "g"]) + @games.command(name="genres", aliases=("genre", "g")) async def genres(self, ctx: Context) -> None: """Get all available genres.""" await ctx.send(f"Currently available genres: {', '.join(f'`{genre}`' for genre in self.genres)}") - @games.command(name="search", aliases=["s"]) + @games.command(name="search", aliases=("s",)) async def search(self, ctx: Context, *, search_term: str) -> None: """Find games by name.""" lines = await self.search_games(search_term) await LinePaginator.paginate(lines, ctx, Embed(title=f"Game Search Results: {search_term}"), empty=False) - @games.command(name="company", aliases=["companies"]) + @games.command(name="company", aliases=("companies",)) async def company(self, ctx: Context, amount: int = 5) -> None: """ Get random Game Companies companies from IGDB API. @@ -325,7 +325,7 @@ class Games(Cog): await ImagePaginator.paginate(pages, ctx, Embed(title="Random Game Companies")) @with_role(*STAFF_ROLES) - @games.command(name="refresh", aliases=["r"]) + @games.command(name="refresh", aliases=("r",)) async def refresh_genres_command(self, ctx: Context) -> None: """Refresh .games command genres.""" try: diff --git a/bot/exts/evergreen/movie.py b/bot/exts/evergreen/movie.py index ff23df4c..10638aea 100644 --- a/bot/exts/evergreen/movie.py +++ b/bot/exts/evergreen/movie.py @@ -53,7 +53,7 @@ class Movie(Cog): def __init__(self, bot: Bot): self.http_session: ClientSession = bot.http_session - @group(name="movies", aliases=["movie"], invoke_without_command=True) + @group(name="movies", aliases=("movie",), invoke_without_command=True) async def movies(self, ctx: Context, genre: str = "", amount: int = 5) -> None: """ Get random movies by specifying genre. Also support amount parameter, that define how much movies will be shown. @@ -103,7 +103,7 @@ class Movie(Cog): await ImagePaginator.paginate(pages, ctx, embed) - @movies.command(name="genres", aliases=["genre", "g"]) + @movies.command(name="genres", aliases=("genre", "g")) async def genres(self, ctx: Context) -> None: """Show all currently available genres for .movies command.""" await ctx.send(f"Current available genres: {', '.join('`' + genre.name + '`' for genre in MovieGenres)}") diff --git a/bot/exts/evergreen/pythonfacts.py b/bot/exts/evergreen/pythonfacts.py index e162c9bd..606545ab 100644 --- a/bot/exts/evergreen/pythonfacts.py +++ b/bot/exts/evergreen/pythonfacts.py @@ -15,7 +15,7 @@ COLORS = itertools.cycle([Colours.python_blue, Colours.python_yellow]) class PythonFacts(commands.Cog): """Sends a random fun fact about Python.""" - @commands.command(name="pythonfact", aliases=["pyfact"]) + @commands.command(name="pythonfact", aliases=("pyfact",)) async def get_python_fact(self, ctx: commands.Context) -> None: """Sends a Random fun fact about Python.""" embed = discord.Embed( diff --git a/bot/exts/evergreen/recommend_game.py b/bot/exts/evergreen/recommend_game.py index 56596020..45108969 100644 --- a/bot/exts/evergreen/recommend_game.py +++ b/bot/exts/evergreen/recommend_game.py @@ -25,7 +25,7 @@ class RecommendGame(commands.Cog): self.bot = bot self.index = 0 - @commands.command(name="recommendgame", aliases=["gamerec"]) + @commands.command(name="recommendgame", aliases=("gamerec",)) async def recommend_game(self, ctx: commands.Context) -> None: """Sends an Embed of a random game recommendation.""" if self.index >= len(game_recs): diff --git a/bot/exts/evergreen/space.py b/bot/exts/evergreen/space.py index 6c991d26..5e87c6d5 100644 --- a/bot/exts/evergreen/space.py +++ b/bot/exts/evergreen/space.py @@ -193,7 +193,7 @@ class Space(Cog): ) ) - @mars.command(name="dates", aliases=["d", "date", "rover", "rovers", "r"]) + @mars.command(name="dates", aliases=("d", "date", "rover", "rovers", "r")) async def dates(self, ctx: Context) -> None: """Get current available rovers photo date ranges.""" await ctx.send("\n".join( diff --git a/bot/exts/evergreen/timed.py b/bot/exts/evergreen/timed.py index 491231cc..2ea6b419 100644 --- a/bot/exts/evergreen/timed.py +++ b/bot/exts/evergreen/timed.py @@ -18,7 +18,7 @@ class TimedCommands(commands.Cog): return await ctx.bot.get_context(msg) - @commands.command(name="timed", aliases=["time", "t"]) + @commands.command(name="timed", aliases=("time", "t")) async def timed(self, ctx: commands.Context, *, command: str) -> None: """Time the command execution of a command.""" new_ctx = await self.create_execution_context(ctx, command) diff --git a/bot/exts/evergreen/trivia_quiz.py b/bot/exts/evergreen/trivia_quiz.py index 9db201ef..352d5ae8 100644 --- a/bot/exts/evergreen/trivia_quiz.py +++ b/bot/exts/evergreen/trivia_quiz.py @@ -43,7 +43,7 @@ class TriviaQuiz(commands.Cog): p = Path("bot", "resources", "evergreen", "trivia_quiz.json") return json.loads(p.read_text("utf8")) - @commands.group(name="quiz", aliases=["trivia"], invoke_without_command=True) + @commands.group(name="quiz", aliases=("trivia",), invoke_without_command=True) async def quiz_game(self, ctx: commands.Context, category: str = None) -> None: """ Start a quiz! diff --git a/bot/exts/evergreen/wikipedia.py b/bot/exts/evergreen/wikipedia.py index fa21b916..83937438 100644 --- a/bot/exts/evergreen/wikipedia.py +++ b/bot/exts/evergreen/wikipedia.py @@ -72,7 +72,7 @@ class WikipediaSearch(commands.Cog): return @commands.cooldown(1, 10, commands.BucketType.user) - @commands.command(name="wikipedia", aliases=["wiki"]) + @commands.command(name="wikipedia", aliases=("wiki",)) async def wikipedia_search_command(self, ctx: commands.Context, *, search: str) -> None: """Sends paginated top 10 results of Wikipedia search..""" contents = await self.wiki_request(ctx.channel, search) diff --git a/bot/exts/evergreen/wonder_twins.py b/bot/exts/evergreen/wonder_twins.py index 437d69f1..40edf785 100644 --- a/bot/exts/evergreen/wonder_twins.py +++ b/bot/exts/evergreen/wonder_twins.py @@ -38,7 +38,7 @@ class WonderTwins(Cog): object_name = self.append_onto(adjective, object_name) return f"{object_name} of {water_type}" - @command(name="formof", aliases=["wondertwins", "wondertwin", "fo"]) + @command(name="formof", aliases=("wondertwins", "wondertwin", "fo")) async def form_of(self, ctx: Context) -> None: """Command to send a Wonder Twins inspired phrase to the user invoking the command.""" await ctx.send(f"Form of {self.format_phrase()}!") diff --git a/bot/exts/halloween/spookynamerate.py b/bot/exts/halloween/spookynamerate.py index 63040289..c9b42ef5 100644 --- a/bot/exts/halloween/spookynamerate.py +++ b/bot/exts/halloween/spookynamerate.py @@ -117,7 +117,7 @@ class SpookyNameRate(Cog): """Get help on the Spooky Name Rate game.""" await ctx.send(embed=Embed.from_dict(HELP_MESSAGE_DICT)) - @spooky_name_rate.command(name="list", aliases=["all", "entries"]) + @spooky_name_rate.command(name="list", aliases=("all", "entries")) async def list_entries(self, ctx: Context) -> None: """Send all the entries up till now in a single embed.""" await ctx.send(embed=await self.get_responses_list(final=False)) @@ -134,7 +134,7 @@ class SpookyNameRate(Cog): "add an entry." ) - @spooky_name_rate.command(name="add", aliases=["register"]) + @spooky_name_rate.command(name="add", aliases=("register",)) async def add_name(self, ctx: Context, *, name: str) -> None: """Use this command to add/register your spookified name.""" if self.poll: diff --git a/bot/exts/pride/drag_queen_name.py b/bot/exts/pride/drag_queen_name.py index 6bf43913..81eeaff5 100644 --- a/bot/exts/pride/drag_queen_name.py +++ b/bot/exts/pride/drag_queen_name.py @@ -21,7 +21,7 @@ class DragNames(commands.Cog): """Loads a list of drag queen names.""" return json.loads(Path("bot/resources/pride/drag_queen_names.json").read_text("utf8")) - @commands.command(name="dragname", aliases=["dragqueenname", "queenme"]) + @commands.command(name="dragname", aliases=("dragqueenname", "queenme")) async def dragname(self, ctx: commands.Context) -> None: """Sends a message with a drag queen name.""" await ctx.send(random.choice(self.names)) diff --git a/bot/exts/pride/pride_anthem.py b/bot/exts/pride/pride_anthem.py index 21b7e468..ce4b06af 100644 --- a/bot/exts/pride/pride_anthem.py +++ b/bot/exts/pride/pride_anthem.py @@ -38,7 +38,7 @@ class PrideAnthem(commands.Cog): """Loads a list of videos from the resources folder as dictionaries.""" return json.loads(Path("bot/resources/pride/anthems.json").read_text("utf8")) - @commands.command(name="prideanthem", aliases=["anthem", "pridesong"]) + @commands.command(name="prideanthem", aliases=("anthem", "pridesong")) async def prideanthem(self, ctx: commands.Context, genre: str = None) -> None: """ Sends a message with a video of a random pride anthem. diff --git a/bot/exts/pride/pride_facts.py b/bot/exts/pride/pride_facts.py index 47e69a03..5bea1d32 100644 --- a/bot/exts/pride/pride_facts.py +++ b/bot/exts/pride/pride_facts.py @@ -72,7 +72,7 @@ class PrideFacts(commands.Cog): else: await target.send("The fact for the selected day is not yet available.") - @commands.command(name="pridefact", aliases=["pridefacts"]) + @commands.command(name="pridefact", aliases=("pridefacts",)) async def pridefact(self, ctx: commands.Context, option: str = None) -> None: """ Sends a message with a pride fact of the day. diff --git a/bot/exts/valentines/valentine_zodiac.py b/bot/exts/valentines/valentine_zodiac.py index 45d1edd5..d862ee63 100644 --- a/bot/exts/valentines/valentine_zodiac.py +++ b/bot/exts/valentines/valentine_zodiac.py @@ -116,7 +116,7 @@ class ValentineZodiac(commands.Cog): await ctx.send(embed=final_embed) log.trace("Embed from date successfully sent.") - @zodiac.command(name="partnerzodiac", aliases=["partner"]) + @zodiac.command(name="partnerzodiac", aliases=("partner",)) async def partner_zodiac(self, ctx: commands.Context, zodiac_sign: str) -> None: """Provides a random counter compatible zodiac sign to the given user's zodiac sign.""" embed = discord.Embed() -- cgit v1.2.3 From 28eb6d83dd8f1d1b41b4d45f09409cb6391ce52f Mon Sep 17 00:00:00 2001 From: ToxicKidz <78174417+ToxicKidz@users.noreply.github.com> Date: Sat, 15 May 2021 12:54:54 -0400 Subject: fix: Remove the useless return Co-authored-by: Xithrius <15021300+Xithrius@users.noreply.github.com> --- bot/exts/evergreen/reddit.py | 1 - 1 file changed, 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/reddit.py b/bot/exts/evergreen/reddit.py index b5af83e7..e57fa2c0 100644 --- a/bot/exts/evergreen/reddit.py +++ b/bot/exts/evergreen/reddit.py @@ -99,7 +99,6 @@ class Reddit(Cog): f"{content_type}\u2003{Emojis.reddit_upvote}{ups}\u2003{Emojis.reddit_comments}" f"\u2002{comments}\u2003{Emojis.reddit_users}{author}\n\n" ) - return if paginate: post_page += f"**[{title}]({link})**\n\n" -- cgit v1.2.3 From f8a179df350eb3abb7f3595aaff3dbca312f540c Mon Sep 17 00:00:00 2001 From: ToxicKidz Date: Sat, 15 May 2021 13:31:34 -0400 Subject: chore: Make things that are only used once constants --- bot/exts/christmas/hanukkah_embed.py | 21 ++++++++++----------- bot/exts/easter/earth_photos.py | 4 +++- bot/exts/easter/egg_facts.py | 12 +++--------- bot/exts/evergreen/cheatsheet.py | 2 +- bot/exts/evergreen/magic_8ball.py | 7 +++---- bot/exts/evergreen/pythonfacts.py | 3 ++- bot/exts/halloween/halloween_facts.py | 9 ++++----- bot/exts/halloween/halloweenify.py | 10 +++++----- bot/exts/halloween/spookygif.py | 7 ++++--- bot/exts/pride/drag_queen_name.py | 12 +++--------- bot/exts/pride/pride_anthem.py | 12 +++--------- bot/exts/pride/pride_facts.py | 13 ++++--------- 12 files changed, 45 insertions(+), 67 deletions(-) (limited to 'bot') diff --git a/bot/exts/christmas/hanukkah_embed.py b/bot/exts/christmas/hanukkah_embed.py index 7a06b558..ca3146cc 100644 --- a/bot/exts/christmas/hanukkah_embed.py +++ b/bot/exts/christmas/hanukkah_embed.py @@ -11,16 +11,17 @@ from bot.utils.decorators import in_month log = logging.getLogger(__name__) +HEBCAL_URL = ( + "https://www.hebcal.com/hebcal/?v=1&cfg=json&maj=on&min=on&mod=on&nx=on&" + "year=now&month=x&ss=on&mf=on&c=on&geo=geoname&geonameid=3448439&m=50&s=on" +) + class HanukkahEmbed(commands.Cog): """A cog that returns information about Hanukkah festival.""" def __init__(self, bot: Bot): self.bot = bot - self.url = ( - "https://www.hebcal.com/hebcal/?v=1&cfg=json&maj=on&min=on&mod=on&nx=on&" - "year=now&month=x&ss=on&mf=on&c=on&geo=geoname&geonameid=3448439&m=50&s=on" - ) self.hanukkah_days = [] self.hanukkah_months = [] self.hanukkah_years = [] @@ -28,7 +29,7 @@ class HanukkahEmbed(commands.Cog): async def get_hanukkah_dates(self) -> List[str]: """Gets the dates for hanukkah festival.""" hanukkah_dates = [] - async with self.bot.http_session.get(self.url) as response: + async with self.bot.http_session.get(HEBCAL_URL) as response: json_data = await response.json() festivals = json_data["items"] for festival in festivals: @@ -57,9 +58,7 @@ class HanukkahEmbed(commands.Cog): day = str(today.day) month = str(today.month) year = str(today.year) - embed = Embed() - embed.title = "Hanukkah" - embed.colour = Colours.blue + embed = Embed(title="Hanukkah", colour=Colours.blue) if day in self.hanukkah_days and month in self.hanukkah_months and year in self.hanukkah_years: if int(day) == hanukkah_start_day: now = datetime.datetime.utcnow() @@ -74,8 +73,8 @@ class HanukkahEmbed(commands.Cog): return elif hours > hanukkah_start_hour: embed.description = ( - "It is the starting day of Hanukkah ! " - f"Its been {hours - hanukkah_start_hour} hours hanukkah started !" + "It is the starting day of Hanukkah! " + f"Its been {hours - hanukkah_start_hour} hours hanukkah started!" ) await ctx.send(embed=embed) return @@ -106,7 +105,7 @@ class HanukkahEmbed(commands.Cog): else: festival_end_month = hanukkah_end.strftime("%B") embed.description = ( - f"Looks like you missed Hanukkah !" + f"Looks like you missed Hanukkah!" f"Hanukkah ended on {hanukkah_end_day}th of {festival_end_month}." ) diff --git a/bot/exts/easter/earth_photos.py b/bot/exts/easter/earth_photos.py index 9df4f9ca..f65790af 100644 --- a/bot/exts/easter/earth_photos.py +++ b/bot/exts/easter/earth_photos.py @@ -9,6 +9,8 @@ from bot.constants import Tokens log = logging.getLogger(__name__) +API_URL = "https://api.unsplash.com/photos/random" + class EarthPhotos(commands.Cog): """This cog contains the command for earth photos.""" @@ -21,7 +23,7 @@ class EarthPhotos(commands.Cog): """Returns a random photo of earth, sourced from Unsplash.""" async with ctx.typing(): async with self.bot.http_session.get( - "https://api.unsplash.com/photos/random", + API_URL, params={"query": "planet_earth", "client_id": Tokens.unsplash_access_key} ) as r: jsondata = await r.json() diff --git a/bot/exts/easter/egg_facts.py b/bot/exts/easter/egg_facts.py index d60032f0..ee383171 100644 --- a/bot/exts/easter/egg_facts.py +++ b/bot/exts/easter/egg_facts.py @@ -12,6 +12,8 @@ from bot.utils.decorators import seasonal_task log = logging.getLogger(__name__) +EGG_FACTS = loads(Path("bot/resources/easter/easter_egg_facts.json").read_text("utf8")) + class EasterFacts(commands.Cog): """ @@ -22,16 +24,8 @@ class EasterFacts(commands.Cog): def __init__(self, bot: Bot): self.bot = bot - self.facts = self.load_json() - self.daily_fact_task = self.bot.loop.create_task(self.send_egg_fact_daily()) - @staticmethod - def load_json() -> dict: - """Load a list of easter egg facts from the resource JSON file.""" - p = Path("bot/resources/easter/easter_egg_facts.json") - return loads(p.read_text("utf8")) - @seasonal_task(Month.APRIL) async def send_egg_fact_daily(self) -> None: """A background task that sends an easter egg fact in the event channel everyday.""" @@ -51,7 +45,7 @@ class EasterFacts(commands.Cog): return discord.Embed( colour=Colours.soft_red, title="Easter Egg Fact", - description=random.choice(self.facts) + description=random.choice(EGG_FACTS) ) diff --git a/bot/exts/evergreen/cheatsheet.py b/bot/exts/evergreen/cheatsheet.py index 7ee34b1d..ae7793c9 100644 --- a/bot/exts/evergreen/cheatsheet.py +++ b/bot/exts/evergreen/cheatsheet.py @@ -12,7 +12,7 @@ from bot.bot import Bot from bot.constants import Categories, Channels, Colours, ERROR_REPLIES from bot.utils.decorators import whitelist_override -ERROR_MESSAGE = f"""\ +ERROR_MESSAGE = f""" Unknown cheat sheet. Please try to reformulate your query. **Examples**: diff --git a/bot/exts/evergreen/magic_8ball.py b/bot/exts/evergreen/magic_8ball.py index 4b3ed2a4..28ddcea0 100644 --- a/bot/exts/evergreen/magic_8ball.py +++ b/bot/exts/evergreen/magic_8ball.py @@ -9,18 +9,17 @@ from bot.bot import Bot log = logging.getLogger(__name__) +ANSWERS = json.loads(Path("bot/resources/evergreen/magic8ball.json").read_text("utf8")) + class Magic8ball(commands.Cog): """A Magic 8ball command to respond to a user's question.""" - def __init__(self): - self.answers = json.loads(Path("bot/resources/evergreen/magic8ball.json").read_text("utf8")) - @commands.command(name="8ball") async def output_answer(self, ctx: commands.Context, *, question: str) -> None: """Return a Magic 8ball answer from answers list.""" if len(question.split()) >= 3: - answer = random.choice(self.answers) + answer = random.choice(ANSWERS) await ctx.send(answer) else: await ctx.send("Usage: .8ball (minimum length of 3 eg: `will I win?`)") diff --git a/bot/exts/evergreen/pythonfacts.py b/bot/exts/evergreen/pythonfacts.py index 606545ab..80a8da5d 100644 --- a/bot/exts/evergreen/pythonfacts.py +++ b/bot/exts/evergreen/pythonfacts.py @@ -10,6 +10,7 @@ with open("bot/resources/evergreen/python_facts.txt") as file: FACTS = itertools.cycle(list(file)) COLORS = itertools.cycle([Colours.python_blue, Colours.python_yellow]) +PYFACTS_DISCUSSION = "https://github.com/python-discord/meta/discussions/93" class PythonFacts(commands.Cog): @@ -25,7 +26,7 @@ class PythonFacts(commands.Cog): ) embed.add_field( name="Suggestions", - value="Suggest more facts [here!](https://github.com/python-discord/meta/discussions/93)" + value=f"Suggest more facts [here!]({PYFACTS_DISCUSSION})" ) await ctx.send(embed=embed) diff --git a/bot/exts/halloween/halloween_facts.py b/bot/exts/halloween/halloween_facts.py index 98cc2db0..5ad8cc57 100644 --- a/bot/exts/halloween/halloween_facts.py +++ b/bot/exts/halloween/halloween_facts.py @@ -25,17 +25,16 @@ SPOOKY_EMOJIS = [ PUMPKIN_ORANGE = 0xFF7518 INTERVAL = timedelta(hours=6).total_seconds() +FACTS = json.loads(Path("bot/resources/halloween/halloween_facts.json").read_text("utf8")) +FACTS = list(enumerate(FACTS)) + class HalloweenFacts(commands.Cog): """A Cog for displaying interesting facts about Halloween.""" - def __init__(self): - self.halloween_facts = json.loads(Path("bot/resources/halloween/halloween_facts.json").read_text("utf8")) - self.facts = list(enumerate(self.halloween_facts)) - def random_fact(self) -> Tuple[int, str]: """Return a random fact from the loaded facts.""" - return random.choice(self.facts) + return random.choice(FACTS) @commands.command(name="spookyfact", aliases=("halloweenfact",), brief="Get the most recent Halloween fact") async def get_random_fact(self, ctx: commands.Context) -> None: diff --git a/bot/exts/halloween/halloweenify.py b/bot/exts/halloween/halloweenify.py index e839950a..83cfbaa7 100644 --- a/bot/exts/halloween/halloweenify.py +++ b/bot/exts/halloween/halloweenify.py @@ -12,6 +12,8 @@ from bot.bot import Bot log = logging.getLogger(__name__) +HALLOWEENIFY_DATA = loads(Path("bot/resources/halloween/halloweenify.json").read_text("utf8")) + class Halloweenify(commands.Cog): """A cog to change a invokers nickname to a spooky one!""" @@ -21,12 +23,10 @@ class Halloweenify(commands.Cog): async def halloweenify(self, ctx: commands.Context) -> None: """Change your nickname into a much spookier one!""" async with ctx.typing(): - data = loads(Path("bot/resources/halloween/halloweenify.json").read_text("utf8")) - # Choose a random character from our list we loaded above and set apart the nickname and image url. - character = choice(data["characters"]) - nickname = "".join([nickname for nickname in character]) - image = "".join([character[nickname] for nickname in character]) + character = choice(HALLOWEENIFY_DATA["characters"]) + nickname = "".join(nickname for nickname in character) + image = "".join(character[nickname] for nickname in character) # Build up a Embed embed = discord.Embed() diff --git a/bot/exts/halloween/spookygif.py b/bot/exts/halloween/spookygif.py index ffb91b1b..a2146a84 100644 --- a/bot/exts/halloween/spookygif.py +++ b/bot/exts/halloween/spookygif.py @@ -8,6 +8,8 @@ from bot.constants import Tokens log = logging.getLogger(__name__) +API_URL = "http://api.giphy.com/v1/gifs/random" + class SpookyGif(commands.Cog): """A cog to fetch a random spooky gif from the web!""" @@ -21,12 +23,11 @@ class SpookyGif(commands.Cog): async with ctx.typing(): params = {"api_key": Tokens.giphy, "tag": "halloween", "rating": "g"} # Make a GET request to the Giphy API to get a random halloween gif. - async with self.bot.http_session.get("http://api.giphy.com/v1/gifs/random", params=params) as resp: + async with self.bot.http_session.get(API_URL, params=params) as resp: data = await resp.json() url = data["data"]["image_url"] - embed = discord.Embed(colour=0x9b59b6) - embed.title = "A spooooky gif!" + embed = discord.Embed(title="A spooooky gif!", colour=0x9b59b6) embed.set_image(url=url) await ctx.send(embed=embed) diff --git a/bot/exts/pride/drag_queen_name.py b/bot/exts/pride/drag_queen_name.py index 81eeaff5..15ca6576 100644 --- a/bot/exts/pride/drag_queen_name.py +++ b/bot/exts/pride/drag_queen_name.py @@ -9,22 +9,16 @@ from bot.bot import Bot log = logging.getLogger(__name__) +NAMES = json.loads(Path("bot/resources/pride/drag_queen_names.json").read_text("utf8")) + class DragNames(commands.Cog): """Gives a random drag queen name!""" - def __init__(self): - self.names = self.load_names() - - @staticmethod - def load_names() -> list: - """Loads a list of drag queen names.""" - return json.loads(Path("bot/resources/pride/drag_queen_names.json").read_text("utf8")) - @commands.command(name="dragname", aliases=("dragqueenname", "queenme")) async def dragname(self, ctx: commands.Context) -> None: """Sends a message with a drag queen name.""" - await ctx.send(random.choice(self.names)) + await ctx.send(random.choice(NAMES)) def setup(bot: Bot) -> None: diff --git a/bot/exts/pride/pride_anthem.py b/bot/exts/pride/pride_anthem.py index ce4b06af..4650595a 100644 --- a/bot/exts/pride/pride_anthem.py +++ b/bot/exts/pride/pride_anthem.py @@ -10,13 +10,12 @@ from bot.bot import Bot log = logging.getLogger(__name__) +VIDEOS = json.loads(Path("bot/resources/pride/anthems.json").read_text("utf8")) + class PrideAnthem(commands.Cog): """Embed a random youtube video for a gay anthem!""" - def __init__(self): - self.anthems = self.load_vids() - def get_video(self, genre: Optional[str] = None) -> dict: """ Picks a random anthem from the list. @@ -27,17 +26,12 @@ class PrideAnthem(commands.Cog): if not genre: return random.choice(self.anthems) else: - songs = [song for song in self.anthems if genre.casefold() in song["genre"]] + songs = [song for song in VIDEOS if genre.casefold() in song["genre"]] try: return random.choice(songs) except IndexError: log.info("No videos for that genre.") - @staticmethod - def load_vids() -> list: - """Loads a list of videos from the resources folder as dictionaries.""" - return json.loads(Path("bot/resources/pride/anthems.json").read_text("utf8")) - @commands.command(name="prideanthem", aliases=("anthem", "pridesong")) async def prideanthem(self, ctx: commands.Context, genre: str = None) -> None: """ diff --git a/bot/exts/pride/pride_facts.py b/bot/exts/pride/pride_facts.py index 5bea1d32..631e2e8b 100644 --- a/bot/exts/pride/pride_facts.py +++ b/bot/exts/pride/pride_facts.py @@ -15,21 +15,16 @@ from bot.utils.decorators import seasonal_task log = logging.getLogger(__name__) +FACTS = json.loads(Path("bot/resources/pride/facts.json").read_text("utf8")) + class PrideFacts(commands.Cog): """Provides a new fact every day during the Pride season!""" def __init__(self, bot: Bot): self.bot = bot - self.facts = self.load_facts() - self.daily_fact_task = self.bot.loop.create_task(self.send_pride_fact_daily()) - @staticmethod - def load_facts() -> dict: - """Loads a dictionary of years mapping to lists of facts.""" - return json.loads(Path("bot/resources/pride/facts.json").read_text("utf8")) - @seasonal_task(Month.JUNE) async def send_pride_fact_daily(self) -> None: """Background task to post the daily pride fact every day.""" @@ -41,8 +36,8 @@ class PrideFacts(commands.Cog): async def send_random_fact(self, ctx: commands.Context) -> None: """Provides a fact from any previous day, or today.""" now = datetime.utcnow() - previous_years_facts = (y for x, y in self.facts.items() if int(x) < now.year) - current_year_facts = self.facts.get(str(now.year), [])[:now.day] + previous_years_facts = (y for x, y in FACTS.items() if int(x) < now.year) + current_year_facts = FACTS.get(str(now.year), [])[:now.day] previous_facts = current_year_facts + [x for y in previous_years_facts for x in y] try: await ctx.send(embed=self.make_embed(random.choice(previous_facts))) -- cgit v1.2.3 From bf999d03b02e0664ffe43c31cf9208c0251a8370 Mon Sep 17 00:00:00 2001 From: ToxicKidz Date: Sat, 15 May 2021 13:44:14 -0400 Subject: chore: Apply suggested changes to hanukkah_embed.py --- bot/exts/christmas/hanukkah_embed.py | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) (limited to 'bot') diff --git a/bot/exts/christmas/hanukkah_embed.py b/bot/exts/christmas/hanukkah_embed.py index ca3146cc..119f2446 100644 --- a/bot/exts/christmas/hanukkah_embed.py +++ b/bot/exts/christmas/hanukkah_embed.py @@ -80,18 +80,8 @@ class HanukkahEmbed(commands.Cog): return festival_day = self.hanukkah_days.index(day) number_suffixes = ["st", "nd", "rd", "th"] - suffix = "" - if festival_day == 1: - suffix = number_suffixes[0] - if festival_day == 2: - suffix = number_suffixes[1] - if festival_day == 3: - suffix = number_suffixes[2] - if festival_day > 3: - suffix = number_suffixes[3] - message = "" - for _ in range(1, festival_day + 1): - message += ":menorah:" + suffix = number_suffixes[festival_day - 1 if festival_day <= 3 else 3] + message = ":menorah:" * festival_day embed.description = f"It is the {festival_day}{suffix} day of Hanukkah!\n{message}" await ctx.send(embed=embed) else: -- cgit v1.2.3 From 85733251d46151dc76378fad253c1d45e49eb9a0 Mon Sep 17 00:00:00 2001 From: Matteo Bertucci Date: Sat, 15 May 2021 20:07:10 +0200 Subject: Latex: temporarily disable loading of the cog As we are currently working on restricting resources on the latex cog, this commit will prevent it from loading. It will be reverted once solved. --- bot/exts/evergreen/latex.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'bot') diff --git a/bot/exts/evergreen/latex.py b/bot/exts/evergreen/latex.py index c4a8597c..8f42da90 100644 --- a/bot/exts/evergreen/latex.py +++ b/bot/exts/evergreen/latex.py @@ -91,4 +91,9 @@ class Latex(commands.Cog): def setup(bot: commands.Bot) -> None: """Load the Latex Cog.""" + # As we have resource issues on this cog, + # we have it currently disabled while we fix it. + import logging + logging.info("Latex cog is currently disabled. It won't be loaded.") + return bot.add_cog(Latex(bot)) -- cgit v1.2.3 From 01e22725c627b677224ccf8409f71c9b7bc68c81 Mon Sep 17 00:00:00 2001 From: ToxicKidz Date: Sat, 15 May 2021 15:34:08 -0400 Subject: chore: Make more things use constants --- bot/exts/halloween/spookygif.py | 4 ++-- bot/exts/halloween/spookynamerate.py | 22 +++++++--------------- 2 files changed, 9 insertions(+), 17 deletions(-) (limited to 'bot') diff --git a/bot/exts/halloween/spookygif.py b/bot/exts/halloween/spookygif.py index a2146a84..9511d407 100644 --- a/bot/exts/halloween/spookygif.py +++ b/bot/exts/halloween/spookygif.py @@ -4,7 +4,7 @@ import discord from discord.ext import commands from bot.bot import Bot -from bot.constants import Tokens +from bot.constants import Colours, Tokens log = logging.getLogger(__name__) @@ -27,7 +27,7 @@ class SpookyGif(commands.Cog): data = await resp.json() url = data["data"]["image_url"] - embed = discord.Embed(title="A spooooky gif!", colour=0x9b59b6) + embed = discord.Embed(title="A spooooky gif!", colour=Colours.purple) embed.set_image(url=url) await ctx.send(embed=embed) diff --git a/bot/exts/halloween/spookynamerate.py b/bot/exts/halloween/spookynamerate.py index c9b42ef5..3d6d95fa 100644 --- a/bot/exts/halloween/spookynamerate.py +++ b/bot/exts/halloween/spookynamerate.py @@ -6,7 +6,7 @@ from datetime import datetime, timedelta from logging import getLogger from os import getenv from pathlib import Path -from typing import Dict, Union +from typing import Union from async_rediscache import RedisCache from discord import Embed, Reaction, TextChannel, User @@ -65,6 +65,11 @@ HELP_MESSAGE_DICT = { ], } +# The names are from https://www.mockaroo.com/ +NAMES = json.loads(Path("bot/resources/halloween/spookynamerate_names.json").read_text("utf8")) +FIRST_NAMES = NAMES["first_names"] +LAST_NAMES = NAMES["last_names"] + class SpookyNameRate(Cog): """ @@ -88,14 +93,6 @@ class SpookyNameRate(Cog): def __init__(self, bot: Bot) -> None: self.bot = bot - - names_data = self.load_json( - Path("bot", "resources", "halloween", "spookynamerate_names.json") - ) - self.first_names = names_data["first_names"] - self.last_names = names_data["last_names"] - # the names are from https://www.mockaroo.com/ - self.name = None self.bot.loop.create_task(self.load_vars()) @@ -302,7 +299,7 @@ class SpookyNameRate(Cog): await self.messages.clear() # reset the messages # send the next name - self.name = f"{random.choice(self.first_names)} {random.choice(self.last_names)}" + self.name = f"{random.choice(FIRST_NAMES)} {random.choice(LAST_NAMES)}" await self.data.set("name", self.name) await channel.send( @@ -368,11 +365,6 @@ class SpookyNameRate(Cog): logger.warning("Bot is unable to get the #seasonalbot-commands channel. Please check the channel ID.") return channel - @staticmethod - def load_json(file: Path) -> Dict[str, str]: - """Loads a JSON file and returns its contents.""" - return json.loads(file.read_text("utf8")) - @staticmethod def in_allowed_month() -> bool: """Returns whether running in the limited month.""" -- cgit v1.2.3 From 369134e791fd9a3e52a4948725c249d03128fdd6 Mon Sep 17 00:00:00 2001 From: ToxicKidz <78174417+ToxicKidz@users.noreply.github.com> Date: Sat, 15 May 2021 15:40:09 -0400 Subject: chore: Apply suggestions from code review Co-authored-by: Xithrius <15021300+Xithrius@users.noreply.github.com> --- bot/exts/evergreen/recommend_game.py | 2 +- bot/exts/valentines/myvalenstate.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/recommend_game.py b/bot/exts/evergreen/recommend_game.py index 45108969..35d60128 100644 --- a/bot/exts/evergreen/recommend_game.py +++ b/bot/exts/evergreen/recommend_game.py @@ -41,7 +41,7 @@ class RecommendGame(commands.Cog): if author is not None: embed.set_author(name=author.name, icon_url=author.avatar_url) embed.set_image(url=game["image"]) - embed.add_field(name="Recommendation: " + game["title"] + "\n" + game["link"], value=game["description"]) + embed.add_field(name=f"Recommendation: {game['title']}\n{game['link']}", value=game["description"]) await ctx.send(embed=embed) diff --git a/bot/exts/valentines/myvalenstate.py b/bot/exts/valentines/myvalenstate.py index c699de94..52a61011 100644 --- a/bot/exts/valentines/myvalenstate.py +++ b/bot/exts/valentines/myvalenstate.py @@ -69,7 +69,7 @@ class MyValenstate(commands.Cog): embed = discord.Embed( title=f"Your Valenstate is {valenstate} \u2764", - description=STATES[valenstate]['text'], + description=STATES[valenstate]["text"], colour=Colours.pink ) embed.add_field(name=embed_title, value=embed_text) -- cgit v1.2.3 From 8ac335afc7466d92888c40db46e0c6d454743ff6 Mon Sep 17 00:00:00 2001 From: ToxicKidz Date: Sat, 15 May 2021 17:32:09 -0400 Subject: chore: Add Iceman's suggested changes --- bot/exts/easter/bunny_name_generator.py | 9 ++++++--- bot/exts/easter/easter_riddle.py | 2 +- bot/exts/easter/egg_facts.py | 3 ++- bot/exts/evergreen/avatar_modification/_effects.py | 4 ++-- bot/exts/evergreen/avatar_modification/avatar_modify.py | 4 ++-- bot/exts/evergreen/battleship.py | 8 ++++---- 6 files changed, 17 insertions(+), 13 deletions(-) (limited to 'bot') diff --git a/bot/exts/easter/bunny_name_generator.py b/bot/exts/easter/bunny_name_generator.py index adde8704..3e97373f 100644 --- a/bot/exts/easter/bunny_name_generator.py +++ b/bot/exts/easter/bunny_name_generator.py @@ -17,14 +17,16 @@ BUNNY_NAMES = json.loads(Path("bot/resources/easter/bunny_names.json").read_text class BunnyNameGenerator(commands.Cog): """Generate a random bunny name, or bunnify your Discord username!""" - def find_separators(self, displayname: str) -> Union[List[str], None]: + @staticmethod + def find_separators(displayname: str) -> Union[List[str], None]: """Check if Discord name contains spaces so we can bunnify an individual word in the name.""" new_name = re.split(r"[_.\s]", displayname) if displayname not in new_name: return new_name return None - def find_vowels(self, displayname: str) -> str: + @staticmethod + def find_vowels(displayname: str) -> str: """ Finds vowels in the user's display name. @@ -45,7 +47,8 @@ class BunnyNameGenerator(commands.Cog): if new_name != displayname: return new_name - def append_name(self, displayname: str) -> str: + @staticmethod + def append_name(displayname: str) -> str: """Adds a suffix to the end of the Discord name.""" extensions = ["foot", "ear", "nose", "tail"] suffix = random.choice(extensions) diff --git a/bot/exts/easter/easter_riddle.py b/bot/exts/easter/easter_riddle.py index a87f93d1..88b3be2f 100644 --- a/bot/exts/easter/easter_riddle.py +++ b/bot/exts/easter/easter_riddle.py @@ -48,7 +48,7 @@ class EasterRiddle(commands.Cog): ) return - self.current_channel = ctx.message.channel + self.current_channel = ctx.channel random_question = random.choice(RIDDLE_QUESTIONS) question = random_question["question"] diff --git a/bot/exts/easter/egg_facts.py b/bot/exts/easter/egg_facts.py index ee383171..486e735f 100644 --- a/bot/exts/easter/egg_facts.py +++ b/bot/exts/easter/egg_facts.py @@ -40,7 +40,8 @@ class EasterFacts(commands.Cog): embed = self.make_embed() await ctx.send(embed=embed) - def make_embed(self) -> discord.Embed: + @staticmethod + def make_embed() -> discord.Embed: """Makes a nice embed for the message to be sent.""" return discord.Embed( colour=Colours.soft_red, diff --git a/bot/exts/evergreen/avatar_modification/_effects.py b/bot/exts/evergreen/avatar_modification/_effects.py index 9339ecc4..cd798fc2 100644 --- a/bot/exts/evergreen/avatar_modification/_effects.py +++ b/bot/exts/evergreen/avatar_modification/_effects.py @@ -14,7 +14,7 @@ class PfpEffects: """ Implements various image modifying effects, for the PfpModify cog. - All of these fuctions are slow, and blocking, so they should be ran in executors. + All of these functions are slow, and blocking, so they should be ran in executors. """ @staticmethod @@ -102,7 +102,7 @@ class PfpEffects: Applies the easter effect to the given image. This is done by getting the closest "easter" colour to each pixel and changing the colour - to the half-way RGBvalue. + to the half-way RGB value. We also then add an overlay image on top in middle right, a chocolate bunny by default. """ diff --git a/bot/exts/evergreen/avatar_modification/avatar_modify.py b/bot/exts/evergreen/avatar_modification/avatar_modify.py index 199b6dcb..17f34ed4 100644 --- a/bot/exts/evergreen/avatar_modification/avatar_modify.py +++ b/bot/exts/evergreen/avatar_modification/avatar_modify.py @@ -33,7 +33,7 @@ GENDER_OPTIONS = json.loads(Path("bot/resources/pride/gender_options.json").read async def in_executor(func: t.Callable[..., T], *args) -> T: """ - Runs the given synchronus function `func` in an executor. + Runs the given synchronous function `func` in an executor. This is useful for running slow, blocking code within async functions, so that they don't block the bot. @@ -71,7 +71,7 @@ class AvatarModify(commands.Cog): Fetches a user and handles errors. This helper function is required as the member cache doesn't always have the most up to date - profile picture. This can lead to errors if the image is delted from the Discord CDN. + profile picture. This can lead to errors if the image is deleted from the Discord CDN. fetch_member can't be used due to the avatar url being part of the user object, and some weird caching that D.py does """ diff --git a/bot/exts/evergreen/battleship.py b/bot/exts/evergreen/battleship.py index 27c50bdb..c2f2079c 100644 --- a/bot/exts/evergreen/battleship.py +++ b/bot/exts/evergreen/battleship.py @@ -31,8 +31,8 @@ EmojiSet = typing.Dict[typing.Tuple[bool, bool], str] class Player: """Each player in the game - their messages for the boards and their current grid.""" - user: discord.Member - board: discord.Message + user: typing.Optional[discord.Member] + board: typing.Optional[discord.Message] opponent_board: discord.Message grid: Grid @@ -417,9 +417,9 @@ class Battleship(commands.Cog): self.waiting.remove(ctx.author) if self.already_playing(ctx.author): return + game = Game(self.bot, ctx.channel, ctx.author, user) + self.games.append(game) try: - game = Game(self.bot, ctx.channel, ctx.author, user) - self.games.append(game) await game.start_game() self.games.remove(game) except discord.Forbidden: -- cgit v1.2.3 From 0285da6cae32bb5986111477bbd4d61e723005f3 Mon Sep 17 00:00:00 2001 From: ToxicKidz <78174417+ToxicKidz@users.noreply.github.com> Date: Sat, 15 May 2021 17:34:11 -0400 Subject: chore: Apply Iceman's suggestions Co-authored-by: Rohan Reddy Alleti --- bot/exts/easter/egg_decorating.py | 2 +- bot/exts/evergreen/avatar_modification/_effects.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/exts/easter/egg_decorating.py b/bot/exts/easter/egg_decorating.py index d4b27b20..fd7620d4 100644 --- a/bot/exts/easter/egg_decorating.py +++ b/bot/exts/easter/egg_decorating.py @@ -44,7 +44,7 @@ class EggDecorating(commands.Cog): @commands.command(aliases=("decorateegg",)) async def eggdecorate( self, ctx: commands.Context, *colours: Union[discord.Colour, str] - ) -> Union[Image.Image, discord.Message]: + ) -> Union[Image.Image, None]: """ Picks a random egg design and decorates it using the given colours. diff --git a/bot/exts/evergreen/avatar_modification/_effects.py b/bot/exts/evergreen/avatar_modification/_effects.py index cd798fc2..b53b26f3 100644 --- a/bot/exts/evergreen/avatar_modification/_effects.py +++ b/bot/exts/evergreen/avatar_modification/_effects.py @@ -273,7 +273,7 @@ class PfpEffects: @staticmethod def mosaic_effect(img_bytes: bytes, squares: int, file_name: str) -> discord.File: - """Seperate function run from an executor which turns an image into a mosaic.""" + """Separate function run from an executor which turns an image into a mosaic.""" avatar = Image.open(BytesIO(img_bytes)) avatar = avatar.convert("RGBA").resize((1024, 1024)) -- cgit v1.2.3