diff options
| -rw-r--r-- | bot/converters.py | 15 | ||||
| -rw-r--r-- | bot/exts/filters/antispam.py | 22 | ||||
| -rw-r--r-- | bot/exts/info/doc/_cog.py | 9 | ||||
| -rw-r--r-- | bot/exts/info/doc/_inventory_parser.py | 22 | 
4 files changed, 48 insertions, 20 deletions
diff --git a/bot/converters.py b/bot/converters.py index 8b0890830..c96e2c984 100644 --- a/bot/converters.py +++ b/bot/converters.py @@ -235,11 +235,16 @@ class Inventory(Converter):      async def convert(ctx: Context, url: str) -> t.Tuple[str, _inventory_parser.InventoryDict]:          """Convert url to Intersphinx inventory URL."""          await ctx.trigger_typing() -        if (inventory := await _inventory_parser.fetch_inventory(url)) is None: -            raise BadArgument( -                f"Failed to fetch inventory file after {_inventory_parser.FAILED_REQUEST_ATTEMPTS} attempts." -            ) -        return url, inventory +        try: +            inventory = await _inventory_parser.fetch_inventory(url) +        except _inventory_parser.InvalidHeaderError: +            raise BadArgument("Unable to parse inventory because of invalid header, check if URL is correct.") +        else: +            if inventory is None: +                raise BadArgument( +                    f"Failed to fetch inventory file after {_inventory_parser.FAILED_REQUEST_ATTEMPTS} attempts." +                ) +            return url, inventory  class Snowflake(IDConverter): diff --git a/bot/exts/filters/antispam.py b/bot/exts/filters/antispam.py index 70c1168bf..8bae159d2 100644 --- a/bot/exts/filters/antispam.py +++ b/bot/exts/filters/antispam.py @@ -82,28 +82,34 @@ class DeletionContext:              f"**Rules:** {', '.join(rule for rule in self.rules)}\n"          ) -        # For multiple messages or those with excessive newlines, use the logs API -        if len(self.messages) > 1 or 'newlines' in self.rules: +        messages_as_list = list(self.messages.values()) +        first_message = messages_as_list[0] +        # For multiple messages and those with attachments or excessive newlines, use the logs API +        if any(( +            len(messages_as_list) > 1, +            len(first_message.attachments) > 0, +            first_message.content.count('\n') > 15 +        )):              url = await modlog.upload_log(self.messages.values(), actor_id, self.attachments)              mod_alert_message += f"A complete log of the offending messages can be found [here]({url})"          else:              mod_alert_message += "Message:\n" -            [message] = self.messages.values() -            content = message.clean_content +            content = first_message.clean_content              remaining_chars = 4080 - len(mod_alert_message)              if len(content) > remaining_chars: -                content = content[:remaining_chars] + "..." +                url = await modlog.upload_log([first_message], actor_id, self.attachments) +                log_site_msg = f"The full message can be found [here]({url})" +                content = content[:remaining_chars - (3 + len(log_site_msg))] + "..." -            mod_alert_message += f"{content}" +            mod_alert_message += content -        *_, last_message = self.messages.values()          await modlog.send_log_message(              icon_url=Icons.filtering,              colour=Colour(Colours.soft_red),              title="Spam detected!",              text=mod_alert_message, -            thumbnail=last_message.author.avatar_url_as(static_format="png"), +            thumbnail=first_message.author.avatar_url_as(static_format="png"),              channel_id=Channels.mod_alerts,              ping_everyone=AntiSpamConfig.ping_everyone          ) diff --git a/bot/exts/info/doc/_cog.py b/bot/exts/info/doc/_cog.py index 1624c50f6..e7710db24 100644 --- a/bot/exts/info/doc/_cog.py +++ b/bot/exts/info/doc/_cog.py @@ -22,7 +22,7 @@ from bot.utils.lock import SharedEvent, lock  from bot.utils.messages import send_denial, wait_for_deletion  from bot.utils.scheduling import Scheduler  from . import NAMESPACE, PRIORITY_PACKAGES, _batch_parser, doc_cache -from ._inventory_parser import InventoryDict, fetch_inventory +from ._inventory_parser import InvalidHeaderError, InventoryDict, fetch_inventory  log = logging.getLogger(__name__) @@ -137,7 +137,12 @@ class DocCog(commands.Cog):          The first attempt is rescheduled to execute in `FETCH_RESCHEDULE_DELAY.first` minutes, the subsequent attempts          in `FETCH_RESCHEDULE_DELAY.repeated` minutes.          """ -        package = await fetch_inventory(inventory_url) +        try: +            package = await fetch_inventory(inventory_url) +        except InvalidHeaderError as e: +            # Do not reschedule if the header is invalid, as the request went through but the contents are invalid. +            log.warning(f"Invalid inventory header at {inventory_url}. Reason: {e}") +            return          if not package:              if api_package_name in self.inventory_scheduler: diff --git a/bot/exts/info/doc/_inventory_parser.py b/bot/exts/info/doc/_inventory_parser.py index 80d5841a0..61924d070 100644 --- a/bot/exts/info/doc/_inventory_parser.py +++ b/bot/exts/info/doc/_inventory_parser.py @@ -16,6 +16,10 @@ _V2_LINE_RE = re.compile(r'(?x)(.+?)\s+(\S*:\S*)\s+(-?\d+)\s+?(\S*)\s+(.*)')  InventoryDict = DefaultDict[str, List[Tuple[str, str]]] +class InvalidHeaderError(Exception): +    """Raised when an inventory file has an invalid header.""" + +  class ZlibStreamReader:      """Class used for decoding zlib data of a stream line by line.""" @@ -80,19 +84,25 @@ async def _fetch_inventory(url: str) -> InventoryDict:          stream = response.content          inventory_header = (await stream.readline()).decode().rstrip() -        inventory_version = int(inventory_header[-1:]) -        await stream.readline()  # skip project name -        await stream.readline()  # skip project version +        try: +            inventory_version = int(inventory_header[-1:]) +        except ValueError: +            raise InvalidHeaderError("Unable to convert inventory version header.") + +        has_project_header = (await stream.readline()).startswith(b"# Project") +        has_version_header = (await stream.readline()).startswith(b"# Version") +        if not (has_project_header and has_version_header): +            raise InvalidHeaderError("Inventory missing project or version header.")          if inventory_version == 1:              return await _load_v1(stream)          elif inventory_version == 2:              if b"zlib" not in await stream.readline(): -                raise ValueError(f"Invalid inventory file at url {url}.") +                raise InvalidHeaderError("'zlib' not found in header of compressed inventory.")              return await _load_v2(stream) -        raise ValueError(f"Invalid inventory file at url {url}.") +        raise InvalidHeaderError("Incompatible inventory version.")  async def fetch_inventory(url: str) -> Optional[InventoryDict]: @@ -115,6 +125,8 @@ async def fetch_inventory(url: str) -> Optional[InventoryDict]:                  f"Failed to get inventory from {url}; "                  f"trying again ({attempt}/{FAILED_REQUEST_ATTEMPTS})."              ) +        except InvalidHeaderError: +            raise          except Exception:              log.exception(                  f"An unexpected error has occurred during fetching of {url}; "  |