diff options
| -rw-r--r-- | bot/constants.py | 1 | ||||
| -rw-r--r-- | bot/converters.py | 25 | ||||
| -rw-r--r-- | bot/exts/fun/off_topic_names.py | 18 | ||||
| -rw-r--r-- | bot/exts/help_channels/_cog.py | 4 | ||||
| -rw-r--r-- | bot/exts/help_channels/_message.py | 33 | ||||
| -rw-r--r-- | bot/resources/tags/off-topic.md | 2 | ||||
| -rw-r--r-- | config-default.yml | 1 | 
7 files changed, 73 insertions, 11 deletions
| diff --git a/bot/constants.py b/bot/constants.py index 8a93ff9cf..69bc82b89 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -326,6 +326,7 @@ class Icons(metaclass=YAMLGetter):      filtering: str      green_checkmark: str +    green_questionmark: str      guild_update: str      hash_blurple: str diff --git a/bot/converters.py b/bot/converters.py index 0d9a519df..80ce99459 100644 --- a/bot/converters.py +++ b/bot/converters.py @@ -357,27 +357,38 @@ class Duration(DurationDelta):  class OffTopicName(Converter):      """A converter that ensures an added off-topic name is valid.""" +    ALLOWED_CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ!?'`-" + +    @classmethod +    def translate_name(cls, name: str, *, from_unicode: bool = True) -> str: +        """ +        Translates `name` into a format that is allowed in discord channel names. + +        If `from_unicode` is True, the name is translated from a discord-safe format, back to normalized text. +        """ +        if from_unicode: +            table = str.maketrans(cls.ALLOWED_CHARACTERS, '๐ ๐ก๐ข๐ฃ๐ค๐ฅ๐ฆ๐ง๐จ๐ฉ๐ช๐ซ๐ฌ๐ญ๐ฎ๐ฏ๐ฐ๐ฑ๐ฒ๐ณ๐ด๐ต๐ถ๐ท๐ธ๐นว๏ผโโ-') +        else: +            table = str.maketrans('๐ ๐ก๐ข๐ฃ๐ค๐ฅ๐ฆ๐ง๐จ๐ฉ๐ช๐ซ๐ฌ๐ญ๐ฎ๐ฏ๐ฐ๐ฑ๐ฒ๐ณ๐ด๐ต๐ถ๐ท๐ธ๐นว๏ผโโ-', cls.ALLOWED_CHARACTERS) + +        return name.translate(table) +      async def convert(self, ctx: Context, argument: str) -> str:          """Attempt to replace any invalid characters with their approximate Unicode equivalent.""" -        allowed_characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ!?'`-" -          # Chain multiple words to a single one          argument = "-".join(argument.split())          if not (2 <= len(argument) <= 96):              raise BadArgument("Channel name must be between 2 and 96 chars long") -        elif not all(c.isalnum() or c in allowed_characters for c in argument): +        elif not all(c.isalnum() or c in self.ALLOWED_CHARACTERS for c in argument):              raise BadArgument(                  "Channel name must only consist of "                  "alphanumeric characters, minus signs or apostrophes."              )          # Replace invalid characters with unicode alternatives. -        table = str.maketrans( -            allowed_characters, '๐ ๐ก๐ข๐ฃ๐ค๐ฅ๐ฆ๐ง๐จ๐ฉ๐ช๐ซ๐ฌ๐ญ๐ฎ๐ฏ๐ฐ๐ฑ๐ฒ๐ณ๐ด๐ต๐ถ๐ท๐ธ๐นว๏ผโโ-' -        ) -        return argument.translate(table) +        return self.translate_name(argument)  class ISODateTime(Converter): diff --git a/bot/exts/fun/off_topic_names.py b/bot/exts/fun/off_topic_names.py index 7fc93b88c..845b8175c 100644 --- a/bot/exts/fun/off_topic_names.py +++ b/bot/exts/fun/off_topic_names.py @@ -139,10 +139,20 @@ class OffTopicNames(Cog):      @has_any_role(*MODERATION_ROLES)      async def search_command(self, ctx: Context, *, query: OffTopicName) -> None:          """Search for an off-topic name.""" -        result = await self.bot.api_client.get('bot/off-topic-channel-names') -        in_matches = {name for name in result if query in name} -        close_matches = difflib.get_close_matches(query, result, n=10, cutoff=0.70) -        lines = sorted(f"โข {name}" for name in in_matches.union(close_matches)) +        query = OffTopicName.translate_name(query, from_unicode=False).lower() + +        # Map normalized names to returned names for search purposes +        result = { +            OffTopicName.translate_name(name, from_unicode=False).lower(): name +            for name in await self.bot.api_client.get('bot/off-topic-channel-names') +        } + +        # Search normalized keys +        in_matches = {name for name in result.keys() if query in name} +        close_matches = difflib.get_close_matches(query, result.keys(), n=10, cutoff=0.70) + +        # Send Results +        lines = sorted(f"โข {result[name]}" for name in in_matches.union(close_matches))          embed = Embed(              title="Query results",              colour=Colour.blue() diff --git a/bot/exts/help_channels/_cog.py b/bot/exts/help_channels/_cog.py index 0995c8a79..6abf99810 100644 --- a/bot/exts/help_channels/_cog.py +++ b/bot/exts/help_channels/_cog.py @@ -102,6 +102,10 @@ class HelpChannels(commands.Cog):          await _cooldown.revoke_send_permissions(message.author, self.scheduler)          await _message.pin(message) +        try: +            await _message.dm_on_open(message) +        except Exception as e: +            log.warning("Error occurred while sending DM:", exc_info=e)          # Add user with channel for dormant check.          await _caches.claimants.set(message.channel.id, message.author.id) diff --git a/bot/exts/help_channels/_message.py b/bot/exts/help_channels/_message.py index 2bbd4bdd6..36388f9bd 100644 --- a/bot/exts/help_channels/_message.py +++ b/bot/exts/help_channels/_message.py @@ -1,4 +1,5 @@  import logging +import textwrap  import typing as t  from datetime import datetime @@ -92,6 +93,38 @@ async def is_empty(channel: discord.TextChannel) -> bool:      return False +async def dm_on_open(message: discord.Message) -> None: +    """ +    DM claimant with a link to the claimed channel's first message, with a 100 letter preview of the message. + +    Does nothing if the user has DMs disabled. +    """ +    embed = discord.Embed( +        title="Help channel opened", +        description=f"You claimed {message.channel.mention}.", +        colour=bot.constants.Colours.bright_green, +        timestamp=message.created_at, +    ) + +    embed.set_thumbnail(url=constants.Icons.green_questionmark) +    formatted_message = textwrap.shorten(message.content, width=100, placeholder="...") +    if formatted_message: +        embed.add_field(name="Your message", value=formatted_message, inline=False) +    embed.add_field( +        name="Conversation", +        value=f"[Jump to message!]({message.jump_url})", +        inline=False, +    ) + +    try: +        await message.author.send(embed=embed) +        log.trace(f"Sent DM to {message.author.id} after claiming help channel.") +    except discord.errors.Forbidden: +        log.trace( +            f"Ignoring to send DM to {message.author.id} after claiming help channel: DMs disabled." +        ) + +  async def notify(channel: discord.TextChannel, last_notification: t.Optional[datetime]) -> t.Optional[datetime]:      """      Send a message in `channel` notifying about a lack of available help channels. diff --git a/bot/resources/tags/off-topic.md b/bot/resources/tags/off-topic.md index c7f98a813..6a864a1d5 100644 --- a/bot/resources/tags/off-topic.md +++ b/bot/resources/tags/off-topic.md @@ -6,3 +6,5 @@ There are three off-topic channels:  โข <#463035268514185226>    Their names change randomly every 24 hours, but you can always find them under the `OFF-TOPIC/GENERAL` category in the channel list. + +Please read our [off-topic etiquette](https://pythondiscord.com/pages/resources/guides/off-topic-etiquette/) before participating in conversations. diff --git a/config-default.yml b/config-default.yml index 8e9a29a51..7d9afaa0e 100644 --- a/config-default.yml +++ b/config-default.yml @@ -90,6 +90,7 @@ style:          filtering: "https://cdn.discordapp.com/emojis/472472638594482195.png"          green_checkmark: "https://raw.githubusercontent.com/python-discord/branding/master/icons/checkmark/green-checkmark-dist.png" +        green_questionmark: "https://raw.githubusercontent.com/python-discord/branding/master/icons/checkmark/green-question-mark-dist.png"          guild_update: "https://cdn.discordapp.com/emojis/469954765141442561.png"          hash_blurple: "https://cdn.discordapp.com/emojis/469950142942806017.png" | 
