diff options
author | 2023-08-23 10:31:24 +0100 | |
---|---|---|
committer | 2023-08-23 10:31:24 +0100 | |
commit | 6ebc2806b1b2737c27c090da324d85577ea67fb6 (patch) | |
tree | 1fb65b1dd1c8185bab4874dfc8fe5e272f20de3d /bot | |
parent | Handle snakes without images (diff) | |
parent | Corrected attribute name to fetch github url in extensions.py (#1348) (diff) |
Merge branch 'main' into snakes-cleanup
Diffstat (limited to 'bot')
24 files changed, 357 insertions, 300 deletions
@@ -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 f1a1cab9..82496629 100644 --- a/bot/exts/fun/snakes/_snakes_cog.py +++ b/bot/exts/fun/snakes/_snakes_cog.py @@ -848,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. @@ -1041,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? @@ -1076,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/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 f3ce50a9..5be1b085 100644 --- a/bot/exts/holidays/pride/pride_facts.py +++ b/bot/exts/holidays/pride/pride_facts.py @@ -4,7 +4,6 @@ import random from datetime import UTC, datetime from pathlib import Path -import dateutil.parser import discord from discord.ext import commands @@ -28,65 +27,45 @@ class PrideFacts(commands.Cog): async def send_pride_fact_daily(self) -> None: """Background task to post the daily pride fact every day.""" channel = self.bot.get_channel(Channels.sir_lancebot_playground) - await self.send_select_fact(channel, datetime.now(tz=UTC)) + await self.send_select_fact(channel, datetime.now(tz=UTC).day) - async def send_random_fact(self, ctx: commands.Context) -> None: - """Provides a fact from any previous day, or today.""" - now = datetime.now(tz=UTC) - 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] + async def send_select_fact(self, target: discord.abc.Messageable, day_num: int) -> None: + """Provides the fact for the specified day.""" try: - await ctx.send(embed=self.make_embed(random.choice(previous_facts))) + await target.send(embed=self.get_fact_embed(day_num)) except IndexError: - await ctx.send("No facts available") - - async def send_select_fact(self, target: discord.abc.Messageable, _date: str | datetime) -> None: - """Provides the fact for the specified day, if the day is today, or is in the past.""" - now = datetime.now(tz=UTC) - if isinstance(_date, str): - try: - date = dateutil.parser.parse(_date, dayfirst=False, yearfirst=False, fuzzy=True) - except (ValueError, OverflowError) as err: - await target.send(f"Error parsing date: {err}") - return - else: - date = _date - if date.year < now.year or (date.year == now.year and date.day <= now.day): - try: - await target.send(embed=self.make_embed(FACTS[str(date.year)][date.day - 1])) - except KeyError: - await target.send(f"The year {date.year} is not yet supported") - return - except IndexError: - await target.send(f"Day {date.day} of {date.year} is not yet support") - return - else: - await target.send("The fact for the selected day is not yet available.") + await target.send(f"Day {day_num} is not supported") + return @commands.command(name="pridefact", aliases=("pridefacts",)) - async def pridefact(self, ctx: commands.Context, option: 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. - If "random" is given as an argument, a random previous fact will be provided. - - If a date is given as an argument, and the date is in the past, the fact from that day - will be provided. + "option" is an optional setting, which has two has two accepted values: + - "random": a random previous fact will be provided. + - If a option is a number (1-30), the fact for that given day of June is returned. """ if not option: - await self.send_select_fact(ctx, datetime.now(tz=UTC)) + await self.send_select_fact(ctx, datetime.now(tz=UTC).day) + elif isinstance(option, int): + await self.send_select_fact(ctx, option) elif option.lower().startswith("rand"): - await self.send_random_fact(ctx) + await ctx.send(embed=self.get_fact_embed()) else: - await self.send_select_fact(ctx, option) + await ctx.send(f"Could not parse option {option}") @staticmethod - def make_embed(fact: str) -> discord.Embed: - """Makes a nice embed for the fact to be sent.""" + def get_fact_embed(day_num: int | None=None) -> discord.Embed: + """ + Makes a embed for the fact on the given day_num to be sent. + + if day_num is not set, a random fact is selected. + """ + fact = FACTS[day_num-1] if day_num else random.choice(FACTS) return discord.Embed( colour=Colours.pink, - title="Pride Fact!", + title=f"Day {day_num}'s pride fact!" if day_num else "Random pride fact!", description=fact ) 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/bookmark.py b/bot/exts/utilities/bookmark.py index 150dfc48..22801d14 100644 --- a/bot/exts/utilities/bookmark.py +++ b/bot/exts/utilities/bookmark.py @@ -6,10 +6,76 @@ from discord.ext import commands from bot.bot import Bot from bot.constants import Colours, ERROR_REPLIES, Icons, Roles +from bot.utils.converters import WrappedMessageConverter from bot.utils.decorators import whitelist_override log = logging.getLogger(__name__) +MESSAGE_NOT_FOUND_ERROR = ( + "You must either provide a reference to a valid message, or reply to one." + "\n\nThe lookup strategy for a message is as follows (in order):" + "\n1. Lookup by '{channel ID}-{message ID}' (retrieved by shift-clicking on 'Copy ID')" + "\n2. Lookup by message ID (the message **must** be in the current channel)" + "\n3. Lookup by message URL" +) + + +async def dm_bookmark( + target_user: discord.Member | discord.User, + target_message: discord.Message, + title: str, +) -> None: + """ + Sends the `target_message` as a bookmark to the `target_user` DMs, with `title` as the embed title. + + Raises `discord.Forbidden` if the user's DMs are closed. + """ + embed = Bookmark.build_bookmark_dm(target_message, title) + message_url_view = discord.ui.View().add_item( + discord.ui.Button(label="View Message", url=target_message.jump_url) + ) + await target_user.send(embed=embed, view=message_url_view) + log.info(f"{target_user} bookmarked {target_message.jump_url} with title {title!r}") + + +class SendBookmark(discord.ui.View): + """The button that sends a bookmark to other users.""" + + def __init__( + self, + author: discord.Member, + channel: discord.TextChannel, + target_message: discord.Message, + title: str, + ): + super().__init__() + + self.clicked = [] + self.channel = channel + self.target_message = target_message + self.title = title + + @discord.ui.button(label="Receive Bookmark", style=discord.ButtonStyle.green) + async def button_callback(self, interaction: discord.Interaction, button: discord.ui.Button) -> None: + """The button callback.""" + if interaction.user.id in self.clicked: + await interaction.response.send_message( + "You have already received a bookmark to that message.", + ephemeral=True, + ) + return + + try: + await dm_bookmark(interaction.user, self.target_message, self.title) + except discord.Forbidden: + await interaction.response.send_message( + embed=Bookmark.build_error_embed("Enable your DMs to receive the bookmark."), + ephemeral=True, + ) + else: + self.clicked.append(interaction.user.id) + await interaction.response.send_message("You have received a bookmark to that message.", ephemeral=True) + class BookmarkForm(discord.ui.Modal): """The form where a user can fill in a custom title for their bookmark & submit it.""" @@ -31,7 +97,7 @@ class BookmarkForm(discord.ui.Modal): """Sends the bookmark embed to the user with the newly chosen title.""" title = self.bookmark_title.value or self.bookmark_title.default try: - await self.dm_bookmark(interaction, self.message, title) + await dm_bookmark(interaction.user, self.message, title) except discord.Forbidden: await interaction.response.send_message( embed=Bookmark.build_error_embed("Enable your DMs to receive the bookmark."), @@ -44,24 +110,6 @@ class BookmarkForm(discord.ui.Modal): ephemeral=True, ) - async def dm_bookmark( - self, - interaction: discord.Interaction, - target_message: discord.Message, - title: str, - ) -> None: - """ - Sends the target_message as a bookmark to the interaction user's DMs. - - Raises ``discord.Forbidden`` if the user's DMs are closed. - """ - embed = Bookmark.build_bookmark_dm(target_message, title) - message_url_view = discord.ui.View().add_item( - discord.ui.Button(label="View Message", url=target_message.jump_url) - ) - await interaction.user.send(embed=embed, view=message_url_view) - log.info(f"{interaction.user} bookmarked {target_message.jump_url} with title {title!r}") - class Bookmark(commands.Cog): """Creates personal bookmarks by relaying a message link to the user's DMs.""" @@ -104,6 +152,17 @@ class Bookmark(commands.Cog): colour=Colours.soft_red, ) + @staticmethod + def build_bookmark_embed(target_message: discord.Message) -> discord.Embed: + """Build the channel embed to the bookmark requester.""" + return discord.Embed( + description=( + f"Click the button to be sent your very own bookmark to " + f"[this message]({target_message.jump_url})." + ), + colour=Colours.soft_green, + ) + async def _bookmark_context_menu_callback(self, interaction: discord.Interaction, message: discord.Message) -> None: """The callback that will be invoked upon using the bookmark's context menu command.""" permissions = interaction.channel.permissions_for(interaction.user) @@ -122,15 +181,46 @@ class Bookmark(commands.Cog): @commands.guild_only() @whitelist_override(roles=(Roles.everyone,)) @commands.cooldown(1, 30, commands.BucketType.channel) - async def bookmark(self, ctx: commands.Context) -> None: - """Teach the invoker how to use the new context-menu based command for a smooth migration.""" - await ctx.send( - embed=self.build_error_embed( - "The bookmark text command has been replaced with a context menu command!\n\n" - "To bookmark a message simply right-click (press and hold on mobile) " - "on a message, open the 'Apps' menu, and click 'Bookmark'." + async def bookmark( + self, + ctx: commands.Context, + target_message: WrappedMessageConverter | None, + *, + title: str = "Bookmark", + ) -> None: + """ + Send the author a link to the specified message via DMs. + + Members can either give a message as an argument, or reply to a message. + + Bookmarks can subsequently be deleted by using the `bookmark delete` command in DMs. + """ + target_message: discord.Message | None = target_message or getattr(ctx.message.reference, "resolved", None) + if target_message is None: + raise commands.UserInputError(MESSAGE_NOT_FOUND_ERROR) + + permissions = target_message.channel.permissions_for(ctx.author) + if not permissions.read_messages: + log.info(f"{ctx.author} tried to bookmark a message in #{target_message.channel} but has no permissions.") + embed = self.build_error_embed("You don't have permission to view this channel.") + await ctx.send(embed=embed) + return + + view = SendBookmark(ctx.author, ctx.channel, target_message, title) + try: + await dm_bookmark(ctx.author, target_message, title) + except discord.Forbidden: + error_embed = self.build_error_embed( + f"{ctx.author.mention}, please enable your DMs to receive the bookmark." ) - ) + await ctx.send(embed=error_embed) + else: + view.clicked.append(ctx.author.id) + log.info(f"{ctx.author.mention} bookmarked {target_message.jump_url} with title '{title}'") + + embed = self.build_bookmark_embed(target_message) + + await ctx.send(embed=embed, view=view, delete_after=180) @bookmark.command(name="delete", aliases=("del", "rm"), root_aliases=("unbm", "unbookmark", "dmdelete", "dmdel")) @whitelist_override(bypass_defaults=True, allow_dm=True) 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 cfc70d85..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 @@ -37,7 +36,7 @@ class Reddit(Cog): self.access_token = None self.client_auth = BasicAuth(RedditConfig.client_id.get_secret_value(), RedditConfig.secret.get_secret_value()) - # self.auto_poster_loop.start() + self.auto_poster_loop.start() async def cog_unload(self) -> None: """Stop the loop task and revoke the access token when the cog is unloaded.""" @@ -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/resources/holidays/pride/facts.json b/bot/resources/holidays/pride/facts.json index 2151f5ca..cb10e409 100644 --- a/bot/resources/holidays/pride/facts.json +++ b/bot/resources/holidays/pride/facts.json @@ -1,34 +1,32 @@ -{ - "2020": [ - "No research has conclusively proven what causes homosexuality, heterosexuality, or bisexuality.", - "Records of same-sex relationships have been found in nearly every culture throughout history with varying degrees of acceptance.", - "Various slurs targeting queer people have been reappropriated by them, notably \"dyke\", and \"queer\".", - "Historians note that in some cultures, some homosexual behavior was not viewed as effeminate, but as evidence of a man's masculinity. Examples include the Celtic and Greek cultures.", - "Over time, the proportion of people who identify as homosexual or bisexual appears to be increasing. It is not know if this is because they feel it is safer to come out, or if the actual numbers of homosexual/bisexual people are increasing.", - "A large proportion of people, both in and out of the LGBTQ+ communities, do not believe bisexuality exists. This is known as bisexual erasure.", - "Queer people commit suicide are much more common in politically conservative regions, and also more common than non-queer people in general.", - "October 8th is lesbian pride day!", - "Stormé DeLarverie, a lesbian drag king, had a \"scuffle\" with the police which many claim is what kicked off the Stonewall Riots.", - "Gilbert Baker, also known as the “Gay Betsy Ross,” designed the rainbow flag, or Pride Flag, in San Francisco in 1978.", - "The rainbow pride flag is well-known, but there are flags for most labeled gender/sexual minorities.", - "In 1968, Dr. John Money performed the first complete male-to-female sex-change operation in the United States at Johns Hopkins University.", - "At the age of 24, Leonardo Da Vinci was arrested for having sex with a man. He was eventually acquitted.", - "Alfred Kinsey, the creator of the Kinsey scale, is known as \"the father of the sexual revolution\". The Kinsey scale was created in order to demonstrate that sexuality does not fit into two strict categories: homosexual and heterosexual. Instead, Kinsey believed that sexuality is fluid and subject to change over time.", - "The Kinsey scale ranges from 0, which is exclusively heterosexual, to 6, which is exclusively homosexual.", - "November 20th is the Transgender Day of Remembrance, which is a day to memorialize those who have been murdered as a result of transphobia.", - "The pink triangle was the symbol that queer people were required to wear in Nazi concentration camps during WWII. The symbol has since been reclaimed as a positive symbol of self-identity.", - "The term \"AC/DC\" has been used to refer to bisexuals.", - "September 23rd is bisexual pride day!", - "Pride Day refers to celebrations that typically take place in June that commemorate the Stonewall Inn riots of June 28, 1969. These riots are considered the birth of the modern gay civil rights movement.", - "A \"beard\" is someone of the opposite sex who knowingly dates a closeted lesbian or gay man to provide that person with a heterosexual \"disguise\", usually for family or career purposes.", - "In Nigeria, where homosexuality is punishable by death by stoning, a post-grad student claimed he had proved being gay was wrong by using magnets. He hoped to win a Nobel Prize for his research. He has not received one.", - "In 1982, the Gay Related Immune Disorder (GRID) was renamed Acquired Immune Deficiency Syndrome (AIDS).", - "The word \"lesbian\" is derived from the Greek island Lesbos, home of Greek poet Sappho. Her poetry proclaimed her love for women, and their beauty.", - "Nearly all bonobos (a kind of chimpanzee) appear to be bisexual.", - "Homosexual behavior has been observed in 1,500 animal species and is most widespread among animals with a complex herd life.", - "Many queer people identify their sexual orientation independently from their romantic orientation. For instance, it is possible to be sexually attracted to both women and men, but only be romantically attracted to one of them.", - "In 2005, Swedish researchers found that when straight men smelled a female urine compound, their hypothalamus lit up in brain images. In gay men, it did not. Instead, homosexual men's hypothalamus lit up when they smelled the male-sweat compound, which was the same way straight women responded.", - "As of 2019-10-02, there are 17 states in the United States of America where queer people can be fired for being queer. In most other states, there is minimal protection offered, often only for public employees.", - "In 1985, an official Star Trek novel was published with scenes depicting Kirk and Spock as lovers. These parts were largely removed, which made the original into a collector's item." - ] -} +[ + "No research has conclusively proven what causes homosexuality, heterosexuality, or bisexuality.", + "Records of same-sex relationships have been found in nearly every culture throughout history with varying degrees of acceptance.", + "Various slurs targeting queer people have been reappropriated by them, notably \"dyke\", and \"queer\".", + "Historians note that in some cultures, some homosexual behavior was not viewed as effeminate, but as evidence of a man's masculinity. Examples include the Celtic and Greek cultures.", + "Over time, the proportion of people who identify as homosexual or bisexual appears to be increasing. It is not know if this is because they feel it is safer to come out, or if the actual numbers of homosexual/bisexual people are increasing.", + "A large proportion of people, both in and out of the LGBTQ+ communities, do not believe bisexuality exists. This is known as bisexual erasure.", + "Queer people commit suicide are much more common in politically conservative regions, and also more common than non-queer people in general.", + "October 8th is lesbian pride day!", + "Stormé DeLarverie, a lesbian drag king, had a \"scuffle\" with the police which many claim is what kicked off the Stonewall Riots.", + "Gilbert Baker, also known as the “Gay Betsy Ross,” designed the rainbow flag, or Pride Flag, in San Francisco in 1978.", + "The rainbow pride flag is well-known, but there are flags for most labeled gender/sexual minorities.", + "In 1968, Dr. John Money performed the first complete male-to-female sex-change operation in the United States at Johns Hopkins University.", + "At the age of 24, Leonardo Da Vinci was arrested for having sex with a man. He was eventually acquitted.", + "Alfred Kinsey, the creator of the Kinsey scale, is known as \"the father of the sexual revolution\". The Kinsey scale was created in order to demonstrate that sexuality does not fit into two strict categories: homosexual and heterosexual. Instead, Kinsey believed that sexuality is fluid and subject to change over time.", + "The Kinsey scale ranges from 0, which is exclusively heterosexual, to 6, which is exclusively homosexual.", + "November 20th is the Transgender Day of Remembrance, which is a day to memorialize those who have been murdered as a result of transphobia.", + "The pink triangle was the symbol that queer people were required to wear in Nazi concentration camps during WWII. The symbol has since been reclaimed as a positive symbol of self-identity.", + "The term \"AC/DC\" has been used to refer to bisexuals.", + "September 23rd is bisexual pride day!", + "Pride Day refers to celebrations that typically take place in June that commemorate the Stonewall Inn riots of June 28, 1969. These riots are considered the birth of the modern gay civil rights movement.", + "A \"beard\" is someone of the opposite sex who knowingly dates a closeted lesbian or gay man to provide that person with a heterosexual \"disguise\", usually for family or career purposes.", + "In Nigeria, where homosexuality is punishable by death by stoning, a post-grad student claimed he had proved being gay was wrong by using magnets. He hoped to win a Nobel Prize for his research. He has not received one.", + "In 1982, the Gay Related Immune Disorder (GRID) was renamed Acquired Immune Deficiency Syndrome (AIDS).", + "The word \"lesbian\" is derived from the Greek island Lesbos, home of Greek poet Sappho. Her poetry proclaimed her love for women, and their beauty.", + "Nearly all bonobos (a kind of chimpanzee) appear to be bisexual.", + "Homosexual behavior has been observed in 1,500 animal species and is most widespread among animals with a complex herd life.", + "Many queer people identify their sexual orientation independently from their romantic orientation. For instance, it is possible to be sexually attracted to both women and men, but only be romantically attracted to one of them.", + "In 2005, Swedish researchers found that when straight men smelled a female urine compound, their hypothalamus lit up in brain images. In gay men, it did not. Instead, homosexual men's hypothalamus lit up when they smelled the male-sweat compound, which was the same way straight women responded.", + "As of 2019-10-02, there are 17 states in the United States of America where queer people can be fired for being queer. In most other states, there is minimal protection offered, often only for public employees.", + "In 1985, an official Star Trek novel was published with scenes depicting Kirk and Spock as lovers. These parts were largely removed, which made the original into a collector's item." +] diff --git a/bot/utils/pagination.py b/bot/utils/pagination.py index d86bb033..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: float = 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,7 +302,7 @@ 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) |