diff options
author | 2021-08-23 02:06:27 -0700 | |
---|---|---|
committer | 2021-08-23 02:06:27 -0700 | |
commit | 1ee1f76581aa3549d65bf7dfbe38dfa55b1d4549 (patch) | |
tree | 73d1b3d2a2c82733b4e0ec642249b6a0f0f96d2e /bot | |
parent | Improve overall code transparency in the Wikipedia Cog (diff) | |
parent | Merge pull request #813 from python-discord/avatar-effects-fix (diff) |
Merge branch 'main' into pr/wikipediaissue
Diffstat (limited to 'bot')
-rw-r--r-- | bot/exts/christmas/advent_of_code/_helpers.py | 8 | ||||
-rw-r--r-- | bot/exts/evergreen/avatar_modification/_effects.py | 31 | ||||
-rw-r--r-- | bot/exts/evergreen/avatar_modification/avatar_modify.py | 42 | ||||
-rw-r--r-- | bot/exts/evergreen/help.py | 4 | ||||
-rw-r--r-- | bot/exts/evergreen/snakes/_converter.py | 2 | ||||
-rw-r--r-- | bot/exts/evergreen/trivia_quiz.py | 2 | ||||
-rw-r--r-- | bot/exts/pride/pride_leader.py | 2 | ||||
-rw-r--r-- | bot/exts/valentines/lovecalculator.py | 49 | ||||
-rw-r--r-- | bot/resources/evergreen/py_topics.yaml | 15 | ||||
-rw-r--r-- | bot/utils/pagination.py | 6 |
10 files changed, 112 insertions, 49 deletions
diff --git a/bot/exts/christmas/advent_of_code/_helpers.py b/bot/exts/christmas/advent_of_code/_helpers.py index 96de90c4..e26a17ca 100644 --- a/bot/exts/christmas/advent_of_code/_helpers.py +++ b/bot/exts/christmas/advent_of_code/_helpers.py @@ -67,7 +67,7 @@ class UnexpectedResponseStatus(aiohttp.ClientError): """Raised when an unexpected redirect was detected.""" -class FetchingLeaderboardFailed(Exception): +class FetchingLeaderboardFailedError(Exception): """Raised when one or more leaderboards could not be fetched at all.""" @@ -210,7 +210,7 @@ async def _fetch_leaderboard_data() -> typing.Dict[str, typing.Any]: except UnexpectedRedirect: if cookies["session"] == AdventOfCode.fallback_session: log.error("It seems like the fallback cookie has expired!") - raise FetchingLeaderboardFailed from None + raise FetchingLeaderboardFailedError from None # If we're here, it means that the original session did not # work. Let's fall back to the fallback session. @@ -218,7 +218,7 @@ async def _fetch_leaderboard_data() -> typing.Dict[str, typing.Any]: continue except aiohttp.ClientError: # Don't retry, something unexpected is wrong and it may not be the session. - raise FetchingLeaderboardFailed from None + raise FetchingLeaderboardFailedError from None else: # Get the participants and store their current count. board_participants = raw_data["members"] @@ -227,7 +227,7 @@ async def _fetch_leaderboard_data() -> typing.Dict[str, typing.Any]: break else: log.error(f"reached 'unreachable' state while fetching board `{leaderboard.id}`.") - raise FetchingLeaderboardFailed + raise FetchingLeaderboardFailedError log.info(f"Fetched leaderboard information for {len(participants)} participants") return participants diff --git a/bot/exts/evergreen/avatar_modification/_effects.py b/bot/exts/evergreen/avatar_modification/_effects.py index b53b26f3..92244207 100644 --- a/bot/exts/evergreen/avatar_modification/_effects.py +++ b/bot/exts/evergreen/avatar_modification/_effects.py @@ -22,6 +22,7 @@ class PfpEffects: """Applies the given effect to the image passed to it.""" im = Image.open(BytesIO(image_bytes)) im = im.convert("RGBA") + im = im.resize((1024, 1024)) im = effect(im, *args) bufferedio = BytesIO() @@ -74,7 +75,6 @@ class PfpEffects: @staticmethod 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) ring = Image.open(Path(f"bot/resources/pride/flags/{flag}.png")).resize((1024, 1024)) @@ -97,6 +97,17 @@ class PfpEffects: return image.quantize() @staticmethod + def flip_effect(image: Image.Image) -> Image.Image: + """ + Flips the image horizontally. + + This is done by just using ImageOps.mirror(). + """ + image = ImageOps.mirror(image) + + return image + + @staticmethod def easterify_effect(image: Image.Image, overlay_image: t.Optional[Image.Image] = None) -> Image.Image: """ Applies the easter effect to the given image. @@ -272,16 +283,14 @@ class PfpEffects: return new_image @staticmethod - def mosaic_effect(img_bytes: bytes, squares: int, file_name: str) -> discord.File: - """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)) + def mosaic_effect(image: Image.Image, squares: int) -> Image.Image: + """ + Applies a mosaic effect to the given image. - img_squares = PfpEffects.split_image(avatar, squares) + The "squares" argument specifies the number of squares to split + the image into. This should be a square number. + """ + img_squares = PfpEffects.split_image(image, 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) + return new_img diff --git a/bot/exts/evergreen/avatar_modification/avatar_modify.py b/bot/exts/evergreen/avatar_modification/avatar_modify.py index bd324f67..7b4ae9c7 100644 --- a/bot/exts/evergreen/avatar_modification/avatar_modify.py +++ b/bot/exts/evergreen/avatar_modification/avatar_modify.py @@ -120,6 +120,43 @@ class AvatarModify(commands.Cog): await ctx.send(embed=embed, file=file) + @avatar_modify.command(name="reverse", root_aliases=("reverse",)) + async def reverse(self, ctx: commands.Context, *, text: t.Optional[str]) -> None: + """ + Reverses the sent text. + + If no text is provided, the user's profile picture will be reversed. + """ + if text: + await ctx.send(f"> {text[::-1]}", allowed_mentions=discord.AllowedMentions.none()) + return + + async with ctx.typing(): + 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 user.avatar_url_as(size=1024).read() + filename = file_safe_name("reverse_avatar", ctx.author.display_name) + + file = await in_executor( + PfpEffects.apply_effect, + image_bytes, + PfpEffects.flip_effect, + filename + ) + + embed = discord.Embed( + title="Your reversed avatar.", + description="Here is your reversed avatar. I think it is a spitting image of you." + ) + + embed.set_image(url=f"attachment://{filename}") + embed.set_footer(text=f"Made by {ctx.author.display_name}.", icon_url=user.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: """ @@ -301,10 +338,11 @@ class AvatarModify(commands.Cog): img_bytes = await user.avatar_url_as(size=1024).read() file = await in_executor( - PfpEffects.mosaic_effect, + PfpEffects.apply_effect, img_bytes, + PfpEffects.mosaic_effect, + file_name, squares, - file_name ) if squares == 1: diff --git a/bot/exts/evergreen/help.py b/bot/exts/evergreen/help.py index 3c9ba4d2..bfb5db17 100644 --- a/bot/exts/evergreen/help.py +++ b/bot/exts/evergreen/help.py @@ -8,7 +8,7 @@ from typing import List, NamedTuple, Union from discord import Colour, Embed, HTTPException, Message, Reaction, User from discord.ext import commands from discord.ext.commands import CheckFailure, Cog as DiscordCog, Command, Context -from fuzzywuzzy import fuzz, process +from rapidfuzz import process from bot import constants from bot.bot import Bot @@ -159,7 +159,7 @@ class HelpSession: # Combine command and cog names choices = list(self._bot.all_commands) + list(self._bot.cogs) - result = process.extractBests(query, choices, scorer=fuzz.ratio, score_cutoff=90) + result = process.extract(query, choices, score_cutoff=90) raise HelpQueryNotFound(f'Query "{query}" not found.', dict(result)) diff --git a/bot/exts/evergreen/snakes/_converter.py b/bot/exts/evergreen/snakes/_converter.py index 26bde611..c8d1909b 100644 --- a/bot/exts/evergreen/snakes/_converter.py +++ b/bot/exts/evergreen/snakes/_converter.py @@ -5,7 +5,7 @@ from typing import Iterable, List import discord from discord.ext.commands import Context, Converter -from fuzzywuzzy import fuzz +from rapidfuzz import fuzz from bot.exts.evergreen.snakes._utils import SNAKE_RESOURCES from bot.utils import disambiguate diff --git a/bot/exts/evergreen/trivia_quiz.py b/bot/exts/evergreen/trivia_quiz.py index 28924aed..bc25cbf7 100644 --- a/bot/exts/evergreen/trivia_quiz.py +++ b/bot/exts/evergreen/trivia_quiz.py @@ -9,7 +9,7 @@ from typing import Callable, List, Optional import discord from discord.ext import commands -from fuzzywuzzy import fuzz +from rapidfuzz import fuzz from bot.bot import Bot from bot.constants import Colours, NEGATIVE_REPLIES, Roles diff --git a/bot/exts/pride/pride_leader.py b/bot/exts/pride/pride_leader.py index c3426ad1..8e88183b 100644 --- a/bot/exts/pride/pride_leader.py +++ b/bot/exts/pride/pride_leader.py @@ -6,7 +6,7 @@ from typing import Optional import discord from discord.ext import commands -from fuzzywuzzy import fuzz +from rapidfuzz import fuzz from bot import bot from bot import constants diff --git a/bot/exts/valentines/lovecalculator.py b/bot/exts/valentines/lovecalculator.py index b10b7bca..1cb10e64 100644 --- a/bot/exts/valentines/lovecalculator.py +++ b/bot/exts/valentines/lovecalculator.py @@ -4,7 +4,7 @@ import json import logging import random from pathlib import Path -from typing import Coroutine, Union +from typing import Coroutine, Optional import discord from discord import Member @@ -12,6 +12,8 @@ from discord.ext import commands from discord.ext.commands import BadArgument, Cog, clean_content from bot.bot import Bot +from bot.constants import Channels, Client, Lovefest, Month +from bot.utils.decorators import in_month log = logging.getLogger(__name__) @@ -22,45 +24,45 @@ LOVE_DATA = sorted((int(key), value) for key, value in LOVE_DATA.items()) class LoveCalculator(Cog): """A cog for calculating the love between two people.""" + @in_month(Month.FEBRUARY) @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: + async def love(self, ctx: commands.Context, who: Member, whom: Optional[Member] = None) -> None: """ Tells you how much the two love each other. - This command accepts users or arbitrary strings as arguments. - Users are converted from: + This command requires at least one member as input, if two are given love will be calculated between + those two users, if only one is given, the second member is asusmed to be the invoker. + Members are converted from: - User ID - Mention - name#discrim - name - nickname - Any two arguments will always yield the same result, though the order of arguments matters: - Running .love joseph erlang will always yield the same result. - Running .love erlang joseph won't yield the same result as .love joseph erlang - - If you want to use multiple words for one argument, you must include quotes. - .love "Zes Vappa" "morning coffee" + Any two arguments will always yield the same result, regardless of the order of arguments: + Running .love @joe#6000 @chrisjl#2655 will always yield the same result. + Running .love @chrisjl#2655 @joe#6000 will yield the same result as before. """ + if ( + Lovefest.role_id not in [role.id for role in who.roles] + or (whom is not None and Lovefest.role_id not in [role.id for role in whom.roles]) + ): + raise BadArgument( + "This command can only be ran against members with the lovefest role! " + "This role be can assigned by running " + f"`{Client.prefix}lovefest sub` in <#{Channels.community_bot_commands}>." + ) + if whom is None: whom = ctx.author - def normalize(arg: Union[Member, str]) -> Coroutine: - if isinstance(arg, Member): - # If we are given a member, return name#discrim without any extra changes - arg = str(arg) - else: - # Otherwise normalise case and remove any leading/trailing whitespace - arg = arg.strip().title() + def normalize(arg: Member) -> Coroutine: # This has to be done manually to be applied to usernames - return clean_content(escape_markdown=True).convert(ctx, arg) - - who, whom = [await normalize(arg) for arg in (who, whom)] + return clean_content(escape_markdown=True).convert(ctx, str(arg)) - # Make sure user didn't provide something silly such as 10 spaces - if not (who and whom): - raise BadArgument("Arguments must be non-empty strings.") + # Sort to ensure same result for same input, regardless of order + who, whom = sorted([await normalize(arg) for arg in (who, whom)]) # Hash inputs to guarantee consistent results (hashing algorithm choice arbitrary) # @@ -87,6 +89,7 @@ class LoveCalculator(Cog): name="A letter from Dr. Love:", value=data["text"] ) + embed.set_footer(text=f"You can unsubscribe from lovefest by using {Client.prefix}lovefest unsub") await ctx.send(embed=embed) diff --git a/bot/resources/evergreen/py_topics.yaml b/bot/resources/evergreen/py_topics.yaml index 6b7e0206..a3fb2ccc 100644 --- a/bot/resources/evergreen/py_topics.yaml +++ b/bot/resources/evergreen/py_topics.yaml @@ -23,6 +23,19 @@ - When you were first learning, what is a resource you wish you had? - What is something you know now, that you wish you knew when starting out? - What is something simple that you still error on today? + - What do you plan on eventually achieving with Python? + - Is Python your first programming language? If not, what is it? + - What's your favourite aspect of Python development? (Backend, frontend, game dev, machine learning, ai, etc.) + - In what ways has Python Discord helped you with Python? + - Are you currently using Python professionally, for education, or as a hobby? + - What is your process when you decide to start a project in Python? + - Have you ever been unable to finish a Python project? What is it and why? + - How often do you program in Python? + - How would you learn a new library if needed to do so? + - Have you ever worked with a microcontroller or anything physical with Python before? + - How good would you say you are at Python so far? Beginner, intermediate, or advanced? + - Have you ever tried making your own programming language? + - Has a recently discovered Python module changed your general use of Python? # algos-and-data-structs 650401909852864553: @@ -52,7 +65,7 @@ - What unique features does your bot contain, if any? - What commands/features are you proud of making? - What feature would you be the most interested in making? - - What feature would you like to see added to the library? what feature in the library do you think is redundant? + - What feature would you like to see added to the library? What feature in the library do you think is redundant? - Do you think there's a way in which Discord could handle bots better? - What's one feature you wish more developers had in their bots? diff --git a/bot/utils/pagination.py b/bot/utils/pagination.py index d9c0862a..b1062c09 100644 --- a/bot/utils/pagination.py +++ b/bot/utils/pagination.py @@ -20,7 +20,7 @@ PAGINATION_EMOJI = (FIRST_EMOJI, LEFT_EMOJI, RIGHT_EMOJI, LAST_EMOJI, DELETE_EMO log = logging.getLogger(__name__) -class EmptyPaginatorEmbed(Exception): +class EmptyPaginatorEmbedError(Exception): """Base Exception class for an empty paginator embed.""" @@ -141,7 +141,7 @@ class LinePaginator(Paginator): if not lines: if exception_on_empty_embed: log.exception("Pagination asked for empty lines iterable") - raise EmptyPaginatorEmbed("No lines to paginate") + raise EmptyPaginatorEmbedError("No lines to paginate") log.debug("No lines to add to paginator, adding '(nothing to display)' message") lines.append("(nothing to display)") @@ -349,7 +349,7 @@ class ImagePaginator(Paginator): if not pages: if exception_on_empty_embed: log.exception("Pagination asked for empty image list") - raise EmptyPaginatorEmbed("No images to paginate") + raise EmptyPaginatorEmbedError("No images to paginate") log.debug("No images to add to paginator, adding '(no images to display)' message") pages.append(("(no images to display)", "")) |