""" Contains the election cycle class """ from datetime import datetime import nextcord from sqlalchemy import select from sqlalchemy.orm import Session from classes.nominate_views import NominateView from config import DakinyConfig from database_setup.sql_data_classes import ElectionInformation, engine from utils import get_datetime 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_emote_id=self.config.params.voting_emote_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 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 = self.get_election_cycle_name() # 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()) 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) 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) 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_cycle_name(self) -> str: """ Builds the unique name of the current election cycle Returns: str: the name of the election cycle """ return f"vote-{self.election_start.year}-{self.election_cycle.name}" 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 == self.get_election_cycle_name() ) return session.scalar(stmt)