Compare commits

...
Author SHA1 Message Date
imcompiling d56251c0b9 Do not ping a user on their cakeday.
build_release / build (push) Successful in 17s
build_latest / build (push) Successful in 1m45s
2025-02-04 22:49:53 -05:00
imcompiling 667a600ec6 Add poetry lock file. 2025-02-04 22:46:51 -05:00
imcompiling 03a94ca6dd Turn on lockFileMaintenance for renovatebot. 2025-02-04 22:45:14 -05:00
imcompiling 9da598e4d9 Add renovatebot json config.
build_latest / build (push) Successful in 2m3s
2025-02-04 17:35:11 -05:00
imcompiling 3a280696ca Need to install specific poetry version, export gets removed in v2.
build_latest / build (push) Successful in 1m41s
build_release / build (push) Successful in 19s
2025-01-19 18:35:38 -05:00
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
imcompiling 70676a98dd Merge branch '77-verification-for-splits'
build_latest / build (push) Successful in 16s
build_release / build (push) Successful in 16s
2024-09-07 15:15:26 -04:00
imcompiling 512802def7 Add ability to configure event verification.
build_develop / build (push) Successful in 1m22s
2024-09-07 15:14:50 -04:00
imcompiling 87b21782ce Remove unneeded import. 2024-09-07 15:08:53 -04:00
imcompiling 38ef2f4cb8 process_event isn't part of the class anymore. 2024-09-07 15:07:59 -04:00
imcompiling 79706fbe34 Fix bad array index. Send event to event channel when approved.
build_develop / build (push) Successful in 1m22s
2024-09-07 15:05:34 -04:00
imcompiling 83d39d0002 Add an approval process for event submissions.
build_develop / build (push) Successful in 1m25s
2024-09-07 15:00:16 -04:00
imcompiling d81ff295dd Don't need to save right after loading.
build_latest / build (push) Successful in 1m26s
2024-09-05 15:40:35 -04:00
imcompiling 9bfbafe15b Merge branch '21-edit-configs-from-discord'
build_latest / build (push) Successful in 17s
build_release / build (push) Successful in 17s
2024-09-05 15:30:36 -04:00
imcompiling b42f04aada Implemented rest of config settings commands.
build_develop / build (push) Successful in 1m27s
2024-09-05 15:30:08 -04:00
imcompiling 2ba56ead82 Alphabetically sorted DakinySettings. 2024-09-05 15:21:23 -04:00
imcompiling be824de52e Add rest of setting channel id commands. 2024-09-05 15:02:40 -04:00
imcompiling d3084c4ddb Create new settings command. Create first channel id settings command.
build_develop / build (push) Successful in 1m25s
2024-09-05 14:49:30 -04:00
imcompiling da7e529502 Remove unneeded typechecking import.
build_develop / build (push) Successful in 1m20s
2024-09-03 19:47:49 -04:00
imcompiling 4044c84811 Change file copied by Dockerfile. Remove last references to old config in docstrings. 2024-09-03 19:46:02 -04:00
imcompiling 44586183fe Replace rest of bot.config references. Clear record.py of old config usage. 2024-09-03 19:43:37 -04:00
imcompiling 3a47f58382 Remove config param from edit_clan_council_info(). 2024-09-03 19:33:13 -04:00
imcompiling 06d81a4604 Remove config param from record_event_points(). 2024-09-03 19:31:40 -04:00
imcompiling b1f8e9d742 Remove config param from build_highcouncil_votes_str(). 2024-09-03 19:28:05 -04:00
imcompiling 609fa38f20 Remove config param from build_votes_str(). 2024-09-03 16:13:35 -04:00
imcompiling f81e95381e Remove config param from get_quarter_cycle(). 2024-09-03 16:08:27 -04:00
imcompiling 62f9902298 Remove config param from get_current_election_cycle(). Update test.py to use new settings. 2024-09-03 16:07:23 -04:00
imcompiling 1dd5a33da5 Remove config param from get_quarter_name(). 2024-09-03 16:04:33 -04:00
imcompiling 8dc2668f44 Remove config param from get_election_name(). 2024-09-03 16:03:36 -04:00
imcompiling 3b289e74c8 Updated files in classes directory to use new configs. Ensured usages are updated. 2024-09-03 15:57:50 -04:00
imcompiling 2244765d87 Organized settings better within the dataclass. 2024-09-03 15:13:47 -04:00
imcompiling 62f53bde55 Renamed to DakinySettings. Converted dataclass to YAMLWizard.
build_develop / build (push) Failing after 17s
2024-09-03 14:37:06 -04:00
imcompiling b15e99ca68 Remove hydra from main. Install PyYAML. 2024-09-03 14:14:22 -04:00
imcompiling 628152c22c Change DakinyConfig.py file name. 2024-09-03 13:53:11 -04:00
imcompiling f00b6502f6 Merge branch '65-database-changes'
build_release / build (push) Successful in 16s
build_latest / build (push) Successful in 16s
2024-09-03 11:55:04 -04:00
imcompiling a55a90b922 Fixed missed column name change.
build_develop / build (push) Successful in 1m24s
2024-09-03 11:52:40 -04:00
imcompiling 7e0ff1da18 Rename EventPoints.Cycle to EventPoints.QuarterName.
build_develop / build (push) Successful in 1m23s
2024-09-03 11:42:20 -04:00
imcompiling eb5102578d Change ElectionCycle class to Election. Change ElectionCycle column in database to ElectionName. Update some variables for clarification. 2024-09-03 11:24:28 -04:00
imcompiling 306e0d687a Update ElectionCycle to ElectionName in database creation script. 2024-09-03 11:10:10 -04:00
imcompiling 8fb59ba394 Remove unused noqa.
build_develop / build (push) Failing after 42s
2024-09-03 11:00:48 -04:00
imcompiling 7b521893d3 Rename ParticipationPoints to EventPoints. 2024-09-03 10:59:39 -04:00
imcompiling 19e654aed0 Update ruff version 2024-09-03 10:59:28 -04:00
imcompiling 0d38bc82c4 Merge commit 'b64e2471fa7fdca5bb925c54eaa9adee78a19e5d'
build_latest / build (push) Successful in 16s
build_release / build (push) Successful in 16s
build_develop / build (push) Successful in 2m10s
2024-08-02 16:23:45 -04:00
imcompiling b64e2471fa Prevent double period.
build_develop / build (push) Successful in 1m28s
2024-08-02 16:23:35 -04:00
imcompiling 0b5330f7ca Merge commit '4e64eac17669e5e9b4e4626b3e2f9501b55c7aac'
build_latest / build (push) Successful in 16s
build_release / build (push) Successful in 16s
2024-08-01 22:07:55 -04:00
26 changed files with 2997 additions and 459 deletions
-1
View File
@@ -105,7 +105,6 @@ venv/
ENV/
env.bak/
venv.bak/
poetry.lock
# Spyder project settings
.spyderproject
+3
View File
@@ -1,7 +1,10 @@
{
"cSpell.words": [
"aiofiles",
"autofilled",
"blurple",
"CAKEDAY",
"cakedays",
"CLANLEADER",
"Dakiny",
"DISCORDMANAGER",
+86
View File
@@ -0,0 +1,86 @@
"""File contains classes defining Dakiny settings structure."""
from dataclasses import dataclass
from dataclass_wizard import YAMLWizard
CONFIG_LOCATION = "conf/config.yaml"
@dataclass
class ChannelIds:
"""Class holds all channel ids needed by bot."""
admin: int # might want to rename this...
announcement: int
clan_info: int
event_post: int
general: int
voting: int
@dataclass
class Election:
"""Class defines relevant election dates."""
active: bool
election_start: str
highcouncil_election_announce: str
highcouncil_election_end: str
highcouncil_election_start: str
name: str
term_end: str
term_start: str
voting_end: str
voting_start: str
@dataclass
class Params:
"""Class defines start parameters that shouldn't be changed."""
discord_token: str
git_branch: str
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."""
clan_council: int
clan_council_elect: int
clan_member: int
event_host: int
high_council: int
@dataclass
class DakinySettings(YAMLWizard):
"""Class defines all configs required for Dakiny Bot."""
channel_ids: ChannelIds
election_cycles: list[Election]
params: Params
point_settings: PointSettings
role_ids: RoleIds
def save(self) -> None:
"""Saves settings to the yaml file."""
self.to_yaml_file(CONFIG_LOCATION)
settings = DakinySettings.from_yaml_file(CONFIG_LOCATION)
+2 -2
View File
@@ -5,7 +5,7 @@ RUN apt-get update && apt-get -y upgrade && apt-get -y install git sqlite3
WORKDIR /home
COPY config.py config.py
COPY DakinySettings.py DakinySettings.py
COPY main.py main.py
COPY utils.py utils.py
COPY pyproject.toml pyproject.toml
@@ -14,6 +14,6 @@ COPY classes classes/
COPY cogs cogs/
COPY database_setup database_setup/
RUN pip install --upgrade pip && pip install poetry && poetry export -f requirements.txt --output requirements.txt && pip install -r requirements.txt
RUN pip install --upgrade pip && pip install poetry==1.8.3 && poetry export -f requirements.txt --output requirements.txt && pip install -r requirements.txt
CMD ["python3", "-u", "./main.py"]
@@ -9,7 +9,7 @@ from classes.nominate_highcouncil_views import NominateHighCouncilView
from classes.nominate_views import NominateView
from classes.voting_highcouncil_views import VoteHighcouncilView
from classes.voting_views import VoteView
from config import DakinyConfig
from DakinySettings import settings
from database_setup.sql_data_classes import (
CouncilMembers,
ElectionInformation,
@@ -31,26 +31,25 @@ from utils import (
REQUIRED_VOTES_TO_BE_ELECTED = 3
class ElectionCycle:
class Election:
"""
ElectionCycle class.
Election class.
This class represents all the dates required for an election cycle along with all the functions needed to operate
the cycle.
"""
def __init__(self, cycle: int, config: DakinyConfig, guild: nextcord.Guild) -> None:
def __init__(self, cycle: int, guild: nextcord.Guild) -> None:
"""
Generates all dates relevant to the election cycle determined by the config and cycle.
Args:
cycle (int): Which election cycle this cycle is.
config (DakinyConfig): The entire config object.
guild (nextcord.Guild): The guild that this election is running in.
"""
self.config: DakinyConfig = config
self.cycle = cycle
self.election_cycle = self.config.election_cycles[self.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)
@@ -74,12 +73,12 @@ class ElectionCycle:
message = file.read()
next_election = get_datetime(
self.config.election_cycles[(self.cycle + 1) % len(self.config.election_cycles)].election_start
settings.election_cycles[(self.cycle + 1) % len(settings.election_cycles)].election_start
)
message = message.format(
clan_council_id=self.config.params.clan_council_role_id,
high_council_id=self.config.params.high_council_role_id,
clan_council_id=settings.role_ids.clan_council,
high_council_id=settings.role_ids.high_council,
election_start=self.election_start.strftime("%B %d"),
term_start=self.term_start.strftime("%B %d"),
term_end=self.term_end.strftime("%B %d"),
@@ -102,7 +101,7 @@ class ElectionCycle:
message = file.read()
return message.format(
clan_council_elect_role_id=self.config.params.clan_council_elect_role_id,
clan_council_elect_role_id=settings.role_ids.clan_council_elect,
highcouncil_election_announce=self.highcouncil_election_announce.strftime("%B %d"),
highcouncil_election_start=self.highcouncil_election_start.strftime("%B %d"),
highcouncil_election_end=self.highcouncil_election_end.strftime("%B %d"),
@@ -120,7 +119,7 @@ class ElectionCycle:
message = file.read()
return message.format(
clan_council_elect_role_id=self.config.params.clan_council_elect_role_id,
clan_council_elect_role_id=settings.role_ids.clan_council_elect,
term_start=self.term_start.strftime("%B %d"),
)
@@ -133,10 +132,10 @@ class ElectionCycle:
"""
with Path(f"{TEMPLATES_DIR}/vote_start.txt", encoding="utf8").open() as file:
message = file.read()
vote_end: datetime = get_datetime(self.config.election_cycles[self.cycle].voting_end)
vote_end: datetime = get_datetime(settings.election_cycles[self.cycle].voting_end)
return message.format(
clan_member_id=self.config.params.clan_member_role_id,
clan_member_id=settings.role_ids.clan_member,
voting_end=vote_end.strftime("%B %d"),
voting_end_day=vote_end.strftime("%A"),
)
@@ -162,8 +161,8 @@ class ElectionCycle:
async def election_start_action(self) -> None:
"""Performs required actions for when the election cycle starts."""
# create new channel for election
parent: nextcord.TextChannel = self.guild.get_channel(self.config.params.voting_parent_id)
quarter_name: str = get_election_name(self.config)
parent: nextcord.TextChannel = self.guild.get_channel(settings.channel_ids.voting)
quarter_name: str = get_election_name()
thread_msg: nextcord.Message = await parent.send(content=quarter_name)
@@ -176,14 +175,14 @@ class ElectionCycle:
for index, message in enumerate(messages):
# if last item, add the nominate button
if index + 1 == len(messages):
nominate_msg: nextcord.Message = await thread.send(message, view=NominateView(self.config, self.guild))
nominate_msg: nextcord.Message = await thread.send(message, view=NominateView(self.guild))
else:
await thread.send(message)
# create new entry into database.
with Session(engine) as session:
new_cycle = ElectionInformation(
ElectionCycle=quarter_name,
ElectionName=quarter_name,
ThreadId=thread.id,
CouncilNominationMsgId=nominate_msg.id,
)
@@ -201,14 +200,12 @@ class ElectionCycle:
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(self.config, self.guild),
view=VoteView(self.config, self.guild),
build_votes_str(self.guild),
view=VoteView(self.guild),
)
with Session(engine) as session:
stmt = select(ElectionInformation).where(
ElectionInformation.ElectionCycle == get_election_name(self.config)
)
stmt = select(ElectionInformation).where(ElectionInformation.ElectionName == get_election_name())
info = session.scalar(stmt)
info.CouncilVoteMsgId = vote_msg.id
session.commit()
@@ -220,18 +217,16 @@ class ElectionCycle:
async def voting_end_action(self) -> None:
"""Performs required actions for when voting time has ended."""
message = self.build_highcouncil_election_announce()
council_elect_role: nextcord.Role = self.guild.get_role(self.config.params.clan_council_elect_role_id)
council_elect_role: nextcord.Role = self.guild.get_role(settings.role_ids.clan_council_elect)
election_thread: nextcord.Thread = self.guild.get_thread(self.get_election_info().ThreadId)
nominate_msg: nextcord.Message = await election_thread.send(
message, view=NominateHighCouncilView(self.config, self.guild)
)
nominate_msg: nextcord.Message = await election_thread.send(message, view=NominateHighCouncilView(self.guild))
election_name = get_election_name(self.config)
election_name = get_election_name()
with Session(engine) as session:
# remove vote button from vote message
stmt = select(ElectionInformation).where(ElectionInformation.ElectionCycle == election_name)
stmt = select(ElectionInformation).where(ElectionInformation.ElectionName == election_name)
info = session.scalar(stmt)
vote_msg: nextcord.Message = await election_thread.fetch_message(info.CouncilVoteMsgId)
await vote_msg.edit(view=None)
@@ -243,7 +238,7 @@ class ElectionCycle:
# add the highcouncil role to everyone with 3 or more votes. set elected to true in database
stmt = (
select(Votes.VotedForId)
.where(Votes.ElectionCycle == election_name)
.where(Votes.ElectionName == election_name)
.where(Votes.Type == VoteType.COUNCIL)
)
vote_count = collections.Counter(session.scalars(stmt))
@@ -252,7 +247,7 @@ class ElectionCycle:
if votes >= REQUIRED_VOTES_TO_BE_ELECTED:
stmt = (
select(CouncilMembers)
.where(CouncilMembers.ElectionCycle == election_name)
.where(CouncilMembers.ElectionName == election_name)
.where(CouncilMembers.MemberId == user_id)
)
council_member = session.scalar(stmt)
@@ -272,14 +267,12 @@ class ElectionCycle:
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_highcouncil_votes_str(self.config, self.guild),
view=VoteHighcouncilView(self.config, self.guild),
build_highcouncil_votes_str(self.guild),
view=VoteHighcouncilView(self.guild),
)
with Session(engine) as session:
stmt = select(ElectionInformation).where(
ElectionInformation.ElectionCycle == get_election_name(self.config)
)
stmt = select(ElectionInformation).where(ElectionInformation.ElectionName == get_election_name())
info = session.scalar(stmt)
info.HighCouncilVoteMsgId = vote_msg.id
session.commit()
@@ -292,16 +285,16 @@ class ElectionCycle:
"""Marks the election as over."""
self.election_over = True
elect_role: nextcord.Role = self.guild.get_role(self.config.params.clan_council_elect_role_id)
council_role: nextcord.Role = self.guild.get_role(self.config.params.clan_council_role_id)
elect_role: nextcord.Role = self.guild.get_role(settings.role_ids.clan_council_elect)
council_role: nextcord.Role = self.guild.get_role(settings.role_ids.clan_council)
election_cycle = get_election_name(self.config)
election_name = get_election_name()
with Session(engine) as session:
# role management
stmt = (
select(CouncilMembers.MemberId)
.where(CouncilMembers.ElectionCycle == election_cycle)
.where(CouncilMembers.ElectionName == election_name)
.where(CouncilMembers.Elected == true())
)
@@ -320,13 +313,13 @@ class ElectionCycle:
# ties are undefined behavior.
stmt = (
select(Votes.VotedForId)
.where(Votes.ElectionCycle == election_cycle)
.where(Votes.ElectionName == election_name)
.where(Votes.Type == VoteType.DISCORDMANAGER)
)
dm_votes = collections.Counter(session.scalars(stmt))
stmt = (
select(Votes.VotedForId)
.where(Votes.ElectionCycle == election_cycle)
.where(Votes.ElectionName == election_name)
.where(Votes.Type == VoteType.CLANLEADER)
)
cl_votes = collections.Counter(session.scalars(stmt))
@@ -335,7 +328,7 @@ class ElectionCycle:
dm_winner = max(dm_votes, key=dm_votes.get)
stmt = (
update(CouncilMembers)
.where(CouncilMembers.ElectionCycle == election_cycle)
.where(CouncilMembers.ElectionName == election_name)
.where(CouncilMembers.MemberId == dm_winner)
.values(DiscordManager=True)
)
@@ -346,7 +339,7 @@ class ElectionCycle:
cl_winner = max(cl_votes, key=cl_votes.get)
stmt = (
update(CouncilMembers)
.where(CouncilMembers.ElectionCycle == election_cycle)
.where(CouncilMembers.ElectionName == election_name)
.where(CouncilMembers.MemberId == cl_winner)
.values(ClanLeader=True)
)
@@ -360,7 +353,7 @@ class ElectionCycle:
# clear view from vote message
stmt = select(ElectionInformation.HighCouncilVoteMsgId).where(
ElectionInformation.ElectionCycle == get_election_name(self.config)
ElectionInformation.ElectionName == get_election_name()
)
vote_msg_id = session.scalar(stmt)
vote_msg: nextcord.Message = await election_thread.fetch_message(vote_msg_id)
@@ -373,7 +366,7 @@ class ElectionCycle:
await election_thread.send("This election cycle is now over. Thank you everyone for participating!")
await election_thread.edit(archived=True, locked=True)
await edit_clan_council_info(self.guild, self.config)
await edit_clan_council_info(self.guild)
def get_election_info(self) -> ElectionInformation:
"""
@@ -383,16 +376,14 @@ class ElectionCycle:
ElectionInformation: The information from the database about the current election.
"""
with Session(engine) as session:
stmt = select(ElectionInformation).where(
ElectionInformation.ElectionCycle == get_election_name(self.config)
)
stmt = select(ElectionInformation).where(ElectionInformation.ElectionName == get_election_name())
return session.scalar(stmt)
async def election_reminder(self) -> None:
"""Sends a message to general chat reminder clan about election."""
general: nextcord.TextChannel = self.guild.get_channel(self.config.params.general_channel_id)
general: nextcord.TextChannel = self.guild.get_channel(settings.channel_ids.general)
message = f"Reminder to <@&{self.config.params.clan_member_role_id}>! Clan elections are happening! Head over "
message = f"Reminder to <@&{settings.role_ids.clan_member}>! Clan elections are happening! Head over "
message += f"to <#{self.get_election_info().ThreadId}> to cast your vote!"
await general.send(content=message)
+16 -25
View File
@@ -1,7 +1,7 @@
"""Contains various views to be used in the high council nomination process."""
import nextcord
from config import DakinyConfig
from DakinySettings import settings
from database_setup.sql_data_classes import CouncilMembers, engine
from nextcord.interactions import Interaction
from sqlalchemy import select
@@ -23,7 +23,6 @@ class NominateDMModal(nextcord.ui.Modal):
def __init__(
self,
config: DakinyConfig,
usr_id: int,
usr_nominated: bool, # noqa: FBT001
speech_content: str,
@@ -33,7 +32,6 @@ class NominateDMModal(nextcord.ui.Modal):
timeout=5 * 60, # 5 minutes
)
self.usr_id: int = usr_id
self.config: DakinyConfig = config
self.usr_nominated = usr_nominated
self.speech = nextcord.ui.TextInput(
@@ -58,10 +56,10 @@ class NominateDMModal(nextcord.ui.Modal):
with Session(engine) as session:
# user already in table
cycle = get_election_name(self.config)
election_name = get_election_name()
stmt = (
select(CouncilMembers)
.where(CouncilMembers.ElectionCycle == cycle)
.where(CouncilMembers.ElectionName == election_name)
.where(CouncilMembers.MemberId == self.usr_id)
)
council_elect = session.scalar(stmt)
@@ -85,7 +83,6 @@ class NominateCLModal(nextcord.ui.Modal):
def __init__(
self,
config: DakinyConfig,
usr_id: int,
usr_nominated: bool, # noqa: FBT001
speech_content: str,
@@ -95,7 +92,6 @@ class NominateCLModal(nextcord.ui.Modal):
timeout=5 * 60, # 5 minutes
)
self.usr_id: int = usr_id
self.config: DakinyConfig = config
self.usr_nominated = usr_nominated
self.speech = nextcord.ui.TextInput(
@@ -120,10 +116,10 @@ class NominateCLModal(nextcord.ui.Modal):
with Session(engine) as session:
# user already in table
cycle = get_election_name(self.config)
election_name = get_election_name()
stmt = (
select(CouncilMembers)
.where(CouncilMembers.ElectionCycle == cycle)
.where(CouncilMembers.ElectionName == election_name)
.where(CouncilMembers.MemberId == self.usr_id)
)
council_elect = session.scalar(stmt)
@@ -141,9 +137,8 @@ class NominateCLModal(nextcord.ui.Modal):
class NominateHighCouncilView(nextcord.ui.View):
"""The buttons to press to start your High Council nomination."""
def __init__(self, config: DakinyConfig, guild: nextcord.Guild):
def __init__(self, guild: nextcord.Guild):
super().__init__(timeout=None)
self.config: DakinyConfig = config
self.guild: nextcord.Guild = guild
@nextcord.ui.button(
@@ -163,20 +158,20 @@ class NominateHighCouncilView(nextcord.ui.View):
interaction (nextcord.Interaction): Interaction of user who pressed the button
"""
# enforce council elect only
if self.guild.get_role(self.config.params.clan_council_elect_role_id) not in interaction.user.roles:
if self.guild.get_role(settings.role_ids.clan_council_elect) not in interaction.user.roles:
await interaction.response.send_message(
f"You must have the <@&{self.config.params.clan_council_elect_role_id}> role to vote.",
f"You must have the <@&{settings.role_ids.clan_council_elect}> role to vote.",
ephemeral=True,
)
return
cycle = get_election_name(self.config)
election_name = get_election_name()
usr_nominated = False
speech_content = ""
with Session(engine) as session:
stmt = (
select(CouncilMembers)
.where(CouncilMembers.ElectionCycle == cycle)
.where(CouncilMembers.ElectionName == election_name)
.where(CouncilMembers.MemberId == interaction.user.id)
)
obj = session.scalar(stmt)
@@ -187,9 +182,7 @@ class NominateHighCouncilView(nextcord.ui.View):
msg = await interaction.channel.fetch_message(obj.DiscordManagerSpeechId)
speech_content = msg.content.split(NOMINEE_SPLITTER)[1]
await interaction.response.send_modal(
NominateDMModal(self.config, interaction.user.id, usr_nominated, speech_content)
)
await interaction.response.send_modal(NominateDMModal(interaction.user.id, usr_nominated, speech_content))
@nextcord.ui.button(
label="Nominate for Clan Leader",
@@ -208,20 +201,20 @@ class NominateHighCouncilView(nextcord.ui.View):
interaction (nextcord.Interaction): Interaction of user who pressed the button
"""
# enforce council elect only
if self.guild.get_role(self.config.params.clan_council_elect_role_id) not in interaction.user.roles:
if self.guild.get_role(settings.role_ids.clan_council_elect) not in interaction.user.roles:
await interaction.response.send_message(
f"You must have the <@&{self.config.params.clan_council_elect_role_id}> role to vote.",
f"You must have the <@&{settings.role_ids.clan_council_elect}> role to vote.",
ephemeral=True,
)
return
cycle = get_election_name(self.config)
election_name = get_election_name()
usr_nominated = False
speech_content = ""
with Session(engine) as session:
stmt = (
select(CouncilMembers)
.where(CouncilMembers.ElectionCycle == cycle)
.where(CouncilMembers.ElectionName == election_name)
.where(CouncilMembers.MemberId == interaction.user.id)
)
obj = session.scalar(stmt)
@@ -232,6 +225,4 @@ class NominateHighCouncilView(nextcord.ui.View):
msg = await interaction.channel.fetch_message(obj.ClanLeaderSpeechId)
speech_content = msg.content.split(NOMINEE_SPLITTER)[1]
await interaction.response.send_modal(
NominateCLModal(self.config, interaction.user.id, usr_nominated, speech_content)
)
await interaction.response.send_modal(NominateCLModal(interaction.user.id, usr_nominated, speech_content))
+11 -16
View File
@@ -1,7 +1,7 @@
"""Contains various views to be used in the nomination process."""
import nextcord
from config import DakinyConfig
from DakinySettings import settings
from database_setup.sql_data_classes import CouncilMembers, engine
from nextcord.interactions import Interaction
from sqlalchemy import select
@@ -18,7 +18,6 @@ class NominateModal(nextcord.ui.Modal):
def __init__(
self,
config: DakinyConfig,
usr_id: int,
usr_nominated: bool, # noqa: FBT001
speech_content: str,
@@ -28,7 +27,6 @@ class NominateModal(nextcord.ui.Modal):
timeout=5 * 60, # 5 minutes
)
self.usr_id: int = usr_id
self.config: DakinyConfig = config
self.usr_nominated = usr_nominated
self.speech = nextcord.ui.TextInput(
@@ -50,13 +48,14 @@ class NominateModal(nextcord.ui.Modal):
"""
speech = decorate_nomination(interaction.user.id, self.speech.value)
election_name = get_election_name()
with Session(engine) as session:
if not self.usr_nominated:
msg = await interaction.channel.send(speech)
council_member = CouncilMembers(
ElectionCycle=get_election_name(self.config),
ElectionName=election_name,
MemberId=interaction.user.id,
SpeechMsgId=msg.id,
)
@@ -64,10 +63,9 @@ class NominateModal(nextcord.ui.Modal):
session.commit()
else:
# user already nominated, just need to update the election speech
cycle = get_election_name(self.config)
stmt = (
select(CouncilMembers)
.where(CouncilMembers.ElectionCycle == cycle)
.where(CouncilMembers.ElectionName == election_name)
.where(CouncilMembers.MemberId == self.usr_id)
)
obj = session.scalar(stmt)
@@ -79,9 +77,8 @@ class NominateModal(nextcord.ui.Modal):
class NominateView(nextcord.ui.View):
"""The button to press to start your nomination."""
def __init__(self, config: DakinyConfig, guild: nextcord.Guild):
def __init__(self, guild: nextcord.Guild):
super().__init__(timeout=None)
self.config: DakinyConfig = config
self.guild: nextcord.Guild = guild
@nextcord.ui.button(
@@ -91,7 +88,7 @@ class NominateView(nextcord.ui.View):
)
async def nominate(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: # noqa: ARG002
"""
Button to nominate onself for Clan Council.
Button to nominate oneself for Clan Council.
Gathers previous information about the user if they have already nominated themselves, then launches
the nomination modal to get their speech.
@@ -101,20 +98,20 @@ class NominateView(nextcord.ui.View):
interaction (nextcord.Interaction): Interaction of user who pressed the button
"""
# enforce members only
if self.guild.get_role(self.config.params.clan_member_role_id) not in interaction.user.roles:
if self.guild.get_role(settings.role_ids.clan_member) not in interaction.user.roles:
await interaction.response.send_message(
f"You must have the <@&{self.config.params.clan_member_role_id}> role to vote.",
f"You must have the <@&{settings.role_ids.clan_member}> role to vote.",
ephemeral=True,
)
return
cycle = get_election_name(self.config)
election_name = get_election_name()
usr_nominated = False
speech_content = ""
with Session(engine) as session:
stmt = (
select(CouncilMembers)
.where(CouncilMembers.ElectionCycle == cycle)
.where(CouncilMembers.ElectionName == election_name)
.where(CouncilMembers.MemberId == interaction.user.id)
)
obj = session.scalar(stmt)
@@ -123,6 +120,4 @@ class NominateView(nextcord.ui.View):
msg = await interaction.channel.fetch_message(obj.SpeechMsgId)
speech_content = msg.content.split(NOMINEE_SPLITTER)[1]
await interaction.response.send_modal(
NominateModal(self.config, interaction.user.id, usr_nominated, speech_content)
)
await interaction.response.send_modal(NominateModal(interaction.user.id, usr_nominated, speech_content))
+19 -22
View File
@@ -1,7 +1,7 @@
"""Contains various views to be used while voting for high council. Controls the logic when performing the vote."""
import nextcord
from config import DakinyConfig
from DakinySettings import settings
from database_setup.sql_data_classes import (
CouncilMembers,
ElectionInformation,
@@ -18,14 +18,13 @@ from utils import VoteType, build_highcouncil_votes_str, get_election_name
class VoteSelect(nextcord.ui.StringSelect):
"""Vote view string select."""
def __init__(self, options: list["SelectOption"], config: DakinyConfig, election_type: str) -> None:
def __init__(self, options: list["SelectOption"], election_type: str) -> None:
super().__init__(
placeholder="Choose 1",
min_values=1,
max_values=1,
options=options,
)
self.config = config
self.election_type = election_type
async def callback(self, interaction: Interaction) -> None:
@@ -38,17 +37,17 @@ class VoteSelect(nextcord.ui.StringSelect):
Args:
interaction (Interaction): Interaction with this callback.
"""
election_name: str = get_election_name(self.config)
election_name: str = get_election_name()
with Session(engine) as session:
stmt = select(ElectionInformation.HighCouncilVoteMsgId).where(
ElectionInformation.ElectionCycle == election_name
ElectionInformation.ElectionName == election_name
)
vote_msg_id = session.scalar(stmt)
voted_for = None if self.values[0] == "Abstain" else int(self.values[0])
new_vote = Votes(
ElectionCycle=election_name,
ElectionName=election_name,
VoterId=interaction.user.id,
VotedForId=voted_for,
Type=self.election_type,
@@ -63,7 +62,7 @@ class VoteSelect(nextcord.ui.StringSelect):
session.rollback()
stmt = (
update(Votes)
.where(Votes.ElectionCycle == election_name)
.where(Votes.ElectionName == election_name)
.where(Votes.VoterId == interaction.user.id)
.where(Votes.Type == self.election_type)
.values(VotedForId=voted_for)
@@ -71,7 +70,7 @@ class VoteSelect(nextcord.ui.StringSelect):
session.execute(stmt)
session.commit()
vote_message_str = build_highcouncil_votes_str(self.config, interaction.guild)
vote_message_str = build_highcouncil_votes_str(interaction.guild)
vote_msg: nextcord.Message = await interaction.channel.fetch_message(vote_msg_id)
await vote_msg.edit(content=vote_message_str)
@@ -83,9 +82,8 @@ class VoteSelect(nextcord.ui.StringSelect):
class VoteHighcouncilView(nextcord.ui.View):
"""The button to press to start voting for HighCouncil."""
def __init__(self, config: DakinyConfig, guild: nextcord.Guild):
def __init__(self, guild: nextcord.Guild):
super().__init__(timeout=None)
self.config = config
self.guild = guild
@nextcord.ui.button(
@@ -106,19 +104,19 @@ class VoteHighcouncilView(nextcord.ui.View):
interaction (nextcord.Interaction): Interaction of user who pressed the button
"""
# enforce council elect only
if self.guild.get_role(self.config.params.clan_council_elect_role_id) not in interaction.user.roles:
if self.guild.get_role(settings.role_ids.clan_council_elect) not in interaction.user.roles:
await interaction.response.send_message(
f"You must have the <@&{self.config.params.clan_council_elect_role_id}> role to vote.",
f"You must have the <@&{settings.role_ids.clan_council_elect}> role to vote.",
ephemeral=True,
)
return
election_name: str = get_election_name(self.config)
election_name: str = get_election_name()
with Session(engine) as session:
stmt = (
select(Votes.VotedForId)
.where(Votes.ElectionCycle == election_name)
.where(Votes.ElectionName == election_name)
.where(Votes.VoterId == interaction.user.id)
.where(Votes.Type == VoteType.CLANLEADER)
)
@@ -126,7 +124,7 @@ class VoteHighcouncilView(nextcord.ui.View):
stmt = (
select(CouncilMembers.MemberId)
.where(CouncilMembers.ElectionCycle == election_name)
.where(CouncilMembers.ElectionName == election_name)
.filter(CouncilMembers.ClanLeaderSpeechId.is_not(None))
)
@@ -152,7 +150,7 @@ class VoteHighcouncilView(nextcord.ui.View):
)
view = nextcord.ui.View()
view.add_item(VoteSelect(options=options, config=self.config, election_type=VoteType.CLANLEADER))
view.add_item(VoteSelect(options=options, election_type=VoteType.CLANLEADER))
await interaction.response.send_message(
content="Please vote for a Clan Leader!",
@@ -178,19 +176,19 @@ class VoteHighcouncilView(nextcord.ui.View):
interaction (nextcord.Interaction): Interaction of user who pressed the button
"""
# enforce council elect only
if self.guild.get_role(self.config.params.clan_council_elect_role_id) not in interaction.user.roles:
if self.guild.get_role(settings.role_ids.clan_council_elect) not in interaction.user.roles:
await interaction.response.send_message(
f"You must have the <@&{self.config.params.clan_council_elect_role_id}> role to vote.",
f"You must have the <@&{settings.role_ids.clan_council_elect}> role to vote.",
ephemeral=True,
)
return
election_name: str = get_election_name(self.config)
election_name: str = get_election_name()
with Session(engine) as session:
stmt = (
select(Votes.VotedForId)
.where(Votes.ElectionCycle == election_name)
.where(Votes.ElectionName == election_name)
.where(Votes.VoterId == interaction.user.id)
.where(Votes.Type == VoteType.DISCORDMANAGER)
)
@@ -198,7 +196,7 @@ class VoteHighcouncilView(nextcord.ui.View):
stmt = (
select(CouncilMembers.MemberId)
.where(CouncilMembers.ElectionCycle == election_name)
.where(CouncilMembers.ElectionName == election_name)
.filter(CouncilMembers.DiscordManagerSpeechId.is_not(None))
)
@@ -227,7 +225,6 @@ class VoteHighcouncilView(nextcord.ui.View):
view.add_item(
VoteSelect(
options=options,
config=self.config,
election_type=VoteType.DISCORDMANAGER,
)
)
+14 -18
View File
@@ -1,7 +1,7 @@
"""Contains various views to be used while voting. Controls the logic when performing the vote."""
import nextcord
from config import DakinyConfig
from DakinySettings import settings
from database_setup.sql_data_classes import (
CouncilMembers,
ElectionInformation,
@@ -18,14 +18,13 @@ from utils import VoteType, build_votes_str, get_election_name
class VoteSelect(nextcord.ui.StringSelect):
"""Vote view string select."""
def __init__(self, options: list["SelectOption"], config: DakinyConfig) -> None:
def __init__(self, options: list["SelectOption"]) -> None:
super().__init__(
placeholder="Choose 1",
min_values=1,
max_values=1,
options=options,
)
self.config = config
async def callback(self, interaction: Interaction) -> None:
"""
@@ -37,17 +36,15 @@ class VoteSelect(nextcord.ui.StringSelect):
Args:
interaction (Interaction): Interaction with this callback.
"""
election_name = get_election_name(self.config)
election_name = get_election_name()
with Session(engine) as session:
stmt = select(ElectionInformation.CouncilVoteMsgId).where(
ElectionInformation.ElectionCycle == election_name
)
stmt = select(ElectionInformation.CouncilVoteMsgId).where(ElectionInformation.ElectionName == election_name)
vote_msg_id = session.scalar(stmt)
voted_for = None if self.values[0] == "Abstain" else int(self.values[0])
new_vote = Votes(
ElectionCycle=election_name,
ElectionName=election_name,
VoterId=interaction.user.id,
VotedForId=voted_for,
Type=VoteType.COUNCIL,
@@ -61,7 +58,7 @@ class VoteSelect(nextcord.ui.StringSelect):
session.rollback()
stmt = (
update(Votes)
.where(Votes.ElectionCycle == election_name)
.where(Votes.ElectionName == election_name)
.where(Votes.VoterId == interaction.user.id)
.where(Votes.Type == VoteType.COUNCIL)
.values(VotedForId=voted_for)
@@ -69,7 +66,7 @@ class VoteSelect(nextcord.ui.StringSelect):
session.execute(stmt)
session.commit()
vote_message_str = build_votes_str(self.config, 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)
@@ -81,9 +78,8 @@ class VoteSelect(nextcord.ui.StringSelect):
class VoteView(nextcord.ui.View):
"""The button to press to start your nomination."""
def __init__(self, config: DakinyConfig, guild: nextcord.Guild):
def __init__(self, guild: nextcord.Guild):
super().__init__(timeout=None)
self.config = config
self.guild = guild
@nextcord.ui.button(label="Vote", style=nextcord.ButtonStyle.primary, custom_id="vote")
@@ -99,25 +95,25 @@ class VoteView(nextcord.ui.View):
interaction (nextcord.Interaction): Interaction of user who pressed the button
"""
# enforce members only
if self.guild.get_role(self.config.params.clan_member_role_id) not in interaction.user.roles:
if self.guild.get_role(settings.role_ids.clan_member) not in interaction.user.roles:
await interaction.response.send_message(
f"You must have the <@&{self.config.params.clan_member_role_id}> role to vote.",
f"You must have the <@&{settings.role_ids.clan_member}> role to vote.",
ephemeral=True,
)
return
election: str = get_election_name(self.config)
election_name: str = get_election_name()
with Session(engine) as session:
stmt = (
select(Votes.VotedForId)
.where(Votes.ElectionCycle == election)
.where(Votes.ElectionName == election_name)
.where(Votes.VoterId == interaction.user.id)
.where(Votes.Type == VoteType.COUNCIL)
)
voted_for = session.scalar(stmt)
stmt = select(CouncilMembers.MemberId).where(CouncilMembers.ElectionCycle == election)
stmt = select(CouncilMembers.MemberId).where(CouncilMembers.ElectionName == election_name)
options = [
SelectOption(
@@ -141,7 +137,7 @@ class VoteView(nextcord.ui.View):
)
view = nextcord.ui.View()
view.add_item(VoteSelect(options=options, config=self.config))
view.add_item(VoteSelect(options))
await interaction.response.send_message(
content="Please vote for a clan member!",
+4 -6
View File
@@ -9,13 +9,12 @@ from git import Repo
from nextcord.ext import commands
from classes.gitea_webhook import GiteaWebhook
from DakinySettings import settings
from utils import REPO_DIR, edit_clan_council_info, update_clan_messages
if TYPE_CHECKING:
import nextcord
from config import DakinyConfig
app = FastAPI()
REPO_URL = "https://gitea.cheezers.org/imcompiling/dakiny-messages.git"
@@ -30,7 +29,6 @@ class ClanMessages(commands.Cog, name="Clan Messages"):
def __init__(self, bot: commands.Bot):
self.bot = bot
self.config: DakinyConfig = self.bot.config
self.repo_setup()
@commands.Cog.listener()
@@ -58,13 +56,13 @@ class ClanMessages(commands.Cog, name="Clan Messages"):
channel_updates = {file.split("/")[1] for file in modified_files if file.split("/")[1] not in template_updates}
self.origin.pull()
guild: nextcord.Guild = self.bot.get_guild(self.config.params.guild_id)
guild: nextcord.Guild = self.bot.get_guild(settings.params.guild_id)
await update_clan_messages(guild, channel_updates)
# if the council members template has changed, update the message
if "council_members.txt" in template_updates:
await edit_clan_council_info(self.bot.get_guild(self.config.params.guild_id), self.config)
await edit_clan_council_info(self.bot.get_guild(settings.params.guild_id))
return {"message": "Successfully updated all discord messages."}
@@ -78,7 +76,7 @@ class ClanMessages(commands.Cog, name="Clan Messages"):
self.origin = self.repo.remote("origin")
if self.config.params.git_branch == "test":
if settings.params.git_branch == "test":
self.repo.create_head("test", self.origin.refs.test).set_tracking_branch(self.origin.refs.test).checkout()
else:
self.repo.create_head("main", self.origin.refs.main).set_tracking_branch(self.origin.refs.main).checkout()
+2
View File
@@ -7,6 +7,7 @@ from cogs.commands.refresh import RefreshCog
from cogs.commands.remove import RemoveCog
from cogs.commands.request import RequestCog
from cogs.commands.set import SetCog
from cogs.commands.settings import SettingsCog
from cogs.commands.test import TestCog
from cogs.commands.time import TimeCog
from cogs.commands.view import ViewCog
@@ -21,6 +22,7 @@ def setup(bot: commands.Bot) -> None:
bot.add_cog(RemoveCog())
bot.add_cog(RequestCog(bot))
bot.add_cog(SetCog())
bot.add_cog(SettingsCog())
bot.add_cog(TestCog(bot))
bot.add_cog(TimeCog())
bot.add_cog(ViewCog())
+113 -43
View File
@@ -4,8 +4,8 @@ import nextcord
from discord import TextInputStyle
from nextcord.ext import commands
from config import DakinyConfig
from utils import GUILDS, MAX_DISCORD_MESSAGE_LENGTH, record_event_points
from DakinySettings import settings
from utils import GUILDS, MAX_DISCORD_MESSAGE_LENGTH, PointType, record_points
class RecordCog(commands.Cog, name="Record Commands"):
@@ -42,9 +42,9 @@ class RecordCog(commands.Cog, name="Record Commands"):
host (nextcord.Member, optional): Alternative host to user using command.
"""
# check role
if interaction.guild.get_role(self.bot.config.params.event_host_role_id) not in interaction.user.roles:
if interaction.guild.get_role(settings.role_ids.event_host) not in interaction.user.roles:
await interaction.response.send_message(
f"You must have the <@&{self.bot.config.params.event_host_role_id}> role to record an event.",
f"You must have the <@&{settings.role_ids.event_host}> role to record an event.",
ephemeral=True,
)
return
@@ -59,13 +59,13 @@ class RecordCog(commands.Cog, name="Record Commands"):
await interaction.response.send_message(
"Fill out event details and select all members who participated!",
view=EventView(name, self.bot.config, host.id),
view=EventView(name, host.id),
ephemeral=True,
embed=embed,
)
@record.subcommand(description="Record a miscellaneous point for a clan member.")
async def point( # noqa: PLR0913
async def point(
self,
interaction: nextcord.Interaction,
activity: str = nextcord.SlashOption(
@@ -105,9 +105,9 @@ class RecordCog(commands.Cog, name="Record Commands"):
non_discord_recipient (str): Non Discord member to give point to.
"""
# role check
if interaction.guild.get_role(self.bot.config.params.clan_member_role_id) not in interaction.user.roles:
if interaction.guild.get_role(settings.role_ids.clan_member) not in interaction.user.roles:
await interaction.response.send_message(
f"You must have the <@&{self.bot.config.params.clan_member_role_id}> role to record points.",
f"You must have the <@&{settings.role_ids.clan_member}> role to record points.",
ephemeral=True,
)
return
@@ -117,18 +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(
self.bot.config,
record_points(
[str(recipient.id)],
self.bot.config.params.event_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(
self.bot.config,
record_points(
[non_discord_recipient],
self.bot.config.params.event_participate_points,
settings.point_settings.clan_points,
PointType.CLAN,
is_member=False,
)
recipient_str = f'"{non_discord_recipient}"'
@@ -136,11 +136,11 @@ class RecordCog(commands.Cog, name="Record Commands"):
await interaction.response.send_message("You must provide a recipient.", ephemeral=True)
return
event_channel: nextcord.TextChannel = interaction.guild.get_channel(
self.bot.config.params.event_post_channel_id
)
event_channel: nextcord.TextChannel = interaction.guild.get_channel(settings.channel_ids.event_post)
msg = f"<@{interaction.user.id}> has given {recipient_str} points for the {activity} activity. Justification: {justification}." # noqa: E501
msg = f"<@{interaction.user.id}> has given {recipient_str} points for the {activity} activity. Justification: {justification}" # noqa: E501
if not msg.endswith("."):
msg += "."
await event_channel.send(msg)
await interaction.response.send_message("Point has been added.", ephemeral=True)
@@ -153,11 +153,10 @@ class EventView(nextcord.ui.View):
Also contains a UserSelect object to pick discord members.
"""
def __init__(self, name: str, config: DakinyConfig, host_id: int):
super().__init__()
def __init__(self, name: str, host_id: int):
super().__init__(timeout=60 * 60 * 2) # timeout 2 hours
self.name: str = name
self.config: DakinyConfig = config
self.host_id: int = host_id
self.event_participants = ParticipantSelect()
@@ -183,35 +182,76 @@ class EventView(nextcord.ui.View):
Removes view and embed from recording message.
Sends point information to database.
"""
discord_users = self.event_participants.values.members # noqa: PD011 not a pandas object
if discord_users:
discord_users_list: list[str] = [str(user.id) for user in discord_users if user.id is not self.host_id]
record_event_points(
self.config,
discord_users_list,
self.config.params.event_participate_points,
is_member=True,
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
)
await verification_channel.send(
content="Event record request.", embed=interaction.message.embeds[0], view=verification_view
)
submission_response = "Event has been submitted to admins for approval."
else:
process_event(
self.event_participants.values.members,
interaction.message.embeds[0].fields[1].value,
self.host_id,
)
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_points(
self.config,
non_discord_users_list,
self.config.params.event_participate_points,
is_member=False,
)
record_event_points(self.config, [self.host_id], self.config.params.event_host_points, is_member=True)
event_channel: nextcord.TextChannel = interaction.guild.get_channel(self.config.params.event_post_channel_id)
event_channel: nextcord.TextChannel = interaction.guild.get_channel(settings.channel_ids.event_post)
await event_channel.send(embed=interaction.message.embeds[0])
await interaction.response.edit_message(content="Event has been submitted.", view=None, embed=None)
submission_response = "Event has been submitted."
await interaction.response.edit_message(content=submission_response, view=None, embed=None)
self.stop()
class EventVerification(nextcord.ui.View):
"""Class contains code to approve/deny event submissions."""
def __init__(
self,
discord_users: list[nextcord.Member],
non_discord_users: str,
host_id: int,
):
super().__init__(timeout=None, auto_defer=True)
self.discord_users: list[nextcord.Member] = discord_users
self.non_discord_users: str = non_discord_users
self.host_id: int = host_id
@nextcord.ui.button(label="Approve", style=nextcord.ButtonStyle.green)
async def approve(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: # noqa: ARG002
"""
Approve button for an event submission.
Calls the process event function. Records who approved the event.
Removes the view from the interaction message.
Args:
button (nextcord.ui.Button): Unused.
interaction (nextcord.Interaction): The interaction of the user clicking the button.
"""
process_event(self.discord_users, self.non_discord_users, self.host_id)
event_channel: nextcord.TextChannel = interaction.guild.get_channel(settings.channel_ids.event_post)
await event_channel.send(embed=interaction.message.embeds[0])
content = f"\nApproved by <@{interaction.user.id}>."
await interaction.response.edit_message(content=content, view=None)
@nextcord.ui.button(label="Deny", style=nextcord.ButtonStyle.red)
async def deny(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: # noqa: ARG002
"""
Deny button for an event submission. Just removes the view and logs who denied the request.
Args:
button (nextcord.ui.Button): Unused.
interaction (nextcord.Interaction): The interaction of the user clicking the button.
"""
content = f"\nDenied by <@{interaction.user.id}>."
await interaction.response.edit_message(content=content, view=None)
class ParticipantModal(nextcord.ui.Modal):
"""
This modal contains all the text edits for an event.
@@ -286,3 +326,33 @@ class ParticipantSelect(nextcord.ui.UserSelect):
participants: str = "\n".join([f"<@{participant.id}>" for participant in self.values.members])
embed.set_field_at(0, name="Participants", value=participants)
await interaction.response.edit_message(embed=embed)
def process_event(discord_users: list[nextcord.Member], non_discord_users: str, host_id: int) -> None:
"""
Processes all points to give to participants and host for an event.
Args:
discord_users (list[nextcord.Member]): List of discord members that participated in event.
non_discord_users (str): Newline separated list of non discord members that participated in event.
host_id (int): Discord ID of host.
"""
if discord_users:
discord_users_list: list[str] = [str(user.id) for user in discord_users if user.id is not host_id]
record_points(
discord_users_list,
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_points(
non_discord_users_list,
settings.point_settings.participate_points,
PointType.EVENT,
is_member=False,
)
record_points([host_id], settings.point_settings.host_points, PointType.EVENT, is_member=True)
+1 -1
View File
@@ -61,5 +61,5 @@ class RefreshCog(commands.Cog, name="Refresh Commands"):
Args:
interaction (nextcord.Interaction): Interaction with the command.
"""
await edit_clan_council_info(interaction.guild, self.bot.config)
await edit_clan_council_info(interaction.guild)
await interaction.response.send_message("Clan Council Info message updated.", ephemeral=True)
+4 -10
View File
@@ -1,16 +1,12 @@
"""Contains commands and views for request commands."""
from typing import TYPE_CHECKING
import nextcord
from nextcord.ext import commands
from sqlalchemy.orm import Session
from DakinySettings import settings
from database_setup.sql_data_classes import Presences, engine
if TYPE_CHECKING:
from config import DakinyConfig
class RequestCog(commands.Cog, name="Request Commands"):
"""Class holds request commands."""
@@ -41,10 +37,9 @@ class RequestCog(commands.Cog, name="Request Commands"):
interaction (nextcord.Interaction): Interaction that invoked the command.
presence (str): The presence to add.
"""
config: DakinyConfig = self.bot.config
content = f'<@{interaction.user.id}> would like to add presence "{presence}."'
view = PresenceChange(presence)
await self.bot.get_channel(config.params.admin_channel_id).send(content=content, view=view)
await self.bot.get_channel(settings.channel_ids.admin).send(content=content, view=view)
await interaction.response.send_message("Request sent to admins.", ephemeral=True)
@request.subcommand(description="Request to change your Dakiny nickname")
@@ -65,10 +60,9 @@ class RequestCog(commands.Cog, name="Request Commands"):
interaction (nextcord.Interaction): Interaction that invoked the command.
new_nickname(str): The username to change to.
"""
config: DakinyConfig = self.bot.config
content = f'<@{interaction.user.id}> would like to change their username to "{new_nickname}."'
view = UsernameRequest(interaction.user.id, new_nickname)
await self.bot.get_channel(config.params.admin_channel_id).send(content=content, view=view)
await self.bot.get_channel(settings.channel_ids.admin).send(content=content, view=view)
await interaction.response.send_message("Request sent to admins.", ephemeral=True)
@@ -115,7 +109,7 @@ class UsernameRequest(nextcord.ui.View):
await interaction.followup.edit_message(interaction.message.id, content=content, view=None)
else:
await interaction.response.edit_message(
content="An error occured while changing the user's nickname.",
content="An error occurred while changing the user's nickname.",
view=None,
)
+379
View File
@@ -0,0 +1,379 @@
"""Contains commands and views for settings commands."""
import nextcord
from nextcord.ext import commands
from DakinySettings import settings
from utils import GUILDS
class SettingsCog(commands.Cog, name="Settings Commands"):
"""Class holds settings commands."""
def __init__(self) -> None:
"""Default constructor for SettingsCog class. Does nothing."""
@nextcord.slash_command(guild_ids=GUILDS, default_member_permissions=nextcord.Permissions(administrator=True))
async def settings(self, interaction: nextcord.Interaction) -> None:
"""Parent slash command for settings. Never called."""
@settings.subcommand()
async def channel_ids(self, interaction: nextcord.Interaction) -> None:
"""Subcommand parent. Never called."""
@channel_ids.subcommand(description="Sets channel that bot sends verification requests to admins.")
async def admin(
self,
interaction: nextcord.Interaction,
channel: nextcord.TextChannel = nextcord.SlashOption(
required=False, description="Channel to set to. If left blank, uses current channel."
),
) -> None:
"""
Sets DakinySettings channel_ids.admin value to given channel id or current channel id.
Args:
interaction (nextcord.Interaction): Discord interaction of command.
channel (nextcord.TextChannel, optional): Channel to set in settings.
"""
channel_id = interaction.channel_id if channel is None else channel.id
settings.channel_ids.admin = channel_id
settings.save()
await interaction.response.send_message(f"Admin verification channel set to <#{channel_id}>.", ephemeral=True)
@channel_ids.subcommand(description="Sets announcement channel.")
async def announcement(
self,
interaction: nextcord.Interaction,
channel: nextcord.TextChannel = nextcord.SlashOption(
required=False, description="Channel to set to. If left blank, uses current channel."
),
) -> None:
"""
Sets DakinySettings channel_ids.announcement value to given channel id or current channel id.
Args:
interaction (nextcord.Interaction): Discord interaction of command.
channel (nextcord.TextChannel, optional): Channel to set in settings.
"""
channel_id = interaction.channel_id if channel is None else channel.id
settings.channel_ids.announcement = channel_id
settings.save()
await interaction.response.send_message(f"Announcement channel set to <#{channel_id}>.", ephemeral=True)
@channel_ids.subcommand(description="Sets clan info channel. Channel used for tracking current council.")
async def clan_info(
self,
interaction: nextcord.Interaction,
channel: nextcord.TextChannel = nextcord.SlashOption(
required=False, description="Channel to set to. If left blank, uses current channel."
),
) -> None:
"""
Sets DakinySettings channel_ids.clan_info value to given channel id or current channel id.
Args:
interaction (nextcord.Interaction): Discord interaction of command.
channel (nextcord.TextChannel, optional): Channel to set in settings.
"""
channel_id = interaction.channel_id if channel is None else channel.id
settings.channel_ids.clan_info = channel_id
settings.save()
await interaction.response.send_message(f"Clan information channel set to <#{channel_id}>.", ephemeral=True)
@channel_ids.subcommand(
description="Sets event post channel. Channel is where event tracking information is posted."
)
async def event_post(
self,
interaction: nextcord.Interaction,
channel: nextcord.TextChannel = nextcord.SlashOption(
required=False, description="Channel to set to. If left blank, uses current channel."
),
) -> None:
"""
Sets DakinySettings channel_ids.event_post value to given channel id or current channel id.
Args:
interaction (nextcord.Interaction): Discord interaction of command.
channel (nextcord.TextChannel, optional): Channel to set in settings.
"""
channel_id = interaction.channel_id if channel is None else channel.id
settings.channel_ids.event_post = channel_id
settings.save()
await interaction.response.send_message(f"Event posting channel set to <#{channel_id}>.", ephemeral=True)
@channel_ids.subcommand(description="Sets general channel.")
async def general(
self,
interaction: nextcord.Interaction,
channel: nextcord.TextChannel = nextcord.SlashOption(
required=False, description="Channel to set to. If left blank, uses current channel."
),
) -> None:
"""
Sets DakinySettings channel_ids.general value to given channel id or current channel id.
Args:
interaction (nextcord.Interaction): Discord interaction of command.
channel (nextcord.TextChannel, optional): Channel to set in settings.
"""
channel_id = interaction.channel_id if channel is None else channel.id
settings.channel_ids.general = channel_id
settings.save()
await interaction.response.send_message(f"General channel set to <#{channel_id}>.", ephemeral=True)
@channel_ids.subcommand(description="Sets voting channel. Channel used as parent for voting threads.")
async def voting(
self,
interaction: nextcord.Interaction,
channel: nextcord.TextChannel = nextcord.SlashOption(
required=False, description="Channel to set to. If left blank, uses current channel."
),
) -> None:
"""
Sets DakinySettings channel_ids.voting value to given channel id or current channel id.
Args:
interaction (nextcord.Interaction): Discord interaction of command.
channel (nextcord.TextChannel, optional): Channel to set in settings.
"""
channel_id = interaction.channel_id if channel is None else channel.id
settings.channel_ids.voting = channel_id
settings.save()
await interaction.response.send_message(f"Voting channel set to <#{channel_id}>.", ephemeral=True)
@settings.subcommand()
async def point_settings(self, interaction: nextcord.Interaction) -> None:
"""Subcommand parent. Never called."""
@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,
points: int = nextcord.SlashOption(
required=True, description="Number of Event Points a host earns when hosting event.", min_value=0
),
) -> None:
"""
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.point_settings.host_points = points
settings.save()
await interaction.response.send_message(f"Event Host Points set to {points}.", ephemeral=True)
@point_settings.subcommand(description="Sets Event Participation Points value.")
async def participate_points(
self,
interaction: nextcord.Interaction,
points: int = nextcord.SlashOption(
required=True,
description="Number of Event Points a participant earns when attending an event.",
min_value=0,
),
) -> None:
"""
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.point_settings.participate_points = points
settings.save()
await interaction.response.send_message(f"Event Participation Points set to {points}.", ephemeral=True)
@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
required=True,
description="Whether an event should be verified or not.",
),
) -> None:
"""
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.point_settings.verify_clan_points = do_verification
settings.save()
await interaction.response.send_message(f"Event verification set to {do_verification}.", ephemeral=True)
@point_settings.subcommand(description="Sets whether an event.")
async def verify_events(
self,
interaction: nextcord.Interaction,
do_verification: bool = nextcord.SlashOption( # noqa: FBT001
required=True,
description="Whether an event should be verified or not.",
),
) -> None:
"""
Sets DakinySettings point_settings.verify_events to the given number of points.
Args:
interaction (nextcord.Interaction): Discord interaction of command.
do_verification (bool): If the bot should verify events.
"""
settings.point_settings.verify_events = do_verification
settings.save()
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:
"""Subcommand parent. Never called."""
@role_ids.subcommand(description="Sets Clan Council role id.")
async def clan_council(
self,
interaction: nextcord.Interaction,
role: nextcord.Role = nextcord.SlashOption(required=True, description="Role to set."),
) -> None:
"""
Sets DakinySettings role_ids.clan_council to the given role id.
Args:
interaction (nextcord.Interaction): Discord interaction of command.
role (nextcord.TextChannel): Role to set in settings.
"""
role_id = role.id
settings.role_ids.clan_council = role_id
settings.save()
await interaction.response.send_message(f"Clan Council role set to <@&{role_id}>.", ephemeral=True)
@role_ids.subcommand(description="Sets Clan Council Elect role id.")
async def clan_council_elect(
self,
interaction: nextcord.Interaction,
role: nextcord.Role = nextcord.SlashOption(required=True, description="Role to set."),
) -> None:
"""
Sets DakinySettings role_ids.clan_council_elect to the given role id.
Args:
interaction (nextcord.Interaction): Discord interaction of command.
role (nextcord.TextChannel): Role to set in settings.
"""
role_id = role.id
settings.role_ids.clan_council_elect = role_id
settings.save()
await interaction.response.send_message(f"Clan Council Elect role set to <@&{role_id}>.", ephemeral=True)
@role_ids.subcommand(description="Sets Clan Member role id.")
async def clan_member(
self,
interaction: nextcord.Interaction,
role: nextcord.Role = nextcord.SlashOption(required=True, description="Role to set."),
) -> None:
"""
Sets DakinySettings role_ids.clan_member to the given role id.
Args:
interaction (nextcord.Interaction): Discord interaction of command.
role (nextcord.TextChannel): Role to set in settings.
"""
role_id = role.id
settings.role_ids.clan_member = role_id
settings.save()
await interaction.response.send_message(f"Clan Member role set to <@&{role_id}>.", ephemeral=True)
@role_ids.subcommand(description="Sets Event Host role id.")
async def event_host(
self,
interaction: nextcord.Interaction,
role: nextcord.Role = nextcord.SlashOption(required=True, description="Role to set."),
) -> None:
"""
Sets DakinySettings role_ids.event_host to the given role id.
Args:
interaction (nextcord.Interaction): Discord interaction of command.
role (nextcord.TextChannel): Role to set in settings.
"""
role_id = role.id
settings.role_ids.event_host = role_id
settings.save()
await interaction.response.send_message(f"Event Host role set to <@&{role_id}>.", ephemeral=True)
@role_ids.subcommand(description="Sets High Council role id.")
async def high_council(
self,
interaction: nextcord.Interaction,
role: nextcord.Role = nextcord.SlashOption(required=True, description="Role to set."),
) -> None:
"""
Sets DakinySettings role_ids.high_council to the given role id.
Args:
interaction (nextcord.Interaction): Discord interaction of command.
role (nextcord.TextChannel): Role to set in settings.
"""
role_id = role.id
settings.role_ids.high_council = role_id
settings.save()
await interaction.response.send_message(f"High Council role set to <@&{role_id}>.", ephemeral=True)
+36 -43
View File
@@ -1,16 +1,11 @@
"""Commands for testing."""
from typing import TYPE_CHECKING
import nextcord
from nextcord.ext import application_checks, commands
from classes.election_cycle import ElectionCycle
from utils import GUILDS, get_current_election_cycle
if TYPE_CHECKING:
from config import DakinyConfig
from classes.election import Election
from DakinySettings import settings
from utils import GUILDS, get_active_election_cycle, get_quarter_name
ME = 206602713511886849
@@ -44,11 +39,9 @@ class TestCog(commands.Cog, name="Test commands."):
Args:
interaction (nextcord.Interaction): Interaction with the message.
"""
config: DakinyConfig = self.bot.config
cycle = ElectionCycle(
get_current_election_cycle(config),
config,
self.bot.get_guild(config.params.guild_id),
cycle = Election(
get_active_election_cycle(),
self.bot.get_guild(settings.params.guild_id),
)
await cycle.election_start_action()
await interaction.send("Test command sent.", ephemeral=True)
@@ -62,11 +55,9 @@ class TestCog(commands.Cog, name="Test commands."):
Args:
interaction (nextcord.Interaction): Interaction with the message.
"""
config: DakinyConfig = self.bot.config
cycle = ElectionCycle(
get_current_election_cycle(config),
config,
self.bot.get_guild(config.params.guild_id),
cycle = Election(
get_active_election_cycle(),
self.bot.get_guild(settings.params.guild_id),
)
await cycle.voting_start_action()
await interaction.send("Test command sent.", ephemeral=True)
@@ -80,11 +71,9 @@ class TestCog(commands.Cog, name="Test commands."):
Args:
interaction (nextcord.Interaction): Interaction with the message.
"""
config: DakinyConfig = self.bot.config
cycle = ElectionCycle(
get_current_election_cycle(config),
config,
self.bot.get_guild(config.params.guild_id),
cycle = Election(
get_active_election_cycle(),
self.bot.get_guild(settings.params.guild_id),
)
await cycle.voting_end_action()
await interaction.send("Test command sent.", ephemeral=True)
@@ -98,11 +87,9 @@ class TestCog(commands.Cog, name="Test commands."):
Args:
interaction (nextcord.Interaction): Interaction with the message.
"""
config: DakinyConfig = self.bot.config
cycle = ElectionCycle(
get_current_election_cycle(config),
config,
self.bot.get_guild(config.params.guild_id),
cycle = Election(
get_active_election_cycle(),
self.bot.get_guild(settings.params.guild_id),
)
await cycle.highcouncil_election_start_action()
await interaction.send("Test command sent.", ephemeral=True)
@@ -116,11 +103,9 @@ class TestCog(commands.Cog, name="Test commands."):
Args:
interaction (nextcord.Interaction): Interaction with the message.
"""
config: DakinyConfig = self.bot.config
cycle = ElectionCycle(
get_current_election_cycle(config),
config,
self.bot.get_guild(config.params.guild_id),
cycle = Election(
get_active_election_cycle(),
self.bot.get_guild(settings.params.guild_id),
)
await cycle.election_reminder()
await interaction.send("Test command sent.", ephemeral=True)
@@ -134,11 +119,9 @@ class TestCog(commands.Cog, name="Test commands."):
Args:
interaction (nextcord.Interaction): Interaction with the message.
"""
config: DakinyConfig = self.bot.config
cycle = ElectionCycle(
get_current_election_cycle(config),
config,
self.bot.get_guild(config.params.guild_id),
cycle = Election(
get_active_election_cycle(),
self.bot.get_guild(settings.params.guild_id),
)
await cycle.check_date()
await interaction.send("Test command sent.", ephemeral=True)
@@ -152,11 +135,21 @@ class TestCog(commands.Cog, name="Test commands."):
Args:
interaction (nextcord.Interaction): Interaction with the message.
"""
config: DakinyConfig = self.bot.config
cycle = ElectionCycle(
get_current_election_cycle(config),
config,
self.bot.get_guild(config.params.guild_id),
cycle = Election(
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)
+112 -32
View File
@@ -7,12 +7,12 @@ from sqlalchemy import select
from sqlalchemy.orm import Session
from database_setup.sql_data_classes import (
ParticipationPoints,
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,14 +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(ParticipationPoints)
.where(ParticipationPoints.Cycle.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(ParticipationPoints).statement,
sql=session.query(Points).statement,
con=engine,
)
@@ -64,31 +62,54 @@ class ViewCog(commands.Cog, name="View Commands"):
await interaction.followup.send(f'Quarter "{quarter_name}" is invalid.', ephemeral=True)
return
cycles = ppdf.groupby("Cycle")
content = ""
for cycle in cycles.groups:
content += f"**{cycle}**\n"
unsorted_all = cycles.get_group(cycle).groupby("DiscordMember")
members = unsorted_all.get_group(name=True)
quarters = ppdf.groupby("QuarterName")
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.")
@@ -151,7 +172,7 @@ class ViewCog(commands.Cog, name="View Commands"):
election_name (str): Is SlashOption. The name of the election to view votes for.
election_type(str): Is SlashOption. The type of election to search for. Choice from StrEnum VoteType.
"""
stmt = select(Votes).where(Votes.ElectionCycle == election_name).where(Votes.Type == election_type)
stmt = select(Votes).where(Votes.ElectionName == election_name).where(Votes.Type == election_type)
with Session(engine) as session:
# Check if election exists.
@@ -180,7 +201,7 @@ class ViewCog(commands.Cog, name="View Commands"):
election_name (str): String to run autocomplete on.
"""
with Session(engine) as session:
stmt = select(Votes.ElectionCycle).distinct()
stmt = select(Votes.ElectionName).distinct()
elections = session.scalars(stmt)
# if empty compare string
@@ -194,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.
@@ -203,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(ParticipationPoints.Cycle).distinct()
stmt = select(Points.QuarterName).distinct()
quarters = session.scalars(stmt)
# if empty compare string
@@ -215,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)
+20 -14
View File
@@ -9,18 +9,19 @@ from pytz import timezone
from sqlalchemy import select
from sqlalchemy.orm import Session
from classes.election_cycle import ElectionCycle
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
from config import DakinyConfig
KANDY = 239547993106284544
IGNORE_CAKEDAY = [266378407682506754]
class DailyLoop(commands.Cog, name="Daily Loop"):
"""Class for performing daily functions."""
@@ -43,7 +44,7 @@ class DailyLoop(commands.Cog, name="Daily Loop"):
election is over to delete the current election object.
"""
if self.current_election is None:
self.current_election = self.get_election_cycle()
self.current_election = self.get_election()
if self.current_election is not None:
await self.current_election.check_date()
@@ -65,25 +66,26 @@ class DailyLoop(commands.Cog, name="Daily Loop"):
await self.bot.wait_until_ready()
# Bot needs to know guilds to get the current cycle.
self.current_election = self.get_election_cycle()
self.current_election = self.get_election()
def get_election_cycle(self) -> ElectionCycle:
def get_election(self) -> Election:
"""
Creates an object for the current election cycle if one is active.
An election is considered active between the election start and the term start.
Returns:
ElectionCycle: Current election cycle, None if there is no election happening.
Election: Current election cycle, None if there is no election happening.
"""
config: DakinyConfig = self.bot.config
cycle = get_current_election_cycle(config)
cycle = get_active_election_cycle()
# prevent exception from using cycle as an Int in an array
if cycle is None:
return None
return ElectionCycle(cycle, config, self.bot.get_guild(config.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."""
@@ -108,11 +110,15 @@ class DailyLoop(commands.Cog, name="Daily Loop"):
Sends a message saying happy cakeday.
"""
today = datetime.now(tz=UTC).date()
guild: nextcord.Guild = self.bot.get_guild(self.bot.config.params.guild_id)
announce_channel: nextcord.TextChannel = guild.get_channel(self.bot.config.params.general_channel_id)
guild: nextcord.Guild = self.bot.get_guild(settings.params.guild_id)
announce_channel: nextcord.TextChannel = guild.get_channel(settings.channel_ids.general)
cakedays: list[nextcord.Member] = [
human for human in guild.humans if human.joined_at.month == today.month and human.joined_at.day == today.day
human
for human in guild.humans
if human.joined_at.month == today.month
and human.joined_at.day == today.day
and human.id not in IGNORE_CAKEDAY
]
for member in cakedays:
-49
View File
@@ -1,49 +0,0 @@
"""File contains classes defining the Hydra configuraton structure."""
from dataclasses import dataclass
@dataclass
class Params:
"""Class defines the various configuration parameters."""
admin_channel_id: int # might want to rename this...
announcement_category_id: int
clan_council_elect_role_id: int
clan_council_role_id: int
clan_info_channel_id: int
clan_member_role_id: int
discord_token: str
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
guild_id: int
high_council_role_id: int
voting_parent_id: int
@dataclass
class ElectionCycle:
"""Class defines relevant election dates."""
election_start: str
highcouncil_election_announce: str
highcouncil_election_end: str
highcouncil_election_start: str
name: str
term_end: str
term_start: str
voting_end: str
voting_start: str
@dataclass
class DakinyConfig:
"""Class defines all configs required for Dakiny Bot."""
election_cycles: list[ElectionCycle]
params: Params
+16 -14
View File
@@ -1,15 +1,15 @@
CREATE TABLE ElectionInformation(
ElectionCycle varchar(30) not null unique,
ElectionName varchar(30) not null unique,
ThreadId bigint unsigned not null,
CouncilVoteMsgId bigint unsigned,
HighCouncilVoteMsgId bigint unsigned,
CouncilNominationMsgId bigint unsigned,
HighCouncilNominationMsgId bigint unsigned,
PRIMARY KEY (ElectionCycle)
PRIMARY KEY (ElectionName)
);
CREATE TABLE CouncilMembers(
ElectionCycle varchar(30) not null,
ElectionName varchar(30) not null,
MemberId bigint unsigned not null,
SpeechMsgId bigint unsigned not null,
Elected boolean DEFAULT false,
@@ -17,17 +17,17 @@ CREATE TABLE CouncilMembers(
ClanLeader boolean DEFAULT false,
DiscordManagerSpeechId bigint unsigned,
ClanLeaderSpeechId bigint unsigned,
FOREIGN KEY (ElectionCycle) REFERENCES ElectionInformation(ElectionCycle),
PRIMARY KEY (ElectionCycle, MemberId)
FOREIGN KEY (ElectionName) REFERENCES ElectionInformation(ElectionName),
PRIMARY KEY (ElectionName, MemberId)
);
CREATE TABLE Votes(
ElectionCycle varchar(30) not null,
ElectionName varchar(30) not null,
VoterId bigint unsigned not null,
VotedForId bigint unsigned,
Type varchar(14) not null DEFAULT 'council' check("Type" in ('council', 'discordmanager', 'clanleader')),
FOREIGN KEY (ElectionCycle) REFERENCES ElectionInformation(ElectionCycle),
PRIMARY KEY (ElectionCycle, VoterId, Type)
FOREIGN KEY (ElectionName) REFERENCES ElectionInformation(ElectionName),
PRIMARY KEY (ElectionName, VoterId, Type)
);
CREATE TABLE UserBirthday (
@@ -41,12 +41,13 @@ CREATE TABLE Presences (
Presence varchar(100) not null
);
CREATE TABLE ParticipationPoints (
Cycle varchar(30) not null,
CREATE TABLE Points (
QuarterName varchar(30) not null,
UserId varchar(32) not null,
DiscordMember boolean not null,
Points int unsigned DEFAULT 0,
PRIMARY KEY (Cycle, UserId)
EventPoints int unsigned DEFAULT 0,
ClanPoints int unsigned DEFAULT 0,
PRIMARY KEY (QuarterName, UserId)
);
CREATE TABLE Messages (
@@ -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
ParticipationPoints = Base.classes.ParticipationPoints
Presences = Base.classes.Presences
UserBirthday = Base.classes.UserBirthday
Votes = Base.classes.Votes
+11 -27
View File
@@ -2,23 +2,17 @@
import logging
import os
import sys
import hydra
import nextcord
from hydra.core.config_store import ConfigStore
from nextcord.ext import commands
from classes.nominate_highcouncil_views import NominateHighCouncilView
from classes.nominate_views import NominateView
from classes.voting_highcouncil_views import VoteHighcouncilView
from classes.voting_views import VoteView
from config import DakinyConfig
from DakinySettings import settings
from database_setup import create_database
cs = ConfigStore.instance()
cs.store(name="dakiny_config", node=DakinyConfig)
birthday_list: list[int] = []
logger = logging.getLogger("nextcord")
@@ -28,26 +22,19 @@ handler.setFormatter(logging.Formatter("%(asctime)s:%(levelname)s:%(name)s: %(me
logger.addHandler(handler)
@hydra.main(config_path="conf", config_name="config")
def main(cfg: DakinyConfig) -> None:
"""
Builds and runs bot.
Args:
cfg (DakinyConfig): Passed in hydra configuration.
"""
# Allows privledged intents for monitoring members joining, roles editing, and role assignments
def main() -> None:
"""Builds and runs bot."""
# Allows privileged intents for monitoring members joining, roles editing, and role assignments
# These need to be enabled in the developer portal as well
intents = nextcord.Intents.default()
# Required in order to read messages (eg. prefix commands)
intents.message_content = True
# To enable the members priveleged intent:
# To enable the members privileged intent:
intents.members = True
bot = commands.Bot(intents=intents)
bot.config = cfg
# Get the modules of all cogs whose directory structure is ./cogs/<module_name>
for folder in os.listdir("cogs"):
@@ -58,18 +45,15 @@ def main(cfg: DakinyConfig) -> None:
"""When Discord is connected."""
assert bot.user is not None # noqa: S101 documentation does this
create_database # noqa: B018
bot.add_view(NominateView(cfg, bot.get_guild(cfg.params.guild_id)))
bot.add_view(NominateHighCouncilView(cfg, bot.get_guild(cfg.params.guild_id)))
bot.add_view(VoteView(cfg, bot.get_guild(cfg.params.guild_id)))
bot.add_view(VoteHighcouncilView(cfg, bot.get_guild(cfg.params.guild_id)))
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(bot.config.params.discord_token)
bot.run(settings.params.discord_token)
if __name__ == "__main__":
sys.argv.append("hydra.job.chdir=False")
sys.argv.append("hydra/job_logging=none")
sys.argv.append("hydra/hydra_logging=none")
main() # pylint: disable=no-value-for-parameter
main()
Generated
+2006
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -10,18 +10,17 @@ package-mode = false
python = "^3.12"
gitpython = "^3.1.43"
fastapi = "^0.111.0"
hydra-core = "^1.3.2"
nextcord = "^2.6.0"
pandas = "^2.2.2"
python-dotenv = "^1.0.1"
pytz = "^2024.1"
sqlalchemy = "^2.0.30"
aiofiles = "^23.2.1"
setuptools = "^70.0.0"
dataclass-wizard = "^0.22.3"
[tool.poetry.group.dev.dependencies]
ruff = "^0.4.5"
ruff = "^0.6.9"
[build-system]
requires = ["poetry-core"]
@@ -86,6 +85,7 @@ ignore = [
"E117",
"ISC001",
"ISC002",
"PD011", # doesn't like me using '.values' on embeds....
"Q000",
"Q001",
"Q002",
+20
View File
@@ -0,0 +1,20 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"lockFileMaintenance": {
"automerge": true,
"enabled": true
},
"minor": {
"automerge": true,
"automergeType": "branch"
},
"patch": {
"automerge": true,
"automergeType": "branch"
},
"pin": {
"automerge": true,
"automergeType": "branch"
},
"ignoreTests": true
}
+73 -78
View File
@@ -12,12 +12,12 @@ import nextcord
from sqlalchemy import select, true, update
from sqlalchemy.orm import Session
from config import DakinyConfig
from DakinySettings import settings
from database_setup.sql_data_classes import (
CouncilMembers,
EventPointsLimit,
Messages,
ParticipationPoints,
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,59 +49,50 @@ 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)
def get_election_name(config: DakinyConfig) -> str:
def get_election_name() -> str:
"""
Builds the unique name of the current election cycle.
Args:
config (DakinyConfig): the config used to know the election cycle.
Returns:
str: the name of the election cycle
"""
cycle = get_current_election_cycle(config)
current_cycle = config.election_cycles[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}"
def get_quarter_name(config: DakinyConfig) -> str:
def get_quarter_name() -> str:
"""
Builds the name of the current cycle/quarter.
Args:
config (DakinyConfig): the config used to know the election cycle.
Returns:
str: the name of the cycle.
"""
cycle = get_quarter_cycle(config)
current_cycle = config.election_cycles[cycle]
cycle = get_quarter_cycle()
current_cycle = settings.election_cycles[cycle]
return f"{get_datetime(current_cycle.term_start).year}-{current_cycle.name}"
def get_current_election_cycle(config: DakinyConfig) -> 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.
Args:
config (DakinyConfig): Config object to use
Returns:
int: cycle index for active election. None for no active cycle
"""
today = datetime.now(tz=UTC)
for cycle, election in enumerate(config.election_cycles):
for cycle, election in enumerate(settings.election_cycles):
if (
today.date() >= get_datetime(election.election_start).date()
and today.date() <= get_datetime(election.term_start).date()
@@ -108,21 +100,20 @@ def get_current_election_cycle(config: DakinyConfig) -> int:
return cycle
# It isn't a current election, get the quarter
return get_quarter_cycle(config)
return get_quarter_cycle()
def get_quarter_cycle(config: DakinyConfig) -> 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.
Args:
config (DakinyConfig): Config object to use
Can return None if entire year isn't covered by cycles.
Returns:
int: cycle index for current cycle.
"""
today = datetime.now(tz=UTC)
for cycle, election in enumerate(config.election_cycles):
for cycle, election in enumerate(settings.election_cycles):
if (
today.date() >= get_datetime(election.term_start).date()
and today.date() <= get_datetime(election.term_end).date()
@@ -132,12 +123,11 @@ def get_quarter_cycle(config: DakinyConfig) -> int:
return None
def build_votes_str(config: DakinyConfig, guild: nextcord.Guild) -> str:
def build_votes_str(guild: nextcord.Guild) -> str:
"""
Creates a string that contains all nominees and their votes to be sent as a discord message.
Args:
config (DakinyConfig): Dakiny config object
guild (nextcord.Guild): The guild object. Used to get display names for sorting.
Returns:
@@ -145,10 +135,10 @@ def build_votes_str(config: DakinyConfig, guild: nextcord.Guild) -> str:
"""
votes: dict[int, int] = {}
election_name = get_election_name(config)
election_name = get_election_name()
with Session(engine) as session:
stmt = select(CouncilMembers).where(CouncilMembers.ElectionCycle == election_name)
stmt = select(CouncilMembers).where(CouncilMembers.ElectionName == election_name)
for nominee in sorted(
session.scalars(stmt),
@@ -156,7 +146,7 @@ def build_votes_str(config: DakinyConfig, guild: nextcord.Guild) -> str:
):
votes[nominee.MemberId] = 0
stmt = select(Votes).where(Votes.ElectionCycle == election_name).where(Votes.Type == VoteType.COUNCIL)
stmt = select(Votes).where(Votes.ElectionName == election_name).where(Votes.Type == VoteType.COUNCIL)
for vote in session.scalars(stmt):
if vote.VotedForId is not None:
@@ -168,12 +158,11 @@ def build_votes_str(config: DakinyConfig, guild: nextcord.Guild) -> str:
return votes_str
def build_highcouncil_votes_str(config: DakinyConfig, guild: nextcord.Guild) -> str:
def build_highcouncil_votes_str(guild: nextcord.Guild) -> str:
"""
Creates a string that contains all high council nominees and their votes to be sent as a discord message.
Args:
config (DakinyConfig): Dakiny config object
guild (nextcord.Guild): The guild object. Used to get display names for sorting.
Returns:
@@ -182,12 +171,12 @@ def build_highcouncil_votes_str(config: DakinyConfig, guild: nextcord.Guild) ->
cl_votes: dict[int, int] = {}
dm_votes: dict[int, int] = {}
election_name = get_election_name(config)
election_name = get_election_name()
with Session(engine) as session:
stmt = (
select(CouncilMembers)
.where(CouncilMembers.ElectionCycle == election_name)
.where(CouncilMembers.ElectionName == election_name)
.filter(CouncilMembers.ClanLeaderSpeechId.is_not(None))
)
@@ -197,7 +186,7 @@ def build_highcouncil_votes_str(config: DakinyConfig, guild: nextcord.Guild) ->
):
cl_votes[nominee.MemberId] = 0
stmt = select(Votes).where(Votes.ElectionCycle == election_name).where(Votes.Type == VoteType.CLANLEADER)
stmt = select(Votes).where(Votes.ElectionName == election_name).where(Votes.Type == VoteType.CLANLEADER)
for vote in session.scalars(stmt):
if vote.VotedForId is not None:
@@ -205,7 +194,7 @@ def build_highcouncil_votes_str(config: DakinyConfig, guild: nextcord.Guild) ->
stmt = (
select(CouncilMembers)
.where(CouncilMembers.ElectionCycle == election_name)
.where(CouncilMembers.ElectionName == election_name)
.filter(CouncilMembers.DiscordManagerSpeechId.is_not(None))
)
@@ -215,7 +204,7 @@ def build_highcouncil_votes_str(config: DakinyConfig, guild: nextcord.Guild) ->
):
dm_votes[nominee.MemberId] = 0
stmt = select(Votes).where(Votes.ElectionCycle == election_name).where(Votes.Type == VoteType.DISCORDMANAGER)
stmt = select(Votes).where(Votes.ElectionName == election_name).where(Votes.Type == VoteType.DISCORDMANAGER)
for vote in session.scalars(stmt):
if vote.VotedForId is not None:
@@ -302,46 +291,46 @@ def decorate_dm_nomination(user_id: int, speech: str) -> str:
return msg
def record_event_points(config: DakinyConfig, 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:
config (DakinyConfig): Dakiny config for getting points
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(config)
quarter_name = get_quarter_name()
with Session(engine) as session:
for user in users:
stmt = (
select(ParticipationPoints)
.where(ParticipationPoints.UserId == user)
.where(ParticipationPoints.Cycle == 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, config.params.event_points_weekly_limit, user, add_points)
new_points = ParticipationPoints(
Cycle=quarter_name,
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, config.params.event_points_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.
@@ -351,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.
@@ -361,22 +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()
@@ -510,33 +506,32 @@ async def edit_clan_message(
session.commit()
async def edit_clan_council_info(guild: nextcord.Guild, config: DakinyConfig) -> None:
async def edit_clan_council_info(guild: nextcord.Guild) -> None:
"""
Gets the winners of the elections and updates the permanent message that states the current council.
Args:
guild (nextcord.Guild): Guild object that to use to get channel.
config (DakinyConfig): Configs
"""
clan_info_channel: nextcord.TextChannel = await guild.fetch_channel(config.params.clan_info_channel_id)
clan_info_channel: nextcord.TextChannel = await guild.fetch_channel(settings.channel_ids.clan_info)
election = get_election_name(config)
election_name = get_election_name()
with Session(engine) as session:
stmt = (
select(CouncilMembers.MemberId)
.where(CouncilMembers.ElectionCycle == election)
.where(CouncilMembers.ElectionName == election_name)
.where(CouncilMembers.DiscordManager == true())
)
discord_manager = session.scalar(stmt)
stmt = (
select(CouncilMembers.MemberId)
.where(CouncilMembers.ElectionCycle == election)
.where(CouncilMembers.ElectionName == election_name)
.where(CouncilMembers.ClanLeader == true())
)
clan_leader = session.scalar(stmt)
stmt = (
select(CouncilMembers.MemberId)
.where(CouncilMembers.ElectionCycle == election)
.where(CouncilMembers.ElectionName == election_name)
.where(CouncilMembers.Elected == true())
)
@@ -548,7 +543,7 @@ async def edit_clan_council_info(guild: nextcord.Guild, config: DakinyConfig) ->
f"<@{user_id}>" for user_id in council_member_ids if user_id not in (discord_manager, clan_leader)
]
quarter = get_quarter_name(config)
quarter = get_quarter_name()
async with aiofiles.open(f"{TEMPLATES_DIR}/council_members.txt", encoding="utf8") as file:
message = await file.read()