"""Defines the daily loop that runs the Dakiny bot functions.""" import random from datetime import UTC, datetime, time from typing import TYPE_CHECKING from nextcord.ext import commands, tasks from pytz import timezone from sqlalchemy import select from sqlalchemy.orm import Session from classes.election import Election from DakinySettings import settings from database_setup.sql_data_classes import UserBirthday, engine from main import birthday_list from utils import get_current_election_cycle if TYPE_CHECKING: import nextcord KANDY = 239547993106284544 class DailyLoop(commands.Cog, name="Daily Loop"): """Class for performing daily functions.""" def __init__(self, bot: commands.Bot) -> None: self.bot = bot self.current_election = None self.update_birthdays() # pylint doesn't recognize that daily_loop is subclassed from tasks.loop. self.daily_loop.start() # pylint: disable=no-member @tasks.loop(time=time(hour=0, tzinfo=timezone("UTC"))) async def daily_loop(self) -> None: """ Runs daily. Checks if the current election is None. If so, checks if there is an active election. If there is an active election, runs the check_date() function for the election to perform actions. Finally checks if the election is over to delete the current election object. """ if self.current_election is None: self.current_election = self.get_election() if self.current_election is not None: await self.current_election.check_date() if self.current_election.election_over: self.current_election = None self.update_birthdays() if KANDY in birthday_list: # inform kandy that it is her "birthday" kandy: nextcord.User = await self.bot.fetch_user(KANDY) await kandy.send('Happy "Birthday"!') await self.cakedays() @daily_loop.before_loop async def before_loop(self) -> None: """Actions run before the loop starts. Want to wait for bot to be ready and get the current cycle.""" await self.bot.wait_until_ready() # Bot needs to know guilds to get the current cycle. self.current_election = self.get_election() def get_election(self) -> Election: """ Creates an object for the current election cycle if one is active. An election is considered active between the election start and the term start. Returns: Election: Current election cycle, None if there is no election happening. """ cycle = get_current_election_cycle() # prevent exception from using cycle as an Int in an array if cycle is None: return None return Election(cycle, self.bot.get_guild(settings.params.guild_id)) def update_birthdays(self) -> None: """Goes through database to collect all birthdays that are today. Adds user's id to birthday list.""" birthday_list.clear() # kandy's 1% chance if random.randint(0, 99) == 0: # noqa: S311 isn't cryptography related. birthday_list.append(KANDY) today = datetime.now(tz=UTC).date() birthday = f"{today.month}/{today.day}" stmt = select(UserBirthday.UserId).where(UserBirthday.Birthday == birthday) with Session(engine) as session: to_add = session.scalars(stmt) birthday_list.extend(to_add.all()) async def cakedays(self) -> None: """ Loops through the guild's humans checking for joined_at dates that match today's date. Sends a message saying happy cakeday. """ today = datetime.now(tz=UTC).date() guild: nextcord.Guild = self.bot.get_guild(settings.params.guild_id) announce_channel: nextcord.TextChannel = guild.get_channel(settings.channel_ids.general) cakedays: list[nextcord.Member] = [ human for human in guild.humans if human.joined_at.month == today.month and human.joined_at.day == today.day ] for member in cakedays: years = today.year - member.joined_at.year year_str = "years" if years > 1 else "year" msg = f"Happy Cake day! <@{member.id}> joined the discord {years} {year_str} ago!" await announce_channel.send(msg)