Compare commits
15
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77ce54f383 | ||
|
|
f81ebca4de | ||
|
|
2e66b1c37c | ||
|
|
ae1f98abe3 | ||
|
|
490c30e88c | ||
|
|
35670358c4 | ||
|
|
7a59f3b453 | ||
|
|
e01ab64411 | ||
|
|
df482d80f0 | ||
|
|
0d06d98060 | ||
|
|
c267762efa | ||
|
|
e837cec46b | ||
|
|
0a856755c8 | ||
|
|
eb8cfee7fd | ||
|
|
6b5dc771d4 |
Vendored
+2
-1
@@ -9,6 +9,7 @@
|
||||
"Dakiny",
|
||||
"DISCORDMANAGER",
|
||||
"highcouncil",
|
||||
"nextcord"
|
||||
"nextcord",
|
||||
"ondelete"
|
||||
]
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
FROM python:3.13.7-slim
|
||||
FROM python:3.13.5-slim
|
||||
|
||||
# install dependencies
|
||||
RUN apt-get update && apt-get -y upgrade && apt-get -y install git sqlite3
|
||||
|
||||
# install uv
|
||||
COPY --from=ghcr.io/astral-sh/uv:0.13.0 /uv /uvx /bin/
|
||||
COPY --from=ghcr.io/astral-sh/uv:0.7.20 /uv /uvx /bin/
|
||||
|
||||
COPY . /home
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ class ChannelIds:
|
||||
event_post: int
|
||||
general: int
|
||||
voting: int
|
||||
ban: int
|
||||
|
||||
|
||||
@dataclass
|
||||
|
||||
@@ -4,7 +4,6 @@ import nextcord
|
||||
from nextcord.ext import commands
|
||||
|
||||
from dakiny_bot.app import birthday_list
|
||||
from dakiny_bot.DakinySettings import settings
|
||||
|
||||
|
||||
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:
|
||||
"""Listener for message creation. Adds score and attachments to submission."""
|
||||
# don't want to process any messages from bot.
|
||||
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."
|
||||
)
|
||||
if message.author.id == self.bot.user.id:
|
||||
return
|
||||
|
||||
await birthday_action(message)
|
||||
@@ -40,7 +31,7 @@ def setup(bot: commands.Bot) -> 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:
|
||||
message (nextcord.Message): The message to check for a birthday person.
|
||||
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
+2
-1
@@ -9,10 +9,11 @@ dependencies = [
|
||||
"pytz<2025.3,>=2025.2",
|
||||
"sqlalchemy<3.0.0,>=2.0.30",
|
||||
"aiofiles<24.0.0,>=23.2.1",
|
||||
"setuptools<80.10.3,>=80.10.2",
|
||||
"setuptools<80.9.1,>=80.9.0",
|
||||
"dataclass-wizard[yaml]>=0.22.3,<1.0.0",
|
||||
"uvicorn>=0.34.0",
|
||||
"audioop-lts>=0.2.1",
|
||||
"sqlmodel>=0.0.24",
|
||||
]
|
||||
name = "dakiny-bot"
|
||||
version = "0.1.0"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
revision = 2
|
||||
requires-python = ">=3.13, <4.0"
|
||||
|
||||
[[package]]
|
||||
@@ -189,7 +189,7 @@ requires-dist = [
|
||||
{ name = "nextcord", specifier = ">=2.6.0,<3.0.0" },
|
||||
{ name = "pandas", specifier = ">=2.2.2,<3.0.0" },
|
||||
{ name = "pytz", specifier = ">=2025.2,<2025.3" },
|
||||
{ name = "setuptools", specifier = ">=80.10.2,<80.10.3" },
|
||||
{ name = "setuptools", specifier = ">=80.9.0,<80.9.1" },
|
||||
{ name = "sqlalchemy", specifier = ">=2.0.30,<3.0.0" },
|
||||
{ name = "uvicorn", specifier = ">=0.34.0" },
|
||||
]
|
||||
@@ -509,11 +509,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "setuptools"
|
||||
version = "80.10.2"
|
||||
version = "80.9.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
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" }
|
||||
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" }
|
||||
wheels = [
|
||||
{ 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" },
|
||||
{ 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" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
Reference in New Issue
Block a user