From cb951a920fd77eca35b355ca8835781e63250d78 Mon Sep 17 00:00:00 2001 From: Kingsley McDonald Date: Fri, 1 Nov 2019 23:54:05 +0000 Subject: implement !zen command. --- bot/cogs/utils.py | 98 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 96 insertions(+), 2 deletions(-) diff --git a/bot/cogs/utils.py b/bot/cogs/utils.py index 793fe4c1a..db8e77062 100644 --- a/bot/cogs/utils.py +++ b/bot/cogs/utils.py @@ -1,21 +1,45 @@ +import difflib import logging +import random import re import unicodedata from asyncio import TimeoutError, sleep from email.parser import HeaderParser from io import StringIO -from typing import Tuple +from typing import Optional, Tuple from dateutil import relativedelta from discord import Colour, Embed, Message, Role from discord.ext.commands import Bot, Cog, Context, command -from bot.constants import Channels, MODERATION_ROLES, Mention, STAFF_ROLES +from bot.constants import Channels, MODERATION_ROLES, Mention, NEGATIVE_REPLIES, STAFF_ROLES from bot.decorators import in_channel, with_role from bot.utils.time import humanize_delta log = logging.getLogger(__name__) +ZEN_OF_PYTHON = """\ +Beautiful is better than ugly. +Explicit is better than implicit. +Simple is better than complex. +Complex is better than complicated. +Flat is better than nested. +Sparse is better than dense. +Readability counts. +Special cases aren't special enough to break the rules. +Although practicality beats purity. +Errors should never pass silently. +Unless explicitly silenced. +In the face of ambiguity, refuse the temptation to guess. +There should be one-- and preferably only one --obvious way to do it. +Although that way may not be obvious at first unless you're Dutch. +Now is better than never. +Although never is often better than *right* now. +If the implementation is hard to explain, it's a bad idea. +If the implementation is easy to explain, it may be a good idea. +Namespaces are one honking great idea -- let's do more of those! +""" + class Utils(Cog): """A selection of utilities which don't have a clear category.""" @@ -174,6 +198,76 @@ class Utils(Cog): f"as I detected unauthorised use by {msg.author} (ID: {msg.author.id})." ) + @command() + async def zen(self, ctx: Context, *, search_value: Optional[str] = None) -> None: + """ + Show the Zen of Python. + + Without any arguments, the full Zen will be produced. + If an integer is provided, the line with that index will be produced. + If a string is provided, the line which matches best will be produced. + """ + if search_value is None: + embed = Embed( + colour=Colour.blurple(), + title="The Zen of Python, by Tim Peters", + description=ZEN_OF_PYTHON + ) + + return await ctx.send(embed=embed) + + zen_lines = ZEN_OF_PYTHON.splitlines() + + # check if it's an integer. could be negative. why not. + if search_value.lstrip("-").isdigit(): + index = int(search_value) + + try: + line = zen_lines[index] + except IndexError: + embed = Embed( + colour=Colour.red(), + title=random.choice(NEGATIVE_REPLIES), + description="Please provide a valid index." + ) + + else: + embed = Embed( + colour=Colour.blurple(), + title=f"The Zen of Python (line {index % len(zen_lines)}):", + description=line + ) + + return await ctx.send(embed=embed) + + # at this point, we must be dealing with a string search. + matcher = difflib.SequenceMatcher(None, search_value.lower()) + + best_match = "" + match_index = 0 + best_ratio = 0 + + for index, line in enumerate(zen_lines): + matcher.set_seq2(line.lower()) + + # the match ratio needs to be adjusted because, naturally, + # longer lines will have worse ratios than shorter lines when + # fuzzy searching for keywords. this seems to work okay. + adjusted_ratio = (len(line) - 5) ** 0.5 * matcher.ratio() + + if adjusted_ratio > best_ratio: + best_ratio = adjusted_ratio + best_match = line + match_index = index + + embed = Embed( + colour=Colour.blurple(), + title=f"The Zen of Python (line {match_index}):", + description=best_match + ) + + return await ctx.send(embed=embed) + def setup(bot: Bot) -> None: """Utils cog load.""" -- cgit v1.2.3 From abee8a8c51cbea40b2265cec245071ab9a5297a1 Mon Sep 17 00:00:00 2001 From: Kingsley McDonald Date: Sat, 2 Nov 2019 12:15:22 +0000 Subject: apply kosa's requested changes. - return None from the command's coroutine as hinted, rather than a discord.Message object. - only check for one negative sign on !zen index searches (rather than any amount) so that `int(...)` does not fail. - provide a range of valid indices when a user requests a !zen index out of range. --- bot/cogs/utils.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/bot/cogs/utils.py b/bot/cogs/utils.py index db8e77062..7dd5e2e56 100644 --- a/bot/cogs/utils.py +++ b/bot/cogs/utils.py @@ -214,12 +214,14 @@ class Utils(Cog): description=ZEN_OF_PYTHON ) - return await ctx.send(embed=embed) + await ctx.send(embed=embed) + return zen_lines = ZEN_OF_PYTHON.splitlines() # check if it's an integer. could be negative. why not. - if search_value.lstrip("-").isdigit(): + is_negative_integer = search_value[0] == "-" and search_value[1:].isdigit() + if search_value.isdigit() or is_negative_integer: index = int(search_value) try: @@ -228,9 +230,8 @@ class Utils(Cog): embed = Embed( colour=Colour.red(), title=random.choice(NEGATIVE_REPLIES), - description="Please provide a valid index." + description=f"Please provide an index between {-len(zen_lines)} and {len(zen_lines) - 1}." ) - else: embed = Embed( colour=Colour.blurple(), @@ -238,7 +239,8 @@ class Utils(Cog): description=line ) - return await ctx.send(embed=embed) + await ctx.send(embed=embed) + return # at this point, we must be dealing with a string search. matcher = difflib.SequenceMatcher(None, search_value.lower()) @@ -266,7 +268,7 @@ class Utils(Cog): description=best_match ) - return await ctx.send(embed=embed) + await ctx.send(embed=embed) def setup(bot: Bot) -> None: -- cgit v1.2.3 From aa6113792ca9c328c428fc1ea75cac3b03bcd7f3 Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Thu, 5 Mar 2020 22:58:07 +1000 Subject: Re-use embed, use command converter, raise BadArgument. --- bot/cogs/utils.py | 59 +++++++++++++++++++++---------------------------------- 1 file changed, 22 insertions(+), 37 deletions(-) diff --git a/bot/cogs/utils.py b/bot/cogs/utils.py index df2254e0b..49fe6d344 100644 --- a/bot/cogs/utils.py +++ b/bot/cogs/utils.py @@ -1,19 +1,18 @@ import difflib import logging -import random import re import unicodedata from asyncio import TimeoutError, sleep from email.parser import HeaderParser from io import StringIO -from typing import Optional, Tuple +from typing import Tuple, Union from dateutil import relativedelta from discord import Colour, Embed, Message, Role -from discord.ext.commands import Cog, Context, command +from discord.ext.commands import BadArgument, Cog, Context, command from bot.bot import Bot -from bot.constants import Channels, MODERATION_ROLES, Mention, NEGATIVE_REPLIES, STAFF_ROLES +from bot.constants import Channels, MODERATION_ROLES, Mention, STAFF_ROLES from bot.decorators import in_channel, with_role from bot.utils.time import humanize_delta @@ -198,7 +197,7 @@ class Utils(Cog): ) @command() - async def zen(self, ctx: Context, *, search_value: Optional[str] = None) -> None: + async def zen(self, ctx: Context, *, search_value: Union[int, str, None] = None) -> None: """ Show the Zen of Python. @@ -206,42 +205,32 @@ class Utils(Cog): If an integer is provided, the line with that index will be produced. If a string is provided, the line which matches best will be produced. """ - if search_value is None: - embed = Embed( - colour=Colour.blurple(), - title="The Zen of Python, by Tim Peters", - description=ZEN_OF_PYTHON - ) + embed = Embed( + colour=Colour.blurple(), + title="The Zen of Python", + description=ZEN_OF_PYTHON + ) + if search_value is None: + embed.title += ", by Tim Peters" await ctx.send(embed=embed) return zen_lines = ZEN_OF_PYTHON.splitlines() - # check if it's an integer. could be negative. why not. - is_negative_integer = search_value[0] == "-" and search_value[1:].isdigit() - if search_value.isdigit() or is_negative_integer: - index = int(search_value) - - try: - line = zen_lines[index] - except IndexError: - embed = Embed( - colour=Colour.red(), - title=random.choice(NEGATIVE_REPLIES), - description=f"Please provide an index between {-len(zen_lines)} and {len(zen_lines) - 1}." - ) - else: - embed = Embed( - colour=Colour.blurple(), - title=f"The Zen of Python (line {index % len(zen_lines)}):", - description=line - ) + # handle if it's an index int + if isinstance(search_value, int): + upper_bound = len(zen_lines) - 1 + lower_bound = -1 * upper_bound + if not (lower_bound <= search_value <= upper_bound): + raise BadArgument(f"Please provide an index between {lower_bound} and {upper_bound}.") + embed.title += f" (line {search_value % len(zen_lines)}):" + embed.description = zen_lines[search_value] await ctx.send(embed=embed) return - # at this point, we must be dealing with a string search. + # handle if it's a search string matcher = difflib.SequenceMatcher(None, search_value.lower()) best_match = "" @@ -261,12 +250,8 @@ class Utils(Cog): best_match = line match_index = index - embed = Embed( - colour=Colour.blurple(), - title=f"The Zen of Python (line {match_index}):", - description=best_match - ) - + embed.title += f" (line {match_index}):" + embed.description = best_match await ctx.send(embed=embed) -- cgit v1.2.3 From d515941ac0af2e176d186bed5d8fafb1cec13de4 Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Thu, 5 Mar 2020 23:33:44 +1000 Subject: Raise BadArgument if no string match. --- bot/cogs/utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bot/cogs/utils.py b/bot/cogs/utils.py index 49fe6d344..8ea972145 100644 --- a/bot/cogs/utils.py +++ b/bot/cogs/utils.py @@ -250,6 +250,9 @@ class Utils(Cog): best_match = line match_index = index + if not best_match: + raise BadArgument("I didn't get a match! Please try again with a different search term.") + embed.title += f" (line {match_index}):" embed.description = best_match await ctx.send(embed=embed) -- cgit v1.2.3