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
7 changed files with 225 additions and 89 deletions
+128 -46
View File
@@ -6,13 +6,18 @@ import collections
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,7 +167,11 @@ 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():
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()
@@ -154,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()
@@ -173,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:
"""
@@ -183,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)
@@ -215,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
@@ -234,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:
"""
@@ -248,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:
"""
@@ -272,7 +351,8 @@ 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)
@@ -281,9 +361,11 @@ class ElectionCycle:
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(
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().ElectionChannel}> to cast your vote!"
message += f"to <#{self.get_election_info().ThreadId}> to cast your vote!"
await general.send(content=message)
+37 -7
View File
@@ -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
@@ -70,8 +76,12 @@ class NominateModal(nextcord.ui.Modal):
)
obj = session.scalar(stmt)
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):
@@ -79,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.
@@ -94,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 = ""
@@ -110,7 +138,9 @@ 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
)
)
+2 -2
View File
@@ -130,9 +130,9 @@ class VoteView(nextcord.ui.View):
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)
+52 -29
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,17 +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))
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))
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()
+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)
);