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