""" Defines the daily loop that runs the Dakiny bot functions. """ import random from datetime import datetime, time import nextcord from nextcord.ext import commands, tasks from pytz import timezone from sqlalchemy import select from sqlalchemy.orm import Session from classes.election_cycle import ElectionCycle from config import DakinyConfig from dakiny_bot import birthday_list from database_setup.sql_data_classes import UserBirthday, engine from utils import get_current_cycle 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("GMT"))) async def daily_loop(self): """ 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_cycle() 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 239547993106284544 in birthday_list: # inform kandy that it is her "birthday" kandy: nextcord.User = await self.bot.fetch_user(239547993106284544) await kandy.send('Happy "Birthday"!') await self.cakedays() @daily_loop.before_loop async def before_loop(self): """ 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_cycle() def get_election_cycle(self) -> ElectionCycle: """ 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: ElectionCycle: Current election cycle, None if there is no election happening. """ config: DakinyConfig = self.bot.config cycle = get_current_cycle(config) return ElectionCycle(cycle, config, self.bot.get_guild(config.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: birthday_list.append(239547993106284544) today = datetime.today() 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.today() guild: nextcord.Guild = self.bot.get_guild(self.bot.config.params.guild_id) announce_channel: nextcord.TextChannel = guild.get_channel(self.bot.config.params.general_channel_id) 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)