aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar wookie184 <[email protected]>2022-05-01 23:37:55 +0100
committerGravatar GitHub <[email protected]>2022-05-01 23:37:55 +0100
commitd3058d5ec5c8ab840a32ea6fb3e3760f4167bd2e (patch)
tree8ccfbe97745f6313b5a87203f1ce969240557db5
parentMake modpings rescheduling robust to unfilled cache (diff)
parentMerge pull request #2152 from python-discord/chunnk-guild-if-not-chunked (diff)
Merge branch 'main' into mbaruh/modpings-cache
-rw-r--r--bot/converters.py4
-rw-r--r--bot/exts/backend/sync/_cog.py18
-rw-r--r--bot/exts/backend/sync/_syncers.py13
-rw-r--r--bot/exts/info/doc/_cog.py2
-rw-r--r--bot/exts/utils/extensions.py18
-rw-r--r--bot/exts/utils/thread_bumper.py41
-rw-r--r--bot/resources/tags/or-gotcha.md1
-rw-r--r--poetry.lock10
-rw-r--r--pyproject.toml4
-rw-r--r--tests/helpers.py2
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)]