diff --git a/README.md b/README.md index f2e9f20..8d3153a 100644 --- a/README.md +++ b/README.md @@ -1,50 +1,5 @@ -# Discord Bot Template +# Dakiny Bot -[![Discord](https://img.shields.io/discord/819650821314052106?color=7289DA&logo=discord&logoColor=white)](https://discord.gg/fPrdqh3Zfu "Dev Pro Tips Discussion & Support Server") -[![Powered by Nextcord](https://custom-icon-badges.herokuapp.com/badge/-Powered%20by%20Nextcord-0d1620?logo=nextcord)](https://github.com/nextcord/nextcord "Powered by Nextcord Python API Wrapper") - -This repo is a template for easy creation of maintainable Python Discord bots. - -The library used is [Nextcord](https://github.com/nextcord/nextcord), a maintained fork of Discord.py. - -Nextcord documentation: https://nextcord.readthedocs.io/en/latest/ - -📺 Python Discord Tutorial: https://www.youtube.com/playlist?list=PL9YUC9AZJGFG6larkQJYio_f0V-O1NRjy - -## How to use - -Click "use this template" at the top of this repo and follow the instructions, or alternatively, initialize a git repo and copy the template files into the directory. - -```bash -# Create a new folder (replace my-discord-bot with your bot's name) -mkdir my-discord-bot && cd my-discord-bot -# Initialize the folder as a git repository and copy the template files -git init -cp ../discord-bot-template/* . -``` - -## Environment variables - -To run your bot, you'll need a token and other secrets set in a `.env` file. - -Create a file called `.env` and place it in the root of your project. - -(You can do this by creating a copy of `.env.sample` and renaming it to `.env`) - -The contents should look something like this (where the part after `=` is the token you received from the Discord Developer Portal) - -``` -DISCORD_TOKEN=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -``` - -If you require additional API keys or variables specific to an enviroment, they should also be added here. You can access them by adding a line such as `GUILD_ID = os.getenv("GUILD_ID", "")` to `config.py`. - -## Heroku - -`runtime.txt` and `Procfile` are used for Heroku configuration and can be deleted in case you do not plan on hosting there. - -Heroku setup tutorial: https://www.youtube.com/watch?v=EreE-0hQibM - -## IDE Configuration - -IDE config such as the `.vscode` folder do not normally belong on GitHub since they are often specific to a particular environment. To make sure GitHub will ignore the `.vscode` folder uncomment the line at the end of the `.gitignore`. +Bot assumes that it always did the step before it. If the step before an action didn't happen, behavior is undefined. +Object creation in the middle of a cycle is only there for restarting the bot. All relevant information should already +exist in the database. \ No newline at end of file diff --git a/classes/election_cycle.py b/classes/election_cycle.py index c2e4a60..3ae32b2 100644 --- a/classes/election_cycle.py +++ b/classes/election_cycle.py @@ -5,7 +5,6 @@ Contains the election cycle class from datetime import datetime, timedelta import nextcord -from nextcord.ext import commands from sqlalchemy import select from sqlalchemy.orm import Session @@ -20,7 +19,7 @@ class ElectionCycle: the cycle. """ - def __init__(self, cycle: int, bot: commands.Bot) -> None: + def __init__(self, cycle: int, config: DakinyConfig, guild: nextcord.Guild) -> None: """ Generates all dates relevant to the election cycle from input election start, term start and end dates. @@ -30,7 +29,7 @@ class ElectionCycle: term_end (datetime): Date the term being elected ends. bot: the discord bot instance """ - self.config: DakinyConfig = bot.config + self.config: DakinyConfig = config self.cycle = cycle self.elecion_cycle = self.config.election_cycles[self.cycle] self.election_start: datetime = get_datetime(self.elecion_cycle.election_start) @@ -41,7 +40,8 @@ class ElectionCycle: self.highcouncil_election_announce: datetime = self.voting_end + timedelta(days=1) self.highcouncil_election_start: datetime = self.highcouncil_election_announce + timedelta(days=3) self.highcouncil_election_end: datetime = self.term_start - timedelta(days=1) - self.guild: nextcord.Guild = bot.get_guild(self.config.params.guild_id) + self.guild: nextcord.Guild = guild + self.election_over: bool = False def build_election_announcement(self) -> list[str]: """ @@ -200,6 +200,8 @@ class ElectionCycle: """ 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) diff --git a/cogs/loops/daily_loop.py b/cogs/loops/daily_loop.py index eed954b..372f86c 100644 --- a/cogs/loops/daily_loop.py +++ b/cogs/loops/daily_loop.py @@ -1,13 +1,15 @@ """ -_summary_ +Defines the daily loop that runs the Dakiny bot functions. """ -from datetime import time +from datetime import datetime, time from nextcord.ext import commands, tasks from pytz import timezone from classes.election_cycle import ElectionCycle +from config import DakinyConfig +from utils import get_datetime class DailyLoop(commands.Cog, name="Daily Loop"): @@ -17,8 +19,7 @@ class DailyLoop(commands.Cog, name="Daily Loop"): def __init__(self, bot: commands.Bot) -> None: self.bot = bot - # TODO need to figure out how to determine the current cycle - self.current_election = ElectionCycle(0, self.bot) + self.current_election = self.get_current_cycle() @commands.Cog.listener() async def on_ready(self): @@ -27,11 +28,42 @@ class DailyLoop(commands.Cog, name="Daily Loop"): """ await self.bot.wait_until_ready() # pylint doesn't recognize that periodic_update_guild_data is subclassed from tasks.loop. - await self.do_thing.start() # pylint: disable=no-member + await self.daily_loop.start() # pylint: disable=no-member @tasks.loop(time=time(hour=0, tzinfo=timezone("GMT"))) - async def do_thing(self): - """ """ - print("loop") - await self.current_election.check_date() - # TODO need to figure out how to change the election cycle after current one is over + 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_current_cycle() + + if self.current_election is not None: + await self.current_election.check_date() + if self.current_election.election_over: + self.current_election = None + + def get_current_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. + """ + + today = datetime.today() + config: DakinyConfig = self.bot.config + current_election = None + + for cycle, election in enumerate(config.election_cycles): + if ( + today.date() > get_datetime(election.election_start).date() + and today.date() < get_datetime(election.term_start).date() + ): + current_election = ElectionCycle(cycle, config, self.bot.get_guild(config.params.guild_id)) + + return current_election