Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d06d98060 | ||
|
|
c267762efa | ||
|
|
e837cec46b | ||
|
|
0a856755c8 | ||
|
|
eb8cfee7fd | ||
|
|
6b5dc771d4 |
+2
-2
@@ -1,10 +1,10 @@
|
|||||||
FROM python:3.13.7-slim
|
FROM python:3.13.5-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.7.20 /uv /uvx /bin/
|
||||||
|
|
||||||
COPY . /home
|
COPY . /home
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ class ChannelIds:
|
|||||||
event_post: int
|
event_post: int
|
||||||
general: int
|
general: int
|
||||||
voting: int
|
voting: int
|
||||||
ban: int
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -0,0 +1,148 @@
|
|||||||
|
"""File holds all model definitions of SQL tables."""
|
||||||
|
|
||||||
|
from sqlalchemy.dialects.mysql import BIGINT, 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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
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"), default=False)
|
||||||
|
discord_manager: bool = Field(sa_column=Column("DiscordManager"), default=False)
|
||||||
|
clan_leader: bool = Field(sa_column=Column("ClanLeader"), 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.member_id"), nullable=True)
|
||||||
|
)
|
||||||
|
type: str = Field(sa_column=Column("Type", 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)
|
||||||
+3
-2
@@ -8,11 +8,12 @@ dependencies = [
|
|||||||
"pandas<3.0.0,>=2.2.2",
|
"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<25.2.0,>=25.1.0",
|
"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",
|
"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"
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
version = 1
|
version = 1
|
||||||
revision = 3
|
revision = 2
|
||||||
requires-python = ">=3.13, <4.0"
|
requires-python = ">=3.13, <4.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aiofiles"
|
name = "aiofiles"
|
||||||
version = "25.1.0"
|
version = "23.2.1"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/41/c3/534eac40372d8ee36ef40df62ec129bee4fdb5ad9706e58a29be53b2c970/aiofiles-25.1.0.tar.gz", hash = "sha256:a8d728f0a29de45dc521f18f07297428d56992a742f0cd2701ba86e44d23d5b2", size = 46354, upload-time = "2025-10-09T20:51:04.358Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/af/41/cfed10bc64d774f497a86e5ede9248e1d062db675504b41c320954d99641/aiofiles-23.2.1.tar.gz", hash = "sha256:84ec2218d8419404abcb9f0c02df3f34c6e0a68ed41072acfb1cef5cbc29051a", size = 32072, upload-time = "2023-08-09T15:23:11.564Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/bc/8a/340a1555ae33d7354dbca4faa54948d76d89a27ceef032c8c3bc661d003e/aiofiles-25.1.0-py3-none-any.whl", hash = "sha256:abe311e527c862958650f9438e859c1fa7568a141b22abcd015e120e86a85695", size = 14668, upload-time = "2025-10-09T20:51:03.174Z" },
|
{ url = "https://files.pythonhosted.org/packages/c5/19/5af6804c4cc0fed83f47bff6e413a98a36618e7d40185cd36e69737f3b0e/aiofiles-23.2.1-py3-none-any.whl", hash = "sha256:19297512c647d4b27a2cf7c34caa7e405c0d60b5560618a29a9fe027b18b0107", size = 15727, upload-time = "2023-08-09T15:23:09.774Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -181,7 +181,7 @@ dev = [
|
|||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "aiofiles", specifier = ">=25.1.0,<25.2.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 = ">=0.22.3,<1.0.0" },
|
||||||
{ name = "fastapi", specifier = ">=0.111.0,<1.0.0" },
|
{ name = "fastapi", specifier = ">=0.111.0,<1.0.0" },
|
||||||
@@ -189,7 +189,7 @@ requires-dist = [
|
|||||||
{ 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.10.2,<80.10.3" },
|
{ name = "setuptools", specifier = ">=80.9.0,<80.9.1" },
|
||||||
{ 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" },
|
||||||
]
|
]
|
||||||
@@ -509,11 +509,11 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "setuptools"
|
name = "setuptools"
|
||||||
version = "80.10.2"
|
version = "80.9.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
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 = [
|
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]]
|
[[package]]
|
||||||
|
|||||||
Reference in New Issue
Block a user