Browse Source

Add example showcasing how to do a settings panel

auto/crowdin
Rapptz 21 hours ago
parent
commit
22d6e8d0aa
  1. 258
      examples/views/settings.py

258
examples/views/settings.py

@ -0,0 +1,258 @@
from __future__ import annotations
from dataclasses import dataclass, field
from typing import List, Optional, Union
from discord.ext import commands
from discord import ui
import discord
import enum
class FruitType(enum.Enum):
apple = "Apple"
banana = "Banana"
orange = "Orange"
grape = "Grape"
mango = "Mango"
watermelon = "Watermelon"
coconut = "Coconut"
@property
def emoji(self) -> str:
emojis = {
"Apple": "🍎",
"Banana": "🍌",
"Orange": "🍊",
"Grape": "🍇",
"Mango": "🥭",
"Watermelon": "🍉",
"Coconut": "🥥",
}
return emojis[self.value]
def as_option(self) -> discord.SelectOption:
return discord.SelectOption(label=self.value, emoji=self.emoji, value=self.name)
# This is where we'll store our settings for the purpose of this example.
# In a real application you would want to store this in a database or file.
@dataclass
class Settings:
fruit_type: FruitType = FruitType.apple
channel: Optional[discord.PartialMessageable] = None
members: List[Union[discord.Member, discord.User]] = field(default_factory=list)
count: int = 1
silent: bool = False
class Bot(commands.Bot):
# Suppress error on the User attribute being None since it fills up later
user: discord.ClientUser
def __init__(self):
intents = discord.Intents.default()
super().__init__(command_prefix=commands.when_mentioned, intents=intents)
self.settings: Settings = Settings()
async def on_ready(self):
print(f'Logged in as {self.user} (ID: {self.user.id})')
print('------')
class FruitsSetting(ui.ActionRow['SettingsView']):
def __init__(self, settings: Settings):
super().__init__()
self.settings = settings
self.update_options()
def update_options(self):
for option in self.select_fruit.options:
if option.value == self.settings.fruit_type.name:
option.default = True
@ui.select(placeholder='Select a fruit', options=[fruit.as_option() for fruit in FruitType])
async def select_fruit(self, interaction: discord.Interaction[Bot], select: discord.ui.Select) -> None:
self.settings.fruit_type = FruitType[select.values[0]]
self.update_options()
await interaction.response.edit_message(view=self.view)
class ChannelSetting(ui.ActionRow['SettingsView']):
def __init__(self, settings: Settings):
super().__init__()
self.settings = settings
if settings.channel is not None:
self.select_channel.default_values = [
discord.SelectDefaultValue(id=settings.channel.id, type=discord.SelectDefaultValueType.channel)
]
@ui.select(
placeholder='Select a channel',
channel_types=[discord.ChannelType.text, discord.ChannelType.public_thread],
max_values=1,
min_values=0,
cls=ui.ChannelSelect,
)
async def select_channel(self, interaction: discord.Interaction[Bot], select: ui.ChannelSelect) -> None:
if select.values:
channel = select.values[0]
self.settings.channel = interaction.client.get_partial_messageable(
channel.id, guild_id=channel.guild_id, type=channel.type
)
select.default_values = [discord.SelectDefaultValue(id=channel.id, type=discord.SelectDefaultValueType.channel)]
else:
self.settings.channel = None
select.default_values = []
await interaction.response.edit_message(view=self.view)
class MembersSetting(ui.ActionRow['SettingsView']):
def __init__(self, settings: Settings):
super().__init__()
self.settings = settings
self.update_options()
def update_options(self):
self.select_members.default_values = [
discord.SelectDefaultValue(id=member.id, type=discord.SelectDefaultValueType.user)
for member in self.settings.members
]
@ui.select(placeholder='Select members', max_values=5, min_values=0, cls=ui.UserSelect)
async def select_members(self, interaction: discord.Interaction[Bot], select: ui.UserSelect) -> None:
self.settings.members = select.values
self.update_options()
await interaction.response.edit_message(view=self.view)
class CountModal(ui.Modal, title='Set emoji count'):
count = ui.TextInput(label='Count', style=discord.TextStyle.short, default='1', required=True)
def __init__(self, view: 'SettingsView', button: SetCountButton):
super().__init__()
self.view = view
self.settings = view.settings
self.button = button
async def on_submit(self, interaction: discord.Interaction[Bot]) -> None:
try:
self.settings.count = int(self.count.value)
self.button.label = str(self.settings.count)
await interaction.response.edit_message(view=self.view)
except ValueError:
await interaction.response.send_message('Invalid count. Please enter a number.', ephemeral=True)
class SetCountButton(ui.Button['SettingsView']):
def __init__(self, settings: Settings):
super().__init__(label=str(settings.count), style=discord.ButtonStyle.secondary)
self.settings = settings
async def callback(self, interaction: discord.Interaction[Bot]) -> None:
# Tell the type checker that a view is attached already
assert self.view is not None
await interaction.response.send_modal(CountModal(self.view, self))
class NotificationToggleButton(ui.Button['SettingsView']):
def __init__(self, settings: Settings):
super().__init__(label='\N{BELL}', style=discord.ButtonStyle.green)
self.settings = settings
self.update_button()
def update_button(self):
if self.settings.silent:
self.label = '\N{BELL WITH CANCELLATION STROKE} Disabled'
self.style = discord.ButtonStyle.red
else:
self.label = '\N{BELL} Enabled'
self.style = discord.ButtonStyle.green
async def callback(self, interaction: discord.Interaction[Bot]) -> None:
self.settings.silent = not self.settings.silent
self.update_button()
await interaction.response.edit_message(view=self.view)
class SettingsView(ui.LayoutView):
row = ui.ActionRow()
def __init__(self, settings: Settings):
super().__init__()
self.settings = settings
# For this example, we'll use multiple sections to organize the settings.
container = ui.Container()
header = ui.TextDisplay('# Settings\n-# This is an example to showcase how to do settings.')
container.add_item(header)
container.add_item(ui.Separator(spacing=discord.SeparatorSpacing.large))
self.count_button = SetCountButton(self.settings)
container.add_item(
ui.Section(
ui.TextDisplay('## Emoji Count\n-# This is the number of times the emoji will be repeated in the message.'),
accessory=self.count_button,
)
)
container.add_item(ui.Separator(spacing=discord.SeparatorSpacing.small))
container.add_item(
ui.Section(
ui.TextDisplay(
'## Notification Settings\n-# This controls whether the bot will use silent messages or not.'
),
accessory=NotificationToggleButton(self.settings),
)
)
container.add_item(ui.Separator(spacing=discord.SeparatorSpacing.large))
container.add_item(ui.TextDisplay('## Fruit Selection\n-# This is the fruit that is shown in the message.'))
container.add_item(FruitsSetting(self.settings))
container.add_item(ui.TextDisplay('## Channel Selection\n-# This is the channel where the message will be sent.'))
container.add_item(ChannelSetting(self.settings))
container.add_item(
ui.TextDisplay('## Member Selection\n-# These are the members that will be mentioned in the message.')
)
container.add_item(MembersSetting(self.settings))
self.add_item(container)
# Swap the row so it's at the end
self.remove_item(self.row)
self.add_item(self.row)
@row.button(label='Finish', style=discord.ButtonStyle.green)
async def finish_button(self, interaction: discord.Interaction[Bot], button: ui.Button) -> None:
# Edit the message to make it the interaction response...
await interaction.response.edit_message(view=self)
# ...and then send a confirmation message.
await interaction.followup.send(f'Settings saved.', ephemeral=True)
# Then delete the settings panel
self.stop()
await interaction.delete_original_response()
bot = Bot()
@bot.command()
async def settings(ctx: commands.Context[Bot]):
"""Shows the settings view."""
view = SettingsView(ctx.bot.settings)
await ctx.send(view=view)
@bot.command()
async def send(ctx: commands.Context[Bot]):
"""Sends the message with the current settings."""
settings = ctx.bot.settings
if settings.channel is None:
await ctx.send('No channel is configured. Please use the settings command to set one.')
return
# This example is super silly, so don't do this for real. It's annoying.
content = ' '.join(settings.fruit_type.emoji for _ in range(settings.count))
mentions = ' '.join(member.mention for member in settings.members)
await settings.channel.send(content=f'{mentions} {content}', silent=settings.silent)
bot.run('token')
Loading…
Cancel
Save