Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
09958c31ad | ||
|
|
0f3596692f | ||
|
|
def9c4aa79 | ||
|
|
af3b8e9e69 | ||
|
|
a5655c4d6b | ||
|
|
a277566654 | ||
|
|
6adb98111b | ||
|
|
4d96aebeb1 | ||
|
|
d38f56ff41 | ||
|
|
95fb9e289b | ||
|
|
3bcd1a3a01 | ||
|
|
6c57644eb0 |
Vendored
+1
-4
@@ -9,10 +9,7 @@
|
|||||||
"type": "debugpy",
|
"type": "debugpy",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"module": "dakiny_bot",
|
"module": "dakiny_bot",
|
||||||
"justMyCode": true,
|
"justMyCode": true
|
||||||
"env": {
|
|
||||||
"DEBUG": "true"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -1,10 +1,10 @@
|
|||||||
FROM python:3.13.7-slim
|
FROM python:3.13.3-slim
|
||||||
|
|
||||||
# install dependencies
|
# install dependencies
|
||||||
RUN apt-get update && apt-get -y upgrade && apt-get -y install git sqlite3
|
RUN apt-get update && apt-get -y upgrade && apt-get -y install git sqlite3
|
||||||
|
|
||||||
# install uv
|
# install uv
|
||||||
COPY --from=ghcr.io/astral-sh/uv:0.9.28 /uv /uvx /bin/
|
COPY --from=ghcr.io/astral-sh/uv:0.6.14 /uv /uvx /bin/
|
||||||
|
|
||||||
COPY . /home
|
COPY . /home
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1,7 @@
|
|||||||
"""Empty init file for dakiny_bot module."""
|
"""Empty init file for dakiny_bot module."""
|
||||||
|
|
||||||
|
from dakiny_bot.dakiny_settings import CONFIG_LOCATION, DakinySettings, settings
|
||||||
|
|
||||||
|
if settings is None:
|
||||||
|
tmp = DakinySettings.from_yaml_file(CONFIG_LOCATION)
|
||||||
|
settings = tmp[0] if isinstance(tmp, list) else tmp
|
||||||
|
|||||||
+8
-1
@@ -1,5 +1,6 @@
|
|||||||
"""Main file for bot."""
|
"""Main file for bot."""
|
||||||
|
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import nextcord
|
import nextcord
|
||||||
@@ -11,10 +12,16 @@ from dakiny_bot.classes.voting_highcouncil_views import VoteHighcouncilView
|
|||||||
from dakiny_bot.classes.voting_views import VoteView
|
from dakiny_bot.classes.voting_views import VoteView
|
||||||
from dakiny_bot.database import create_database
|
from dakiny_bot.database import create_database
|
||||||
|
|
||||||
from .DakinySettings import settings
|
from .dakiny_settings import settings
|
||||||
|
|
||||||
birthday_list: list[int] = []
|
birthday_list: list[int] = []
|
||||||
|
|
||||||
|
logger = logging.getLogger("nextcord")
|
||||||
|
logger.setLevel(logging.DEBUG)
|
||||||
|
handler = logging.FileHandler(filename="nextcord.log", encoding="utf-8", mode="w")
|
||||||
|
handler.setFormatter(logging.Formatter("%(asctime)s:%(levelname)s:%(name)s: %(message)s"))
|
||||||
|
logger.addHandler(handler)
|
||||||
|
|
||||||
|
|
||||||
def run() -> None:
|
def run() -> None:
|
||||||
"""Builds and runs bot."""
|
"""Builds and runs bot."""
|
||||||
|
|||||||
@@ -9,21 +9,20 @@ from dakiny_bot.classes.nominate_highcouncil_views import NominateHighCouncilVie
|
|||||||
from dakiny_bot.classes.nominate_views import NominateView
|
from dakiny_bot.classes.nominate_views import NominateView
|
||||||
from dakiny_bot.classes.voting_highcouncil_views import VoteHighcouncilView
|
from dakiny_bot.classes.voting_highcouncil_views import VoteHighcouncilView
|
||||||
from dakiny_bot.classes.voting_views import VoteView
|
from dakiny_bot.classes.voting_views import VoteView
|
||||||
from dakiny_bot.DakinySettings import settings
|
from dakiny_bot.dakiny_settings import settings
|
||||||
from dakiny_bot.database.sql_data_classes import (
|
from dakiny_bot.database.sql_data_classes import (
|
||||||
CouncilMembers,
|
CouncilMembers,
|
||||||
ElectionInformation,
|
ElectionInformation,
|
||||||
Votes,
|
Votes,
|
||||||
engine,
|
engine,
|
||||||
)
|
)
|
||||||
from dakiny_bot.logger import dakiny_logger
|
from dakiny_bot.election_utils import get_datetime
|
||||||
from dakiny_bot.utils import (
|
from dakiny_bot.utils import (
|
||||||
TEMPLATES_DIR,
|
TEMPLATES_DIR,
|
||||||
VoteType,
|
VoteType,
|
||||||
build_highcouncil_votes_str,
|
build_highcouncil_votes_str,
|
||||||
build_votes_str,
|
build_votes_str,
|
||||||
edit_clan_council_info,
|
edit_clan_council_info,
|
||||||
get_datetime,
|
|
||||||
get_election_name,
|
get_election_name,
|
||||||
)
|
)
|
||||||
from sqlalchemy import select, true, update
|
from sqlalchemy import select, true, update
|
||||||
@@ -70,7 +69,7 @@ class Election:
|
|||||||
list[str]: A list of message contents in the correct order. Must be a string because the message is too
|
list[str]: A list of message contents in the correct order. Must be a string because the message is too
|
||||||
long.
|
long.
|
||||||
"""
|
"""
|
||||||
with Path(f"{TEMPLATES_DIR}/election_announcement.txt", encoding="utf8").open() as file:
|
with Path(f"{TEMPLATES_DIR}/election_announcement.txt").open() as file:
|
||||||
message = file.read()
|
message = file.read()
|
||||||
|
|
||||||
next_election = get_datetime(
|
next_election = get_datetime(
|
||||||
@@ -98,7 +97,7 @@ class Election:
|
|||||||
Returns:
|
Returns:
|
||||||
str: Message to send.
|
str: Message to send.
|
||||||
"""
|
"""
|
||||||
with Path(f"{TEMPLATES_DIR}/highcouncil_election.txt", encoding="utf8").open() as file:
|
with Path(f"{TEMPLATES_DIR}/highcouncil_election.txt").open() as file:
|
||||||
message = file.read()
|
message = file.read()
|
||||||
|
|
||||||
return message.format(
|
return message.format(
|
||||||
@@ -116,7 +115,7 @@ class Election:
|
|||||||
Returns:
|
Returns:
|
||||||
str: Message to send.
|
str: Message to send.
|
||||||
"""
|
"""
|
||||||
with Path(f"{TEMPLATES_DIR}/highcouncil_vote.txt", encoding="utf8").open() as file:
|
with Path(f"{TEMPLATES_DIR}/highcouncil_vote.txt").open() as file:
|
||||||
message = file.read()
|
message = file.read()
|
||||||
|
|
||||||
return message.format(
|
return message.format(
|
||||||
@@ -131,7 +130,7 @@ class Election:
|
|||||||
Returns:
|
Returns:
|
||||||
str: Message to send
|
str: Message to send
|
||||||
"""
|
"""
|
||||||
with Path(f"{TEMPLATES_DIR}/vote_start.txt", encoding="utf8").open() as file:
|
with Path(f"{TEMPLATES_DIR}/vote_start.txt").open() as file:
|
||||||
message = file.read()
|
message = file.read()
|
||||||
vote_end: datetime = get_datetime(settings.election_cycles[self.cycle].voting_end)
|
vote_end: datetime = get_datetime(settings.election_cycles[self.cycle].voting_end)
|
||||||
|
|
||||||
@@ -144,29 +143,20 @@ class Election:
|
|||||||
async def check_date(self) -> None:
|
async def check_date(self) -> None:
|
||||||
"""Function checks current date to see if something needs to be run."""
|
"""Function checks current date to see if something needs to be run."""
|
||||||
today = datetime.now(tz=UTC)
|
today = datetime.now(tz=UTC)
|
||||||
dakiny_logger.debug("Checking date.")
|
|
||||||
|
|
||||||
# check all dates if action is needed
|
# check all dates if action is needed
|
||||||
if today.date() == self.election_start.date():
|
if today.date() == self.election_start.date():
|
||||||
dakiny_logger.debug("Election start.")
|
|
||||||
await self.election_start_action()
|
await self.election_start_action()
|
||||||
elif today.date() == self.voting_start.date():
|
elif today.date() == self.voting_start.date():
|
||||||
dakiny_logger.debug("Voting start.")
|
|
||||||
await self.voting_start_action()
|
await self.voting_start_action()
|
||||||
elif (self.voting_end - timedelta(3)).date() <= today.date() < self.voting_end.date():
|
elif (self.voting_end - timedelta(3)).date() <= today.date() < self.voting_end.date():
|
||||||
dakiny_logger.debug("Election reminder.")
|
|
||||||
await self.election_reminder()
|
await self.election_reminder()
|
||||||
elif today.date() == self.voting_end.date():
|
elif today.date() == self.voting_end.date():
|
||||||
dakiny_logger.debug("Voting end.")
|
|
||||||
await self.voting_end_action()
|
await self.voting_end_action()
|
||||||
elif today.date() == self.highcouncil_election_start.date():
|
elif today.date() == self.highcouncil_election_start.date():
|
||||||
dakiny_logger.debug("Highcouncil election start.")
|
|
||||||
await self.highcouncil_election_start_action()
|
await self.highcouncil_election_start_action()
|
||||||
elif today.date() == self.term_start.date():
|
elif today.date() == self.term_start.date():
|
||||||
dakiny_logger.debug("Term start")
|
|
||||||
await self.term_start_action()
|
await self.term_start_action()
|
||||||
else:
|
|
||||||
dakiny_logger.warning("Check date didn't do anything. This should never happen.")
|
|
||||||
|
|
||||||
async def election_start_action(self) -> None:
|
async def election_start_action(self) -> None:
|
||||||
"""Performs required actions for when the election cycle starts."""
|
"""Performs required actions for when the election cycle starts."""
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
"""Contains various views to be used in the high council nomination process."""
|
"""Contains various views to be used in the high council nomination process."""
|
||||||
|
|
||||||
import nextcord
|
import nextcord
|
||||||
from nextcord.interactions import Interaction
|
from dakiny_bot.dakiny_settings import settings
|
||||||
from sqlalchemy import select
|
|
||||||
from sqlalchemy.orm import Session
|
|
||||||
|
|
||||||
from dakiny_bot.DakinySettings import settings
|
|
||||||
from dakiny_bot.database.sql_data_classes import CouncilMembers, engine
|
from dakiny_bot.database.sql_data_classes import CouncilMembers, engine
|
||||||
from dakiny_bot.utils import (
|
from dakiny_bot.utils import (
|
||||||
NOMINEE_SPLITTER,
|
NOMINEE_SPLITTER,
|
||||||
@@ -13,6 +9,9 @@ from dakiny_bot.utils import (
|
|||||||
decorate_dm_nomination,
|
decorate_dm_nomination,
|
||||||
get_election_name,
|
get_election_name,
|
||||||
)
|
)
|
||||||
|
from nextcord.interactions import Interaction
|
||||||
|
from sqlalchemy import select
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
|
||||||
class NominateDMModal(nextcord.ui.Modal):
|
class NominateDMModal(nextcord.ui.Modal):
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
"""Contains various views to be used in the nomination process."""
|
"""Contains various views to be used in the nomination process."""
|
||||||
|
|
||||||
import nextcord
|
import nextcord
|
||||||
|
from dakiny_bot.dakiny_settings import settings
|
||||||
|
from dakiny_bot.database.sql_data_classes import CouncilMembers, engine
|
||||||
|
from dakiny_bot.utils import NOMINEE_SPLITTER, decorate_nomination, get_election_name
|
||||||
from nextcord.interactions import Interaction
|
from nextcord.interactions import Interaction
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from dakiny_bot.DakinySettings import settings
|
|
||||||
from dakiny_bot.database.sql_data_classes import CouncilMembers, engine
|
|
||||||
from dakiny_bot.utils import NOMINEE_SPLITTER, decorate_nomination, get_election_name
|
|
||||||
|
|
||||||
|
|
||||||
class NominateModal(nextcord.ui.Modal):
|
class NominateModal(nextcord.ui.Modal):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
"""Contains various views to be used while voting for high council. Controls the logic when performing the vote."""
|
"""Contains various views to be used while voting for high council. Controls the logic when performing the vote."""
|
||||||
|
|
||||||
import nextcord
|
import nextcord
|
||||||
from nextcord import SelectOption
|
from dakiny_bot.dakiny_settings import settings
|
||||||
from nextcord.interactions import Interaction
|
|
||||||
from sqlalchemy import exc, select, update
|
|
||||||
from sqlalchemy.orm import Session
|
|
||||||
|
|
||||||
from dakiny_bot.DakinySettings import settings
|
|
||||||
from dakiny_bot.database.sql_data_classes import (
|
from dakiny_bot.database.sql_data_classes import (
|
||||||
CouncilMembers,
|
CouncilMembers,
|
||||||
ElectionInformation,
|
ElectionInformation,
|
||||||
@@ -14,6 +9,10 @@ from dakiny_bot.database.sql_data_classes import (
|
|||||||
engine,
|
engine,
|
||||||
)
|
)
|
||||||
from dakiny_bot.utils import VoteType, build_highcouncil_votes_str, get_election_name
|
from dakiny_bot.utils import VoteType, build_highcouncil_votes_str, get_election_name
|
||||||
|
from nextcord import SelectOption
|
||||||
|
from nextcord.interactions import Interaction
|
||||||
|
from sqlalchemy import exc, select, update
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
|
||||||
class VoteSelect(nextcord.ui.StringSelect):
|
class VoteSelect(nextcord.ui.StringSelect):
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
"""Contains various views to be used while voting. Controls the logic when performing the vote."""
|
"""Contains various views to be used while voting. Controls the logic when performing the vote."""
|
||||||
|
|
||||||
import nextcord
|
import nextcord
|
||||||
from nextcord import SelectOption
|
from dakiny_bot.dakiny_settings import settings
|
||||||
from nextcord.interactions import Interaction
|
|
||||||
from sqlalchemy import exc, select, update
|
|
||||||
from sqlalchemy.orm import Session
|
|
||||||
|
|
||||||
from dakiny_bot.DakinySettings import settings
|
|
||||||
from dakiny_bot.database.sql_data_classes import (
|
from dakiny_bot.database.sql_data_classes import (
|
||||||
CouncilMembers,
|
CouncilMembers,
|
||||||
ElectionInformation,
|
ElectionInformation,
|
||||||
@@ -14,6 +9,10 @@ from dakiny_bot.database.sql_data_classes import (
|
|||||||
engine,
|
engine,
|
||||||
)
|
)
|
||||||
from dakiny_bot.utils import VoteType, build_votes_str, get_election_name
|
from dakiny_bot.utils import VoteType, build_votes_str, get_election_name
|
||||||
|
from nextcord import SelectOption
|
||||||
|
from nextcord.interactions import Interaction
|
||||||
|
from sqlalchemy import exc, select, update
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
|
||||||
class VoteSelect(nextcord.ui.StringSelect):
|
class VoteSelect(nextcord.ui.StringSelect):
|
||||||
|
|||||||
@@ -6,12 +6,10 @@ from typing import TYPE_CHECKING
|
|||||||
import uvicorn
|
import uvicorn
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from git import Repo
|
from git import Repo
|
||||||
from git.exc import GitCommandError
|
|
||||||
from nextcord.ext import commands
|
from nextcord.ext import commands
|
||||||
|
|
||||||
from dakiny_bot.classes.gitea_webhook import GiteaWebhook
|
from dakiny_bot.classes.gitea_webhook import GiteaWebhook
|
||||||
from dakiny_bot.DakinySettings import settings
|
from dakiny_bot.dakiny_settings import settings
|
||||||
from dakiny_bot.logger import dakiny_logger
|
|
||||||
from dakiny_bot.utils import REPO_DIR, edit_clan_council_info, update_clan_messages
|
from dakiny_bot.utils import REPO_DIR, edit_clan_council_info, update_clan_messages
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -57,12 +55,7 @@ class ClanMessages(commands.Cog, name="Clan Messages"):
|
|||||||
template_updates = {file.split("/")[1] for file in modified_files if "message_templates" in file}
|
template_updates = {file.split("/")[1] for file in modified_files if "message_templates" in file}
|
||||||
channel_updates = {file.split("/")[1] for file in modified_files if file.split("/")[1] not in template_updates}
|
channel_updates = {file.split("/")[1] for file in modified_files if file.split("/")[1] not in template_updates}
|
||||||
|
|
||||||
try:
|
self.origin.pull()
|
||||||
self.origin.pull()
|
|
||||||
except GitCommandError:
|
|
||||||
# I think the git connection timed out. Get a new one.
|
|
||||||
dakiny_logger.info("Refreshing git connection.")
|
|
||||||
self.repo_setup()
|
|
||||||
guild: nextcord.Guild = self.bot.get_guild(settings.params.guild_id)
|
guild: nextcord.Guild = self.bot.get_guild(settings.params.guild_id)
|
||||||
|
|
||||||
await update_clan_messages(guild, channel_updates)
|
await update_clan_messages(guild, channel_updates)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import nextcord
|
|||||||
from discord import TextInputStyle
|
from discord import TextInputStyle
|
||||||
from nextcord.ext import commands
|
from nextcord.ext import commands
|
||||||
|
|
||||||
from dakiny_bot.DakinySettings import settings
|
from dakiny_bot.dakiny_settings import settings
|
||||||
from dakiny_bot.utils import GUILDS, MAX_DISCORD_MESSAGE_LENGTH, PointType, record_points
|
from dakiny_bot.utils import GUILDS, MAX_DISCORD_MESSAGE_LENGTH, PointType, record_points
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import nextcord
|
|||||||
from nextcord.ext import commands
|
from nextcord.ext import commands
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from dakiny_bot.DakinySettings import settings
|
from dakiny_bot.dakiny_settings import settings
|
||||||
from dakiny_bot.database.sql_data_classes import Presences, engine
|
from dakiny_bot.database.sql_data_classes import Presences, engine
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import nextcord
|
import nextcord
|
||||||
from nextcord.ext import commands
|
from nextcord.ext import commands
|
||||||
|
|
||||||
from dakiny_bot.DakinySettings import settings
|
from dakiny_bot.dakiny_settings import settings
|
||||||
from dakiny_bot.utils import GUILDS
|
from dakiny_bot.utils import GUILDS
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ import nextcord
|
|||||||
from nextcord.ext import application_checks, commands
|
from nextcord.ext import application_checks, commands
|
||||||
|
|
||||||
from dakiny_bot.classes.election import Election
|
from dakiny_bot.classes.election import Election
|
||||||
from dakiny_bot.cogs.loops import daily_loop
|
from dakiny_bot.dakiny_settings import settings
|
||||||
from dakiny_bot.DakinySettings import settings
|
|
||||||
from dakiny_bot.utils import GUILDS, get_active_election_cycle, get_quarter_name
|
from dakiny_bot.utils import GUILDS, get_active_election_cycle, get_quarter_name
|
||||||
|
|
||||||
ME = 206602713511886849
|
ME = 206602713511886849
|
||||||
@@ -120,8 +119,11 @@ class TestCog(commands.Cog, name="Test commands."):
|
|||||||
Args:
|
Args:
|
||||||
interaction (nextcord.Interaction): Interaction with the message.
|
interaction (nextcord.Interaction): Interaction with the message.
|
||||||
"""
|
"""
|
||||||
dl = daily_loop.DailyLoop(self.bot)
|
cycle = Election(
|
||||||
await dl.daily_loop()
|
get_active_election_cycle(),
|
||||||
|
self.bot.get_guild(settings.params.guild_id),
|
||||||
|
)
|
||||||
|
await cycle.check_date()
|
||||||
await interaction.send("Test command sent.", ephemeral=True)
|
await interaction.send("Test command sent.", ephemeral=True)
|
||||||
|
|
||||||
@test.subcommand()
|
@test.subcommand()
|
||||||
|
|||||||
@@ -11,9 +11,8 @@ from sqlalchemy.orm import Session
|
|||||||
|
|
||||||
from dakiny_bot.app import birthday_list
|
from dakiny_bot.app import birthday_list
|
||||||
from dakiny_bot.classes.election import Election
|
from dakiny_bot.classes.election import Election
|
||||||
from dakiny_bot.DakinySettings import settings
|
from dakiny_bot.dakiny_settings import settings
|
||||||
from dakiny_bot.database.sql_data_classes import UserBirthday, engine
|
from dakiny_bot.database.sql_data_classes import UserBirthday, engine
|
||||||
from dakiny_bot.logger import dakiny_logger
|
|
||||||
from dakiny_bot.utils import get_active_election_cycle
|
from dakiny_bot.utils import get_active_election_cycle
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -44,10 +43,8 @@ class DailyLoop(commands.Cog, name="Daily Loop"):
|
|||||||
an active election, runs the check_date() function for the election to perform actions. Finally checks if the
|
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.
|
election is over to delete the current election object.
|
||||||
"""
|
"""
|
||||||
dakiny_logger.debug("Daily loop. Current election: %s.", self.current_election)
|
|
||||||
if self.current_election is None:
|
if self.current_election is None:
|
||||||
self.current_election = self.get_election()
|
self.current_election = self.get_election()
|
||||||
dakiny_logger.debug("Current election: %s.", self.current_election)
|
|
||||||
|
|
||||||
if self.current_election is not None:
|
if self.current_election is not None:
|
||||||
await self.current_election.check_date()
|
await self.current_election.check_date()
|
||||||
@@ -81,17 +78,13 @@ class DailyLoop(commands.Cog, name="Daily Loop"):
|
|||||||
Election: Current election cycle, None if there is no election happening.
|
Election: Current election cycle, None if there is no election happening.
|
||||||
"""
|
"""
|
||||||
cycle = get_active_election_cycle()
|
cycle = get_active_election_cycle()
|
||||||
dakiny_logger.debug("Current cycle: %s.", cycle)
|
|
||||||
|
|
||||||
# prevent exception from using cycle as an Int in an array
|
# prevent exception from using cycle as an Int in an array
|
||||||
if cycle is None:
|
if cycle is None:
|
||||||
dakiny_logger.info("Cycle was None. Returning without an election.")
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
election = Election(cycle, self.bot.get_guild(settings.params.guild_id))
|
election = Election(cycle, self.bot.get_guild(settings.params.guild_id))
|
||||||
|
|
||||||
dakiny_logger.debug("Election active: %s", election.active)
|
|
||||||
|
|
||||||
return election if election.active else None
|
return election if election.active else None
|
||||||
|
|
||||||
def update_birthdays(self) -> None:
|
def update_birthdays(self) -> None:
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import nextcord
|
|||||||
from nextcord.ext import commands
|
from nextcord.ext import commands
|
||||||
|
|
||||||
from dakiny_bot.app import birthday_list
|
from dakiny_bot.app import birthday_list
|
||||||
from dakiny_bot.DakinySettings import settings
|
|
||||||
|
|
||||||
|
|
||||||
class SubmissionListener(commands.Cog, name="Message Listener"):
|
class SubmissionListener(commands.Cog, name="Message Listener"):
|
||||||
@@ -17,15 +16,7 @@ class SubmissionListener(commands.Cog, name="Message Listener"):
|
|||||||
async def on_message(self, message: nextcord.Message) -> None:
|
async def on_message(self, message: nextcord.Message) -> None:
|
||||||
"""Listener for message creation. Adds score and attachments to submission."""
|
"""Listener for message creation. Adds score and attachments to submission."""
|
||||||
# don't want to process any messages from bot.
|
# don't want to process any messages from bot.
|
||||||
if message.author.id == self.bot.user.id: # pyright: ignore[reportOptionalMemberAccess]
|
if message.author.id == self.bot.user.id:
|
||||||
return
|
|
||||||
|
|
||||||
# Ban all members who message in this channel. Everyone in guild is a member, so shouldn't miss a ban
|
|
||||||
# due to message.author not being a nextcord.Member object.
|
|
||||||
if message.channel.id == settings.channel_ids.ban and isinstance(message.author, nextcord.Member):
|
|
||||||
await message.author.ban(
|
|
||||||
delete_message_seconds=30 * 60, reason="Probable spam. The ban channel was messaged."
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
await birthday_action(message)
|
await birthday_action(message)
|
||||||
@@ -40,7 +31,7 @@ def setup(bot: commands.Bot) -> None:
|
|||||||
|
|
||||||
async def birthday_action(message: nextcord.Message) -> None:
|
async def birthday_action(message: nextcord.Message) -> None:
|
||||||
"""
|
"""
|
||||||
Checks a message's author and mentions to see if they are in the birthday list.
|
Checks a message's auther and mentions to see if they are in the birthday list.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
message (nextcord.Message): The message to check for a birthday person.
|
message (nextcord.Message): The message to check for a birthday person.
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ class ChannelIds:
|
|||||||
event_post: int
|
event_post: int
|
||||||
general: int
|
general: int
|
||||||
voting: int
|
voting: int
|
||||||
ban: int
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -84,4 +83,4 @@ class DakinySettings(YAMLWizard):
|
|||||||
self.to_yaml_file(CONFIG_LOCATION)
|
self.to_yaml_file(CONFIG_LOCATION)
|
||||||
|
|
||||||
|
|
||||||
settings = DakinySettings.from_yaml_file(CONFIG_LOCATION)
|
settings: DakinySettings | None = None
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
"""Broken out util functions to avoid importing sql during testing."""
|
||||||
|
|
||||||
|
import calendar
|
||||||
|
from datetime import UTC, datetime
|
||||||
|
|
||||||
|
from dakiny_bot.dakiny_settings import settings
|
||||||
|
|
||||||
|
|
||||||
|
def get_datetime(month_day: str) -> datetime:
|
||||||
|
"""
|
||||||
|
Takes in a string month and day and returns a datetime object of that Month/Day this year.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
month_day (str): Month/Day in that format.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
datetime: Datetime representation of Month/Day.
|
||||||
|
"""
|
||||||
|
today = datetime.now(tz=UTC).date()
|
||||||
|
year = today.year
|
||||||
|
month = int(month_day.split("/")[0])
|
||||||
|
day = int(month_day.split("/")[1])
|
||||||
|
|
||||||
|
# ensure that year rolls over if current month is December and the next month is less than 12.
|
||||||
|
if today.month == calendar.DECEMBER and month < calendar.OCTOBER:
|
||||||
|
year += 1
|
||||||
|
|
||||||
|
if today.month == calendar.JANUARY and month > calendar.MARCH:
|
||||||
|
year -= 1
|
||||||
|
|
||||||
|
return datetime(year, month, day, tzinfo=UTC)
|
||||||
|
|
||||||
|
|
||||||
|
def get_quarter_cycle() -> int | None:
|
||||||
|
"""
|
||||||
|
Returns the index for the current cycle/quarter. Defined as between term start and end.
|
||||||
|
|
||||||
|
Can return None if entire year isn't covered by cycles.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: cycle index for current cycle.
|
||||||
|
"""
|
||||||
|
today = datetime.now(tz=UTC)
|
||||||
|
for cycle, election in enumerate(settings.election_cycles):
|
||||||
|
if (
|
||||||
|
today.date() >= get_datetime(election.term_start).date()
|
||||||
|
and today.date() <= get_datetime(election.term_end).date()
|
||||||
|
):
|
||||||
|
return cycle
|
||||||
|
|
||||||
|
return None
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
"""Creates logger for Dakiny bot."""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
dakiny_logger = logging.getLogger("default")
|
|
||||||
dakiny_logger.setLevel(logging.DEBUG if os.getenv("DEBUG") == "true" else logging.INFO)
|
|
||||||
handler = logging.StreamHandler(sys.stdout)
|
|
||||||
handler.setFormatter(
|
|
||||||
logging.Formatter("%(asctime)s:%(levelname)s [%(filename)s:%(lineno)s - %(funcName)20s() ] %(message)s")
|
|
||||||
)
|
|
||||||
dakiny_logger.addHandler(handler)
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
"""Empty init for testing util functions."""
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
"""Empty init for testing util functions."""
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
"""Tests for get_datetime function."""
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
from freezegun import freeze_time
|
||||||
|
|
||||||
|
from dakiny_bot.election_utils import get_datetime
|
||||||
|
|
||||||
|
|
||||||
|
@freeze_time("2025-6-16")
|
||||||
|
def test_regular_get_datetime() -> None:
|
||||||
|
"""
|
||||||
|
Test a regular get_datetime.
|
||||||
|
|
||||||
|
Expect the returned datetime to be the frozen date.
|
||||||
|
Have two different days to ensure that the date is being returned correctly.
|
||||||
|
"""
|
||||||
|
date_time = get_datetime("6/15")
|
||||||
|
assert date_time == datetime.datetime(2025, 6, 15, tzinfo=datetime.UTC)
|
||||||
|
|
||||||
|
|
||||||
|
@freeze_time("2025-1-16")
|
||||||
|
def test_jan_get_datetime() -> None:
|
||||||
|
"""
|
||||||
|
Test previous year get_datetime.
|
||||||
|
|
||||||
|
Frozen day is in January 2025, datetime string is in December. Expect datetime to be from 2024.
|
||||||
|
"""
|
||||||
|
date_time = get_datetime("12/15")
|
||||||
|
assert date_time == datetime.datetime(2024, 12, 15, tzinfo=datetime.UTC)
|
||||||
|
|
||||||
|
|
||||||
|
@freeze_time("2025-12-16")
|
||||||
|
def test_dec_get_datetime() -> None:
|
||||||
|
"""
|
||||||
|
Test next year get_datetime.
|
||||||
|
|
||||||
|
Frozen day is in December 2025, datetime string is in January. Expect datetime to be from 2026.
|
||||||
|
"""
|
||||||
|
date_time = get_datetime("1/15")
|
||||||
|
assert date_time == datetime.datetime(2026, 1, 15, tzinfo=datetime.UTC)
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
"""Test functions for get_quarter_cycle."""
|
||||||
|
|
||||||
|
from unittest.mock import mock_open, patch
|
||||||
|
|
||||||
|
from freezegun import freeze_time
|
||||||
|
|
||||||
|
from dakiny_bot.dakiny_settings import ChannelIds, DakinySettings, Election, Params, PointSettings, RoleIds
|
||||||
|
from dakiny_bot.election_utils import get_quarter_cycle
|
||||||
|
|
||||||
|
# I only care about term start and term end for this test
|
||||||
|
q1 = Election(True, "", "", "", "", "q1", "4/30", "2/1", "", "") # noqa: FBT003
|
||||||
|
q2 = Election(True, "", "", "", "", "q2", "7/31", "5/1", "", "") # noqa: FBT003
|
||||||
|
q3 = Election(True, "", "", "", "", "q3", "10/31", "8/1", "", "") # noqa: FBT003
|
||||||
|
q4 = Election(True, "", "", "", "", "q4", "1/31", "11/1", "", "") # noqa: FBT003
|
||||||
|
|
||||||
|
# Dummy all the values I don't care about.
|
||||||
|
settings = DakinySettings(
|
||||||
|
ChannelIds(1, 1, 1, 1, 1, 1),
|
||||||
|
[q1, q2, q3, q4],
|
||||||
|
Params("", "", 1),
|
||||||
|
PointSettings(1, 1, 1, 1, 1, True, True), # noqa: FBT003
|
||||||
|
RoleIds(1, 1, 1, 1, 1),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@patch("election_utils.get_quarter_cycle.settings", settings)
|
||||||
|
@patch("builtins.open", new_callable=mock_open)
|
||||||
|
class TestGetQuarterCycle:
|
||||||
|
@freeze_time("2025-2-25")
|
||||||
|
def test_regular_q1(self) -> None:
|
||||||
|
assert get_quarter_cycle() == 0
|
||||||
+11
-58
@@ -1,6 +1,5 @@
|
|||||||
"""File contains util functions."""
|
"""File contains util functions."""
|
||||||
|
|
||||||
import calendar
|
|
||||||
import os
|
import os
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
from datetime import UTC, datetime
|
from datetime import UTC, datetime
|
||||||
@@ -20,9 +19,9 @@ from dakiny_bot.database.sql_data_classes import (
|
|||||||
Votes,
|
Votes,
|
||||||
engine,
|
engine,
|
||||||
)
|
)
|
||||||
from dakiny_bot.logger import dakiny_logger
|
|
||||||
|
|
||||||
from .DakinySettings import settings
|
from .dakiny_settings import settings
|
||||||
|
from .election_utils import get_datetime, get_quarter_cycle
|
||||||
|
|
||||||
NOMINEE_SPLITTER = "========================================\n\n"
|
NOMINEE_SPLITTER = "========================================\n\n"
|
||||||
REPO_DIR = "./dakiny-messages"
|
REPO_DIR = "./dakiny-messages"
|
||||||
@@ -35,33 +34,6 @@ VoteType = StrEnum("VoteType", ["COUNCIL", "DISCORDMANAGER", "CLANLEADER"])
|
|||||||
PointType = StrEnum("PointType", ["CLAN", "EVENT"])
|
PointType = StrEnum("PointType", ["CLAN", "EVENT"])
|
||||||
|
|
||||||
|
|
||||||
def get_datetime(month_day: str) -> datetime:
|
|
||||||
"""
|
|
||||||
Takes in a string month and day and returns a datetime object of that Month/Day this year.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
month_day (str): Month/Day in that format.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
datetime: Datetime representation of Month/Day.
|
|
||||||
"""
|
|
||||||
today = datetime.now(tz=UTC).date()
|
|
||||||
year = today.year
|
|
||||||
month = int(month_day.split("/")[0])
|
|
||||||
day = int(month_day.split("/")[1])
|
|
||||||
|
|
||||||
# ensure that year rolls over if current month is December and the next month is less than 12.
|
|
||||||
if today.month == calendar.DECEMBER and month < calendar.OCTOBER:
|
|
||||||
year += 1
|
|
||||||
|
|
||||||
if today.month == calendar.JANUARY and month > calendar.MARCH:
|
|
||||||
year -= 1
|
|
||||||
try:
|
|
||||||
return datetime(year, month, day, tzinfo=UTC)
|
|
||||||
except ValueError:
|
|
||||||
dakiny_logger.warning("Invalid month day: %s.", month_day)
|
|
||||||
|
|
||||||
|
|
||||||
def get_election_name() -> str:
|
def get_election_name() -> str:
|
||||||
"""
|
"""
|
||||||
Builds the unique name of the current election cycle.
|
Builds the unique name of the current election cycle.
|
||||||
@@ -69,7 +41,10 @@ def get_election_name() -> str:
|
|||||||
Returns:
|
Returns:
|
||||||
str: the name of the election cycle
|
str: the name of the election cycle
|
||||||
"""
|
"""
|
||||||
cycle = get_active_election_cycle()
|
if cycle := get_active_election_cycle() is None:
|
||||||
|
# undefined behavior
|
||||||
|
print("Did not find an active election cycle. Undefined behavior with return string.") # noqa: T201
|
||||||
|
return "No-active-election-cycle"
|
||||||
current_cycle = settings.election_cycles[cycle]
|
current_cycle = settings.election_cycles[cycle]
|
||||||
return f"vote-{get_datetime(current_cycle.term_start).year}-{current_cycle.name}"
|
return f"vote-{get_datetime(current_cycle.term_start).year}-{current_cycle.name}"
|
||||||
|
|
||||||
@@ -81,13 +56,16 @@ def get_quarter_name() -> str:
|
|||||||
Returns:
|
Returns:
|
||||||
str: the name of the cycle.
|
str: the name of the cycle.
|
||||||
"""
|
"""
|
||||||
cycle = get_quarter_cycle()
|
if cycle := get_quarter_cycle() is None:
|
||||||
|
# undefined behavior
|
||||||
|
print("Did not find a quarter cycle. Undefined behavior with return string.") # noqa: T201
|
||||||
|
return "no-quarter-cycle"
|
||||||
current_cycle = settings.election_cycles[cycle]
|
current_cycle = settings.election_cycles[cycle]
|
||||||
|
|
||||||
return f"{get_datetime(current_cycle.term_start).year}-{current_cycle.name}"
|
return f"{get_datetime(current_cycle.term_start).year}-{current_cycle.name}"
|
||||||
|
|
||||||
|
|
||||||
def get_active_election_cycle() -> int:
|
def get_active_election_cycle() -> int | None:
|
||||||
"""
|
"""
|
||||||
Returns index for active election. An election is considered active between the election start and the term start.
|
Returns index for active election. An election is considered active between the election start and the term start.
|
||||||
|
|
||||||
@@ -95,43 +73,18 @@ def get_active_election_cycle() -> int:
|
|||||||
int: cycle index for active election. None for no active cycle
|
int: cycle index for active election. None for no active cycle
|
||||||
"""
|
"""
|
||||||
today = datetime.now(tz=UTC)
|
today = datetime.now(tz=UTC)
|
||||||
dakiny_logger.debug("Today is %s", today)
|
|
||||||
|
|
||||||
for cycle, election in enumerate(settings.election_cycles):
|
for cycle, election in enumerate(settings.election_cycles):
|
||||||
if (
|
if (
|
||||||
today.date() >= get_datetime(election.election_start).date()
|
today.date() >= get_datetime(election.election_start).date()
|
||||||
and today.date() <= get_datetime(election.term_start).date()
|
and today.date() <= get_datetime(election.term_start).date()
|
||||||
):
|
):
|
||||||
dakiny_logger.debug("Cycle is %d", cycle)
|
|
||||||
return cycle
|
return cycle
|
||||||
|
|
||||||
# It isn't a current election, get the quarter
|
# It isn't a current election, get the quarter
|
||||||
return get_quarter_cycle()
|
return get_quarter_cycle()
|
||||||
|
|
||||||
|
|
||||||
def get_quarter_cycle() -> int:
|
|
||||||
"""
|
|
||||||
Returns the index for the current cycle/quarter. Defined as between term start and end.
|
|
||||||
|
|
||||||
Can return None if entire year isn't covered by cycles.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
int: cycle index for current cycle.
|
|
||||||
"""
|
|
||||||
today = datetime.now(tz=UTC)
|
|
||||||
dakiny_logger.debug("Today is %s", today)
|
|
||||||
for cycle, election in enumerate(settings.election_cycles):
|
|
||||||
if (
|
|
||||||
today.date() >= get_datetime(election.term_start).date()
|
|
||||||
and today.date() <= get_datetime(election.term_end).date()
|
|
||||||
):
|
|
||||||
dakiny_logger.debug("Cycle is %d", cycle)
|
|
||||||
return cycle
|
|
||||||
|
|
||||||
dakiny_logger.debug("Quarter cycle is None.")
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def build_votes_str(guild: nextcord.Guild) -> str:
|
def build_votes_str(guild: nextcord.Guild) -> str:
|
||||||
"""
|
"""
|
||||||
Creates a string that contains all nominees and their votes to be sent as a discord message.
|
Creates a string that contains all nominees and their votes to be sent as a discord message.
|
||||||
|
|||||||
+12
-5
@@ -1,18 +1,17 @@
|
|||||||
[project]
|
[project]
|
||||||
authors = [{ name = "imcompiling", email = "[email protected]" }]
|
authors = [{ name = "imcompiling", email = "[email protected]" }]
|
||||||
requires-python = "<4.0,>=3.13"
|
requires-python = "<4.0,>=3.12"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"gitpython<4.0.0,>=3.1.43",
|
"gitpython<4.0.0,>=3.1.43",
|
||||||
"fastapi<1.0.0,>=0.111.0",
|
"fastapi<1.0.0,>=0.111.0",
|
||||||
"nextcord<3.0.0,>=2.6.0",
|
"nextcord<3.0.0,>=2.6.0",
|
||||||
"pandas<3.1.0,>=3.0.6",
|
"pandas<3.0.0,>=2.2.2",
|
||||||
"pytz<2025.3,>=2025.2",
|
"pytz<2025.3,>=2025.2",
|
||||||
"sqlalchemy<3.0.0,>=2.0.30",
|
"sqlalchemy<3.0.0,>=2.0.30",
|
||||||
"aiofiles<24.0.0,>=23.2.1",
|
"aiofiles<24.0.0,>=23.2.1",
|
||||||
"setuptools<80.10.3,>=80.10.2",
|
"setuptools<71.0.0,>=70.0.0",
|
||||||
"dataclass-wizard[yaml]>=0.22.3,<1.0.0",
|
"dataclass-wizard[yaml]>=0.22.3,<1.0.0",
|
||||||
"uvicorn>=0.34.0",
|
"uvicorn>=0.34.0",
|
||||||
"audioop-lts>=0.2.1",
|
|
||||||
]
|
]
|
||||||
name = "dakiny-bot"
|
name = "dakiny-bot"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -20,7 +19,12 @@ description = "Dakiny's Discord Bot."
|
|||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
dev = ["ruff<1.0.0,>=0.6.9"]
|
dev = [
|
||||||
|
"freezegun>=1.5.1",
|
||||||
|
"pytest>=8.3.5",
|
||||||
|
"pytest-cov>=6.1.1",
|
||||||
|
"ruff<1.0.0,>=0.6.9",
|
||||||
|
]
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
# Exclude a variety of commonly ignored directories.
|
# Exclude a variety of commonly ignored directories.
|
||||||
@@ -127,3 +131,6 @@ docstring-code-format = true
|
|||||||
# This only has an effect when the `docstring-code-format` setting is
|
# This only has an effect when the `docstring-code-format` setting is
|
||||||
# enabled.
|
# enabled.
|
||||||
docstring-code-line-length = "dynamic"
|
docstring-code-line-length = "dynamic"
|
||||||
|
|
||||||
|
[tool.ruff.lint.per-file-ignores]
|
||||||
|
"**/{tests}/*" = ["ANN001", "ANN201", "S101", "PLR2004"]
|
||||||
|
|||||||
+3
-12
@@ -1,11 +1,8 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
"lockFileMaintenance": {
|
"lockFileMaintenance": {
|
||||||
"automerge": true
|
"automerge": true,
|
||||||
},
|
"enabled": true
|
||||||
"major": {
|
|
||||||
"automerge": false,
|
|
||||||
"automergeType": "pr"
|
|
||||||
},
|
},
|
||||||
"minor": {
|
"minor": {
|
||||||
"automerge": true,
|
"automerge": true,
|
||||||
@@ -19,11 +16,5 @@
|
|||||||
"automerge": true,
|
"automerge": true,
|
||||||
"automergeType": "branch"
|
"automergeType": "branch"
|
||||||
},
|
},
|
||||||
"ignoreTests": true,
|
"ignoreTests": true
|
||||||
"dockerfile": {
|
|
||||||
"minor": {
|
|
||||||
"automerge": false,
|
|
||||||
"automergeType": "pr"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user