Search code examples
python-3.xdiscord.pychatbot

Discord.py How do I split a bots message?


So, I have a discord information bot I am working on and this is one of the commands.

@client.command()
async def rd(ctx):
    embed = discord.Embed(title="**[R&D Cost at every level]:**")
    embed.add_field(name="**Level//All Units ATK Bonus//March Size Increase//R&D Cost**", value= "|", inline=False)
    embed.add_field(name="6    0.6%    0    10", value='.', inline=False)
    embed.add_field(name="7    0.7%    0    20", value='.', inline=False)
    embed.add_field(name="8    0.8%    0    30", value='.', inline=False)
    embed.add_field(name="9    0.9%    0    35", value='.', inline=False)
    embed.add_field(name="10    1.0%    0    40", value='.', inline=False)
    embed.add_field(name="11    1.2%    0    45", value='.', inline=False)
    embed.add_field(name="12    1.5%    0    50", value='.', inline=False)
    embed.add_field(name="13    2.0%    0    60", value='.', inline=False)
    embed.add_field(name="14    2.5%    0    TBD", value='.', inline=False)
    embed.add_field(name="15    3.0%    0    TBD", value='.', inline=False)
    embed.add_field(name="16    3.5%    0    TBD", value='.', inline=False)
    embed.add_field(name="17    4.0%    0    TBD", value='.', inline=False)
    embed.add_field(name="18    4.5%    0    TBD", value='.', inline=False)
    embed.add_field(name="19    5.0%    0    TBD", value='.', inline=False)
    embed.add_field(name="20    5.5%    0    TBD", value='.', inline=False)
    embed.add_field(name="21    6.0%    0    TBD", value='.', inline=False)
    embed.add_field(name="22    6.5%    0    TBD", value='.', inline=False)
    embed.add_field(name="23    7.0%    0    TBD", value='.', inline=False)
    embed.add_field(name="24    7.5%    0    TBD", value='.', inline=False)
    embed.add_field(name="25    8.0%    1    TBD", value='.', inline=False)
    embed.add_field(name="26    8.5%    1    TBD", value='.', inline=False)
    embed.add_field(name="27    9.0%    1    TBD", value='.', inline=False)
    embed.add_field(name="28    9.5%    1    TBD", value='.', inline=False)
    embed.add_field(name="29    10.0%    1    TBD", value='.', inline=False)
    embed.add_field(name="30    10.5%    2    TBD", value='.', inline=False)
    embed.add_field(name="31    11.0%    2    TBD", value='.', inline=False)
    embed.add_field(name="32    11.5%    2    TBD", value='.', inline=False)
    embed.add_field(name="33    12.0%    2    1.27K", value='.', inline=False)
    embed.add_field(name="34    12.5%    2    1.46K", value='.', inline=False)
    embed.add_field(name="35    13.0%    3    1.87K", value='.', inline=False)
    embed.add_field(name="36    13.5%    3    2.02K", value='.', inline=False)
    embed.add_field(name="37    14.0%    3    2.18K", value='.', inline=False)
    embed.add_field(name="38    14.5%    3    2.36K", value='.', inline=False)
    embed.add_field(name="39    15.0%    3    2.54K", value='.', inline=False)
    embed.add_field(name="40    15.5%    4    2.73K", value='.', inline=False)
    embed.add_field(name="41    16.0%    4    2.87K", value='.', inline=False)
    embed.add_field(name="42    16.5%    4    3.07K", value='.', inline=False)
    embed.add_field(name="43    17.0%    4    3.29K", value='.', inline=False)
    embed.add_field(name="44    17.5%    4    3.51K", value='.', inline=False)
    embed.add_field(name="45    18.0%    5    3.74K", value='.', inline=False)
    embed.add_field(name="46    18.5%    5    3.99K", value='.', inline=False)
    embed.add_field(name="47    19.0%    5    4.09K", value='.', inline=False)
    embed.add_field(name="48    19.5%    5    4.34K", value='.', inline=False)
    embed.add_field(name="49    20.0%    5    4.44K", value='.', inline=False)
    embed.add_field(name="50    20.5%    6    4.54K", value='.', inline=False)

The command works great, except that discord cuts the message off on "embed.add_field(name="29 10.0% 1 TBD", value='.', inline=False)", due to the max character limit in a single message.

enter image description here

How would I go about splitting this message into pages? I have seen where you can have emojis that scroll through information, but I am unsure on how to apply that to this command here.

Or how could I make it to where it posts into multiple messages instead of it attempting to post into one whole message?

Any help is much appreciated!


Solution

  • This is the code I used to work with pagination before I started implementing buttons

    async def paginate(
        ctx: discord.ext.commands.context.Context,
        *embed_pages: typing.Union[discord.Embed, list[discord.Embed]],
        content=None,
        overwrite_footer=True,
        timeout=None,
    ):
    
        if isinstance(embed_pages[0], list):
            embed_pages = embed_pages[0]
    
        every_embed = list()
        if overwrite_footer:
            for index, each_embed in enumerate(embed_pages):
                each_embed.remove_footer()
                each_embed.set_footer(text=f"Page {index + 1} of {len(embed_pages)}")
                every_embed.append(each_embed)
        else:
            every_embed = embed_pages[:]
    
        sent_embed = await ctx.send(
            content=content, embed=every_embed[0]
        )  # Send the first page
        page_index = 0  # Set the starting index
    
        reactions = ["⬅️", "🔁", "➡️"]
        for each_reaction in reactions:
            await sent_embed.add_reaction(each_reaction)
    
        while True:
            try:
                payload = await bot.wait_for(
                    "raw_reaction_add",
                    check=lambda payload: payload.message_id == sent_embed.id
                    and not payload.member == bot.user
                    and payload.member.id
                    == ctx.author.id,  # Do this if you want it to be author-only
                    timeout=timeout,
                )
            except asyncio.TimeoutError:
                # timeout has been hit
                if overwrite_footer:
                    every_embed[page_index].remove_footer()
                    every_embed[page_index].set_footer(
                        text="Pagination traversal has timed out."
                    )
                    await sent_embed.edit(embed=every_embed[page_index])
                    try:
                        await every_embed.clear_reactions()
                    except:
                        pass
                return
            else:
                try:
                    await sent_embed.remove_reaction(payload.emoji, payload.member)
                except discord.Forbidden:
                    # Bot does not have permission
                    pass
    
                if (
                    str(payload.emoji.name) not in reactions
                ):  # Some user reacted with something else
                    pass
    
                elif str(payload.emoji.name) == reactions[0]:  # Previous page
                    if not page_index == 0:
                        page_index -= 1
                    else:
                        page_index = len(every_embed) - 1
                    await sent_embed.edit(embed=every_embed[page_index], content=content)
    
                elif str(payload.emoji.name) == reactions[1]:  # Goto page 0
                    page_index = 0
                    await sent_embed.edit(embed=every_embed[page_index], content=content)
    
                else:
                    if not page_index == (len(every_embed) - 1):
                        page_index += 1
                    else:
                        page_index = 0
                    await sent_embed.edit(embed=every_embed[page_index], content=content)
    
    

    This function is a coroutine and has to be awaited, the first argument must be ctx (context), rest of the arguments may be discord.Embed objects or a single list that contain instances of aforementioned discord.Embed.

    The rest of the arguments are optional and self-explanatory.

    Here's an use-case :

    import discord
    import os
    import asyncio
    import typing
    import Lorem  # Custom module
    from discord.ext import commands
    from dotenv import load_dotenv
    
    load_dotenv()
    bot = commands.Bot(command_prefix=">")
    
    
    async def paginate(
        ctx: discord.ext.commands.context.Context,
        *embed_pages: typing.Union[discord.Embed, list[discord.Embed]],
        content=None,
        overwrite_footer=True,
        timeout=None,
    ):
    
        if isinstance(embed_pages[0], list):
            embed_pages = embed_pages[0]
    
        every_embed = list()
        if overwrite_footer:
            for index, each_embed in enumerate(embed_pages):
                each_embed.remove_footer()
                each_embed.set_footer(text=f"Page {index + 1} of {len(embed_pages)}")
                every_embed.append(each_embed)
        else:
            every_embed = embed_pages[:]
    
        sent_embed = await ctx.send(
            content=content, embed=every_embed[0]
        )  # Send the first page
        page_index = 0  # Set the starting index
    
        reactions = ["⬅️", "🔁", "➡️"]
        for each_reaction in reactions:
            await sent_embed.add_reaction(each_reaction)
    
        while True:
            try:
                payload = await bot.wait_for(
                    "raw_reaction_add",
                    check=lambda payload: payload.message_id == sent_embed.id
                    and not payload.member == bot.user
                    and payload.member.id
                    == ctx.author.id,  # Do this if you want it to be author-only
                    timeout=timeout,
                )
            except asyncio.TimeoutError:
                # timeout has been hit
                if overwrite_footer:
                    every_embed[page_index].remove_footer()
                    every_embed[page_index].set_footer(
                        text="Pagination traversal has timed out."
                    )
                    await sent_embed.edit(embed=every_embed[page_index])
                    try:
                        await every_embed.clear_reactions()
                    except:
                        pass
                return
            else:
                try:
                    await sent_embed.remove_reaction(payload.emoji, payload.member)
                except discord.Forbidden:
                    # Bot does not have permission
                    pass
    
                if (
                    str(payload.emoji.name) not in reactions
                ):  # Some user reacted with something else
                    pass
    
                elif str(payload.emoji.name) == reactions[0]:  # Previous page
                    if not page_index == 0:
                        page_index -= 1
                    else:
                        page_index = len(every_embed) - 1
                    await sent_embed.edit(embed=every_embed[page_index], content=content)
    
                elif str(payload.emoji.name) == reactions[1]:  # Goto page 0
                    page_index = 0
                    await sent_embed.edit(embed=every_embed[page_index], content=content)
    
                else:
                    if not page_index == (len(every_embed) - 1):
                        page_index += 1
                    else:
                        page_index = 0
                    await sent_embed.edit(embed=every_embed[page_index], content=content)
    
    
    @bot.event
    async def on_ready():
        print(f"Sucessfully logged in as {bot.user}")
    
    
    @bot.command()
    async def start(ctx):
    
        await paginate(
            ctx,
            [
                discord.Embed(title="Answered by Achxy!", description=Lorem.lorem(6))
                for _ in range(100)
            ],
        )
        # We just generated 100 pages of Lorem Ipsum :D
    
    
    @bot.command()
    async def ping(ctx):
        # I made this command just to prove that the while loop earlier isn't blocking.
        embed = discord.Embed(
            title="Pong! 🏓",
            description=f"Current Latency of the bot is {round(bot.latency * 1000)}ms",
        )
        await ctx.reply(embed=embed)
    
    
    bot.run(os.getenv("DISCORD_TOKEN"))
    

    This code will bring the following paginated output :

    https://i.sstatic.net/BUCtW.jpg