Compare commits

..
15 Commits
Author SHA1 Message Date
imcompiling 77ce54f383 Remove: Old database files.
build_develop / build (push) Successful in 39s
2025-07-16 10:37:51 -04:00
imcompiling f81ebca4de Fix: errors on models from generating database. 2025-07-16 10:35:00 -04:00
imcompiling 2e66b1c37c Feat: Add database engine. 2025-07-16 10:26:26 -04:00
imcompiling ae1f98abe3 Add: ondelete to spelling settings.
build_develop / build (push) Successful in 19s
2025-07-16 09:10:05 -04:00
imcompiling 490c30e88c Add: PointsLimit model.
build_develop / build (push) Successful in 41s
2025-07-16 09:04:42 -04:00
imcompiling 35670358c4 Fix: Add default to points. 2025-07-16 09:00:22 -04:00
imcompiling 7a59f3b453 Add: Messages model. 2025-07-16 08:56:27 -04:00
imcompiling e01ab64411 Add: Points model. 2025-07-16 08:51:51 -04:00
imcompiling df482d80f0 Fix: Add boolean column types. 2025-07-16 08:51:39 -04:00
imcompiling 0d06d98060 Add: Presence model.
build_develop / build (push) Successful in 45s
2025-07-15 15:58:57 -04:00
imcompiling c267762efa Add: UserBirthday model.
build_develop / build (push) Successful in 38s
2025-07-15 15:45:26 -04:00
imcompiling e837cec46b Add: Vote model.
build_develop / build (push) Successful in 39s
2025-07-15 15:40:32 -04:00
imcompiling 0a856755c8 Add: CouncilMembers model.
build_develop / build (push) Successful in 44s
2025-07-15 15:05:15 -04:00
imcompiling eb8cfee7fd Feat: Add ElectionInformation table.
build_develop / build (push) Successful in 37s
2025-07-15 09:48:17 -04:00
imcompiling 6b5dc771d4 Feat: Install sqlmodel.
build_develop / build (push) Successful in 37s
2025-07-15 09:20:58 -04:00
8 changed files with 232 additions and 46 deletions
+2 -1
View File
@@ -9,6 +9,7 @@
"Dakiny", "Dakiny",
"DISCORDMANAGER", "DISCORDMANAGER",
"highcouncil", "highcouncil",
"nextcord" "nextcord",
"ondelete"
] ]
} }
-1
View File
@@ -17,7 +17,6 @@ class ChannelIds:
event_post: int event_post: int
general: int general: int
voting: int voting: int
ban: int
@dataclass @dataclass
+2 -11
View File
@@ -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.
-13
View File
@@ -1,13 +0,0 @@
"""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
@@ -0,0 +1,13 @@
"""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
@@ -0,0 +1,214 @@
"""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
@@ -1,20 +0,0 @@
"""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
View File
@@ -13,6 +13,7 @@ dependencies = [
"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", "audioop-lts>=0.2.1",
"sqlmodel>=0.0.24",
] ]
name = "dakiny-bot" name = "dakiny-bot"
version = "0.1.0" version = "0.1.0"