Skip to content

discord_interactive

Exposed classes

Module containing the definition of the Help class. The Help class contains the code to properly display the help tree, and handle interactions with the user.

Help

Class representing the whole Help system.

Attributes:

Name Type Description
client Discord.Client

Discord client (to send messages).

tree RootLink

Link representing the whole help pages as a tree.

quit_react str

Reaction used to leave the help system..

Source code in discord_interactive/help.py
 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
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
class Help:
    """Class representing the whole Help system.

    Attributes:
        client (Discord.Client): Discord client (to send messages).
        tree (RootLink): Link representing the whole help pages as a tree.
        quit_react (str): Reaction used to leave the help system..
    """

    def __init__(self, client, pages, callbacks=[], quit_react=DEFAULT_QUIT_REACT):
        """Help constructor.

        Args:
            client (Discord.Client): Discord client (to send messages).
            pages (list of Page or Page): List of pages representing the
                starting point of the help.
            callbacks (list, optional): List of functions to call when taking
                this link. Defaults to empty list.
            quit_react (str, optional): Reaction used to leave the help system.
                Defaults to `❌`.
        """
        self.client = client
        self.quit_react = quit_react

        # Create a RootLink, representing the root of the help tree
        root = RootLink(pages, callbacks)
        self.tree = root

    async def display(self, member):  # noqa: C901
        """Main function of the Help system.

        This function is the main function of the help system. When a user
        request help, simply call this function.
        It will display the first message of the help, and then wait the user to
        react. Depending on the reaction, it will display the next page, etc...

        Args:
            member (Discord.Member): Member who called help. Help will be
                displayed as a private message to him.
        """
        current_link = self.tree
        prev_input = []

        # Never stop displaying help
        while True:
            # Run basic callbacks before displaying the page
            for callback in current_link.callbacks:
                await callback(current_link, member, prev_input)

            # After running the callbacks, we can retrieve the page to be
            # displayed
            page = current_link.page()

            # Send the current page to the user as private message :
            # Ensure the channel exist
            if member.dm_channel is None:
                await member.create_dm()

            # Different page type should be sent differently
            if page.type == PageType.MESSAGE:
                bot_message = await member.dm_channel.send(page.get_message())
            elif page.type == PageType.EMBED:
                bot_message = await member.dm_channel.send(embed=page.get_embed(), content=None)

            # Display possible reactions
            for react in page.reactions() + [self.quit_react]:
                asyncio.ensure_future(bot_message.add_reaction(react))

            next_link = None
            # While user give wrong reaction/input, keep waiting for better input
            while next_link is None:
                # Get user input
                reaction, message = await self._get_user_input(member, bot_message, page)

                # 2 cases : reaction or message
                if reaction is not None and message is None:
                    # If the user wants to quit, quit
                    if reaction.emoji == self.quit_react:
                        # Clean and quit. This is the only way to quit for now
                        await bot_message.delete()
                        return

                    # Else, retrieve the next link based on reaction
                    next_link = page.next_link(reaction.emoji)

                elif reaction is None and message is not None:
                    # Retrieve next link
                    next_link = page.next_link()

            # Before going to next page, remember the input of the user if given
            if message is not None:
                prev_input.append(message)

            # Here the next page is valid. Clean current message and loop
            await bot_message.delete()
            current_link = next_link

    async def _get_user_input(self, member, message, current_page):
        """Function retrieving the user input.

        This function retrieve the user input. The user input is either a
        message or a reaction (if the page does not need user input, only
        reaction will be an acceptable input).
        If both input and reaction are correct input, return the first event met.

        Args:
            member (Discord.Member): Member who called help. Help will be
                displayed as a private message to him.
            message (Discord.Message): Message sent by the bot, where the user
                should react.
            current_page (Page): Page being displayed. We know what kind of
                input we need from this page.

        Returns:
            reaction (Discord.Reaction): Reaction of the user, or None if the
                correct input was a user message.
            message (Discord.Message): Message of the user, or None if the
                correct input was a user reaction.
        """

        def check_reaction(reaction, user):
            return user == member

        def check_message(m):
            return m.author == member

        task_react = asyncio.ensure_future(self.client.wait_for("reaction_add", check=check_reaction))
        task_answer = asyncio.ensure_future(self.client.wait_for("message", check=check_message))
        tasks = [task_react]  # Always wait for user reaction
        if current_page.need_user_input():
            tasks.append(task_answer)  # Sometimes need to expect input too

        # Wait the actual user input
        done, _ = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)

        # Depending on what the user did, return the right thing
        if task_react in done:
            # User reacted
            reaction, _ = done.pop().result()
            return reaction, None
        else:
            # User answered
            msg = done.pop().result()
            return None, msg

__init__(client, pages, callbacks=[], quit_react=DEFAULT_QUIT_REACT)

Help constructor.

Parameters:

Name Type Description Default
client Discord.Client

Discord client (to send messages).

required
pages list of Page or Page

List of pages representing the starting point of the help.

required
callbacks list

List of functions to call when taking this link. Defaults to empty list.

[]
quit_react str

Reaction used to leave the help system. Defaults to .

DEFAULT_QUIT_REACT
Source code in discord_interactive/help.py
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
def __init__(self, client, pages, callbacks=[], quit_react=DEFAULT_QUIT_REACT):
    """Help constructor.

    Args:
        client (Discord.Client): Discord client (to send messages).
        pages (list of Page or Page): List of pages representing the
            starting point of the help.
        callbacks (list, optional): List of functions to call when taking
            this link. Defaults to empty list.
        quit_react (str, optional): Reaction used to leave the help system.
            Defaults to `❌`.
    """
    self.client = client
    self.quit_react = quit_react

    # Create a RootLink, representing the root of the help tree
    root = RootLink(pages, callbacks)
    self.tree = root

display(member) async

Main function of the Help system.

This function is the main function of the help system. When a user request help, simply call this function. It will display the first message of the help, and then wait the user to react. Depending on the reaction, it will display the next page, etc...

Parameters:

Name Type Description Default
member Discord.Member

Member who called help. Help will be displayed as a private message to him.

required
Source code in discord_interactive/help.py
 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
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
async def display(self, member):  # noqa: C901
    """Main function of the Help system.

    This function is the main function of the help system. When a user
    request help, simply call this function.
    It will display the first message of the help, and then wait the user to
    react. Depending on the reaction, it will display the next page, etc...

    Args:
        member (Discord.Member): Member who called help. Help will be
            displayed as a private message to him.
    """
    current_link = self.tree
    prev_input = []

    # Never stop displaying help
    while True:
        # Run basic callbacks before displaying the page
        for callback in current_link.callbacks:
            await callback(current_link, member, prev_input)

        # After running the callbacks, we can retrieve the page to be
        # displayed
        page = current_link.page()

        # Send the current page to the user as private message :
        # Ensure the channel exist
        if member.dm_channel is None:
            await member.create_dm()

        # Different page type should be sent differently
        if page.type == PageType.MESSAGE:
            bot_message = await member.dm_channel.send(page.get_message())
        elif page.type == PageType.EMBED:
            bot_message = await member.dm_channel.send(embed=page.get_embed(), content=None)

        # Display possible reactions
        for react in page.reactions() + [self.quit_react]:
            asyncio.ensure_future(bot_message.add_reaction(react))

        next_link = None
        # While user give wrong reaction/input, keep waiting for better input
        while next_link is None:
            # Get user input
            reaction, message = await self._get_user_input(member, bot_message, page)

            # 2 cases : reaction or message
            if reaction is not None and message is None:
                # If the user wants to quit, quit
                if reaction.emoji == self.quit_react:
                    # Clean and quit. This is the only way to quit for now
                    await bot_message.delete()
                    return

                # Else, retrieve the next link based on reaction
                next_link = page.next_link(reaction.emoji)

            elif reaction is None and message is not None:
                # Retrieve next link
                next_link = page.next_link()

        # Before going to next page, remember the input of the user if given
        if message is not None:
            prev_input.append(message)

        # Here the next page is valid. Clean current message and loop
        await bot_message.delete()
        current_link = next_link

Module containing the definition of the Page class, which is the main class to define the pages of your interactive help for your Discord bot.

Page

Class representing a page of the help.

This class represents a page of the help. A page is displayed to the user, and the user can naviguate through pages using reaction or messages. A page have several attributes : a message, and a map of linked pages, based on reaction of the user.

Attributes:

Name Type Description
msg str

Message to display to the user when displaying the page.

links list of Links

List of Links associated to this page.

msg_link MsgLink

MsgLink if there is one. It's not part of the links list because there can be only 1 msg link per page.

parent Link

Link to the parent page.

root Link

Link to the root page.

sep str

String used to separate the message and the links description (for display).

links_sep str

String used to separate the links description (between each of them) (for display).

type PageType

Type of the page. Can be PageType.MESSAGE or PageType.EMBED.

embed_kwargs dict

Others keywords arguments, used to initialize the Embed for display. Only used if the type of the page is PageType.EMBED.

Source code in discord_interactive/page.py
 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
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
class Page:
    """Class representing a page of the help.

    This class represents a page of the help. A page is displayed to the user,
    and the user can naviguate through pages using reaction or messages.
    A page have several attributes : a message, and a map of linked pages,
    based on reaction of the user.

    Attributes:
        msg (str): Message to display to the user when displaying the page.
        links (list of Links): List of Links associated to this page.
        msg_link (MsgLink): MsgLink if there is one. It's not part of the links
            list because there can be only 1 msg link per page.
        parent (Link): Link to the parent page.
        root (Link): Link to the root page.
        sep (str): String used to separate the message and the links description
            (for display).
        links_sep (str): String used to separate the links description (between
            each of them) (for display).
        type (PageType): Type of the page. Can be `PageType.MESSAGE` or
            `PageType.EMBED`.
        embed_kwargs (dict): Others keywords arguments, used to initialize
            the `Embed` for display. Only used if the type of the page is
            `PageType.EMBED`.
    """

    def __init__(self, msg="", sep="\n\n", links_sep="\n", embed=True, **embed_kwargs):
        r"""Page constructor.

        Constructor of the class Page. Create a Page with a message.

        Args:
            msg (str, optional): Message to display to the user when displaying
                the page.
            sep (str, optional): String used to separate the message and the
                links description (for display). Defaults to `\n\n`.
            links_sep (str, optional): String used to separate the links
                description (between each of them) (for display). Defaults to
                `\n`.
            embed (bool, optional): If set to `True`, create a page of type
                `PageType.EMBED`, if `False` the page type is
                `PageType.MESSAGE`. Defaults to `True`.
            embed_kwargs (dict): Others keywords arguments, used to initialize
                the `Embed` for display. Only used if the type of the page is
                `PageType.EMBED`.
        """
        self.msg = msg
        self.links = []
        self.msg_link = None
        self.parent = None
        self.root = None
        self.sep = sep
        self.links_sep = links_sep
        self.type = PageType.EMBED if embed else PageType.MESSAGE
        self.embed_kwargs = embed_kwargs

    ####################### Construction of the Tree ###########################

    def link(
        self,
        pages,
        reaction=None,
        description=None,
        callbacks=[],
        user_input=False,
        is_parent=True,
        parent_reaction=DEFAULT_PARENT_REACT,
    ):
        """Page linker with reactions.

        Link a page to other pages by creating a link with reaction.

        Args:
            pages (list of Page or Page): List of pages associated to this link.
            reaction (str, optional): Reaction needed to go through the link.
                If None is given, use a default reaction. Defaults to `None`.
            description (str, optional): Description of the link, to explain to
                user the effect of this link. Defaults to `None`.
            callbacks (list, optional): List of functions to call when taking
                this link. Defaults to empty list.
            user_input (bool, optional): Boolean indicating if this is a MsgLink
                or not. Defaults to `False`.
            is_parent (bool, optional): Boolean indicating if the currentpage
                should be represented as the parent of the pages linked.
                Defaults to `True`.
            parent_reaction (str or list of str, optional): Reaction to use for
                the child to come back to its parent (current page). If a list
                is given, each reaction is associated to one page of the list
                of pages given. Defaults to `🔙`.

        Throws:
            IndexError: Only the 9 first links are provided with default
                reactions (digit 1 ~ 9). If you try to create another link with
                default reaction, this Exception will be thrown.
            ValueError: The number of parent reaction given does not correspond
                to the number of child pages.
        """
        # Create the appropriate link
        if user_input:  # Create a MsgLink
            self.msg_link = MsgLink(pages, description, callbacks)
        else:  # Create a ReactLink
            # First, retrieve the default reaction if none was given
            if reaction is None:
                reaction = DEFAULT_LINK_REACTS[len(self.links)]

            # Then create a ReactLink
            link = ReactLink(reaction, pages, description, callbacks)

            # And link it to this page
            self.links.append(link)

        # Create the parent links
        if is_parent:
            self.parent_of(pages, parent_reaction)

    def parent_of(self, pages, parent_reaction=DEFAULT_PARENT_REACT):
        """Parent Page linker.

        Link a list of pages the current page as a parent.

        Args:
            pages (list of Page or Page): List of pages to associate the current
                page as a parent.
            parent_reaction (str or list of str, optional): Reaction to use for
                the child to come back to its parent (current page). If a list
                is given, each reaction is associated to one page of the list
                of pages given. Defaults to `🔙`.

        Throws:
            ValueError: The number of parent reaction given does not correspond
                to the number of child pages.
        """
        # Normalize list of pages
        if type(pages) != list:
            pages = [pages]

        if type(parent_reaction) == list:
            if len(pages) != len(parent_reaction):
                raise ValueError(
                    "You gave a list of reaction for the parent "
                    "page, but the number of pages given are not matching this"
                    " list ({} pages, but {} reactions)".format(len(pages), len(parent_reaction))
                )
        else:  # Normalize list of reaction
            parent_reaction = [parent_reaction] * len(pages)

        # Assign to each page this page as parent with the right reaction
        for p, r in zip(pages, parent_reaction):
            # First, create the parent link
            p_link = ReactLink(r, self)

            # Then associate this link to the page
            p.parent = p_link

    def root_of(self, pages, root_reaction=DEFAULT_ROOT_REACT):
        """Root Page linker.

        Link a list of pages the current page as root.

        Args:
            pages (list of Page or Page): List of pages to associate the current
                page as root.
            root_reaction (str or list of str, optional): Reaction to use for
                the pages to come back to the root (current page). If a list
                is given, each reaction is associated to one page of the list
                of pages given. Defaults to `🔝`.

        Throws:
            ValueError: The number of root reaction given does not correspond
                to the number of pages given.
        """
        # Normalize list of pages
        if type(pages) != list:
            pages = [pages]

        if type(root_reaction) == list:
            if len(pages) != len(root_reaction):
                raise ValueError(
                    "You gave a list of reaction for the root "
                    "page, but the number of pages given are not matching this"
                    " list ({} pages, but {} reactions)".format(len(pages), len(root_reaction))
                )
        else:  # Normalize list of reaction
            root_reaction = [root_reaction] * len(pages)

        # Assign to each page this page as parent with the right reaction
        for p, r in zip(pages, root_reaction):
            # First, create the parent link
            r_link = ReactLink(r, self)

            # Then associate this link to the page
            p.root = r_link

    ######################## Display of the Tree ###############################

    def get_message(self):
        """This method is called by the Help if the page is a `PageType.MESSAGE`.
        It returns the formatted content of the Page as a string.
        This will display the main message of the Page, as well as the message
        describing each Link of the Page.
        This method simply construct the string to send to the channel.

        Returns:
            str: Content to display to user.
        """
        content = self.msg
        content += self.sep
        content += self.links_sep.join([link.description for link in self.links if link.description is not None])
        if self.msg_link is not None and self.msg_link.description is not None:
            content += self.links_sep + self.msg_link.description
        return content

    def get_embed(self):
        """This method is called by the Help if the page is a `PageType.EMBED`.
        It returns an `Embed` object, representing the formatted page.
        This will display the main message of the Page, as well as the message
        describing each Link of the Page.

        Returns:
            Embed: Embed to display to user.
        """
        return discord.Embed(description=self.get_message(), **self.embed_kwargs)

    def reactions(self):
        """This method is called by the Help, to retrieve the list of reactions
        that the user can use to interact with the help.

        Returns:
            list of str: List of reactions (str) that the user can use for this
                page.
        """
        return [link.reaction for link in self._all_links()]

    def need_user_input(self):
        """Method to know if the Help display needs to wait for the user to
        input something.

        If the page contains a MsgLink, then the helper needs to wait the user
        to input something.

        Returns:
            bool: True if there is MsgLink, False otherwise.
        """
        return self.msg_link is not None

    def next_link(self, reaction=None):
        """Accessing the next Link.

        This function access the next Link based on the reaction given. If the
        reaction is `None`, retrieve the MsgLink. If the reaction is not valid
        (no link associated to this reaction), `None` is returned.

        Args:
            reaction (str, optional): Reaction chosen by the user, representing
                a Link. If `None`, the link returned is MsgLink. Default to None.

        Returns:
            Link or None: The next link to display, based on the reaction of
                the user, or `None` if the choice of the user is not valid.
        """
        if reaction is None:
            return self.msg_link

        next_link = None
        for link in self._all_links():
            if link.reaction == reaction:
                next_link = link
        return next_link

    ############################## Private #####################################

    def _all_links(self):
        """Private function.

        Return a list of available ReactLink for this page.

        Returns:
            list of ReactLink: All links.
        """
        all_links = self.links
        if self.parent is not None:
            all_links += [self.parent]
        if self.root is not None:
            all_links += [self.root]
        return all_links

__init__(msg='', sep='\n\n', links_sep='\n', embed=True, **embed_kwargs)

Page constructor.

Constructor of the class Page. Create a Page with a message.

Parameters:

Name Type Description Default
msg str

Message to display to the user when displaying the page.

''
sep str

String used to separate the message and the links description (for display). Defaults to \n\n.

'\n\n'
links_sep str

String used to separate the links description (between each of them) (for display). Defaults to \n.

'\n'
embed bool

If set to True, create a page of type PageType.EMBED, if False the page type is PageType.MESSAGE. Defaults to True.

True
embed_kwargs dict

Others keywords arguments, used to initialize the Embed for display. Only used if the type of the page is PageType.EMBED.

{}
Source code in discord_interactive/page.py
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
def __init__(self, msg="", sep="\n\n", links_sep="\n", embed=True, **embed_kwargs):
    r"""Page constructor.

    Constructor of the class Page. Create a Page with a message.

    Args:
        msg (str, optional): Message to display to the user when displaying
            the page.
        sep (str, optional): String used to separate the message and the
            links description (for display). Defaults to `\n\n`.
        links_sep (str, optional): String used to separate the links
            description (between each of them) (for display). Defaults to
            `\n`.
        embed (bool, optional): If set to `True`, create a page of type
            `PageType.EMBED`, if `False` the page type is
            `PageType.MESSAGE`. Defaults to `True`.
        embed_kwargs (dict): Others keywords arguments, used to initialize
            the `Embed` for display. Only used if the type of the page is
            `PageType.EMBED`.
    """
    self.msg = msg
    self.links = []
    self.msg_link = None
    self.parent = None
    self.root = None
    self.sep = sep
    self.links_sep = links_sep
    self.type = PageType.EMBED if embed else PageType.MESSAGE
    self.embed_kwargs = embed_kwargs

Page linker with reactions.

Link a page to other pages by creating a link with reaction.

Parameters:

Name Type Description Default
pages list of Page or Page

List of pages associated to this link.

required
reaction str

Reaction needed to go through the link. If None is given, use a default reaction. Defaults to None.

None
description str

Description of the link, to explain to user the effect of this link. Defaults to None.

None
callbacks list

List of functions to call when taking this link. Defaults to empty list.

[]
user_input bool

Boolean indicating if this is a MsgLink or not. Defaults to False.

False
is_parent bool

Boolean indicating if the currentpage should be represented as the parent of the pages linked. Defaults to True.

True
parent_reaction str or list of str

Reaction to use for the child to come back to its parent (current page). If a list is given, each reaction is associated to one page of the list of pages given. Defaults to 🔙.

DEFAULT_PARENT_REACT
Throws

IndexError: Only the 9 first links are provided with default reactions (digit 1 ~ 9). If you try to create another link with default reaction, this Exception will be thrown. ValueError: The number of parent reaction given does not correspond to the number of child pages.

Source code in discord_interactive/page.py
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
def link(
    self,
    pages,
    reaction=None,
    description=None,
    callbacks=[],
    user_input=False,
    is_parent=True,
    parent_reaction=DEFAULT_PARENT_REACT,
):
    """Page linker with reactions.

    Link a page to other pages by creating a link with reaction.

    Args:
        pages (list of Page or Page): List of pages associated to this link.
        reaction (str, optional): Reaction needed to go through the link.
            If None is given, use a default reaction. Defaults to `None`.
        description (str, optional): Description of the link, to explain to
            user the effect of this link. Defaults to `None`.
        callbacks (list, optional): List of functions to call when taking
            this link. Defaults to empty list.
        user_input (bool, optional): Boolean indicating if this is a MsgLink
            or not. Defaults to `False`.
        is_parent (bool, optional): Boolean indicating if the currentpage
            should be represented as the parent of the pages linked.
            Defaults to `True`.
        parent_reaction (str or list of str, optional): Reaction to use for
            the child to come back to its parent (current page). If a list
            is given, each reaction is associated to one page of the list
            of pages given. Defaults to `🔙`.

    Throws:
        IndexError: Only the 9 first links are provided with default
            reactions (digit 1 ~ 9). If you try to create another link with
            default reaction, this Exception will be thrown.
        ValueError: The number of parent reaction given does not correspond
            to the number of child pages.
    """
    # Create the appropriate link
    if user_input:  # Create a MsgLink
        self.msg_link = MsgLink(pages, description, callbacks)
    else:  # Create a ReactLink
        # First, retrieve the default reaction if none was given
        if reaction is None:
            reaction = DEFAULT_LINK_REACTS[len(self.links)]

        # Then create a ReactLink
        link = ReactLink(reaction, pages, description, callbacks)

        # And link it to this page
        self.links.append(link)

    # Create the parent links
    if is_parent:
        self.parent_of(pages, parent_reaction)

parent_of(pages, parent_reaction=DEFAULT_PARENT_REACT)

Parent Page linker.

Link a list of pages the current page as a parent.

Parameters:

Name Type Description Default
pages list of Page or Page

List of pages to associate the current page as a parent.

required
parent_reaction str or list of str

Reaction to use for the child to come back to its parent (current page). If a list is given, each reaction is associated to one page of the list of pages given. Defaults to 🔙.

DEFAULT_PARENT_REACT
Throws

ValueError: The number of parent reaction given does not correspond to the number of child pages.

Source code in discord_interactive/page.py
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
def parent_of(self, pages, parent_reaction=DEFAULT_PARENT_REACT):
    """Parent Page linker.

    Link a list of pages the current page as a parent.

    Args:
        pages (list of Page or Page): List of pages to associate the current
            page as a parent.
        parent_reaction (str or list of str, optional): Reaction to use for
            the child to come back to its parent (current page). If a list
            is given, each reaction is associated to one page of the list
            of pages given. Defaults to `🔙`.

    Throws:
        ValueError: The number of parent reaction given does not correspond
            to the number of child pages.
    """
    # Normalize list of pages
    if type(pages) != list:
        pages = [pages]

    if type(parent_reaction) == list:
        if len(pages) != len(parent_reaction):
            raise ValueError(
                "You gave a list of reaction for the parent "
                "page, but the number of pages given are not matching this"
                " list ({} pages, but {} reactions)".format(len(pages), len(parent_reaction))
            )
    else:  # Normalize list of reaction
        parent_reaction = [parent_reaction] * len(pages)

    # Assign to each page this page as parent with the right reaction
    for p, r in zip(pages, parent_reaction):
        # First, create the parent link
        p_link = ReactLink(r, self)

        # Then associate this link to the page
        p.parent = p_link

root_of(pages, root_reaction=DEFAULT_ROOT_REACT)

Root Page linker.

Link a list of pages the current page as root.

Parameters:

Name Type Description Default
pages list of Page or Page

List of pages to associate the current page as root.

required
root_reaction str or list of str

Reaction to use for the pages to come back to the root (current page). If a list is given, each reaction is associated to one page of the list of pages given. Defaults to 🔝.

DEFAULT_ROOT_REACT
Throws

ValueError: The number of root reaction given does not correspond to the number of pages given.

Source code in discord_interactive/page.py
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
def root_of(self, pages, root_reaction=DEFAULT_ROOT_REACT):
    """Root Page linker.

    Link a list of pages the current page as root.

    Args:
        pages (list of Page or Page): List of pages to associate the current
            page as root.
        root_reaction (str or list of str, optional): Reaction to use for
            the pages to come back to the root (current page). If a list
            is given, each reaction is associated to one page of the list
            of pages given. Defaults to `🔝`.

    Throws:
        ValueError: The number of root reaction given does not correspond
            to the number of pages given.
    """
    # Normalize list of pages
    if type(pages) != list:
        pages = [pages]

    if type(root_reaction) == list:
        if len(pages) != len(root_reaction):
            raise ValueError(
                "You gave a list of reaction for the root "
                "page, but the number of pages given are not matching this"
                " list ({} pages, but {} reactions)".format(len(pages), len(root_reaction))
            )
    else:  # Normalize list of reaction
        root_reaction = [root_reaction] * len(pages)

    # Assign to each page this page as parent with the right reaction
    for p, r in zip(pages, root_reaction):
        # First, create the parent link
        r_link = ReactLink(r, self)

        # Then associate this link to the page
        p.root = r_link

get_message()

This method is called by the Help if the page is a PageType.MESSAGE. It returns the formatted content of the Page as a string. This will display the main message of the Page, as well as the message describing each Link of the Page. This method simply construct the string to send to the channel.

Returns:

Name Type Description
str

Content to display to user.

Source code in discord_interactive/page.py
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
def get_message(self):
    """This method is called by the Help if the page is a `PageType.MESSAGE`.
    It returns the formatted content of the Page as a string.
    This will display the main message of the Page, as well as the message
    describing each Link of the Page.
    This method simply construct the string to send to the channel.

    Returns:
        str: Content to display to user.
    """
    content = self.msg
    content += self.sep
    content += self.links_sep.join([link.description for link in self.links if link.description is not None])
    if self.msg_link is not None and self.msg_link.description is not None:
        content += self.links_sep + self.msg_link.description
    return content

get_embed()

This method is called by the Help if the page is a PageType.EMBED. It returns an Embed object, representing the formatted page. This will display the main message of the Page, as well as the message describing each Link of the Page.

Returns:

Name Type Description
Embed

Embed to display to user.

Source code in discord_interactive/page.py
239
240
241
242
243
244
245
246
247
248
def get_embed(self):
    """This method is called by the Help if the page is a `PageType.EMBED`.
    It returns an `Embed` object, representing the formatted page.
    This will display the main message of the Page, as well as the message
    describing each Link of the Page.

    Returns:
        Embed: Embed to display to user.
    """
    return discord.Embed(description=self.get_message(), **self.embed_kwargs)

reactions()

This method is called by the Help, to retrieve the list of reactions that the user can use to interact with the help.

Returns:

Type Description

list of str: List of reactions (str) that the user can use for this page.

Source code in discord_interactive/page.py
250
251
252
253
254
255
256
257
258
def reactions(self):
    """This method is called by the Help, to retrieve the list of reactions
    that the user can use to interact with the help.

    Returns:
        list of str: List of reactions (str) that the user can use for this
            page.
    """
    return [link.reaction for link in self._all_links()]

need_user_input()

Method to know if the Help display needs to wait for the user to input something.

If the page contains a MsgLink, then the helper needs to wait the user to input something.

Returns:

Name Type Description
bool

True if there is MsgLink, False otherwise.

Source code in discord_interactive/page.py
260
261
262
263
264
265
266
267
268
269
270
def need_user_input(self):
    """Method to know if the Help display needs to wait for the user to
    input something.

    If the page contains a MsgLink, then the helper needs to wait the user
    to input something.

    Returns:
        bool: True if there is MsgLink, False otherwise.
    """
    return self.msg_link is not None

Accessing the next Link.

This function access the next Link based on the reaction given. If the reaction is None, retrieve the MsgLink. If the reaction is not valid (no link associated to this reaction), None is returned.

Parameters:

Name Type Description Default
reaction str

Reaction chosen by the user, representing a Link. If None, the link returned is MsgLink. Default to None.

None

Returns:

Type Description

Link or None: The next link to display, based on the reaction of the user, or None if the choice of the user is not valid.

Source code in discord_interactive/page.py
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
def next_link(self, reaction=None):
    """Accessing the next Link.

    This function access the next Link based on the reaction given. If the
    reaction is `None`, retrieve the MsgLink. If the reaction is not valid
    (no link associated to this reaction), `None` is returned.

    Args:
        reaction (str, optional): Reaction chosen by the user, representing
            a Link. If `None`, the link returned is MsgLink. Default to None.

    Returns:
        Link or None: The next link to display, based on the reaction of
            the user, or `None` if the choice of the user is not valid.
    """
    if reaction is None:
        return self.msg_link

    next_link = None
    for link in self._all_links():
        if link.reaction == reaction:
            next_link = link
    return next_link

Private function.

Return a list of available ReactLink for this page.

Returns:

Type Description

list of ReactLink: All links.

Source code in discord_interactive/page.py
298
299
300
301
302
303
304
305
306
307
308
309
310
311
def _all_links(self):
    """Private function.

    Return a list of available ReactLink for this page.

    Returns:
        list of ReactLink: All links.
    """
    all_links = self.links
    if self.parent is not None:
        all_links += [self.parent]
    if self.root is not None:
        all_links += [self.root]
    return all_links

Private classes

Module containing the definition of the Page class, which is the main class to define the pages of your interactive help for your Discord bot.

PageType

Bases: Enum

Existing type of page. The type of a page define how this page will be displayed.

Source code in discord_interactive/page.py
18
19
20
21
22
23
24
class PageType(Enum):
    """Existing type of page. The type of a page define how this page will be
    displayed.
    """

    MESSAGE = auto()
    EMBED = auto()

Module containing the definition of the Link classes. These classes are not public-facing, they are used only internally. User can create links using the link() method in the Page class.

Base class for all type of link.

Links represent branches of the help tree. All links can have callbacks, and a description. Links also have a list of child pages. Most of the time, we want simple link, so the link have a single child, which is displayed if the link is taken. But sometimes, we want something more complex, like if the user take link1, ans user is a specific role, we will display a different page. That's why links can have several child pages. By default, if callbacks does not change this behavior, the first child of the list is selected.

Attributes:

Name Type Description
pages list of Page

List of pages associated to this link.

description str

Description of this link, to explain to user the effect of this link.

callbacks list

List of functions to call when taking this link.

path int

Which child page will be displayed. Default to 0 (the first Page of the list).

Source code in discord_interactive/link.py
 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
class Link:
    """Base class for all type of link.

    Links represent branches of the help tree.
    All links can have callbacks, and a description.
    Links also have a list of child pages. Most of the time, we want simple link,
    so the link have a single child, which is displayed if the link is taken.
    But sometimes, we want something more complex, like if the user take link1,
    ans user is a specific role, we will display a different page. That's why
    links can have several child pages.
    By default, if callbacks does not change this behavior, the first child of
    the list is selected.

    Attributes:
        pages (list of Page): List of pages associated to this link.
        description (str): Description of this link, to explain to user the
            effect of this link.
        callbacks (list): List of functions to call when taking this link.
        path (int): Which child page will be displayed. Default to 0 (the first
            Page of the list).
    """

    def __init__(self, pages, description=None, callbacks=[]):
        """Link constructor.

        Create a Link with a list of child pages, a description, and
        callbacks optionally.

        Args:
            pages (list of Page or Page): List of pages associated to this link.
            description (str, optional): Description of this link, to explain to
                user the effect of this link. Defaults to `None`.
            callbacks (list, optional): List of functions to call when taking
                this link. Defaults to empty list.
        """
        # Normalize input
        if type(pages) == list:
            self.pages = pages
        else:
            self.pages = [pages]
        if type(callbacks) == list:
            self.callbacks = callbacks
        else:
            self.callbacks = [callbacks]

        self.description = description
        self.path = 0

    def page(self):
        """This method is called by the Help, after calling the callbacks.
        So the path was updated (or not) to select the right page, and we should
        return the appropriate page.

        Returns:
            Page: The page selected by callbacks (or default choice).
        """
        return self.pages[self.path]

__init__(pages, description=None, callbacks=[])

Link constructor.

Create a Link with a list of child pages, a description, and callbacks optionally.

Parameters:

Name Type Description Default
pages list of Page or Page

List of pages associated to this link.

required
description str

Description of this link, to explain to user the effect of this link. Defaults to None.

None
callbacks list

List of functions to call when taking this link. Defaults to empty list.

[]
Source code in discord_interactive/link.py
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
def __init__(self, pages, description=None, callbacks=[]):
    """Link constructor.

    Create a Link with a list of child pages, a description, and
    callbacks optionally.

    Args:
        pages (list of Page or Page): List of pages associated to this link.
        description (str, optional): Description of this link, to explain to
            user the effect of this link. Defaults to `None`.
        callbacks (list, optional): List of functions to call when taking
            this link. Defaults to empty list.
    """
    # Normalize input
    if type(pages) == list:
        self.pages = pages
    else:
        self.pages = [pages]
    if type(callbacks) == list:
        self.callbacks = callbacks
    else:
        self.callbacks = [callbacks]

    self.description = description
    self.path = 0

page()

This method is called by the Help, after calling the callbacks. So the path was updated (or not) to select the right page, and we should return the appropriate page.

Returns:

Name Type Description
Page

The page selected by callbacks (or default choice).

Source code in discord_interactive/link.py
55
56
57
58
59
60
61
62
63
def page(self):
    """This method is called by the Help, after calling the callbacks.
    So the path was updated (or not) to select the right page, and we should
    return the appropriate page.

    Returns:
        Page: The page selected by callbacks (or default choice).
    """
    return self.pages[self.path]

Bases: Link

Class for link using reaction to naviguate to next page.

Attributes:

Name Type Description
reaction str

Reaction needed by this link to display the page.

Source code in discord_interactive/link.py
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
class ReactLink(Link):
    """Class for link using reaction to naviguate to next page.

    Attributes:
        reaction (str): Reaction needed by this link to display the page.
    """

    def __init__(self, reaction, pages, description=None, callbacks=[]):
        """ReactLink constructor.

        Create a ReactLink to other pages, with a reaction, and a possibly a
        description, as well as callbacks.

        Args:
            reaction (str): Reaction needed by this link to display the page.
            pages (list of Page or Page): Page to display when this link is used.
            description (str, optional): Description of this link, to explain to
                user the effect of this link. Defaults to `None`.
            callbacks (list, optional): List of functions to call when taking
                this link. Defaults to empty list.
        """
        super(ReactLink, self).__init__(pages, description, callbacks)
        self.reaction = reaction

        # We need to update the description of this link to add the reaction
        if self.description is not None:
            self.description = self.reaction + " " + self.description

__init__(reaction, pages, description=None, callbacks=[])

ReactLink constructor.

Create a ReactLink to other pages, with a reaction, and a possibly a description, as well as callbacks.

Parameters:

Name Type Description Default
reaction str

Reaction needed by this link to display the page.

required
pages list of Page or Page

Page to display when this link is used.

required
description str

Description of this link, to explain to user the effect of this link. Defaults to None.

None
callbacks list

List of functions to call when taking this link. Defaults to empty list.

[]
Source code in discord_interactive/link.py
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
def __init__(self, reaction, pages, description=None, callbacks=[]):
    """ReactLink constructor.

    Create a ReactLink to other pages, with a reaction, and a possibly a
    description, as well as callbacks.

    Args:
        reaction (str): Reaction needed by this link to display the page.
        pages (list of Page or Page): Page to display when this link is used.
        description (str, optional): Description of this link, to explain to
            user the effect of this link. Defaults to `None`.
        callbacks (list, optional): List of functions to call when taking
            this link. Defaults to empty list.
    """
    super(ReactLink, self).__init__(pages, description, callbacks)
    self.reaction = reaction

    # We need to update the description of this link to add the reaction
    if self.description is not None:
        self.description = self.reaction + " " + self.description

Bases: Link

Class for link using message to naviguate to next page.

Source code in discord_interactive/link.py
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
class MsgLink(Link):
    """Class for link using message to naviguate to next page."""

    def __init__(self, pages, description=None, callbacks=[]):
        """MsgLink constructor.

        Create a MsgLink with a list of child pages, a description, and
        callbacks optionally.

        Args:
            pages (list of Page or Page): List of pages associated to this link.
            description (str, optional): Description of this link, to explain to
                user the effect of this link. Defaults to `None`.
            callbacks (list, optional): List of functions to call when taking
                this link. Defaults to empty list.
        """
        super(MsgLink, self).__init__(pages, description, callbacks)

__init__(pages, description=None, callbacks=[])

MsgLink constructor.

Create a MsgLink with a list of child pages, a description, and callbacks optionally.

Parameters:

Name Type Description Default
pages list of Page or Page

List of pages associated to this link.

required
description str

Description of this link, to explain to user the effect of this link. Defaults to None.

None
callbacks list

List of functions to call when taking this link. Defaults to empty list.

[]
Source code in discord_interactive/link.py
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
def __init__(self, pages, description=None, callbacks=[]):
    """MsgLink constructor.

    Create a MsgLink with a list of child pages, a description, and
    callbacks optionally.

    Args:
        pages (list of Page or Page): List of pages associated to this link.
        description (str, optional): Description of this link, to explain to
            user the effect of this link. Defaults to `None`.
        callbacks (list, optional): List of functions to call when taking
            this link. Defaults to empty list.
    """
    super(MsgLink, self).__init__(pages, description, callbacks)

Bases: Link

Class for the first link of the help tree.

Source code in discord_interactive/link.py
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
class RootLink(Link):
    """Class for the first link of the help tree."""

    def __init__(self, pages, callbacks=[]):
        """RootLink constructor.

        RootLink is like any other links (callbacks included), but there is no
        description.

        Args:
            pages (list of Page or Page): List of pages associated to this link.
            callbacks (list, optional): List of functions to call when taking
                this link. Defaults to empty list.
        """
        super(RootLink, self).__init__(pages, callbacks=callbacks)

__init__(pages, callbacks=[])

RootLink constructor.

RootLink is like any other links (callbacks included), but there is no description.

Parameters:

Name Type Description Default
pages list of Page or Page

List of pages associated to this link.

required
callbacks list

List of functions to call when taking this link. Defaults to empty list.

[]
Source code in discord_interactive/link.py
117
118
119
120
121
122
123
124
125
126
127
128
def __init__(self, pages, callbacks=[]):
    """RootLink constructor.

    RootLink is like any other links (callbacks included), but there is no
    description.

    Args:
        pages (list of Page or Page): List of pages associated to this link.
        callbacks (list, optional): List of functions to call when taking
            this link. Defaults to empty list.
    """
    super(RootLink, self).__init__(pages, callbacks=callbacks)