diff --git a/.vscode/settings.json b/.vscode/settings.json index 0e73f13..a5f9061 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,5 +3,8 @@ "reportOptionalMemberAccess": "warning", "reportArgumentType": "information", "reportAttributeAccessIssue": "warning" - } + }, + "cSpell.words": [ + "cakedays" + ] } \ No newline at end of file diff --git a/cogs/loops/__init__.py b/cogs/loops/__init__.py new file mode 100644 index 0000000..cd5eaa7 --- /dev/null +++ b/cogs/loops/__init__.py @@ -0,0 +1,10 @@ +"""Initializing code for all loops.""" + +from nextcord.ext import commands + +from cogs.loops.daily_loop import DailyLoop + + +def setup(bot: commands.Bot) -> None: + """Add functions to bot.""" + bot.add_cog(DailyLoop(bot)) diff --git a/cogs/loops/daily_loop.py b/cogs/loops/daily_loop.py new file mode 100644 index 0000000..1ff3b67 --- /dev/null +++ b/cogs/loops/daily_loop.py @@ -0,0 +1,80 @@ +"""Defines the daily loop that runs the Dakiny bot functions.""" + +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 logger import ocd_logger +from main import birthday_list +from settings import settings + +if TYPE_CHECKING: + import nextcord + + +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: + """Run daily repeated functions.""" + self.update_birthdays() + + 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() + + 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() + + 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() + if (guild := self.bot.get_guild(settings.params.guild_id)) is None: + ocd_logger.warning("Guild not found in bot.") + return + if (announce_channel := guild.get_channel(settings.channel_ids.general)) is None or not isinstance( + announce_channel, nextcord.TextChannel + ): + ocd_logger.warning("Set general channel isn't found or isn't a text channel.") + return + + cakedays: list[nextcord.Member] = [ + human + for human in guild.humans + if human.joined_at is not None and 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) diff --git a/logger.py b/logger.py new file mode 100644 index 0000000..c593446 --- /dev/null +++ b/logger.py @@ -0,0 +1,10 @@ +"""Creates logger for Agon bot.""" + +import logging +import sys + +ocd_logger = logging.getLogger("default") +ocd_logger.setLevel(logging.INFO) +handler = logging.StreamHandler(sys.stdout) +handler.setFormatter(logging.Formatter("%(asctime)s:%(levelname)s:%(name)s: %(message)s")) +ocd_logger.addHandler(handler) diff --git a/main.py b/main.py index ee38aad..00f6978 100644 --- a/main.py +++ b/main.py @@ -28,7 +28,7 @@ def main() -> None: # intents.guilds = True # To enable the members privileged intent: - # intents.members = True + intents.members = True bot = commands.Bot(intents=intents) diff --git a/settings.py b/settings.py index 5acc9c3..809154e 100644 --- a/settings.py +++ b/settings.py @@ -7,17 +7,26 @@ from dataclass_wizard import YAMLWizard CONFIG_LOCATION = "conf/config.yaml" +@dataclass +class ChannelIds: + """Class holds all channel ids needed by bot.""" + + general: int + + @dataclass class Params: """Class defines start parameters that shouldn't be changed.""" discord_token: str + guild_id: int @dataclass class Settings(YAMLWizard): """Class defines all configs required for off-cooldown-bot.""" + channel_ids: ChannelIds params: Params def save(self) -> None: