359 lines
14 KiB
Python
359 lines
14 KiB
Python
"""Contained commands and views for record commands."""
|
|
|
|
import nextcord
|
|
from discord import TextInputStyle
|
|
from nextcord.ext import commands
|
|
|
|
from dakiny_bot.DakinySettings import settings
|
|
from dakiny_bot.utils import GUILDS, MAX_DISCORD_MESSAGE_LENGTH, PointType, record_points
|
|
|
|
|
|
class RecordCog(commands.Cog, name="Record Commands"):
|
|
"""Class holds record commands."""
|
|
|
|
def __init__(self, bot: commands.Bot) -> None:
|
|
"""Default constructor for RecordCog class. Does nothing."""
|
|
super().__init__()
|
|
self.bot: commands.Bot = bot
|
|
|
|
@nextcord.slash_command(guild_ids=GUILDS)
|
|
async def record(self, interaction: nextcord.Interaction) -> None:
|
|
"""Parent slash command for record. Never called."""
|
|
|
|
@record.subcommand(description="Record an event and its participants.")
|
|
async def event(
|
|
self,
|
|
interaction: nextcord.Interaction,
|
|
name: str = nextcord.SlashOption(required=True, description="Name of the event that was hosted."),
|
|
host: nextcord.Member = nextcord.SlashOption(
|
|
required=False,
|
|
description="An alternative host if you are recording event for someone else.",
|
|
),
|
|
) -> None:
|
|
"""
|
|
Create and send recording event.
|
|
|
|
This command creates the base event recording embed and sends a message with it and the EventView
|
|
for gathering more information on the event.
|
|
|
|
Args:
|
|
interaction (nextcord.Interaction): interaction with this command.
|
|
name (str): Name of the event.
|
|
host (nextcord.Member, optional): Alternative host to user using command.
|
|
"""
|
|
# check role
|
|
if interaction.guild.get_role(settings.role_ids.event_host) not in interaction.user.roles:
|
|
await interaction.response.send_message(
|
|
f"You must have the <@&{settings.role_ids.event_host}> role to record an event.",
|
|
ephemeral=True,
|
|
)
|
|
return
|
|
|
|
if host is None:
|
|
host = interaction.user
|
|
|
|
embed: nextcord.Embed = nextcord.Embed(title=name, description="")
|
|
embed.add_field(name="Participants", value="")
|
|
embed.add_field(name="Non-Discord Participants", value="")
|
|
embed.add_field(name="Hosted by", value=f"<@{host.id}>", inline=False)
|
|
|
|
await interaction.response.send_message(
|
|
"Fill out event details and select all members who participated!",
|
|
view=EventView(name, host.id),
|
|
ephemeral=True,
|
|
embed=embed,
|
|
)
|
|
|
|
@record.subcommand(description="Record a miscellaneous point for a clan member.")
|
|
async def point(
|
|
self,
|
|
interaction: nextcord.Interaction,
|
|
activity: str = nextcord.SlashOption(
|
|
description="The type of activity to receive points for.",
|
|
required=True,
|
|
choices=[
|
|
"Clan chat activity",
|
|
"Teaching or sharing in-game tips",
|
|
"Recruiting efforts",
|
|
"Successful recruitment",
|
|
],
|
|
),
|
|
justification: str = nextcord.SlashOption(
|
|
required=True,
|
|
description="Reason to give the recipient a point.",
|
|
max_length=int(MAX_DISCORD_MESSAGE_LENGTH / 2),
|
|
),
|
|
recipient: nextcord.Member = nextcord.SlashOption(
|
|
required=False,
|
|
description="Discord user to give point to.",
|
|
),
|
|
non_discord_recipient: str = nextcord.SlashOption(
|
|
required=False, description="Non Discord user to give point to.", max_length=32
|
|
),
|
|
) -> None:
|
|
"""
|
|
Gives a point to the provided recipient. Sends recording message to event participation channel.
|
|
|
|
Only one recipient should be given point/provided to the command. Fails if not exactly one recipient
|
|
is given.
|
|
|
|
Args:
|
|
interaction (nextcord.Interaction): interaction with this command.
|
|
activity (str): Choice list of activities to give point for.
|
|
justification (str): Reason to give point.
|
|
recipient (nextcord.Member): Discord member to give point to.
|
|
non_discord_recipient (str): Non Discord member to give point to.
|
|
"""
|
|
# role check
|
|
if interaction.guild.get_role(settings.role_ids.clan_member) not in interaction.user.roles:
|
|
await interaction.response.send_message(
|
|
f"You must have the <@&{settings.role_ids.clan_member}> role to record points.",
|
|
ephemeral=True,
|
|
)
|
|
return
|
|
|
|
# pylint or whatever seems to think that there are lines of unreachable code. That is false.
|
|
if recipient and non_discord_recipient:
|
|
await interaction.response.send_message("Please only provide 1 recipient.", ephemeral=True)
|
|
return
|
|
elif recipient is not None: # noqa: RET505
|
|
record_points(
|
|
[str(recipient.id)],
|
|
settings.point_settings.clan_points,
|
|
PointType.CLAN,
|
|
is_member=True,
|
|
)
|
|
recipient_str = f"<@{recipient.id}>"
|
|
elif non_discord_recipient is not None:
|
|
record_points(
|
|
[non_discord_recipient],
|
|
settings.point_settings.clan_points,
|
|
PointType.CLAN,
|
|
is_member=False,
|
|
)
|
|
recipient_str = f'"{non_discord_recipient}"'
|
|
else:
|
|
await interaction.response.send_message("You must provide a recipient.", ephemeral=True)
|
|
return
|
|
|
|
event_channel: nextcord.TextChannel = interaction.guild.get_channel(settings.channel_ids.event_post)
|
|
|
|
msg = f"<@{interaction.user.id}> has given {recipient_str} points for the {activity} activity. Justification: {justification}" # noqa: E501
|
|
if not msg.endswith("."):
|
|
msg += "."
|
|
await event_channel.send(msg)
|
|
await interaction.response.send_message("Point has been added.", ephemeral=True)
|
|
|
|
|
|
class EventView(nextcord.ui.View):
|
|
"""
|
|
Class handles gathering information on an event.
|
|
|
|
Has buttons to edit the information, cancel, and submit.
|
|
Also contains a UserSelect object to pick discord members.
|
|
"""
|
|
|
|
def __init__(self, name: str, host_id: int):
|
|
super().__init__(timeout=60 * 60 * 2) # timeout 2 hours
|
|
|
|
self.name: str = name
|
|
self.host_id: int = host_id
|
|
|
|
self.event_participants = ParticipantSelect()
|
|
|
|
self.add_item(self.event_participants)
|
|
|
|
@nextcord.ui.button(label="Cancel", style=nextcord.ButtonStyle.red, row=1)
|
|
async def cancel(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: # noqa: ARG002
|
|
"""Cancels event submission. Removes embed and view."""
|
|
await interaction.response.edit_message("Canceled", ephemeral=True, embed=None, view=None)
|
|
self.stop()
|
|
|
|
@nextcord.ui.button(label="Edit", style=nextcord.ButtonStyle.blurple, row=1)
|
|
async def edit(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: # noqa: ARG002
|
|
"""Calls modal to update the text input information of the event."""
|
|
await interaction.response.send_modal(ParticipantModal(interaction.message.embeds[0]))
|
|
|
|
@nextcord.ui.button(label="Confirm", style=nextcord.ButtonStyle.green, row=1)
|
|
async def confirm(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: # noqa: ARG002
|
|
"""
|
|
Submits the event embed to the event channel.
|
|
|
|
Removes view and embed from recording message.
|
|
Sends point information to database.
|
|
"""
|
|
if settings.point_settings.verify_events:
|
|
verification_channel: nextcord.TextChannel = interaction.guild.get_channel(settings.channel_ids.admin)
|
|
verification_view = EventVerification(
|
|
self.event_participants.values.members, interaction.message.embeds[0].fields[1].value, self.host_id
|
|
)
|
|
await verification_channel.send(
|
|
content="Event record request.", embed=interaction.message.embeds[0], view=verification_view
|
|
)
|
|
submission_response = "Event has been submitted to admins for approval."
|
|
else:
|
|
process_event(
|
|
self.event_participants.values.members,
|
|
interaction.message.embeds[0].fields[1].value,
|
|
self.host_id,
|
|
)
|
|
|
|
event_channel: nextcord.TextChannel = interaction.guild.get_channel(settings.channel_ids.event_post)
|
|
await event_channel.send(embed=interaction.message.embeds[0])
|
|
submission_response = "Event has been submitted."
|
|
|
|
await interaction.response.edit_message(content=submission_response, view=None, embed=None)
|
|
self.stop()
|
|
|
|
|
|
class EventVerification(nextcord.ui.View):
|
|
"""Class contains code to approve/deny event submissions."""
|
|
|
|
def __init__(
|
|
self,
|
|
discord_users: list[nextcord.Member],
|
|
non_discord_users: str,
|
|
host_id: int,
|
|
):
|
|
super().__init__(timeout=None, auto_defer=True)
|
|
self.discord_users: list[nextcord.Member] = discord_users
|
|
self.non_discord_users: str = non_discord_users
|
|
self.host_id: int = host_id
|
|
|
|
@nextcord.ui.button(label="Approve", style=nextcord.ButtonStyle.green)
|
|
async def approve(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: # noqa: ARG002
|
|
"""
|
|
Approve button for an event submission.
|
|
|
|
Calls the process event function. Records who approved the event.
|
|
Removes the view from the interaction message.
|
|
|
|
Args:
|
|
button (nextcord.ui.Button): Unused.
|
|
interaction (nextcord.Interaction): The interaction of the user clicking the button.
|
|
"""
|
|
process_event(self.discord_users, self.non_discord_users, self.host_id)
|
|
event_channel: nextcord.TextChannel = interaction.guild.get_channel(settings.channel_ids.event_post)
|
|
await event_channel.send(embed=interaction.message.embeds[0])
|
|
|
|
content = f"\nApproved by <@{interaction.user.id}>."
|
|
await interaction.response.edit_message(content=content, view=None)
|
|
|
|
@nextcord.ui.button(label="Deny", style=nextcord.ButtonStyle.red)
|
|
async def deny(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: # noqa: ARG002
|
|
"""
|
|
Deny button for an event submission. Just removes the view and logs who denied the request.
|
|
|
|
Args:
|
|
button (nextcord.ui.Button): Unused.
|
|
interaction (nextcord.Interaction): The interaction of the user clicking the button.
|
|
"""
|
|
content = f"\nDenied by <@{interaction.user.id}>."
|
|
await interaction.response.edit_message(content=content, view=None)
|
|
|
|
|
|
class ParticipantModal(nextcord.ui.Modal):
|
|
"""
|
|
This modal contains all the text edits for an event.
|
|
|
|
You can change the name, description, and add the usernames of participants who are not in the discord.
|
|
"""
|
|
|
|
# consider turning this embed into a dict
|
|
def __init__(self, embed: nextcord.Embed):
|
|
super().__init__(
|
|
"Edit the event.",
|
|
timeout=5 * 60,
|
|
)
|
|
self.embed: nextcord.Embed = embed
|
|
self.name: nextcord.ui.TextInput = nextcord.ui.TextInput(
|
|
required=True,
|
|
label="Event Name",
|
|
default_value=self.embed.title,
|
|
)
|
|
self.description: nextcord.ui.TextInput = nextcord.ui.TextInput(
|
|
required=False,
|
|
label="Event Description",
|
|
default_value=self.embed.description,
|
|
style=TextInputStyle.paragraph,
|
|
placeholder="Add a description if desired.",
|
|
max_length=1000,
|
|
)
|
|
self.participants: nextcord.ui.TextInput = nextcord.ui.TextInput(
|
|
required=False,
|
|
label="Non-Discord Usernames",
|
|
style=TextInputStyle.paragraph,
|
|
placeholder="Please separate usernames with a new line.",
|
|
default_value=self.embed.fields[1].value,
|
|
)
|
|
self.add_item(self.name)
|
|
self.add_item(self.description)
|
|
self.add_item(self.participants)
|
|
|
|
async def callback(self, interaction: nextcord.Interaction) -> None:
|
|
"""
|
|
Edits the interaction message to contain the Non-Discord participants.
|
|
|
|
Args:
|
|
interaction (nextcord.Interaction): Interaction of the callback.
|
|
"""
|
|
self.embed.title = self.name.value
|
|
self.embed.description = self.description.value
|
|
self.embed.set_field_at(index=1, name="Non-Discord Participants", value=self.participants.value)
|
|
|
|
await interaction.response.edit_message(embed=self.embed)
|
|
|
|
|
|
class ParticipantSelect(nextcord.ui.UserSelect):
|
|
"""A selection drop down for discord members. Updates the embed with all users selected."""
|
|
|
|
def __init__(self) -> None:
|
|
super().__init__(
|
|
min_values=1,
|
|
max_values=25,
|
|
placeholder="Select all participants",
|
|
row=0,
|
|
)
|
|
|
|
async def callback(self, interaction: nextcord.Interaction) -> None:
|
|
"""
|
|
Edits the interaction message to contain the Discord participants.
|
|
|
|
Args:
|
|
interaction (nextcord.Interaction): Interaction of the callback.
|
|
"""
|
|
embed: nextcord.Embed = interaction.message.embeds[0]
|
|
participants: str = "\n".join([f"<@{participant.id}>" for participant in self.values.members])
|
|
embed.set_field_at(0, name="Participants", value=participants)
|
|
await interaction.response.edit_message(embed=embed)
|
|
|
|
|
|
def process_event(discord_users: list[nextcord.Member], non_discord_users: str, host_id: int) -> None:
|
|
"""
|
|
Processes all points to give to participants and host for an event.
|
|
|
|
Args:
|
|
discord_users (list[nextcord.Member]): List of discord members that participated in event.
|
|
non_discord_users (str): Newline separated list of non discord members that participated in event.
|
|
host_id (int): Discord ID of host.
|
|
"""
|
|
if discord_users:
|
|
discord_users_list: list[str] = [str(user.id) for user in discord_users if user.id is not host_id]
|
|
record_points(
|
|
discord_users_list,
|
|
settings.point_settings.participate_points,
|
|
PointType.EVENT,
|
|
is_member=True,
|
|
)
|
|
|
|
if non_discord_users:
|
|
non_discord_users_list: list[str] = non_discord_users.split("\n")
|
|
record_points(
|
|
non_discord_users_list,
|
|
settings.point_settings.participate_points,
|
|
PointType.EVENT,
|
|
is_member=False,
|
|
)
|
|
|
|
record_points([host_id], settings.point_settings.host_points, PointType.EVENT, is_member=True)
|