Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b221f1e438 | ||
|
|
1b08ba1355 | ||
|
|
e191df1a38 | ||
|
|
e35a2a8c8d | ||
|
|
d7046e4595 |
Vendored
+1
-2
@@ -9,7 +9,6 @@
|
||||
"Dakiny",
|
||||
"DISCORDMANAGER",
|
||||
"highcouncil",
|
||||
"nextcord",
|
||||
"ondelete"
|
||||
"nextcord"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ class ChannelIds:
|
||||
event_post: int
|
||||
general: int
|
||||
voting: int
|
||||
ban: int
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -83,5 +84,4 @@ class DakinySettings(YAMLWizard):
|
||||
self.to_yaml_file(CONFIG_LOCATION)
|
||||
|
||||
|
||||
tmp = DakinySettings.from_yaml_file(CONFIG_LOCATION)
|
||||
settings: DakinySettings = tmp[0] if isinstance(tmp, list) else tmp
|
||||
settings = DakinySettings.from_yaml_file(CONFIG_LOCATION)
|
||||
+12
-18
@@ -1,6 +1,6 @@
|
||||
"""Main file for bot."""
|
||||
|
||||
import pathlib
|
||||
import os
|
||||
|
||||
import nextcord
|
||||
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.voting_highcouncil_views import VoteHighcouncilView
|
||||
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] = []
|
||||
|
||||
@@ -31,25 +31,19 @@ def run() -> None:
|
||||
bot = commands.Bot(intents=intents)
|
||||
|
||||
# Get the modules of all cogs whose directory structure is ./cogs/<module_name>
|
||||
for folder in pathlib.Path("dakiny_bot/cogs").iterdir():
|
||||
bot.load_extension(f"dakiny_bot.cogs.{folder.name}")
|
||||
for folder in os.listdir("dakiny_bot/cogs"):
|
||||
bot.load_extension(f"dakiny_bot.cogs.{folder}")
|
||||
|
||||
@bot.listen()
|
||||
async def on_ready() -> None:
|
||||
"""When Discord is connected."""
|
||||
if bot.user is None:
|
||||
dakiny_logger.error("Discord connection failed.")
|
||||
return
|
||||
|
||||
if (guild := bot.get_guild(settings.params.guild_id)) is None:
|
||||
dakiny_logger.error("Could not find Dakiny guild object.")
|
||||
return
|
||||
|
||||
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)
|
||||
assert bot.user is not None # noqa: S101 documentation does this
|
||||
create_database # noqa: B018
|
||||
bot.add_view(NominateView(bot.get_guild(settings.params.guild_id)))
|
||||
bot.add_view(NominateHighCouncilView(bot.get_guild(settings.params.guild_id)))
|
||||
bot.add_view(VoteView(bot.get_guild(settings.params.guild_id)))
|
||||
bot.add_view(VoteHighcouncilView(bot.get_guild(settings.params.guild_id)))
|
||||
print(f"{bot.user.name} has connected to Discord!") # noqa: T201 TODO make logging to print my stuff to console
|
||||
|
||||
# Run Discord bot
|
||||
bot.run(settings.params.discord_token)
|
||||
|
||||
@@ -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.voting_highcouncil_views import VoteHighcouncilView
|
||||
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 (
|
||||
CouncilMembers,
|
||||
ElectionInformation,
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
"""Contains various views to be used in the high council nomination process."""
|
||||
|
||||
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.utils import (
|
||||
NOMINEE_SPLITTER,
|
||||
@@ -9,9 +13,6 @@ from dakiny_bot.utils import (
|
||||
decorate_dm_nomination,
|
||||
get_election_name,
|
||||
)
|
||||
from nextcord.interactions import Interaction
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
|
||||
class NominateDMModal(nextcord.ui.Modal):
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
"""Contains various views to be used in the nomination process."""
|
||||
|
||||
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 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.utils import NOMINEE_SPLITTER, decorate_nomination, get_election_name
|
||||
|
||||
|
||||
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."""
|
||||
|
||||
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 (
|
||||
CouncilMembers,
|
||||
ElectionInformation,
|
||||
@@ -9,10 +14,6 @@ from dakiny_bot.database.sql_data_classes import (
|
||||
engine,
|
||||
)
|
||||
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):
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
"""Contains various views to be used while voting. Controls the logic when performing the vote."""
|
||||
|
||||
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 (
|
||||
CouncilMembers,
|
||||
ElectionInformation,
|
||||
@@ -9,10 +14,6 @@ from dakiny_bot.database.sql_data_classes import (
|
||||
engine,
|
||||
)
|
||||
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):
|
||||
|
||||
@@ -10,7 +10,7 @@ from git.exc import GitCommandError
|
||||
from nextcord.ext import commands
|
||||
|
||||
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.utils import REPO_DIR, edit_clan_council_info, update_clan_messages
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import nextcord
|
||||
from discord import TextInputStyle
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import nextcord
|
||||
from nextcord.ext import commands
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import nextcord
|
||||
from nextcord.ext import commands
|
||||
|
||||
from dakiny_bot.dakiny_settings import settings
|
||||
from dakiny_bot.DakinySettings import settings
|
||||
from dakiny_bot.utils import GUILDS
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ from nextcord.ext import application_checks, commands
|
||||
|
||||
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
|
||||
|
||||
ME = 206602713511886849
|
||||
|
||||
@@ -11,7 +11,7 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from dakiny_bot.app import birthday_list
|
||||
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.logger import dakiny_logger
|
||||
from dakiny_bot.utils import get_active_election_cycle
|
||||
|
||||
@@ -4,6 +4,7 @@ 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"):
|
||||
@@ -16,7 +17,15 @@ 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:
|
||||
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
|
||||
|
||||
await birthday_action(message)
|
||||
@@ -31,7 +40,7 @@ def setup(bot: commands.Bot) -> 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:
|
||||
message (nextcord.Message): The message to check for a birthday person.
|
||||
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -1,218 +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 | None = Field(
|
||||
sa_column=Column("SpeechMsgId", BIGINT(unsigned=True), nullable=False), default=None
|
||||
)
|
||||
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 | None = Field(
|
||||
sa_column=Column("DiscordManagerSpeechId", BIGINT(unsigned=True), nullable=True), default=None
|
||||
)
|
||||
clan_leader_speech_id: int | None = Field(
|
||||
sa_column=Column("ClanLeaderSpeechId", BIGINT(unsigned=True), nullable=True), default=None
|
||||
)
|
||||
|
||||
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)
|
||||
@@ -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
|
||||
+85
-104
@@ -1,7 +1,7 @@
|
||||
"""File contains util functions."""
|
||||
|
||||
import calendar
|
||||
import pathlib
|
||||
import os
|
||||
from collections.abc import Sequence
|
||||
from datetime import UTC, datetime
|
||||
from enum import StrEnum
|
||||
@@ -9,15 +9,20 @@ from pathlib import Path
|
||||
|
||||
import aiofiles
|
||||
import nextcord
|
||||
from sqlalchemy import select, update
|
||||
from sqlalchemy import select, true, update
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlmodel import col
|
||||
|
||||
from dakiny_bot.database.db import engine
|
||||
from dakiny_bot.database.models import CouncilMember, Message, Points, PointsLimit, Vote
|
||||
from dakiny_bot.database.sql_data_classes import (
|
||||
CouncilMembers,
|
||||
Messages,
|
||||
Points,
|
||||
PointsLimit,
|
||||
Votes,
|
||||
engine,
|
||||
)
|
||||
from dakiny_bot.logger import dakiny_logger
|
||||
|
||||
from .dakiny_settings import settings
|
||||
from .DakinySettings import settings
|
||||
|
||||
NOMINEE_SPLITTER = "========================================\n\n"
|
||||
REPO_DIR = "./dakiny-messages"
|
||||
@@ -30,9 +35,7 @@ VoteType = StrEnum("VoteType", ["COUNCIL", "DISCORDMANAGER", "CLANLEADER"])
|
||||
PointType = StrEnum("PointType", ["CLAN", "EVENT"])
|
||||
|
||||
|
||||
# TODO(imcompiling): resolve returning None from this function
|
||||
# https://gitea.cheezers.org/imcompiling/dakiny-bot/issues/102
|
||||
def get_datetime(month_day: str) -> datetime: # pyright: ignore[reportReturnType]
|
||||
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.
|
||||
|
||||
@@ -126,9 +129,7 @@ def get_quarter_cycle() -> int:
|
||||
return cycle
|
||||
|
||||
dakiny_logger.debug("Quarter cycle is None.")
|
||||
# TODO(imcompiling) Add None to return signature or find better return value
|
||||
# https://gitea.cheezers.org/imcompiling/dakiny-bot/issues/102
|
||||
return None # pyright: ignore[reportReturnType]
|
||||
return None
|
||||
|
||||
|
||||
def build_votes_str(guild: nextcord.Guild) -> str:
|
||||
@@ -146,20 +147,19 @@ def build_votes_str(guild: nextcord.Guild) -> str:
|
||||
election_name = get_election_name()
|
||||
|
||||
with Session(engine) as session:
|
||||
stmt = select(CouncilMember).where(col(CouncilMember.election_name == election_name))
|
||||
stmt = select(CouncilMembers).where(CouncilMembers.ElectionName == election_name)
|
||||
|
||||
for nominee in sorted(
|
||||
session.scalars(stmt),
|
||||
# Unsure how to None check in the lambda.
|
||||
key=lambda nominee: guild.get_member(nominee.member_id).display_name.lower(), # pyright: ignore[reportOptionalMemberAccess]
|
||||
key=lambda nominee: guild.get_member(nominee.MemberId).display_name.lower(),
|
||||
):
|
||||
votes[nominee.member_id] = 0
|
||||
votes[nominee.MemberId] = 0
|
||||
|
||||
stmt = select(Vote).where(col(Vote.election_name == election_name)).where(col(Vote.type == VoteType.COUNCIL))
|
||||
stmt = select(Votes).where(Votes.ElectionName == election_name).where(Votes.Type == VoteType.COUNCIL)
|
||||
|
||||
for vote in session.scalars(stmt):
|
||||
if vote.voted_for_id is not None:
|
||||
votes[vote.voted_for_id] += 1
|
||||
if vote.VotedForId is not None:
|
||||
votes[vote.VotedForId] += 1
|
||||
|
||||
votes_str: str = "===================="
|
||||
votes_str += user_vote_str(votes)
|
||||
@@ -184,52 +184,46 @@ def build_highcouncil_votes_str(guild: nextcord.Guild) -> str:
|
||||
|
||||
with Session(engine) as session:
|
||||
stmt = (
|
||||
select(CouncilMember)
|
||||
.where(col(CouncilMember.election_name == election_name))
|
||||
.filter(col(CouncilMember.clan_leader_speech_id is not None))
|
||||
select(CouncilMembers)
|
||||
.where(CouncilMembers.ElectionName == election_name)
|
||||
.filter(CouncilMembers.ClanLeaderSpeechId.is_not(None))
|
||||
)
|
||||
|
||||
for nominee in sorted(
|
||||
session.scalars(stmt),
|
||||
# Unsure how to None check in the lambda.
|
||||
key=lambda nominee: guild.get_member(nominee.member_id).display_name.lower(), # pyright: ignore[reportOptionalMemberAccess]
|
||||
key=lambda nominee: guild.get_member(nominee.MemberId).display_name.lower(),
|
||||
):
|
||||
cl_votes[nominee.member_id] = 0
|
||||
cl_votes[nominee.MemberId] = 0
|
||||
|
||||
stmt = select(Vote).where(col(Vote.election_name == election_name)).where(col(Vote.type == VoteType.CLANLEADER))
|
||||
stmt = select(Votes).where(Votes.ElectionName == election_name).where(Votes.Type == VoteType.CLANLEADER)
|
||||
|
||||
for vote in session.scalars(stmt):
|
||||
if vote.voted_for_id is not None:
|
||||
cl_votes[vote.voted_for_id] += 1
|
||||
if vote.VotedForId is not None:
|
||||
cl_votes[vote.VotedForId] += 1
|
||||
|
||||
stmt = (
|
||||
select(CouncilMember)
|
||||
.where(col(CouncilMember.election_name == election_name))
|
||||
.filter(col(CouncilMember.discord_manager_speech_id is not None))
|
||||
select(CouncilMembers)
|
||||
.where(CouncilMembers.ElectionName == election_name)
|
||||
.filter(CouncilMembers.DiscordManagerSpeechId.is_not(None))
|
||||
)
|
||||
|
||||
for nominee in sorted(
|
||||
session.scalars(stmt),
|
||||
# Unsure how to None check in the lambda.
|
||||
key=lambda nominee: guild.get_member(nominee.member_id).display_name.lower(), # pyright: ignore[reportOptionalMemberAccess]
|
||||
key=lambda nominee: guild.get_member(nominee.MemberId).display_name.lower(),
|
||||
):
|
||||
dm_votes[nominee.member_id] = 0
|
||||
dm_votes[nominee.MemberId] = 0
|
||||
|
||||
stmt = (
|
||||
select(Vote)
|
||||
.where(col(Vote.election_name == election_name))
|
||||
.where(col(Vote.type == VoteType.DISCORDMANAGER))
|
||||
)
|
||||
stmt = select(Votes).where(Votes.ElectionName == election_name).where(Votes.Type == VoteType.DISCORDMANAGER)
|
||||
|
||||
for vote in session.scalars(stmt):
|
||||
if vote.voted_for_id is not None:
|
||||
dm_votes[vote.voted_for_id] += 1
|
||||
if vote.VotedForId is not None:
|
||||
dm_votes[vote.VotedForId] += 1
|
||||
|
||||
# build the votes string
|
||||
votes_str: str = "Clan Leader Vote\n===================="
|
||||
votes_str: str = "Clan Leader Votes\n===================="
|
||||
votes_str += user_vote_str(cl_votes)
|
||||
|
||||
votes_str += "\n\nDiscord Manager Vote\n===================="
|
||||
votes_str += "\n\nDiscord Manager Votes\n===================="
|
||||
votes_str += user_vote_str(dm_votes)
|
||||
|
||||
return votes_str
|
||||
@@ -323,7 +317,7 @@ def record_points(users: Sequence[str], add_points: int, point_type: PointType,
|
||||
quarter_name = get_quarter_name()
|
||||
with Session(engine) as session:
|
||||
for user in users:
|
||||
stmt = select(Points).where(col(Points.user_id == user)).where(col(Points.quarter_name == quarter_name))
|
||||
stmt = select(Points).where(Points.UserId == user).where(Points.QuarterName == quarter_name)
|
||||
|
||||
points_obj = session.scalar(stmt)
|
||||
add_points = get_allowed_points(session, point_type, user, add_points)
|
||||
@@ -331,16 +325,16 @@ def record_points(users: Sequence[str], add_points: int, point_type: PointType,
|
||||
add_event_points = add_points if point_type == PointType.EVENT else 0
|
||||
if points_obj is None:
|
||||
new_points = Points(
|
||||
quarter_name=quarter_name,
|
||||
user_id=user,
|
||||
discord_member=is_member,
|
||||
event_points=add_event_points,
|
||||
clan_points=add_clan_points,
|
||||
QuarterName=quarter_name,
|
||||
UserId=user,
|
||||
DiscordMember=is_member,
|
||||
EventPoints=add_event_points,
|
||||
ClanPoints=add_clan_points,
|
||||
)
|
||||
session.add(new_points)
|
||||
else:
|
||||
points_obj.event_points += add_event_points
|
||||
points_obj.clan_points += add_clan_points
|
||||
points_obj.EventPoints += add_event_points
|
||||
points_obj.ClanPoints += add_clan_points
|
||||
|
||||
session.commit()
|
||||
|
||||
@@ -370,7 +364,7 @@ def get_allowed_points(session: Session, point_type: PointType, user: str, point
|
||||
if point_type == PointType.EVENT
|
||||
else settings.point_settings.clan_points_weekly_limit
|
||||
)
|
||||
stmt = select(PointsLimit).where(col(PointsLimit.user_id == user)).where(col(PointsLimit.week == this_week))
|
||||
stmt = select(PointsLimit).where(PointsLimit.UserId == user).where(PointsLimit.Week == this_week)
|
||||
|
||||
# if under limit, can add up to however many points would make the current limit equal to the
|
||||
# allowed limit.
|
||||
@@ -378,16 +372,16 @@ def get_allowed_points(session: Session, point_type: PointType, user: str, point
|
||||
if limit_obj is None:
|
||||
clan_points = points if point_type == PointType.CLAN else 0
|
||||
event_points = points if point_type == PointType.EVENT else 0
|
||||
new_limit = PointsLimit(user_id=user, week=this_week, event_points=event_points, clan_points=clan_points)
|
||||
new_limit = PointsLimit(UserId=user, Week=this_week, EventPoints=event_points, ClanPoints=clan_points)
|
||||
add_points = points
|
||||
session.add(new_limit)
|
||||
else:
|
||||
curr_points = limit_obj.event_points if point_type == PointType.EVENT else limit_obj.clan_points
|
||||
curr_points = limit_obj.EventPoints if point_type == PointType.EVENT else limit_obj.ClanPoints
|
||||
add_points = points if curr_points + points <= points_limit else (max(0, points_limit - curr_points))
|
||||
if point_type == PointType.EVENT:
|
||||
limit_obj.event_points += add_points
|
||||
limit_obj.EventPoints += add_points
|
||||
else:
|
||||
limit_obj.clan_points += add_points
|
||||
limit_obj.ClanPoints += add_points
|
||||
|
||||
session.commit()
|
||||
|
||||
@@ -416,8 +410,8 @@ async def update_clan_messages(guild: nextcord.Guild, channel_updates: Sequence[
|
||||
dir_path = f"{MESSAGES_DIR}/{dir_name}"
|
||||
channel = await get_clan_message_channel(session, dir_name, guild)
|
||||
sorted_files = sorted(
|
||||
pathlib.Path(dir_path).iterdir(),
|
||||
key=lambda file: int(file.name.split(".")[0]),
|
||||
os.listdir(dir_path),
|
||||
key=lambda filename: int(filename.split(".")[0]),
|
||||
)
|
||||
|
||||
for file_name in sorted_files:
|
||||
@@ -446,49 +440,41 @@ async def get_clan_message_channel(session: Session, channel_name: str, guild: n
|
||||
Returns:
|
||||
nextcord.TextChannel: Channel object with name channel_name.
|
||||
"""
|
||||
stmt = select(col(Message.id)).where(col(Message.path == channel_name))
|
||||
stmt = select(Messages.Id).where(Messages.Path == channel_name)
|
||||
channel_id = session.scalar(stmt)
|
||||
ret_channel = None
|
||||
if channel_id is None:
|
||||
ret_channel = None
|
||||
# channel is not tracked. see if it exists
|
||||
channels = await guild.fetch_channels()
|
||||
for channel in channels:
|
||||
if channel.name == channel_name and isinstance(channel, nextcord.TextChannel):
|
||||
if channel.name == channel_name and channel.__class__ is nextcord.TextChannel:
|
||||
ret_channel = channel
|
||||
|
||||
if ret_channel is None:
|
||||
# channel doesn't exist, we must create it
|
||||
ret_channel = await guild.create_text_channel(
|
||||
channel_name,
|
||||
reason="Creating channel as required for by design of Dakiny-Message repo.",
|
||||
reason="Creating channel as required for by design of Dakiny-Messages repo.",
|
||||
)
|
||||
|
||||
# record channel information
|
||||
new_item = Message(path=channel_name, id=ret_channel.id)
|
||||
new_item = Messages(Path=channel_name, Id=ret_channel.id)
|
||||
session.add(new_item)
|
||||
session.commit()
|
||||
return ret_channel
|
||||
|
||||
else:
|
||||
try:
|
||||
ret_channel = await guild.fetch_channel(channel_id)
|
||||
if not isinstance(ret_channel, nextcord.TextChannel):
|
||||
dakiny_logger.warning(
|
||||
"Channel %s is not a text channel. Does not work for clan messages.", ret_channel.name
|
||||
)
|
||||
# TODO(imcompiling) Figure out how to handle returning None here. Maybe delete channel and remake it?
|
||||
# https://gitea.cheezers.org/imcompiling/dakiny-bot/issues/102
|
||||
return None # pyright: ignore[reportReturnType]
|
||||
except nextcord.NotFound:
|
||||
# the channel was deleted for some reason
|
||||
ret_channel = await guild.create_text_channel(
|
||||
channel_name,
|
||||
reason="Creating channel as required for by design of Dakiny-Message repo.",
|
||||
)
|
||||
stmt = update(Message).where(col(Message.path == channel_name)).values(id=ret_channel.id)
|
||||
session.execute(stmt)
|
||||
session.commit()
|
||||
|
||||
return ret_channel
|
||||
try:
|
||||
return await guild.fetch_channel(channel_id)
|
||||
except nextcord.NotFound:
|
||||
# the channel was deleted for some reason
|
||||
ret_channel = await guild.create_text_channel(
|
||||
channel_name,
|
||||
reason="Creating channel as required for by design of Dakiny-Messages repo.",
|
||||
)
|
||||
stmt = update(Messages).where(Messages.Path == channel_name).values(Id=ret_channel.id)
|
||||
session.execute(stmt)
|
||||
session.commit()
|
||||
return ret_channel
|
||||
|
||||
|
||||
async def edit_clan_message(
|
||||
@@ -510,21 +496,21 @@ async def edit_clan_message(
|
||||
message_name (str): Name of the message. Corresponds to the file that contains message content.
|
||||
message_content (str): Content of the message to edit/send.
|
||||
"""
|
||||
stmt = select(col(Message.id)).where(col(Message.path == message_name))
|
||||
stmt = select(Messages.Id).where(Messages.Path == message_name)
|
||||
msg_id = session.scalar(stmt)
|
||||
if msg_id is None:
|
||||
# don't have a message for this file
|
||||
msg = await channel.send(message_content)
|
||||
new_message = Message(path=message_name, id=msg.id)
|
||||
new_message = Messages(Path=message_name, Id=msg.id)
|
||||
session.add(new_message)
|
||||
session.commit()
|
||||
else:
|
||||
try:
|
||||
msg: nextcord.Message = await channel.fetch_message(msg_id)
|
||||
await msg.edit(content=message_content)
|
||||
await msg.edit(message_content)
|
||||
except nextcord.NotFound:
|
||||
msg = await channel.send(message_content)
|
||||
stmt = update(Message).where(col(Message.path == message_name)).values(id=msg.id)
|
||||
stmt = update(Messages).where(Messages.Path == message_name).values(Id=msg.id)
|
||||
session.execute(stmt)
|
||||
session.commit()
|
||||
|
||||
@@ -536,35 +522,30 @@ async def edit_clan_council_info(guild: nextcord.Guild) -> None:
|
||||
Args:
|
||||
guild (nextcord.Guild): Guild object that to use to get channel.
|
||||
"""
|
||||
clan_info_channel = await guild.fetch_channel(settings.channel_ids.clan_info)
|
||||
|
||||
if not isinstance(clan_info_channel, nextcord.TextChannel):
|
||||
return
|
||||
clan_info_channel: nextcord.TextChannel = await guild.fetch_channel(settings.channel_ids.clan_info)
|
||||
|
||||
election_name = get_election_name()
|
||||
with Session(engine) as session:
|
||||
stmt = (
|
||||
select(col(CouncilMember.member_id))
|
||||
.where(col(CouncilMember.election_name == election_name))
|
||||
.where(col(CouncilMember.discord_manager is True))
|
||||
select(CouncilMembers.MemberId)
|
||||
.where(CouncilMembers.ElectionName == election_name)
|
||||
.where(CouncilMembers.DiscordManager == true())
|
||||
)
|
||||
discord_manager = session.scalar(stmt)
|
||||
stmt = (
|
||||
select(col(CouncilMember.member_id))
|
||||
.where(col(CouncilMember.election_name == election_name))
|
||||
.where(col(CouncilMember.clan_leader is True))
|
||||
select(CouncilMembers.MemberId)
|
||||
.where(CouncilMembers.ElectionName == election_name)
|
||||
.where(CouncilMembers.ClanLeader == true())
|
||||
)
|
||||
clan_leader = session.scalar(stmt)
|
||||
stmt = (
|
||||
select(col(CouncilMember.member_id))
|
||||
.where(col(CouncilMember.election_name == election_name))
|
||||
.where(col(CouncilMember.elected is True))
|
||||
select(CouncilMembers.MemberId)
|
||||
.where(CouncilMembers.ElectionName == election_name)
|
||||
.where(CouncilMembers.Elected == true())
|
||||
)
|
||||
|
||||
council_member_ids = sorted(
|
||||
session.scalars(stmt),
|
||||
# Unsure how to None check in the lambda.
|
||||
key=lambda user_id: guild.get_member(user_id).display_name.lower(), # pyright: ignore[reportOptionalMemberAccess]
|
||||
session.scalars(stmt), key=lambda user_id: guild.get_member(user_id).display_name.lower()
|
||||
)
|
||||
|
||||
council_members_strs = [
|
||||
|
||||
@@ -13,7 +13,6 @@ dependencies = [
|
||||
"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"
|
||||
|
||||
Reference in New Issue
Block a user