diff options
| -rw-r--r-- | bot/cogs/utils.py | 100 | 
1 files changed, 98 insertions, 2 deletions
diff --git a/bot/cogs/utils.py b/bot/cogs/utils.py index 47a59db66..1e4fda248 100644 --- a/bot/cogs/utils.py +++ b/bot/cogs/utils.py @@ -1,22 +1,46 @@ +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 Cog, Context, command  from bot.bot import Bot -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.""" @@ -175,6 +199,78 @@ 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 +            ) + +            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 +                ) + +            await ctx.send(embed=embed) +            return + +        # 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 +        ) + +        await ctx.send(embed=embed) +  def setup(bot: Bot) -> None:      """Load the Utils cog."""  |