Files
dakiny-bot/cogs/loops/daily_loop.py
T
imcompiling 9109ac53b3
build_latest / build (push) Successful in 1m44s
Use KANDY const instead of int.
2024-07-13 12:54:55 -04:00

123 lines
4.4 KiB
Python

"""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_cycle import ElectionCycle
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
from config import DakinyConfig
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_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 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_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_election_cycle(config)
# prevent exception from using cycle as an Int in an array
if cycle is None:
return None
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: # 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(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)