Compare commits

..
Author SHA1 Message Date
renovate-bot 2c4de96c4b Update dependency dataclass-wizard to v1 2026-07-05 00:01:20 +00:00
renovate-bot aaef1f249f Update ghcr.io/astral-sh/uv Docker tag to v0.9.28
build_latest / build (push) Successful in 47s
2026-02-01 00:01:18 +00:00
renovate-bot 4936013697 Update dependency setuptools to <80.10.3,>=80.10.2
build_latest / build (push) Has been cancelled
2026-02-01 00:01:11 +00:00
renovate-bot 945f5766d5 Update dependency setuptools to <80.10.2,>=80.10.1
build_latest / build (push) Successful in 1m25s
2026-01-25 00:01:57 +00:00
renovate-bot 8727c0d6f6 Update ghcr.io/astral-sh/uv Docker tag to v0.9.26
build_latest / build (push) Successful in 48s
2026-01-18 00:00:53 +00:00
renovate-bot 64e2c10b39 Update ghcr.io/astral-sh/uv Docker tag to v0.9.24
build_latest / build (push) Successful in 42s
2026-01-11 00:00:50 +00:00
renovate-bot 3d44c5c905 Update ghcr.io/astral-sh/uv Docker tag to v0.9.21
build_latest / build (push) Successful in 1m4s
2026-01-04 00:01:02 +00:00
renovate-bot 6653bb9bdd Update ghcr.io/astral-sh/uv Docker tag to v0.9.18
build_latest / build (push) Successful in 43s
2025-12-21 00:00:56 +00:00
renovate-bot f5b45b8cab Update ghcr.io/astral-sh/uv Docker tag to v0.9.17
build_latest / build (push) Successful in 45s
2025-12-14 00:00:58 +00:00
renovate-bot 412a81db6e Update ghcr.io/astral-sh/uv Docker tag to v0.9.16
build_latest / build (push) Successful in 49s
2025-12-07 00:00:58 +00:00
renovate-bot 8069176128 Update ghcr.io/astral-sh/uv Docker tag to v0.9.13
build_latest / build (push) Successful in 46s
2025-11-30 00:00:56 +00:00
renovate-bot 5b5e699f7a Update ghcr.io/astral-sh/uv Docker tag to v0.9.11
build_latest / build (push) Successful in 36s
2025-11-23 00:00:46 +00:00
renovate-bot 3b0535663a Update ghcr.io/astral-sh/uv Docker tag to v0.9.9
build_latest / build (push) Successful in 40s
2025-11-16 00:00:45 +00:00
renovate-bot 6fd61c79cc Update ghcr.io/astral-sh/uv Docker tag to v0.9.8
build_latest / build (push) Successful in 40s
2025-11-09 00:00:46 +00:00
imcompiling c2c2b132e1 Merge pull request 'Update ghcr.io/astral-sh/uv Docker tag to v0.9.5' (#107) from renovate/ghcr.io-astral-sh-uv-0.x into main
build_latest / build (push) Successful in 42s
Reviewed-on: #107
2025-11-01 17:01:06 -04:00
renovate-bot 66f3d797fd Update ghcr.io/astral-sh/uv Docker tag to v0.9.5 2025-10-26 00:00:38 +00:00
renovate-bot ccc489dbaf Update ghcr.io/astral-sh/uv Docker tag to v0.8.23
build_latest / build (push) Successful in 55s
2025-10-05 04:01:27 +00:00
renovate-bot d0961bb9f0 Update ghcr.io/astral-sh/uv Docker tag to v0.8.22
build_latest / build (push) Successful in 36s
2025-09-28 04:01:28 +00:00
renovate-bot 83b11206da Update ghcr.io/astral-sh/uv Docker tag to v0.8.19
build_latest / build (push) Successful in 41s
2025-09-21 04:01:29 +00:00
renovate-bot 6f28f09b66 Update ghcr.io/astral-sh/uv Docker tag to v0.8.17
build_latest / build (push) Successful in 1m1s
2025-09-14 04:01:27 +00:00
renovate-bot e1508e6f31 Update ghcr.io/astral-sh/uv Docker tag to v0.8.15
build_latest / build (push) Successful in 42s
2025-09-07 04:01:27 +00:00
renovate-bot be88f1e179 Update ghcr.io/astral-sh/uv Docker tag to v0.8.14
build_latest / build (push) Successful in 1m28s
2025-08-31 04:02:06 +00:00
renovate-bot ae64ab6dde Update python Docker tag to v3.13.7
build_latest / build (push) Successful in 59s
2025-08-17 04:00:55 +00:00
renovate-bot a76df955ec Update ghcr.io/astral-sh/uv Docker tag to v0.8.11
build_latest / build (push) Waiting to run
2025-08-17 04:00:49 +00:00
renovate-bot 2473d258ff Update python Docker tag to v3.13.6
build_latest / build (push) Successful in 58s
2025-08-10 04:01:01 +00:00
renovate-bot 9c94704fbd Update ghcr.io/astral-sh/uv Docker tag to v0.8.8
build_latest / build (push) Waiting to run
2025-08-10 04:00:54 +00:00
renovate-bot 0c62678797 Update ghcr.io/astral-sh/uv Docker tag to v0.8.4
build_latest / build (push) Successful in 40s
2025-08-03 04:01:12 +00:00
renovate-bot 0528eea5cd Update ghcr.io/astral-sh/uv Docker tag to v0.8.3
build_latest / build (push) Successful in 40s
2025-07-27 04:00:44 +00:00
imcompiling 2836237b76 Merge pull request 'Update ghcr.io/astral-sh/uv Docker tag to v0.8.0' (#104) from renovate/ghcr.io-astral-sh-uv-0.x into main
build_latest / build (push) Successful in 37s
Reviewed-on: #104
Reviewed-by: imcompiling <[email protected]>
2025-07-21 09:15:20 -04:00
renovate-bot 4c741136cc Update ghcr.io/astral-sh/uv Docker tag to v0.8.0 2025-07-20 04:00:49 +00:00
imcompiling b221f1e438 Feat: Update the ban message.
build_develop / build (push) Successful in 37s
build_latest / build (push) Successful in 21s
build_release / build (push) Successful in 22s
For some reason, the user's name was None. I'm guessing because they were
banned and are no longer a member or something. Don't need to record the member's
name anyway though, as its already a part of the audit log.
2025-07-16 21:24:03 -04:00
imcompiling 1b08ba1355 Feat: Set delete messages to 30 minutes.
build_develop / build (push) Successful in 32s
2025-07-16 16:03:26 -04:00
imcompiling e191df1a38 Fix: spelling and ignore pylance warning.
build_develop / build (push) Successful in 32s
2025-07-16 15:57:31 -04:00
imcompiling e35a2a8c8d Feat: Add autoban
build_develop / build (push) Successful in 38s
2025-07-16 15:55:09 -04:00
imcompiling d7046e4595 Add: config for ban channel id. 2025-07-16 15:45:35 -04:00
23 changed files with 101 additions and 290 deletions
+1 -2
View File
@@ -9,7 +9,6 @@
"Dakiny", "Dakiny",
"DISCORDMANAGER", "DISCORDMANAGER",
"highcouncil", "highcouncil",
"nextcord", "nextcord"
"ondelete"
] ]
} }
+2 -2
View File
@@ -1,10 +1,10 @@
FROM python:3.13.5-slim FROM python:3.13.7-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.7.20 /uv /uvx /bin/ COPY --from=ghcr.io/astral-sh/uv:0.9.28 /uv /uvx /bin/
COPY . /home COPY . /home
@@ -17,6 +17,7 @@ class ChannelIds:
event_post: int event_post: int
general: int general: int
voting: int voting: int
ban: int
@dataclass @dataclass
@@ -83,5 +84,4 @@ class DakinySettings(YAMLWizard):
self.to_yaml_file(CONFIG_LOCATION) self.to_yaml_file(CONFIG_LOCATION)
tmp = DakinySettings.from_yaml_file(CONFIG_LOCATION) settings = DakinySettings.from_yaml_file(CONFIG_LOCATION)
settings: DakinySettings = tmp[0] if isinstance(tmp, list) else tmp
+12 -18
View File
@@ -1,6 +1,6 @@
"""Main file for bot.""" """Main file for bot."""
import pathlib import os
import nextcord import nextcord
from nextcord.ext import commands from nextcord.ext import commands
@@ -9,9 +9,9 @@ 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.logger import dakiny_logger from dakiny_bot.database import create_database
from .dakiny_settings import settings from .DakinySettings import settings
birthday_list: list[int] = [] birthday_list: list[int] = []
@@ -31,25 +31,19 @@ def run() -> None:
bot = commands.Bot(intents=intents) bot = commands.Bot(intents=intents)
# Get the modules of all cogs whose directory structure is ./cogs/<module_name> # Get the modules of all cogs whose directory structure is ./cogs/<module_name>
for folder in pathlib.Path("dakiny_bot/cogs").iterdir(): for folder in os.listdir("dakiny_bot/cogs"):
bot.load_extension(f"dakiny_bot.cogs.{folder.name}") bot.load_extension(f"dakiny_bot.cogs.{folder}")
@bot.listen() @bot.listen()
async def on_ready() -> None: async def on_ready() -> None:
"""When Discord is connected.""" """When Discord is connected."""
if bot.user is None: assert bot.user is not None # noqa: S101 documentation does this
dakiny_logger.error("Discord connection failed.") create_database # noqa: B018
return bot.add_view(NominateView(bot.get_guild(settings.params.guild_id)))
bot.add_view(NominateHighCouncilView(bot.get_guild(settings.params.guild_id)))
if (guild := bot.get_guild(settings.params.guild_id)) is None: bot.add_view(VoteView(bot.get_guild(settings.params.guild_id)))
dakiny_logger.error("Could not find Dakiny guild object.") bot.add_view(VoteHighcouncilView(bot.get_guild(settings.params.guild_id)))
return print(f"{bot.user.name} has connected to Discord!") # noqa: T201 TODO make logging to print my stuff to console
bot.add_view(NominateView(guild))
bot.add_view(NominateHighCouncilView(guild))
bot.add_view(VoteView(guild))
bot.add_view(VoteHighcouncilView(guild))
dakiny_logger.info("%s has connected to Discord!", bot.user.name)
# Run Discord bot # Run Discord bot
bot.run(settings.params.discord_token) bot.run(settings.params.discord_token)
+1 -1
View File
@@ -9,7 +9,7 @@ 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.dakiny_settings import settings 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,
@@ -1,7 +1,11 @@
"""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 dakiny_bot.dakiny_settings import settings from nextcord.interactions import Interaction
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,
@@ -9,9 +13,6 @@ 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):
+4 -3
View File
@@ -1,13 +1,14 @@
"""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,7 +1,12 @@
"""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 dakiny_bot.dakiny_settings import settings from nextcord import SelectOption
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,
@@ -9,10 +14,6 @@ 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):
+6 -5
View File
@@ -1,7 +1,12 @@
"""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 dakiny_bot.dakiny_settings import settings from nextcord import SelectOption
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,
@@ -9,10 +14,6 @@ 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):
+1 -1
View File
@@ -10,7 +10,7 @@ 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.dakiny_settings import settings from dakiny_bot.DakinySettings import settings
from dakiny_bot.logger import dakiny_logger 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
+1 -1
View File
@@ -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.dakiny_settings import settings from dakiny_bot.DakinySettings 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
+1 -1
View File
@@ -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.dakiny_settings import settings from dakiny_bot.DakinySettings import settings
from dakiny_bot.database.sql_data_classes import Presences, engine from dakiny_bot.database.sql_data_classes import Presences, engine
+1 -1
View File
@@ -3,7 +3,7 @@
import nextcord import nextcord
from nextcord.ext import commands from nextcord.ext import commands
from dakiny_bot.dakiny_settings import settings from dakiny_bot.DakinySettings import settings
from dakiny_bot.utils import GUILDS from dakiny_bot.utils import GUILDS
+1 -1
View File
@@ -5,7 +5,7 @@ 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.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
+1 -1
View File
@@ -11,7 +11,7 @@ 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.dakiny_settings import settings from dakiny_bot.DakinySettings 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.logger import dakiny_logger
from dakiny_bot.utils import get_active_election_cycle from dakiny_bot.utils import get_active_election_cycle
+11 -2
View File
@@ -4,6 +4,7 @@ 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"):
@@ -16,7 +17,15 @@ 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: if message.author.id == self.bot.user.id: # pyright: ignore[reportOptionalMemberAccess]
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)
@@ -31,7 +40,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 auther and mentions to see if they are in the birthday list. Checks a message's author 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.
+13
View File
@@ -0,0 +1,13 @@
"""Small script to create the database if it doesn't already exist."""
import sqlite3
from pathlib import Path
if not Path("database/dakiny.db").exists():
with sqlite3.connect("database/dakiny.db") as con:
cur = con.cursor()
with Path("dakiny_bot/database/database.sql").open() as file:
database_script = file.read()
cur.executescript(database_script)
con.commit()
-13
View File
@@ -1,13 +0,0 @@
"""Simple file to set up the sqlmodel connection to database."""
from sqlmodel import SQLModel, create_engine
from dakiny_bot.logger import dakiny_logger
from . import models # noqa: F401 - Need this import for creating the models.
url = "sqlite:///database/dakiny.db"
dakiny_logger.info("Connected to local dev database.")
engine = create_engine(url)
SQLModel.metadata.create_all(engine)
-214
View File
@@ -1,214 +0,0 @@
"""File holds all model definitions of SQL tables."""
from sqlalchemy.dialects.mysql import BIGINT, BOOLEAN, INTEGER, VARCHAR
from sqlmodel import Column, Field, ForeignKey, Relationship, SQLModel
class ElectionInformation(SQLModel, table=True):
"""
Represents all required information for an election.
Attributes:
name: Name of the election.
thread_id: Thread of where all the election related messages take place.
council_vote_msg_id: Message where the council vote is presented.
high_council_vote_msg_id: Message where the high council vote is presented.
council_nomination_msg_id: Message where council nomination button is held.
high_council_nomination_msg_id: Message where high council nomination button is held.
council_members: List of council members who ran during this election.
votes: All votes cast during this election.
"""
__tablename__: str = "ElectionInformation"
name: str = Field(sa_column=Column("ElectionName", VARCHAR, primary_key=True, unique=True), max_length=30)
thread_id: int = Field(sa_column=Column("ThreadId", BIGINT(unsigned=True), nullable=False))
council_vote_msg_id: int | None = Field(
sa_column=Column("CouncilVoteMsgId", BIGINT(unsigned=True), nullable=True), default=None
)
high_council_vote_msg_id: int | None = Field(
sa_column=Column("HighCouncilVoteMsgId", BIGINT(unsigned=True), nullable=True), default=None
)
council_nomination_msg_id: int | None = Field(
sa_column=Column("CouncilNominationMsgId", BIGINT(unsigned=True), nullable=True), default=None
)
high_council_nomination_msg_id: int | None = Field(
sa_column=Column("HighCouncilNominationMsgId", BIGINT(unsigned=True), nullable=True), default=None
)
council_members: list["CouncilMember"] = Relationship(back_populates="election", cascade_delete=True)
votes: list["Vote"] = Relationship(back_populates="election", cascade_delete=True)
class CouncilMember(SQLModel, table=True):
"""
Represents members involved in a given election.
Records speech message ids for general election and high council position. Booleans record if the user was elected
to a council/high council position.
Attributes:
member_id: User that is running for council's discord id.
speech_msg_id: Message id of user's speech.
elected: Whether the user got elected to council.
discord_manager: Whether the user got elected to the Discord Manager position.
clan_leader: Whether the user got elected to the Clan Leader position.
discord_manager_speech_id: Message id of the user's Discord Manager speech.
clan_leader_speech_id: Message id of the user's Clan Leader speech.
election: The election this user object is for.
votes: A list of votes for this user.
"""
__tablename__: str = "CouncilMembers"
election_name: str = Field(
sa_column=Column(
"ElectionName",
VARCHAR,
ForeignKey("ElectionInformation.ElectionName", ondelete="CASCADE"),
primary_key=True,
),
max_length=30,
)
member_id: int = Field(sa_column=Column("MemberId", BIGINT(unsigned=True), primary_key=True, nullable=False))
speech_msg_id: int = Field(sa_column=Column("SpeechMsgId", BIGINT(unsigned=True), nullable=False))
elected: bool = Field(sa_column=Column("Elected", BOOLEAN), default=False)
discord_manager: bool = Field(sa_column=Column("DiscordManager", BOOLEAN), default=False)
clan_leader: bool = Field(sa_column=Column("ClanLeader", BOOLEAN), default=False)
discord_manager_speech_id: int = Field(
sa_column=Column("DiscordManagerSpeechId", BIGINT(unsigned=True), nullable=True)
)
clan_leader_speech_id: int = Field(sa_column=Column("ClanLeaderSpeechId", BIGINT(unsigned=True), nullable=True))
election: ElectionInformation | None = Relationship(back_populates="council_members")
votes: list["Vote"] = Relationship(back_populates="council_member")
class Vote(SQLModel, table=True):
"""
Represents a vote cast during an election.
Attributes:
election_name: Election this vote was cast in.
voter_id: User id that cast the vote.
voted_for_id: User id that was voted for.
type: Type of election, council, discordmanager, or clanleader.
election: A link to the ElectionInformation object that the vote was cast in.
council_member: A link to the CouncilMember object that the vote is for.
"""
__tablename__: str = "Votes"
election_name: str = Field(
sa_column=Column(
"ElectionName",
VARCHAR,
ForeignKey("ElectionInformation.ElectionName", ondelete="CASCADE"),
primary_key=True,
),
max_length=30,
)
voter_id: int = Field(sa_column=Column("VoterId", BIGINT(unsigned=True), primary_key=True, nullable=False))
# might have to set this foreign key later with alembic migration...
voted_for_id: int | None = Field(
sa_column=Column("VotedForId", BIGINT(unsigned=True), ForeignKey("CouncilMembers.MemberId"), nullable=True)
)
type: str = Field(
sa_column=Column("Type", VARCHAR, primary_key=True, nullable=False, default="council"), max_length=14
)
election: ElectionInformation | None = Relationship(back_populates="votes")
council_member: CouncilMember | None = Relationship(back_populates="votes")
class UserBirthday(SQLModel, table=True):
"""
Represents a user's birth month and day.
Attributes:
id: User's discord id.
birthday: Month/day that is a user's birthday.
"""
id: int = Field(sa_column=Column("UserId", BIGINT(unsigned=True), primary_key=True, nullable=False))
# TODO(imcompiling): split into month and day. Also relies on Alembic migration.
# https://gitea.cheezers.org/imcompiling/dakiny-bot/issues/93
birthday: str = Field(sa_column=Column("Birthday", VARCHAR, nullable=False), max_length=5)
class Presence(SQLModel, table=True):
"""
Stores presences for the bot to pull and use.
Attributes:
id: Auto-incremented id for each entry.
presence: The presence itself.
"""
__tablename__: str = "Presences"
id: int | None = Field(sa_column=Column("Id", INTEGER, primary_key=True), default=None)
presence: str = Field(sa_column=Column("Presence", VARCHAR, nullable=False), max_length=100)
class Points(SQLModel, table=True):
"""
Stores activity points a user can gain by participating in clan activities.
Event points and clan points were separated out.
Attributes:
quarter_name: Name of the quarter the points were earned in.
user_id: The user that earned them. Is a discord id if the user is a discord member, otherwise their rsn.
discord_member: Boolean on whether the user is a discord member or not. Used to know what the user_id
represents.
event_points: Event points earned during the quarter.
clan_points: Clan points earned during the quarter.
"""
quarter_name: str = Field(sa_column=Column("QuarterName", VARCHAR, primary_key=True, nullable=False), max_length=30)
user_id: str = Field(sa_column=Column("UserId", VARCHAR, primary_key=True, nullable=False), max_length=32)
discord_member: bool = Field(sa_column=Column("DiscordMember", BOOLEAN, nullable=False))
event_points: int = Field(sa_column=Column("EventPoints", INTEGER(unsigned=True), nullable=False), default=0)
clan_points: int = Field(sa_column=Column("ClanPoints", INTEGER(unsigned=True), nullable=False), default=0)
class Message(SQLModel, table=True):
"""
Table is used to map folders and files to channels and messages in discord.
If folder name is stored in path, the corresponding id is a channel id. If the path is to a file, the id is for a
message.
Attributes:
path: Either directory or file pathname.
id: Channel or message id depending on the path.
"""
__tablename__: str = "Messages"
path: str = Field(sa_column=Column("Path", VARCHAR, primary_key=True, nullable=False), max_length=100)
id: int = Field(sa_column=Column("Id", BIGINT(unsigned=True), nullable=False))
class PointsLimit(SQLModel, table=True):
"""
Table tracks how many points a user has gained in a week.
Council didn't want users spamming points, so they added a limit to how many can be gained in a week. This table
tracks point gain per week.
Attributes:
user_id: The user that earned points. Is a discord id if the user is a discord member, otherwise their rsn.
week: week number/year representation of the week. Ex: 04/2025.
event_points: Number of event points earned during this week.
clan_points: Number of clan points earned during this week.
"""
user_id: str = Field(sa_column=Column("UserId", VARCHAR, primary_key=True, nullable=False), max_length=32)
# Maybe should break this out into two columns, week and year. Better data validation and more clear.
week: str = Field(sa_column=Column("Week", VARCHAR, primary_key=True, nullable=False), max_length=7)
event_points: int = Field(sa_column=Column("EventPoints", INTEGER(unsigned=True), nullable=False), default=0)
clan_points: int = Field(sa_column=Column("ClanPoints", INTEGER(unsigned=True), nullable=False), default=0)
+20
View File
@@ -0,0 +1,20 @@
"""Simple file to set up the sqlalchemy connection to database."""
from sqlalchemy import create_engine
from sqlalchemy.ext.automap import automap_base
# connect with sql alchemy and autoload schema
URL = "sqlite+pysqlite:///database/dakiny.db"
engine = create_engine(URL)
Base = automap_base()
Base.prepare(autoload_with=engine)
# define the classes
Points = Base.classes.Points
PointsLimit = Base.classes.PointsLimit
CouncilMembers = Base.classes.CouncilMembers
ElectionInformation = Base.classes.ElectionInformation
Messages = Base.classes.Messages
Presences = Base.classes.Presences
UserBirthday = Base.classes.UserBirthday
Votes = Base.classes.Votes
+1 -1
View File
@@ -22,7 +22,7 @@ from dakiny_bot.database.sql_data_classes import (
) )
from dakiny_bot.logger import dakiny_logger from dakiny_bot.logger import dakiny_logger
from .dakiny_settings import settings from .DakinySettings import settings
NOMINEE_SPLITTER = "========================================\n\n" NOMINEE_SPLITTER = "========================================\n\n"
REPO_DIR = "./dakiny-messages" REPO_DIR = "./dakiny-messages"
+2 -3
View File
@@ -9,11 +9,10 @@ dependencies = [
"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.9.1,>=80.9.0", "setuptools<80.10.3,>=80.10.2",
"dataclass-wizard[yaml]>=0.22.3,<1.0.0", "dataclass-wizard[yaml]>=1.0.0,<1.1.0",
"uvicorn>=0.34.0", "uvicorn>=0.34.0",
"audioop-lts>=0.2.1", "audioop-lts>=0.2.1",
"sqlmodel>=0.0.24",
] ]
name = "dakiny-bot" name = "dakiny-bot"
version = "0.1.0" version = "0.1.0"
Generated
+9 -9
View File
@@ -1,5 +1,5 @@
version = 1 version = 1
revision = 2 revision = 3
requires-python = ">=3.13, <4.0" requires-python = ">=3.13, <4.0"
[[package]] [[package]]
@@ -183,13 +183,13 @@ dev = [
requires-dist = [ requires-dist = [
{ name = "aiofiles", specifier = ">=23.2.1,<24.0.0" }, { name = "aiofiles", specifier = ">=23.2.1,<24.0.0" },
{ name = "audioop-lts", specifier = ">=0.2.1" }, { name = "audioop-lts", specifier = ">=0.2.1" },
{ name = "dataclass-wizard", extras = ["yaml"], specifier = ">=0.22.3,<1.0.0" }, { name = "dataclass-wizard", extras = ["yaml"], specifier = ">=1.0.0,<1.1.0" },
{ name = "fastapi", specifier = ">=0.111.0,<1.0.0" }, { name = "fastapi", specifier = ">=0.111.0,<1.0.0" },
{ name = "gitpython", specifier = ">=3.1.43,<4.0.0" }, { name = "gitpython", specifier = ">=3.1.43,<4.0.0" },
{ name = "nextcord", specifier = ">=2.6.0,<3.0.0" }, { name = "nextcord", specifier = ">=2.6.0,<3.0.0" },
{ name = "pandas", specifier = ">=2.2.2,<3.0.0" }, { name = "pandas", specifier = ">=2.2.2,<3.0.0" },
{ name = "pytz", specifier = ">=2025.2,<2025.3" }, { name = "pytz", specifier = ">=2025.2,<2025.3" },
{ name = "setuptools", specifier = ">=80.9.0,<80.9.1" }, { name = "setuptools", specifier = ">=80.10.2,<80.10.3" },
{ name = "sqlalchemy", specifier = ">=2.0.30,<3.0.0" }, { name = "sqlalchemy", specifier = ">=2.0.30,<3.0.0" },
{ name = "uvicorn", specifier = ">=0.34.0" }, { name = "uvicorn", specifier = ">=0.34.0" },
] ]
@@ -199,11 +199,11 @@ dev = [{ name = "ruff", specifier = ">=0.6.9,<1.0.0" }]
[[package]] [[package]]
name = "dataclass-wizard" name = "dataclass-wizard"
version = "0.33.0" version = "1.0.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/0a/2a/6ae6638cd5ca919b73d393cdb4a4e53c8c3dcacc003a79d9480ecad46798/dataclass-wizard-0.33.0.tar.gz", hash = "sha256:10edbac626c390ed1f3be4f972303bc2c25c162fd453d55adfe22b05b263ee6d", size = 269911, upload-time = "2024-12-17T21:46:57.38Z" } sdist = { url = "https://files.pythonhosted.org/packages/b1/9d/465deff762411b97cc37fdd475402484724f695af042fa89ff221f725172/dataclass_wizard-1.0.0.tar.gz", hash = "sha256:d458e6a97585377337f1633e5738ee1c051e84ca7726276ecde78d6534e485b5", size = 449143, upload-time = "2026-07-03T03:21:14.302Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/f8/e6/fa8b6615f988a271b1ab990f0086f90c936608762f02d02db9164cfd7ff9/dataclass_wizard-0.33.0-py2.py3-none-any.whl", hash = "sha256:d257d65a707f6ea76140ad5442189ffe6fc68e1d678dc6b3fa25754e3106160d", size = 164545, upload-time = "2024-12-17T21:46:53.835Z" }, { url = "https://files.pythonhosted.org/packages/2d/8b/d4db2e85e6f0a5dd2e590eb646aea1cabbf3c4629aec2bc5a5540c7a4250/dataclass_wizard-1.0.0-py3-none-any.whl", hash = "sha256:0dc53acbfed4ca5fe6bbdc517003959afbd50c0794dec1f8405d3c4d8ae6690c", size = 305849, upload-time = "2026-07-03T03:21:12.252Z" },
] ]
[package.optional-dependencies] [package.optional-dependencies]
@@ -509,11 +509,11 @@ wheels = [
[[package]] [[package]]
name = "setuptools" name = "setuptools"
version = "80.9.0" version = "80.10.2"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } sdist = { url = "https://files.pythonhosted.org/packages/76/95/faf61eb8363f26aa7e1d762267a8d602a1b26d4f3a1e758e92cb3cb8b054/setuptools-80.10.2.tar.gz", hash = "sha256:8b0e9d10c784bf7d262c4e5ec5d4ec94127ce206e8738f29a437945fbc219b70", size = 1200343, upload-time = "2026-01-25T22:38:17.252Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, { url = "https://files.pythonhosted.org/packages/94/b8/f1f62a5e3c0ad2ff1d189590bfa4c46b4f3b6e49cef6f26c6ee4e575394d/setuptools-80.10.2-py3-none-any.whl", hash = "sha256:95b30ddfb717250edb492926c92b5221f7ef3fbcc2b07579bcd4a27da21d0173", size = 1064234, upload-time = "2026-01-25T22:38:15.216Z" },
] ]
[[package]] [[package]]