Compare commits

...
Author SHA1 Message Date
imcompiling 969880fdb6 Merge commit 'f9da9fe7ef1b4d134884bba202af6a8ae5b06203'
build_latest / build (push) Successful in 1m10s
build_release / build (push) Successful in 49s
2023-12-13 17:36:01 -05:00
imcompiling f9da9fe7ef Merge branch 'main' into 33-fixes-from-mock-election
build_develop / build (push) Successful in 1m24s
2023-11-08 22:53:40 -05:00
imcompiling b5ea709c3a Add council role management to term start.
build_develop / build (push) Successful in 1m29s
2023-11-08 22:50:42 -05:00
imcompiling 9980c49764 Need to use display name in case someone doesn't have a nickname.
build_develop / build (push) Successful in 1m27s
2023-11-08 22:22:32 -05:00
imcompiling 8d02e28271 clean up test file a little.
build_latest / build (push) Successful in 1m26s
2023-11-08 16:31:38 -05:00
imcompiling 3a5add3c60 Remove unneeded config items.
build_latest / build (push) Successful in 1m29s
2023-11-08 16:28:07 -05:00
imcompiling dd1c304522 Add role check to nominate button.
build_develop / build (push) Successful in 1m24s
2023-11-08 16:17:16 -05:00
imcompiling 619ae92d03 Merge branch '30-threads'
build_develop / build (push) Successful in 46s
build_release / build (push) Successful in 44s
build_latest / build (push) Successful in 45s
2023-11-08 16:12:07 -05:00
imcompiling 431e9c0b35 Filled out term start action.
build_develop / build (push) Successful in 1m32s
2023-11-08 16:11:47 -05:00
imcompiling 27f90bbaef Add test for term start. 2023-11-08 15:51:06 -05:00
imcompiling c98bb5888c Give a message for thread to attach to. Lets it be public. 2023-11-08 15:44:19 -05:00
imcompiling c38632ca21 Remove another permission setting.
name a parameter.
2023-11-08 15:43:58 -05:00
imcompiling 75fc59ed5e Removed references to ElectionChannel.
Removed permission managing as threads don't have permissions.
2023-11-08 15:30:05 -05:00
imcompiling 8d2f2f7dba Thread create ini election start.
build_develop / build (push) Successful in 1m35s
Modified config and database to use threads.
2023-11-07 15:36:33 -05:00
imcompiling ab88652cdb Automatic reformat. Guessing from vscode update. 2023-11-07 15:35:43 -05:00
imcompiling b0e3162123 Change bot to use update query.
build_latest / build (push) Successful in 48s
build_develop / build (push) Successful in 50s
2023-11-07 14:59:07 -05:00
imcompiling 8c1eb8d11f Filled out a docstring.
For some reason, file wasn't formatted.
2023-11-07 14:52:22 -05:00
imcompiling 40d220e2bb Merge branch '32-view-votes-command'
build_develop / build (push) Successful in 50s
2023-11-07 14:44:33 -05:00
imcompiling 4e8032823d Fix check if election exists.
build_develop / build (push) Successful in 1m35s
Scalars doesn't return None if the thing doesn't exists while scalar does.
2023-11-07 14:44:07 -05:00
imcompiling 678fcd37e1 Add command to init.
Correctly set autocomplete.
Put newlines where I forgot to.
2023-11-07 14:12:42 -05:00
imcompiling 7530c59a94 Update function header.
build_develop / build (push) Successful in 1m25s
2023-11-06 20:27:36 -05:00
imcompiling 6abab5c4e5 Remove missedd code snippet.
build_develop / build (push) Successful in 1m26s
2023-11-06 20:26:24 -05:00
imcompiling 088de263cc Created initial view votes command.
build_develop / build (push) Successful in 1m26s
2023-11-06 20:20:58 -05:00
imcompiling ffc78a8814 Merge branch '33-fixes-from-mock-election'
build_latest / build (push) Successful in 50s
build_release / build (push) Successful in 47s
build_develop / build (push) Successful in 45s
2023-11-06 19:47:22 -05:00
imcompiling 3e88d62577 Edit election announcement first line.
build_develop / build (push) Successful in 1m24s
2023-11-06 19:46:54 -05:00
imcompiling 392044c658 Reduce length of header splitter.
build_develop / build (push) Successful in 1m34s
2023-11-06 19:45:49 -05:00
imcompiling 9442a294f3 Merge branch '28-add-election-reminder'
build_latest / build (push) Successful in 44s
build_develop / build (push) Successful in 53s
2023-11-03 17:32:00 -04:00
imcompiling d149caafea Add election reminder.
build_develop / build (push) Successful in 1m26s
Happens daily starting 3 days before election end.
2023-11-03 17:31:52 -04:00
imcompiling 000cb9b257 Merge branch '11-council-member-vote-automation'
build_latest / build (push) Successful in 1m23s
build_release / build (push) Successful in 45s
build_develop / build (push) Successful in 48s
2023-11-03 15:10:34 -04:00
imcompiling a5f0fb71e0 Update to the election rules.
build_latest / build (push) Successful in 1m26s
2023-11-02 16:36:16 -04:00
10 changed files with 399 additions and 109 deletions
+9 -5
View File
@@ -1,5 +1,5 @@
```fix
The Dakiny's admin team election
Dakiny's Clan Council election
```
Dakiny is an organization built by and for its members. We are a community of people who come together to support each other in the creation of this magical and unique experience. To sustain what we've created here, and to grow and involve into the future, we vote in our leadership every 3 months to provide an opportunity for new clan members to represent their faction of Dakiny in the Clan Council.
@@ -14,10 +14,14 @@ Each quarter, a channel is created here on Discord for clan members to cast thei
**Only clan members can vote and all clan members are granted exactly one equally weighted vote.** If you vote twice for different people, your vote will be annulled.
All clan members are permitted to run for a seat on the Clan Council as long as they:
**i) **have been in the clan for enough time to have participated in at least one election;
**ii)** have no warnings under their name
and
**iii)** have been active (hosted 1 clan event or participate in 1 clan event) in the clan in the past 12 weeks before current election.
**i)** have been in the clan for at least 13 weeks (enough time to have participated in at least one election);
**ii)** have no warnings under their name in the last 6 months and
**iii)** have been active in the clan for the past 13 weeks prior to the first day of elections. To be considered active in that timeframe, one must fulfill the following criteria: hosted/participated in at least three clan events, each one in a different week;
**iv)** are not running under aliases that hide their true identity.
Candidates are permitted to vote for themselves. Once a candidate reaches 3 votes from clan members, they have earned their seat, meaning no more votes can be cast for them.
+140 -44
View File
@@ -3,16 +3,21 @@ Contains the election cycle class
"""
import collections
from datetime import datetime
from datetime import datetime, timedelta
import nextcord
from sqlalchemy import select
from sqlalchemy import select, true
from sqlalchemy.orm import Session
from classes.nominate_views import NominateView
from classes.voting_views import VoteView
from config import DakinyConfig
from database_setup.sql_data_classes import CouncilMembers, ElectionInformation, Votes, engine
from database_setup.sql_data_classes import (
CouncilMembers,
ElectionInformation,
Votes,
engine,
)
from utils import build_votes_str, get_datetime, get_election_cycle_name
@@ -40,9 +45,15 @@ class ElectionCycle:
self.term_end: datetime = get_datetime(self.election_cycle.term_end)
self.voting_start: datetime = get_datetime(self.election_cycle.voting_start)
self.voting_end: datetime = get_datetime(self.election_cycle.voting_end)
self.highcouncil_election_announce: datetime = get_datetime(self.election_cycle.highcouncil_election_announce)
self.highcouncil_election_start: datetime = get_datetime(self.election_cycle.highcouncil_election_start)
self.highcouncil_election_end: datetime = get_datetime(self.election_cycle.highcouncil_election_end)
self.highcouncil_election_announce: datetime = get_datetime(
self.election_cycle.highcouncil_election_announce
)
self.highcouncil_election_start: datetime = get_datetime(
self.election_cycle.highcouncil_election_start
)
self.highcouncil_election_end: datetime = get_datetime(
self.election_cycle.highcouncil_election_end
)
self.guild: nextcord.Guild = guild
self.election_over: bool = False
@@ -55,11 +66,15 @@ class ElectionCycle:
long.
"""
with open("bot_messages/election_announcement.txt", mode="r", encoding="utf8") as file:
with open(
"bot_messages/election_announcement.txt", mode="r", encoding="utf8"
) as file:
message = file.read()
next_election = get_datetime(
self.config.election_cycles[(self.cycle + 1) % len(self.config.election_cycles)].election_start
self.config.election_cycles[
(self.cycle + 1) % len(self.config.election_cycles)
].election_start
)
message = message.format(
@@ -84,14 +99,22 @@ class ElectionCycle:
str: Message to send.
"""
with open("bot_messages/highcouncil_election.txt", mode="r", encoding="utf8") as file:
with open(
"bot_messages/highcouncil_election.txt", mode="r", encoding="utf8"
) as file:
message = file.read()
return message.format(
clan_council_elect_role_id=self.config.params.clan_council_elect_role_id,
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"),
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"
),
term_start=self.term_start.strftime("%B %d"),
)
@@ -103,7 +126,9 @@ class ElectionCycle:
str: Message to send.
"""
with open("bot_messages/highcouncil_vote.txt", mode="r", encoding="utf8") as file:
with open(
"bot_messages/highcouncil_vote.txt", mode="r", encoding="utf8"
) as file:
message = file.read()
return message.format(
@@ -121,7 +146,9 @@ class ElectionCycle:
with open("bot_messages/vote_start.txt", mode="r", encoding="utf8") as file:
message = file.read()
vote_end: datetime = get_datetime(self.config.election_cycles[self.cycle].voting_end)
vote_end: datetime = get_datetime(
self.config.election_cycles[self.cycle].voting_end
)
return message.format(
clan_member_id=self.config.params.clan_member_role_id,
@@ -140,6 +167,12 @@ class ElectionCycle:
await self.election_start_action()
elif today.date() == self.voting_start.date():
await self.voting_start_action()
elif (
(self.voting_end - timedelta(3)).date()
<= today.date()
< self.voting_end.date()
):
await self.election_reminder()
elif today.date() == self.voting_end.date():
await self.voting_end_action()
elif today.date() == self.highcouncil_election_start.date():
@@ -152,15 +185,21 @@ class ElectionCycle:
Performs required actions for when the election cycle starts.
"""
# create new channel for election
category: nextcord.CategoryChannel = self.guild.get_channel(self.config.params.active_category_id)
parent: nextcord.TextChannel = self.guild.get_channel(
self.config.params.voting_parent_id
)
quarter_name: str = get_election_cycle_name(self.config)
thread_msg: nextcord.Message = await parent.send(content=quarter_name)
# create new entry into database.
election_channel: nextcord.TextChannel = await category.create_text_channel(quarter_name)
clan_member_role: nextcord.Role = self.guild.get_role(self.config.params.clan_member_role_id)
await election_channel.set_permissions(clan_member_role, add_reactions=True)
thread: nextcord.Thread = await parent.create_thread(
name=quarter_name, message=thread_msg
)
with Session(engine) as session:
new_cycle = ElectionInformation(ElectionCycle=quarter_name, ElectionChannel=election_channel.id)
new_cycle = ElectionInformation(
ElectionCycle=quarter_name, ThreadId=thread.id
)
session.add(new_cycle)
session.commit()
@@ -171,9 +210,9 @@ class ElectionCycle:
for index, message in enumerate(messages):
# if last item, add the nominate button
if index + 1 == len(messages):
await election_channel.send(message, view=NominateView(self.config))
await thread.send(message, view=NominateView(self.config, self.guild))
else:
await election_channel.send(message)
await thread.send(message)
async def voting_start_action(self) -> None:
"""
@@ -181,30 +220,35 @@ class ElectionCycle:
Removes write permission from the general clan member role.
"""
message = self.build_vote_start()
clan_member_role: nextcord.Role = self.guild.get_role(self.config.params.clan_member_role_id)
election_channel: nextcord.TextChannel = self.guild.get_channel(self.get_election_info().ElectionChannel)
await election_channel.send(message)
vote_msg = await election_channel.send(build_votes_str(self.config), view=VoteView(self.config, self.guild))
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), view=VoteView(self.config, self.guild)
)
with Session(engine) as session:
stmt = select(ElectionInformation).where(
ElectionInformation.ElectionCycle == get_election_cycle_name(self.config)
ElectionInformation.ElectionCycle
== get_election_cycle_name(self.config)
)
info = session.scalar(stmt)
info.CouncilVoteMsgId = vote_msg.id
session.commit()
await election_channel.set_permissions(clan_member_role, send_messages=False)
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)
election_channel: nextcord.TextChannel = self.guild.get_channel(self.get_election_info().ElectionChannel)
clan_member_role: nextcord.Role = self.guild.get_role(self.config.params.clan_member_role_id)
council_elect_role: nextcord.Role = self.guild.get_role(
self.config.params.clan_council_elect_role_id
)
election_thread: nextcord.Thread = self.guild.get_thread(
self.get_election_info().ThreadId
)
# remove vote button from vote message
election_name = get_election_cycle_name(self.config)
@@ -213,7 +257,7 @@ class ElectionCycle:
ElectionInformation.ElectionCycle == election_name
)
vote_msg_id = session.scalar(stmt)
vote_msg: nextcord.Message = await election_channel.fetch_message(vote_msg_id)
vote_msg: nextcord.Message = await election_thread.fetch_message(vote_msg_id)
await vote_msg.edit(view=None)
# add the highcouncil role to everyone with 3 or more votes. set elected to true in database
@@ -232,13 +276,14 @@ class ElectionCycle:
council_member.Elected = True
session.commit()
user: nextcord.Member = self.guild.get_member(user_id)
member: nextcord.Member = self.guild.get_member(user_id)
await user.add_roles(council_elect_role)
await member.add_roles(
council_elect_role,
reason=f"{member.display_name} has won a nomination and become a Council Elect.",
)
await election_channel.send(message)
await election_channel.set_permissions(council_elect_role, send_messages=True, add_reactions=True)
await election_channel.set_permissions(clan_member_role, add_reactions=False)
await election_thread.send(message)
async def highcouncil_election_start_action(self) -> None:
"""
@@ -246,19 +291,55 @@ class ElectionCycle:
"""
message = self.build_highcouncil_vote_start()
election_channel: nextcord.TextChannel = self.guild.get_channel(self.get_election_info().ElectionChannel)
await election_channel.send(message)
election_thread: nextcord.Thread = self.guild.get_thread(
self.get_election_info().ThreadId
)
await election_thread.send(message)
async def term_start_action(self) -> None:
"""
Moves the election channel into the archive category.
Marks the election as over.
"""
self.election_over = True
election_channel: nextcord.TextChannel = self.guild.get_channel(self.get_election_info().ElectionChannel)
archive: nextcord.CategoryChannel = self.guild.get_channel(self.config.params.archive_channel_group_id)
await election_channel.move(beginning=True, category=archive)
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
)
# role management
with Session(engine) as session:
stmt = (
select(CouncilMembers.MemberId)
.where(
CouncilMembers.ElectionCycle == get_election_cycle_name(self.config)
)
.where(CouncilMembers.Elected == true())
)
for member_id in session.scalars(stmt):
member = self.guild.get_member(member_id)
await member.remove_roles(
elect_role,
reason=f"{member.display_name} no longer needs the elect role.",
)
await member.add_roles(
council_role,
reason=f"{member.display_name} has been elected to council.",
)
election_thread: nextcord.Thread = self.guild.get_thread(
self.get_election_info().ThreadId
)
await election_thread.send(
"This election cycle is now over. Thank you everyone for participating!"
)
await election_thread.edit(archived=True, locked=True)
def get_election_info(self) -> ElectionInformation:
"""
@@ -270,6 +351,21 @@ class ElectionCycle:
with Session(engine) as session:
stmt = select(ElectionInformation).where(
ElectionInformation.ElectionCycle == get_election_cycle_name(self.config)
ElectionInformation.ElectionCycle
== get_election_cycle_name(self.config)
)
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
)
message = f"Reminder to <@&{self.config.params.clan_member_role_id}>! Clan elections are happening! Head over "
message += f"to <#{self.get_election_info().ThreadId}> to cast your vote!"
await general.send(content=message)
+41 -12
View File
@@ -11,7 +11,7 @@ from config import DakinyConfig
from database_setup.sql_data_classes import CouncilMembers, engine
from utils import get_election_cycle_name
NOMINEE_SPLITTER = "========================================================\n\n"
NOMINEE_SPLITTER = "========================================\n\n"
class NominateModal(nextcord.ui.Modal):
@@ -19,7 +19,13 @@ class NominateModal(nextcord.ui.Modal):
Modal for nominating oneself. User writes a speech and then the callback sends the speech into the correct channel.
"""
def __init__(self, config: DakinyConfig, usr_id: int, usr_nominated: bool, speech_content: str):
def __init__(
self,
config: DakinyConfig,
usr_id: int,
usr_nominated: bool,
speech_content: str,
):
super().__init__(
"Nomination",
timeout=5 * 60, # 5 minutes
@@ -69,10 +75,13 @@ class NominateModal(nextcord.ui.Modal):
.where(CouncilMembers.MemberId == self.usr_id)
)
obj = session.scalar(stmt)
obj.SpeechMsgContents = self.speech.value
msg: nextcord.Message = await interaction.channel.fetch_message(obj.SpeechMsgId)
await msg.edit(content=decorate_nomination(interaction.user.id, self.speech.value))
msg: nextcord.Message = await interaction.channel.fetch_message(
obj.SpeechMsgId
)
await msg.edit(
content=decorate_nomination(interaction.user.id, self.speech.value)
)
class NominateView(nextcord.ui.View):
@@ -80,12 +89,19 @@ class NominateView(nextcord.ui.View):
The button to press to start your nomination.
"""
def __init__(self, config):
def __init__(self, config: DakinyConfig, guild: nextcord.Guild):
super().__init__(timeout=None)
self.config: DakinyConfig = config
self.guild: nextcord.Guild = guild
@nextcord.ui.button(label="Nominate Yourself", style=nextcord.ButtonStyle.primary, custom_id="nominate")
async def nominate(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None:
@nextcord.ui.button(
label="Nominate Yourself",
style=nextcord.ButtonStyle.primary,
custom_id="nominate",
)
async def nominate(
self, button: nextcord.ui.Button, interaction: nextcord.Interaction
) -> None:
"""
Gathers previous information about the user if they have already nominated themselves, then launches
the nomination modal to get their speech.
@@ -95,6 +111,17 @@ 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
):
await interaction.response.send_message(
f"You must have the <@&{self.config.params.clan_member_role_id}> role to vote.",
ephemeral=True,
)
return
cycle = get_election_cycle_name(self.config)
usr_nominated = False
speech_content = ""
@@ -111,17 +138,19 @@ class NominateView(nextcord.ui.View):
speech_content = msg.content.split(NOMINEE_SPLITTER)[1]
await interaction.response.send_modal(
NominateModal(self.config, interaction.user.id, usr_nominated, speech_content)
NominateModal(
self.config, interaction.user.id, usr_nominated, speech_content
)
)
def decorate_nomination(user_id, speech) -> str:
def decorate_nomination(user_id: int, speech: str) -> str:
"""
Decorates a nomination speech so that it sits within multiple speeches better.
Args:
user_id (_type_): ID of user sending the speech
speech (_type_): Speech from the user
user_id (int): ID of user sending the speech
speech (str): Speech from the user
Returns:
str: Decorated speech
+43 -16
View File
@@ -7,11 +7,16 @@ from typing import List
import nextcord
from nextcord import SelectOption
from nextcord.interactions import Interaction
from sqlalchemy import exc, select
from sqlalchemy import exc, select, update
from sqlalchemy.orm import Session
from config import DakinyConfig
from database_setup.sql_data_classes import CouncilMembers, ElectionInformation, Votes, engine
from database_setup.sql_data_classes import (
CouncilMembers,
ElectionInformation,
Votes,
engine,
)
from utils import build_votes_str, get_election_cycle_name
@@ -38,7 +43,11 @@ class VoteSelect(nextcord.ui.StringSelect):
else:
voted_for = int(self.values[0])
new_vote = Votes(ElectionCycle=election_name, VoterId=interaction.user.id, VotedForId=voted_for)
new_vote = Votes(
ElectionCycle=election_name,
VoterId=interaction.user.id,
VotedForId=voted_for,
)
try:
session.add(new_vote)
@@ -47,16 +56,18 @@ class VoteSelect(nextcord.ui.StringSelect):
except exc.IntegrityError:
session.rollback()
stmt = (
select(Votes)
update(Votes)
.where(Votes.ElectionCycle == election_name)
.where(Votes.VoterId == interaction.user.id)
.values(VotedForId=voted_for)
)
vote = session.scalar(stmt)
vote.VotedForId = voted_for
session.execute(stmt)
session.commit()
vote_message_str = build_votes_str(self.config)
vote_msg: nextcord.Message = await interaction.channel.fetch_message(vote_msg_id)
vote_msg: nextcord.Message = await interaction.channel.fetch_message(
vote_msg_id
)
await vote_msg.edit(content=vote_message_str)
if voted_for is not None:
@@ -77,10 +88,15 @@ class VoteView(nextcord.ui.View):
self.config = config
self.guild = guild
@nextcord.ui.button(label="Vote", style=nextcord.ButtonStyle.primary, custom_id="vote")
async def vote(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None:
@nextcord.ui.button(
label="Vote", style=nextcord.ButtonStyle.primary, custom_id="vote"
)
async def vote(
self, button: nextcord.ui.Button, interaction: nextcord.Interaction
) -> None:
"""
TODO
Enforces only members vote. Gets election cycle, creates voting options (checking if user has voted already),
then sends the voting view.
Args:
button (nextcord.ui.Button): Unused. idk why its needed in the decorator
@@ -88,9 +104,13 @@ class VoteView(nextcord.ui.View):
"""
# 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(self.config.params.clan_member_role_id)
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.", ephemeral=True
f"You must have the <@&{self.config.params.clan_member_role_id}> role to vote.",
ephemeral=True,
)
return
@@ -104,18 +124,25 @@ class VoteView(nextcord.ui.View):
)
voted_for = session.scalar(stmt)
stmt = select(CouncilMembers.MemberId).where(CouncilMembers.ElectionCycle == election)
stmt = select(CouncilMembers.MemberId).where(
CouncilMembers.ElectionCycle == election
)
options = [
SelectOption(
label=f"{self.guild.get_member(nominee).nick} ☑️"
label=f"{self.guild.get_member(nominee).display_name} ☑️"
if voted_for == nominee
else f"{self.guild.get_member(nominee).nick}",
else f"{self.guild.get_member(nominee).display_name}",
value=str(nominee),
)
for nominee in session.scalars(stmt)
]
options.append(SelectOption(label="Abstain ☑️" if voted_for is None else "Abstain", value="Abstain"))
options.append(
SelectOption(
label="Abstain ☑️" if voted_for is None else "Abstain",
value="Abstain",
)
)
view = nextcord.ui.View()
view.add_item(VoteSelect(options=options, config=self.config))
+2
View File
@@ -7,6 +7,7 @@ from nextcord.ext import commands
from cogs.commands.request import RequestCog
from cogs.commands.set import setCog
from cogs.commands.test import TestCog
from cogs.commands.view import ViewCog
# This function will be called when this extension is loaded.
@@ -19,3 +20,4 @@ def setup(bot: commands.Bot):
bot.add_cog(RequestCog(bot))
bot.add_cog(TestCog(bot))
bot.add_cog(setCog())
bot.add_cog(ViewCog())
+62 -27
View File
@@ -10,6 +10,17 @@ from config import DakinyConfig
from utils import get_current_cycle
def is_me():
"""
predicate that makes sure only I can run the test commands.
"""
def predicate(interaction: nextcord.Interaction):
return interaction.user.id == 206602713511886849
return application_checks.check(predicate)
class TestCog(commands.Cog, name="Test commands."):
"""
Class holds commands for testing.
@@ -21,37 +32,14 @@ class TestCog(commands.Cog, name="Test commands."):
"""
self.bot = bot
def is_me():
def predicate(interaction: nextcord.Interaction):
return interaction.user.id == 206602713511886849
return application_checks.check(predicate)
@nextcord.slash_command()
@is_me()
async def test(self, interaction: nextcord.Interaction):
"""
Parent slash command for help. Never called.
"""
@test.subcommand()
async def msg_builder(self, interaction: nextcord.Interaction):
"""
Tests the message builder.
Args:
interaction (nextcord.Interaction): Interaction that invoked the command.
"""
cycle = ElectionCycle(0, self.bot)
message = cycle.build_election_announcement()
# doesn't tell discord that the message was responded to. Is a test command so I don't really care atm.
for m in message:
await interaction.channel.send(content=m)
@test.subcommand()
@is_me()
async def election_start(self, interaction: nextcord.Interaction):
"""
idc
@@ -61,10 +49,15 @@ class TestCog(commands.Cog, name="Test commands."):
"""
config: DakinyConfig = self.bot.config
cycle = ElectionCycle(get_current_cycle(config), config, self.bot.get_guild(config.params.guild_id))
cycle = ElectionCycle(
get_current_cycle(config),
config,
self.bot.get_guild(config.params.guild_id),
)
await cycle.election_start_action()
@test.subcommand()
@is_me()
async def voting_start(self, interaction: nextcord.Interaction):
"""
tests the voting start action
@@ -73,10 +66,15 @@ class TestCog(commands.Cog, name="Test commands."):
interaction (nextcord.Interaction): _description_
"""
config: DakinyConfig = self.bot.config
cycle = ElectionCycle(get_current_cycle(config), config, self.bot.get_guild(config.params.guild_id))
cycle = ElectionCycle(
get_current_cycle(config),
config,
self.bot.get_guild(config.params.guild_id),
)
await cycle.voting_start_action()
@test.subcommand()
@is_me()
async def voting_end(self, interaction: nextcord.Interaction):
"""
tests the voting start action
@@ -85,5 +83,42 @@ class TestCog(commands.Cog, name="Test commands."):
interaction (nextcord.Interaction): _description_
"""
config: DakinyConfig = self.bot.config
cycle = ElectionCycle(get_current_cycle(config), config, self.bot.get_guild(config.params.guild_id))
cycle = ElectionCycle(
get_current_cycle(config),
config,
self.bot.get_guild(config.params.guild_id),
)
await cycle.voting_end_action()
@test.subcommand()
@is_me()
async def election_reminder(self, interaction: nextcord.Interaction):
config: DakinyConfig = self.bot.config
cycle = ElectionCycle(
get_current_cycle(config),
config,
self.bot.get_guild(config.params.guild_id),
)
await cycle.election_reminder()
@test.subcommand()
@is_me()
async def daily(self, interaction: nextcord.Interaction):
config: DakinyConfig = self.bot.config
cycle = ElectionCycle(
get_current_cycle(config),
config,
self.bot.get_guild(config.params.guild_id),
)
await cycle.check_date()
@test.subcommand()
@is_me()
async def term_start(self, interaction: nextcord.Interaction):
config: DakinyConfig = self.bot.config
cycle = ElectionCycle(
get_current_cycle(config),
config,
self.bot.get_guild(config.params.guild_id),
)
await cycle.term_start_action()
+96
View File
@@ -0,0 +1,96 @@
"""
Commands for viewing information in the database.
"""
import nextcord
from nextcord.ext import commands
from sqlalchemy import select
from sqlalchemy.orm import Session
from database_setup.sql_data_classes import ElectionInformation, Votes, engine
class ViewCog(commands.Cog, name="View Commands"):
"""
Holds the view commands.
"""
def __init__(self) -> None:
"""
Default constructor for ViewCog class. Does nothing.
"""
@nextcord.slash_command()
async def view(self, interaction: nextcord.Interaction):
"""
Parent slash command for set. Never called.
"""
@view.subcommand(
description="Shows all voters and who they voted for in a given election."
)
async def votes(
self,
interaction: nextcord.Interaction,
election_name: str = nextcord.SlashOption(
required=True, description="The election you want to view votes for."
),
):
"""
Takes in an election name and sends a response message to the user with a list of people who voted in that
election and who they voted for. Notes those tho abstained. Tells a user if the election is invalid. Uses
autocompletion to assist with getting elections.
Args:
interaction (nextcord.Interaction): User's interaction with the command.
election_name (str): Is SlashOption. The name of the election to view votes for.
"""
stmt = select(Votes).where(Votes.ElectionCycle == election_name)
with Session(engine) as session:
# Check if election exists.
if session.scalar(stmt) is None:
await interaction.response.send_message(
f"Invalid election: {election_name}.", ephemeral=True
)
return
votes = session.scalars(stmt)
content = ""
for vote in votes:
content += (
f"<@{vote.VoterId}> voted for <@{vote.VotedForId}>.\n"
if vote.VotedForId is not None
else f"<@{vote.VoterId}> abstained from voted.\n"
)
await interaction.response.send_message(content=content, ephemeral=True)
@votes.on_autocomplete("election_name")
async def election_autocomplete(
self, interaction: nextcord.Interaction, election_name: str
):
"""
Collects all elections that start with the election string.
Args:
interaction (nextcord.Interaction): Interaction with command.
election_name (str): String to run autocomplete on.
"""
with Session(engine) as session:
stmt = select(ElectionInformation.ElectionCycle)
elections = session.scalars(stmt)
# if empty compare string
if not election_name:
await interaction.response.send_autocomplete(elections)
return
autofilled_elections: "list[str]" = [
election
for election in elections
if election.lower().startswith(election_name.lower())
]
await interaction.response.send_autocomplete(autofilled_elections[:25])
+1 -2
View File
@@ -10,10 +10,8 @@ class Params:
Class defines the various configuration parameters.
"""
active_category_id: int
admin_channel_id: int # might want to rename this...
announcement_category_id: int
archive_channel_group_id: int
clan_council_elect_role_id: int
clan_council_role_id: int
clan_info_channel_id: int
@@ -22,6 +20,7 @@ class Params:
general_channel_id: int
guild_id: int
high_council_role_id: int
voting_parent_id: int
@dataclass
+4 -2
View File
@@ -20,7 +20,9 @@ birthday_list: list[int] = []
logger = logging.getLogger("nextcord")
logger.setLevel(logging.DEBUG)
handler = logging.FileHandler(filename="nextcord.log", encoding="utf-8", mode="w")
handler.setFormatter(logging.Formatter("%(asctime)s:%(levelname)s:%(name)s: %(message)s"))
handler.setFormatter(
logging.Formatter("%(asctime)s:%(levelname)s:%(name)s: %(message)s")
)
logger.addHandler(handler)
@@ -51,7 +53,7 @@ def main(cfg: DakinyConfig):
"""When Discord is connected"""
assert bot.user is not None
create_database # pylint: disable=pointless-statement
bot.add_view(NominateView(cfg))
bot.add_view(NominateView(cfg, bot.get_guild(cfg.params.guild_id)))
bot.add_view(VoteView(cfg, bot.get_guild(cfg.params.guild_id)))
print(f"{bot.user.name} has connected to Discord!")
+1 -1
View File
@@ -1,6 +1,6 @@
CREATE TABLE ElectionInformation(
ElectionCycle varchar(30) not null unique,
ElectionChannel bigint unsigned not null,
ThreadId bigint unsigned not null,
CouncilVoteMsgId bigint unsigned,
PRIMARY KEY (ElectionCycle)
);