diff --git a/examples/views/dynamic_counter.py b/examples/views/dynamic_counter.py new file mode 100644 index 000000000..d54ab19de --- /dev/null +++ b/examples/views/dynamic_counter.py @@ -0,0 +1,98 @@ +from __future__ import annotations + +from discord.ext import commands +import discord +import re + +# Complicated use cases for persistent views can be difficult to achieve when dealing +# with state changes or dynamic items. In order to facilitate these complicated use cases, +# the library provides DynamicItem which allows you to define an item backed by a regular +# expression that can parse state out of the custom_id. + +# The following example showcases a dynamic item that implements a counter. +# The `template` class parameter is used to give the library a regular expression to parse +# the custom_id. In this case we're parsing out custom_id in the form of e.g. +# `counter:5:user:80088516616269824` where the first number is the current count and the +# second number is the user ID who owns the button. + +# Note that custom_ids can only be up to 100 characters long. +class DynamicCounter( + discord.ui.DynamicItem[discord.ui.Button], + template=r'counter:(?P[0-9]+):user:(?P[0-9]+)', +): + def __init__(self, user_id: int, count: int = 0) -> None: + self.user_id: int = user_id + self.count: int = count + super().__init__( + discord.ui.Button( + label=f'Total: {count}', + style=self.style, + custom_id=f'counter:{count}:user:{user_id}', + emoji='\N{THUMBS UP SIGN}', + ) + ) + + # We want the style of the button to be dynamic depending on the count. + @property + def style(self) -> discord.ButtonStyle: + if self.count < 10: + return discord.ButtonStyle.grey + if self.count < 15: + return discord.ButtonStyle.red + if self.count < 20: + return discord.ButtonStyle.blurple + return discord.ButtonStyle.green + + # This method actually extracts the information from the custom ID and creates the item. + @classmethod + async def from_custom_id(cls, interaction: discord.Interaction, match: re.Match[str], /): + count = int(match['count']) + user_id = int(match['id']) + return cls(count, user_id) + + # We want to ensure that our button is only called by the user who created it. + async def interaction_check(self, interaction: discord.Interaction) -> bool: + return interaction.user.id == self.user_id + + async def callback(self, interaction: discord.Interaction) -> None: + # When the button is invoked, we want to increase the count and update the button's + # styling and label. + # In order to actually persist these changes we need to also update the custom_id + # to match the new information. + # Note that the custom ID *must* match the template. + self.count += 1 + self.item.label = f'Total: {self.count}' + self.custom_id = f'counter:{self.count}:user:{self.user_id}' + self.item.style = self.style + # In here, self.view is the view given by the interaction's message. + # It cannot be a custom subclass due to limitations. + await interaction.response.edit_message(view=self.view) + + +class DynamicCounterBot(commands.Bot): + def __init__(self): + intents = discord.Intents.default() + super().__init__(command_prefix=commands.when_mentioned, intents=intents) + + async def setup_hook(self) -> None: + # For dynamic items, we must register the classes instead of the views. + self.add_dynamic_items(DynamicCounter) + + async def on_ready(self): + print(f'Logged in as {self.user} (ID: {self.user.id})') + print('------') + + +bot = DynamicCounterBot() + + +@bot.command() +async def counter(ctx: commands.Context): + """Starts a dynamic counter.""" + + view = discord.ui.View(timeout=None) + view.add_item(DynamicCounter(ctx.author.id)) + await ctx.send('Here is your very own button!', view=view) + + +bot.run('token') diff --git a/examples/views/persistent.py b/examples/views/persistent.py index e3dea8114..46612bbc7 100644 --- a/examples/views/persistent.py +++ b/examples/views/persistent.py @@ -1,4 +1,5 @@ # This example requires the 'message_content' privileged intent to function. +from __future__ import annotations from discord.ext import commands import discord