paper-dynasty-discord/discord_ui/confirmations.py
Cal Corum 3debfd6e82 Catchup commit
Includes discord_ui refactor, testing overhaul, addition of
2025-07-22 09:22:19 -05:00

188 lines
6.7 KiB
Python

"""
Discord confirmation and interaction UI components.
Contains Question, Confirm, and ButtonOptions classes for user interactions.
"""
import asyncio
import discord
from typing import Literal
class Question:
def __init__(self, bot, channel, prompt, qtype, timeout, embed=None):
"""
Version 0.4
:param bot, discord bot object
:param channel, discord Channel object
:param prompt, string, prompt message to post
:param qtype, string, 'yesno', 'int', 'float', 'text', or 'url'
:param timeout, float, time to wait for a response
"""
if not prompt and not embed:
raise TypeError('prompt and embed may not both be None')
self.bot = bot
self.channel = channel
self.prompt = prompt
self.qtype = qtype
self.timeout = timeout
self.embed = embed
async def ask(self, responders: list):
"""
Params: responder, list of discord User objects
Returns: True/False if confirm question; full response otherwise
"""
yes = ['yes', 'y', 'ye', 'yee', 'yerp', 'yep', 'yeet', 'yip', 'yup', 'yarp', 'si']
no = ['no', 'n', 'nope', 'nah', 'nyet', 'nein']
if type(responders) is not list:
raise TypeError('Responders must be a list of Members')
def yesno(mes):
return mes.channel == self.channel and mes.author in responders and mes.content.lower() in yes + no
def text(mes):
return mes.channel == self.channel and mes.author in responders
await self.channel.send(content=self.prompt, embed=self.embed)
try:
resp = await self.bot.wait_for(
'message',
timeout=self.timeout,
check=yesno if self.qtype == 'yesno' else text
)
except asyncio.TimeoutError:
return None
except Exception as e:
await self.channel.send(f'Yuck, do you know what this means?\n\n{e}')
return None
if self.qtype == 'yesno':
if resp.content.lower() in yes:
return True
else:
return False
elif self.qtype == 'int':
return int(resp.content)
elif self.qtype == 'float':
return float(resp.content)
elif self.qtype == 'url':
if resp.content[:7] == 'http://' or resp.content[:8] == 'https://':
return resp.content
else:
return False
else:
return resp.content
class Confirm(discord.ui.View):
def __init__(self, responders: list, timeout: float = 300.0, label_type: Literal['yes', 'confirm'] = None):
super().__init__(timeout=timeout)
if not isinstance(responders, list):
raise TypeError('responders must be a list')
self.value = None
self.responders = responders
if label_type == 'yes':
self.confirm.label = 'Yes'
self.cancel.label = 'No'
# When the confirm button is pressed, set the inner value to `True` and
# stop the View from listening to more input.
# We also send the user an ephemeral message that we're confirming their choice.
@discord.ui.button(label='Confirm', style=discord.ButtonStyle.green)
async def confirm(self, interaction: discord.Interaction, button: discord.ui.Button):
if interaction.user not in self.responders:
return
self.value = True
self.clear_items()
self.stop()
# This one is similar to the confirmation button except sets the inner value to `False`
@discord.ui.button(label='Cancel', style=discord.ButtonStyle.grey)
async def cancel(self, interaction: discord.Interaction, button: discord.ui.Button):
if interaction.user not in self.responders:
return
self.value = False
self.clear_items()
self.stop()
class ButtonOptions(discord.ui.View):
def __init__(self, responders: list, timeout: float = 300.0, labels=None):
super().__init__(timeout=timeout)
if not isinstance(responders, list):
raise TypeError('responders must be a list')
self.value = None
self.responders = responders
self.options = labels
for count, x in enumerate(labels):
if count == 0:
self.option1.label = x
if x is None or x.lower() == 'na' or x == 'N/A':
self.remove_item(self.option1)
if count == 1:
self.option2.label = x
if x is None or x.lower() == 'na' or x == 'N/A':
self.remove_item(self.option2)
if count == 2:
self.option3.label = x
if x is None or x.lower() == 'na' or x == 'N/A':
self.remove_item(self.option3)
if count == 3:
self.option4.label = x
if x is None or x.lower() == 'na' or x == 'N/A':
self.remove_item(self.option4)
if count == 4:
self.option5.label = x
if x is None or x.lower() == 'na' or x == 'N/A':
self.remove_item(self.option5)
@discord.ui.button(label='Option 1', style=discord.ButtonStyle.primary)
async def option1(self, interaction: discord.Interaction, button: discord.ui.Button):
if interaction.user not in self.responders:
return
self.value = self.options[0]
self.clear_items()
self.stop()
@discord.ui.button(label='Option 2', style=discord.ButtonStyle.primary)
async def option2(self, interaction: discord.Interaction, button: discord.ui.Button):
if interaction.user not in self.responders:
return
self.value = self.options[1]
self.clear_items()
self.stop()
@discord.ui.button(label='Option 3', style=discord.ButtonStyle.primary)
async def option3(self, interaction: discord.Interaction, button: discord.ui.Button):
if interaction.user not in self.responders:
return
self.value = self.options[2]
self.clear_items()
self.stop()
@discord.ui.button(label='Option 4', style=discord.ButtonStyle.primary)
async def option4(self, interaction: discord.Interaction, button: discord.ui.Button):
if interaction.user not in self.responders:
return
self.value = self.options[3]
self.clear_items()
self.stop()
@discord.ui.button(label='Option 5', style=discord.ButtonStyle.primary)
async def option5(self, interaction: discord.Interaction, button: discord.ui.Button):
if interaction.user not in self.responders:
return
self.value = self.options[4]
self.clear_items()
self.stop()