File size: 3,901 Bytes
cb6590e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
import asyncio
from datetime import datetime, timedelta
from nextcord import ui, Interaction
from threading import Timer
from typing import Optional

# the amount of seconds before the interaction becomes invalid
# made for securing the edit of that message, should be higher than 0.
MERCY_SECONDS: int = 30


# this is an abstract class that can be inherited to make a view
# that disables itself after a certain amount of time
# before you cannot respond to an interaction anymore or when timeout has been reached.
class AutoDisableView(ui.View):
    interaction: Optional[Interaction] = None
    message_id: Optional[int] = None
    _timer: Optional[Timer] = None

    def init_interaction(
            self,
            interaction: Interaction,
            message_id: Optional[int] = None,
            start_timer: bool = True
    ) -> None:
        '''
        Initializes the interaction and message_id attributes.
        This is required for the view to work properly.

        Parameters
        ----------
        interaction: Interaction
            The interaction that the view is attached to.
            Needs to be the interaction that currently was received and is being processed.
        message_id: Optional[int]
            The message id of the message if the response is a followup.
        start_timer: bool
            Whether to start the timer that will disable the view
            after the timeout has been reached. Defaults to True.'''

        self.interaction = interaction
        self.__stopped: asyncio.Future[bool] = interaction.client.loop.create_future()
        self.message_id = message_id
        if start_timer:
            self.disable_message_start_timer()

    def disable_message_start_timer(self, interaction: Optional[Interaction] = None) -> None:
        '''
        Starts the timer that will disable the message after the timeout has been reached.
        This is called automatically when init_interaction() is called with `start_timer` set to True.'''
        seconds = (
                          self.interaction.created_at  # dont rely on expires_at because it may not have responded
                          + timedelta(minutes=15)  # interaction is valid for 15 minutes in case of message being sent
                          - datetime.utcnow().replace(tzinfo=self.interaction.expires_at.tzinfo)
                  ).total_seconds() - MERCY_SECONDS
        if self._timer:
            self._timer.cancel()
        self._timer = Timer(seconds, self._disable_message_task)
        self._timer.start()
        if interaction:
            # if you responded with defer or edit_message, you can pass the interaction to this method
            # to extend the timer to the new interaction
            self.init_interaction(interaction, self.message_id, False)

    # override
    def stop(self) -> None:
        self._disable_message_task()
        super().stop()

    # override
    def _dispatch_timeout(self) -> None:
        if self.__stopped.done():
            return
        self.interaction.client.loop.create_task(self._disable_view_on_timeout(),
                                                 name=f"discord-ui-disable-view-timeout-{self.id}")
        super()._dispatch_timeout()

    def _disable_message_task(self) -> None:
        self.interaction.client.loop.create_task(self._disable_message_buttons())

    def _disable_children(self) -> None:
        for child in self.children:
            child.disabled = True

    async def _disable_message_buttons(self) -> None:
        self._disable_children()
        if self.message_id:
            await self.interaction.followup.edit_message(self.message_id, view=self)
        else:
            await self.interaction.edit_original_message(view=self)
        self._timer.cancel()

    async def _disable_view_on_timeout(self) -> None:
        await self._disable_message_buttons()