Files
dakiny-bot/classes/election_cycle.py
T
imcompiling d149caafea
build_develop / build (push) Successful in 1m26s
Add election reminder.
Happens daily starting 3 days before election end.
2023-11-03 17:31:52 -04:00

290 lines
12 KiB
Python

"""
Contains the election cycle class
"""
import collections
from datetime import datetime, timedelta
import nextcord
from sqlalchemy import select
from sqlalchemy.orm import Session
from classes.nominate_views import NominateView
from classes.voting_views import VoteView
from config import DakinyConfig
from database_setup.sql_data_classes import CouncilMembers, ElectionInformation, Votes, engine
from utils import build_votes_str, get_datetime, get_election_cycle_name
class ElectionCycle:
"""
This class represents all the dates required for an election cycle along with all the functions needed to operate
the cycle.
"""
def __init__(self, cycle: int, config: DakinyConfig, guild: nextcord.Guild) -> None:
"""
Generates all dates relevant to the election cycle determined by the config and cycle.
Args:
cycle (int): Which election cycle this cycle is.
config (DakinyConfig): The entire config object.
guild (nextcord.Guild): The guild that this election is running in.
"""
self.config: DakinyConfig = config
self.cycle = cycle
self.election_cycle = self.config.election_cycles[self.cycle]
self.election_start: datetime = get_datetime(self.election_cycle.election_start)
self.term_start: datetime = get_datetime(self.election_cycle.term_start)
self.term_end: datetime = get_datetime(self.election_cycle.term_end)
self.voting_start: datetime = get_datetime(self.election_cycle.voting_start)
self.voting_end: datetime = get_datetime(self.election_cycle.voting_end)
self.highcouncil_election_announce: datetime = get_datetime(self.election_cycle.highcouncil_election_announce)
self.highcouncil_election_start: datetime = get_datetime(self.election_cycle.highcouncil_election_start)
self.highcouncil_election_end: datetime = get_datetime(self.election_cycle.highcouncil_election_end)
self.guild: nextcord.Guild = guild
self.election_over: bool = False
def build_election_announcement(self) -> list[str]:
"""
Builds the messages to announce the election is starting.
Returns:
list[str]: A list of message contents in the correct order. Must be a string because the message is too
long.
"""
with open("bot_messages/election_announcement.txt", mode="r", encoding="utf8") as file:
message = file.read()
next_election = get_datetime(
self.config.election_cycles[(self.cycle + 1) % len(self.config.election_cycles)].election_start
)
message = message.format(
clan_council_id=self.config.params.clan_council_role_id,
high_council_id=self.config.params.high_council_role_id,
election_start=self.election_start.strftime("%B %d"),
term_start=self.term_start.strftime("%B %d"),
term_end=self.term_end.strftime("%B %d"),
voting_start=self.voting_start.strftime("%B %d"),
voting_end=self.voting_end.strftime("%B %d"),
highcouncil_election=self.highcouncil_election_start.strftime("%B %d"),
next_election_start=next_election.strftime("%B %d"),
)
return message.split("---message_break---")
def build_highcouncil_election_announce(self) -> str:
"""
Builds message to announce highcouncil election start.
Returns:
str: Message to send.
"""
with open("bot_messages/highcouncil_election.txt", mode="r", encoding="utf8") as file:
message = file.read()
return message.format(
clan_council_elect_role_id=self.config.params.clan_council_elect_role_id,
highcouncil_election_announce=self.highcouncil_election_announce.strftime("%B %d"),
highcouncil_election_start=self.highcouncil_election_start.strftime("%B %d"),
highcouncil_election_end=self.highcouncil_election_end.strftime("%B %d"),
term_start=self.term_start.strftime("%B %d"),
)
def build_highcouncil_vote_start(self) -> str:
"""
Builds the message to announce that highcouncil voting has started.
Returns:
str: Message to send.
"""
with open("bot_messages/highcouncil_vote.txt", mode="r", encoding="utf8") as file:
message = file.read()
return message.format(
clan_council_elect_role_id=self.config.params.clan_council_elect_role_id,
term_start=self.term_start.strftime("%B %d"),
)
def build_vote_start(self) -> str:
"""
Builds the voting start message out of template and configurations.
Returns:
str: Message to send
"""
with open("bot_messages/vote_start.txt", mode="r", encoding="utf8") as file:
message = file.read()
vote_end: datetime = get_datetime(self.config.election_cycles[self.cycle].voting_end)
return message.format(
clan_member_id=self.config.params.clan_member_role_id,
voting_end=vote_end.strftime("%B %d"),
voting_end_day=vote_end.strftime("%A"),
)
async def check_date(self) -> None:
"""
Function checks current date to see if something needs to be run.
"""
today = datetime.today()
# check all dates if action is needed
if today.date() == self.election_start.date():
await self.election_start_action()
elif today.date() == self.voting_start.date():
await self.voting_start_action()
elif (self.voting_end - timedelta(3)).date() <= today.date() < self.voting_end.date():
await self.election_reminder()
elif today.date() == self.voting_end.date():
await self.voting_end_action()
elif today.date() == self.highcouncil_election_start.date():
await self.highcouncil_election_start_action()
elif today.date() == self.term_start.date():
await self.term_start_action()
async def election_start_action(self) -> None:
"""
Performs required actions for when the election cycle starts.
"""
# create new channel for election
category: nextcord.CategoryChannel = self.guild.get_channel(self.config.params.active_category_id)
quarter_name: str = get_election_cycle_name(self.config)
# create new entry into database.
election_channel: nextcord.TextChannel = await category.create_text_channel(quarter_name)
clan_member_role: nextcord.Role = self.guild.get_role(self.config.params.clan_member_role_id)
await election_channel.set_permissions(clan_member_role, add_reactions=True)
with Session(engine) as session:
new_cycle = ElectionInformation(ElectionCycle=quarter_name, ElectionChannel=election_channel.id)
session.add(new_cycle)
session.commit()
# post message announcing election
# election start message is returned as an array of string because it is too long. send each string
# individually.
messages = self.build_election_announcement()
for index, message in enumerate(messages):
# if last item, add the nominate button
if index + 1 == len(messages):
await election_channel.send(message, view=NominateView(self.config))
else:
await election_channel.send(message)
async def voting_start_action(self) -> None:
"""
Sends message informing members that voting has started.
Removes write permission from the general clan member role.
"""
message = self.build_vote_start()
clan_member_role: nextcord.Role = self.guild.get_role(self.config.params.clan_member_role_id)
election_channel: nextcord.TextChannel = self.guild.get_channel(self.get_election_info().ElectionChannel)
await election_channel.send(message)
vote_msg = await election_channel.send(build_votes_str(self.config), view=VoteView(self.config, self.guild))
with Session(engine) as session:
stmt = select(ElectionInformation).where(
ElectionInformation.ElectionCycle == get_election_cycle_name(self.config)
)
info = session.scalar(stmt)
info.CouncilVoteMsgId = vote_msg.id
session.commit()
await election_channel.set_permissions(clan_member_role, send_messages=False)
async def voting_end_action(self) -> None:
"""
Performs required actions for when voting time has ended.
"""
message = self.build_highcouncil_election_announce()
council_elect_role: nextcord.Role = self.guild.get_role(self.config.params.clan_council_elect_role_id)
election_channel: nextcord.TextChannel = self.guild.get_channel(self.get_election_info().ElectionChannel)
clan_member_role: nextcord.Role = self.guild.get_role(self.config.params.clan_member_role_id)
# remove vote button from vote message
election_name = get_election_cycle_name(self.config)
with Session(engine) as session:
stmt = select(ElectionInformation.CouncilVoteMsgId).where(
ElectionInformation.ElectionCycle == election_name
)
vote_msg_id = session.scalar(stmt)
vote_msg: nextcord.Message = await election_channel.fetch_message(vote_msg_id)
await vote_msg.edit(view=None)
# add the highcouncil role to everyone with 3 or more votes. set elected to true in database
with Session(engine) as session:
stmt = select(Votes.VotedForId).where(Votes.ElectionCycle == election_name)
vote_count = collections.Counter(session.scalars(stmt))
for user_id, votes in vote_count.items():
if votes >= 3:
stmt = (
select(CouncilMembers)
.where(CouncilMembers.ElectionCycle == election_name)
.where(CouncilMembers.MemberId == user_id)
)
council_member = session.scalar(stmt)
council_member.Elected = True
session.commit()
user: nextcord.Member = self.guild.get_member(user_id)
await user.add_roles(council_elect_role)
await election_channel.send(message)
await election_channel.set_permissions(council_elect_role, send_messages=True, add_reactions=True)
await election_channel.set_permissions(clan_member_role, add_reactions=False)
async def highcouncil_election_start_action(self) -> None:
"""
Performs required actions for when the highcouncil is elected.
"""
message = self.build_highcouncil_vote_start()
election_channel: nextcord.TextChannel = self.guild.get_channel(self.get_election_info().ElectionChannel)
await election_channel.send(message)
async def term_start_action(self) -> None:
"""
Moves the election channel into the archive category.
"""
self.election_over = True
election_channel: nextcord.TextChannel = self.guild.get_channel(self.get_election_info().ElectionChannel)
archive: nextcord.CategoryChannel = self.guild.get_channel(self.config.params.archive_channel_group_id)
await election_channel.move(beginning=True, category=archive)
def get_election_info(self) -> ElectionInformation:
"""
Gets information for current election.
Returns:
ElectionInformation: The information from the database about the current election.
"""
with Session(engine) as session:
stmt = select(ElectionInformation).where(
ElectionInformation.ElectionCycle == get_election_cycle_name(self.config)
)
return session.scalar(stmt)
async def election_reminder(self) -> None:
"""
Sends a message to general chat reminder clan about election.
"""
general: nextcord.TextChannel = self.guild.get_channel(self.config.params.general_channel_id)
message = f"Reminder to <@&{self.config.params.clan_member_role_id}>! Clan elections are happening! Head over "
message += f"to <#{self.get_election_info().ElectionChannel}> to cast your vote!"
await general.send(content=message)