diff options
author | 2022-05-01 23:37:55 +0100 | |
---|---|---|
committer | 2022-05-01 23:37:55 +0100 | |
commit | d3058d5ec5c8ab840a32ea6fb3e3760f4167bd2e (patch) | |
tree | 8ccfbe97745f6313b5a87203f1ce969240557db5 | |
parent | Make modpings rescheduling robust to unfilled cache (diff) | |
parent | Merge pull request #2152 from python-discord/chunnk-guild-if-not-chunked (diff) |
Merge branch 'main' into mbaruh/modpings-cache
-rw-r--r-- | bot/converters.py | 4 | ||||
-rw-r--r-- | bot/exts/backend/sync/_cog.py | 18 | ||||
-rw-r--r-- | bot/exts/backend/sync/_syncers.py | 13 | ||||
-rw-r--r-- | bot/exts/info/doc/_cog.py | 2 | ||||
-rw-r--r-- | bot/exts/utils/extensions.py | 18 | ||||
-rw-r--r-- | bot/exts/utils/thread_bumper.py | 41 | ||||
-rw-r--r-- | bot/resources/tags/or-gotcha.md | 1 | ||||
-rw-r--r-- | poetry.lock | 10 | ||||
-rw-r--r-- | pyproject.toml | 4 | ||||
-rw-r--r-- | tests/helpers.py | 2 |
10 files changed, 80 insertions, 33 deletions
diff --git a/bot/converters.py b/bot/converters.py index 7393a1ddc..910ed9e39 100644 --- a/bot/converters.py +++ b/bot/converters.py @@ -382,8 +382,8 @@ class Age(DurationDelta): class OffTopicName(Converter): """A converter that ensures an added off-topic name is valid.""" - ALLOWED_CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ!?'`-<>" - TRANSLATED_CHARACTERS = "๐ ๐ก๐ข๐ฃ๐ค๐ฅ๐ฆ๐ง๐จ๐ฉ๐ช๐ซ๐ฌ๐ญ๐ฎ๐ฏ๐ฐ๐ฑ๐ฒ๐ณ๐ด๐ต๐ถ๐ท๐ธ๐นว๏ผโโ-๏ผ๏ผ" + ALLOWED_CHARACTERS = r"ABCDEFGHIJKLMNOPQRSTUVWXYZ!?'`-<>\/" + TRANSLATED_CHARACTERS = "๐ ๐ก๐ข๐ฃ๐ค๐ฅ๐ฆ๐ง๐จ๐ฉ๐ช๐ซ๐ฌ๐ญ๐ฎ๐ฏ๐ฐ๐ฑ๐ฒ๐ณ๐ด๐ต๐ถ๐ท๐ธ๐นว๏ผโโ-๏ผ๏ผโงนโงธ" @classmethod def translate_name(cls, name: str, *, from_unicode: bool = True) -> str: diff --git a/bot/exts/backend/sync/_cog.py b/bot/exts/backend/sync/_cog.py index a5bf82397..433ff5024 100644 --- a/bot/exts/backend/sync/_cog.py +++ b/bot/exts/backend/sync/_cog.py @@ -1,3 +1,4 @@ +import asyncio from typing import Any, Dict from botcore.site_api import ResponseCodeError @@ -11,6 +12,7 @@ from bot.exts.backend.sync import _syncers from bot.log import get_logger log = get_logger(__name__) +MAX_ATTEMPTS = 3 class Sync(Cog): @@ -27,6 +29,22 @@ class Sync(Cog): if guild is None: return + attempts = 0 + while True: + attempts += 1 + if guild.chunked: + log.info("Guild was found to be chunked after %d attempt(s).", attempts) + break + + if attempts == MAX_ATTEMPTS: + log.info("Guild not chunked after %d attempts, calling chunk manually.", MAX_ATTEMPTS) + await guild.chunk() + break + + log.info("Attempt %d/%d: Guild not yet chunked, checking again in 10s.", attempts, MAX_ATTEMPTS) + await asyncio.sleep(10) + + log.info("Starting syncers.") for syncer in (_syncers.RoleSyncer, _syncers.UserSyncer): await syncer.sync(guild) diff --git a/bot/exts/backend/sync/_syncers.py b/bot/exts/backend/sync/_syncers.py index e1c4541ef..799137cb9 100644 --- a/bot/exts/backend/sync/_syncers.py +++ b/bot/exts/backend/sync/_syncers.py @@ -2,6 +2,7 @@ import abc import typing as t from collections import namedtuple +import discord.errors from botcore.site_api import ResponseCodeError from discord import Guild from discord.ext.commands import Context @@ -9,7 +10,6 @@ from more_itertools import chunked import bot from bot.log import get_logger -from bot.utils.members import get_or_fetch_member log = get_logger(__name__) @@ -157,7 +157,16 @@ class UserSyncer(Syncer): if db_user[db_field] != guild_value: updated_fields[db_field] = guild_value - if guild_user := await get_or_fetch_member(guild, db_user["id"]): + guild_user = guild.get_member(db_user["id"]) + if not guild_user and db_user["in_guild"]: + # The member was in the guild during the last sync. + # We try to fetch them to verify cache integrity. + try: + guild_user = await guild.fetch_member(db_user["id"]) + except discord.errors.NotFound: + guild_user = None + + if guild_user: seen_guild_users.add(guild_user.id) maybe_update("name", guild_user.name) diff --git a/bot/exts/info/doc/_cog.py b/bot/exts/info/doc/_cog.py index 079bfc942..dece44063 100644 --- a/bot/exts/info/doc/_cog.py +++ b/bot/exts/info/doc/_cog.py @@ -431,7 +431,7 @@ class DocCog(commands.Cog): async def refresh_command(self, ctx: commands.Context) -> None: """Refresh inventories and show the difference.""" old_inventories = set(self.base_urls) - with ctx.typing(): + async with ctx.typing(): await self.refresh_inventories() new_inventories = set(self.base_urls) diff --git a/bot/exts/utils/extensions.py b/bot/exts/utils/extensions.py index 9862988cb..0f5fc0de4 100644 --- a/bot/exts/utils/extensions.py +++ b/bot/exts/utils/extensions.py @@ -54,7 +54,7 @@ class Extensions(commands.Cog): if "*" in extensions or "**" in extensions: extensions = set(self.bot.all_extensions) - set(self.bot.extensions.keys()) - msg = self.batch_manage(Action.LOAD, *extensions) + msg = await self.batch_manage(Action.LOAD, *extensions) await ctx.send(msg) @extensions_group.command(name="unload", aliases=("ul",)) @@ -76,7 +76,7 @@ class Extensions(commands.Cog): if "*" in extensions or "**" in extensions: extensions = set(self.bot.extensions.keys()) - UNLOAD_BLACKLIST - msg = self.batch_manage(Action.UNLOAD, *extensions) + msg = await self.batch_manage(Action.UNLOAD, *extensions) await ctx.send(msg) @@ -100,7 +100,7 @@ class Extensions(commands.Cog): extensions = set(self.bot.extensions.keys()) | set(extensions) extensions.remove("*") - msg = self.batch_manage(Action.RELOAD, *extensions) + msg = await self.batch_manage(Action.RELOAD, *extensions) await ctx.send(msg) @@ -151,21 +151,21 @@ class Extensions(commands.Cog): return categories - def batch_manage(self, action: Action, *extensions: str) -> str: + async def batch_manage(self, action: Action, *extensions: str) -> str: """ Apply an action to multiple extensions and return a message with the results. If only one extension is given, it is deferred to `manage()`. """ if len(extensions) == 1: - msg, _ = self.manage(action, extensions[0]) + msg, _ = await self.manage(action, extensions[0]) return msg verb = action.name.lower() failures = {} for extension in extensions: - _, error = self.manage(action, extension) + _, error = await self.manage(action, extension) if error: failures[extension] = error @@ -180,17 +180,17 @@ class Extensions(commands.Cog): return msg - def manage(self, action: Action, ext: str) -> t.Tuple[str, t.Optional[str]]: + async def manage(self, action: Action, ext: str) -> t.Tuple[str, t.Optional[str]]: """Apply an action to an extension and return the status message and any error message.""" verb = action.name.lower() error_msg = None try: - action.value(self.bot, ext) + await action.value(self.bot, ext) except (commands.ExtensionAlreadyLoaded, commands.ExtensionNotLoaded): if action is Action.RELOAD: # When reloading, just load the extension if it was not loaded. - return self.manage(Action.LOAD, ext) + return await self.manage(Action.LOAD, ext) msg = f":x: Extension `{ext}` is already {verb}ed." log.debug(msg[4:]) diff --git a/bot/exts/utils/thread_bumper.py b/bot/exts/utils/thread_bumper.py index ee0636b37..743919d4e 100644 --- a/bot/exts/utils/thread_bumper.py +++ b/bot/exts/utils/thread_bumper.py @@ -2,6 +2,7 @@ import typing as t import discord from async_rediscache import RedisCache +from botcore.site_api import ResponseCodeError from discord.ext import commands from bot import constants @@ -11,6 +12,7 @@ from bot.pagination import LinePaginator from bot.utils import channel log = get_logger(__name__) +THREAD_BUMP_ENDPOINT = "bot/bumped-threads" class ThreadBumper(commands.Cog): @@ -45,7 +47,7 @@ class ThreadBumper(commands.Cog): thread.name, thread.id ) - await self.threads_to_bump.delete(thread.id) + await self.bot.api_client.delete(f"{THREAD_BUMP_ENDPOINT}/{thread.id}") else: await thread.edit(archived=False) @@ -54,18 +56,23 @@ class ThreadBumper(commands.Cog): await self.bot.wait_until_guild_available() threads_to_maybe_bump = [] - for thread_id, _ in await self.threads_to_bump.items(): + for thread_id in await self.bot.api_client.get(THREAD_BUMP_ENDPOINT): try: thread = await channel.get_or_fetch_channel(thread_id) except discord.NotFound: log.info("Thread %d has been deleted, removing from bumped threads.", thread_id) - await self.threads_to_bump.delete(thread_id) + await self.bot.api_client.delete(f"{THREAD_BUMP_ENDPOINT}/{thread_id}") + continue + + if not isinstance(thread, discord.Thread): + await self.bot.api_client.delete(f"{THREAD_BUMP_ENDPOINT}/{thread_id}") continue if thread.archived: threads_to_maybe_bump.append(thread) - await self.unarchive_threads_not_manually_archived(threads_to_maybe_bump) + if threads_to_maybe_bump: + await self.unarchive_threads_not_manually_archived(threads_to_maybe_bump) @commands.group(name="bump") async def thread_bump_group(self, ctx: commands.Context) -> None: @@ -82,10 +89,15 @@ class ThreadBumper(commands.Cog): else: raise commands.BadArgument("You must provide a thread, or run this command within a thread.") - if await self.threads_to_bump.contains(thread.id): + try: + await self.bot.api_client.get(f"{THREAD_BUMP_ENDPOINT}/{thread.id}") + except ResponseCodeError as e: + if e.status != 404: + raise + else: raise commands.BadArgument("This thread is already in the bump list.") - await self.threads_to_bump.set(thread.id, "sentinel") + await self.bot.api_client.post(THREAD_BUMP_ENDPOINT, data={"thread_id": thread.id}) await ctx.send(f":ok_hand:{thread.mention} has been added to the bump list.") @thread_bump_group.command(name="remove", aliases=("r", "rem", "d", "del", "delete")) @@ -97,21 +109,23 @@ class ThreadBumper(commands.Cog): else: raise commands.BadArgument("You must provide a thread, or run this command within a thread.") - if not await self.threads_to_bump.contains(thread.id): + try: + await self.bot.api_client.get(f"{THREAD_BUMP_ENDPOINT}/{thread.id}") + except ResponseCodeError: raise commands.BadArgument("This thread is not in the bump list.") - await self.threads_to_bump.delete(thread.id) + await self.bot.api_client.delete(f"{THREAD_BUMP_ENDPOINT}/{thread.id}") await ctx.send(f":ok_hand: {thread.mention} has been removed from the bump list.") @thread_bump_group.command(name="list", aliases=("get",)) async def list_all_threads_in_bump_list(self, ctx: commands.Context) -> None: """List all the threads in the bump list.""" - lines = [f"<#{k}>" for k, _ in await self.threads_to_bump.items()] + lines = [f"<#{thread_id}>" for thread_id in await self.bot.api_client.get(THREAD_BUMP_ENDPOINT)] embed = discord.Embed( title="Threads in the bump list", colour=constants.Colours.blue ) - await LinePaginator.paginate(lines, ctx, embed) + await LinePaginator.paginate(lines, ctx, embed, max_lines=10) @commands.Cog.listener() async def on_thread_update(self, _: discord.Thread, after: discord.Thread) -> None: @@ -123,7 +137,12 @@ class ThreadBumper(commands.Cog): if not after.archived: return - if await self.threads_to_bump.contains(after.id): + try: + await self.bot.api_client.get(f"{THREAD_BUMP_ENDPOINT}/{after.id}") + except ResponseCodeError as e: + if e.status != 404: + raise + else: await self.unarchive_threads_not_manually_archived([after]) async def cog_check(self, ctx: commands.Context) -> bool: diff --git a/bot/resources/tags/or-gotcha.md b/bot/resources/tags/or-gotcha.md index d75a73d78..25ade8620 100644 --- a/bot/resources/tags/or-gotcha.md +++ b/bot/resources/tags/or-gotcha.md @@ -1,5 +1,6 @@ When checking if something is equal to one thing or another, you might think that this is possible: ```py +# Incorrect... if favorite_fruit == 'grapefruit' or 'lemon': print("That's a weird favorite fruit to have.") ``` diff --git a/poetry.lock b/poetry.lock index 3cfcb8089..8281394e6 100644 --- a/poetry.lock +++ b/poetry.lock @@ -125,7 +125,7 @@ lxml = ["lxml"] [[package]] name = "bot-core" -version = "6.1.0" +version = "6.4.0" description = "Bot-Core provides the core functionality and utilities for the bots of the Python Discord community." category = "main" optional = false @@ -133,7 +133,7 @@ python-versions = "3.9.*" [package.dependencies] async-rediscache = {version = "0.2.0", extras = ["fakeredis"], optional = true, markers = "extra == \"async-rediscache\""} -"discord.py" = {url = "https://github.com/Rapptz/discord.py/archive/987235d5649e7c2b1a927637bab6547244ecb2cf.zip"} +"discord.py" = {url = "https://github.com/Rapptz/discord.py/archive/5a06fa5f3e28d2b7191722e1a84c541560008aea.zip"} statsd = "3.3.0" [package.extras] @@ -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.3.0.zip" +url = "https://github.com/python-discord/bot-core/archive/refs/tags/v6.4.0.zip" [[package]] name = "certifi" version = "2021.10.8" @@ -263,7 +263,7 @@ voice = ["PyNaCl (>=1.3.0,<1.6)"] [package.source] type = "url" -url = "https://github.com/Rapptz/discord.py/archive/987235d5649e7c2b1a927637bab6547244ecb2cf.zip" +url = "https://github.com/Rapptz/discord.py/archive/5a06fa5f3e28d2b7191722e1a84c541560008aea.zip" [[package]] name = "distlib" @@ -1150,7 +1150,7 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" python-versions = "3.9.*" -content-hash = "edd7c686b0f6f6dee5b48853b94c58038349ee17e87c1e3baaf4872dfc7ec721" +content-hash = "a07f619c75f8133982984eb506ad350144829f10c704421f09b3dbe72cd037d8" [metadata.files] aiodns = [ diff --git a/pyproject.toml b/pyproject.toml index 6977b4b09..402d05f70 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,9 +8,9 @@ license = "MIT" [tool.poetry.dependencies] python = "3.9.*" -"discord.py" = {url = "https://github.com/Rapptz/discord.py/archive/987235d5649e7c2b1a927637bab6547244ecb2cf.zip"} +"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.3.0.zip", extras = ["async-rediscache"]} +bot-core = {url = "https://github.com/python-discord/bot-core/archive/refs/tags/v6.4.0.zip", extras = ["async-rediscache"]} aiodns = "3.0.0" aiohttp = "3.8.1" diff --git a/tests/helpers.py b/tests/helpers.py index a6e4bdd66..5f3111616 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -171,7 +171,7 @@ class MockGuild(CustomMockMixin, unittest.mock.Mock, HashableMixin): spec_set = guild_instance def __init__(self, roles: Optional[Iterable[MockRole]] = None, **kwargs) -> None: - default_kwargs = {'id': next(self.discord_id), 'members': []} + default_kwargs = {'id': next(self.discord_id), 'members': [], "chunked": True} super().__init__(**collections.ChainMap(kwargs, default_kwargs)) self.roles = [MockRole(name="@everyone", position=1, id=0)] |