1 changed files with 258 additions and 0 deletions
@ -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…
Reference in new issue