aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Sebastiaan Zeeff <[email protected]>2020-04-07 20:06:00 +0200
committerGravatar Sebastiaan Zeeff <[email protected]>2020-04-07 20:06:00 +0200
commit8ca0b7f5161f0a71df39a36758b3d8041c895fe8 (patch)
treeddd153ed525f3fda97bfedc3106f2194900dfcdd
parentChange help available embed to use occupied term (diff)
Ensure available help channels sync their permissions
The help channels in the `Help: Available` category should automatically synchronize their permissions with the category permissions to ensure that the overwrites we use to prevent people from claiming multiple help channels are properly enforced. Unfortunately, for unknown reasons, they sometimes get in an "out of sync" state that requires intervention to get them back in sync. This PR mitigates that issue by checking the available channel for their synchronisation status during certain critical times in our help channel system: 1. Whenever the overwrites for the category change 2. Whenever a channel is moved into the new category 3. After the categories have been reset during the initialization process The check is straightforward: The `ensure_permissions_synchronization` method iterates over all the channels in the category and checks if the channels are currently synchronizing their permissions. If not, we remedy that by making a channel edit request to the Discord API. If all channels were already "in sync", no API calls are made. The latter should make this an inexpensive mitigation procedure: As we typically have very few channels in the available category and channels mostly stay in sync, we typically do very little. To make this process a bit easier, I've factored out `set_permissions` calls to a helper function that also calls the `ensure_permissions_synchronization` method. The only exception is during the reset process: As we may edit multiple permissions in this loop, it's better to only ensure the synchronization after we're done with all permission changes.
-rw-r--r--bot/cogs/help_channels.py51
1 files changed, 48 insertions, 3 deletions
diff --git a/bot/cogs/help_channels.py b/bot/cogs/help_channels.py
index 915961b34..697a4d3b7 100644
--- a/bot/cogs/help_channels.py
+++ b/bot/cogs/help_channels.py
@@ -427,6 +427,12 @@ class HelpChannels(Scheduler, commands.Cog):
topic=AVAILABLE_TOPIC,
)
+ log.trace(
+ f"Ensuring that all channels in `{self.available_category}` have "
+ f"synchronized permissions after moving `{channel}` into it."
+ )
+ await self.ensure_permissions_synchronization(self.available_category)
+
async def move_to_dormant(self, channel: discord.TextChannel) -> None:
"""Make the `channel` dormant."""
log.info(f"Moving #{channel} ({channel.id}) to the Dormant category.")
@@ -544,6 +550,39 @@ class HelpChannels(Scheduler, commands.Cog):
# be put in the queue.
await self.move_to_available()
+ @staticmethod
+ async def ensure_permissions_synchronization(category: discord.CategoryChannel) -> None:
+ """
+ Ensure that all channels in the `category` have their permissions synchronized.
+
+ This method mitigates an issue we have yet to find the cause for: Every so often, a channel in the
+ `Help: Available` category gets in a state in which it will no longer synchronizes its permissions
+ with the category. To prevent that, we iterate over the channels in the category and edit the channels
+ that are observed to be in such a state. If no "out of sync" channels are observed, this method will
+ not make API calls and should be fairly inexpensive to run.
+ """
+ for channel in category.channels:
+ if not channel.permissions_synced:
+ log.info(f"The permissions of channel `{channel}` were out of sync with category `{category}`.")
+ await channel.edit(sync_permissions=True)
+
+ async def update_category_permissions(
+ self, category: discord.CategoryChannel, member: discord.Member, **permissions
+ ) -> None:
+ """
+ Update the permissions of the given `member` for the given `category` with `permissions` passed.
+
+ After updating the permissions for the member in the category, this helper function will call the
+ `ensure_permissions_synchronization` method to ensure that all channels are still synchronizing their
+ permissions with the category. It's currently unknown why some channels get "out of sync", but this
+ hopefully mitigates the issue.
+ """
+ log.trace(f"Updating permissions for `{member}` in `{category}` with {permissions}.")
+ await category.set_permissions(member, **permissions)
+
+ log.trace(f"Ensuring that all channels in `{category}` are synchronized after permissions update.")
+ await self.ensure_permissions_synchronization(category)
+
async def reset_send_permissions(self) -> None:
"""Reset send permissions for members with it set to False in the Available category."""
log.trace("Resetting send permissions in the Available category.")
@@ -551,7 +590,13 @@ class HelpChannels(Scheduler, commands.Cog):
for member, overwrite in self.available_category.overwrites.items():
if isinstance(member, discord.Member) and overwrite.send_messages is False:
log.trace(f"Resetting send permissions for {member} ({member.id}).")
- await self.available_category.set_permissions(member, send_messages=None)
+
+ # We don't use the permissions helper function here as we may have to reset multiple overwrites
+ # and we don't want to enforce the permissions synchronization in each iteration.
+ await self.available_category.set_permissions(member, overwrite=None)
+
+ log.trace(f"Ensuring channels in `Help: Available` are synchronized after permissions reset.")
+ await self.ensure_permissions_synchronization(self.available_category)
async def revoke_send_permissions(self, member: discord.Member) -> None:
"""
@@ -564,14 +609,14 @@ class HelpChannels(Scheduler, commands.Cog):
f"Revoking {member}'s ({member.id}) send message permissions in the Available category."
)
- await self.available_category.set_permissions(member, send_messages=False)
+ await self.update_category_permissions(self.available_category, member, send_messages=False)
# Cancel the existing task, if any.
# Would mean the user somehow bypassed the lack of permissions (e.g. user is guild owner).
self.cancel_task(member.id, ignore_missing=True)
timeout = constants.HelpChannels.claim_minutes * 60
- callback = self.available_category.set_permissions(member, overwrite=None)
+ callback = self.update_category_permissions(self.available_category, member, overwrite=None)
log.trace(f"Scheduling {member}'s ({member.id}) send message permissions to be reinstated.")
self.schedule_task(member.id, TaskData(timeout, callback))