aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar ItsDrike <[email protected]>2020-06-01 19:09:22 +0200
committerGravatar ItsDrike <[email protected]>2020-06-01 19:09:22 +0200
commiteac3be892a31a508f966fd73f1802086d83ed954 (patch)
tree05714cd3061ca8a9b401d31e686120a09a51a22f
parentMerge pull request #973 from python-discord/kwzrd/improve-free (diff)
Use Scheduler instead of asyncio.sleep on silence cog
`asyncio.sleep` doesn't provide us with the ability to stop that timer, while in most of the cases, this is fine, there is a possibility that channel will be unsilenced manually and silenced again, but this sleep from the first silence will cancel the second (new) silence. This will replace this `asyncio.sleep` with Scheduler which provides the ability to cancel the unsilencing task when aborted manually. That means we also have the ability to send a response if the channel is not silenced and someone tries to unsilence it.
-rw-r--r--bot/cogs/moderation/silence.py66
1 files changed, 56 insertions, 10 deletions
diff --git a/bot/cogs/moderation/silence.py b/bot/cogs/moderation/silence.py
index 25febfa51..a2fd39906 100644
--- a/bot/cogs/moderation/silence.py
+++ b/bot/cogs/moderation/silence.py
@@ -1,19 +1,54 @@
import asyncio
+import datetime
import logging
+from collections import namedtuple
from contextlib import suppress
from typing import Optional
-from discord import TextChannel
-from discord.ext import commands, tasks
-from discord.ext.commands import Context
-
from bot.bot import Bot
-from bot.constants import Channels, Emojis, Guild, MODERATION_ROLES, Roles
+from bot.constants import MODERATION_ROLES, Channels, Emojis, Guild, Roles
from bot.converters import HushDurationConverter
+from bot.utils import time
from bot.utils.checks import with_role_check
+from bot.utils.scheduling import Scheduler
+from discord import TextChannel
+from discord.ext import commands, tasks
+from discord.ext.commands import Context
log = logging.getLogger(__name__)
+SilencedChannel = namedtuple(
+ "SilencedChannel", ("id", "ctx", "silence", "stop")
+)
+
+
+class UnsilenceScheduler(Scheduler):
+ """Scheduler for unsilencing channels"""
+
+ def __init__(self, bot: Bot):
+ super().__init__()
+
+ self.bot = bot
+
+ async def schedule_unsilence(self, channel: SilencedChannel) -> None:
+ """Schedule expiration for silenced channels"""
+ await self.bot.wait_until_guild_available()
+ log.debug("Scheduling unsilencer")
+ self.schedule_task(channel.id, channel)
+
+ async def _scheduled_task(self, channel: SilencedChannel) -> None:
+ """
+ Removes expired silenced channel from `silence.muted_channels`
+ and calls `silence.unsilence` to unsilence the channel
+ after the silence expires
+ """
+ await time.wait_until(channel.stop)
+ log.info("Unsilencing channel after set delay.")
+
+ # Because `silence.unsilence` explicitly cancels this scheduled task, it is shielded
+ # to avoid prematurely cancelling itself.
+ await asyncio.shield(channel.ctx.invoke(channel.silence.unsilence))
+
class SilenceNotifier(tasks.Loop):
"""Loop notifier for posting notices to `alert_channel` containing added channels."""
@@ -61,6 +96,7 @@ class Silence(commands.Cog):
self.muted_channels = set()
self._get_instance_vars_task = self.bot.loop.create_task(self._get_instance_vars())
self._get_instance_vars_event = asyncio.Event()
+ self.scheduler = UnsilenceScheduler(bot)
async def _get_instance_vars(self) -> None:
"""Get instance variables after they're available to get from the guild."""
@@ -90,9 +126,15 @@ class Silence(commands.Cog):
return
await ctx.send(f"{Emojis.check_mark} silenced current channel for {duration} minute(s).")
- await asyncio.sleep(duration*60)
- log.info("Unsilencing channel after set delay.")
- await ctx.invoke(self.unsilence)
+
+ channel = SilencedChannel(
+ id=ctx.channel.id,
+ ctx=ctx,
+ silence=self,
+ stop=datetime.datetime.now() + datetime.timedelta(minutes=duration),
+ )
+
+ await self.scheduler.schedule_unsilence(channel)
@commands.command(aliases=("unhush",))
async def unsilence(self, ctx: Context) -> None:
@@ -103,8 +145,11 @@ class Silence(commands.Cog):
"""
await self._get_instance_vars_event.wait()
log.debug(f"Unsilencing channel #{ctx.channel} from {ctx.author}'s command.")
- if await self._unsilence(ctx.channel):
- await ctx.send(f"{Emojis.check_mark} unsilenced current channel.")
+ if not await self._unsilence(ctx.channel):
+ await ctx.send(f"{Emojis.cross_mark} current channel is not silenced.")
+ return
+
+ await ctx.send(f"{Emojis.check_mark} unsilenced current channel.")
async def _silence(self, channel: TextChannel, persistent: bool, duration: Optional[int]) -> bool:
"""
@@ -141,6 +186,7 @@ class Silence(commands.Cog):
await channel.set_permissions(self._verified_role, **dict(current_overwrite, send_messages=None))
log.info(f"Unsilenced channel #{channel} ({channel.id}).")
self.notifier.remove_channel(channel)
+ self.scheduler.cancel_task(channel.id)
self.muted_channels.discard(channel)
return True
log.info(f"Tried to unsilence channel #{channel} ({channel.id}) but the channel was not silenced.")