Compare commits

...
24 Commits
Author SHA1 Message Date
imcompiling 8825afdb46 Missed removing a parameter from buiild_votes_str().
build_latest / build (push) Failing after 29s
build_release / build (push) Failing after 29s
2025-01-19 18:19:51 -05:00
imcompiling 6fc91e0708 dakiny-bot#83 fix the view points bug when someone isn't in the guild anymore.
build_latest / build (push) Successful in 1m36s
build_release / build (push) Successful in 18s
build_develop / build (push) Successful in 23s
2024-12-22 22:21:01 -05:00
imcompiling 95cfcca600 Issue #79, add a 2 hour timeout to event submission.
build_latest / build (push) Successful in 1m27s
build_release / build (push) Successful in 22s
2024-12-22 17:11:54 -05:00
imcompiling 0419c9dfd6 Add auto complete for view clan points command.
build_latest / build (push) Successful in 1m30s
2024-12-22 16:51:10 -05:00
imcompiling 6ec2862aed Merge branch '76-clan-points'
build_latest / build (push) Successful in 17s
build_release / build (push) Successful in 17s
2024-12-22 15:49:24 -05:00
imcompiling c8f5c5bffb Add view clan points command. Break out function for creating the string so that code isn't duplicated.
build_develop / build (push) Successful in 2m11s
2024-12-22 15:48:10 -05:00
imcompiling a56b47a5fa Revamped utils to handle clan and event points in one. Updated record as needed. 2024-12-22 15:34:14 -05:00
imcompiling 906b63d57f Update view event points for new table name. 2024-12-22 15:13:35 -05:00
imcompiling 1bae320446 Fix in typos. 2024-12-22 15:10:47 -05:00
imcompiling a081718f8d Combine event and clan points into one table. 2024-12-22 14:41:50 -05:00
imcompiling 0dd1a90edd Merge branch 'main' into 76-clan-points 2024-12-22 14:04:03 -05:00
imcompiling 1305e15edf Change cutoff months for rollover.
build_latest / build (push) Successful in 1m49s
build_develop / build (push) Successful in 24s
build_release / build (push) Successful in 20s
2024-12-14 18:36:47 -05:00
imcompiling 86e9e3e32b Write a new test case for quarter name. 2024-12-14 17:57:01 -05:00
imcompiling 8498f42ba0 update ruff version
build_develop / build (push) Successful in 2m41s
2024-10-05 14:16:01 -04:00
imcompiling be46edd4ea Updated docustrings.
build_develop / build (push) Successful in 1m24s
2024-09-20 21:52:00 -04:00
imcompiling e34d847364 Add all settings commands for clan points.
build_develop / build (push) Successful in 1m25s
2024-09-20 21:49:13 -04:00
imcompiling 50d10f5168 Rework point settings to combine clan and event points into same class for simplicity. 2024-09-20 21:43:08 -04:00
imcompiling a01503c2c3 Add new sql tables to sql data classes 2024-09-20 21:21:07 -04:00
imcompiling 4c40a4c0e5 Reordered to alphabetical like rest of classes.
build_develop / build (push) Successful in 1m25s
2024-09-20 21:19:23 -04:00
imcompiling c3f25b6b54 Add settings to DakinySettings.
build_develop / build (push) Has been cancelled
2024-09-20 21:18:14 -04:00
imcompiling e36b2425aa Add new tables for clan points.
build_develop / build (push) Successful in 1m24s
2024-09-20 21:15:09 -04:00
imcompiling d7a0480c2b Merge branch '78-config-for-skipping-election-cycle'
build_latest / build (push) Successful in 17s
build_release / build (push) Successful in 17s
build_develop / build (push) Has been cancelled
2024-09-07 15:49:21 -04:00
imcompiling 8457c09b5b Remove bad import.
build_develop / build (push) Successful in 1m24s
2024-09-07 15:46:52 -04:00
imcompiling 3c31d0ad33 Add active parameter to elections. Only return an election for use if the election is active. 2024-09-07 15:46:30 -04:00
12 changed files with 300 additions and 116 deletions
+15 -11
View File
@@ -23,6 +23,7 @@ class ChannelIds:
class Election:
"""Class defines relevant election dates."""
active: bool
election_start: str
highcouncil_election_announce: str
highcouncil_election_end: str
@@ -34,16 +35,6 @@ class Election:
voting_start: str
@dataclass
class EventSettings:
"""Class defines settings editable by Clan Council."""
host_points: int
participate_points: int
verify: bool
weekly_limit: int
@dataclass
class Params:
"""Class defines start parameters that shouldn't be changed."""
@@ -53,6 +44,19 @@ class Params:
guild_id: int
@dataclass
class PointSettings:
"""Class defines point settings editable by Clan Council."""
clan_points: int
clan_points_weekly_limit: int
events_weekly_limit: int
host_points: int
participate_points: int
verify_clan_points: bool
verify_events: bool
@dataclass
class RoleIds:
"""Class holds all role ids needed by bot."""
@@ -70,8 +74,8 @@ class DakinySettings(YAMLWizard):
channel_ids: ChannelIds
election_cycles: list[Election]
event_settings: EventSettings
params: Params
point_settings: PointSettings
role_ids: RoleIds
def save(self) -> None:
+2 -1
View File
@@ -49,6 +49,7 @@ class Election:
"""
self.cycle = cycle
self.election_cycle = settings.election_cycles[self.cycle]
self.active = self.election_cycle.active
self.election_start: datetime = get_datetime(self.election_cycle.election_start)
self.term_start: datetime = get_datetime(self.election_cycle.term_start)
self.term_end: datetime = get_datetime(self.election_cycle.term_end)
@@ -199,7 +200,7 @@ class Election:
election_thread: nextcord.Thread = self.guild.get_thread(self.get_election_info().ThreadId)
await election_thread.send(message)
vote_msg = await election_thread.send(
build_votes_str(settings, self.guild),
build_votes_str(self.guild),
view=VoteView(self.guild),
)
+1 -1
View File
@@ -66,7 +66,7 @@ class VoteSelect(nextcord.ui.StringSelect):
session.execute(stmt)
session.commit()
vote_message_str = build_votes_str(settings, interaction.guild)
vote_message_str = build_votes_str(interaction.guild)
vote_msg: nextcord.Message = await interaction.channel.fetch_message(vote_msg_id)
await vote_msg.edit(content=vote_message_str)
+16 -12
View File
@@ -5,7 +5,7 @@ from discord import TextInputStyle
from nextcord.ext import commands
from DakinySettings import settings
from utils import GUILDS, MAX_DISCORD_MESSAGE_LENGTH, record_event_points
from utils import GUILDS, MAX_DISCORD_MESSAGE_LENGTH, PointType, record_points
class RecordCog(commands.Cog, name="Record Commands"):
@@ -117,16 +117,18 @@ class RecordCog(commands.Cog, name="Record Commands"):
await interaction.response.send_message("Please only provide 1 recipient.", ephemeral=True)
return
elif recipient is not None: # noqa: RET505
record_event_points(
record_points(
[str(recipient.id)],
settings.event_settings.participate_points,
settings.point_settings.clan_points,
PointType.CLAN,
is_member=True,
)
recipient_str = f"<@{recipient.id}>"
elif non_discord_recipient is not None:
record_event_points(
record_points(
[non_discord_recipient],
settings.event_settings.participate_points,
settings.point_settings.clan_points,
PointType.CLAN,
is_member=False,
)
recipient_str = f'"{non_discord_recipient}"'
@@ -152,7 +154,7 @@ class EventView(nextcord.ui.View):
"""
def __init__(self, name: str, host_id: int):
super().__init__()
super().__init__(timeout=60 * 60 * 2) # timeout 2 hours
self.name: str = name
self.host_id: int = host_id
@@ -180,7 +182,7 @@ class EventView(nextcord.ui.View):
Removes view and embed from recording message.
Sends point information to database.
"""
if settings.event_settings.verify:
if settings.point_settings.verify_events:
verification_channel: nextcord.TextChannel = interaction.guild.get_channel(settings.channel_ids.admin)
verification_view = EventVerification(
self.event_participants.values.members, interaction.message.embeds[0].fields[1].value, self.host_id
@@ -337,18 +339,20 @@ def process_event(discord_users: list[nextcord.Member], non_discord_users: str,
"""
if discord_users:
discord_users_list: list[str] = [str(user.id) for user in discord_users if user.id is not host_id]
record_event_points(
record_points(
discord_users_list,
settings.event_settings.participate_points,
settings.point_settings.participate_points,
PointType.EVENT,
is_member=True,
)
if non_discord_users:
non_discord_users_list: list[str] = non_discord_users.split("\n")
record_event_points(
record_points(
non_discord_users_list,
settings.event_settings.participate_points,
settings.point_settings.participate_points,
PointType.EVENT,
is_member=False,
)
record_event_points([host_id], settings.event_settings.host_points, is_member=True)
record_points([host_id], settings.point_settings.host_points, PointType.EVENT, is_member=True)
+77 -19
View File
@@ -144,10 +144,67 @@ class SettingsCog(commands.Cog, name="Settings Commands"):
await interaction.response.send_message(f"Voting channel set to <#{channel_id}>.", ephemeral=True)
@settings.subcommand()
async def event_settings(self, interaction: nextcord.Interaction) -> None:
async def point_settings(self, interaction: nextcord.Interaction) -> None:
"""Subcommand parent. Never called."""
@event_settings.subcommand(description="Sets Event Host Points value.")
@point_settings.subcommand(description="Sets Points value.")
async def clan_points(
self,
interaction: nextcord.Interaction,
points: int = nextcord.SlashOption(
required=True, description="Number of Clan Points the recipient earns.", min_value=0
),
) -> None:
"""
Sets DakinySettings point_settings.clan_points to the given number of points.
Args:
interaction (nextcord.Interaction): Discord interaction of command.
points (int): Number of points to assign to PointSettings.points.
"""
settings.point_settings.clan_points = points
settings.save()
await interaction.response.send_message(f"Points set to {points}.", ephemeral=True)
@point_settings.subcommand(description="Sets Event Weekly Limit value for Clan Points.")
async def clan_points_weekly_limit(
self,
interaction: nextcord.Interaction,
points: int = nextcord.SlashOption(
required=True, description="Number of Clan Points a clan member can earn in a week.", min_value=0
),
) -> None:
"""
Sets DakinySettings point_settings.clan_points_weekly_limit to the given number of points.
Args:
interaction (nextcord.Interaction): Discord interaction of command.
points (int): Number of points to set.
"""
settings.point_settings.clan_points_weekly_limit = points
settings.save()
await interaction.response.send_message(f"Clan Points Weekly Limit set to {points}.", ephemeral=True)
@point_settings.subcommand(description="Sets Event Weekly Limit value.")
async def events_weekly_limit(
self,
interaction: nextcord.Interaction,
points: int = nextcord.SlashOption(
required=True, description="Number of Event Points a clan member can earn in a week.", min_value=0
),
) -> None:
"""
Sets DakinySettings point_settings.events_weekly_limit to the given number of points.
Args:
interaction (nextcord.Interaction): Discord interaction of command.
points (int): Number of points to set.
"""
settings.point_settings.events_weekly_limit = points
settings.save()
await interaction.response.send_message(f"Event Weekly Limit set to {points}.", ephemeral=True)
@point_settings.subcommand(description="Sets Event Host Points value.")
async def host_points(
self,
interaction: nextcord.Interaction,
@@ -156,17 +213,17 @@ class SettingsCog(commands.Cog, name="Settings Commands"):
),
) -> None:
"""
Sets DakinySettings event_settings.host_points to the given number of points.
Sets DakinySettings point_settings.host_points to the given number of points.
Args:
interaction (nextcord.Interaction): Discord interaction of command.
points (int): Number of points to set.
"""
settings.event_settings.host_points = points
settings.point_settings.host_points = points
settings.save()
await interaction.response.send_message(f"Event Host Points set to {points}.", ephemeral=True)
@event_settings.subcommand(description="Sets Event Participation Points value.")
@point_settings.subcommand(description="Sets Event Participation Points value.")
async def participate_points(
self,
interaction: nextcord.Interaction,
@@ -177,18 +234,18 @@ class SettingsCog(commands.Cog, name="Settings Commands"):
),
) -> None:
"""
Sets DakinySettings event_settings.participate_points to the given number of points.
Sets DakinySettings point_settings.participate_points to the given number of points.
Args:
interaction (nextcord.Interaction): Discord interaction of command.
points (int): Number of points to set.
"""
settings.event_settings.participate_points = points
settings.point_settings.participate_points = points
settings.save()
await interaction.response.send_message(f"Event Participation Points set to {points}.", ephemeral=True)
@event_settings.subcommand(description="Sets Event Participation Points value.")
async def verify(
@point_settings.subcommand(description="Sets whether to verify a clan point entry.")
async def verify_clan_points(
self,
interaction: nextcord.Interaction,
do_verification: bool = nextcord.SlashOption( # noqa: FBT001
@@ -197,34 +254,35 @@ class SettingsCog(commands.Cog, name="Settings Commands"):
),
) -> None:
"""
Sets DakinySettings event_settings.verify to the given number of points.
Sets DakinySettings point_settings.verify_clan_points to the given number of points.
Args:
interaction (nextcord.Interaction): Discord interaction of command.
do_verification (bool): If the bot should verify events.
"""
settings.event_settings.verify = do_verification
settings.point_settings.verify_clan_points = do_verification
settings.save()
await interaction.response.send_message(f"Event verification set to {do_verification}.", ephemeral=True)
@event_settings.subcommand(description="Sets Event Weekly Limit value.")
async def weekly_limit(
@point_settings.subcommand(description="Sets whether an event.")
async def verify_events(
self,
interaction: nextcord.Interaction,
points: int = nextcord.SlashOption(
required=True, description="Number of Event Points a clan member can earn in a week.", min_value=0
do_verification: bool = nextcord.SlashOption( # noqa: FBT001
required=True,
description="Whether an event should be verified or not.",
),
) -> None:
"""
Sets DakinySettings event_settings.weekly_limit to the given number of points.
Sets DakinySettings point_settings.verify_events to the given number of points.
Args:
interaction (nextcord.Interaction): Discord interaction of command.
points (int): Number of points to set.
do_verification (bool): If the bot should verify events.
"""
settings.event_settings.weekly_limit = points
settings.point_settings.verify_events = do_verification
settings.save()
await interaction.response.send_message(f"Event Weekly Limit set to {points}.", ephemeral=True)
await interaction.response.send_message(f"Event verification set to {do_verification}.", ephemeral=True)
@settings.subcommand()
async def role_ids(self, interaction: nextcord.Interaction) -> None:
+20 -8
View File
@@ -5,7 +5,7 @@ from nextcord.ext import application_checks, commands
from classes.election import Election
from DakinySettings import settings
from utils import GUILDS, get_current_election_cycle
from utils import GUILDS, get_active_election_cycle, get_quarter_name
ME = 206602713511886849
@@ -40,7 +40,7 @@ class TestCog(commands.Cog, name="Test commands."):
interaction (nextcord.Interaction): Interaction with the message.
"""
cycle = Election(
get_current_election_cycle(),
get_active_election_cycle(),
self.bot.get_guild(settings.params.guild_id),
)
await cycle.election_start_action()
@@ -56,7 +56,7 @@ class TestCog(commands.Cog, name="Test commands."):
interaction (nextcord.Interaction): Interaction with the message.
"""
cycle = Election(
get_current_election_cycle(),
get_active_election_cycle(),
self.bot.get_guild(settings.params.guild_id),
)
await cycle.voting_start_action()
@@ -72,7 +72,7 @@ class TestCog(commands.Cog, name="Test commands."):
interaction (nextcord.Interaction): Interaction with the message.
"""
cycle = Election(
get_current_election_cycle(),
get_active_election_cycle(),
self.bot.get_guild(settings.params.guild_id),
)
await cycle.voting_end_action()
@@ -88,7 +88,7 @@ class TestCog(commands.Cog, name="Test commands."):
interaction (nextcord.Interaction): Interaction with the message.
"""
cycle = Election(
get_current_election_cycle(),
get_active_election_cycle(),
self.bot.get_guild(settings.params.guild_id),
)
await cycle.highcouncil_election_start_action()
@@ -104,7 +104,7 @@ class TestCog(commands.Cog, name="Test commands."):
interaction (nextcord.Interaction): Interaction with the message.
"""
cycle = Election(
get_current_election_cycle(),
get_active_election_cycle(),
self.bot.get_guild(settings.params.guild_id),
)
await cycle.election_reminder()
@@ -120,7 +120,7 @@ class TestCog(commands.Cog, name="Test commands."):
interaction (nextcord.Interaction): Interaction with the message.
"""
cycle = Election(
get_current_election_cycle(),
get_active_election_cycle(),
self.bot.get_guild(settings.params.guild_id),
)
await cycle.check_date()
@@ -136,8 +136,20 @@ class TestCog(commands.Cog, name="Test commands."):
interaction (nextcord.Interaction): Interaction with the message.
"""
cycle = Election(
get_current_election_cycle(),
get_active_election_cycle(),
self.bot.get_guild(settings.params.guild_id),
)
await cycle.term_start_action()
await interaction.send("Test command sent.", ephemeral=True)
@test.subcommand()
@is_me()
async def get_quarter_name(self, interaction: nextcord.Interaction) -> None:
"""
Tests getting the quarter name.
Args:
interaction (nextcord.Interaction): Interaction with the message.
"""
name = get_quarter_name()
await interaction.send(name)
+109 -27
View File
@@ -7,12 +7,12 @@ from sqlalchemy import select
from sqlalchemy.orm import Session
from database_setup.sql_data_classes import (
EventPoints,
Points,
Presences,
Votes,
engine,
)
from utils import MAX_DISCORD_MESSAGE_LENGTH, VoteType
from utils import GUILDS, MAX_DISCORD_MESSAGE_LENGTH, PointType, VoteType
class ViewCog(commands.Cog, name="View Commands"):
@@ -21,7 +21,7 @@ class ViewCog(commands.Cog, name="View Commands"):
def __init__(self) -> None:
"""Default constructor for ViewCog class. Does nothing."""
@nextcord.slash_command()
@nextcord.slash_command(guild_ids=GUILDS)
async def view(self, interaction: nextcord.Interaction) -> None:
"""Parent slash command for set. Never called."""
@@ -49,12 +49,12 @@ class ViewCog(commands.Cog, name="View Commands"):
with Session(engine) as session:
if quarter_name:
ppdf = pd.read_sql_query(
sql=session.query(EventPoints).where(EventPoints.QuarterName.startswith(quarter_name)).statement,
sql=session.query(Points).where(Points.QuarterName.startswith(quarter_name)).statement,
con=engine,
)
else:
ppdf = pd.read_sql_query(
sql=session.query(EventPoints).statement,
sql=session.query(Points).statement,
con=engine,
)
@@ -63,30 +63,53 @@ class ViewCog(commands.Cog, name="View Commands"):
return
quarters = ppdf.groupby("QuarterName")
content = ""
for quarter in quarters.groups:
content += f"**{quarter}**\n"
unsorted_all = quarters.get_group(quarter).groupby("DiscordMember")
members = unsorted_all.get_group(name=True)
content = create_points_string(interaction, quarters, PointType.EVENT)
# there can be no non-discord members sometimes.
try:
sorted_non_members = unsorted_all.get_group(name=False).sort_values(by="UserId")
except KeyError:
sorted_non_members = None
sorted_members = members.sort_values(
by="UserId",
key=lambda user_ids: (
[interaction.guild.get_member(int(user_id)).display_name.lower() for user_id in user_ids]
if len(content) > MAX_DISCORD_MESSAGE_LENGTH:
await interaction.followup.send("Message length is too long, please select search to limit results.")
return
await interaction.followup.send(content=content, ephemeral=True)
@view.subcommand(description="Show event points.")
async def clan_points(
self,
interaction: nextcord.Interaction,
quarter_name: str = nextcord.SlashOption(
description="The quarter to view",
required=False,
),
) -> None:
"""
View the clan points.
Builds a message with the clan points for the quarter that matches with quarter_name.
Args:
interaction (nextcord.Interaction): Interaction with the message.
quarter_name (str, optional): Same of quarter to autofill with. Is SlashOption.
"""
# just in case
await interaction.response.defer(ephemeral=True)
with Session(engine) as session:
if quarter_name:
ppdf = pd.read_sql_query(
sql=session.query(Points).where(Points.QuarterName.startswith(quarter_name)).statement,
con=engine,
)
sorted_all = pd.concat([sorted_members, sorted_non_members], ignore_index=True, sort=False)
for row in sorted_all.itertuples():
userid = row.UserId
points = row.Points
discord_member = row.DiscordMember
content += f"<@{userid}>: {points}\n" if discord_member else f"{userid}: {points}\n"
content += "\n"
else:
ppdf = pd.read_sql_query(
sql=session.query(Points).statement,
con=engine,
)
if ppdf.empty:
await interaction.followup.send(f'Quarter "{quarter_name}" is invalid.', ephemeral=True)
return
quarters = ppdf.groupby("QuarterName")
content = create_points_string(interaction, quarters, PointType.CLAN)
if len(content) > MAX_DISCORD_MESSAGE_LENGTH:
await interaction.followup.send("Message length is too long, please select search to limit results.")
@@ -192,6 +215,7 @@ class ViewCog(commands.Cog, name="View Commands"):
await interaction.response.send_autocomplete(autofilled_elections[:25])
@event_points.on_autocomplete("quarter_name")
@clan_points.on_autocomplete("quarter_name")
async def event_points_autocomplete(self, interaction: nextcord.Interaction, quarter_name: str) -> None:
"""
Collects all elections that start with the election string.
@@ -201,7 +225,7 @@ class ViewCog(commands.Cog, name="View Commands"):
quarter_name (str): String to run autocomplete on.
"""
with Session(engine) as session:
stmt = select(EventPoints.QuarterName).distinct()
stmt = select(Points.QuarterName).distinct()
quarters = session.scalars(stmt)
# if empty compare string
@@ -213,3 +237,61 @@ class ViewCog(commands.Cog, name="View Commands"):
quarter for quarter in quarters if quarter.lower().startswith(quarter_name.lower())
]
await interaction.response.send_autocomplete(autofilled_quarters[:25])
def create_points_string(interaction: nextcord.Interaction, quarters: any, point_type: PointType) -> str:
"""
Creates the string to show how many clan/event points users have.
Args:
interaction (nextcord.Interaction): Discord interaction of the command.
quarters (any): Pandas objects grouped by quarters.
point_type (PointType): The type of points to create the message for.
Returns:
str: The string with the message content.
"""
content: str = ""
for quarter in quarters.groups:
content += f"**{quarter}**\n"
unsorted_all = quarters.get_group(quarter).groupby("DiscordMember")
members = unsorted_all.get_group(name=True)
# there can be no non-discord members sometimes.
try:
sorted_non_members = unsorted_all.get_group(name=False).sort_values(by="UserId")
except KeyError:
sorted_non_members = None
sorted_members = members.sort_values(
by="UserId",
key=lambda user_ids: ([member_lambda_string(interaction.guild, user_id) for user_id in user_ids]),
)
sorted_all = pd.concat([sorted_members, sorted_non_members], ignore_index=True, sort=False)
for row in sorted_all.itertuples():
points = row.EventPoints if point_type == PointType.EVENT else row.ClanPoints
discord_member = row.DiscordMember
if points > 0:
content += f"<@{row.UserId}>: {points}\n" if discord_member else f"{row.UserId}: {points}\n"
content += "\n"
return content
def member_lambda_string(guild: nextcord.Guild, user_id: int) -> str:
"""
Finds a user in a guild and returns the display name.
Returns the user id in string form if user is no longer in guild.
I don't know if I like this name very much.
Args:
guild (nextcord.Guild): Guild object to find the user in.
user_id (int): User id to get display name of.
Returns:
str: String to use for sorting.
"""
try:
return guild.get_member(int(user_id)).display_name.lower()
except AttributeError:
# The user is no longer a part of the guild
return str(user_id)
+5 -3
View File
@@ -13,7 +13,7 @@ from classes.election import Election
from DakinySettings import settings
from database_setup.sql_data_classes import UserBirthday, engine
from main import birthday_list
from utils import get_current_election_cycle
from utils import get_active_election_cycle
if TYPE_CHECKING:
import nextcord
@@ -75,13 +75,15 @@ class DailyLoop(commands.Cog, name="Daily Loop"):
Returns:
Election: Current election cycle, None if there is no election happening.
"""
cycle = get_current_election_cycle()
cycle = get_active_election_cycle()
# prevent exception from using cycle as an Int in an array
if cycle is None:
return None
return Election(cycle, self.bot.get_guild(settings.params.guild_id))
election = Election(cycle, self.bot.get_guild(settings.params.guild_id))
return election if election.active else None
def update_birthdays(self) -> None:
"""Goes through database to collect all birthdays that are today. Adds user's id to birthday list."""
+6 -4
View File
@@ -41,11 +41,12 @@ CREATE TABLE Presences (
Presence varchar(100) not null
);
CREATE TABLE EventPoints (
CREATE TABLE Points (
QuarterName varchar(30) not null,
UserId varchar(32) not null,
DiscordMember boolean not null,
Points int unsigned DEFAULT 0,
EventPoints int unsigned DEFAULT 0,
ClanPoints int unsigned DEFAULT 0,
PRIMARY KEY (QuarterName, UserId)
);
@@ -54,9 +55,10 @@ CREATE TABLE Messages (
Id bigint unsigned not null
);
CREATE TABLE EventPointsLimit (
CREATE TABLE PointsLimit (
UserId varchar(32) not null,
Points int unsigned DEFAULT 0,
EventPoints int unsigned DEFAULT 0,
ClanPoints int unsigned DEFAULT 0,
Week varchar(7) not null,
PRIMARY KEY (UserId, Week)
);
+2 -2
View File
@@ -10,11 +10,11 @@ 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
EventPointsLimit = Base.classes.EventPointsLimit
Messages = Base.classes.Messages
EventPoints = Base.classes.EventPoints
Presences = Base.classes.Presences
UserBirthday = Base.classes.UserBirthday
Votes = Base.classes.Votes
+1 -1
View File
@@ -20,7 +20,7 @@ dataclass-wizard = "^0.22.3"
[tool.poetry.group.dev.dependencies]
ruff = "^0.6.3"
ruff = "^0.6.9"
[build-system]
requires = ["poetry-core"]
+45 -26
View File
@@ -15,9 +15,9 @@ from sqlalchemy.orm import Session
from DakinySettings import settings
from database_setup.sql_data_classes import (
CouncilMembers,
EventPoints,
EventPointsLimit,
Messages,
Points,
PointsLimit,
Votes,
engine,
)
@@ -30,6 +30,7 @@ MAX_DISCORD_MESSAGE_LENGTH = 2000
GUILDS = [1000247361639030804, 239758371358638080]
VoteType = StrEnum("VoteType", ["COUNCIL", "DISCORDMANAGER", "CLANLEADER"])
PointType = StrEnum("PointType", ["CLAN", "EVENT"])
def get_datetime(day: str) -> datetime:
@@ -48,10 +49,10 @@ def get_datetime(day: str) -> datetime:
day = int(day.split("/")[1])
# ensure that year rolls over if current month is December and the next month is less than 12.
if today.month == calendar.DECEMBER and month < calendar.DECEMBER:
if today.month == calendar.DECEMBER and month < calendar.OCTOBER:
year += 1
if today.month == calendar.JANUARY and month > calendar.JANUARY:
if today.month == calendar.JANUARY and month > calendar.MARCH:
year -= 1
return datetime(year, month, day, tzinfo=UTC)
@@ -64,7 +65,7 @@ def get_election_name() -> str:
Returns:
str: the name of the election cycle
"""
cycle = get_current_election_cycle()
cycle = get_active_election_cycle()
current_cycle = settings.election_cycles[cycle]
return f"vote-{get_datetime(current_cycle.term_start).year}-{current_cycle.name}"
@@ -82,7 +83,7 @@ def get_quarter_name() -> str:
return f"{get_datetime(current_cycle.term_start).year}-{current_cycle.name}"
def get_current_election_cycle() -> int:
def get_active_election_cycle() -> int:
"""
Returns index for active election. An election is considered active between the election start and the term start.
@@ -104,7 +105,9 @@ def get_current_election_cycle() -> int:
def get_quarter_cycle() -> int:
"""
Returns the index for the current cycle/quarter. Defined as between term start and end. Shouldn't return none.
Returns the index for the current cycle/quarter. Defined as between term start and end.
Can return None if entire year isn't covered by cycles.
Returns:
int: cycle index for current cycle.
@@ -288,41 +291,46 @@ def decorate_dm_nomination(user_id: int, speech: str) -> str:
return msg
def record_event_points(users: Sequence[str], add_points: int, *, is_member: bool) -> None:
def record_points(users: Sequence[str], add_points: int, point_type: PointType, *, is_member: bool) -> None:
"""
Adds event points to all users listed.
Adds points to all users listed.
Checks if they don't exist in order to make a new entry, otherwise
adds points to the existing value. Can be used for hosting or participating.
adds points to the existing value. Can be used for hosting or participating in events or for the clan
points.
Args:
users (Sequence[int]): users that participated and need points.
add_points (int): number of points to add
point_type (PointType): which point type to add to
is_member (bool): whether the users are a discord member or not
"""
quarter_name = get_quarter_name()
with Session(engine) as session:
for user in users:
stmt = select(EventPoints).where(EventPoints.UserId == user).where(EventPoints.QuarterName == quarter_name)
stmt = select(Points).where(Points.UserId == user).where(Points.QuarterName == quarter_name)
points = session.scalar(stmt)
if points is None:
add_points = get_allowed_points(session, settings.event_settings.weekly_limit, user, add_points)
new_points = EventPoints(
points_obj = session.scalar(stmt)
add_points = get_allowed_points(session, point_type, user, add_points)
add_clan_points = add_points if point_type == PointType.CLAN else 0
add_event_points = add_points if point_type == PointType.EVENT else 0
if points_obj is None:
new_points = Points(
QuarterName=quarter_name,
UserId=user,
DiscordMember=is_member,
Points=add_points,
EventPoints=add_event_points,
ClanPoints=add_clan_points,
)
session.add(new_points)
else:
add_points = get_allowed_points(session, settings.event_settings.weekly_limit, user, add_points)
points.Points += add_points
points_obj.EventPoints += add_event_points
points_obj.ClanPoints += add_clan_points
session.commit()
def get_allowed_points(session: Session, points_limit: int, user: str, points: int) -> int:
def get_allowed_points(session: Session, point_type: PointType, user: str, points: int) -> int:
"""
Gets the maximum number of points a user can be given.
@@ -332,7 +340,7 @@ def get_allowed_points(session: Session, points_limit: int, user: str, points: i
Args:
session (Session): Open connection to database.
points_limit (int): Maximum number of points a user can be given in a week.
point_type (PointType): Which type of points to check the limit for.
user (str): user to be given points. Used to check a user's limit.
points (int): maximum number of points to give to user.
@@ -342,18 +350,29 @@ def get_allowed_points(session: Session, points_limit: int, user: str, points: i
# check for limit
today = datetime.now(tz=UTC).date()
this_week = f"{today.isocalendar().year}-{today.isocalendar().week}"
stmt = select(EventPointsLimit).where(EventPointsLimit.UserId == user).where(EventPointsLimit.Week == this_week)
points_limit = (
settings.point_settings.events_weekly_limit
if point_type == PointType.EVENT
else settings.point_settings.clan_points_weekly_limit
)
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.
limit = session.scalar(stmt)
if limit is None:
new_limit = EventPointsLimit(UserId=user, Points=points, Week=this_week)
limit_obj = session.scalar(stmt)
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(UserId=user, Week=this_week, EventPoints=event_points, ClanPoints=clan_points)
add_points = points
session.add(new_limit)
else:
add_points = points if limit.Points + points <= points_limit else (max(0, points_limit - limit.Points))
limit.Points += add_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.EventPoints += add_points
else:
limit_obj.ClanPoints += add_points
session.commit()