|
| 1 | +import asyncio |
| 2 | + |
| 3 | +import discord |
1 | 4 | from discord.ext import commands |
2 | 5 |
|
3 | 6 | from bot import ModmailBot |
4 | 7 | from core import time |
5 | 8 |
|
6 | 9 |
|
7 | | -class StrictUserFriendlyDuration(time.UserFriendlyTime): |
| 10 | +async def close_after_confirmation(ctx: commands.Context, converted_arg: time.UserFriendlyTime) -> None: |
8 | 11 | """ |
9 | | - A converter which parses user-friendly time durations. |
10 | | -
|
11 | | - Since this converter is meant for parsing close messages while |
12 | | - closing threads, both custom close messages and time durations are |
13 | | - parsed. |
| 12 | + Send a message and allow users to react to it to close the thread. |
14 | 13 |
|
15 | | - Unlike the parent class, a time duration must be provided when |
16 | | - a custom close message is provided. |
| 14 | + The reaction times out after 5 minutes. |
17 | 15 | """ |
| 16 | + unicode_reaction = '\N{WHITE HEAVY CHECK MARK}' |
| 17 | + warning_message = ("\N{WARNING SIGN} A time duration wasn't provided, reacting to this message will close" |
| 18 | + " this thread instantly with the provided custom close message.") |
| 19 | + |
| 20 | + message = await ctx.send(warning_message) |
| 21 | + await message.add_reaction(unicode_reaction) |
| 22 | + |
| 23 | + def checkmark_press_check(reaction: discord.Reaction, user: discord.User) -> bool: |
| 24 | + is_right_reaction = ( |
| 25 | + user != ctx.bot.user |
| 26 | + and reaction.message.id == message.id |
| 27 | + and str(reaction.emoji) == unicode_reaction |
| 28 | + ) |
| 29 | + |
| 30 | + return is_right_reaction |
| 31 | + |
| 32 | + try: |
| 33 | + await ctx.bot.wait_for('reaction_add', check=checkmark_press_check, timeout=5 * 60) |
| 34 | + except asyncio.TimeoutError: |
| 35 | + await message.edit(content=message.content+'\n\n**Timed out.**') |
| 36 | + await message.clear_reactions() |
| 37 | + else: |
| 38 | + await original_close_command(ctx, after=converted_arg) |
| 39 | + |
| 40 | + |
| 41 | +async def safe_close( |
| 42 | + self: time.UserFriendlyTime, |
| 43 | + ctx: commands.Context, |
| 44 | + *, |
| 45 | + after: time.UserFriendlyTime = None |
| 46 | +) -> None: |
| 47 | + """ |
| 48 | + Close the current thread. |
18 | 49 |
|
19 | | - MODIFIERS = {'silently', 'silent', 'cancel'} |
20 | | - |
21 | | - async def convert(self, ctx: commands.Context, argument: str) -> "StrictUserFriendlyDuration": |
22 | | - """ |
23 | | - Parse the provided time duration along with any close message. |
24 | | -
|
25 | | - Fail if a custom close message is provided without a time |
26 | | - duration. |
27 | | - """ |
28 | | - await super().convert(ctx, argument) |
29 | | - |
30 | | - argument_passed = bool(argument) |
31 | | - not_a_modifier = argument not in self.MODIFIERS |
32 | | - if argument_passed and not_a_modifier and self.arg == argument: |
33 | | - # Fail since only a close message was provided. |
34 | | - raise commands.BadArgument("A time duration must be provided when closing with a custom message.") |
35 | | - |
36 | | - return self |
| 50 | + Unlike the original close command, confirmation is awaited when |
| 51 | + a time duration isn't provided but a custom close message is. |
| 52 | + """ |
| 53 | + modifiers = {'silently', 'silent', 'cancel'} |
37 | 54 |
|
| 55 | + argument_passed = bool(after) |
| 56 | + not_a_modifier = after.arg not in modifiers |
38 | 57 |
|
39 | | -ADDED_HELP_TEXT = '\n\n*Note: Providing a time duration is necessary when closing with a custom message.*' |
| 58 | + if argument_passed and not_a_modifier and after.arg == after.raw: |
| 59 | + # Ask for confirmation since only a close message was provided. |
| 60 | + await close_after_confirmation(ctx, after) |
| 61 | + else: |
| 62 | + await original_close_command(ctx, after=after) |
40 | 63 |
|
41 | 64 |
|
42 | 65 | def setup(bot: ModmailBot) -> None: |
43 | 66 | """ |
44 | | - Monkey patch the close command's callback. |
| 67 | + Monkey patch the close command's callback to safe_close. |
45 | 68 |
|
46 | | - This makes it use the StrictUserFriendlyTime converter and updates |
47 | | - the help text to reflect the new behaviour. |
| 69 | + The help text is also updated to reflect the new behaviour. |
48 | 70 | """ |
49 | | - global previous_converter |
| 71 | + global original_close_command |
50 | 72 |
|
51 | 73 | command = bot.get_command('close') |
| 74 | + original_close_command = command.copy() |
| 75 | + original_close_command.cog = command.cog |
52 | 76 |
|
53 | | - previous_converter = command.callback.__annotations__['after'] |
54 | | - command.callback.__annotations__['after'] = StrictUserFriendlyDuration |
55 | | - command.callback = command.callback |
56 | | - |
57 | | - command.help += ADDED_HELP_TEXT |
| 77 | + command.callback = safe_close |
| 78 | + command.help += '\n\n*Note: A time duration should be provided when closing with a custom message.*' |
58 | 79 |
|
59 | 80 |
|
60 | 81 | def teardown(bot: ModmailBot) -> None: |
61 | | - """Undo changes to the close command.""" |
62 | | - command = bot.get_command('close') |
63 | | - |
64 | | - command.callback.__annotations__['after'] = previous_converter |
65 | | - command.callback = command.callback |
66 | | - |
67 | | - command.help = command.help.remove_suffix(ADDED_HELP_TEXT) |
| 82 | + """Restore the original close command.""" |
| 83 | + bot.remove_command('close') |
| 84 | + bot.add_command(original_close_command) |
0 commit comments