diff --git a/classes/nominate_cl_views.py b/classes/nominate_cl_views.py new file mode 100644 index 0000000..297b105 --- /dev/null +++ b/classes/nominate_cl_views.py @@ -0,0 +1,139 @@ +""" +Contains various views to be used in the clan leader nomination process. +""" + +import nextcord +from nextcord.interactions import Interaction +from sqlalchemy import select +from sqlalchemy.orm import Session + +from config import DakinyConfig +from database_setup.sql_data_classes import CouncilMembers, engine +from utils import NOMINEE_SPLITTER, decorate_nomination, get_election_cycle_name + + +class NominateCLModal(nextcord.ui.Modal): + """ + Modal for nominating oneself. User writes a speech and then the callback sends the speech into the correct channel. + """ + + def __init__( + self, + config: DakinyConfig, + usr_id: int, + usr_nominated: bool, + speech_content: str, + ): + super().__init__( + "Clan Leader Nomination", + timeout=5 * 60, # 5 minutes + ) + self.usr_id: int = usr_id + self.config: DakinyConfig = config + self.usr_nominated = usr_nominated + + self.speech = nextcord.ui.TextInput( + label="Nomination Speech", + style=nextcord.TextInputStyle.paragraph, + placeholder="Please write your speech here.", + required=True, + max_length=1500, + default_value=speech_content, + ) + self.add_item(self.speech) + + async def callback(self, interaction: Interaction) -> None: + """ + Sends the nomination speech into the voting channel and enters the user into the voting database. + + Args: + interaction (Interaction): Interaction with user. + + """ + + speech = decorate_nomination(interaction.user.id, self.speech.value) + + with Session(engine) as session: + # user already in table + cycle = get_election_cycle_name(self.config) + stmt = ( + select(CouncilMembers) + .where(CouncilMembers.ElectionCycle == cycle) + .where(CouncilMembers.MemberId == self.usr_id) + ) + council_elect = session.scalar(stmt) + + if not self.usr_nominated: + msg = await interaction.channel.send(speech) + + council_elect.ClanLeaderSpeechId = msg.id + session.commit() + else: + msg: nextcord.Message = await interaction.channel.fetch_message( + council_elect.ClanLeaderSpeechId + ) + await msg.edit( + content=decorate_nomination(interaction.user.id, self.speech.value) + ) + + +class NominateCLView(nextcord.ui.View): + """ + The button to press to start your Clan Leader nomination. + """ + + def __init__(self, config: DakinyConfig, guild: nextcord.Guild): + super().__init__(timeout=None) + self.config: DakinyConfig = config + self.guild: nextcord.Guild = guild + + @nextcord.ui.button( + label="Nominate for Clan Leader", + style=nextcord.ButtonStyle.primary, + custom_id="nominate-cl", + ) + async def nominate( + self, button: nextcord.ui.Button, interaction: nextcord.Interaction + ) -> None: + """ + Gathers previous information about the user if they have already nominated themselves, then launches + the nomination modal to get their speech. + + Args: + button (nextcord.ui.Button): Unused. idk why its needed in the decorator + interaction (nextcord.Interaction): Interaction of user who pressed the button + """ + + # enforce council elect only + if ( + self.guild.get_role(self.config.params.clan_council_elect_role_id) + not in interaction.user.roles + ): + await interaction.response.send_message( + f"You must have the <@&{self.config.params.clan_council_elect_role_id}> role to vote.", + ephemeral=True, + ) + return + + cycle = get_election_cycle_name(self.config) + usr_nominated = False + speech_content = "" + with Session(engine) as session: + stmt = ( + select(CouncilMembers) + .where(CouncilMembers.ElectionCycle == cycle) + .where(CouncilMembers.MemberId == interaction.user.id) + ) + obj = session.scalar(stmt) + + # user needs to exist to get here anyways... + if obj.ClanLeaderSpeechId is not None: + usr_nominated = True + msg = await interaction.channel.fetch_message(obj.ClanLeaderSpeechId) + speech_content = msg.content.split(NOMINEE_SPLITTER)[1] + + await interaction.response.send_modal( + NominateCLModal( + self.config, interaction.user.id, usr_nominated, speech_content + ) + ) diff --git a/dakiny_bot.py b/dakiny_bot.py index ab9bcf6..bef7ff6 100644 --- a/dakiny_bot.py +++ b/dakiny_bot.py @@ -8,6 +8,8 @@ from hydra.core.config_store import ConfigStore from nextcord.ext import commands import database_setup.create_database as create_database +from classes.nominate_cl_views import NominateCLView +from classes.nominate_dm_views import NominateDMView from classes.nominate_views import NominateView from classes.voting_views import VoteView from config import DakinyConfig @@ -54,6 +56,8 @@ def main(cfg: DakinyConfig): assert bot.user is not None create_database # pylint: disable=pointless-statement bot.add_view(NominateView(cfg, bot.get_guild(cfg.params.guild_id))) + bot.add_view(NominateCLView(cfg, bot.get_guild(cfg.params.guild_id))) + bot.add_view(NominateDMView(cfg, bot.get_guild(cfg.params.guild_id))) bot.add_view(VoteView(cfg, bot.get_guild(cfg.params.guild_id))) print(f"{bot.user.name} has connected to Discord!")