aboutsummaryrefslogtreecommitdiffstats
path: root/bot
diff options
context:
space:
mode:
authorGravatar SomeHybrid <[email protected]>2023-08-27 19:51:22 +0700
committerGravatar GitHub <[email protected]>2023-08-27 19:51:22 +0700
commitbda41cb204c3303a74ab199448d391120155286b (patch)
treeb8b5bb0ff6cd21303928b74d81b986281a74784d /bot
parentChanged cooldown to 5s, removed description if abstract not found (diff)
parentholidayreact: add alternate spellings to February (#1214) (diff)
Merge branch 'python-discord:main' into main
Diffstat (limited to 'bot')
-rw-r--r--bot/bot.py2
-rw-r--r--bot/constants.py33
-rw-r--r--bot/exts/core/extensions.py2
-rw-r--r--bot/exts/core/internal_eval/_helpers.py2
-rw-r--r--bot/exts/events/hacktoberfest/hacktoberstats.py4
-rw-r--r--bot/exts/events/trivianight/_game.py4
-rw-r--r--bot/exts/events/trivianight/_scoreboard.py6
-rw-r--r--bot/exts/events/trivianight/trivianight.py2
-rw-r--r--bot/exts/fun/snakes/_snakes_cog.py40
-rw-r--r--bot/exts/fun/trivia_quiz.py254
-rw-r--r--bot/exts/holidays/halloween/monstersurvey.py6
-rw-r--r--bot/exts/holidays/holidayreact.py2
-rw-r--r--bot/exts/holidays/pride/pride_anthem.py2
-rw-r--r--bot/exts/holidays/pride/pride_facts.py2
-rw-r--r--bot/exts/holidays/pride/pride_leader.py2
-rw-r--r--bot/exts/holidays/valentines/be_my_valentine.py4
-rw-r--r--bot/exts/holidays/valentines/myvalenstate.py2
-rw-r--r--bot/exts/utilities/challenges.py2
-rw-r--r--bot/exts/utilities/colour.py2
-rw-r--r--bot/exts/utilities/emoji.py2
-rw-r--r--bot/exts/utilities/reddit.py31
-rw-r--r--bot/exts/utilities/wolfram.py4
-rw-r--r--bot/utils/__init__.py51
-rw-r--r--bot/utils/pagination.py8
24 files changed, 223 insertions, 246 deletions
diff --git a/bot/bot.py b/bot/bot.py
index 0b20ac54..c81bfc5f 100644
--- a/bot/bot.py
+++ b/bot/bot.py
@@ -39,7 +39,7 @@ class Bot(BotBase):
else:
await super().on_command_error(context, exception)
- async def log_to_dev_log(self, title: str, details: str = None, *, icon: str = None) -> None:
+ async def log_to_dev_log(self, title: str, details: str | None = None, *, icon: str | None = None) -> None:
"""Send an embed message to the dev-log channel."""
devlog = self.get_channel(constants.Channels.devlog)
diff --git a/bot/constants.py b/bot/constants.py
index cd866a0b..20953771 100644
--- a/bot/constants.py
+++ b/bot/constants.py
@@ -1,6 +1,7 @@
import enum
import logging
from os import environ
+from types import MappingProxyType
from pydantic import BaseSettings, SecretStr
@@ -129,7 +130,7 @@ class Colours:
grass_green = 0x66FF00
gold = 0xE6C200
- easter_like_colours = [
+ easter_like_colours = (
(255, 247, 0),
(255, 255, 224),
(0, 255, 127),
@@ -143,7 +144,7 @@ class Colours:
(135, 206, 235),
(0, 204, 204),
(64, 224, 208),
- ]
+ )
class Emojis:
@@ -177,17 +178,19 @@ class Emojis:
pull_request_draft = "<:PRDraft:852596025045680218>"
pull_request_merged = "<:PRMerged:852596100301193227>"
- number_emojis = {
- 1: "\u0031\ufe0f\u20e3",
- 2: "\u0032\ufe0f\u20e3",
- 3: "\u0033\ufe0f\u20e3",
- 4: "\u0034\ufe0f\u20e3",
- 5: "\u0035\ufe0f\u20e3",
- 6: "\u0036\ufe0f\u20e3",
- 7: "\u0037\ufe0f\u20e3",
- 8: "\u0038\ufe0f\u20e3",
- 9: "\u0039\ufe0f\u20e3"
- }
+ number_emojis = MappingProxyType(
+ {
+ 1: "\u0031\ufe0f\u20e3",
+ 2: "\u0032\ufe0f\u20e3",
+ 3: "\u0033\ufe0f\u20e3",
+ 4: "\u0034\ufe0f\u20e3",
+ 5: "\u0035\ufe0f\u20e3",
+ 6: "\u0036\ufe0f\u20e3",
+ 7: "\u0037\ufe0f\u20e3",
+ 8: "\u0038\ufe0f\u20e3",
+ 9: "\u0039\ufe0f\u20e3"
+ }
+ )
confirmation = "\u2705"
decline = "\u274c"
@@ -314,11 +317,11 @@ Redis = _Redis()
class _Reddit(EnvConfig):
EnvConfig.Config.env_prefix = "reddit_"
- subreddits = ["r/Python"]
+ subreddits: tuple[str, ...] = ("r/Python",)
client_id: SecretStr = ""
secret: SecretStr = ""
- webhook = 635408384794951680
+ webhook: int = 635408384794951680
Reddit = _Reddit()
diff --git a/bot/exts/core/extensions.py b/bot/exts/core/extensions.py
index 87ef2ed1..f96b0292 100644
--- a/bot/exts/core/extensions.py
+++ b/bot/exts/core/extensions.py
@@ -153,7 +153,7 @@ class Extensions(commands.Cog):
embed = Embed(colour=Colour.og_blurple())
embed.set_author(
name="Extensions List",
- url=Client.github_bot_repo,
+ url=Client.github_repo,
icon_url=str(self.bot.user.display_avatar.url)
)
diff --git a/bot/exts/core/internal_eval/_helpers.py b/bot/exts/core/internal_eval/_helpers.py
index 34ef7fef..30c4b11c 100644
--- a/bot/exts/core/internal_eval/_helpers.py
+++ b/bot/exts/core/internal_eval/_helpers.py
@@ -227,7 +227,7 @@ class CaptureLastExpression(ast.NodeTransformer):
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]
+ right_hand_side = next(iter(ast.iter_child_nodes(node)))
assignment = ast.Assign(
targets=[ast.Name(id="_value_last_expression", ctx=ast.Store())],
diff --git a/bot/exts/events/hacktoberfest/hacktoberstats.py b/bot/exts/events/hacktoberfest/hacktoberstats.py
index c7fd3601..04dc4aae 100644
--- a/bot/exts/events/hacktoberfest/hacktoberstats.py
+++ b/bot/exts/events/hacktoberfest/hacktoberstats.py
@@ -43,7 +43,7 @@ class HacktoberStats(commands.Cog):
@in_month(Month.SEPTEMBER, Month.OCTOBER, Month.NOVEMBER)
@commands.group(name="hacktoberstats", aliases=("hackstats",), invoke_without_command=True)
- async def hacktoberstats_group(self, ctx: commands.Context, github_username: str = None) -> None:
+ async def hacktoberstats_group(self, ctx: commands.Context, github_username: str | None = None) -> None:
"""
Display an embed for a user's Hacktoberfest contributions.
@@ -70,7 +70,7 @@ class HacktoberStats(commands.Cog):
@in_month(Month.SEPTEMBER, Month.OCTOBER, Month.NOVEMBER)
@hacktoberstats_group.command(name="link")
- async def link_user(self, ctx: commands.Context, github_username: str = None) -> None:
+ async def link_user(self, ctx: commands.Context, github_username: str | None = None) -> None:
"""
Link the invoking user's Github github_username to their Discord ID.
diff --git a/bot/exts/events/trivianight/_game.py b/bot/exts/events/trivianight/_game.py
index 15126f60..0568ce81 100644
--- a/bot/exts/events/trivianight/_game.py
+++ b/bot/exts/events/trivianight/_game.py
@@ -134,7 +134,7 @@ class TriviaNightGame:
def __iter__(self) -> Iterable[Question]:
return iter(self._questions)
- def next_question(self, number: str = None) -> Question:
+ def next_question(self, number: str | None = None) -> Question:
"""
Consume one random question from the trivia night game.
@@ -145,7 +145,7 @@ class TriviaNightGame:
if number is not None:
try:
- question = [q for q in self._all_questions if q.number == int(number)][0]
+ question = next(q for q in self._all_questions if q.number == int(number))
except IndexError:
raise ValueError(f"Question number {number} does not exist.")
elif len(self._questions) == 0:
diff --git a/bot/exts/events/trivianight/_scoreboard.py b/bot/exts/events/trivianight/_scoreboard.py
index bd61be3d..c2b39d67 100644
--- a/bot/exts/events/trivianight/_scoreboard.py
+++ b/bot/exts/events/trivianight/_scoreboard.py
@@ -155,18 +155,18 @@ class Scoreboard:
self._points = {}
self._speed = {}
- def assign_points(self, user_id: int, *, points: int = None, speed: float = None) -> None:
+ def assign_points(self, user_id: int, *, points: int | None = None, speed: float | None = None) -> None:
"""
Assign points or deduct points to/from a certain user.
This method should be called once the question has finished and all answers have been registered.
"""
- if points is not None and user_id not in self._points.keys():
+ if points is not None and user_id not in self._points:
self._points[user_id] = points
elif points is not None:
self._points[user_id] += points
- if speed is not None and user_id not in self._speed.keys():
+ if speed is not None and user_id not in self._speed:
self._speed[user_id] = [1, speed]
elif speed is not None:
self._speed[user_id] = [
diff --git a/bot/exts/events/trivianight/trivianight.py b/bot/exts/events/trivianight/trivianight.py
index e0db45d8..e47d1d83 100644
--- a/bot/exts/events/trivianight/trivianight.py
+++ b/bot/exts/events/trivianight/trivianight.py
@@ -107,7 +107,7 @@ class TriviaNightCog(commands.Cog):
@trivianight.command(aliases=("next",))
@commands.has_any_role(*TRIVIA_NIGHT_ROLES)
- async def question(self, ctx: commands.Context, question_number: str = None) -> None:
+ async def question(self, ctx: commands.Context, question_number: str | None = None) -> None:
"""
Gets a random question from the unanswered question list and lets the user(s) choose the answer.
diff --git a/bot/exts/fun/snakes/_snakes_cog.py b/bot/exts/fun/snakes/_snakes_cog.py
index eca462c6..82496629 100644
--- a/bot/exts/fun/snakes/_snakes_cog.py
+++ b/bot/exts/fun/snakes/_snakes_cog.py
@@ -11,8 +11,8 @@ from functools import partial
from io import BytesIO
from typing import Any
-import async_timeout
from PIL import Image, ImageDraw, ImageFont
+from aiohttp import ClientTimeout
from discord import Colour, Embed, File, Member, Message, Reaction
from discord.errors import HTTPException
from discord.ext.commands import Cog, CommandError, Context, bot_has_permissions, group
@@ -241,8 +241,11 @@ 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"])
+
+ _left, top, _right, bottom = CARD["font"].getbbox(line)
+ # Height of the text + 4px spacing
+ offset += bottom - top + 4
# Get the image contents as a BufferIO object
buffer = BytesIO()
@@ -279,8 +282,7 @@ class Snakes(Cog):
if params is None:
params = {}
- async with async_timeout.timeout(10):
- async with self.bot.http_session.get(url, params=params) as response:
+ async with self.bot.http_session.get(url, params=params, timeout=ClientTimeout(total=10)) as response:
return await response.json()
def _get_random_long_message(self, messages: list[str], retries: int = 10) -> str:
@@ -846,7 +848,7 @@ class Snakes(Cog):
await self._validate_answer(ctx, quiz, answer, options)
@snakes_group.command(name="name", aliases=("name_gen",))
- async def name_command(self, ctx: Context, *, name: str = None) -> None:
+ async def name_command(self, ctx: Context, *, name: str | None = None) -> None:
"""
Snakifies a username.
@@ -987,23 +989,29 @@ class Snakes(Cog):
"""
# Get the snake data we need
if not name:
- name_obj = await self._get_snake_name()
- name = name_obj["scientific"]
- content = await self._get_snek(name)
+ for _ in range(3):
+ name_obj = await self._get_snake_name()
+ name = name_obj["scientific"]
+ content = await self._get_snek(name)
+ if len(content["image_list"]) > 0:
+ break
elif isinstance(name, dict):
content = name
-
else:
content = await self._get_snek(name)
+ try:
+ image_url = content["image_list"][0]
+ except IndexError:
+ await ctx.send("No images found for this snake.")
+ return
+
# Make the card
async with ctx.typing():
-
stream = BytesIO()
- async with async_timeout.timeout(10):
- async with self.bot.http_session.get(content["image_list"][0]) as response:
- stream.write(await response.read())
+ async with self.bot.http_session.get(image_url, timeout=ClientTimeout(total=10)) as response:
+ stream.write(await response.read())
stream.seek(0)
@@ -1033,7 +1041,7 @@ class Snakes(Cog):
await ctx.send(embed=embed)
@snakes_group.command(name="snakify")
- async def snakify_command(self, ctx: Context, *, message: str = None) -> None:
+ async def snakify_command(self, ctx: Context, *, message: str | None = None) -> None:
"""
How would I talk if I were a snake?
@@ -1068,7 +1076,7 @@ class Snakes(Cog):
await ctx.send(embed=embed)
@snakes_group.command(name="video", aliases=("get_video",))
- async def video_command(self, ctx: Context, *, search: str = None) -> None:
+ async def video_command(self, ctx: Context, *, search: str | None = None) -> None:
"""
Gets a YouTube video about snakes.
diff --git a/bot/exts/fun/trivia_quiz.py b/bot/exts/fun/trivia_quiz.py
index 28cd4657..b96007b5 100644
--- a/bot/exts/fun/trivia_quiz.py
+++ b/bot/exts/fun/trivia_quiz.py
@@ -40,6 +40,41 @@ WIKI_FEED_API_URL = "https://en.wikipedia.org/api/rest_v1/feed/featured/{date}"
TRIVIA_QUIZ_ICON = (
"https://raw.githubusercontent.com/python-discord/branding/main/icons/trivia_quiz/trivia-quiz-dist.png"
)
+N_PREFIX_STARTS_AT = 5
+N_PREFIXES = [
+ "penta", "hexa", "hepta", "octa", "nona",
+ "deca", "hendeca", "dodeca", "trideca", "tetradeca",
+]
+
+PLANETS = [
+ ("1st", "Mercury"),
+ ("2nd", "Venus"),
+ ("3rd", "Earth"),
+ ("4th", "Mars"),
+ ("5th", "Jupiter"),
+ ("6th", "Saturn"),
+ ("7th", "Uranus"),
+ ("8th", "Neptune"),
+]
+
+TAXONOMIC_HIERARCHY = [
+ "species", "genus", "family", "order",
+ "class", "phylum", "kingdom", "domain",
+]
+
+UNITS_TO_BASE_UNITS = {
+ "hertz": ("(unit of frequency)", "s^-1"),
+ "newton": ("(unit of force)", "m*kg*s^-2"),
+ "pascal": ("(unit of pressure & stress)", "m^-1*kg*s^-2"),
+ "joule": ("(unit of energy & quantity of heat)", "m^2*kg*s^-2"),
+ "watt": ("(unit of power)", "m^2*kg*s^-3"),
+ "coulomb": ("(unit of electric charge & quantity of electricity)", "s*A"),
+ "volt": ("(unit of voltage & electromotive force)", "m^2*kg*s^-3*A^-1"),
+ "farad": ("(unit of capacitance)", "m^-2*kg^-1*s^4*A^2"),
+ "ohm": ("(unit of electric resistance)", "m^2*kg*s^-3*A^-2"),
+ "weber": ("(unit of magnetic flux)", "m^2*kg*s^-2*A^-1"),
+ "tesla": ("(unit of magnetic flux density)", "kg*s^-2*A^-1"),
+}
@dataclass(frozen=True)
@@ -51,163 +86,117 @@ class QuizEntry:
var_tol: int
-class DynamicQuestionGen:
- """Class that contains functions to generate math/science questions for TriviaQuiz Cog."""
-
- N_PREFIX_STARTS_AT = 5
- N_PREFIXES = [
- "penta", "hexa", "hepta", "octa", "nona",
- "deca", "hendeca", "dodeca", "trideca", "tetradeca",
- ]
-
- PLANETS = [
- ("1st", "Mercury"),
- ("2nd", "Venus"),
- ("3rd", "Earth"),
- ("4th", "Mars"),
- ("5th", "Jupiter"),
- ("6th", "Saturn"),
- ("7th", "Uranus"),
- ("8th", "Neptune"),
- ]
-
- TAXONOMIC_HIERARCHY = [
- "species", "genus", "family", "order",
- "class", "phylum", "kingdom", "domain",
- ]
-
- UNITS_TO_BASE_UNITS = {
- "hertz": ("(unit of frequency)", "s^-1"),
- "newton": ("(unit of force)", "m*kg*s^-2"),
- "pascal": ("(unit of pressure & stress)", "m^-1*kg*s^-2"),
- "joule": ("(unit of energy & quantity of heat)", "m^2*kg*s^-2"),
- "watt": ("(unit of power)", "m^2*kg*s^-3"),
- "coulomb": ("(unit of electric charge & quantity of electricity)", "s*A"),
- "volt": ("(unit of voltage & electromotive force)", "m^2*kg*s^-3*A^-1"),
- "farad": ("(unit of capacitance)", "m^-2*kg^-1*s^4*A^2"),
- "ohm": ("(unit of electric resistance)", "m^2*kg*s^-3*A^-2"),
- "weber": ("(unit of magnetic flux)", "m^2*kg*s^-2*A^-1"),
- "tesla": ("(unit of magnetic flux density)", "kg*s^-2*A^-1"),
- }
-
- @classmethod
- def linear_system(cls, q_format: str, a_format: str) -> QuizEntry:
- """Generate a system of linear equations with two unknowns."""
- x, y = random.randint(2, 5), random.randint(2, 5)
- answer = a_format.format(x, y)
-
- coeffs = random.sample(range(1, 6), 4)
-
- question = q_format.format(
- coeffs[0],
- coeffs[1],
- coeffs[0] * x + coeffs[1] * y,
- coeffs[2],
- coeffs[3],
- coeffs[2] * x + coeffs[3] * y,
- )
+def linear_system(q_format: str, a_format: str) -> QuizEntry:
+ """Generate a system of linear equations with two unknowns."""
+ x, y = random.randint(2, 5), random.randint(2, 5)
+ answer = a_format.format(x, y)
- return QuizEntry(question, [answer], DYNAMICALLY_GEN_VARIATION_TOLERANCE)
+ coeffs = random.sample(range(1, 6), 4)
- @classmethod
- def mod_arith(cls, q_format: str, a_format: str) -> QuizEntry:
- """Generate a basic modular arithmetic question."""
- quotient, m, b = random.randint(30, 40), random.randint(10, 20), random.randint(200, 350)
- ans = random.randint(0, 9) # max remainder is 9, since the minimum modulus is 10
- a = quotient * m + ans - b
+ question = q_format.format(
+ coeffs[0],
+ coeffs[1],
+ coeffs[0] * x + coeffs[1] * y,
+ coeffs[2],
+ coeffs[3],
+ coeffs[2] * x + coeffs[3] * y,
+ )
- question = q_format.format(a, b, m)
- answer = a_format.format(ans)
+ return QuizEntry(question, [answer], DYNAMICALLY_GEN_VARIATION_TOLERANCE)
- return QuizEntry(question, [answer], DYNAMICALLY_GEN_VARIATION_TOLERANCE)
+def mod_arith(q_format: str, a_format: str) -> QuizEntry:
+ """Generate a basic modular arithmetic question."""
+ quotient, m, b = random.randint(30, 40), random.randint(10, 20), random.randint(200, 350)
+ ans = random.randint(0, 9) # max remainder is 9, since the minimum modulus is 10
+ a = quotient * m + ans - b
- @classmethod
- def ngonal_prism(cls, q_format: str, a_format: str) -> QuizEntry:
- """Generate a question regarding vertices on n-gonal prisms."""
- n = random.randint(0, len(cls.N_PREFIXES) - 1)
+ question = q_format.format(a, b, m)
+ answer = a_format.format(ans)
- question = q_format.format(cls.N_PREFIXES[n])
- answer = a_format.format((n + cls.N_PREFIX_STARTS_AT) * 2)
+ return QuizEntry(question, [answer], DYNAMICALLY_GEN_VARIATION_TOLERANCE)
- return QuizEntry(question, [answer], DYNAMICALLY_GEN_VARIATION_TOLERANCE)
+def ngonal_prism(q_format: str, a_format: str) -> QuizEntry:
+ """Generate a question regarding vertices on n-gonal prisms."""
+ n = random.randint(0, len(N_PREFIXES) - 1)
- @classmethod
- def imag_sqrt(cls, q_format: str, a_format: str) -> QuizEntry:
- """Generate a negative square root question."""
- ans_coeff = random.randint(3, 10)
+ question = q_format.format(N_PREFIXES[n])
+ answer = a_format.format((n + N_PREFIX_STARTS_AT) * 2)
- question = q_format.format(ans_coeff ** 2)
- answer = a_format.format(ans_coeff)
+ return QuizEntry(question, [answer], DYNAMICALLY_GEN_VARIATION_TOLERANCE)
- return QuizEntry(question, [answer], DYNAMICALLY_GEN_VARIATION_TOLERANCE)
+def imag_sqrt(q_format: str, a_format: str) -> QuizEntry:
+ """Generate a negative square root question."""
+ ans_coeff = random.randint(3, 10)
- @classmethod
- def binary_calc(cls, q_format: str, a_format: str) -> QuizEntry:
- """Generate a binary calculation question."""
- a = random.randint(15, 20)
- b = random.randint(10, a)
- oper = random.choice(
- (
- ("+", operator.add),
- ("-", operator.sub),
- ("*", operator.mul),
- )
+ question = q_format.format(ans_coeff ** 2)
+ answer = a_format.format(ans_coeff)
+
+ return QuizEntry(question, [answer], DYNAMICALLY_GEN_VARIATION_TOLERANCE)
+
+def binary_calc(q_format: str, a_format: str) -> QuizEntry:
+ """Generate a binary calculation question."""
+ a = random.randint(15, 20)
+ b = random.randint(10, a)
+ oper = random.choice(
+ (
+ ("+", operator.add),
+ ("-", operator.sub),
+ ("*", operator.mul),
)
+ )
- # if the operator is multiplication, lower the values of the two operands to make it easier
- if oper[0] == "*":
- a -= 5
- b -= 5
+ # if the operator is multiplication, lower the values of the two operands to make it easier
+ if oper[0] == "*":
+ a -= 5
+ b -= 5
- question = q_format.format(a, oper[0], b)
- answer = a_format.format(oper[1](a, b))
+ question = q_format.format(a, oper[0], b)
+ answer = a_format.format(oper[1](a, b))
- return QuizEntry(question, [answer], DYNAMICALLY_GEN_VARIATION_TOLERANCE)
+ return QuizEntry(question, [answer], DYNAMICALLY_GEN_VARIATION_TOLERANCE)
- @classmethod
- def solar_system(cls, q_format: str, a_format: str) -> QuizEntry:
- """Generate a question on the planets of the Solar System."""
- planet = random.choice(cls.PLANETS)
+def solar_system(q_format: str, a_format: str) -> QuizEntry:
+ """Generate a question on the planets of the Solar System."""
+ planet = random.choice(PLANETS)
- question = q_format.format(planet[0])
- answer = a_format.format(planet[1])
+ question = q_format.format(planet[0])
+ answer = a_format.format(planet[1])
- return QuizEntry(question, [answer], DYNAMICALLY_GEN_VARIATION_TOLERANCE)
+ return QuizEntry(question, [answer], DYNAMICALLY_GEN_VARIATION_TOLERANCE)
- @classmethod
- def taxonomic_rank(cls, q_format: str, a_format: str) -> QuizEntry:
- """Generate a question on taxonomic classification."""
- level = random.randint(0, len(cls.TAXONOMIC_HIERARCHY) - 2)
+def taxonomic_rank(q_format: str, a_format: str) -> QuizEntry:
+ """Generate a question on taxonomic classification."""
+ level = random.randint(0, len(TAXONOMIC_HIERARCHY) - 2)
- question = q_format.format(cls.TAXONOMIC_HIERARCHY[level])
- answer = a_format.format(cls.TAXONOMIC_HIERARCHY[level + 1])
+ question = q_format.format(TAXONOMIC_HIERARCHY[level])
+ answer = a_format.format(TAXONOMIC_HIERARCHY[level + 1])
- return QuizEntry(question, [answer], DYNAMICALLY_GEN_VARIATION_TOLERANCE)
+ return QuizEntry(question, [answer], DYNAMICALLY_GEN_VARIATION_TOLERANCE)
- @classmethod
- def base_units_convert(cls, q_format: str, a_format: str) -> QuizEntry:
- """Generate a SI base units conversion question."""
- unit = random.choice(list(cls.UNITS_TO_BASE_UNITS))
- question = q_format.format(
- unit + " " + cls.UNITS_TO_BASE_UNITS[unit][0]
- )
- answer = a_format.format(
- cls.UNITS_TO_BASE_UNITS[unit][1]
- )
+def base_units_convert(q_format: str, a_format: str) -> QuizEntry:
+ """Generate a SI base units conversion question."""
+ unit = random.choice(list(UNITS_TO_BASE_UNITS))
+
+ question = q_format.format(
+ unit + " " + UNITS_TO_BASE_UNITS[unit][0]
+ )
+ answer = a_format.format(
+ UNITS_TO_BASE_UNITS[unit][1]
+ )
- return QuizEntry(question, [answer], DYNAMICALLY_GEN_VARIATION_TOLERANCE)
+ return QuizEntry(question, [answer], DYNAMICALLY_GEN_VARIATION_TOLERANCE)
DYNAMIC_QUESTIONS_FORMAT_FUNCS = {
- 201: DynamicQuestionGen.linear_system,
- 202: DynamicQuestionGen.mod_arith,
- 203: DynamicQuestionGen.ngonal_prism,
- 204: DynamicQuestionGen.imag_sqrt,
- 205: DynamicQuestionGen.binary_calc,
- 301: DynamicQuestionGen.solar_system,
- 302: DynamicQuestionGen.taxonomic_rank,
- 303: DynamicQuestionGen.base_units_convert,
+ 201: linear_system,
+ 202: mod_arith,
+ 203: ngonal_prism,
+ 204: imag_sqrt,
+ 205: binary_calc,
+ 301: solar_system,
+ 302: taxonomic_rank,
+ 303: base_units_convert,
}
@@ -361,7 +350,7 @@ class TriviaQuiz(commands.Cog):
)
return
- elif questions < 1:
+ if questions < 1:
await ctx.send(
embed=self.make_error_embed(
"You must choose to complete at least one question. "
@@ -370,8 +359,7 @@ class TriviaQuiz(commands.Cog):
)
return
- else:
- self.question_limit = questions
+ self.question_limit = questions
# Start game if not running.
if not self.game_status[ctx.channel.id]:
diff --git a/bot/exts/holidays/halloween/monstersurvey.py b/bot/exts/holidays/halloween/monstersurvey.py
index d129f3cc..fdc15e13 100644
--- a/bot/exts/holidays/halloween/monstersurvey.py
+++ b/bot/exts/holidays/halloween/monstersurvey.py
@@ -90,7 +90,7 @@ class MonsterSurvey(Cog):
@monster_group.command(
name="vote"
)
- async def monster_vote(self, ctx: Context, name: str = None) -> None:
+ async def monster_vote(self, ctx: Context, name: str | None = None) -> None:
"""
Cast a vote for a particular monster.
@@ -109,7 +109,7 @@ class MonsterSurvey(Cog):
name = name.lower()
vote_embed = Embed(
- name="Monster Voting",
+ title="Monster Voting",
color=0xFF6800
)
@@ -141,7 +141,7 @@ class MonsterSurvey(Cog):
@monster_group.command(
name="show"
)
- async def monster_show(self, ctx: Context, name: str = None) -> None:
+ async def monster_show(self, ctx: Context, name: str | None = None) -> None:
"""Shows the named monster. If one is not named, it sends the default voting embed instead."""
if name is None:
await ctx.invoke(self.monster_leaderboard)
diff --git a/bot/exts/holidays/holidayreact.py b/bot/exts/holidays/holidayreact.py
index c3686fab..306cde53 100644
--- a/bot/exts/holidays/holidayreact.py
+++ b/bot/exts/holidays/holidayreact.py
@@ -29,7 +29,7 @@ class Holiday(NamedTuple):
Valentines = Holiday([Month.FEBRUARY], {
- "heart": Trigger(r"\b(love|heart)\b", ["\u2764\uFE0F"]),
+ "heart": Trigger(r"\b((l|w)(ove|uv)(s|lies?)?|hearts?)\b", ["\u2764\uFE0F"]),
}
)
Easter = Holiday([Month.APRIL], {
diff --git a/bot/exts/holidays/pride/pride_anthem.py b/bot/exts/holidays/pride/pride_anthem.py
index c719e388..1f214fc0 100644
--- a/bot/exts/holidays/pride/pride_anthem.py
+++ b/bot/exts/holidays/pride/pride_anthem.py
@@ -32,7 +32,7 @@ class PrideAnthem(commands.Cog):
log.info("No videos for that genre.")
@commands.command(name="prideanthem", aliases=("anthem", "pridesong"))
- async def prideanthem(self, ctx: commands.Context, genre: str = None) -> None:
+ async def prideanthem(self, ctx: commands.Context, genre: str | None = None) -> None:
"""
Sends a message with a video of a random pride anthem.
diff --git a/bot/exts/holidays/pride/pride_facts.py b/bot/exts/holidays/pride/pride_facts.py
index 3d52a3c7..5be1b085 100644
--- a/bot/exts/holidays/pride/pride_facts.py
+++ b/bot/exts/holidays/pride/pride_facts.py
@@ -38,7 +38,7 @@ class PrideFacts(commands.Cog):
return
@commands.command(name="pridefact", aliases=("pridefacts",))
- async def pridefact(self, ctx: commands.Context, option: int | str = None) -> None:
+ async def pridefact(self, ctx: commands.Context, option: int | str | None = None) -> None:
"""
Sends a message with a pride fact of the day.
diff --git a/bot/exts/holidays/pride/pride_leader.py b/bot/exts/holidays/pride/pride_leader.py
index b4a98892..9f01ebf9 100644
--- a/bot/exts/holidays/pride/pride_leader.py
+++ b/bot/exts/holidays/pride/pride_leader.py
@@ -57,7 +57,7 @@ class PrideLeader(commands.Cog):
def embed_builder(self, pride_leader: dict) -> discord.Embed:
"""Generate an Embed with information about a pride leader."""
- name = [name for name, info in PRIDE_RESOURCE.items() if info == pride_leader][0]
+ name = next(name for name, info in PRIDE_RESOURCE.items() if info == pride_leader)
embed = discord.Embed(
title=name,
diff --git a/bot/exts/holidays/valentines/be_my_valentine.py b/bot/exts/holidays/valentines/be_my_valentine.py
index c2dd8bb6..714d3884 100644
--- a/bot/exts/holidays/valentines/be_my_valentine.py
+++ b/bot/exts/holidays/valentines/be_my_valentine.py
@@ -44,7 +44,7 @@ class BeMyValentine(commands.Cog):
@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
+ self, ctx: commands.Context, user: discord.Member, *, valentine_type: str | None = None
) -> None:
"""
Send a valentine to a specified user with the lovefest role.
@@ -83,7 +83,7 @@ class BeMyValentine(commands.Cog):
@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
+ self, ctx: commands.Context, user: discord.Member, *, valentine_type: str | None = None
) -> None:
"""
Send an anonymous Valentine via DM to to a specified user with the lovefest role.
diff --git a/bot/exts/holidays/valentines/myvalenstate.py b/bot/exts/holidays/valentines/myvalenstate.py
index fcef24bc..977b9665 100644
--- a/bot/exts/holidays/valentines/myvalenstate.py
+++ b/bot/exts/holidays/valentines/myvalenstate.py
@@ -39,7 +39,7 @@ class MyValenstate(commands.Cog):
return pre_row[-1]
@commands.command()
- async def myvalenstate(self, ctx: commands.Context, *, name: str = None) -> None:
+ async def myvalenstate(self, ctx: commands.Context, *, name: str | None = None) -> None:
"""Find the vacation spot(s) with the most matching characters to the invoking user."""
eq_chars = collections.defaultdict(int)
if name is None:
diff --git a/bot/exts/utilities/challenges.py b/bot/exts/utilities/challenges.py
index 2f9ac73e..6d1813bb 100644
--- a/bot/exts/utilities/challenges.py
+++ b/bot/exts/utilities/challenges.py
@@ -258,7 +258,7 @@ class Challenges(commands.Cog):
@commands.command(aliases=["kata"])
@commands.cooldown(1, 5, commands.BucketType.user)
- async def challenge(self, ctx: commands.Context, language: str = "python", *, query: str = None) -> None:
+ async def challenge(self, ctx: commands.Context, language: str = "python", *, query: str | None = None) -> None:
"""
The challenge command pulls a random kata (challenge) from codewars.com.
diff --git a/bot/exts/utilities/colour.py b/bot/exts/utilities/colour.py
index 95f9ac22..b0ff8747 100644
--- a/bot/exts/utilities/colour.py
+++ b/bot/exts/utilities/colour.py
@@ -241,7 +241,7 @@ class Colour(commands.Cog):
choices=self.colour_mapping.values(),
score_cutoff=80
)
- colour_name = [name for name, hex_code in self.colour_mapping.items() if hex_code == match][0]
+ colour_name = next(name for name, hex_code in self.colour_mapping.items() if hex_code == match)
except TypeError:
colour_name = None
return colour_name
diff --git a/bot/exts/utilities/emoji.py b/bot/exts/utilities/emoji.py
index ce352fe2..bbaf6d25 100644
--- a/bot/exts/utilities/emoji.py
+++ b/bot/exts/utilities/emoji.py
@@ -78,7 +78,7 @@ class Emojis(commands.Cog):
await self.bot.invoke_help_command(ctx)
@emoji_group.command(name="count", aliases=("c",))
- async def count_command(self, ctx: commands.Context, *, category_query: str = None) -> None:
+ async def count_command(self, ctx: commands.Context, *, category_query: str | None = None) -> None:
"""Returns embed with emoji category and info given by the user."""
emoji_dict = defaultdict(list)
diff --git a/bot/exts/utilities/reddit.py b/bot/exts/utilities/reddit.py
index f8e358de..5dd4a377 100644
--- a/bot/exts/utilities/reddit.py
+++ b/bot/exts/utilities/reddit.py
@@ -20,16 +20,15 @@ from bot.utils.pagination import ImagePaginator, LinePaginator
log = logging.getLogger(__name__)
AccessToken = namedtuple("AccessToken", ["token", "expires_at"])
+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
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
@@ -68,7 +67,7 @@ class Reddit(Cog):
# Normal brackets interfere with Markdown.
title = escape_markdown(title).replace("[", "⦋").replace("]", "⦌")
- link = self.URL + data["permalink"]
+ link = URL + data["permalink"]
first_page += f"**[{title.replace('*', '')}]({link})**\n"
@@ -121,10 +120,10 @@ class Reddit(Cog):
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):
+ for i in range(1, MAX_RETRIES + 1):
response = await self.bot.http_session.post(
- url=f"{self.URL}/api/v1/access_token",
- headers=self.HEADERS,
+ url=f"{URL}/api/v1/access_token",
+ headers=HEADERS,
auth=self.client_auth,
data={
"grant_type": "client_credentials",
@@ -144,7 +143,7 @@ class Reddit(Cog):
return
log.debug(
f"Failed to get an access token: status {response.status} & content type {response.content_type}; "
- f"retrying ({i}/{self.MAX_RETRIES})"
+ f"retrying ({i}/{MAX_RETRIES})"
)
await asyncio.sleep(3)
@@ -159,8 +158,8 @@ class Reddit(Cog):
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,
+ url=f"{URL}/api/v1/revoke_token",
+ headers=HEADERS,
auth=self.client_auth,
data={
"token": self.access_token.token,
@@ -173,7 +172,7 @@ class Reddit(Cog):
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]:
+ async def fetch_posts(self, route: str, *, amount: int = 25, params: dict | None = 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:
@@ -183,11 +182,11 @@ class Reddit(Cog):
if not self.access_token or self.access_token.expires_at < datetime.now(tz=UTC):
await self.get_access_token()
- url = f"{self.OAUTH_URL}/{route}"
- for _ in range(self.MAX_RETRIES):
+ url = f"{OAUTH_URL}/{route}"
+ for _ in range(MAX_RETRIES):
response = await self.bot.http_session.get(
url=url,
- headers={**self.HEADERS, "Authorization": f"bearer {self.access_token.token}"},
+ headers=HEADERS | {"Authorization": f"bearer {self.access_token.token}"},
params=params
)
if response.status == 200 and response.content_type == "application/json":
diff --git a/bot/exts/utilities/wolfram.py b/bot/exts/utilities/wolfram.py
index d5669c6b..e77573a7 100644
--- a/bot/exts/utilities/wolfram.py
+++ b/bot/exts/utilities/wolfram.py
@@ -33,8 +33,8 @@ async def send_embed(
ctx: Context,
message_txt: str,
colour: int = Colours.soft_red,
- footer: str = None,
- img_url: str = None,
+ footer: str | None = None,
+ img_url: str | None = None,
f: discord.File = None
) -> None:
"""Generate & send a response embed with Wolfram as the author."""
diff --git a/bot/utils/__init__.py b/bot/utils/__init__.py
index ddc2d111..f1ae0e75 100644
--- a/bot/utils/__init__.py
+++ b/bot/utils/__init__.py
@@ -7,6 +7,7 @@ from datetime import UTC, datetime
import discord
from discord.ext.commands import BadArgument, Context
+from pydis_core.utils.scheduling import create_task
from bot.constants import Client, Month
from bot.utils.pagination import LinePaginator
@@ -55,52 +56,30 @@ 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.author == ctx.author and message.channel == ctx.channel
- try:
- if embed is None:
- embed = discord.Embed()
+ if embed is None:
+ embed = discord.Embed()
- coro1 = ctx.bot.wait_for("message", check=check, timeout=timeout)
- coro2 = LinePaginator.paginate(
+ # Run the paginator in the background, this means it will continue to work after the user has
+ # selected an option, and stopping it wont prevent the user from selecting an option, both
+ # of which are fine and make implementation simpler.
+ create_task(
+ LinePaginator.paginate(
choices, ctx, embed=embed, max_lines=entries_per_page,
- empty=empty, max_size=6000, timeout=9000
+ empty=empty, max_size=6000, timeout=timeout
)
+ )
- # 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)]
- done, pending = await asyncio.wait(futures, return_when=asyncio.FIRST_COMPLETED, loop=ctx.bot.loop)
-
- # :yert:
- result = list(done)[0].result()
-
- # Pagination was canceled - result is None
- if result is None:
- for coro in pending:
- coro.cancel()
- raise BadArgument("Canceled.")
-
- # Pagination was not initiated, only one page
- if result.author == ctx.bot.user:
- # Continue the wait_for
- result = await list(pending)[0]
-
- # Love that duplicate code
- for coro in pending:
- coro.cancel()
+ try:
+ message = await ctx.bot.wait_for("message", check=check, timeout=timeout)
except asyncio.TimeoutError:
raise BadArgument("Timed out.")
- # Guaranteed to not error because of isdecimal() in check
- index = int(result.content)
-
try:
+ index = int(message.content)
return entries[index - 1]
- except IndexError:
+ except (ValueError, IndexError):
raise BadArgument("Invalid choice.")
diff --git a/bot/utils/pagination.py b/bot/utils/pagination.py
index ef3186d0..58115fd6 100644
--- a/bot/utils/pagination.py
+++ b/bot/utils/pagination.py
@@ -90,8 +90,8 @@ class LinePaginator(Paginator):
cls, lines: Iterable[str], ctx: Context,
embed: Embed, prefix: str = "", suffix: str = "",
max_lines: int | None = 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
+ restrict_to_user: User = None, timeout: float = 300, footer_text: str | None = None,
+ url: str | None = None, exception_on_empty_embed: bool = False
) -> None:
"""
Use a paginator and set of reactions to provide pagination over a set of lines.
@@ -302,13 +302,13 @@ class ImagePaginator(Paginator):
self._current_page.append(line)
self.close_page()
- def add_image(self, image: str = None) -> None:
+ def add_image(self, image: str | None = None) -> None:
"""Adds an image to a page given the url."""
self.images.append(image)
@classmethod
async def paginate(cls, pages: list[tuple[str, str]], ctx: Context, embed: Embed,
- prefix: str = "", suffix: str = "", timeout: int = 300,
+ prefix: str = "", suffix: str = "", timeout: float = 300,
exception_on_empty_embed: bool = False) -> None:
"""
Use a paginator and set of reactions to provide pagination over a set of title/image pairs.