diff options
Diffstat (limited to '')
| -rw-r--r-- | bot/cogs/filtering.py | 4 | ||||
| -rw-r--r-- | bot/cogs/help_channels.py | 40 | ||||
| -rw-r--r-- | bot/cogs/stats.py | 8 | ||||
| -rw-r--r-- | bot/cogs/tags.py | 59 | ||||
| -rw-r--r-- | bot/constants.py | 3 | ||||
| -rw-r--r-- | config-default.yml | 7 | 
6 files changed, 97 insertions, 24 deletions
| diff --git a/bot/cogs/filtering.py b/bot/cogs/filtering.py index 1e21a4ce3..1d9fddb12 100644 --- a/bot/cogs/filtering.py +++ b/bot/cogs/filtering.py @@ -214,7 +214,9 @@ class Filtering(Cog):                          additional_embeds = None                          additional_embeds_msg = None -                        if filter_name == "filter_invites": +                        # The function returns True for invalid invites. +                        # They have no data so additional embeds can't be created for them. +                        if filter_name == "filter_invites" and match is not True:                              additional_embeds = []                              for invite, data in match.items():                                  embed = discord.Embed(description=( diff --git a/bot/cogs/help_channels.py b/bot/cogs/help_channels.py index a20fe2b05..d2a55fba6 100644 --- a/bot/cogs/help_channels.py +++ b/bot/cogs/help_channels.py @@ -438,13 +438,13 @@ class HelpChannels(Scheduler, commands.Cog):          """Return True if `member` has the 'Help Cooldown' role."""          return any(constants.Roles.help_cooldown == role.id for role in member.roles) -    def is_dormant_message(self, message: t.Optional[discord.Message]) -> bool: -        """Return True if the contents of the `message` match `DORMANT_MSG`.""" +    def match_bot_embed(self, message: t.Optional[discord.Message], description: str) -> bool: +        """Return `True` if the bot's `message`'s embed description matches `description`."""          if not message or not message.embeds:              return False          embed = message.embeds[0] -        return message.author == self.bot.user and embed.description.strip() == DORMANT_MSG.strip() +        return message.author == self.bot.user and embed.description.strip() == description.strip()      @staticmethod      def is_in_category(channel: discord.TextChannel, category_id: int) -> bool: @@ -461,7 +461,11 @@ class HelpChannels(Scheduler, commands.Cog):          """          log.trace(f"Handling in-use channel #{channel} ({channel.id}).") -        idle_seconds = constants.HelpChannels.idle_minutes * 60 +        if not await self.is_empty(channel): +            idle_seconds = constants.HelpChannels.idle_minutes * 60 +        else: +            idle_seconds = constants.HelpChannels.deleted_idle_minutes * 60 +          time_elapsed = await self.get_idle_time(channel)          if time_elapsed is None or time_elapsed >= idle_seconds: @@ -713,6 +717,32 @@ class HelpChannels(Scheduler, commands.Cog):          # be put in the queue.          await self.move_to_available() +    @commands.Cog.listener() +    async def on_message_delete(self, msg: discord.Message) -> None: +        """ +        Reschedule an in-use channel to become dormant sooner if the channel is empty. + +        The new time for the dormant task is configured with `HelpChannels.deleted_idle_minutes`. +        """ +        if not self.is_in_category(msg.channel, constants.Categories.help_in_use): +            return + +        if not await self.is_empty(msg.channel): +            return + +        log.info(f"Claimant of #{msg.channel} ({msg.author}) deleted message, channel is empty now. Rescheduling task.") + +        # Cancel existing dormant task before scheduling new. +        self.cancel_task(msg.channel.id) + +        task = TaskData(constants.HelpChannels.deleted_idle_minutes * 60, self.move_idle_channel(msg.channel)) +        self.schedule_task(msg.channel.id, task) + +    async def is_empty(self, channel: discord.TextChannel) -> bool: +        """Return True if the most recent message in `channel` is the bot's `AVAILABLE_MSG`.""" +        msg = await self.get_last_message(channel) +        return self.match_bot_embed(msg, AVAILABLE_MSG) +      async def reset_send_permissions(self) -> None:          """Reset send permissions in the Available category for claimants."""          log.trace("Resetting send permissions in the Available category.") @@ -788,7 +818,7 @@ class HelpChannels(Scheduler, commands.Cog):          embed = discord.Embed(description=AVAILABLE_MSG)          msg = await self.get_last_message(channel) -        if self.is_dormant_message(msg): +        if self.match_bot_embed(msg, DORMANT_MSG):              log.trace(f"Found dormant message {msg.id} in {channel_info}; editing it.")              await msg.edit(embed=embed)          else: diff --git a/bot/cogs/stats.py b/bot/cogs/stats.py index b55497e68..4ebb6423c 100644 --- a/bot/cogs/stats.py +++ b/bot/cogs/stats.py @@ -6,7 +6,7 @@ from discord.ext.commands import Cog, Context  from discord.ext.tasks import loop  from bot.bot import Bot -from bot.constants import Channels, Guild, Stats as StatConf +from bot.constants import Categories, Channels, Guild, Stats as StatConf  CHANNEL_NAME_OVERRIDES = { @@ -36,6 +36,12 @@ class Stats(Cog):          if message.guild.id != Guild.id:              return +        if message.channel.category.id == Categories.modmail: +            if message.channel.id != Channels.incidents: +                # Do not report modmail channels to stats, there are too many +                # of them for interesting statistics to be drawn out of this. +                return +          reformatted_name = message.channel.name.replace('-', '_')          if CHANNEL_NAME_OVERRIDES.get(message.channel.id): diff --git a/bot/cogs/tags.py b/bot/cogs/tags.py index a813ffff5..bc7f53f68 100644 --- a/bot/cogs/tags.py +++ b/bot/cogs/tags.py @@ -4,7 +4,7 @@ import time  from pathlib import Path  from typing import Callable, Dict, Iterable, List, Optional -from discord import Colour, Embed +from discord import Colour, Embed, Member  from discord.ext.commands import Cog, Context, group  from bot import constants @@ -35,21 +35,36 @@ class Tags(Cog):      @staticmethod      def get_tags() -> dict:          """Get all tags.""" -        # Save all tags in memory.          cache = {} -        tag_files = Path("bot", "resources", "tags").iterdir() -        for file in tag_files: -            tag_title = file.stem -            tag = { -                "title": tag_title, -                "embed": { -                    "description": file.read_text(encoding="utf-8") + +        base_path = Path("bot", "resources", "tags") +        for file in base_path.glob("**/*"): +            if file.is_file(): +                tag_title = file.stem +                tag = { +                    "title": tag_title, +                    "embed": { +                        "description": file.read_text(), +                    }, +                    "restricted_to": "developers",                  } -            } -            cache[tag_title] = tag + +                # Convert to a list to allow negative indexing. +                parents = list(file.relative_to(base_path).parents) +                if len(parents) > 1: +                    # -1 would be '.' hence -2 is used as the index. +                    tag["restricted_to"] = parents[-2].name + +                cache[tag_title] = tag +          return cache      @staticmethod +    def check_accessibility(user: Member, tag: dict) -> bool: +        """Check if user can access a tag.""" +        return tag["restricted_to"].lower() in [role.name.lower() for role in user.roles] + +    @staticmethod      def _fuzzy_search(search: str, target: str) -> float:          """A simple scoring algorithm based on how many letters are found / total, with order in mind."""          current, index = 0, 0 @@ -93,7 +108,7 @@ class Tags(Cog):              return self._get_suggestions(tag_name)          return found -    def _get_tags_via_content(self, check: Callable[[Iterable], bool], keywords: str) -> list: +    def _get_tags_via_content(self, check: Callable[[Iterable], bool], keywords: str, user: Member) -> list:          """          Search for tags via contents. @@ -114,7 +129,8 @@ class Tags(Cog):          matching_tags = []          for tag in self._cache.values(): -            if check(query in tag['embed']['description'].casefold() for query in keywords_processed): +            matches = (query in tag['embed']['description'].casefold() for query in keywords_processed) +            if self.check_accessibility(user, tag) and check(matches):                  matching_tags.append(tag)          return matching_tags @@ -152,7 +168,7 @@ class Tags(Cog):          Only search for tags that has ALL the keywords.          """ -        matching_tags = self._get_tags_via_content(all, keywords) +        matching_tags = self._get_tags_via_content(all, keywords, ctx.author)          await self._send_matching_tags(ctx, keywords, matching_tags)      @search_tag_content.command(name='any') @@ -162,7 +178,7 @@ class Tags(Cog):          Search for tags that has ANY of the keywords.          """ -        matching_tags = self._get_tags_via_content(any, keywords or 'any') +        matching_tags = self._get_tags_via_content(any, keywords or 'any', ctx.author)          await self._send_matching_tags(ctx, keywords, matching_tags)      @tags_group.command(name='get', aliases=('show', 'g')) @@ -198,7 +214,13 @@ class Tags(Cog):              return          if tag_name is not None: -            founds = self._get_tag(tag_name) +            temp_founds = self._get_tag(tag_name) + +            founds = [] + +            for found_tag in temp_founds: +                if self.check_accessibility(ctx.author, found_tag): +                    founds.append(found_tag)              if len(founds) == 1:                  tag = founds[0] @@ -237,7 +259,10 @@ class Tags(Cog):              else:                  embed: Embed = Embed(title="**Current tags**")                  await LinePaginator.paginate( -                    sorted(f"**»**   {tag['title']}" for tag in tags), +                    sorted( +                        f"**»**   {tag['title']}" for tag in tags +                        if self.check_accessibility(ctx.author, tag) +                    ),                      ctx,                      embed,                      footer_text=FOOTER_TEXT, diff --git a/bot/constants.py b/bot/constants.py index 145ae54db..eae083ab4 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -376,6 +376,7 @@ class Categories(metaclass=YAMLGetter):      help_available: int      help_in_use: int      help_dormant: int +    modmail: int  class Channels(metaclass=YAMLGetter): @@ -395,6 +396,7 @@ class Channels(metaclass=YAMLGetter):      esoteric: int      helpers: int      how_to_get_help: int +    incidents: int      message_log: int      meta: int      mod_alerts: int @@ -552,6 +554,7 @@ class HelpChannels(metaclass=YAMLGetter):      claim_minutes: int      cmd_whitelist: List[int]      idle_minutes: int +    deleted_idle_minutes: int      max_available: int      max_total_channels: int      name_prefix: str diff --git a/config-default.yml b/config-default.yml index cee955f20..bb66890a3 100644 --- a/config-default.yml +++ b/config-default.yml @@ -124,6 +124,7 @@ guild:          help_available:                     691405807388196926          help_in_use:                        696958401460043776          help_dormant:                       691405908919451718 +        modmail:                            714494672835444826      channels:          announcements:                              354619224620138496 @@ -170,6 +171,7 @@ guild:          mod_spam:           &MOD_SPAM       620607373828030464          organisation:       &ORGANISATION   551789653284356126          staff_lounge:       &STAFF_LOUNGE   464905259261755392 +        incidents:                          714214212200562749          # Voice          admins_voice:       &ADMINS_VOICE   500734494840717332 @@ -324,6 +326,7 @@ filter:          - poweredbysecurity.online          - ssteam.site          - steamwalletgift.com +        - discord.gift      word_watchlist:          - goo+ks* @@ -537,6 +540,10 @@ help_channels:      # Allowed duration of inactivity before making a channel dormant      idle_minutes: 30 +    # Allowed duration of inactivity when question message deleted +    # and no one other sent before message making channel dormant. +    deleted_idle_minutes: 5 +      # Maximum number of channels to put in the available category      max_available: 2 | 
