""" Contains various views to be used while voting for high council. Controls the logic when performing the vote. """ from typing import List import nextcord from nextcord import SelectOption from nextcord.interactions import Interaction from sqlalchemy import exc, select, update from sqlalchemy.orm import Session from config import DakinyConfig from database_setup.sql_data_classes import ( CouncilMembers, ElectionInformation, Votes, engine, ) from utils import ( build_highcouncil_votes_str, get_cl_election_name, get_dm_election_name, get_election_cycle_name, ) class VoteSelect(nextcord.ui.StringSelect): def __init__( self, options: List["SelectOption"], config: DakinyConfig, election_type: str ) -> None: super().__init__( placeholder="Choose 1", min_values=1, max_values=1, options=options, ) self.config = config self.election_type = election_type async def callback(self, interaction: Interaction): # decide on election type match self.election_type: case "cl": election_name: str = get_cl_election_name(self.config) case "dm": election_name: str = get_dm_election_name(self.config) case _: # should never happen raise ValueError('election type was not "cl" or "dm"') cycle: str = get_election_cycle_name(self.config) with Session(engine) as session: stmt = select(ElectionInformation.HighCouncilVoteMsgId).where( ElectionInformation.ElectionCycle == cycle ) vote_msg_id = session.scalar(stmt) if self.values[0] == "Abstain": voted_for = None else: voted_for = int(self.values[0]) new_vote = Votes( ElectionCycle=election_name, VoterId=interaction.user.id, VotedForId=voted_for, ) try: session.add(new_vote) session.commit() except exc.IntegrityError: session.rollback() stmt = ( update(Votes) .where(Votes.ElectionCycle == election_name) .where(Votes.VoterId == interaction.user.id) .values(VotedForId=voted_for) ) session.execute(stmt) session.commit() vote_message_str = build_highcouncil_votes_str(self.config, interaction.guild) vote_msg: nextcord.Message = await interaction.channel.fetch_message( vote_msg_id ) await vote_msg.edit(content=vote_message_str) if voted_for is not None: msg = f"You have voted for <@{voted_for}>" else: msg = "You have abstained from voting." await interaction.edit(content=msg, view=None) class VoteHighcouncilView(nextcord.ui.View): """ The button to press to start voting for HighCouncil. """ def __init__(self, config: DakinyConfig, guild: nextcord.Guild): super().__init__(timeout=None) self.config = config self.guild = guild @nextcord.ui.button( label="Vote for Clan Leader", style=nextcord.ButtonStyle.primary, custom_id="vote-cl", ) async def vote_cl( self, button: nextcord.ui.Button, interaction: nextcord.Interaction ) -> None: """ Enforces only council elect vote. Gets election cycle, creates voting options for clan leader (checking if user has voted already), then sends the voting view. 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: str = get_election_cycle_name(self.config) election: str = get_cl_election_name(self.config) with Session(engine) as session: stmt = ( select(Votes.VotedForId) .where(Votes.ElectionCycle == election) .where(Votes.VoterId == interaction.user.id) ) voted_for = session.scalar(stmt) stmt = ( select(CouncilMembers.MemberId) .where(CouncilMembers.ElectionCycle == cycle) .filter(CouncilMembers.ClanLeaderSpeechId.is_not(None)) ) options = [ SelectOption( label=f"{self.guild.get_member(nominee).display_name} ☑️" if voted_for == nominee else f"{self.guild.get_member(nominee).display_name}", value=str(nominee), ) for nominee in sorted( session.scalars(stmt), key=lambda nominee: self.guild.get_member(nominee).display_name, ) ] options.append( SelectOption( label="Abstain ☑️" if voted_for is None else "Abstain", value="Abstain", ) ) view = nextcord.ui.View() view.add_item( VoteSelect(options=options, config=self.config, election_type="cl") ) await interaction.response.send_message( content="Please vote for a Clan Leader!", view=view, ephemeral=True, ) @nextcord.ui.button( label="Vote for Discord Manager", style=nextcord.ButtonStyle.primary, custom_id="vote-dm", ) async def vote_dm( self, button: nextcord.ui.Button, interaction: nextcord.Interaction ) -> None: """ Enforces only council elect vote. Gets election cycle, creates voting options for discord manager (checking if user has voted already), then sends the voting view. 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: str = get_election_cycle_name(self.config) election: str = get_dm_election_name(self.config) with Session(engine) as session: stmt = ( select(Votes.VotedForId) .where(Votes.ElectionCycle == election) .where(Votes.VoterId == interaction.user.id) ) voted_for = session.scalar(stmt) stmt = ( select(CouncilMembers.MemberId) .where(CouncilMembers.ElectionCycle == cycle) .filter(CouncilMembers.DiscordManagerSpeechId.is_not(None)) ) options = [ SelectOption( label=f"{self.guild.get_member(nominee).display_name} ☑️" if voted_for == nominee else f"{self.guild.get_member(nominee).display_name}", value=str(nominee), ) for nominee in sorted( session.scalars(stmt), key=lambda nominee: self.guild.get_member(nominee).display_name, ) ] options.append( SelectOption( label="Abstain ☑️" if voted_for is None else "Abstain", value="Abstain", ) ) view = nextcord.ui.View() view.add_item( VoteSelect(options=options, config=self.config, election_type="dm") ) await interaction.response.send_message( content="Please vote for a Discord Manager!", view=view, ephemeral=True, )