aboutsummaryrefslogtreecommitdiffstats
path: root/bot
diff options
context:
space:
mode:
authorGravatar Xithrius <[email protected]>2021-08-23 02:06:27 -0700
committerGravatar GitHub <[email protected]>2021-08-23 02:06:27 -0700
commit1ee1f76581aa3549d65bf7dfbe38dfa55b1d4549 (patch)
tree73d1b3d2a2c82733b4e0ec642249b6a0f0f96d2e /bot
parentImprove overall code transparency in the Wikipedia Cog (diff)
parentMerge 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.py8
-rw-r--r--bot/exts/evergreen/avatar_modification/_effects.py31
-rw-r--r--bot/exts/evergreen/avatar_modification/avatar_modify.py42
-rw-r--r--bot/exts/evergreen/help.py4
-rw-r--r--bot/exts/evergreen/snakes/_converter.py2
-rw-r--r--bot/exts/evergreen/trivia_quiz.py2
-rw-r--r--bot/exts/pride/pride_leader.py2
-rw-r--r--bot/exts/valentines/lovecalculator.py49
-rw-r--r--bot/resources/evergreen/py_topics.yaml15
-rw-r--r--bot/utils/pagination.py6
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)", ""))