Fix all smells in utils.
This commit is contained in:
@@ -7,3 +7,4 @@ Dakiny clan's own discord bot!
|
|||||||
- For Elections: Bot assumes that it always did the step before it. If the step before an action didn't happen, behavior is undefined.
|
- For Elections: Bot assumes that it always did the step before it. If the step before an action didn't happen, behavior is undefined.
|
||||||
Object creation in the middle of a cycle is only there for restarting the bot. All relevant information should already
|
Object creation in the middle of a cycle is only there for restarting the bot. All relevant information should already
|
||||||
exist in the database.
|
exist in the database.
|
||||||
|
- Bot is running on machine that is usint UTC time. All today() calls specify UTC time zone.
|
||||||
@@ -15,6 +15,7 @@ pandas = "1.5.2"
|
|||||||
python-dotenv = "1.0.0"
|
python-dotenv = "1.0.0"
|
||||||
pytz = "2022.7.1"
|
pytz = "2022.7.1"
|
||||||
sqlalchemy = "1.4.46"
|
sqlalchemy = "1.4.46"
|
||||||
|
aiofiles = "^23.2.1"
|
||||||
|
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
"""
|
"""File contains util functions."""
|
||||||
File contains util functions.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
import calendar
|
||||||
|
import datetime
|
||||||
import os
|
import os
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
from datetime import datetime
|
|
||||||
from enum import StrEnum
|
from enum import StrEnum
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import aiofiles
|
||||||
import nextcord
|
import nextcord
|
||||||
from sqlalchemy import select, true, update
|
from sqlalchemy import select, true, update
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
@@ -29,7 +30,7 @@ TEMPLATES_DIR = f"{REPO_DIR}/message_templates"
|
|||||||
VoteType = StrEnum("VoteType", ["COUNCIL", "DISCORDMANAGER", "CLANLEADER"])
|
VoteType = StrEnum("VoteType", ["COUNCIL", "DISCORDMANAGER", "CLANLEADER"])
|
||||||
|
|
||||||
|
|
||||||
def get_datetime(day: str) -> datetime:
|
def get_datetime(day: str) -> datetime.datetime:
|
||||||
"""
|
"""
|
||||||
Takes in a string month and day and returns a datetime object of that Month/Day this year.
|
Takes in a string month and day and returns a datetime object of that Month/Day this year.
|
||||||
|
|
||||||
@@ -39,23 +40,24 @@ def get_datetime(day: str) -> datetime:
|
|||||||
Returns:
|
Returns:
|
||||||
datetime: Datetime representation of Month/Day.
|
datetime: Datetime representation of Month/Day.
|
||||||
"""
|
"""
|
||||||
year = datetime.today().year
|
today = datetime.datetime.now(tz=datetime.UTC).date()
|
||||||
|
year = today.year
|
||||||
month = int(day.split("/")[0])
|
month = int(day.split("/")[0])
|
||||||
day = int(day.split("/")[1])
|
day = int(day.split("/")[1])
|
||||||
|
|
||||||
# ensure that year rolls over if current month is December and the next month is less than 12.
|
# ensure that year rolls over if current month is December and the next month is less than 12.
|
||||||
if datetime.today().month == 12 and month < 12:
|
if today.month == calendar.DECEMBER and month < calendar.DECEMBER:
|
||||||
year += 1
|
year += 1
|
||||||
|
|
||||||
if datetime.today().month == 1 and month > 1:
|
if today.month == calendar.JANUARY and month > calendar.JANUARY:
|
||||||
year -= 1
|
year -= 1
|
||||||
|
|
||||||
return datetime(year, month, day)
|
return datetime.datetime(year, month, day, tzinfo=datetime.UTC)
|
||||||
|
|
||||||
|
|
||||||
def get_election_name(config: DakinyConfig) -> str:
|
def get_election_name(config: DakinyConfig) -> str:
|
||||||
"""
|
"""
|
||||||
Builds the unique name of the current election cycle
|
Builds the unique name of the current election cycle.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
config (DakinyConfig): the config used to know the election cycle.
|
config (DakinyConfig): the config used to know the election cycle.
|
||||||
@@ -70,7 +72,7 @@ def get_election_name(config: DakinyConfig) -> str:
|
|||||||
|
|
||||||
def get_quarter_name(config: DakinyConfig) -> str:
|
def get_quarter_name(config: DakinyConfig) -> str:
|
||||||
"""
|
"""
|
||||||
Builds the name of the current cycle/quarter
|
Builds the name of the current cycle/quarter.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
config (DakinyConfig): the config used to know the election cycle.
|
config (DakinyConfig): the config used to know the election cycle.
|
||||||
@@ -94,7 +96,7 @@ def get_current_election_cycle(config: DakinyConfig) -> int:
|
|||||||
Returns:
|
Returns:
|
||||||
int: cycle index for active election. None for no active cycle
|
int: cycle index for active election. None for no active cycle
|
||||||
"""
|
"""
|
||||||
today = datetime.today()
|
today = datetime.datetime.now(tz=datetime.UTC).date()
|
||||||
|
|
||||||
for cycle, election in enumerate(config.election_cycles):
|
for cycle, election in enumerate(config.election_cycles):
|
||||||
if (
|
if (
|
||||||
@@ -109,7 +111,7 @@ def get_current_election_cycle(config: DakinyConfig) -> int:
|
|||||||
|
|
||||||
def get_quarter_cycle(config: DakinyConfig) -> int:
|
def get_quarter_cycle(config: DakinyConfig) -> 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. Shouldn't return none.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
config (DakinyConfig): Config object to use
|
config (DakinyConfig): Config object to use
|
||||||
@@ -117,7 +119,7 @@ def get_quarter_cycle(config: DakinyConfig) -> int:
|
|||||||
Returns:
|
Returns:
|
||||||
int: cycle index for current cycle.
|
int: cycle index for current cycle.
|
||||||
"""
|
"""
|
||||||
today = datetime.today()
|
today = datetime.datetime.now(tz=datetime.UTC).date()
|
||||||
for cycle, election in enumerate(config.election_cycles):
|
for cycle, election in enumerate(config.election_cycles):
|
||||||
if (
|
if (
|
||||||
today.date() >= get_datetime(election.term_start).date()
|
today.date() >= get_datetime(election.term_start).date()
|
||||||
@@ -170,10 +172,10 @@ def build_highcouncil_votes_str(config: DakinyConfig, guild: nextcord.Guild) ->
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
config (DakinyConfig): Daking config object
|
config (DakinyConfig): Daking config object
|
||||||
|
guild (nextcord.Guild): The guild object. Used to get display names for sorting.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: String that contains all nominees and their votes.
|
str: String that contains all nominees and their votes.
|
||||||
guild (nextcord.Guild): The guild object. Used to get display names for sorting.
|
|
||||||
"""
|
"""
|
||||||
cl_votes: dict[int, int] = {}
|
cl_votes: dict[int, int] = {}
|
||||||
dm_votes: dict[int, int] = {}
|
dm_votes: dict[int, int] = {}
|
||||||
@@ -298,16 +300,18 @@ def decorate_dm_nomination(user_id: int, speech: str) -> str:
|
|||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
|
||||||
def record_event_points(config: DakinyConfig, users: Sequence[str], is_member: bool, add_points: int):
|
def record_event_points(config: DakinyConfig, users: Sequence[str], is_member: bool, add_points: int) -> None: # noqa: FBT001
|
||||||
"""
|
"""
|
||||||
Adds event points to all users listed. Checks if they don't exist in order to make a new entry, otherwise
|
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.
|
adds points to the existing value. Can be used for hosting or participating.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
config (DakinyConfig): Dakiny config for getting points
|
config (DakinyConfig): Dakiny config for getting points
|
||||||
users (Sequence[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
|
is_member (bool): whether the users are a discord member or not
|
||||||
points (int): number of points to add
|
add_points (int): number of points to add
|
||||||
"""
|
"""
|
||||||
quarter_name = get_quarter_name(config)
|
quarter_name = get_quarter_name(config)
|
||||||
with Session(engine) as session:
|
with Session(engine) as session:
|
||||||
@@ -337,7 +341,9 @@ def record_event_points(config: DakinyConfig, users: Sequence[str], is_member: b
|
|||||||
|
|
||||||
def get_allowed_points(session: Session, points_limit: int, user: str, points: int) -> int:
|
def get_allowed_points(session: Session, points_limit: int, user: str, points: int) -> int:
|
||||||
"""
|
"""
|
||||||
Gets the maximum number of points a user can be given. Searches how many points they have been given
|
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
|
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.
|
go over points_limit) otherwise returns 0. Updates the limit object or creates a new one if one isn't found.
|
||||||
|
|
||||||
@@ -351,7 +357,8 @@ def get_allowed_points(session: Session, points_limit: int, user: str, points: i
|
|||||||
int: how many points are allowed to be given to the user.
|
int: how many points are allowed to be given to the user.
|
||||||
"""
|
"""
|
||||||
# check for limit
|
# check for limit
|
||||||
this_week = f"{datetime.today().isocalendar().year}-{datetime.today().isocalendar().week}"
|
today = datetime.datetime.now(tz=datetime.UTC).date()
|
||||||
|
this_week = f"{today.isocalendar().year}-{today.isocalendar().week}"
|
||||||
stmt = select(EventPointsLimit).where(EventPointsLimit.UserId == user).where(EventPointsLimit.Week == this_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
|
# if under limit, can add up to however many points would make the current limit equal to the
|
||||||
@@ -374,12 +381,13 @@ def get_allowed_points(session: Session, points_limit: int, user: str, points: i
|
|||||||
return add_points
|
return add_points
|
||||||
|
|
||||||
|
|
||||||
async def update_clan_messages(guild: nextcord.Guild, channel_updates: Sequence[str]):
|
async def update_clan_messages(guild: nextcord.Guild, channel_updates: Sequence[str]) -> None:
|
||||||
"""
|
"""
|
||||||
Updates all clan messages to contents in MESSAGES_DIR.
|
Updates all clan messages to contents in MESSAGES_DIR.
|
||||||
|
|
||||||
Does not to any git work. Is expected to be called after git is pulled.
|
Does not to any git work. Is expected to be called after git is pulled.
|
||||||
|
|
||||||
for ever directory, sort the files by number then edits/sends the message.
|
For every directory, sort the files by number then edits/sends the message.
|
||||||
|
|
||||||
Channel fetching/management is handled by another function.
|
Channel fetching/management is handled by another function.
|
||||||
Message editing and sending is handled by another function.
|
Message editing and sending is handled by another function.
|
||||||
@@ -388,7 +396,7 @@ async def update_clan_messages(guild: nextcord.Guild, channel_updates: Sequence[
|
|||||||
guild (nextcord.Guild): Guild object that all channels and messages belong to.
|
guild (nextcord.Guild): Guild object that all channels and messages belong to.
|
||||||
channel_updates: (Sequence[str]): a sequence of the channels to update.
|
channel_updates: (Sequence[str]): a sequence of the channels to update.
|
||||||
"""
|
"""
|
||||||
dirs = [dir_name for dir_name in channel_updates if os.path.isdir(f"{MESSAGES_DIR}/{dir_name}")]
|
dirs = [dir_name for dir_name in channel_updates if Path.is_dir(f"{MESSAGES_DIR}/{dir_name}")]
|
||||||
|
|
||||||
with Session(engine) as session:
|
with Session(engine) as session:
|
||||||
for dir_name in dirs:
|
for dir_name in dirs:
|
||||||
@@ -402,13 +410,15 @@ async def update_clan_messages(guild: nextcord.Guild, channel_updates: Sequence[
|
|||||||
for file_name in sorted_files:
|
for file_name in sorted_files:
|
||||||
message_name = f"{dir_name}/{file_name}"
|
message_name = f"{dir_name}/{file_name}"
|
||||||
file_path = f"{dir_path}/{file_name}"
|
file_path = f"{dir_path}/{file_name}"
|
||||||
with open(file_path, encoding="utf8") as reader:
|
with aiofiles.open(file_path, encoding="utf8") as reader:
|
||||||
await edit_clan_message(session, channel, message_name, reader.read(2000))
|
await edit_clan_message(session, channel, message_name, reader.read(2000))
|
||||||
|
|
||||||
|
|
||||||
async def get_clan_message_channel(session: Session, channel_name: str, guild: nextcord.Guild) -> nextcord.TextChannel:
|
async def get_clan_message_channel(session: Session, channel_name: str, guild: nextcord.Guild) -> nextcord.TextChannel:
|
||||||
"""
|
"""
|
||||||
Used to obtain a channel from the guild object. Searches database for an id to get the channel. If that is not
|
Used to obtain a channel from the guild object.
|
||||||
|
|
||||||
|
Searches database for an id to get the channel. If that is not
|
||||||
found, then searches through all channels in the guild to find one with a matching name. If that also doesn't
|
found, then searches through all channels in the guild to find one with a matching name. If that also doesn't
|
||||||
turn up a channel, then creates it. Adds an entry into database for last two paths.
|
turn up a channel, then creates it. Adds an entry into database for last two paths.
|
||||||
Assumes that there is an open session to the database.
|
Assumes that there is an open session to the database.
|
||||||
@@ -463,9 +473,11 @@ async def edit_clan_message(
|
|||||||
channel: nextcord.TextChannel,
|
channel: nextcord.TextChannel,
|
||||||
message_name: str,
|
message_name: str,
|
||||||
message_content: str,
|
message_content: str,
|
||||||
):
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Obtains a message id from database. If no id, then need to send a new message and record the id.
|
Obtains a message id from database.
|
||||||
|
|
||||||
|
If no id, then need to send a new message and record the id.
|
||||||
If there is a message, then edit the message. If that message is not found in discord, then need to
|
If there is a message, then edit the message. If that message is not found in discord, then need to
|
||||||
send a new message and record the id in the database.
|
send a new message and record the id in the database.
|
||||||
|
|
||||||
@@ -494,7 +506,7 @@ async def edit_clan_message(
|
|||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
|
|
||||||
async def edit_clan_council_info(guild: nextcord.Guild, config: DakinyConfig):
|
async def edit_clan_council_info(guild: nextcord.Guild, config: DakinyConfig) -> None:
|
||||||
"""
|
"""
|
||||||
Gets the winners of the elections and updates the perminant message that states the current council.
|
Gets the winners of the elections and updates the perminant message that states the current council.
|
||||||
|
|
||||||
@@ -524,11 +536,13 @@ async def edit_clan_council_info(guild: nextcord.Guild, config: DakinyConfig):
|
|||||||
.where(CouncilMembers.Elected == true())
|
.where(CouncilMembers.Elected == true())
|
||||||
)
|
)
|
||||||
|
|
||||||
council_members_strs = [f"<@{id}>" for id in session.scalars(stmt) if id not in (discord_manager, clan_leader)]
|
council_members_strs = [
|
||||||
|
f"<@{user_id}>" for user_id in session.scalars(stmt) if user_id not in (discord_manager, clan_leader)
|
||||||
|
]
|
||||||
|
|
||||||
quarter = get_quarter_name(config)
|
quarter = get_quarter_name(config)
|
||||||
|
|
||||||
with open(f"{TEMPLATES_DIR}/council_members.txt", encoding="utf8") as file:
|
with aiofiles.open(f"{TEMPLATES_DIR}/council_members.txt", encoding="utf8") as file:
|
||||||
message = file.read()
|
message = file.read()
|
||||||
message = message.format(
|
message = message.format(
|
||||||
quarter=quarter,
|
quarter=quarter,
|
||||||
|
|||||||
Reference in New Issue
Block a user