diff options
| -rw-r--r-- | Dockerfile | 5 | ||||
| -rw-r--r-- | Pipfile | 2 | ||||
| -rw-r--r-- | Pipfile.lock | 123 | ||||
| -rw-r--r-- | bot/cogs/filtering.py | 4 | ||||
| -rw-r--r-- | bot/cogs/help_channels.py | 89 | ||||
| -rw-r--r-- | bot/cogs/moderation/scheduler.py | 4 | ||||
| -rw-r--r-- | bot/cogs/moderation/silence.py | 3 | ||||
| -rw-r--r-- | bot/cogs/reminders.py | 4 | ||||
| -rw-r--r-- | bot/cogs/source.py | 22 | ||||
| -rw-r--r-- | bot/cogs/utils.py | 2 | ||||
| -rw-r--r-- | bot/pagination.py | 10 | ||||
| -rw-r--r-- | bot/utils/redis_cache.py | 17 | ||||
| -rw-r--r-- | bot/utils/regex.py | 2 | ||||
| -rw-r--r-- | config-default.yml | 4 | 
14 files changed, 155 insertions, 136 deletions
| diff --git a/Dockerfile b/Dockerfile index 0b1674e7a..06a538b2a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,11 +6,6 @@ ENV PIP_NO_CACHE_DIR=false \      PIPENV_IGNORE_VIRTUALENVS=1 \      PIPENV_NOSPIN=1 -RUN apt-get -y update \ -    && apt-get install -y \ -        git \ -    && rm -rf /var/lib/apt/lists/* -  # Install pipenv  RUN pip install -U pipenv @@ -12,7 +12,7 @@ beautifulsoup4 = "~=4.9"  colorama = {version = "~=0.4.3",sys_platform = "== 'win32'"}  coloredlogs = "~=14.0"  deepdiff = "~=4.0" -discord-py = {git = "https://github.com/Rapptz/discord.py.git",ref = "0bc15fa130b8f01fe2d67446a2184d474b0d0ba7"} +discord.py = "~=1.4.0"  fakeredis = "~=1.4"  feedparser = "~=5.2"  fuzzywuzzy = "~=0.17" diff --git a/Pipfile.lock b/Pipfile.lock index c8cd96d3d..50ddd478c 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@  {      "_meta": {          "hash": { -            "sha256": "eab4852974d26bd2c10362540c3e01d34af62446cb4e1915ec9a0bf2bddf4d94" +            "sha256": "1905fd7eb15074ddbf04f2177b6cdd65edc4c74cb5fcbf4e6ca08ef649ba8a3c"          },          "pipfile-spec": 6,          "requires": { @@ -60,11 +60,11 @@          },          "aiormq": {              "hashes": [ -                "sha256:41a9d4eb17db805f30ed172f3f609fe0c2b16657fb15b1b67df19d251dd93c0d", -                "sha256:7c19477a9450824cb79f9949fd238f4148e2c0dca67756a2868863c387209f04" +                "sha256:106695a836f19c1af6c46b58e8aac80e00f86c5b3287a3c6483a1ee369cc95c9", +                "sha256:9f6dbf6155fe2b7a3d24bf68de97fb812db0fac0a54e96bc1af14ea95078ba7f"              ],              "markers": "python_version >= '3.6'", -            "version": "==3.2.2" +            "version": "==3.2.3"          },          "alabaster": {              "hashes": [ @@ -177,9 +177,22 @@              "index": "pypi",              "version": "==4.3.2"          }, -        "discord-py": { -            "git": "https://github.com/Rapptz/discord.py.git", -            "ref": "0bc15fa130b8f01fe2d67446a2184d474b0d0ba7" +        "discord": { +            "hashes": [ +                "sha256:9d4debb4a37845543bd4b92cb195bc53a302797333e768e70344222857ff1559", +                "sha256:ff6653655e342e7721dfb3f10421345fd852c2a33f2cca912b1c39b3778a9429" +            ], +            "index": "pypi", +            "py": "~=1.4.0", +            "version": "==1.0.1" +        }, +        "discord.py": { +            "hashes": [ +                "sha256:2b1846bfa382b54f4eace8e437a9f59f185388c5b08749ac0e1bbd98e05bfde5", +                "sha256:f3db9531fccc391f51de65cfa46133106a9ba12ff2927aca6c14bffd3b7f17b5" +            ], +            "markers": "python_full_version >= '3.5.3'", +            "version": "==1.4.0"          },          "docutils": {              "hashes": [ @@ -191,11 +204,11 @@          },          "fakeredis": {              "hashes": [ -                "sha256:4d170886865a91dbc8b7f8cbd4e5d488f4c5f2f25dfae127f001617bbe9e8f97", -                "sha256:647b2593d349d9d4e566c8dadb2e4c71ba35be5bdc4f1f7ac2d565a12a965053" +                "sha256:790c85ad0f3b2967aba1f51767021bc59760fcb612159584be018ea7384f7fd2", +                "sha256:fdfe06f277092d022c271fcaefdc1f0c8d9bfa8cb15374cae41d66a20bd96d2b"              ],              "index": "pypi", -            "version": "==1.4.1" +            "version": "==1.4.2"          },          "feedparser": {              "hashes": [ @@ -542,11 +555,11 @@          },          "sentry-sdk": {              "hashes": [ -                "sha256:2de15b13836fa3522815a933bd9c887c77f4868071043349f94f1b896c1bcfb8", -                "sha256:38bb09d0277117f76507c8728d9a5156f09a47ac5175bb8072513859d19a593b" +                "sha256:21b17d6aa064c0fb703a7c00f77cf6c9c497cf2f83345c28892980a5e742d116", +                "sha256:4fc97114c77d005467b9b1a29f042e2bc01923cb683b0ef0bbda46e79fa12532"              ],              "index": "pypi", -            "version": "==0.16.2" +            "version": "==0.16.3"          },          "six": {              "hashes": [ @@ -642,14 +655,6 @@              "index": "pypi",              "version": "==3.3.0"          }, -        "typing-extensions": { -            "hashes": [ -                "sha256:6e95524d8a547a91e08f404ae485bbb71962de46967e1b71a0cb89af24e761c5", -                "sha256:79ee589a3caca649a9bfd2a8de4709837400dfa00b6cc81962a1e6a1815969ae", -                "sha256:f8d2bd89d25bc39dabe7d23df520442fa1d8969b82544370e03d88b5a591c392" -            ], -            "version": "==3.7.4.2" -        },          "urllib3": {              "hashes": [                  "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a", @@ -658,56 +663,28 @@              "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",              "version": "==1.25.10"          }, -        "websockets": { -            "hashes": [ -                "sha256:0e4fb4de42701340bd2353bb2eee45314651caa6ccee80dbd5f5d5978888fed5", -                "sha256:1d3f1bf059d04a4e0eb4985a887d49195e15ebabc42364f4eb564b1d065793f5", -                "sha256:20891f0dddade307ffddf593c733a3fdb6b83e6f9eef85908113e628fa5a8308", -                "sha256:295359a2cc78736737dd88c343cd0747546b2174b5e1adc223824bcaf3e164cb", -                "sha256:2db62a9142e88535038a6bcfea70ef9447696ea77891aebb730a333a51ed559a", -                "sha256:3762791ab8b38948f0c4d281c8b2ddfa99b7e510e46bd8dfa942a5fff621068c", -                "sha256:3db87421956f1b0779a7564915875ba774295cc86e81bc671631379371af1170", -                "sha256:3ef56fcc7b1ff90de46ccd5a687bbd13a3180132268c4254fc0fa44ecf4fc422", -                "sha256:4f9f7d28ce1d8f1295717c2c25b732c2bc0645db3215cf757551c392177d7cb8", -                "sha256:5c01fd846263a75bc8a2b9542606927cfad57e7282965d96b93c387622487485", -                "sha256:5c65d2da8c6bce0fca2528f69f44b2f977e06954c8512a952222cea50dad430f", -                "sha256:751a556205d8245ff94aeef23546a1113b1dd4f6e4d102ded66c39b99c2ce6c8", -                "sha256:7ff46d441db78241f4c6c27b3868c9ae71473fe03341340d2dfdbe8d79310acc", -                "sha256:965889d9f0e2a75edd81a07592d0ced54daa5b0785f57dc429c378edbcffe779", -                "sha256:9b248ba3dd8a03b1a10b19efe7d4f7fa41d158fdaa95e2cf65af5a7b95a4f989", -                "sha256:9bef37ee224e104a413f0780e29adb3e514a5b698aabe0d969a6ba426b8435d1", -                "sha256:c1ec8db4fac31850286b7cd3b9c0e1b944204668b8eb721674916d4e28744092", -                "sha256:c8a116feafdb1f84607cb3b14aa1418424ae71fee131642fc568d21423b51824", -                "sha256:ce85b06a10fc65e6143518b96d3dca27b081a740bae261c2fb20375801a9d56d", -                "sha256:d705f8aeecdf3262379644e4b55107a3b55860eb812b673b28d0fbc347a60c55", -                "sha256:e898a0863421650f0bebac8ba40840fc02258ef4714cb7e1fd76b6a6354bda36", -                "sha256:f8a7bff6e8664afc4e6c28b983845c5bc14965030e3fb98789734d416af77c4b" -            ], -            "markers": "python_full_version >= '3.6.1'", -            "version": "==8.1" -        },          "yarl": {              "hashes": [ -                "sha256:1707230e1ea48ea06a3e20acb4ce05a38d2465bd9566c21f48f6212a88e47536", -                "sha256:1f269e8e6676193a94635399a77c9059e1826fb6265c9204c9e5a8ccd36006e1", -                "sha256:2657716c1fc998f5f2675c0ee6ce91282e0da0ea9e4a94b584bb1917e11c1559", -                "sha256:431faa6858f0ea323714d8b7b4a7da1db2eeb9403607f0eaa3800ab2c5a4b627", -                "sha256:5bbcb195da7de57f4508b7508c33f7593e9516e27732d08b9aad8586c7b8c384", -                "sha256:5c82f5b1499342339f22c83b97dbe2b8a09e47163fab86cd934a8dd46620e0fb", -                "sha256:5d410f69b4f92c5e1e2a8ffb73337cd8a274388c6975091735795588a538e605", -                "sha256:66b4f345e9573e004b1af184bc00431145cf5e089a4dcc1351505c1f5750192c", -                "sha256:875b2a741ce0208f3b818008a859ab5d0f461e98a32bbdc6af82231a9e761c55", -                "sha256:9a3266b047d15e78bba38c8455bf68b391c040231ca5965ef867f7cbbc60bde5", -                "sha256:9a592c4aa642249e9bdaf76897d90feeb08118626b363a6be8788a9b300274b5", -                "sha256:a1772068401d425e803999dada29a6babf041786e08be5e79ef63c9ecc4c9575", -                "sha256:b065a5c3e050395ae563019253cc6c769a50fd82d7fa92d07476273521d56b7c", -                "sha256:b325fefd574ebef50e391a1072d1712a60348ca29c183e1d546c9d87fec2cd32", -                "sha256:cf5eb664910d759bbae0b76d060d6e21f8af5098242d66c448bbebaf2a7bfa70", -                "sha256:f058b6541477022c7b54db37229f87dacf3b565de4f901ff5a0a78556a174fea", -                "sha256:f5cfed0766837303f688196aa7002730d62c5cc802d98c6395ea1feb87252727" +                "sha256:040b237f58ff7d800e6e0fd89c8439b841f777dd99b4a9cca04d6935564b9409", +                "sha256:17668ec6722b1b7a3a05cc0167659f6c95b436d25a36c2d52db0eca7d3f72593", +                "sha256:3a584b28086bc93c888a6c2aa5c92ed1ae20932f078c46509a66dce9ea5533f2", +                "sha256:4439be27e4eee76c7632c2427ca5e73703151b22cae23e64adb243a9c2f565d8", +                "sha256:48e918b05850fffb070a496d2b5f97fc31d15d94ca33d3d08a4f86e26d4e7c5d", +                "sha256:9102b59e8337f9874638fcfc9ac3734a0cfadb100e47d55c20d0dc6087fb4692", +                "sha256:9b930776c0ae0c691776f4d2891ebc5362af86f152dd0da463a6614074cb1b02", +                "sha256:b3b9ad80f8b68519cc3372a6ca85ae02cc5a8807723ac366b53c0f089db19e4a", +                "sha256:bc2f976c0e918659f723401c4f834deb8a8e7798a71be4382e024bcc3f7e23a8", +                "sha256:c22c75b5f394f3d47105045ea551e08a3e804dc7e01b37800ca35b58f856c3d6", +                "sha256:c52ce2883dc193824989a9b97a76ca86ecd1fa7955b14f87bf367a61b6232511", +                "sha256:ce584af5de8830d8701b8979b18fcf450cef9a382b1a3c8ef189bedc408faf1e", +                "sha256:da456eeec17fa8aa4594d9a9f27c0b1060b6a75f2419fe0c00609587b2695f4a", +                "sha256:db6db0f45d2c63ddb1a9d18d1b9b22f308e52c83638c26b422d520a815c4b3fb", +                "sha256:df89642981b94e7db5596818499c4b2219028f2a528c9c37cc1de45bf2fd3a3f", +                "sha256:f18d68f2be6bf0e89f1521af2b1bb46e66ab0018faafa81d70f358153170a317", +                "sha256:f379b7f83f23fe12823085cd6b906edc49df969eb99757f58ff382349a3303c6"              ],              "markers": "python_version >= '3.5'", -            "version": "==1.5.0" +            "version": "==1.5.1"          }      },      "develop": { @@ -728,11 +705,11 @@          },          "cfgv": {              "hashes": [ -                "sha256:1ccf53320421aeeb915275a196e23b3b8ae87dea8ac6698b1638001d4a486d53", -                "sha256:c8e8f552ffcc6194f4e18dd4f68d9aef0c0d58ae7e7be8c82bee3c5e9edfa513" +                "sha256:32e43d604bbe7896fe7c248a9c2276447dbef840feb28fe20494f62af110211d", +                "sha256:cf22deb93d4bcf92f345a5c3cd39d3d41d6340adc60c78bbbd6588c384fda6a1"              ],              "markers": "python_full_version >= '3.6.1'", -            "version": "==3.1.0" +            "version": "==3.2.0"          },          "coverage": {              "hashes": [ @@ -968,11 +945,11 @@          },          "virtualenv": {              "hashes": [ -                "sha256:688a61d7976d82b92f7906c367e83bb4b3f0af96f8f75bfcd3da95608fe8ac6c", -                "sha256:8f582a030156282a9ee9d319984b759a232b07f86048c1d6a9e394afa44e78c8" +                "sha256:7b54fd606a1b85f83de49ad8d80dbec08e983a2d2f96685045b262ebc7481ee5", +                "sha256:8cd7b2a4850b003a11be2fc213e206419efab41115cc14bca20e69654f2ac08e"              ],              "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", -            "version": "==20.0.28" +            "version": "==20.0.30"          }      }  } diff --git a/bot/cogs/filtering.py b/bot/cogs/filtering.py index cdad1d01d..93cc1c655 100644 --- a/bot/cogs/filtering.py +++ b/bot/cogs/filtering.py @@ -99,6 +99,10 @@ class Filtering(Cog):          self.bot.loop.create_task(self.reschedule_offensive_msg_deletion()) +    def cog_unload(self) -> None: +        """Cancel scheduled tasks.""" +        self.scheduler.cancel_all() +      def _get_filterlist_items(self, list_type: str, *, allowed: bool) -> list:          """Fetch items from the filter_list_cache."""          return self.bot.filter_list_cache[f"{list_type.upper()}.{allowed}"].keys() diff --git a/bot/cogs/help_channels.py b/bot/cogs/help_channels.py index 1be980472..57094751e 100644 --- a/bot/cogs/help_channels.py +++ b/bot/cogs/help_channels.py @@ -215,9 +215,6 @@ class HelpChannels(commands.Cog):          log.trace("close command invoked; checking if the channel is in-use.")          if ctx.channel.category == self.in_use_category:              if await self.dormant_check(ctx): - -                # Remove the claimant and the cooldown role -                await self.help_channel_claimants.delete(ctx.channel.id)                  await self.remove_cooldown_role(ctx.author)                  # Ignore missing task when cooldown has passed but the channel still isn't dormant. @@ -551,20 +548,9 @@ class HelpChannels(commands.Cog):          A caller argument is provided for metrics.          """ -        msg_id = await self.question_messages.pop(channel.id) - -        try: -            await self.bot.http.unpin_message(channel.id, msg_id) -        except discord.HTTPException as e: -            if e.code == 10008: -                log.trace(f"Message {msg_id} don't exist, can't unpin.") -            else: -                log.warn(f"Got unexpected status {e.code} when unpinning message {msg_id}: {e.text}") -        else: -            log.trace(f"Unpinned message {msg_id}.") -          log.info(f"Moving #{channel} ({channel.id}) to the Dormant category.") +        await self.help_channel_claimants.delete(channel.id)          await self.move_to_bottom_position(              channel=channel,              category_id=constants.Categories.help_dormant, @@ -587,6 +573,8 @@ class HelpChannels(commands.Cog):          embed = discord.Embed(description=DORMANT_MSG)          await channel.send(embed=embed) +        await self.unpin(channel) +          log.trace(f"Pushing #{channel} ({channel.id}) into the channel queue.")          self.channel_queue.put_nowait(channel)          self.report_stats() @@ -704,15 +692,8 @@ class HelpChannels(commands.Cog):              log.info(f"Channel #{channel} was claimed by `{message.author.id}`.")              await self.move_to_in_use(channel)              await self.revoke_send_permissions(message.author) -            # Pin message for better access and store this to cache -            try: -                await message.pin() -            except discord.NotFound: -                log.info(f"Pinning message {message.id} ({channel}) failed because message got deleted.") -            except discord.HTTPException as e: -                log.info(f"Pinning message {message.id} ({channel.id}) failed with code {e.code}", exc_info=e) -            else: -                await self.question_messages.set(channel.id, message.id) + +            await self.pin(message)              # Add user with channel for dormant check.              await self.help_channel_claimants.set(channel.id, message.author.id) @@ -754,9 +735,22 @@ class HelpChannels(commands.Cog):          self.scheduler.schedule_later(delay, msg.channel.id, self.move_idle_channel(msg.channel))      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) +        """Return True if there's an AVAILABLE_MSG and the messages leading up are bot messages.""" +        log.trace(f"Checking if #{channel} ({channel.id}) is empty.") + +        # A limit of 100 results in a single API call. +        # If AVAILABLE_MSG isn't found within 100 messages, then assume the channel is not empty. +        # Not gonna do an extensive search for it cause it's too expensive. +        async for msg in channel.history(limit=100): +            if not msg.author.bot: +                log.trace(f"#{channel} ({channel.id}) has a non-bot message.") +                return False + +            if self.match_bot_embed(msg, AVAILABLE_MSG): +                log.trace(f"#{channel} ({channel.id}) has the available message embed.") +                return True + +        return False      async def check_cooldowns(self) -> None:          """Remove expired cooldowns and re-schedule active ones.""" @@ -863,6 +857,47 @@ class HelpChannels(commands.Cog):          log.trace(f"Channel #{channel} ({channel_id}) retrieved.")          return channel +    async def pin_wrapper(self, msg_id: int, channel: discord.TextChannel, *, pin: bool) -> bool: +        """ +        Pin message `msg_id` in `channel` if `pin` is True or unpin if it's False. + +        Return True if successful and False otherwise. +        """ +        channel_str = f"#{channel} ({channel.id})" +        if pin: +            func = self.bot.http.pin_message +            verb = "pin" +        else: +            func = self.bot.http.unpin_message +            verb = "unpin" + +        try: +            await func(channel.id, msg_id) +        except discord.HTTPException as e: +            if e.code == 10008: +                log.debug(f"Message {msg_id} in {channel_str} doesn't exist; can't {verb}.") +            else: +                log.exception( +                    f"Error {verb}ning message {msg_id} in {channel_str}: {e.status} ({e.code})" +                ) +            return False +        else: +            log.trace(f"{verb.capitalize()}ned message {msg_id} in {channel_str}.") +            return True + +    async def pin(self, message: discord.Message) -> None: +        """Pin an initial question `message` and store it in a cache.""" +        if await self.pin_wrapper(message.id, message.channel, pin=True): +            await self.question_messages.set(message.channel.id, message.id) + +    async def unpin(self, channel: discord.TextChannel) -> None: +        """Unpin the initial question message sent in `channel`.""" +        msg_id = await self.question_messages.pop(channel.id) +        if msg_id is None: +            log.debug(f"#{channel} ({channel.id}) doesn't have a message pinned.") +        else: +            await self.pin_wrapper(msg_id, channel, pin=False) +      async def wait_for_dormant_channel(self) -> discord.TextChannel:          """Wait for a dormant channel to become available in the queue and return it."""          log.trace("Waiting for a dormant channel.") diff --git a/bot/cogs/moderation/scheduler.py b/bot/cogs/moderation/scheduler.py index 601e238c9..75028d851 100644 --- a/bot/cogs/moderation/scheduler.py +++ b/bot/cogs/moderation/scheduler.py @@ -31,6 +31,10 @@ class InfractionScheduler:          self.bot.loop.create_task(self.reschedule_infractions(supported_infractions)) +    def cog_unload(self) -> None: +        """Cancel scheduled tasks.""" +        self.scheduler.cancel_all() +      @property      def mod_log(self) -> ModLog:          """Get the currently loaded ModLog cog instance.""" diff --git a/bot/cogs/moderation/silence.py b/bot/cogs/moderation/silence.py index ae4fb7b64..f8a6592bc 100644 --- a/bot/cogs/moderation/silence.py +++ b/bot/cogs/moderation/silence.py @@ -152,7 +152,8 @@ class Silence(commands.Cog):          return False      def cog_unload(self) -> None: -        """Send alert with silenced channels on unload.""" +        """Send alert with silenced channels and cancel scheduled tasks on unload.""" +        self.scheduler.cancel_all()          if self.muted_channels:              channels_string = ''.join(channel.mention for channel in self.muted_channels)              message = f"<@&{Roles.moderators}> channels left silenced on cog unload: {channels_string}" diff --git a/bot/cogs/reminders.py b/bot/cogs/reminders.py index b5998cc0e..670493bcf 100644 --- a/bot/cogs/reminders.py +++ b/bot/cogs/reminders.py @@ -37,6 +37,10 @@ class Reminders(Cog):          self.bot.loop.create_task(self.reschedule_reminders()) +    def cog_unload(self) -> None: +        """Cancel scheduled tasks.""" +        self.scheduler.cancel_all() +      async def reschedule_reminders(self) -> None:          """Get all current reminders from the API and reschedule them."""          await self.bot.wait_until_guild_available() diff --git a/bot/cogs/source.py b/bot/cogs/source.py index f1db745cd..205e0ba81 100644 --- a/bot/cogs/source.py +++ b/bot/cogs/source.py @@ -60,11 +60,12 @@ class BotSource(commands.Cog):          await ctx.send(embed=embed)      def get_source_link(self, source_item: SourceType) -> Tuple[str, str, Optional[int]]: -        """Build GitHub link of source item, return this link, file location and first line number.""" -        if isinstance(source_item, commands.HelpCommand): -            src = type(source_item) -            filename = inspect.getsourcefile(src) -        elif isinstance(source_item, commands.Command): +        """ +        Build GitHub link of source item, return this link, file location and first line number. + +        Raise BadArgument if `source_item` is a dynamically-created object (e.g. via internal eval). +        """ +        if isinstance(source_item, commands.Command):              if source_item.cog_name == "Alias":                  cmd_name = source_item.callback.__name__.replace("_alias", "")                  cmd = self.bot.get_command(cmd_name.replace("_", " ")) @@ -78,10 +79,17 @@ class BotSource(commands.Cog):              filename = tags_cog._cache[source_item]["location"]          else:              src = type(source_item) -            filename = inspect.getsourcefile(src) +            try: +                filename = inspect.getsourcefile(src) +            except TypeError: +                raise commands.BadArgument("Cannot get source for a dynamically-created object.")          if not isinstance(source_item, str): -            lines, first_line_no = inspect.getsourcelines(src) +            try: +                lines, first_line_no = inspect.getsourcelines(src) +            except OSError: +                raise commands.BadArgument("Cannot get source for a dynamically-created object.") +              lines_extension = f"#L{first_line_no}-L{first_line_no+len(lines)-1}"          else:              first_line_no = None diff --git a/bot/cogs/utils.py b/bot/cogs/utils.py index 91c6cb36e..d96abbd5a 100644 --- a/bot/cogs/utils.py +++ b/bot/cogs/utils.py @@ -232,6 +232,8 @@ class Utils(Cog):          A maximum of 20 options can be provided, as Discord supports a max of 20          reactions on a single message.          """ +        if len(title) > 256: +            raise BadArgument("The title cannot be longer than 256 characters.")          if len(options) < 2:              raise BadArgument("Please provide at least 2 options.")          if len(options) > 20: diff --git a/bot/pagination.py b/bot/pagination.py index 94c2d7c0c..bab98cacf 100644 --- a/bot/pagination.py +++ b/bot/pagination.py @@ -313,8 +313,6 @@ class LinePaginator(Paginator):                  log.debug(f"Got first page reaction - changing to page 1/{len(paginator.pages)}") -                embed.description = "" -                await message.edit(embed=embed)                  embed.description = paginator.pages[current_page]                  if footer_text:                      embed.set_footer(text=f"{footer_text} (Page {current_page + 1}/{len(paginator.pages)})") @@ -328,8 +326,6 @@ class LinePaginator(Paginator):                  log.debug(f"Got last page reaction - changing to page {current_page + 1}/{len(paginator.pages)}") -                embed.description = "" -                await message.edit(embed=embed)                  embed.description = paginator.pages[current_page]                  if footer_text:                      embed.set_footer(text=f"{footer_text} (Page {current_page + 1}/{len(paginator.pages)})") @@ -347,8 +343,6 @@ class LinePaginator(Paginator):                  current_page -= 1                  log.debug(f"Got previous page reaction - changing to page {current_page + 1}/{len(paginator.pages)}") -                embed.description = "" -                await message.edit(embed=embed)                  embed.description = paginator.pages[current_page]                  if footer_text: @@ -368,8 +362,6 @@ class LinePaginator(Paginator):                  current_page += 1                  log.debug(f"Got next page reaction - changing to page {current_page + 1}/{len(paginator.pages)}") -                embed.description = "" -                await message.edit(embed=embed)                  embed.description = paginator.pages[current_page]                  if footer_text: @@ -532,8 +524,6 @@ class ImagePaginator(Paginator):                  reaction_type = "next"              # Magic happens here, after page and reaction_type is set -            embed.description = "" -            await message.edit(embed=embed)              embed.description = paginator.pages[current_page]              image = paginator.images[current_page] diff --git a/bot/utils/redis_cache.py b/bot/utils/redis_cache.py index 58cfe1df5..52b689b49 100644 --- a/bot/utils/redis_cache.py +++ b/bot/utils/redis_cache.py @@ -226,7 +226,6 @@ class RedisCache:          for attribute in vars(instance).values():              if isinstance(attribute, Bot):                  self.bot = attribute -                self._redis = self.bot.redis_session                  return self          else:              error_message = ( @@ -251,7 +250,7 @@ class RedisCache:          value = self._value_to_typestring(value)          log.trace(f"Setting {key} to {value}.") -        await self._redis.hset(self._namespace, key, value) +        await self.bot.redis_session.hset(self._namespace, key, value)      async def get(self, key: RedisKeyType, default: Optional[RedisValueType] = None) -> Optional[RedisValueType]:          """Get an item from the Redis cache.""" @@ -259,7 +258,7 @@ class RedisCache:          key = self._key_to_typestring(key)          log.trace(f"Attempting to retrieve {key}.") -        value = await self._redis.hget(self._namespace, key) +        value = await self.bot.redis_session.hget(self._namespace, key)          if value is None:              log.trace(f"Value not found, returning default value {default}") @@ -281,7 +280,7 @@ class RedisCache:          key = self._key_to_typestring(key)          log.trace(f"Attempting to delete {key}.") -        return await self._redis.hdel(self._namespace, key) +        return await self.bot.redis_session.hdel(self._namespace, key)      async def contains(self, key: RedisKeyType) -> bool:          """ @@ -291,7 +290,7 @@ class RedisCache:          """          await self._validate_cache()          key = self._key_to_typestring(key) -        exists = await self._redis.hexists(self._namespace, key) +        exists = await self.bot.redis_session.hexists(self._namespace, key)          log.trace(f"Testing if {key} exists in the RedisCache - Result is {exists}")          return exists @@ -314,7 +313,7 @@ class RedisCache:          """          await self._validate_cache()          items = self._dict_from_typestring( -            await self._redis.hgetall(self._namespace) +            await self.bot.redis_session.hgetall(self._namespace)          ).items()          log.trace(f"Retrieving all key/value pairs from cache, total of {len(items)} items.") @@ -323,7 +322,7 @@ class RedisCache:      async def length(self) -> int:          """Return the number of items in the Redis cache."""          await self._validate_cache() -        number_of_items = await self._redis.hlen(self._namespace) +        number_of_items = await self.bot.redis_session.hlen(self._namespace)          log.trace(f"Returning length. Result is {number_of_items}.")          return number_of_items @@ -335,7 +334,7 @@ class RedisCache:          """Deletes the entire hash from the Redis cache."""          await self._validate_cache()          log.trace("Clearing the cache of all key/value pairs.") -        await self._redis.delete(self._namespace) +        await self.bot.redis_session.delete(self._namespace)      async def pop(self, key: RedisKeyType, default: Optional[RedisValueType] = None) -> RedisValueType:          """Get the item, remove it from the cache, and provide a default if not found.""" @@ -364,7 +363,7 @@ class RedisCache:          """          await self._validate_cache()          log.trace(f"Updating the cache with the following items:\n{items}") -        await self._redis.hmset_dict(self._namespace, self._dict_to_typestring(items)) +        await self.bot.redis_session.hmset_dict(self._namespace, self._dict_to_typestring(items))      async def increment(self, key: RedisKeyType, amount: Optional[int, float] = 1) -> None:          """ diff --git a/bot/utils/regex.py b/bot/utils/regex.py index d194f93cb..0d2068f90 100644 --- a/bot/utils/regex.py +++ b/bot/utils/regex.py @@ -7,6 +7,6 @@ INVITE_RE = re.compile(      r"discord(?:[\.,]|dot)me|"                        # or discord.me      r"discord(?:[\.,]|dot)io"                         # or discord.io.      r")(?:[\/]|slash)"                                # / or 'slash' -    r"([a-zA-Z0-9]+)",                                # the invite code itself +    r"([a-zA-Z0-9\-]+)",                              # the invite code itself      flags=re.IGNORECASE  ) diff --git a/config-default.yml b/config-default.yml index aacbe170f..4bd90511c 100644 --- a/config-default.yml +++ b/config-default.yml @@ -432,8 +432,8 @@ 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. +    # Allowed duration of inactivity when channel is empty (due to deleted messages) +    # before message making a channel dormant      deleted_idle_minutes: 5      # Maximum number of channels to put in the available category | 
