build_develop / build (push) Successful in 37s
Also handle the list error from pylance.
237 lines
8.5 KiB
Python
237 lines
8.5 KiB
Python
"""Contains various views to be used while voting for high council. Controls the logic when performing the vote."""
|
|
|
|
import nextcord
|
|
from dakiny_bot.dakiny_settings import settings
|
|
from dakiny_bot.database.sql_data_classes import (
|
|
CouncilMembers,
|
|
ElectionInformation,
|
|
Votes,
|
|
engine,
|
|
)
|
|
from dakiny_bot.utils import VoteType, build_highcouncil_votes_str, get_election_name
|
|
from nextcord import SelectOption
|
|
from nextcord.interactions import Interaction
|
|
from sqlalchemy import exc, select, update
|
|
from sqlalchemy.orm import Session
|
|
|
|
|
|
class VoteSelect(nextcord.ui.StringSelect):
|
|
"""Vote view string select."""
|
|
|
|
def __init__(self, options: list["SelectOption"], election_type: str) -> None:
|
|
super().__init__(
|
|
placeholder="Choose 1",
|
|
min_values=1,
|
|
max_values=1,
|
|
options=options,
|
|
)
|
|
self.election_type = election_type
|
|
|
|
async def callback(self, interaction: Interaction) -> None:
|
|
"""
|
|
Callback override for the StringSelect.
|
|
|
|
Submits the High Council vote to database if a user is selected. Handles adding vs editing a vote.
|
|
Returns message informing user that vote has been recorded.
|
|
|
|
Args:
|
|
interaction (Interaction): Interaction with this callback.
|
|
"""
|
|
election_name: str = get_election_name()
|
|
with Session(engine) as session:
|
|
stmt = select(ElectionInformation.HighCouncilVoteMsgId).where(
|
|
ElectionInformation.ElectionName == election_name
|
|
)
|
|
vote_msg_id = session.scalar(stmt)
|
|
|
|
voted_for = None if self.values[0] == "Abstain" else int(self.values[0])
|
|
|
|
new_vote = Votes(
|
|
ElectionName=election_name,
|
|
VoterId=interaction.user.id,
|
|
VotedForId=voted_for,
|
|
Type=self.election_type,
|
|
)
|
|
|
|
try:
|
|
session.add(new_vote)
|
|
session.commit()
|
|
|
|
except exc.IntegrityError:
|
|
# user already has a vote in the election and election type
|
|
session.rollback()
|
|
stmt = (
|
|
update(Votes)
|
|
.where(Votes.ElectionName == election_name)
|
|
.where(Votes.VoterId == interaction.user.id)
|
|
.where(Votes.Type == self.election_type)
|
|
.values(VotedForId=voted_for)
|
|
)
|
|
session.execute(stmt)
|
|
session.commit()
|
|
|
|
vote_message_str = build_highcouncil_votes_str(interaction.guild)
|
|
vote_msg: nextcord.Message = await interaction.channel.fetch_message(vote_msg_id)
|
|
await vote_msg.edit(content=vote_message_str)
|
|
|
|
msg = f"You have voted for <@{voted_for}>" if voted_for is not None else "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, guild: nextcord.Guild):
|
|
super().__init__(timeout=None)
|
|
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: # noqa: ARG002
|
|
"""
|
|
Button to vote for Clan Leader.
|
|
|
|
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(settings.role_ids.clan_council_elect) not in interaction.user.roles:
|
|
await interaction.response.send_message(
|
|
f"You must have the <@&{settings.role_ids.clan_council_elect}> role to vote.",
|
|
ephemeral=True,
|
|
)
|
|
return
|
|
|
|
election_name: str = get_election_name()
|
|
|
|
with Session(engine) as session:
|
|
stmt = (
|
|
select(Votes.VotedForId)
|
|
.where(Votes.ElectionName == election_name)
|
|
.where(Votes.VoterId == interaction.user.id)
|
|
.where(Votes.Type == VoteType.CLANLEADER)
|
|
)
|
|
voted_for = session.scalar(stmt)
|
|
|
|
stmt = (
|
|
select(CouncilMembers.MemberId)
|
|
.where(CouncilMembers.ElectionName == election_name)
|
|
.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.lower(),
|
|
)
|
|
]
|
|
options.append(
|
|
SelectOption(
|
|
label="Abstain ☑️" if voted_for is None else "Abstain",
|
|
value="Abstain",
|
|
)
|
|
)
|
|
|
|
view = nextcord.ui.View()
|
|
view.add_item(VoteSelect(options=options, election_type=VoteType.CLANLEADER))
|
|
|
|
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: # noqa: ARG002
|
|
"""
|
|
Button to vote for Discord Manager.
|
|
|
|
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(settings.role_ids.clan_council_elect) not in interaction.user.roles:
|
|
await interaction.response.send_message(
|
|
f"You must have the <@&{settings.role_ids.clan_council_elect}> role to vote.",
|
|
ephemeral=True,
|
|
)
|
|
return
|
|
|
|
election_name: str = get_election_name()
|
|
|
|
with Session(engine) as session:
|
|
stmt = (
|
|
select(Votes.VotedForId)
|
|
.where(Votes.ElectionName == election_name)
|
|
.where(Votes.VoterId == interaction.user.id)
|
|
.where(Votes.Type == VoteType.DISCORDMANAGER)
|
|
)
|
|
voted_for = session.scalar(stmt)
|
|
|
|
stmt = (
|
|
select(CouncilMembers.MemberId)
|
|
.where(CouncilMembers.ElectionName == election_name)
|
|
.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.lower(),
|
|
)
|
|
]
|
|
options.append(
|
|
SelectOption(
|
|
label="Abstain ☑️" if voted_for is None else "Abstain",
|
|
value="Abstain",
|
|
)
|
|
)
|
|
|
|
view = nextcord.ui.View()
|
|
view.add_item(
|
|
VoteSelect(
|
|
options=options,
|
|
election_type=VoteType.DISCORDMANAGER,
|
|
)
|
|
)
|
|
|
|
await interaction.response.send_message(
|
|
content="Please vote for a Discord Manager!",
|
|
view=view,
|
|
ephemeral=True,
|
|
)
|