Move all files into dakiny_bot package.
This commit is contained in:
@@ -0,0 +1,358 @@
|
||||
"""Contained commands and views for record commands."""
|
||||
|
||||
import nextcord
|
||||
from discord import TextInputStyle
|
||||
from nextcord.ext import commands
|
||||
|
||||
from DakinySettings import settings
|
||||
from 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)
|
||||
Reference in New Issue
Block a user