497 lines
19 KiB
Python
497 lines
19 KiB
Python
import logging
|
|
import discord
|
|
from typing import Coroutine, Literal
|
|
|
|
from dice import ab_roll, jump_roll
|
|
from exceptions import *
|
|
from helpers import random_insult
|
|
from in_game.gameplay_models import Game, Play, Team
|
|
|
|
|
|
logger = logging.getLogger('discord_app')
|
|
|
|
|
|
# def check_responder(func):
|
|
# def wrap(*args, **kwargs):
|
|
# try:
|
|
# result = func(*args, **kwargs)
|
|
# except Exc
|
|
|
|
class Confirm(discord.ui.View):
|
|
def __init__(self, responders: list, timeout: float = 300.0, label_type: Literal['yes', 'confirm'] = 'confirm'):
|
|
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:
|
|
await interaction.response.send_message(
|
|
content='Get out of here',
|
|
ephemeral=True,
|
|
delete_after=10.0
|
|
)
|
|
|
|
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:
|
|
await interaction.response.send_message(
|
|
content='Get out of here',
|
|
ephemeral=True,
|
|
delete_after=10.0
|
|
)
|
|
|
|
self.value = False
|
|
self.clear_items()
|
|
self.stop()
|
|
|
|
|
|
class ButtonOptions(discord.ui.View):
|
|
def __init__(self, labels: list[str], responders: list, timeout: float = 300.0, disable_chosen: bool = False):
|
|
logger.info(f'ButtonOptions - labels: {labels} / responders: {responders} / timeout: {timeout} / disable_chosen: {disable_chosen}')
|
|
|
|
super().__init__(timeout=timeout)
|
|
if not isinstance(responders, list):
|
|
raise TypeError('responders must be a list')
|
|
if len(labels) > 5 or len(labels) < 1:
|
|
log_exception(ValueError, 'ButtonOptions support between 1 and 5 options')
|
|
|
|
self.value = None
|
|
self.responders = responders
|
|
self.options = labels
|
|
self.disable_chosen = disable_chosen
|
|
# if len(labels) == 5:
|
|
# 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)
|
|
|
|
# else:
|
|
all_options = [self.option1, self.option2, self.option3, self.option4, self.option5]
|
|
logger.info(f'all_options: {all_options}')
|
|
for count, x in enumerate(labels):
|
|
if x is None or x.lower() == 'na' or x.lower() == 'n/a':
|
|
self.remove_item(all_options[count])
|
|
else:
|
|
all_options[count].label = x
|
|
|
|
if len(labels) < 2:
|
|
self.remove_item(self.option2)
|
|
if len(labels) < 3:
|
|
self.remove_item(self.option3)
|
|
if len(labels) < 4:
|
|
self.remove_item(self.option4)
|
|
if len(labels) < 5:
|
|
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:
|
|
await interaction.response.send_message(
|
|
content='Get out of here',
|
|
ephemeral=True,
|
|
delete_after=10.0
|
|
)
|
|
|
|
self.stop()
|
|
if self.disable_chosen:
|
|
button.disabled = True
|
|
self.value = self.options[0]
|
|
await interaction.edit_original_response(view=self)
|
|
|
|
@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:
|
|
await interaction.response.send_message(
|
|
content='Get out of here',
|
|
ephemeral=True,
|
|
delete_after=10.0
|
|
)
|
|
|
|
self.stop()
|
|
if self.disable_chosen:
|
|
button.disabled = True
|
|
self.value = self.options[1]
|
|
await interaction.edit_original_response(view=self)
|
|
|
|
@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:
|
|
await interaction.response.send_message(
|
|
content='Get out of here',
|
|
ephemeral=True,
|
|
delete_after=10.0
|
|
)
|
|
|
|
self.stop()
|
|
if self.disable_chosen:
|
|
button.disabled = True
|
|
self.value = self.options[2]
|
|
await interaction.edit_original_response(view=self)
|
|
|
|
@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:
|
|
await interaction.response.send_message(
|
|
content='Get out of here',
|
|
ephemeral=True,
|
|
delete_after=10.0
|
|
)
|
|
|
|
self.stop()
|
|
if self.disable_chosen:
|
|
button.disabled = True
|
|
self.value = self.options[3]
|
|
await interaction.edit_original_response(view=self)
|
|
|
|
@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:
|
|
await interaction.response.send_message(
|
|
content='Get out of here',
|
|
ephemeral=True,
|
|
delete_after=10.0
|
|
)
|
|
|
|
self.stop()
|
|
if self.disable_chosen:
|
|
button.disabled = True
|
|
self.value = self.options[4]
|
|
await interaction.edit_original_response(view=self)
|
|
|
|
|
|
class SelectPosition(discord.ui.View):
|
|
def __init__(self, *, timeout: int = 30, responders: list[discord.User]):
|
|
if not isinstance(responders, list):
|
|
raise TypeError('responders must be a list')
|
|
|
|
super().__init__(timeout=timeout)
|
|
|
|
self.value = None
|
|
self.responders = responders
|
|
|
|
async def check_responders(self, interaction: discord.Interaction):
|
|
if interaction.user not in self.responders:
|
|
await interaction.response.send_message(
|
|
content=random_insult(),
|
|
ephemeral=True,
|
|
delete_after=10.0
|
|
)
|
|
log_exception(InvalidResponder, f'{interaction.user.name} not in responders: {self.responders}')
|
|
|
|
|
|
async def button_press(self, interaction: discord.Interaction, this_position: str):
|
|
await self.check_responders(interaction)
|
|
self.stop()
|
|
self.value = this_position
|
|
await interaction.edit_original_response(view=self)
|
|
|
|
|
|
@discord.ui.button(label='LF', style=discord.ButtonStyle.blurple, row=0)
|
|
async def left_field(self, interaction: discord.Interaction, button: discord.ui.Button):
|
|
await self.button_press(interaction, button.label)
|
|
|
|
|
|
@discord.ui.button(label='CF', style=discord.ButtonStyle.blurple, row=0)
|
|
async def center_field(self, interaction: discord.Interaction, button: discord.ui.Button):
|
|
await self.button_press(interaction, button.label)
|
|
|
|
|
|
@discord.ui.button(label='RF', style=discord.ButtonStyle.blurple, row=0)
|
|
async def right_field(self, interaction: discord.Interaction, button: discord.ui.Button):
|
|
await self.button_press(interaction, button.label)
|
|
|
|
|
|
@discord.ui.button(label='3B', style=discord.ButtonStyle.green, row=1)
|
|
async def third_base(self, interaction: discord.Interaction, button: discord.ui.Button):
|
|
await self.button_press(interaction, button.label)
|
|
|
|
|
|
@discord.ui.button(label='SS', style=discord.ButtonStyle.green, row=1)
|
|
async def shortstop(self, interaction: discord.Interaction, button: discord.ui.Button):
|
|
await self.button_press(interaction, button.label)
|
|
|
|
|
|
@discord.ui.button(label='2B', style=discord.ButtonStyle.green, row=1)
|
|
async def second_base(self, interaction: discord.Interaction, button: discord.ui.Button):
|
|
await self.button_press(interaction, button.label)
|
|
|
|
|
|
@discord.ui.button(label='1B', style=discord.ButtonStyle.green, row=1)
|
|
async def first_base(self, interaction: discord.Interaction, button: discord.ui.Button):
|
|
await self.button_press(interaction, button.label)
|
|
|
|
|
|
@discord.ui.button(label='C', style=discord.ButtonStyle.gray, row=2)
|
|
async def catcher(self, interaction: discord.Interaction, button: discord.ui.Button):
|
|
await self.button_press(interaction, button.label)
|
|
|
|
|
|
@discord.ui.button(label='P', style=discord.ButtonStyle.gray, row=2)
|
|
async def pitcher(self, interaction: discord.Interaction, button: discord.ui.Button):
|
|
await self.button_press(interaction, button.label)
|
|
|
|
|
|
@discord.ui.button(label='PH/PR', style=discord.ButtonStyle.gray, row=2)
|
|
async def pinch_hitter(self, interaction: discord.Interaction, button: discord.ui.Button):
|
|
await self.button_press(interaction, button.label)
|
|
|
|
|
|
async def ask_position(interaction: discord.Interaction):
|
|
view = SelectPosition(
|
|
responders=[interaction.user],
|
|
timeout=15
|
|
)
|
|
|
|
p_message = await interaction.channel.send(
|
|
content='Please select a position',
|
|
view=view
|
|
)
|
|
await view.wait()
|
|
|
|
if view.value:
|
|
await p_message.delete()
|
|
return view.value
|
|
|
|
else:
|
|
await p_message.edit(
|
|
content='To move things along, I will set the position to PH.',
|
|
view=None
|
|
)
|
|
return 'PH'
|
|
|
|
|
|
async def ask_confirm(interaction: discord.Interaction, question: str, label_type: Literal['yes', 'confirm'] = 'confirm', timeout: int = 60, delete_question: bool = True, custom_confirm_label: str = None, custom_cancel_label: str = None, embed: discord.Embed = None, delete_embed: bool = False) -> bool:
|
|
"""
|
|
button_callbacks: keys are button values, values are async functions
|
|
"""
|
|
try:
|
|
view = Confirm(responders=[interaction.user], timeout=timeout, label_type=label_type)
|
|
except AttributeError:
|
|
view = Confirm(responders=[interaction.author], timeout=timeout, label_type=label_type)
|
|
if custom_confirm_label:
|
|
view.confirm.label = custom_confirm_label
|
|
if custom_cancel_label:
|
|
view.cancel.label = custom_cancel_label
|
|
|
|
q_message = await interaction.channel.send(question, view=view)
|
|
await view.wait()
|
|
|
|
if view.value:
|
|
if delete_question:
|
|
await q_message.delete()
|
|
else:
|
|
await q_message.edit(content=question, view=None)
|
|
return True
|
|
|
|
else:
|
|
if delete_question:
|
|
await q_message.delete()
|
|
else:
|
|
await q_message.edit(content=question, view=None)
|
|
return False
|
|
|
|
|
|
async def ask_with_buttons(interaction: discord.Interaction, button_options: list[str], question: str = None, timeout: int = 60, delete_question: bool = True, embeds: list[discord.Embed] = None, delete_embeds: bool = False, edit_original_interaction: bool = False, none_okay: bool = False, confirmation_message: str = None) -> str:
|
|
"""
|
|
Returns text of button pressed
|
|
"""
|
|
logger.info(f'ask_with_buttons - button_options: {button_options} / question: {question} / timeout: {timeout} / delete_question: {delete_question} / embeds: {embeds} / delete_embeds: {delete_embeds} / edit_original_transaction: {edit_original_interaction} / confirmation_message: {confirmation_message}')
|
|
|
|
if question is None and embeds is None:
|
|
log_exception(KeyError, 'At least one of question or embed must be provided')
|
|
|
|
if confirmation_message is not None and (delete_question or delete_embeds):
|
|
log_exception(KeyError, 'Posting a confirmation message is not supported while deleting the message or embeds')
|
|
|
|
view = ButtonOptions(
|
|
responders=[interaction.user],
|
|
timeout=timeout,
|
|
labels=button_options
|
|
)
|
|
logger.info(f'view: {view}')
|
|
# if edit_original_interaction:
|
|
# logger.info(f'editing message')
|
|
# q_message = await interaction.edit_original_response(
|
|
# content=question,
|
|
# view=view,
|
|
# embeds=embeds
|
|
# )
|
|
# logger.info(f'edited')
|
|
# else:
|
|
# logger.info(f'posting message')
|
|
# q_message = await interaction.channel.send(
|
|
# content=question,
|
|
# view=view,
|
|
# embeds=embeds
|
|
# )
|
|
logger.info(f'posting message')
|
|
if edit_original_interaction:
|
|
q_message = await interaction.edit_original_response(
|
|
content=question,
|
|
view=view,
|
|
embeds=embeds
|
|
)
|
|
else:
|
|
q_message = await interaction.channel.send(
|
|
content=question,
|
|
view=view,
|
|
embeds=embeds
|
|
)
|
|
logger.info(f'waiting for response')
|
|
await view.wait()
|
|
|
|
if view.value:
|
|
return_val = view.value
|
|
|
|
else:
|
|
return_val = None
|
|
logger.info(f'return_val: {return_val}')
|
|
|
|
if question is not None and embeds is not None:
|
|
logger.info(f'checking for deletion with question and embeds')
|
|
if delete_question and delete_embeds:
|
|
logger.info(f'delete it all')
|
|
await q_message.delete()
|
|
elif delete_question:
|
|
logger.info(f'delete question')
|
|
await q_message.edit(
|
|
content=None
|
|
)
|
|
elif delete_embeds:
|
|
logger.info(f'delete embeds')
|
|
await q_message.edit(
|
|
embeds=None
|
|
)
|
|
elif return_val is None:
|
|
logger.info(f'remove view')
|
|
new_content = confirmation_message if confirmation_message is not None else question
|
|
logger.info(f'Confirmation message: {new_content}')
|
|
await q_message.edit(
|
|
content=new_content,
|
|
view=None
|
|
)
|
|
|
|
elif (question is not None and delete_question) or (embeds is not None and delete_embeds):
|
|
logger.info(f'deleting message')
|
|
await q_message.delete()
|
|
|
|
elif return_val is None:
|
|
logger.info(f'No reponse, remove view')
|
|
await q_message.edit(
|
|
view=None
|
|
)
|
|
|
|
if return_val is not None or none_okay:
|
|
logger.info(f'Returning: {return_val}')
|
|
if confirmation_message is not None:
|
|
new_content = confirmation_message if confirmation_message is not None else question
|
|
logger.info(f'Confirmation message: {new_content}')
|
|
await q_message.edit(
|
|
content=new_content,
|
|
view=None
|
|
)
|
|
return return_val
|
|
|
|
log_exception(ButtonOptionNotChosen, 'Selecting an option is mandatory')
|
|
|
|
|
|
class ScorebugButtons(discord.ui.View):
|
|
def __init__(self, play: Play, embed: discord.Embed, timeout: float = 30):
|
|
super().__init__(timeout=timeout)
|
|
self.value = None
|
|
self.batting_team = play.batter.team
|
|
self.pitching_team = play.pitcher.team
|
|
self.pitcher_card_url = play.pitcher.player.pitcher_card_url
|
|
self.batter_card_url = play.batter.player.batter_card_url
|
|
self.team = play.batter.team
|
|
self.play = play
|
|
self.had_chaos = False
|
|
self.embed = embed
|
|
|
|
if play.on_base_code == 0:
|
|
self.remove_item(self.button_jump)
|
|
|
|
async def interaction_check(self, interaction: discord.Interaction[discord.Client]) -> bool:
|
|
logger.info(f'user id: {interaction.user.id} / batting_team: {self.batting_team}')
|
|
if interaction.user.id == self.batting_team.gmid:
|
|
logger.info(f'User {interaction.user.id} rolling in Game {self.play.game.id}')
|
|
return True
|
|
elif self.batting_team.is_ai and interaction.user.id == self.pitching_team.gmid:
|
|
logger.info(f'User {interaction.user.id} rolling for AI in Game {self.play.game.id}')
|
|
return True
|
|
|
|
logger.info(f'User {interaction.user.id} rejected in Game {self.play.game.id}')
|
|
await interaction.response.send_message(
|
|
content='Get out of here',
|
|
ephemeral=True,
|
|
delete_after=5.0
|
|
)
|
|
return False
|
|
|
|
# async def on_timeout(self) -> Coroutine[Any, Any, None]:
|
|
# await self.interaction
|
|
|
|
@discord.ui.button(label='Roll AB', style=discord.ButtonStyle.primary)
|
|
async def button_ab(self, interaction: discord.Interaction, button: discord.ui.Button):
|
|
logger.info(f'User {interaction.user.id} rolling AB in Game {self.play.game.id}')
|
|
|
|
this_roll = ab_roll(self.team, self.play.game, allow_chaos=not self.had_chaos and self.play.on_base_code > 0)
|
|
logger.info(f'this_roll: {this_roll}')
|
|
if this_roll.is_chaos:
|
|
logger.info('AB Roll Is Chaos')
|
|
self.had_chaos = True
|
|
else:
|
|
button.disabled = True
|
|
|
|
await interaction.channel.send(content=None, embeds=this_roll.embeds)
|
|
|
|
if this_roll.d_six_one is not None and this_roll.d_six_one > 3:
|
|
logger.info(f'ScorebugButton - updating embed card to pitcher')
|
|
self.embed.set_image(url=self.pitcher_card_url)
|
|
logger.debug(f'embed image url: {self.embed.image}')
|
|
|
|
logger.debug(f'new embed: {self.embed}')
|
|
await interaction.response.edit_message(view=self, embed=self.embed)
|
|
|
|
@discord.ui.button(label='Check Jump', style=discord.ButtonStyle.secondary)
|
|
async def button_jump(self, interaction: discord.Interaction, button: discord.ui.Button):
|
|
logger.info(f'User {interaction.user.id} rolling jump in Game {self.play.game.id}')
|
|
|
|
this_roll = jump_roll(self.team, self.play.game)
|
|
button.disabled = True
|
|
|
|
await interaction.channel.send(content=None, embeds=this_roll.embeds)
|
|
await interaction.response.edit_message(view=self)
|
|
|