From 0ed1b4a0b533fe887795ad0fdd9569023b11c631 Mon Sep 17 00:00:00 2001 From: Numerlor Date: Fri, 29 Apr 2022 14:21:32 +0200 Subject: revert bump to markdownify version The new versions introduce conversions which causes the doc command embed to be formatted improperly --- poetry.lock | 18 +++++++----------- pyproject.toml | 2 +- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/poetry.lock b/poetry.lock index 8281394e6..47c0a9129 100644 --- a/poetry.lock +++ b/poetry.lock @@ -142,6 +142,7 @@ async-rediscache = ["async-rediscache[fakeredis] (==0.2.0)"] [package.source] type = "url" url = "https://github.com/python-discord/bot-core/archive/refs/tags/v6.4.0.zip" + [[package]] name = "certifi" version = "2021.10.8" @@ -549,15 +550,15 @@ source = ["Cython (>=0.29.7)"] [[package]] name = "markdownify" -version = "0.10.3" +version = "0.6.1" description = "Convert HTML to markdown." category = "main" optional = false python-versions = "*" [package.dependencies] -beautifulsoup4 = ">=4.9,<5" -six = ">=1.15,<2" +beautifulsoup4 = "*" +six = "*" [[package]] name = "mccabe" @@ -1150,7 +1151,7 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" python-versions = "3.9.*" -content-hash = "a07f619c75f8133982984eb506ad350144829f10c704421f09b3dbe72cd037d8" +content-hash = "6eae4e6ee707059b32bf170c4be9abc7e8b6774558d93cbf46cacbde2108c878" [metadata.files] aiodns = [ @@ -1784,8 +1785,8 @@ lxml = [ {file = "lxml-4.8.0.tar.gz", hash = "sha256:f63f62fc60e6228a4ca9abae28228f35e1bd3ce675013d1dfb828688d50c6e23"}, ] markdownify = [ - {file = "markdownify-0.10.3-py3-none-any.whl", hash = "sha256:edad0ad3896ec7460d05537ad804bbb3614877c6cd0df27b56dee218236d9ce2"}, - {file = "markdownify-0.10.3.tar.gz", hash = "sha256:782e310390cd5e4bde7543ceb644598c78b9824ee9f8d7ef9f9f4f8782e46974"}, + {file = "markdownify-0.6.1-py3-none-any.whl", hash = "sha256:7489fd5c601536996a376c4afbcd1dd034db7690af807120681461e82fbc0acc"}, + {file = "markdownify-0.6.1.tar.gz", hash = "sha256:31d7c13ac2ada8bfc7535a25fee6622ca720e1b5f2d4a9cbc429d167c21f886d"}, ] mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, @@ -1903,11 +1904,6 @@ psutil = [ {file = "psutil-5.9.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:742c34fff804f34f62659279ed5c5b723bb0195e9d7bd9907591de9f8f6558e2"}, {file = "psutil-5.9.0-cp310-cp310-win32.whl", hash = "sha256:8293942e4ce0c5689821f65ce6522ce4786d02af57f13c0195b40e1edb1db61d"}, {file = "psutil-5.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:9b51917c1af3fa35a3f2dabd7ba96a2a4f19df3dec911da73875e1edaf22a40b"}, - {file = "psutil-5.9.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e9805fed4f2a81de98ae5fe38b75a74c6e6ad2df8a5c479594c7629a1fe35f56"}, - {file = "psutil-5.9.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c51f1af02334e4b516ec221ee26b8fdf105032418ca5a5ab9737e8c87dafe203"}, - {file = "psutil-5.9.0-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32acf55cb9a8cbfb29167cd005951df81b567099295291bcfd1027365b36591d"}, - {file = "psutil-5.9.0-cp36-cp36m-win32.whl", hash = "sha256:e5c783d0b1ad6ca8a5d3e7b680468c9c926b804be83a3a8e95141b05c39c9f64"}, - {file = "psutil-5.9.0-cp36-cp36m-win_amd64.whl", hash = "sha256:d62a2796e08dd024b8179bd441cb714e0f81226c352c802fca0fd3f89eeacd94"}, {file = "psutil-5.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3d00a664e31921009a84367266b35ba0aac04a2a6cad09c550a89041034d19a0"}, {file = "psutil-5.9.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7779be4025c540d1d65a2de3f30caeacc49ae7a2152108adeaf42c7534a115ce"}, {file = "psutil-5.9.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:072664401ae6e7c1bfb878c65d7282d4b4391f1bc9a56d5e03b5a490403271b5"}, diff --git a/pyproject.toml b/pyproject.toml index 402d05f70..515b086ca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,7 @@ emoji = "1.7.0" feedparser = "6.0.8" rapidfuzz = "2.0.7" lxml = "4.8.0" -markdownify = "0.10.3" +markdownify = "0.6.1" more_itertools = "8.12.0" python-dateutil = "2.8.2" python-frontmatter = "1.0.0" -- cgit v1.2.3 From df99fa8d8208c14e65ff623bb220c659d94aed2e Mon Sep 17 00:00:00 2001 From: Numerlor Date: Fri, 29 Apr 2022 15:40:04 +0200 Subject: clear keys from set expires after deleting them from redis if the expire "cache" is not reset, the class assumes an expire is set, even though no expire was set for the key --- bot/exts/info/doc/_redis_cache.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bot/exts/info/doc/_redis_cache.py b/bot/exts/info/doc/_redis_cache.py index 107f2344f..9232a71f2 100644 --- a/bot/exts/info/doc/_redis_cache.py +++ b/bot/exts/info/doc/_redis_cache.py @@ -1,6 +1,7 @@ from __future__ import annotations import datetime +import fnmatch from typing import Optional, TYPE_CHECKING from async_rediscache.types.base import RedisObject, namespace_lock @@ -49,12 +50,15 @@ class DocRedisCache(RedisObject): @namespace_lock async def delete(self, package: str) -> bool: """Remove all values for `package`; return True if at least one key was deleted, False otherwise.""" + pattern = f"{self.namespace}:{package}:*" + with await self._get_pool_connection() as connection: package_keys = [ - package_key async for package_key in connection.iscan(match=f"{self.namespace}:{package}:*") + package_key async for package_key in connection.iscan(match=pattern) ] if package_keys: await connection.delete(*package_keys) + self._set_expires = {key for key in self._set_expires if not fnmatch.fnmatchcase(key, pattern)} return True return False -- cgit v1.2.3 From 4cdaaf429ec27074117ce41c37aa47e72545c778 Mon Sep 17 00:00:00 2001 From: Numerlor Date: Fri, 29 Apr 2022 15:43:53 +0200 Subject: remove leftover task cancel The task is no longer created in the cog --- bot/exts/info/doc/_cog.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bot/exts/info/doc/_cog.py b/bot/exts/info/doc/_cog.py index dece44063..cbc329a06 100644 --- a/bot/exts/info/doc/_cog.py +++ b/bot/exts/info/doc/_cog.py @@ -464,5 +464,4 @@ class DocCog(commands.Cog): async def cog_unload(self) -> None: """Clear scheduled inventories, queued symbols and cleanup task on cog unload.""" self.inventory_scheduler.cancel_all() - self.init_refresh_task.cancel() await self.item_fetcher.clear() -- cgit v1.2.3 From 89527e104dce7840bc495445ce57ab2f8cf09963 Mon Sep 17 00:00:00 2001 From: Numerlor Date: Sat, 30 Apr 2022 01:06:53 +0200 Subject: Expire key cache values when redis key expires --- bot/exts/info/doc/_redis_cache.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/bot/exts/info/doc/_redis_cache.py b/bot/exts/info/doc/_redis_cache.py index 9232a71f2..aba23ff68 100644 --- a/bot/exts/info/doc/_redis_cache.py +++ b/bot/exts/info/doc/_redis_cache.py @@ -2,6 +2,7 @@ from __future__ import annotations import datetime import fnmatch +import time from typing import Optional, TYPE_CHECKING from async_rediscache.types.base import RedisObject, namespace_lock @@ -17,7 +18,7 @@ class DocRedisCache(RedisObject): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self._set_expires = set() + self._set_expires = dict[str, float]() @namespace_lock async def set(self, item: DocItem, value: str) -> None: @@ -30,13 +31,20 @@ class DocRedisCache(RedisObject): needs_expire = False with await self._get_pool_connection() as connection: - if redis_key not in self._set_expires: + set_expire = self._set_expires.get(redis_key) + if set_expire is None: # An expire is only set if the key didn't exist before. # If this is the first time setting values for this key check if it exists and add it to # `_set_expires` to prevent redundant checks for subsequent uses with items from the same page. - self._set_expires.add(redis_key) needs_expire = not await connection.exists(redis_key) + elif time.monotonic() - set_expire > WEEK_SECONDS: + # If we got here the key expired in redis and we can be sure it doesn't exist. + needs_expire = True + del self._set_expires[redis_key] + + self._set_expires[redis_key] = time.monotonic() + await connection.hset(redis_key, item.symbol_id, value) if needs_expire: await connection.expire(redis_key, WEEK_SECONDS) -- cgit v1.2.3 From ab7d4b608604e4d9fdba07001f6d93ef1efe85e3 Mon Sep 17 00:00:00 2001 From: Numerlor Date: Sat, 30 Apr 2022 18:28:35 +0200 Subject: expire internal cache according to redis' TTL instead of always a week --- bot/exts/info/doc/_redis_cache.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/bot/exts/info/doc/_redis_cache.py b/bot/exts/info/doc/_redis_cache.py index aba23ff68..95d6630f0 100644 --- a/bot/exts/info/doc/_redis_cache.py +++ b/bot/exts/info/doc/_redis_cache.py @@ -7,11 +7,15 @@ from typing import Optional, TYPE_CHECKING from async_rediscache.types.base import RedisObject, namespace_lock +from bot.log import get_logger + if TYPE_CHECKING: from ._cog import DocItem WEEK_SECONDS = datetime.timedelta(weeks=1).total_seconds() +log = get_logger(__name__) + class DocRedisCache(RedisObject): """Interface for redis functionality needed by the Doc cog.""" @@ -34,19 +38,22 @@ class DocRedisCache(RedisObject): set_expire = self._set_expires.get(redis_key) if set_expire is None: # An expire is only set if the key didn't exist before. - # If this is the first time setting values for this key check if it exists and add it to - # `_set_expires` to prevent redundant checks for subsequent uses with items from the same page. - needs_expire = not await connection.exists(redis_key) + ttl = await connection.ttl(redis_key) - elif time.monotonic() - set_expire > WEEK_SECONDS: + if ttl == -1: + log.warning(f"Key `{redis_key}` had no expire set.") + if ttl < 0: # not set or didn't exist + needs_expire = True + else: + self._set_expires[redis_key] = time.monotonic() + ttl - .1 # we need this to expire before redis + + elif time.monotonic() > set_expire: # If we got here the key expired in redis and we can be sure it doesn't exist. needs_expire = True - del self._set_expires[redis_key] - - self._set_expires[redis_key] = time.monotonic() await connection.hset(redis_key, item.symbol_id, value) if needs_expire: + self._set_expires[redis_key] = time.monotonic() + WEEK_SECONDS await connection.expire(redis_key, WEEK_SECONDS) @namespace_lock -- cgit v1.2.3 From e2b56247947effd65f3ee821259176607f292eed Mon Sep 17 00:00:00 2001 From: Numerlor Date: Sat, 30 Apr 2022 19:17:59 +0200 Subject: Fix set_expires being set to a set instead of a dict --- bot/exts/info/doc/_redis_cache.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bot/exts/info/doc/_redis_cache.py b/bot/exts/info/doc/_redis_cache.py index 95d6630f0..45ef03352 100644 --- a/bot/exts/info/doc/_redis_cache.py +++ b/bot/exts/info/doc/_redis_cache.py @@ -73,7 +73,9 @@ class DocRedisCache(RedisObject): ] if package_keys: await connection.delete(*package_keys) - self._set_expires = {key for key in self._set_expires if not fnmatch.fnmatchcase(key, pattern)} + self._set_expires = { + key: expire for key, expire in self._set_expires.items() if not fnmatch.fnmatchcase(key, pattern) + } return True return False -- cgit v1.2.3 From 71e20c27292630dbe45c2fadb85123eeaab279bd Mon Sep 17 00:00:00 2001 From: Numerlor Date: Sat, 30 Apr 2022 19:24:01 +0200 Subject: Add more logging to DocRedisCache --- bot/exts/info/doc/_redis_cache.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bot/exts/info/doc/_redis_cache.py b/bot/exts/info/doc/_redis_cache.py index 45ef03352..8e08e7ae4 100644 --- a/bot/exts/info/doc/_redis_cache.py +++ b/bot/exts/info/doc/_redis_cache.py @@ -39,22 +39,26 @@ class DocRedisCache(RedisObject): if set_expire is None: # An expire is only set if the key didn't exist before. ttl = await connection.ttl(redis_key) + log.debug(f"Checked TTL for `{redis_key}`.") if ttl == -1: log.warning(f"Key `{redis_key}` had no expire set.") if ttl < 0: # not set or didn't exist needs_expire = True else: + log.debug(f"Key `{redis_key}` has a {ttl} TTL.") self._set_expires[redis_key] = time.monotonic() + ttl - .1 # we need this to expire before redis elif time.monotonic() > set_expire: # If we got here the key expired in redis and we can be sure it doesn't exist. needs_expire = True + log.debug(f"Key `{redis_key}` expired in internal key cache.") await connection.hset(redis_key, item.symbol_id, value) if needs_expire: self._set_expires[redis_key] = time.monotonic() + WEEK_SECONDS await connection.expire(redis_key, WEEK_SECONDS) + log.info(f"Set {redis_key} to expire in a week.") @namespace_lock async def get(self, item: DocItem) -> Optional[str]: @@ -73,6 +77,7 @@ class DocRedisCache(RedisObject): ] if package_keys: await connection.delete(*package_keys) + log.info(f"Deleted keys from redis: {package_keys}.") self._set_expires = { key: expire for key, expire in self._set_expires.items() if not fnmatch.fnmatchcase(key, pattern) } -- cgit v1.2.3 From 0edec23e6d3a46c848bc0fb73832e41663d99983 Mon Sep 17 00:00:00 2001 From: Chris Lovering Date: Sun, 1 May 2022 20:15:27 +0100 Subject: Don't decode empty content as json in thread bumper Closes #2162 Closes BOT-343 self.bot.api_client.request can not be used to check if a thread id exists in the bumped thread list on site, as self.bot.api_client.request() always attempts to decode the response as json. The thread bump viewset in site returns a 204 if the given thread exists with no content, so this does not work. --- bot/exts/utils/thread_bumper.py | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/bot/exts/utils/thread_bumper.py b/bot/exts/utils/thread_bumper.py index 743919d4e..7ffb79d5e 100644 --- a/bot/exts/utils/thread_bumper.py +++ b/bot/exts/utils/thread_bumper.py @@ -24,6 +24,23 @@ class ThreadBumper(commands.Cog): def __init__(self, bot: Bot): self.bot = bot + async def thread_exists_in_site(self, thread_id: int) -> bool: + """Return whether the given thread_id exists in the site api's bump list.""" + # If the thread exists, site returns a 204 with no content. + # Due to this, `api_client.request()` cannot be used, as it always attempts to decode the response as json. + # Instead, call the site manually using the api_client's session, to use the auth token logic in the wrapper. + + async with self.bot.api_client.session.get( + f"{self.bot.api_client._url_for(THREAD_BUMP_ENDPOINT)}/{thread_id}" + ) as response: + if response.status == 204: + return True + elif response.status == 404: + return False + else: + # A status other than 204/404 is undefined behaviour from site. Raise error for investigation. + raise ResponseCodeError(response, response.text()) + async def unarchive_threads_not_manually_archived(self, threads: list[discord.Thread]) -> None: """ Iterate through and unarchive any threads that weren't manually archived recently. @@ -89,12 +106,7 @@ class ThreadBumper(commands.Cog): else: raise commands.BadArgument("You must provide a thread, or run this command within a thread.") - try: - await self.bot.api_client.get(f"{THREAD_BUMP_ENDPOINT}/{thread.id}") - except ResponseCodeError as e: - if e.status != 404: - raise - else: + if await self.thread_exists_in_site(thread.id): raise commands.BadArgument("This thread is already in the bump list.") await self.bot.api_client.post(THREAD_BUMP_ENDPOINT, data={"thread_id": thread.id}) @@ -109,9 +121,7 @@ class ThreadBumper(commands.Cog): else: raise commands.BadArgument("You must provide a thread, or run this command within a thread.") - try: - await self.bot.api_client.get(f"{THREAD_BUMP_ENDPOINT}/{thread.id}") - except ResponseCodeError: + if not await self.thread_exists_in_site(thread.id): raise commands.BadArgument("This thread is not in the bump list.") await self.bot.api_client.delete(f"{THREAD_BUMP_ENDPOINT}/{thread.id}") @@ -137,12 +147,7 @@ class ThreadBumper(commands.Cog): if not after.archived: return - try: - await self.bot.api_client.get(f"{THREAD_BUMP_ENDPOINT}/{after.id}") - except ResponseCodeError as e: - if e.status != 404: - raise - else: + if await self.thread_exists_in_site(after.id): await self.unarchive_threads_not_manually_archived([after]) async def cog_check(self, ctx: commands.Context) -> bool: -- cgit v1.2.3 From 3d17a8229e771b59a4e21b8e620cea77f48872bb Mon Sep 17 00:00:00 2001 From: Hassan Abouelela Date: Wed, 11 May 2022 01:14:50 +0400 Subject: Bump Bot Core to v7.0.0 Bumps the botcore version to v7.0.0, and implements the changes required by the breaking fix documented in the changelog for `utils.regex.DISCORD_INVITE`. Signed-off-by: Hassan Abouelela --- bot/exts/filters/filtering.py | 3 + poetry.lock | 155 +++++++++++++++++++++--------------------- pyproject.toml | 2 +- 3 files changed, 82 insertions(+), 78 deletions(-) diff --git a/bot/exts/filters/filtering.py b/bot/exts/filters/filtering.py index 21c155902..803b75454 100644 --- a/bot/exts/filters/filtering.py +++ b/bot/exts/filters/filtering.py @@ -1,6 +1,7 @@ import asyncio import re import unicodedata +import urllib.parse from datetime import timedelta from typing import Any, Dict, List, Mapping, NamedTuple, Optional, Tuple, Union @@ -565,6 +566,7 @@ class Filtering(Cog): If any are detected, a dictionary of invite data is returned, with a key per invite. If none are detected, False is returned. + If we are unable to process an invite, True is returned. Attempts to catch some of common ways to try to cheat the system. """ @@ -577,6 +579,7 @@ class Filtering(Cog): invites = [m.group("invite") for m in DISCORD_INVITE.finditer(text)] invite_data = dict() for invite in invites: + invite = urllib.parse.quote_plus(invite) if invite in invite_data: continue diff --git a/poetry.lock b/poetry.lock index 8281394e6..267b9e22c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -141,7 +141,7 @@ async-rediscache = ["async-rediscache[fakeredis] (==0.2.0)"] [package.source] type = "url" -url = "https://github.com/python-discord/bot-core/archive/refs/tags/v6.4.0.zip" +url = "https://github.com/python-discord/bot-core/archive/refs/tags/v7.0.0.zip" [[package]] name = "certifi" version = "2021.10.8" @@ -297,7 +297,7 @@ testing = ["pre-commit"] [[package]] name = "fakeredis" -version = "1.7.1" +version = "1.7.4" description = "Fake implementation of redis API for testing purposes." category = "main" optional = false @@ -306,7 +306,7 @@ python-versions = ">=3.5" [package.dependencies] lupa = {version = "*", optional = true, markers = "extra == \"lua\""} packaging = "*" -redis = "<4.2.0" +redis = "<=4.2.2" six = ">=1.12" sortedcontainers = "*" @@ -478,7 +478,7 @@ pyreadline3 = {version = "*", markers = "sys_platform == \"win32\" and python_ve [[package]] name = "identify" -version = "2.4.12" +version = "2.5.0" description = "File identification library for Python" category = "dev" optional = false @@ -909,13 +909,14 @@ full = ["numpy"] [[package]] name = "redis" -version = "4.1.4" +version = "4.2.2" description = "Python client for Redis database and key-value store" category = "main" optional = false python-versions = ">=3.6" [package.dependencies] +async-timeout = ">=4.0.2" deprecated = ">=1.2.3" packaging = ">=20.4" @@ -1129,7 +1130,7 @@ testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", [[package]] name = "wrapt" -version = "1.14.0" +version = "1.14.1" description = "Module for decorators, wrappers and monkey patching." category = "main" optional = false @@ -1150,7 +1151,7 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" python-versions = "3.9.*" -content-hash = "a07f619c75f8133982984eb506ad350144829f10c704421f09b3dbe72cd037d8" +content-hash = "cc77bc7d6bb7940767359a860b1ade2014573bab3046b864c3d265b4eee2cb7b" [metadata.files] aiodns = [ @@ -1400,8 +1401,8 @@ execnet = [ {file = "execnet-1.9.0.tar.gz", hash = "sha256:8f694f3ba9cc92cab508b152dcfe322153975c29bda272e2fd7f3f00f36e47c5"}, ] fakeredis = [ - {file = "fakeredis-1.7.1-py3-none-any.whl", hash = "sha256:be3668e50f6b57d5fc4abfd27f9f655bed07a2c5aecfc8b15d0aad59f997c1ba"}, - {file = "fakeredis-1.7.1.tar.gz", hash = "sha256:7c2c4ba1b42e0a75337c54b777bf0671056b4569650e3ff927e4b9b385afc8ec"}, + {file = "fakeredis-1.7.4-py3-none-any.whl", hash = "sha256:cc033ebf9af9f42bba6aa538a3e1a9f1732686b8b7e9ef50c7a44955bbc2aff8"}, + {file = "fakeredis-1.7.4.tar.gz", hash = "sha256:69697ffeeb09939073605eeac97f524bccabae04265757a575c7fc923087aa65"}, ] feedparser = [ {file = "feedparser-6.0.8-py3-none-any.whl", hash = "sha256:1b7f57841d9cf85074deb316ed2c795091a238adb79846bc46dccdaf80f9c59a"}, @@ -1555,8 +1556,8 @@ humanfriendly = [ {file = "humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc"}, ] identify = [ - {file = "identify-2.4.12-py2.py3-none-any.whl", hash = "sha256:5f06b14366bd1facb88b00540a1de05b69b310cbc2654db3c7e07fa3a4339323"}, - {file = "identify-2.4.12.tar.gz", hash = "sha256:3f3244a559290e7d3deb9e9adc7b33594c1bc85a9dd82e0f1be519bf12a1ec17"}, + {file = "identify-2.5.0-py2.py3-none-any.whl", hash = "sha256:3acfe15a96e4272b4ec5662ee3e231ceba976ef63fd9980ed2ce9cc415df393f"}, + {file = "identify-2.5.0.tar.gz", hash = "sha256:c83af514ea50bf2be2c4a3f2fb349442b59dc87284558ae9ff54191bff3541d2"}, ] idna = [ {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, @@ -2098,8 +2099,8 @@ rapidfuzz = [ {file = "rapidfuzz-2.0.7.tar.gz", hash = "sha256:93bf42784fd74ebf1a8e89ca1596e9bea7f3ac4a61b825ecc6eb2d9893ad6844"}, ] redis = [ - {file = "redis-4.1.4-py3-none-any.whl", hash = "sha256:04629f8e42be942c4f7d1812f2094568f04c612865ad19ad3ace3005da70631a"}, - {file = "redis-4.1.4.tar.gz", hash = "sha256:1d9a0cdf89fdd93f84261733e24f55a7bbd413a9b219fdaf56e3e728ca9a2306"}, + {file = "redis-4.2.2-py3-none-any.whl", hash = "sha256:4e95f4ec5f49e636efcf20061a5a9110c20852f607cfca6865c07aaa8a739ee2"}, + {file = "redis-4.2.2.tar.gz", hash = "sha256:0107dc8e98a4f1d1d4aa00100e044287f77121a1e6d2085545c4b7fa94a7a27f"}, ] regex = [ {file = "regex-2022.3.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:42eb13b93765c6698a5ab3bcd318d8c39bb42e5fa8a7fcf7d8d98923f3babdb1"}, @@ -2241,70 +2242,70 @@ virtualenv = [ {file = "virtualenv-20.14.1.tar.gz", hash = "sha256:ef589a79795589aada0c1c5b319486797c03b67ac3984c48c669c0e4f50df3a5"}, ] wrapt = [ - {file = "wrapt-1.14.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:5a9a1889cc01ed2ed5f34574c90745fab1dd06ec2eee663e8ebeefe363e8efd7"}, - {file = "wrapt-1.14.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:9a3ff5fb015f6feb78340143584d9f8a0b91b6293d6b5cf4295b3e95d179b88c"}, - {file = "wrapt-1.14.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:4b847029e2d5e11fd536c9ac3136ddc3f54bc9488a75ef7d040a3900406a91eb"}, - {file = "wrapt-1.14.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:9a5a544861b21e0e7575b6023adebe7a8c6321127bb1d238eb40d99803a0e8bd"}, - {file = "wrapt-1.14.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:88236b90dda77f0394f878324cfbae05ae6fde8a84d548cfe73a75278d760291"}, - {file = "wrapt-1.14.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f0408e2dbad9e82b4c960274214af533f856a199c9274bd4aff55d4634dedc33"}, - {file = "wrapt-1.14.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:9d8c68c4145041b4eeae96239802cfdfd9ef927754a5be3f50505f09f309d8c6"}, - {file = "wrapt-1.14.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:22626dca56fd7f55a0733e604f1027277eb0f4f3d95ff28f15d27ac25a45f71b"}, - {file = "wrapt-1.14.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:65bf3eb34721bf18b5a021a1ad7aa05947a1767d1aa272b725728014475ea7d5"}, - {file = "wrapt-1.14.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09d16ae7a13cff43660155383a2372b4aa09109c7127aa3f24c3cf99b891c330"}, - {file = "wrapt-1.14.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:debaf04f813ada978d7d16c7dfa16f3c9c2ec9adf4656efdc4defdf841fc2f0c"}, - {file = "wrapt-1.14.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:748df39ed634851350efa87690c2237a678ed794fe9ede3f0d79f071ee042561"}, - {file = "wrapt-1.14.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1807054aa7b61ad8d8103b3b30c9764de2e9d0c0978e9d3fc337e4e74bf25faa"}, - {file = "wrapt-1.14.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:763a73ab377390e2af26042f685a26787c402390f682443727b847e9496e4a2a"}, - {file = "wrapt-1.14.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8529b07b49b2d89d6917cfa157d3ea1dfb4d319d51e23030664a827fe5fd2131"}, - {file = "wrapt-1.14.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:68aeefac31c1f73949662ba8affaf9950b9938b712fb9d428fa2a07e40ee57f8"}, - {file = "wrapt-1.14.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59d7d92cee84a547d91267f0fea381c363121d70fe90b12cd88241bd9b0e1763"}, - {file = "wrapt-1.14.0-cp310-cp310-win32.whl", hash = "sha256:3a88254881e8a8c4784ecc9cb2249ff757fd94b911d5df9a5984961b96113fff"}, - {file = "wrapt-1.14.0-cp310-cp310-win_amd64.whl", hash = "sha256:9a242871b3d8eecc56d350e5e03ea1854de47b17f040446da0e47dc3e0b9ad4d"}, - {file = "wrapt-1.14.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a65bffd24409454b889af33b6c49d0d9bcd1a219b972fba975ac935f17bdf627"}, - {file = "wrapt-1.14.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9d9fcd06c952efa4b6b95f3d788a819b7f33d11bea377be6b8980c95e7d10775"}, - {file = "wrapt-1.14.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:db6a0ddc1282ceb9032e41853e659c9b638789be38e5b8ad7498caac00231c23"}, - {file = "wrapt-1.14.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:14e7e2c5f5fca67e9a6d5f753d21f138398cad2b1159913ec9e9a67745f09ba3"}, - {file = "wrapt-1.14.0-cp35-cp35m-win32.whl", hash = "sha256:6d9810d4f697d58fd66039ab959e6d37e63ab377008ef1d63904df25956c7db0"}, - {file = "wrapt-1.14.0-cp35-cp35m-win_amd64.whl", hash = "sha256:d808a5a5411982a09fef6b49aac62986274ab050e9d3e9817ad65b2791ed1425"}, - {file = "wrapt-1.14.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b77159d9862374da213f741af0c361720200ab7ad21b9f12556e0eb95912cd48"}, - {file = "wrapt-1.14.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36a76a7527df8583112b24adc01748cd51a2d14e905b337a6fefa8b96fc708fb"}, - {file = "wrapt-1.14.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0057b5435a65b933cbf5d859cd4956624df37b8bf0917c71756e4b3d9958b9e"}, - {file = "wrapt-1.14.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a0a4ca02752ced5f37498827e49c414d694ad7cf451ee850e3ff160f2bee9d3"}, - {file = "wrapt-1.14.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:8c6be72eac3c14baa473620e04f74186c5d8f45d80f8f2b4eda6e1d18af808e8"}, - {file = "wrapt-1.14.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:21b1106bff6ece8cb203ef45b4f5778d7226c941c83aaaa1e1f0f4f32cc148cd"}, - {file = "wrapt-1.14.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:493da1f8b1bb8a623c16552fb4a1e164c0200447eb83d3f68b44315ead3f9036"}, - {file = "wrapt-1.14.0-cp36-cp36m-win32.whl", hash = "sha256:89ba3d548ee1e6291a20f3c7380c92f71e358ce8b9e48161401e087e0bc740f8"}, - {file = "wrapt-1.14.0-cp36-cp36m-win_amd64.whl", hash = "sha256:729d5e96566f44fccac6c4447ec2332636b4fe273f03da128fff8d5559782b06"}, - {file = "wrapt-1.14.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:891c353e95bb11abb548ca95c8b98050f3620a7378332eb90d6acdef35b401d4"}, - {file = "wrapt-1.14.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23f96134a3aa24cc50614920cc087e22f87439053d886e474638c68c8d15dc80"}, - {file = "wrapt-1.14.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6807bcee549a8cb2f38f73f469703a1d8d5d990815c3004f21ddb68a567385ce"}, - {file = "wrapt-1.14.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6915682f9a9bc4cf2908e83caf5895a685da1fbd20b6d485dafb8e218a338279"}, - {file = "wrapt-1.14.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f2f3bc7cd9c9fcd39143f11342eb5963317bd54ecc98e3650ca22704b69d9653"}, - {file = "wrapt-1.14.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3a71dbd792cc7a3d772ef8cd08d3048593f13d6f40a11f3427c000cf0a5b36a0"}, - {file = "wrapt-1.14.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5a0898a640559dec00f3614ffb11d97a2666ee9a2a6bad1259c9facd01a1d4d9"}, - {file = "wrapt-1.14.0-cp37-cp37m-win32.whl", hash = "sha256:167e4793dc987f77fd476862d32fa404d42b71f6a85d3b38cbce711dba5e6b68"}, - {file = "wrapt-1.14.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d066ffc5ed0be00cd0352c95800a519cf9e4b5dd34a028d301bdc7177c72daf3"}, - {file = "wrapt-1.14.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d9bdfa74d369256e4218000a629978590fd7cb6cf6893251dad13d051090436d"}, - {file = "wrapt-1.14.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2498762814dd7dd2a1d0248eda2afbc3dd9c11537bc8200a4b21789b6df6cd38"}, - {file = "wrapt-1.14.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f24ca7953f2643d59a9c87d6e272d8adddd4a53bb62b9208f36db408d7aafc7"}, - {file = "wrapt-1.14.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b835b86bd5a1bdbe257d610eecab07bf685b1af2a7563093e0e69180c1d4af1"}, - {file = "wrapt-1.14.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b21650fa6907e523869e0396c5bd591cc326e5c1dd594dcdccac089561cacfb8"}, - {file = "wrapt-1.14.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:354d9fc6b1e44750e2a67b4b108841f5f5ea08853453ecbf44c81fdc2e0d50bd"}, - {file = "wrapt-1.14.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1f83e9c21cd5275991076b2ba1cd35418af3504667affb4745b48937e214bafe"}, - {file = "wrapt-1.14.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:61e1a064906ccba038aa3c4a5a82f6199749efbbb3cef0804ae5c37f550eded0"}, - {file = "wrapt-1.14.0-cp38-cp38-win32.whl", hash = "sha256:28c659878f684365d53cf59dc9a1929ea2eecd7ac65da762be8b1ba193f7e84f"}, - {file = "wrapt-1.14.0-cp38-cp38-win_amd64.whl", hash = "sha256:b0ed6ad6c9640671689c2dbe6244680fe8b897c08fd1fab2228429b66c518e5e"}, - {file = "wrapt-1.14.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b3f7e671fb19734c872566e57ce7fc235fa953d7c181bb4ef138e17d607dc8a1"}, - {file = "wrapt-1.14.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:87fa943e8bbe40c8c1ba4086971a6fefbf75e9991217c55ed1bcb2f1985bd3d4"}, - {file = "wrapt-1.14.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4775a574e9d84e0212f5b18886cace049a42e13e12009bb0491562a48bb2b758"}, - {file = "wrapt-1.14.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9d57677238a0c5411c76097b8b93bdebb02eb845814c90f0b01727527a179e4d"}, - {file = "wrapt-1.14.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00108411e0f34c52ce16f81f1d308a571df7784932cc7491d1e94be2ee93374b"}, - {file = "wrapt-1.14.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d332eecf307fca852d02b63f35a7872de32d5ba8b4ec32da82f45df986b39ff6"}, - {file = "wrapt-1.14.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:01f799def9b96a8ec1ef6b9c1bbaf2bbc859b87545efbecc4a78faea13d0e3a0"}, - {file = "wrapt-1.14.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47045ed35481e857918ae78b54891fac0c1d197f22c95778e66302668309336c"}, - {file = "wrapt-1.14.0-cp39-cp39-win32.whl", hash = "sha256:2eca15d6b947cfff51ed76b2d60fd172c6ecd418ddab1c5126032d27f74bc350"}, - {file = "wrapt-1.14.0-cp39-cp39-win_amd64.whl", hash = "sha256:bb36fbb48b22985d13a6b496ea5fb9bb2a076fea943831643836c9f6febbcfdc"}, - {file = "wrapt-1.14.0.tar.gz", hash = "sha256:8323a43bd9c91f62bb7d4be74cc9ff10090e7ef820e27bfe8815c57e68261311"}, + {file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1"}, + {file = "wrapt-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320"}, + {file = "wrapt-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2"}, + {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4"}, + {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069"}, + {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310"}, + {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f"}, + {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656"}, + {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c"}, + {file = "wrapt-1.14.1-cp310-cp310-win32.whl", hash = "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8"}, + {file = "wrapt-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d"}, + {file = "wrapt-1.14.1-cp35-cp35m-win32.whl", hash = "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7"}, + {file = "wrapt-1.14.1-cp35-cp35m-win_amd64.whl", hash = "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00"}, + {file = "wrapt-1.14.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4"}, + {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1"}, + {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1"}, + {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff"}, + {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d"}, + {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1"}, + {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569"}, + {file = "wrapt-1.14.1-cp36-cp36m-win32.whl", hash = "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed"}, + {file = "wrapt-1.14.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471"}, + {file = "wrapt-1.14.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248"}, + {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68"}, + {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d"}, + {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77"}, + {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7"}, + {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015"}, + {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a"}, + {file = "wrapt-1.14.1-cp37-cp37m-win32.whl", hash = "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853"}, + {file = "wrapt-1.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c"}, + {file = "wrapt-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456"}, + {file = "wrapt-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f"}, + {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc"}, + {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1"}, + {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"}, + {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b"}, + {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0"}, + {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57"}, + {file = "wrapt-1.14.1-cp38-cp38-win32.whl", hash = "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5"}, + {file = "wrapt-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d"}, + {file = "wrapt-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383"}, + {file = "wrapt-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7"}, + {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86"}, + {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735"}, + {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b"}, + {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3"}, + {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3"}, + {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe"}, + {file = "wrapt-1.14.1-cp39-cp39-win32.whl", hash = "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5"}, + {file = "wrapt-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb"}, + {file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"}, ] yarl = [ {file = "yarl-1.7.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f2a8508f7350512434e41065684076f640ecce176d262a7d54f0da41d99c5a95"}, diff --git a/pyproject.toml b/pyproject.toml index 402d05f70..fd5c0c71a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ python = "3.9.*" "discord.py" = {url = "https://github.com/Rapptz/discord.py/archive/5a06fa5f3e28d2b7191722e1a84c541560008aea.zip"} # See https://bot-core.pythondiscord.com/ for docs. -bot-core = {url = "https://github.com/python-discord/bot-core/archive/refs/tags/v6.4.0.zip", extras = ["async-rediscache"]} +bot-core = {url = "https://github.com/python-discord/bot-core/archive/refs/tags/v7.0.0.zip", extras = ["async-rediscache"]} aiodns = "3.0.0" aiohttp = "3.8.1" -- cgit v1.2.3 From 9f4fd6dce61ef6d33b71a7d43c427e065dfa8d86 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela Date: Wed, 11 May 2022 02:35:10 +0400 Subject: Strip Trailing Slashes From Invite Filter Removes trailing slashes from invites in the invite filter to prevent false positives. Signed-off-by: Hassan Abouelela --- bot/exts/filters/filtering.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/filters/filtering.py b/bot/exts/filters/filtering.py index 803b75454..70f59c1ee 100644 --- a/bot/exts/filters/filtering.py +++ b/bot/exts/filters/filtering.py @@ -579,7 +579,7 @@ class Filtering(Cog): invites = [m.group("invite") for m in DISCORD_INVITE.finditer(text)] invite_data = dict() for invite in invites: - invite = urllib.parse.quote_plus(invite) + invite = urllib.parse.quote_plus(invite.rstrip("/")) if invite in invite_data: continue -- cgit v1.2.3 From 890d2578d3d3d65814edfb350e981ce2fbe2957e Mon Sep 17 00:00:00 2001 From: wookie184 Date: Sat, 21 May 2022 17:40:01 +0100 Subject: Bump malformed API response from debug to error log (#2175) --- bot/exts/backend/error_handler.py | 2 +- tests/bot/exts/backend/test_error_handler.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/bot/exts/backend/error_handler.py b/bot/exts/backend/error_handler.py index 5391a7f15..35dddd8dc 100644 --- a/bot/exts/backend/error_handler.py +++ b/bot/exts/backend/error_handler.py @@ -285,7 +285,7 @@ class ErrorHandler(Cog): ctx.bot.stats.incr("errors.api_error_404") elif e.status == 400: content = await e.response.json() - log.debug(f"API responded with 400 for command {ctx.command}: %r.", content) + log.error(f"API responded with 400 for command {ctx.command}: %r.", content) await ctx.send("According to the API, your request is malformed.") ctx.bot.stats.incr("errors.api_error_400") elif 500 <= e.status < 600: diff --git a/tests/bot/exts/backend/test_error_handler.py b/tests/bot/exts/backend/test_error_handler.py index 193f1d822..d02bd7c34 100644 --- a/tests/bot/exts/backend/test_error_handler.py +++ b/tests/bot/exts/backend/test_error_handler.py @@ -477,11 +477,11 @@ class IndividualErrorHandlerTests(unittest.IsolatedAsyncioTestCase): @patch("bot.exts.backend.error_handler.log") async def test_handle_api_error(self, log_mock): - """Should `ctx.send` on HTTP error codes, `log.debug|warning` depends on code.""" + """Should `ctx.send` on HTTP error codes, and log at correct level.""" test_cases = ( { "error": ResponseCodeError(AsyncMock(status=400)), - "log_level": "debug" + "log_level": "error" }, { "error": ResponseCodeError(AsyncMock(status=404)), @@ -505,6 +505,8 @@ class IndividualErrorHandlerTests(unittest.IsolatedAsyncioTestCase): self.ctx.send.assert_awaited_once() if case["log_level"] == "warning": log_mock.warning.assert_called_once() + elif case["log_level"] == "error": + log_mock.error.assert_called_once() else: log_mock.debug.assert_called_once() -- cgit v1.2.3 From 4a510cc785e8e2adcfd71be1f95fcca560a58263 Mon Sep 17 00:00:00 2001 From: wookie184 Date: Sun, 22 May 2022 13:56:15 +0100 Subject: Ensure correct tag information is sent to statsd --- bot/exts/info/tags.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bot/exts/info/tags.py b/bot/exts/info/tags.py index e89ffafb1..5d7467caf 100644 --- a/bot/exts/info/tags.py +++ b/bot/exts/info/tags.py @@ -278,7 +278,11 @@ class Tags(Cog): if tag is None and tag_identifier.group is not None: # Try exact match with only the name - tag = self.tags.get(TagIdentifier(None, tag_identifier.group)) + name_only_identifier = TagIdentifier(None, tag_identifier.group) + tag = self.tags.get(name_only_identifier) + if tag: + # Ensure the correct tag information is sent to statsd + tag_identifier = name_only_identifier if tag is None and len(filtered_tags) == 1: tag_identifier = filtered_tags[0][0] -- cgit v1.2.3 From e08cd9c40772a89d9d4a9220699b5010dbd5eaae Mon Sep 17 00:00:00 2001 From: Autonymic <75275746+Autonymic@users.noreply.github.com> Date: Sun, 22 May 2022 20:10:24 +0200 Subject: Updating and improving clarity in help documentation for !clean subcommands --- bot/exts/moderation/clean.py | 41 ++++++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/bot/exts/moderation/clean.py b/bot/exts/moderation/clean.py index 0f14f515e..80492d0c9 100644 --- a/bot/exts/moderation/clean.py +++ b/bot/exts/moderation/clean.py @@ -501,19 +501,25 @@ class Clean(Cog): `message_or_time` can be either a message to stop at (exclusive), a timedelta for max message age, or an ISO datetime. - If a message is specified, `channels` cannot be specified. + If a message is specified the cleanup will be limited to the channel the message is in. + + If a timedelta or an ISO datetime is specified, `channels` can be specified to clean across multiple channels. + An asterisk can also be used to designate cleanup across all channels. """ await self._clean_messages(ctx, users=[user], channels=channels, first_limit=message_or_time) @clean_group.command(name="bots", aliases=["bot"]) async def clean_bots(self, ctx: Context, message_or_time: CleanLimit, *, channels: CleanChannels = None) -> None: """ - Delete all messages posted by a bot, stop cleaning after traversing `traverse` messages. + Delete all messages posted by a bot, stop cleaning after reaching `message_or_time`. `message_or_time` can be either a message to stop at (exclusive), a timedelta for max message age, or an ISO datetime. - If a message is specified, `channels` cannot be specified. + If a message is specified the cleanup will be limited to the channel the message is in. + + If a timedelta or an ISO datetime is specified, `channels` can be specified to clean across multiple channels. + An asterisk can also be used to designate cleanup across all channels. """ await self._clean_messages(ctx, bots_only=True, channels=channels, first_limit=message_or_time) @@ -531,11 +537,21 @@ class Clean(Cog): `message_or_time` can be either a message to stop at (exclusive), a timedelta for max message age, or an ISO datetime. - If a message is specified, `channels` cannot be specified. - The pattern must be provided enclosed in backticks. - If the pattern contains spaces, it still needs to be enclosed in double quotes on top of that. - For example: `[0-9]` + If a message is specified the cleanup will be limited to the channel the message is in. + + If a timedelta or an ISO datetime is specified, `channels` can be specified to clean across multiple channels. + An asterisk can also be used to designate cleanup across all channels. + + The `regex` pattern must be provided enclosed in backticks, that will make it appear as a code section. + + For example: \u02CB[0-9]\u02CB, which should appear as `[0-9]`. + + If the `regex` pattern contains spaces, it still needs to be enclosed in double quotes on top of that. + + For example: "\u02CB[0-9]\u02CB", which should appear as "`[0-9]`". + + Do not copy and paste the backticks from the examples, they are special unicode characters that will not work. """ await self._clean_messages(ctx, regex=regex, channels=channels, first_limit=message_or_time) @@ -550,7 +566,11 @@ class Clean(Cog): Delete all messages until a certain limit. A limit can be either a message, and ISO date-time string, or a time delta. - If a message is specified, `channel` cannot be specified. + + If a message is specified the cleanup will be limited to the channel the message is in. + + If a timedelta or an ISO datetime is specified, `channels` can be specified to clean across multiple channels. + An asterisk can also be used to designate cleanup across all channels. """ await self._clean_messages( ctx, @@ -573,7 +593,10 @@ class Clean(Cog): A limit can be either a message, and ISO date-time string, or a time delta. If two messages are specified, they both must be in the same channel. - If a message is specified, `channel` cannot be specified. + The cleanup will be limited to the channel the messages are in. + + If two timedeltas or ISO datetimes are specified, `channels` can be specified to clean across multiple channels. + An asterisk can also be used to designate cleanup across all channels. """ await self._clean_messages( ctx, -- cgit v1.2.3 From 67f88c7a76d732e5e6d283eb1a24fbf12b766148 Mon Sep 17 00:00:00 2001 From: Chris Lovering Date: Sun, 22 May 2022 22:26:57 +0100 Subject: Use existing ResponseCodeError attrs rather than fetch again This also updates the log string to use % based format strings, rather than a mix of f-string and % string which caused the %r to not work. --- bot/exts/backend/error_handler.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/bot/exts/backend/error_handler.py b/bot/exts/backend/error_handler.py index 35dddd8dc..194a4889d 100644 --- a/bot/exts/backend/error_handler.py +++ b/bot/exts/backend/error_handler.py @@ -284,8 +284,11 @@ class ErrorHandler(Cog): await ctx.send("There does not seem to be anything matching your query.") ctx.bot.stats.incr("errors.api_error_404") elif e.status == 400: - content = await e.response.json() - log.error(f"API responded with 400 for command {ctx.command}: %r.", content) + log.error( + "API responded with 400 for command %s: %r.", + ctx.command, + e.response_json or e.response_text, + ) await ctx.send("According to the API, your request is malformed.") ctx.bot.stats.incr("errors.api_error_400") elif 500 <= e.status < 600: -- cgit v1.2.3 From 676844c67afb56f2d5cab6941bdfc173a81c43a5 Mon Sep 17 00:00:00 2001 From: Chris Lovering Date: Sun, 22 May 2022 22:27:50 +0100 Subject: Add request data as a sentry breadcrumb when clean cog fails This is to aid in the debugging on an issue we have encountered --- bot/exts/moderation/modlog.py | 47 +++++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/bot/exts/moderation/modlog.py b/bot/exts/moderation/modlog.py index 80f68e442..67991730e 100644 --- a/bot/exts/moderation/modlog.py +++ b/bot/exts/moderation/modlog.py @@ -6,12 +6,14 @@ from datetime import datetime, timezone from itertools import zip_longest import discord +from botcore.site_api import ResponseCodeError from dateutil.relativedelta import relativedelta from deepdiff import DeepDiff from discord import Colour, Message, Thread from discord.abc import GuildChannel from discord.ext.commands import Cog, Context from discord.utils import escape_markdown, format_dt, snowflake_time +from sentry_sdk import add_breadcrumb from bot.bot import Bot from bot.constants import Categories, Channels, Colours, Emojis, Event, Guild as GuildConstant, Icons, Roles, URLs @@ -53,24 +55,35 @@ class ModLog(Cog, name="ModLog"): if attachments is None: attachments = [] - response = await self.bot.api_client.post( - 'bot/deleted-messages', - json={ - 'actor': actor_id, - 'creation': datetime.now(timezone.utc).isoformat(), - 'deletedmessage_set': [ - { - 'id': message.id, - 'author': message.author.id, - 'channel_id': message.channel.id, - 'content': message.content.replace("\0", ""), # Null chars cause 400. - 'embeds': [embed.to_dict() for embed in message.embeds], - 'attachments': attachment, - } - for message, attachment in zip_longest(messages, attachments, fillvalue=[]) - ] + deletedmessage_set = [ + { + "id": message.id, + "author": message.author.id, + "channel_id": message.channel.id, + "content": message.content.replace("\0", ""), # Null chars cause 400. + "embeds": [embed.to_dict() for embed in message.embeds], + "attachments": attachment, } - ) + for message, attachment in zip_longest(messages, attachments, fillvalue=[]) + ] + + try: + response = await self.bot.api_client.post( + "bot/deleted-messages", + json={ + "actor": actor_id, + "creation": datetime.now(timezone.utc).isoformat(), + "deletedmessage_set": deletedmessage_set, + } + ) + except ResponseCodeError as e: + add_breadcrumb( + category="api_error", + message=str(e), + level="error", + data=deletedmessage_set, + ) + raise return f"{URLs.site_logs_view}/{response['id']}" -- cgit v1.2.3 From 8e1c6858d517c0328500d317531d6ae8e28ec70b Mon Sep 17 00:00:00 2001 From: wookie184 Date: Fri, 27 May 2022 22:57:26 +0100 Subject: Remove rediscache from thread bumper now it's been migrated (#2161) Co-authored-by: Xithrius <15021300+Xithrius@users.noreply.github.com> --- bot/exts/utils/thread_bumper.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/bot/exts/utils/thread_bumper.py b/bot/exts/utils/thread_bumper.py index 7ffb79d5e..a2f208484 100644 --- a/bot/exts/utils/thread_bumper.py +++ b/bot/exts/utils/thread_bumper.py @@ -1,7 +1,6 @@ import typing as t import discord -from async_rediscache import RedisCache from botcore.site_api import ResponseCodeError from discord.ext import commands @@ -18,9 +17,6 @@ THREAD_BUMP_ENDPOINT = "bot/bumped-threads" class ThreadBumper(commands.Cog): """Cog that allow users to add the current thread to a list that get reopened on archive.""" - # RedisCache[discord.Thread.id, "sentinel"] - threads_to_bump = RedisCache() - def __init__(self, bot: Bot): self.bot = bot -- cgit v1.2.3 From df48d32fb9cb6ded6ca77e182b154611b606fe10 Mon Sep 17 00:00:00 2001 From: ChrisJL Date: Fri, 27 May 2022 22:59:25 +0100 Subject: Catch correct exception in clean cog (#2176) Co-authored-by: Xithrius <15021300+Xithrius@users.noreply.github.com> --- bot/exts/moderation/clean.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/moderation/clean.py b/bot/exts/moderation/clean.py index 0f14f515e..fedb787f9 100644 --- a/bot/exts/moderation/clean.py +++ b/bot/exts/moderation/clean.py @@ -443,7 +443,7 @@ class Clean(Cog): if log_url and is_mod_channel(ctx.channel): try: await ctx.reply(success_message) - except errors.NotFound: + except errors.HTTPException: await ctx.send(success_message) elif log_url: if mods := self.bot.get_channel(Channels.mods): -- cgit v1.2.3 From 777e7e71266980b6b45b981d822d43835518900e Mon Sep 17 00:00:00 2001 From: Autonymic <75275746+Autonymic@users.noreply.github.com> Date: Sat, 28 May 2022 00:43:37 +0200 Subject: Fix !clean users command to handle more than one user (#2178) * Fix !clean users command to properly handle more than one user * Make !clean user an alias of !clean users instead of the other way around Co-authored-by: Xithrius <15021300+Xithrius@users.noreply.github.com> --- bot/exts/moderation/clean.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bot/exts/moderation/clean.py b/bot/exts/moderation/clean.py index fedb787f9..67f1851c4 100644 --- a/bot/exts/moderation/clean.py +++ b/bot/exts/moderation/clean.py @@ -486,24 +486,24 @@ class Clean(Cog): await self._clean_messages(ctx, channels, bots_only, users, regex, first_limit, second_limit) - @clean_group.command(name="user", aliases=["users"]) - async def clean_user( + @clean_group.command(name="users", aliases=["user"]) + async def clean_users( self, ctx: Context, - user: User, + users: Greedy[User], message_or_time: CleanLimit, *, channels: CleanChannels = None ) -> None: """ - Delete messages posted by the provided user, stop cleaning after reaching `message_or_time`. + Delete messages posted by the provided users, stop cleaning after reaching `message_or_time`. `message_or_time` can be either a message to stop at (exclusive), a timedelta for max message age, or an ISO datetime. If a message is specified, `channels` cannot be specified. """ - await self._clean_messages(ctx, users=[user], channels=channels, first_limit=message_or_time) + await self._clean_messages(ctx, users=users, channels=channels, first_limit=message_or_time) @clean_group.command(name="bots", aliases=["bot"]) async def clean_bots(self, ctx: Context, message_or_time: CleanLimit, *, channels: CleanChannels = None) -> None: -- cgit v1.2.3 From ff20531b08d7d2bdf7eed16de985ad41a5fdbb96 Mon Sep 17 00:00:00 2001 From: wookie184 Date: Sat, 28 May 2022 14:37:21 +0100 Subject: Add special handling for eval command followed by backticks. --- bot/exts/backend/error_handler.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/bot/exts/backend/error_handler.py b/bot/exts/backend/error_handler.py index 35dddd8dc..dd7867407 100644 --- a/bot/exts/backend/error_handler.py +++ b/bot/exts/backend/error_handler.py @@ -65,6 +65,8 @@ class ErrorHandler(Cog): if isinstance(e, errors.CommandNotFound) and not getattr(ctx, "invoked_from_error_handler", False): if await self.try_silence(ctx): return + if await self.try_run_eval(ctx): + return await self.try_get_tag(ctx) # Try to look for a tag with the command's name elif isinstance(e, errors.UserInputError): log.debug(debug_message) @@ -179,6 +181,31 @@ class ErrorHandler(Cog): if not any(role.id in MODERATION_ROLES for role in ctx.author.roles): await self.send_command_suggestion(ctx, ctx.invoked_with) + async def try_run_eval(self, ctx: Context) -> bool: + """ + Attempt to run eval command with backticks directly after command. + + For example: !eval```print("hi")``` + + Return True if command was invoked, else False + """ + old_message_content = ctx.message.content + + command, sep, end = ctx.message.content.partition("```") + ctx.message.content = command + " " + sep + end + new_ctx = await self.bot.get_context(ctx.message) + + eval_command = self.bot.get_command("eval") + if eval_command is None or new_ctx.command != eval_command: + ctx.message.content = old_message_content + return False + + log.debug("Running fixed eval command.") + new_ctx.invoked_from_error_handler = True + await self.bot.invoke(new_ctx) + + return True + async def send_command_suggestion(self, ctx: Context, command_name: str) -> None: """Sends user similar commands if any can be found.""" # No similar tag found, or tag on cooldown - -- cgit v1.2.3 From d9b48c5f4103afba015726a36900d7fc7233bc4d Mon Sep 17 00:00:00 2001 From: wookie184 Date: Sat, 28 May 2022 16:46:09 +0100 Subject: Copy message instead of modifying original --- bot/exts/backend/error_handler.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bot/exts/backend/error_handler.py b/bot/exts/backend/error_handler.py index dd7867407..5126c0267 100644 --- a/bot/exts/backend/error_handler.py +++ b/bot/exts/backend/error_handler.py @@ -1,3 +1,4 @@ +import copy import difflib from botcore.site_api import ResponseCodeError @@ -189,15 +190,14 @@ class ErrorHandler(Cog): Return True if command was invoked, else False """ - old_message_content = ctx.message.content + msg = copy.copy(ctx.message) - command, sep, end = ctx.message.content.partition("```") - ctx.message.content = command + " " + sep + end - new_ctx = await self.bot.get_context(ctx.message) + command, sep, end = msg.content.partition("```") + msg.content = command + " " + sep + end + new_ctx = await self.bot.get_context(msg) eval_command = self.bot.get_command("eval") if eval_command is None or new_ctx.command != eval_command: - ctx.message.content = old_message_content return False log.debug("Running fixed eval command.") -- cgit v1.2.3 From 96c7deab22f5018a17b97cd68e3914c37f926be5 Mon Sep 17 00:00:00 2001 From: wookie184 Date: Sat, 28 May 2022 16:46:42 +0100 Subject: Fix tests --- tests/bot/exts/backend/test_error_handler.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/bot/exts/backend/test_error_handler.py b/tests/bot/exts/backend/test_error_handler.py index d02bd7c34..0a58126e7 100644 --- a/tests/bot/exts/backend/test_error_handler.py +++ b/tests/bot/exts/backend/test_error_handler.py @@ -48,6 +48,7 @@ class ErrorHandlerTests(unittest.IsolatedAsyncioTestCase): cog = ErrorHandler(self.bot) cog.try_silence = AsyncMock() cog.try_get_tag = AsyncMock() + cog.try_run_eval = AsyncMock(return_value=False) for case in test_cases: with self.subTest(try_silence_return=case["try_silence_return"], try_get_tag=case["called_try_get_tag"]): @@ -76,6 +77,7 @@ class ErrorHandlerTests(unittest.IsolatedAsyncioTestCase): cog = ErrorHandler(self.bot) cog.try_silence = AsyncMock() cog.try_get_tag = AsyncMock() + cog.try_run_eval = AsyncMock() error = errors.CommandNotFound() @@ -83,6 +85,7 @@ class ErrorHandlerTests(unittest.IsolatedAsyncioTestCase): cog.try_silence.assert_not_awaited() cog.try_get_tag.assert_not_awaited() + cog.try_run_eval.assert_not_awaited() self.ctx.send.assert_not_awaited() async def test_error_handler_user_input_error(self): -- cgit v1.2.3 From c825718dbb8a597e6c9620601ed1b9b2ca5dcb98 Mon Sep 17 00:00:00 2001 From: Chris Lovering Date: Sat, 28 May 2022 22:31:37 +0100 Subject: Clean from public active threads when channels is set to * --- bot/exts/moderation/clean.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bot/exts/moderation/clean.py b/bot/exts/moderation/clean.py index 67f1851c4..1c47f8342 100644 --- a/bot/exts/moderation/clean.py +++ b/bot/exts/moderation/clean.py @@ -7,7 +7,7 @@ from datetime import datetime from itertools import takewhile from typing import Callable, Iterable, Literal, Optional, TYPE_CHECKING, Union -from discord import Colour, Message, NotFound, TextChannel, User, errors +from discord import Colour, Message, NotFound, TextChannel, Thread, User, errors from discord.ext.commands import Cog, Context, Converter, Greedy, group, has_any_role from discord.ext.commands.converter import TextChannelConverter from discord.ext.commands.errors import BadArgument @@ -130,8 +130,8 @@ class Clean(Cog): else: if channels == "*": channels = { - channel for channel in ctx.guild.channels - if isinstance(channel, TextChannel) + channel for channel in ctx.guild.channels + ctx.guild.threads + if isinstance(channel, (TextChannel, Thread)) # Assume that non-public channels are not needed to optimize for speed. and channel.permissions_for(ctx.guild.default_role).view_channel } -- cgit v1.2.3 From 597ad57356de88900fa6730f256aa53f3aa41ce5 Mon Sep 17 00:00:00 2001 From: Autonymic <75275746+Autonymic@users.noreply.github.com> Date: Sun, 29 May 2022 20:09:03 +0200 Subject: Update clean regex description with cleaner examples --- bot/exts/moderation/clean.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/bot/exts/moderation/clean.py b/bot/exts/moderation/clean.py index 5b01494ed..39eff9757 100644 --- a/bot/exts/moderation/clean.py +++ b/bot/exts/moderation/clean.py @@ -543,15 +543,13 @@ class Clean(Cog): If a timedelta or an ISO datetime is specified, `channels` can be specified to clean across multiple channels. An asterisk can also be used to designate cleanup across all channels. - The `regex` pattern must be provided enclosed in backticks, that will make it appear as a code section. + The `regex` pattern must be provided enclosed in backticks. - For example: \u02CB[0-9]\u02CB, which should appear as `[0-9]`. + For example: \\`[0-9]\\`. If the `regex` pattern contains spaces, it still needs to be enclosed in double quotes on top of that. - For example: "\u02CB[0-9]\u02CB", which should appear as "`[0-9]`". - - Do not copy and paste the backticks from the examples, they are special unicode characters that will not work. + For example: "\\`[0-9]\\`". """ await self._clean_messages(ctx, regex=regex, channels=channels, first_limit=message_or_time) -- cgit v1.2.3