diff options
| author | 2020-06-12 17:28:56 +0200 | |
|---|---|---|
| committer | 2020-06-12 17:37:57 +0200 | |
| commit | 7bc6aff14c5a78b708b11cafbd4eba431b3fe52b (patch) | |
| tree | b9df691d40d50bf85934cb36fe06713ad4af28ce | |
| parent | Incidents: implement `make_confirmation_task` method (diff) | |
Incidents: implement `process_event` coroutine
This contains the main logic for handling reactions and glues all the
helpers together.
Unfortunately, gracefully handling everything that can go wrong in
the process requires quite a lot of code ~ but, at least to me, it
seems like this all should now be fairly safe.
The idea to await the message delete event before releasing the lock
was conceived by Ves, while Mark helped me refine it.
Co-authored-by: Sebastiaan Zeeff <[email protected]>
Co-authored-by: MarkKoz <[email protected]>
| -rw-r--r-- | bot/cogs/moderation/incidents.py | 55 |
1 files changed, 52 insertions, 3 deletions
diff --git a/bot/cogs/moderation/incidents.py b/bot/cogs/moderation/incidents.py index 2186530d9..00cceca7d 100644 --- a/bot/cogs/moderation/incidents.py +++ b/bot/cogs/moderation/incidents.py @@ -160,6 +160,58 @@ class Incidents(Cog): ) return self.bot.loop.create_task(coroutine) + async def process_event(self, reaction: str, incident: discord.Message, member: discord.Member) -> None: + """ + Process a valid `reaction_add` event in #incidents. + + First, we check that the reaction is a recognized `Signal` member, and that it was sent by + a permitted user (at least one role in `ALLOWED_ROLES`). If not, the reaction is removed. + + If the reaction was either `Signal.ACTIONED` or `Signal.NOT_ACTIONED`, we attempt to relay + the report to #incidents-archive. If successful, the original message is deleted. + + We do not release `event_lock` until we receive the corresponding `message_delete` event. + This ensures that if there is a racing event awaiting the lock, it will fail to find the + message, and will abort. + """ + members_roles: t.Set[int] = {role.id for role in member.roles} + if not members_roles & ALLOWED_ROLES: # Intersection is truthy on at least 1 common element + log.debug(f"Removing invalid reaction: user {member} is not permitted to send signals") + await incident.remove_reaction(reaction, member) + return + + if reaction not in ALLOWED_EMOJI: + log.debug(f"Removing invalid reaction: emoji {reaction} is not a valid signal") + await incident.remove_reaction(reaction, member) + return + + # If we reach this point, we know that `emoji` is a `Signal` member + signal = Signal(reaction) + log.debug(f"Received signal: {signal}") + + if signal not in (Signal.ACTIONED, Signal.NOT_ACTIONED): + log.debug("Reaction was valid, but no action is currently defined for it") + return + + relay_successful = await self.archive(incident, signal) + if not relay_successful: + log.debug("Original message will not be deleted as we failed to relay it to the archive") + return + + timeout = 5 # Seconds + confirmation_task = self.make_confirmation_task(incident, timeout) + + log.debug("Deleting original message") + await incident.delete() + + log.debug(f"Awaiting deletion confirmation: {timeout=} seconds") + try: + await confirmation_task + except asyncio.TimeoutError: + log.warning(f"Did not receive incident deletion confirmation within {timeout} seconds!") + else: + log.debug("Deletion was confirmed") + async def resolve_message(self, message_id: int) -> t.Optional[discord.Message]: """ Get `discord.Message` for `message_id` from cache, or API. @@ -191,9 +243,6 @@ class Incidents(Cog): log.debug("Message fetched successfully!") return message - async def process_event(self, reaction: str, message: discord.Message, member: discord.Member) -> None: - log.debug("Processing event...") - @Cog.listener() async def on_raw_reaction_add(self, payload: discord.RawReactionActionEvent) -> None: """ |