diff --git a/cogs/commands/record.py b/cogs/commands/record.py index 66afef0..2e53b23 100644 --- a/cogs/commands/record.py +++ b/cogs/commands/record.py @@ -7,7 +7,7 @@ from discord import TextInputStyle from nextcord.ext import commands from config import DakinyConfig -from utils import record_event_host, record_event_participation +from utils import record_event_points class RecordCog(commands.Cog, name="Record Commands"): @@ -129,14 +129,26 @@ class EventView(nextcord.ui.View): discord_users_list: list[str] = [ str(user.id) for user in discord_users if user.id is not self.host_id ] - record_event_participation(self.config, discord_users_list, True) + record_event_points( + self.config, + discord_users_list, + True, + self.config.params.event_participate_points, + ) non_discord_users = interaction.message.embeds[0].fields[1].value if non_discord_users: non_discord_users_list: list[str] = non_discord_users.split("\n") - record_event_participation(self.config, non_discord_users_list, False) + record_event_points( + self.config, + non_discord_users_list, + False, + self.config.params.event_participate_points, + ) - record_event_host(self.config, self.host_id) + record_event_points( + self.config, [self.host_id], True, self.config.params.event_host_points + ) event_channel: nextcord.TextChannel = interaction.guild.get_channel( self.config.params.event_post_channel_id diff --git a/config.py b/config.py index 6510907..84b2144 100644 --- a/config.py +++ b/config.py @@ -21,6 +21,7 @@ class Params: event_host_points: int event_host_role_id: int event_participate_points: int + event_points_weekly_limit: int event_post_channel_id: int general_channel_id: int git_branch: str diff --git a/database_setup/database.sql b/database_setup/database.sql index 03ad9cb..652d7ca 100644 --- a/database_setup/database.sql +++ b/database_setup/database.sql @@ -52,4 +52,11 @@ CREATE TABLE ParticipationPoints ( CREATE TABLE Messages ( Path varchar(100) PRIMARY KEY, Id bigint unsigned not null +); + +CREATE TABLE EventPointsLimit ( + UserId varchar(32) not null, + Points int unsigned DEFAULT 0, + Week varchar(7) not null, + PRIMARY KEY (UserId, Week) ); \ No newline at end of file diff --git a/database_setup/sql_data_classes.py b/database_setup/sql_data_classes.py index 007d432..67411f3 100644 --- a/database_setup/sql_data_classes.py +++ b/database_setup/sql_data_classes.py @@ -14,6 +14,7 @@ Base.prepare(autoload_with=engine) # define the classes CouncilMembers = Base.classes.CouncilMembers ElectionInformation = Base.classes.ElectionInformation +EventPointsLimit = Base.classes.EventPointsLimit Messages = Base.classes.Messages ParticipationPoints = Base.classes.ParticipationPoints Presences = Base.classes.Presences diff --git a/utils.py b/utils.py index 3caba58..18d9973 100644 --- a/utils.py +++ b/utils.py @@ -5,7 +5,7 @@ File contains util functions. import os from datetime import datetime from enum import StrEnum -from typing import Iterable +from typing import Sequence import nextcord from sqlalchemy import select, true, update @@ -14,6 +14,7 @@ from sqlalchemy.orm import Session from config import DakinyConfig from database_setup.sql_data_classes import ( CouncilMembers, + EventPointsLimit, Messages, ParticipationPoints, Votes, @@ -317,76 +318,98 @@ def decorate_dm_nomination(user_id: int, speech: str) -> str: return msg -def record_event_participation(config: DakinyConfig, users: list[str], is_member: bool): +def record_event_points( + config: DakinyConfig, users: Sequence[str], is_member: bool, add_points: int +): """ - Adds participation 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. + Adds event 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. Args: config (DakinyConfig): Dakiny config for getting points - users (list[int]): users that participated and need points. + users (Sequence[int]): users that participated and need points. is_member (bool): whether the users are a discord member or not + points (int): number of points to add """ - cycle = get_quarter_name(config) - participation_point_value = config.params.event_participate_points + quarter_name = get_quarter_name(config) with Session(engine) as session: for user in users: stmt = ( select(ParticipationPoints) .where(ParticipationPoints.UserId == user) - .where(ParticipationPoints.Cycle == cycle) + .where(ParticipationPoints.Cycle == quarter_name) ) points = session.scalar(stmt) if points is None: + add_points = get_allowed_points( + session, config.params.event_points_weekly_limit, user, add_points + ) new_points = ParticipationPoints( - Cycle=cycle, + Cycle=quarter_name, UserId=user, DiscordMember=is_member, - Points=participation_point_value, + Points=add_points, ) session.add(new_points) else: - points.Points += participation_point_value + add_points = get_allowed_points( + session, config.params.event_points_weekly_limit, user, add_points + ) + points.Points += add_points session.commit() -def record_event_host(config: DakinyConfig, user: int): +def get_allowed_points( + session: Session, points_limit: int, user: str, points: int +) -> int: """ - Adds host points to the given user. Checks if they don't exist in order to make a new entry, otherwise adds points - to the existing value. + Gets the maximum number of points a user can be given. Searches how many points they have been given + this week and subtracts that from the weekly limit. If under, returns up to "points" number of points (will not + go over points_limit) otherwise returns 0. Updates the limit object or creates a new one if one isn't found. Args: - config (DakinyConfig): Dakiny config for getting points - user (int): Id of user that hosted. + session (Session): Open connection to database. + points_limit (int): Maximum number of points a user can be given in a week. + user (str): user to be given points. Used to check a user's limit. + points (int): maximum number of points to give to user. + + Returns: + int: how many points are allowed to be given to the user. """ - cycle = get_quarter_name(config) - host_point_value = config.params.event_host_points - with Session(engine) as session: - stmt = ( - select(ParticipationPoints) - .where(ParticipationPoints.UserId == str(user)) - .where(ParticipationPoints.Cycle == cycle) + # check for limit + this_week = ( + f"{datetime.today().isocalendar().year}-{datetime.today().isocalendar().week}" + ) + stmt = ( + select(EventPointsLimit) + .where(EventPointsLimit.UserId == user) + .where(EventPointsLimit.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) + add_points = points + session.add(new_limit) + else: + add_points = ( + points + if limit.Points + points <= points_limit + else (points_limit - limit.Points if points_limit - limit.Points > 0 else 0) ) + limit.Points += add_points - points = session.scalar(stmt) - if points is None: - new_points = ParticipationPoints( - Cycle=cycle, - UserId=str(user), - DiscordMember=True, - Points=host_point_value, - ) - session.add(new_points) - else: - points.Points += host_point_value + session.commit() - session.commit() + return add_points -async def update_clan_messages(guild: nextcord.Guild, channel_updates: Iterable[str]): +async def update_clan_messages(guild: nextcord.Guild, channel_updates: Sequence[str]): """ Updates all clan messages to contents in MESSAGES_DIR. Does not to any git work. Is expected to be called after git is pulled. @@ -398,6 +421,7 @@ async def update_clan_messages(guild: nextcord.Guild, channel_updates: Iterable[ Args: guild (nextcord.Guild): Guild object that all channels and messages belong to. + channel_updates: (Sequence[str]): a sequence of the channels to update. """ dirs = [