diff options
| -rw-r--r-- | bot/cogs/sync/syncers.py | 42 | 
1 files changed, 32 insertions, 10 deletions
| diff --git a/bot/cogs/sync/syncers.py b/bot/cogs/sync/syncers.py index 2ba9a2a3a..2376a3f6f 100644 --- a/bot/cogs/sync/syncers.py +++ b/bot/cogs/sync/syncers.py @@ -21,6 +21,7 @@ _Diff = namedtuple('Diff', ('created', 'updated', 'deleted'))  class Syncer(abc.ABC):      """Base class for synchronising the database with objects in the Discord cache.""" +    _REACTION_EMOJIS = (constants.Emojis.check_mark, constants.Emojis.cross_mark)      CONFIRM_TIMEOUT = 60 * 5  # 5 minutes      MAX_DIFF = 10 @@ -33,17 +34,16 @@ class Syncer(abc.ABC):          """The name of the syncer; used in output messages and logging."""          raise NotImplementedError -    async def _confirm(self, author: Member, message: t.Optional[Message] = None) -> bool: +    async def _send_prompt(self, message: t.Optional[Message] = None) -> t.Optional[Message]:          """ -        Send a prompt to confirm or abort a sync using reactions and return True if confirmed. +        Send a prompt to confirm or abort a sync using reactions and return the sent message.          If a message is given, it is edited to display the prompt and reactions. Otherwise, a new -        message is sent to the dev-core channel and mentions the core developers role. +        message is sent to the dev-core channel and mentions the core developers role. If the +        channel cannot be retrieved, return None.          """          log.trace(f"Sending {self.name} sync confirmation prompt.") -        allowed_emoji = (constants.Emojis.check_mark, constants.Emojis.cross_mark) -        mention = ""          msg_content = (              f'Possible cache issue while syncing {self.name}s. '              f'More than {self.MAX_DIFF} {self.name}s were changed. ' @@ -64,7 +64,7 @@ class Syncer(abc.ABC):                          f"Failed to fetch channel for sending sync confirmation prompt; "                          f"aborting {self.name} sync."                      ) -                    return False +                    return None              mention = f"<@&{constants.Roles.core_developer}> "              message = await channel.send(f"{mention}{msg_content}") @@ -73,9 +73,19 @@ class Syncer(abc.ABC):          # Add the initial reactions.          log.trace(f"Adding reactions to {self.name} syncer confirmation prompt.") -        for emoji in allowed_emoji: +        for emoji in self._REACTION_EMOJIS:              await message.add_reaction(emoji) +        return message + +    async def _wait_for_confirmation(self, author: Member, message: Message) -> bool: +        """ +        Wait for a confirmation reaction by `author` on `message` and return True if confirmed. + +        If `author` is a bot user, then anyone with the core developers role may react to confirm. +        If there is no reaction within `CONFIRM_TIMEOUT` seconds, return False. To acknowledge the +        reaction (or lack thereof), `message` will be edited. +        """          def check(_reaction, user):  # noqa: TYP              # For automatic syncs, check for the core dev role instead of an exact author              has_role = any(constants.Roles.core_developer == role.id for role in user.roles) @@ -83,9 +93,15 @@ class Syncer(abc.ABC):                  _reaction.message.id == message.id                  and not user.bot                  and has_role if author.bot else user == author -                and str(_reaction.emoji) in allowed_emoji +                and str(_reaction.emoji) in self._REACTION_EMOJIS              ) +        # Preserve the core-dev role mention in the message edits so users aren't confused about +        # where notifications came from. +        mention = "" +        if message.role_mentions: +            mention = message.role_mentions[0].mention +          reaction = None          try:              log.trace(f"Waiting for a reaction to the {self.name} syncer confirmation prompt.") @@ -137,8 +153,14 @@ class Syncer(abc.ABC):          totals = {k: len(v) for k, v in diff._asdict().items() if v is not None}          log.trace(f"Determining if confirmation prompt should be sent for {self.name} syncer.") -        if sum(totals.values()) > self.MAX_DIFF and not await self._confirm(author, message): -            return  # Sync aborted. +        if sum(totals.values()) > self.MAX_DIFF: +            message = await self._send_prompt(message) +            if not message: +                return  # Couldn't get channel. + +            confirmed = await self._wait_for_confirmation(author, message) +            if not confirmed: +                return  # Sync aborted.          await self._sync(diff) | 
