Ejemplo n.º 1
0
    async def get_thread_cooldown(self, author: discord.Member):
        thread_cooldown = self.config.get("thread_cooldown")
        now = datetime.utcnow()

        if thread_cooldown == isodate.Duration():
            return

        last_log = await self.api.get_latest_user_logs(author.id)

        if last_log is None:
            logger.debug("Last thread wasn't found, %s.", author.name)
            return

        last_log_closed_at = last_log.get("closed_at")

        if not last_log_closed_at:
            logger.debug("Last thread was not closed, %s.", author.name)
            return

        try:
            cooldown = datetime.fromisoformat(last_log_closed_at) + thread_cooldown
        except ValueError:
            logger.warning("Error with 'thread_cooldown'.", exc_info=True)
            cooldown = datetime.fromisoformat(last_log_closed_at) + self.config.remove(
                "thread_cooldown"
            )

        if cooldown > now:
            # User messaged before thread cooldown ended
            delta = human_timedelta(cooldown)
            logger.debug("Blocked due to thread cooldown, user %s.", author.name)
            return delta
        return
Ejemplo n.º 2
0
    async def _restart_close_timer(self):
        """
        This will create or restart a timer to automatically close this
        thread.
        """
        timeout = await self._fetch_timeout()

        # Exit if timeout was not set
        if timeout is None:
            return

        # Set timeout seconds
        seconds = timeout.total_seconds()
        # seconds = 20  # Uncomment to debug with just 20 seconds
        reset_time = datetime.utcnow() + timedelta(seconds=seconds)
        human_time = human_timedelta(dt=reset_time)

        # Grab message
        close_message = self.bot.config.get(
            "thread_auto_close_response",
            f"This thread has been closed automatically due to inactivity "
            f"after {human_time}.",
        )
        time_marker_regex = "%t"
        if len(re.findall(time_marker_regex, close_message)) == 1:
            close_message = re.sub(time_marker_regex, str(human_time),
                                   close_message)
        elif len(re.findall(time_marker_regex, close_message)) > 1:
            logger.warning(
                "The thread_auto_close_response should only contain one"
                f" '{time_marker_regex}' to specify time.")

        await self.close(closer=self.bot.user,
                         after=seconds,
                         message=close_message)
Ejemplo n.º 3
0
    def check_guild_age(self, author: discord.Member) -> bool:
        guild_age = self.config.get("guild_age")
        now = datetime.utcnow()

        if not hasattr(author, "joined_at"):
            logger.warning("Not in guild, cannot verify guild_age, %s.", author.name)
            return True

        try:
            min_guild_age = author.joined_at + guild_age
        except ValueError:
            logger.warning("Error with 'guild_age'.", exc_info=True)
            min_guild_age = author.joined_at + self.config.remove("guild_age")

        if min_guild_age > now:
            # User has not stayed in the guild for long enough
            delta = human_timedelta(min_guild_age)
            logger.debug("Blocked due to guild age, user %s.", author.name)

            if str(author.id) not in self.blocked_users:
                new_reason = f"System Message: Recently Joined. Required to wait for {delta}."
                self.blocked_users[str(author.id)] = new_reason

            return False
        return True
Ejemplo n.º 4
0
    def check_guild_age(self, author: discord.Member) -> bool:
        guild_age = self.config.get("guild_age")
        now = datetime.utcnow()

        if not hasattr(author, "joined_at"):
            logger.warning("Not in guild, cannot verify guild_age, %s.",
                           author.name)
            return True

        try:
            min_guild_age = author.joined_at + guild_age
        except ValueError:
            logger.warning("Errore con la configurazione 'guild_age'.",
                           exc_info=True)
            min_guild_age = author.joined_at + self.config.remove("guild_age")

        if min_guild_age > now:
            # User has not stayed in the guild for long enough
            delta = human_timedelta(min_guild_age)
            logger.debug(
                "L'utente %s è stato bloccato per via dell'eta dell'account",
                author.name)

            if str(author.id) not in self.blocked_users:
                new_reason = (
                    f"Messaggio di sistema: Entrato di recende. È richiesto aspettare per {delta}."
                )
                self.blocked_users[str(author.id)] = new_reason

            return False
        return True
Ejemplo n.º 5
0
    def check_account_age(self, author: discord.Member) -> bool:
        account_age = self.config.get("account_age")
        now = datetime.utcnow()

        try:
            min_account_age = author.created_at + account_age
        except ValueError:
            logger.warning("Errore con la configurazione 'account_age'.",
                           exc_info=True)
            min_account_age = author.created_at + self.config.remove(
                "account_age")

        if min_account_age > now:
            # User account has not reached the required time
            delta = human_timedelta(min_account_age)
            logger.debug(
                "L'utente %s è stato bloccato per via dell'età del suo account.",
                author.name)

            if str(author.id) not in self.blocked_users:
                new_reason = (
                    f"Messaggio di sistema: Account nuovo. È richiesto aspettare per {delta}."
                )
                self.blocked_users[str(author.id)] = new_reason

            return False
        return True
Ejemplo n.º 6
0
    async def _restart_close_timer(self):
        """
        This will create or restart a timer to automatically close this
        thread.
        """
        timeout = self.bot.config.get("thread_auto_close")

        # Exit if timeout was not set
        if timeout == isodate.Duration():
            return

        # Set timeout seconds
        seconds = timeout.total_seconds()
        # seconds = 20  # Uncomment to debug with just 20 seconds
        reset_time = datetime.utcnow() + timedelta(seconds=seconds)
        human_time = human_timedelta(dt=reset_time)

        if self.bot.config.get("thread_auto_close_silently"):
            return await self.close(closer=self.bot.user, silent=True, after=int(seconds), auto_close=True)

        # Grab message
        close_message = self.bot.formatter.format(
            self.bot.config["thread_auto_close_response"], timeout=human_time
        )

        time_marker_regex = "%t"
        if len(re.findall(time_marker_regex, close_message)) == 1:
            close_message = re.sub(time_marker_regex, str(human_time), close_message)
        elif len(re.findall(time_marker_regex, close_message)) > 1:
            logger.warning(
                "The thread_auto_close_response should only contain one '%s' to specify time.",
                time_marker_regex,
            )

        await self.close(closer=self.bot.user, after=int(seconds), message=close_message, auto_close=True)
Ejemplo n.º 7
0
    async def send_scheduled_close_message(self, ctx, after, silent=False):
        human_delta = human_timedelta(after.dt)

        silent = '*silently* ' if silent else ''

        em = discord.Embed(
            title='Scheduled close',
            description=f'This thread will close {silent}in {human_delta}.',
            color=discord.Color.red())

        if after.arg and not silent:
            em.add_field(name='Message', value=after.arg)

        em.set_footer(
            text='Closing will be cancelled if a thread message is sent.')
        em.timestamp = after.dt

        await ctx.send(embed=em)
Ejemplo n.º 8
0
    async def send_scheduled_close_message(self, ctx, after, silent=False):
        human_delta = human_timedelta(after.dt)

        silent = "*silently* " if silent else ""

        embed = discord.Embed(
            title="Scheduled close",
            description=f"This thread will close {silent}in {human_delta}.",
            color=self.bot.error_color,
        )

        if after.arg and not silent:
            embed.add_field(name="Message", value=after.arg)

        embed.set_footer(text="Closing will be cancelled if a thread message is sent.")
        embed.timestamp = after.dt

        await ctx.send(embed=embed)
    async def send_scheduled_close_message(self, ctx, after, silent=False):
        human_delta = human_timedelta(after.dt)

        silent = "*silently* " if silent else ""

        embed = discord.Embed(
            title="Chiusura automatica",
            description=f"Questo thread si chiuderà {silent}in {human_delta}.",
            color=self.bot.error_color,
        )

        if after.arg and not silent:
            embed.add_field(name="Message", value=after.arg)

        embed.set_footer(text="La chiusura sarà annullata "
                         "se viene inviato un messaggio di thread.")
        embed.timestamp = after.dt

        await ctx.send(embed=embed)
Ejemplo n.º 10
0
    async def send_scheduled_close_message(ctx, after, silent=False):
        human_delta = human_timedelta(after.dt)

        silent = '*silencieusement* ' if silent else ''

        embed = discord.Embed(
            title='Fermeture prévue',
            description=f'Ce ticket se fermera {silent}dans {human_delta}.',
            color=discord.Color.red()
        )

        if after.arg and not silent:
            embed.add_field(name='Message', value=after.arg)

        embed.set_footer(text='La fermeture sera annulée '
                              'si un message de discussion est envoyé.')
        embed.timestamp = after.dt

        await ctx.send(embed=embed)
Ejemplo n.º 11
0
    def check_account_age(self, author: discord.Member) -> bool:
        account_age = self.config.get("account_age")
        now = datetime.utcnow()

        try:
            min_account_age = author.created_at + account_age
        except ValueError:
            logger.warning("Error with 'account_age'.", exc_info=True)
            min_account_age = author.created_at + self.config.remove("account_age")

        if min_account_age > now:
            # User account has not reached the required time
            delta = human_timedelta(min_account_age)
            logger.debug("Blocked due to account age, user %s.", author.name)

            if str(author.id) not in self.blocked_users:
                new_reason = f"System Message: New Account. Required to wait for {delta}."
                self.blocked_users[str(author.id)] = new_reason

            return False
        return True
Ejemplo n.º 12
0
    async def get_thread_cooldown(self, author: discord.Member):
        thread_cooldown = self.config.get("thread_cooldown")
        now = datetime.utcnow()

        if thread_cooldown == isodate.Duration():
            return

        last_log = await self.api.get_latest_user_logs(author.id)

        if last_log is None:
            logger.debug("L'ultima stanza non è stata trovata, %s.",
                         author.name)
            return

        last_log_closed_at = last_log.get("closed_at")

        if not last_log_closed_at:
            logger.debug("L'ultima stanza non è stata chiusa., %s.",
                         author.name)
            return

        try:
            cooldown = datetime.fromisoformat(
                last_log_closed_at) + thread_cooldown
        except ValueError:
            logger.warning("Errore con la configurazione 'thread_cooldown'.",
                           exc_info=True)
            cooldown = datetime.fromisoformat(
                last_log_closed_at) + self.config.remove("thread_cooldown")

        if cooldown > now:
            # User messaged before thread cooldown ended
            delta = human_timedelta(cooldown)
            logger.debug(
                "L'utente %s è stato bloccato per il cooldown della creazione di thread.",
                author.name,
            )
            return delta
        return
Ejemplo n.º 13
0
    async def process_modmail(self, message: discord.Message) -> None:
        """Processes messages sent to the bot."""
        sent_emoji, blocked_emoji = await self.retrieve_emoji()
        now = datetime.utcnow()

        account_age = self.config.get("account_age")
        guild_age = self.config.get("guild_age")
        if account_age is None:
            account_age = isodate.duration.Duration()
        else:
            try:
                account_age = isodate.parse_duration(account_age)
            except isodate.ISO8601Error:
                logger.warning(
                    "The account age limit needs to be a "
                    "ISO-8601 duration formatted duration string "
                    'greater than 0 days, not "%s".',
                    str(account_age),
                )
                del self.config.cache["account_age"]
                await self.config.update()
                account_age = isodate.duration.Duration()

        if guild_age is None:
            guild_age = isodate.duration.Duration()
        else:
            try:
                guild_age = isodate.parse_duration(guild_age)
            except isodate.ISO8601Error:
                logger.warning(
                    "The guild join age limit needs to be a "
                    "ISO-8601 duration formatted duration string "
                    'greater than 0 days, not "%s".',
                    str(guild_age),
                )
                del self.config.cache["guild_age"]
                await self.config.update()
                guild_age = isodate.duration.Duration()

        reason = self.blocked_users.get(str(message.author.id))
        if reason is None:
            reason = ""

        try:
            min_account_age = message.author.created_at + account_age
        except ValueError as exc:
            logger.warning(exc.args[0])
            del self.config.cache["account_age"]
            await self.config.update()
            min_account_age = now

        try:
            member = self.guild.get_member(message.author.id)
            if member:
                min_guild_age = member.joined_at + guild_age
            else:
                min_guild_age = now
        except ValueError as exc:
            logger.warning(exc.args[0])
            del self.config.cache["guild_age"]
            await self.config.update()
            min_guild_age = now

        if min_account_age > now:
            # User account has not reached the required time
            reaction = blocked_emoji
            changed = False
            delta = human_timedelta(min_account_age)

            if str(message.author.id) not in self.blocked_users:
                new_reason = (
                    f"System Message: New Account. Required to wait for {delta}."
                )
                self.config.blocked[str(message.author.id)] = new_reason
                await self.config.update()
                changed = True

            if reason.startswith("System Message: New Account.") or changed:
                await message.channel.send(embed=discord.Embed(
                    title="Message not sent!",
                    description=f"Your must wait for {delta} "
                    f"before you can contact {self.user.mention}.",
                    color=discord.Color.red(),
                ))

        elif min_guild_age > now:
            # User has not stayed in the guild for long enough
            reaction = blocked_emoji
            changed = False
            delta = human_timedelta(min_guild_age)

            if str(message.author.id) not in self.blocked_users:
                new_reason = (
                    f"System Message: Recently Joined. Required to wait for {delta}."
                )
                self.config.blocked[str(message.author.id)] = new_reason
                await self.config.update()
                changed = True

            if reason.startswith(
                    "System Message: Recently Joined.") or changed:
                await message.channel.send(embed=discord.Embed(
                    title="Message not sent!",
                    description=f"Your must wait for {delta} "
                    f"before you can contact {self.user.mention}.",
                    color=discord.Color.red(),
                ))

        elif str(message.author.id) in self.blocked_users:
            reaction = blocked_emoji
            if reason.startswith(
                    "System Message: New Account.") or reason.startswith(
                        "System Message: Recently Joined."):
                # Met the age limit already
                reaction = sent_emoji
                del self.config.blocked[str(message.author.id)]
                await self.config.update()
            else:
                end_time = re.search(r"%(.+?)%$", reason)
                if end_time is not None:
                    after = (datetime.fromisoformat(end_time.group(1)) -
                             now).total_seconds()
                    if after <= 0:
                        # No longer blocked
                        reaction = sent_emoji
                        del self.config.blocked[str(message.author.id)]
                        await self.config.update()
        else:
            reaction = sent_emoji

        if reaction != "disable":
            try:
                await message.add_reaction(reaction)
            except (discord.HTTPException, discord.InvalidArgument):
                pass

        if str(message.author.id) not in self.blocked_users:
            thread = await self.threads.find_or_create(message.author)
            await thread.send(message)
Ejemplo n.º 14
0
    async def _process_blocked(self, message: discord.Message) -> bool:
        sent_emoji, blocked_emoji = await self.retrieve_emoji()

        if str(message.author.id) in self.blocked_whitelisted_users:
            if str(message.author.id) in self.blocked_users:
                self.blocked_users.pop(str(message.author.id))
                await self.config.update()

            if sent_emoji != "disable":
                try:
                    await message.add_reaction(sent_emoji)
                except (discord.HTTPException, discord.InvalidArgument):
                    logger.warning("Failed to add sent_emoji.", exc_info=True)

            return False

        now = datetime.utcnow()

        account_age = self.config["account_age"]
        guild_age = self.config["guild_age"]

        if account_age is None:
            account_age = isodate.Duration()
        if guild_age is None:
            guild_age = isodate.Duration()

        if not isinstance(account_age, isodate.Duration):
            try:
                account_age = isodate.parse_duration(account_age)
            except isodate.ISO8601Error:
                logger.warning(
                    "The account age limit needs to be a "
                    "ISO-8601 duration formatted duration string "
                    'greater than 0 days, not "%s".',
                    str(account_age),
                )
                account_age = self.config.remove("account_age")

        if not isinstance(guild_age, isodate.Duration):
            try:
                guild_age = isodate.parse_duration(guild_age)
            except isodate.ISO8601Error:
                logger.warning(
                    "The guild join age limit needs to be a "
                    "ISO-8601 duration formatted duration string "
                    'greater than 0 days, not "%s".',
                    str(guild_age),
                )
                guild_age = self.config.remove("guild_age")

        reason = self.blocked_users.get(str(message.author.id)) or ""
        min_guild_age = min_account_age = now

        try:
            min_account_age = message.author.created_at + account_age
        except ValueError:
            logger.warning("Error with 'account_age'.", exc_info=True)
            self.config.remove("account_age")

        try:
            joined_at = getattr(message.author, "joined_at", None)
            if joined_at is not None:
                min_guild_age = joined_at + guild_age
        except ValueError:
            logger.warning("Error with 'guild_age'.", exc_info=True)
            self.config.remove("guild_age")

        if min_account_age > now:
            # User account has not reached the required time
            reaction = blocked_emoji
            changed = False
            delta = human_timedelta(min_account_age)
            logger.debug("Blocked due to account age, user %s.",
                         message.author.name)

            if str(message.author.id) not in self.blocked_users:
                new_reason = (
                    f"System Message: New Account. Required to wait for {delta}."
                )
                self.blocked_users[str(message.author.id)] = new_reason
                changed = True

            if reason.startswith("System Message: New Account.") or changed:
                await message.channel.send(embed=discord.Embed(
                    title="Message not sent!",
                    description=f"Your must wait for {delta} "
                    f"before you can contact me.",
                    color=discord.Color.red(),
                ))

        elif min_guild_age > now:
            # User has not stayed in the guild for long enough
            reaction = blocked_emoji
            changed = False
            delta = human_timedelta(min_guild_age)
            logger.debug("Blocked due to guild age, user %s.",
                         message.author.name)

            if str(message.author.id) not in self.blocked_users:
                new_reason = (
                    f"System Message: Recently Joined. Required to wait for {delta}."
                )
                self.blocked_users[str(message.author.id)] = new_reason
                changed = True

            if reason.startswith(
                    "System Message: Recently Joined.") or changed:
                await message.channel.send(embed=discord.Embed(
                    title="Message not sent!",
                    description=f"Your must wait for {delta} "
                    f"before you can contact me.",
                    color=discord.Color.red(),
                ))

        elif str(message.author.id) in self.blocked_users:
            if reason.startswith(
                    "System Message: New Account.") or reason.startswith(
                        "System Message: Recently Joined."):
                # Met the age limit already, otherwise it would've been caught by the previous if's
                reaction = sent_emoji
                logger.debug("No longer internally blocked, user %s.",
                             message.author.name)
                self.blocked_users.pop(str(message.author.id))
            else:
                reaction = blocked_emoji
                end_time = re.search(r"%(.+?)%$", reason)
                if end_time is not None:
                    logger.debug("No longer blocked, user %s.",
                                 message.author.name)
                    after = (datetime.fromisoformat(end_time.group(1)) -
                             now).total_seconds()
                    if after <= 0:
                        # No longer blocked
                        reaction = sent_emoji
                        self.blocked_users.pop(str(message.author.id))
                else:
                    logger.debug("User blocked, user %s.", message.author.name)
        else:
            reaction = sent_emoji

        await self.config.update()
        if reaction != "disable":
            try:
                await message.add_reaction(reaction)
            except (discord.HTTPException, discord.InvalidArgument):
                logger.warning("Failed to add reaction %s.",
                               reaction,
                               exc_info=True)
        return str(message.author.id) in self.blocked_users
Ejemplo n.º 15
0
    async def process_modmail(self, message):
        """Processes messages sent to the bot."""
        sent_emoji, blocked_emoji = await self.retrieve_emoji()

        account_age = self.config.get('account_age')
        if account_age is None:
            account_age = isodate.duration.Duration()
        else:
            try:
                account_age = isodate.parse_duration(account_age)
            except isodate.ISO8601Error:
                logger.warning(
                    'The account age limit needs to be a '
                    'ISO-8601 duration formatted duration string '
                    f'greater than 0 days, not "%s".', str(account_age))
                del self.config.cache['account_age']
                await self.config.update()
                account_age = isodate.duration.Duration()

        reason = self.blocked_users.get(str(message.author.id))
        if reason is None:
            reason = ''
        try:
            min_account_age = message.author.created_at + account_age
        except ValueError as e:
            logger.warning(e.args[0])
            del self.config.cache['account_age']
            await self.config.update()
            min_account_age = message.author.created_at

        if min_account_age > datetime.utcnow():
            # user account has not reached the required time
            reaction = blocked_emoji
            changed = False
            delta = human_timedelta(min_account_age)

            if str(message.author.id) not in self.blocked_users:
                new_reason = f'System Message: New Account. Required to wait for {delta}.'
                self.config.blocked[str(message.author.id)] = new_reason
                await self.config.update()
                changed = True

            if reason.startswith('System Message: New Account.') or changed:
                await message.channel.send(embed=discord.Embed(
                    title='Message not sent!',
                    description=f'Your must wait for {delta} '
                    f'before you can contact {self.user.mention}.',
                    color=discord.Color.red()))

        elif str(message.author.id) in self.blocked_users:
            reaction = blocked_emoji
            if reason.startswith('System Message: New Account.'):
                # Met the age limit already
                reaction = sent_emoji
                del self.config.blocked[str(message.author.id)]
                await self.config.update()
            else:
                end_time = re.search(r'%(.+?)%$', reason)
                if end_time is not None:
                    after = (datetime.fromisoformat(end_time.group(1)) -
                             datetime.utcnow()).total_seconds()
                    if after <= 0:
                        # No longer blocked
                        reaction = sent_emoji
                        del self.config.blocked[str(message.author.id)]
                        await self.config.update()
        else:
            reaction = sent_emoji

        if reaction != 'disable':
            try:
                await message.add_reaction(reaction)
            except (discord.HTTPException, discord.InvalidArgument):
                pass

        if str(message.author.id) not in self.blocked_users:
            thread = await self.threads.find_or_create(message.author)
            await thread.send(message)
Ejemplo n.º 16
0
    async def _process_blocked(
        self, message: discord.Message
    ) -> typing.Tuple[bool, str]:
        sent_emoji, blocked_emoji = await self.retrieve_emoji()

        if str(message.author.id) in self.blocked_whitelisted_users:
            if str(message.author.id) in self.blocked_users:
                self.blocked_users.pop(str(message.author.id))
                await self.config.update()

            return False, sent_emoji

        now = datetime.utcnow()

        account_age = self.config.get("account_age")
        guild_age = self.config.get("guild_age")

        if account_age is None:
            account_age = isodate.Duration()
        if guild_age is None:
            guild_age = isodate.Duration()

        reason = self.blocked_users.get(str(message.author.id)) or ""
        min_guild_age = min_account_age = now

        try:
            min_account_age = message.author.created_at + account_age
        except ValueError:
            logger.warning("Error with 'account_age'.", exc_info=True)
            self.config.remove("account_age")

        try:
            joined_at = getattr(message.author, "joined_at", None)
            if joined_at is not None:
                min_guild_age = joined_at + guild_age
        except ValueError:
            logger.warning("Error with 'guild_age'.", exc_info=True)
            self.config.remove("guild_age")

        if min_account_age > now:
            # User account has not reached the required time
            reaction = blocked_emoji
            changed = False
            delta = human_timedelta(min_account_age)
            logger.debug("Blocked due to account age, user %s.", message.author.name)

            if str(message.author.id) not in self.blocked_users:
                new_reason = (
                    f"System Message: New Account. Required to wait for {delta}."
                )
                self.blocked_users[str(message.author.id)] = new_reason
                changed = True

            if reason.startswith("System Message: New Account.") or changed:
                await message.channel.send(
                    embed=discord.Embed(
                        title="Message not sent!",
                        description=f"Your must wait for {delta} "
                        f"before you can contact me.",
                        color=self.error_color,
                    )
                )

        elif min_guild_age > now:
            # User has not stayed in the guild for long enough
            reaction = blocked_emoji
            changed = False
            delta = human_timedelta(min_guild_age)
            logger.debug("Blocked due to guild age, user %s.", message.author.name)

            if str(message.author.id) not in self.blocked_users:
                new_reason = (
                    f"System Message: Recently Joined. Required to wait for {delta}."
                )
                self.blocked_users[str(message.author.id)] = new_reason
                changed = True

            if reason.startswith("System Message: Recently Joined.") or changed:
                await message.channel.send(
                    embed=discord.Embed(
                        title="Message not sent!",
                        description=f"Your must wait for {delta} "
                        f"before you can contact me.",
                        color=self.error_color,
                    )
                )

        elif str(message.author.id) in self.blocked_users:
            if reason.startswith("System Message: New Account.") or reason.startswith(
                "System Message: Recently Joined."
            ):
                # Met the age limit already, otherwise it would've been caught by the previous if's
                reaction = sent_emoji
                logger.debug(
                    "No longer internally blocked, user %s.", message.author.name
                )
                self.blocked_users.pop(str(message.author.id))
            else:
                reaction = blocked_emoji
                # etc "blah blah blah... until 2019-10-14T21:12:45.559948."
                end_time = re.search(r"until ([^`]+?)\.$", reason)
                if end_time is None:
                    # backwards compat
                    end_time = re.search(r"%([^%]+?)%", reason)
                    if end_time is not None:
                        logger.warning(
                            r"Deprecated time message for user %s, block and unblock again to update.",
                            message.author,
                        )

                if end_time is not None:
                    after = (
                        datetime.fromisoformat(end_time.group(1)) - now
                    ).total_seconds()
                    if after <= 0:
                        # No longer blocked
                        reaction = sent_emoji
                        self.blocked_users.pop(str(message.author.id))
                        logger.debug("No longer blocked, user %s.", message.author.name)
                    else:
                        logger.debug("User blocked, user %s.", message.author.name)
                else:
                    logger.debug("User blocked, user %s.", message.author.name)
        else:
            reaction = sent_emoji

        await self.config.update()
        return str(message.author.id) in self.blocked_users, reaction