From f62f88113d80f3040a81f54c64afa1a966976e4e Mon Sep 17 00:00:00 2001 From: imcompiling Date: Sat, 13 May 2023 10:55:36 -0400 Subject: [PATCH 01/31] Create initial election cycle class. --- classes/election_cycle.py | 69 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 classes/election_cycle.py diff --git a/classes/election_cycle.py b/classes/election_cycle.py new file mode 100644 index 0000000..ef76c02 --- /dev/null +++ b/classes/election_cycle.py @@ -0,0 +1,69 @@ +""" +Contains the election cycle class +""" + +from datetime import datetime, timedelta + + +class ElectionCycle: + """ + This class represents all the dates required for an election cycle along with all the functions needed to operate + the cycle. + """ + + def __init__(self, election_start: datetime, term_start: datetime, term_end: datetime) -> None: + """ + Generates all dates relevant to the election cycle from input election start, term start and end dates. + + Args: + election_start (datetime): Date the election cycle starts. + term_start (datetime): Date the term being elected starts. + term_end (datetime): Date the term being elected ends. + """ + self.election_start: datetime = election_start + self.term_start: datetime = term_start + self.term_end: datetime = term_end # might not be needed + self.voting_start: datetime = self.election_start + timedelta(days=7) + self.voting_end: datetime = self.voting_start + timedelta(days=7) + self.highcouncil_election: datetime = self.voting_end + timedelta(days=1) + + def check_date(self) -> None: + """ + Function checks current date to see if something needs to be run. + """ + today = datetime.today() + + # check all dates if action is needed + match today.date(): + case self.election_start.date(): + self.election_start_action() + case self.voting_start.date(): + self.voting_end_action() + case self.voting_end.date(): + self.voting_end_action() + case self.highcouncil_election.date(): + self.highcouncil_election_action() + + def election_start_action(self) -> None: + """ + Performs required actions for when the election cycle starts. + """ + print("election cycle has started") + + def voting_start_action(self) -> None: + """ + Performs required actions for when voting for candidates starts. + """ + print("Voting has started") + + def voting_end_action(self) -> None: + """ + Performs required actions for when voting time has ended. + """ + print("voting has ended") + + def highcouncil_election_action(self) -> None: + """ + Performs required actions for when the highcouncil is elected. + """ + print("Highcouncil election has started.") From d3858a7ba15a3c4973e29bee3f9cd6c312bc0db1 Mon Sep 17 00:00:00 2001 From: imcompiling Date: Sat, 13 May 2023 12:59:40 -0400 Subject: [PATCH 02/31] Added discord bot to class. --- classes/election_cycle.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/classes/election_cycle.py b/classes/election_cycle.py index ef76c02..97769d1 100644 --- a/classes/election_cycle.py +++ b/classes/election_cycle.py @@ -4,6 +4,8 @@ Contains the election cycle class from datetime import datetime, timedelta +from nextcord.ext import commands + class ElectionCycle: """ @@ -11,7 +13,7 @@ class ElectionCycle: the cycle. """ - def __init__(self, election_start: datetime, term_start: datetime, term_end: datetime) -> None: + def __init__(self, election_start: datetime, term_start: datetime, term_end: datetime, bot) -> None: """ Generates all dates relevant to the election cycle from input election start, term start and end dates. @@ -19,13 +21,15 @@ class ElectionCycle: election_start (datetime): Date the election cycle starts. term_start (datetime): Date the term being elected starts. term_end (datetime): Date the term being elected ends. + bot: the discord bot instance """ - self.election_start: datetime = election_start - self.term_start: datetime = term_start - self.term_end: datetime = term_end # might not be needed - self.voting_start: datetime = self.election_start + timedelta(days=7) - self.voting_end: datetime = self.voting_start + timedelta(days=7) - self.highcouncil_election: datetime = self.voting_end + timedelta(days=1) + self._election_start: datetime = election_start + self._term_start: datetime = term_start + self._term_end: datetime = term_end # might not be needed + self._voting_start: datetime = self._election_start + timedelta(days=7) + self._voting_end: datetime = self._voting_start + timedelta(days=7) + self._highcouncil_election: datetime = self._voting_end + timedelta(days=1) + self._bot: commands.Bot = bot def check_date(self) -> None: """ @@ -35,13 +39,13 @@ class ElectionCycle: # check all dates if action is needed match today.date(): - case self.election_start.date(): + case self._election_start.date(): self.election_start_action() - case self.voting_start.date(): + case self._voting_start.date(): self.voting_end_action() - case self.voting_end.date(): + case self._voting_end.date(): self.voting_end_action() - case self.highcouncil_election.date(): + case self._highcouncil_election.date(): self.highcouncil_election_action() def election_start_action(self) -> None: From 6c9de43e735fd44833b1f4554e146f3ac23b8019 Mon Sep 17 00:00:00 2001 From: imcompiling Date: Mon, 15 May 2023 11:34:14 -0400 Subject: [PATCH 03/31] Set up bot to do initial test. Made changes in class to make test take less time. --- classes/election_cycle.py | 48 ++++++++++++++++++++++++++++++--------- cogs/loops/daily_loop.py | 10 ++++---- 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/classes/election_cycle.py b/classes/election_cycle.py index 97769d1..9e80e4a 100644 --- a/classes/election_cycle.py +++ b/classes/election_cycle.py @@ -4,6 +4,7 @@ Contains the election cycle class from datetime import datetime, timedelta +import nextcord from nextcord.ext import commands @@ -26,12 +27,12 @@ class ElectionCycle: self._election_start: datetime = election_start self._term_start: datetime = term_start self._term_end: datetime = term_end # might not be needed - self._voting_start: datetime = self._election_start + timedelta(days=7) - self._voting_end: datetime = self._voting_start + timedelta(days=7) + self._voting_start: datetime = self._election_start + timedelta(days=1) + self._voting_end: datetime = self._voting_start + timedelta(days=1) self._highcouncil_election: datetime = self._voting_end + timedelta(days=1) self._bot: commands.Bot = bot - def check_date(self) -> None: + async def check_date(self) -> None: """ Function checks current date to see if something needs to be run. """ @@ -40,34 +41,59 @@ class ElectionCycle: # check all dates if action is needed match today.date(): case self._election_start.date(): - self.election_start_action() + await self.election_start_action() case self._voting_start.date(): - self.voting_end_action() + await self.voting_end_action() case self._voting_end.date(): - self.voting_end_action() + await self.voting_end_action() case self._highcouncil_election.date(): - self.highcouncil_election_action() + await self.highcouncil_election_action() + case self._term_start.date(): + await self.term_start_action() + case self._term_end.date(): + await self.term_end_action() - def election_start_action(self) -> None: + async def election_start_action(self) -> None: """ Performs required actions for when the election cycle starts. """ print("election cycle has started") + await self.message_me("election cycle has started") - def voting_start_action(self) -> None: + async def voting_start_action(self) -> None: """ Performs required actions for when voting for candidates starts. """ print("Voting has started") + await self.message_me("Voting has started") - def voting_end_action(self) -> None: + async def voting_end_action(self) -> None: """ Performs required actions for when voting time has ended. """ print("voting has ended") + await self.message_me("voting has ended") - def highcouncil_election_action(self) -> None: + async def highcouncil_election_action(self) -> None: """ Performs required actions for when the highcouncil is elected. """ print("Highcouncil election has started.") + await self.message_me("Highcouncil election has started.") + + async def term_start_action(self) -> None: + msg: str = "the term has started" + print(msg) + self.message_me(msg) + + async def term_end_action(self): + msg: str = "the term has ended" + print(msg) + self.message_me(msg) + + async def message_me(self, msg: str): + """ + message a string to me + """ + imcompiling: nextcord.User = await self._bot.fetch_user(206602713511886849) + await imcompiling.send(msg) diff --git a/cogs/loops/daily_loop.py b/cogs/loops/daily_loop.py index 2bec4ce..4b43d8c 100644 --- a/cogs/loops/daily_loop.py +++ b/cogs/loops/daily_loop.py @@ -2,11 +2,13 @@ _summary_ """ -import nextcord +from datetime import datetime, time + from nextcord.ext import commands, tasks -from datetime import time from pytz import timezone +from classes.election_cycle import ElectionCycle + class DailyLoop(commands.Cog, name="Daily Loop"): """ @@ -15,6 +17,7 @@ class DailyLoop(commands.Cog, name="Daily Loop"): def __init__(self, bot: commands.Bot) -> None: self.bot = bot + self.election = ElectionCycle(datetime(2023, 5, 16), datetime(2023, 5, 20), datetime(2023, 5, 21), self.bot) @commands.Cog.listener() async def on_ready(self): @@ -28,5 +31,4 @@ class DailyLoop(commands.Cog, name="Daily Loop"): @tasks.loop(time=time(hour=0, tzinfo=timezone("GMT"))) async def do_thing(self): """ """ - imcompiling: nextcord.User = await self.bot.fetch_user(206602713511886849) - await imcompiling.send("its reset in runescape") + await self.election.check_date() From 90c5fea67cabf2276e7714955c4d14a349650c0d Mon Sep 17 00:00:00 2001 From: imcompiling Date: Mon, 15 May 2023 11:36:11 -0400 Subject: [PATCH 04/31] Correct builds. --- .gitea/workflows/build_develop.yml | 3 +-- .gitea/workflows/build_latest.yml | 4 +++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.gitea/workflows/build_develop.yml b/.gitea/workflows/build_develop.yml index 68a1e75..24937c2 100644 --- a/.gitea/workflows/build_develop.yml +++ b/.gitea/workflows/build_develop.yml @@ -2,8 +2,7 @@ name: build_develop on: push: - branches: [main] - + jobs: build: runs-on: ubuntu-latest diff --git a/.gitea/workflows/build_latest.yml b/.gitea/workflows/build_latest.yml index d4c0db6..ce1c18e 100644 --- a/.gitea/workflows/build_latest.yml +++ b/.gitea/workflows/build_latest.yml @@ -1,7 +1,9 @@ -name: build_develop +name: build_latest on: push: + branches: [main] + jobs: build: From dfece4adf6e7a66f44bb471c763ff9e93cad556c Mon Sep 17 00:00:00 2001 From: imcompiling Date: Mon, 15 May 2023 11:50:49 -0400 Subject: [PATCH 05/31] add empty list of branches for action --- .gitea/workflows/build_develop.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitea/workflows/build_develop.yml b/.gitea/workflows/build_develop.yml index 24937c2..b52596e 100644 --- a/.gitea/workflows/build_develop.yml +++ b/.gitea/workflows/build_develop.yml @@ -2,6 +2,7 @@ name: build_develop on: push: + branches: [] jobs: build: From 43414abb711239d2d1a6bf7982da7c9b7612dd4d Mon Sep 17 00:00:00 2001 From: imcompiling Date: Mon, 15 May 2023 11:51:37 -0400 Subject: [PATCH 06/31] try different wording --- .gitea/workflows/build_develop.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.gitea/workflows/build_develop.yml b/.gitea/workflows/build_develop.yml index b52596e..e20688f 100644 --- a/.gitea/workflows/build_develop.yml +++ b/.gitea/workflows/build_develop.yml @@ -1,8 +1,6 @@ name: build_develop -on: - push: - branches: [] +on: [push] jobs: build: From f85d1aafd0dd268606f2e18fb816666ed7b22af8 Mon Sep 17 00:00:00 2001 From: imcompiling Date: Thu, 18 May 2023 12:35:48 -0400 Subject: [PATCH 07/31] adjust test timing --- cogs/loops/daily_loop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cogs/loops/daily_loop.py b/cogs/loops/daily_loop.py index 4b43d8c..a5effb5 100644 --- a/cogs/loops/daily_loop.py +++ b/cogs/loops/daily_loop.py @@ -17,7 +17,7 @@ class DailyLoop(commands.Cog, name="Daily Loop"): def __init__(self, bot: commands.Bot) -> None: self.bot = bot - self.election = ElectionCycle(datetime(2023, 5, 16), datetime(2023, 5, 20), datetime(2023, 5, 21), self.bot) + self.election = ElectionCycle(datetime(2023, 5, 18), datetime(2023, 5, 22), datetime(2023, 5, 23), self.bot) @commands.Cog.listener() async def on_ready(self): From 17cd9c1f6d014badce370f544de50c2a3802e131 Mon Sep 17 00:00:00 2001 From: imcompiling Date: Thu, 18 May 2023 12:46:06 -0400 Subject: [PATCH 08/31] Need to add classes directory to docker image. --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index bb01a46..7055d98 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,6 +11,7 @@ COPY cogs cogs/ COPY dakiny_bot.py dakiny_bot.py COPY requirements.txt requirements.txt COPY config.py config.py +COPY classes classes/ RUN pip install -r requirements.txt From f357df807b2c6e9b451e7b4bbee7bd9b81851312 Mon Sep 17 00:00:00 2001 From: imcompiling Date: Thu, 18 May 2023 12:55:55 -0400 Subject: [PATCH 09/31] Move away from match case, don't think it worked. --- classes/election_cycle.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/classes/election_cycle.py b/classes/election_cycle.py index 9e80e4a..d54cc91 100644 --- a/classes/election_cycle.py +++ b/classes/election_cycle.py @@ -39,19 +39,18 @@ class ElectionCycle: today = datetime.today() # check all dates if action is needed - match today.date(): - case self._election_start.date(): - await self.election_start_action() - case self._voting_start.date(): - await self.voting_end_action() - case self._voting_end.date(): - await self.voting_end_action() - case self._highcouncil_election.date(): - await self.highcouncil_election_action() - case self._term_start.date(): - await self.term_start_action() - case self._term_end.date(): - await self.term_end_action() + if today.date() == self._election_start.dat(): + await self.election_start_action() + elif today.date() == self._voting_start.date(): + await self.voting_end_action() + elif today.date() == self._voting_end.date(): + await self.voting_end_action() + elif today.date() == self._highcouncil_election.date(): + await self.highcouncil_election_action() + elif today.date() == self._term_start.date(): + await self.term_start_action() + elif today.date() == self._term_end.date(): + await self.term_end_action() async def election_start_action(self) -> None: """ From 811317fc0a1a26fe6aecee4d31bee1cae32788f4 Mon Sep 17 00:00:00 2001 From: imcompiling Date: Thu, 18 May 2023 20:16:58 -0400 Subject: [PATCH 10/31] Add more debug information --- classes/election_cycle.py | 4 ++++ cogs/loops/daily_loop.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/classes/election_cycle.py b/classes/election_cycle.py index d54cc91..acbc92b 100644 --- a/classes/election_cycle.py +++ b/classes/election_cycle.py @@ -37,6 +37,7 @@ class ElectionCycle: Function checks current date to see if something needs to be run. """ today = datetime.today() + print("checking the date") # check all dates if action is needed if today.date() == self._election_start.dat(): @@ -51,6 +52,9 @@ class ElectionCycle: await self.term_start_action() elif today.date() == self._term_end.date(): await self.term_end_action() + else: + print("today was not a special date") + print(today) async def election_start_action(self) -> None: """ diff --git a/cogs/loops/daily_loop.py b/cogs/loops/daily_loop.py index a5effb5..f0d050e 100644 --- a/cogs/loops/daily_loop.py +++ b/cogs/loops/daily_loop.py @@ -17,7 +17,7 @@ class DailyLoop(commands.Cog, name="Daily Loop"): def __init__(self, bot: commands.Bot) -> None: self.bot = bot - self.election = ElectionCycle(datetime(2023, 5, 18), datetime(2023, 5, 22), datetime(2023, 5, 23), self.bot) + self.election = ElectionCycle(datetime(2023, 5, 19), datetime(2023, 5, 23), datetime(2023, 5, 24), self.bot) @commands.Cog.listener() async def on_ready(self): From 8bbe7388c62ca6c88230759800ea6565fd69d40d Mon Sep 17 00:00:00 2001 From: imcompiling Date: Wed, 24 May 2023 23:00:36 -0400 Subject: [PATCH 11/31] more print statements. --- cogs/loops/daily_loop.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cogs/loops/daily_loop.py b/cogs/loops/daily_loop.py index f0d050e..42ec27c 100644 --- a/cogs/loops/daily_loop.py +++ b/cogs/loops/daily_loop.py @@ -24,6 +24,7 @@ class DailyLoop(commands.Cog, name="Daily Loop"): """ Starts the loop when bot is ready. """ + print("on ready") await self.bot.wait_until_ready() # pylint doesn't recognize that periodic_update_guild_data is subclassed from tasks.loop. await self.do_thing.start() # pylint: disable=no-member @@ -31,4 +32,5 @@ class DailyLoop(commands.Cog, name="Daily Loop"): @tasks.loop(time=time(hour=0, tzinfo=timezone("GMT"))) async def do_thing(self): """ """ + print("loop") await self.election.check_date() From 512fa233871a3ac748c7697ebbe93d43f8e8b004 Mon Sep 17 00:00:00 2001 From: imcompiling Date: Wed, 24 May 2023 23:06:51 -0400 Subject: [PATCH 12/31] unbuffer output stream to view print statements in docker logs. --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 7055d98..b545f5a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,4 +18,4 @@ RUN pip install -r requirements.txt # remove build deps that aren't needed to run # RUN apt-get -y remove python3-dev build-essential && apt-get -y autoremove -CMD ["python3", "./dakiny_bot.py"] \ No newline at end of file +CMD ["python3", "-u", "./dakiny_bot.py"] \ No newline at end of file From 00580460c67f920c7978cf4901517a6686480d59 Mon Sep 17 00:00:00 2001 From: imcompiling Date: Wed, 24 May 2023 23:16:45 -0400 Subject: [PATCH 13/31] update to new dates. --- cogs/loops/daily_loop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cogs/loops/daily_loop.py b/cogs/loops/daily_loop.py index 42ec27c..486c355 100644 --- a/cogs/loops/daily_loop.py +++ b/cogs/loops/daily_loop.py @@ -17,7 +17,7 @@ class DailyLoop(commands.Cog, name="Daily Loop"): def __init__(self, bot: commands.Bot) -> None: self.bot = bot - self.election = ElectionCycle(datetime(2023, 5, 19), datetime(2023, 5, 23), datetime(2023, 5, 24), self.bot) + self.election = ElectionCycle(datetime(2023, 5, 25), datetime(2023, 5, 29), datetime(2023, 5, 30), self.bot) @commands.Cog.listener() async def on_ready(self): From afc4430b31ac326770afae11451f94ccfb38a42b Mon Sep 17 00:00:00 2001 From: imcompiling Date: Fri, 26 May 2023 17:15:44 -0400 Subject: [PATCH 14/31] Typo fix --- classes/election_cycle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classes/election_cycle.py b/classes/election_cycle.py index acbc92b..48d412f 100644 --- a/classes/election_cycle.py +++ b/classes/election_cycle.py @@ -40,7 +40,7 @@ class ElectionCycle: print("checking the date") # check all dates if action is needed - if today.date() == self._election_start.dat(): + if today.date() == self._election_start.date(): await self.election_start_action() elif today.date() == self._voting_start.date(): await self.voting_end_action() From d95d6238d074268ff1fdb5b4f2e6337b1e865b2d Mon Sep 17 00:00:00 2001 From: imcompiling Date: Sat, 27 May 2023 12:24:13 -0400 Subject: [PATCH 15/31] Add election announcement message. --- bot_messages/election_annoucement.txt | 50 +++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 bot_messages/election_annoucement.txt diff --git a/bot_messages/election_annoucement.txt b/bot_messages/election_annoucement.txt new file mode 100644 index 0000000..1fd7d14 --- /dev/null +++ b/bot_messages/election_annoucement.txt @@ -0,0 +1,50 @@ +```fix +The Dakiny's admin team 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. + +This clan is about human interaction, and the Clan Council positions serve to prop up certain voices within our community, in an effort to shape it as a force for good. Our mission here is to spread love. The more loving Council Members (CMs) we have on the team, the greater the community will grow for all of us to enjoy. There are no term limits, but if you go inactive on RS or become uninterested in pouring your effort into this community, request to step down so that others may contribute where you are no longer able to. + +We want all CMs to grow and succeed. Please, reach out for assistance and advice if you find yourself going even a few days without taking real, actionable steps towards the prosperity of Dakiny. Recruiting, hosting events, participating in the Clan Council Discord, and demonstrating the Core Elements of the position are required from each and every CM to grow a happy and healthy clan. + +__So here's how it works:__ +{{message_break}} +Each quarter, a channel is created here on Discord for clan members to cast their vote to determine the Clan Council for that 3-month period. + +**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. + +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. + +Then, the new Clan Council Elect will come together to select the 2 High Council positions: +RuneScape 3 Clan Leader +Discord Server Manager + +The Clan Council will be denoted by the Discord ranks of <@&{{clan_council_id}}> and <@&{{high_council_id}}> and in-game with the following ranks. +<:Rank_Admin:479008185240518656> Admin +<:Rank_Organiser:479008215024009216> Organizer +<:Rank_Coordinator:479008238654849045> Coordinator +<:Rank_Overseer:479008269382451201> Overseer +<:Rank_Deputy:479008295319896064> Deputy Owner +<:Rank_Owner:479008325137072155> Clan Leader +{{message_break}} +__Here's the timeline:__ +Election begins: {{election_start_date}} +Term: {{term_start_date}} - {{term_end_date}} + +**{{election_start_date}}**: Post here to announce candidacy +**{{voting_start_date}}**: Voting begins +**{{voting_end_date}}**: Votes are tallied and winners are announced +**{{highcouncil_election_date}}**: The new Clan Council votes who among them shall receive the two High Council positions of: Clan Leader and Discord Server Manager. +**{{term_start_date}}**: The new Clan Council is officially inaugurated and a drop party (or a giveaway event) begins to commemorate the new leadership. + +This process will begin again next quarter on {{next_election_start_date}} with the inauguration scheduled for {{term_end_date}}. +{{message_break}} +**You may now announce your candidacy for a seat on the Dakiny Clan Council! +** +You may include a speech with your announcement, as well as specific pertinent facts such as total recruits, time in the clan, past Clan Council experience, and donations made. This data is available upon request via <@&{{clan_council_id}}>. \ No newline at end of file From 79525c64d211f640edadac450c70ca4743ab8218 Mon Sep 17 00:00:00 2001 From: imcompiling Date: Sat, 27 May 2023 12:31:00 -0400 Subject: [PATCH 16/31] Add vote start message. Add comments. --- bot_messages/vote_start.txt | 12 ++++++++++++ classes/election_cycle.py | 3 ++- 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 bot_messages/vote_start.txt diff --git a/bot_messages/vote_start.txt b/bot_messages/vote_start.txt new file mode 100644 index 0000000..5cd100d --- /dev/null +++ b/bot_messages/vote_start.txt @@ -0,0 +1,12 @@ +<@&{{clan_member_id}}>, it's time to vote for clan council. + +React with <:Rank_Admin:479008185240518656> to vote. +Voting ends next Wednesday , {{voting_end_date}}. +Announcement of the results to whole clan will happen April 6th. + +After a candidate receives 3 votes, their spot is secured in the new council, but you can still vote for them to show your support. + +**Just a reminder:** only the people listed below are eligible to vote, and each clan member may **only vote for one person**. Votes from guests, diplomats, or allies will be annulled. Votes for multiple candidates from the same person will not be counted. + +RS3 Clan Members +OSRS Clan Members \ No newline at end of file diff --git a/classes/election_cycle.py b/classes/election_cycle.py index 48d412f..d02e7db 100644 --- a/classes/election_cycle.py +++ b/classes/election_cycle.py @@ -26,7 +26,7 @@ class ElectionCycle: """ self._election_start: datetime = election_start self._term_start: datetime = term_start - self._term_end: datetime = term_end # might not be needed + self._term_end: datetime = term_end self._voting_start: datetime = self._election_start + timedelta(days=1) self._voting_end: datetime = self._voting_start + timedelta(days=1) self._highcouncil_election: datetime = self._voting_end + timedelta(days=1) @@ -62,6 +62,7 @@ class ElectionCycle: """ print("election cycle has started") await self.message_me("election cycle has started") + # TODO get message, input variables, split into multiple messages, send to correct channel. async def voting_start_action(self) -> None: """ From 52aa4742e5cf04632557d5ac9eb48b5696feffdb Mon Sep 17 00:00:00 2001 From: imcompiling Date: Sat, 17 Jun 2023 16:45:30 -0400 Subject: [PATCH 17/31] Throw bot config into the bot object. --- dakiny_bot.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dakiny_bot.py b/dakiny_bot.py index 6b68528..56df5f6 100644 --- a/dakiny_bot.py +++ b/dakiny_bot.py @@ -28,6 +28,7 @@ def main(cfg: DakinyConfig): # intents.members = True bot = commands.Bot(intents=intents) + bot.config = cfg # Get the modules of all cogs whose directory structure is ./cogs/ for folder in os.listdir("cogs"): @@ -40,7 +41,7 @@ def main(cfg: DakinyConfig): print(f"{bot.user.name} has connected to Discord!") # Run Discord bot - bot.run(cfg.params.discord_token) + bot.run(bot.config.params.discord_token) if __name__ == "__main__": From bb6bf2b3d87c2e6a0ea23e686cf5e066466fd43b Mon Sep 17 00:00:00 2001 From: imcompiling Date: Wed, 28 Jun 2023 20:59:01 -0400 Subject: [PATCH 18/31] Add message builder. --- bot_messages/message_builder.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 bot_messages/message_builder.py diff --git a/bot_messages/message_builder.py b/bot_messages/message_builder.py new file mode 100644 index 0000000..5cbcf9f --- /dev/null +++ b/bot_messages/message_builder.py @@ -0,0 +1,27 @@ +class MessageBuilder: + """ + Contains all the announcements that the bot will send regarding elections. + """ + + def get_vote_start(self) -> str: + """ + Builds the voting start message out of template and configurations. + + Returns: + str: Message to send + """ + + message = "<@&{{clan_member_id}}>, it's time to vote for clan council.\ + \ + React with <:Rank_Admin:479008185240518656> to vote.\ + Voting ends next Wednesday , {{voting_end_date}}.\ + Announcement of the results to whole clan will happen April 6th.\ + \ + After a candidate receives 3 votes, their spot is secured in the new council, but you can still vote for them to show your support.\ + \ + **Just a reminder:** only the people listed below are eligible to vote, and each clan member may **only vote for one person**. Votes from guests, diplomats, or allies will be annulled. Votes for multiple candidates from the same person will not be counted. \ + \ + RS3 Clan Members\ + OSRS Clan Members" + + return message From 8a48c193ab3293721e119f9b0da61546f879393f Mon Sep 17 00:00:00 2001 From: imcompiling Date: Sat, 1 Jul 2023 22:04:47 -0400 Subject: [PATCH 19/31] Rework the config class to match what is required. --- config.py | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/config.py b/config.py index 282c2d9..da55e69 100644 --- a/config.py +++ b/config.py @@ -1,28 +1,45 @@ +""" +File contains classes defining the Hydra configuraton structure. +""" from dataclasses import dataclass -from datetime import datetime @dataclass class Params: - voting_emote_id: int - annoucement_channel_id: int + """ + Class defines the various configuration parameters. + """ + + active_category_id: int + annoucement_category_id: int archive_channel_group_id: int - clan_council_role_id: int clan_council_elect_role_id: int + clan_council_role_id: int + clan_member_role_id: int discord_token: str + high_council_role_id: int + voting_emote_id: str @dataclass class ElectionCycle: - election_start: datetime - voting_start: datetime - voting_end: datetime - term_start: datetime - term_end: datetime + """ + Class defines relevant election dates. + """ + + 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 - cycles: list[ElectionCycle] From d223a6b14e55f9d0c91faf64e0d229ee33356ed1 Mon Sep 17 00:00:00 2001 From: imcompiling Date: Sat, 1 Jul 2023 22:05:18 -0400 Subject: [PATCH 20/31] Update message builder and add a test command. --- bot_messages/message_builder.py | 53 ++++++++++++++++++++++++++------- cogs/commands/__init__.py | 16 ++++++++++ cogs/commands/test.py | 40 +++++++++++++++++++++++++ cogs/loops/daily_loop.py | 1 - 4 files changed, 98 insertions(+), 12 deletions(-) create mode 100644 cogs/commands/__init__.py create mode 100644 cogs/commands/test.py diff --git a/bot_messages/message_builder.py b/bot_messages/message_builder.py index 5cbcf9f..b48143b 100644 --- a/bot_messages/message_builder.py +++ b/bot_messages/message_builder.py @@ -1,8 +1,21 @@ +""" +Contains the MessageBuilder class. +""" + +from datetime import datetime + +from config import DakinyConfig + + class MessageBuilder: """ Contains all the announcements that the bot will send regarding elections. """ + def __init__(self, config: DakinyConfig, cycle: int) -> None: + self.config = config + self.cycle = cycle + def get_vote_start(self) -> str: """ Builds the voting start message out of template and configurations. @@ -11,17 +24,35 @@ class MessageBuilder: str: Message to send """ - message = "<@&{{clan_member_id}}>, it's time to vote for clan council.\ - \ - React with <:Rank_Admin:479008185240518656> to vote.\ - Voting ends next Wednesday , {{voting_end_date}}.\ - Announcement of the results to whole clan will happen April 6th.\ - \ - After a candidate receives 3 votes, their spot is secured in the new council, but you can still vote for them to show your support.\ - \ - **Just a reminder:** only the people listed below are eligible to vote, and each clan member may **only vote for one person**. Votes from guests, diplomats, or allies will be annulled. Votes for multiple candidates from the same person will not be counted. \ - \ - RS3 Clan Members\ + vote_end = self.get_datetime(self.config.election_cycles[self.cycle].voting_end) + + message = f"====================\n\ + <@&{self.config.params.clan_member_role_id}>, it's time to vote for clan council.\n\n\ + React with <:{self.config.params.voting_emote_id}:> to vote. \ + Voting ends next Wednesday , {vote_end.date()}. \ + Announcement of the results to whole clan will happen {vote_end.date()}.\n\n\ + After a candidate receives 3 votes, their spot is secured in the new council, \ + but you can still vote for them to show your support.\n\n\ + **Just a reminder:** only the people listed below are eligible to vote, and each \ + clan member may **only vote for one person**. Votes from guests, diplomats, or allies \ + will be annulled. Votes for multiple candidates from the same person will not be counted. \n\n\ + RS3 Clan Members\n\ OSRS Clan Members" return message + + def get_datetime(self, day: str) -> datetime: + """ + Takes in a string month and day and returns a datetime object of that Month/Day this year. + + Args: + day (str): Month/Day in that format. + + Returns: + datetime: Datetime representation of Month/Day. + """ + today = datetime.today() + month = int(day.split("/")[0]) + day = int(day.split("/")[1]) + + return datetime(today.year, month, day) diff --git a/cogs/commands/__init__.py b/cogs/commands/__init__.py new file mode 100644 index 0000000..6f55dc7 --- /dev/null +++ b/cogs/commands/__init__.py @@ -0,0 +1,16 @@ +""" +Cog loader. Adds all command cogs to the bot. +""" + +from nextcord.ext import commands + +from cogs.commands.test import TestCog + + +# This function will be called when this extension is loaded. +# It is necessary to add these functions to the bot. +def setup(bot: commands.Bot): + """ + Adds all command classes to bot. + """ + bot.add_cog(TestCog(bot)) diff --git a/cogs/commands/test.py b/cogs/commands/test.py new file mode 100644 index 0000000..c523e1b --- /dev/null +++ b/cogs/commands/test.py @@ -0,0 +1,40 @@ +""" +Commands for testing. +""" + +import nextcord +from nextcord.ext import commands + +from bot_messages.message_builder import MessageBuilder + + +class TestCog(commands.Cog, name="Test commands."): + """ + Class holds commands for testing. + """ + + def __init__(self, bot) -> None: + """ + Default constructor for HelpCog class. Does nothing. + """ + self.bot = bot + + @nextcord.slash_command() + 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. + """ + + msg_bulder = MessageBuilder(self.bot.config, 0) + message = msg_bulder.get_vote_start() + + await interaction.channel.send(content=message) diff --git a/cogs/loops/daily_loop.py b/cogs/loops/daily_loop.py index 486c355..5112d4d 100644 --- a/cogs/loops/daily_loop.py +++ b/cogs/loops/daily_loop.py @@ -24,7 +24,6 @@ class DailyLoop(commands.Cog, name="Daily Loop"): """ Starts the loop when bot is ready. """ - print("on ready") await self.bot.wait_until_ready() # pylint doesn't recognize that periodic_update_guild_data is subclassed from tasks.loop. await self.do_thing.start() # pylint: disable=no-member From cb6530ff57ba1c995111b8ec138898b37b224b98 Mon Sep 17 00:00:00 2001 From: imcompiling Date: Sat, 1 Jul 2023 22:17:42 -0400 Subject: [PATCH 21/31] Fix formatting and emote usage of vote start message. --- bot_messages/message_builder.py | 24 ++++++++++++------------ cogs/commands/test.py | 1 + 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/bot_messages/message_builder.py b/bot_messages/message_builder.py index b48143b..dda3ebc 100644 --- a/bot_messages/message_builder.py +++ b/bot_messages/message_builder.py @@ -26,18 +26,18 @@ class MessageBuilder: vote_end = self.get_datetime(self.config.election_cycles[self.cycle].voting_end) - message = f"====================\n\ - <@&{self.config.params.clan_member_role_id}>, it's time to vote for clan council.\n\n\ - React with <:{self.config.params.voting_emote_id}:> to vote. \ - Voting ends next Wednesday , {vote_end.date()}. \ - Announcement of the results to whole clan will happen {vote_end.date()}.\n\n\ - After a candidate receives 3 votes, their spot is secured in the new council, \ - but you can still vote for them to show your support.\n\n\ - **Just a reminder:** only the people listed below are eligible to vote, and each \ - clan member may **only vote for one person**. Votes from guests, diplomats, or allies \ - will be annulled. Votes for multiple candidates from the same person will not be counted. \n\n\ - RS3 Clan Members\n\ - OSRS Clan Members" + message = "====================\n" + message += f"<@&{self.config.params.clan_member_role_id}>, it's time to vote for clan council.\n\n" + message += f"React with <:{self.config.params.voting_emote_id}> to vote. " + message += f"Voting ends next Wednesday , {vote_end.date()}." + message += f"Announcement of the results to whole clan will happen {vote_end.date()}.\n\n" + message += "After a candidate receives 3 votes, their spot is secured in the new council, " + message += "but you can still vote for them to show your support.\n\n" + message += "**Just a reminder:** only the people listed below are eligible to vote, and each " + message += "clan member may **only vote for one person**. Votes from guests, diplomats, or allies " + message += "will be annulled.\nVotes for multiple candidates from the same person will not be counted. \n\n" + message += "RS3 Clan Members\n" + message += "OSRS Clan Members" return message diff --git a/cogs/commands/test.py b/cogs/commands/test.py index c523e1b..ed15292 100644 --- a/cogs/commands/test.py +++ b/cogs/commands/test.py @@ -37,4 +37,5 @@ class TestCog(commands.Cog, name="Test commands."): msg_bulder = MessageBuilder(self.bot.config, 0) message = msg_bulder.get_vote_start() + # doesn't tell discord that the message was responded to. Is a test command so I don't really care atm. await interaction.channel.send(content=message) From abd9be6341e94cdc681b49d1bfba81a3e8fd740e Mon Sep 17 00:00:00 2001 From: imcompiling Date: Sat, 1 Jul 2023 23:10:48 -0400 Subject: [PATCH 22/31] Rework message builder to format from text file. --- bot_messages/message_builder.py | 58 --------------------------------- bot_messages/vote_start.txt | 12 ++++--- classes/message_builder.py | 37 +++++++++++++++++++++ cogs/commands/test.py | 2 +- utils.py | 18 ++++++++++ 5 files changed, 63 insertions(+), 64 deletions(-) delete mode 100644 bot_messages/message_builder.py create mode 100644 classes/message_builder.py create mode 100644 utils.py diff --git a/bot_messages/message_builder.py b/bot_messages/message_builder.py deleted file mode 100644 index dda3ebc..0000000 --- a/bot_messages/message_builder.py +++ /dev/null @@ -1,58 +0,0 @@ -""" -Contains the MessageBuilder class. -""" - -from datetime import datetime - -from config import DakinyConfig - - -class MessageBuilder: - """ - Contains all the announcements that the bot will send regarding elections. - """ - - def __init__(self, config: DakinyConfig, cycle: int) -> None: - self.config = config - self.cycle = cycle - - def get_vote_start(self) -> str: - """ - Builds the voting start message out of template and configurations. - - Returns: - str: Message to send - """ - - vote_end = self.get_datetime(self.config.election_cycles[self.cycle].voting_end) - - message = "====================\n" - message += f"<@&{self.config.params.clan_member_role_id}>, it's time to vote for clan council.\n\n" - message += f"React with <:{self.config.params.voting_emote_id}> to vote. " - message += f"Voting ends next Wednesday , {vote_end.date()}." - message += f"Announcement of the results to whole clan will happen {vote_end.date()}.\n\n" - message += "After a candidate receives 3 votes, their spot is secured in the new council, " - message += "but you can still vote for them to show your support.\n\n" - message += "**Just a reminder:** only the people listed below are eligible to vote, and each " - message += "clan member may **only vote for one person**. Votes from guests, diplomats, or allies " - message += "will be annulled.\nVotes for multiple candidates from the same person will not be counted. \n\n" - message += "RS3 Clan Members\n" - message += "OSRS Clan Members" - - return message - - def get_datetime(self, day: str) -> datetime: - """ - Takes in a string month and day and returns a datetime object of that Month/Day this year. - - Args: - day (str): Month/Day in that format. - - Returns: - datetime: Datetime representation of Month/Day. - """ - today = datetime.today() - month = int(day.split("/")[0]) - day = int(day.split("/")[1]) - - return datetime(today.year, month, day) diff --git a/bot_messages/vote_start.txt b/bot_messages/vote_start.txt index 5cd100d..d8662ba 100644 --- a/bot_messages/vote_start.txt +++ b/bot_messages/vote_start.txt @@ -1,12 +1,14 @@ -<@&{{clan_member_id}}>, it's time to vote for clan council. +==================== +<@&{clan_member_id}>, it's time to vote for clan council. -React with <:Rank_Admin:479008185240518656> to vote. -Voting ends next Wednesday , {{voting_end_date}}. -Announcement of the results to whole clan will happen April 6th. +React with <{voting_emote_id}> to vote. +Voting ends next {voting_end_day}, {voting_end_date}. +Results will then be announced. After a candidate receives 3 votes, their spot is secured in the new council, but you can still vote for them to show your support. -**Just a reminder:** only the people listed below are eligible to vote, and each clan member may **only vote for one person**. Votes from guests, diplomats, or allies will be annulled. Votes for multiple candidates from the same person will not be counted. +**Just a reminder:** only the people listed below are eligible to vote, and each clan member may **only vote for one person**. Votes from guests, diplomats, or allies will be annulled. +Votes for multiple candidates from the same person will not be counted. RS3 Clan Members OSRS Clan Members \ No newline at end of file diff --git a/classes/message_builder.py b/classes/message_builder.py new file mode 100644 index 0000000..559130b --- /dev/null +++ b/classes/message_builder.py @@ -0,0 +1,37 @@ +""" +Contains the MessageBuilder class. +""" + +from datetime import datetime + +from config import DakinyConfig +from utils import get_datetime + + +class MessageBuilder: + """ + Contains all the announcements that the bot will send regarding elections. + """ + + def __init__(self, config: DakinyConfig, cycle: int) -> None: + self.config = config + self.cycle = cycle + + def get_vote_start(self) -> str: + """ + Builds the voting start message out of template and configurations. + + Returns: + str: Message to send + """ + + 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) + + return message.format( + clan_member_id=self.config.params.clan_member_role_id, + voting_emote_id=self.config.params.voting_emote_id, + voting_end_date=vote_end.strftime("%B %d"), + voting_end_day=vote_end.strftime("%A"), + ) diff --git a/cogs/commands/test.py b/cogs/commands/test.py index ed15292..8a433ce 100644 --- a/cogs/commands/test.py +++ b/cogs/commands/test.py @@ -5,7 +5,7 @@ Commands for testing. import nextcord from nextcord.ext import commands -from bot_messages.message_builder import MessageBuilder +from classes.message_builder import MessageBuilder class TestCog(commands.Cog, name="Test commands."): diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..7a2fc75 --- /dev/null +++ b/utils.py @@ -0,0 +1,18 @@ +from datetime import datetime + + +def get_datetime(day: str) -> datetime: + """ + Takes in a string month and day and returns a datetime object of that Month/Day this year. + + Args: + day (str): Month/Day in that format. + + Returns: + datetime: Datetime representation of Month/Day. + """ + today = datetime.today() + month = int(day.split("/")[0]) + day = int(day.split("/")[1]) + + return datetime(today.year, month, day) From 9f3b625cd28f1a1db4915b9adb33be33838cf258 Mon Sep 17 00:00:00 2001 From: imcompiling Date: Sat, 1 Jul 2023 23:22:17 -0400 Subject: [PATCH 23/31] Start working on election announcement builder. --- bot_messages/election_annoucement.txt | 26 +++++++++++++------------- classes/message_builder.py | 24 +++++++++++++++++++++++- cogs/commands/test.py | 2 +- 3 files changed, 37 insertions(+), 15 deletions(-) diff --git a/bot_messages/election_annoucement.txt b/bot_messages/election_annoucement.txt index 1fd7d14..d38fe62 100644 --- a/bot_messages/election_annoucement.txt +++ b/bot_messages/election_annoucement.txt @@ -8,7 +8,7 @@ This clan is about human interaction, and the Clan Council positions serve to pr We want all CMs to grow and succeed. Please, reach out for assistance and advice if you find yourself going even a few days without taking real, actionable steps towards the prosperity of Dakiny. Recruiting, hosting events, participating in the Clan Council Discord, and demonstrating the Core Elements of the position are required from each and every CM to grow a happy and healthy clan. __So here's how it works:__ -{{message_break}} +---message_break--- Each quarter, a channel is created here on Discord for clan members to cast their vote to determine the Clan Council for that 3-month period. **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. @@ -25,26 +25,26 @@ Then, the new Clan Council Elect will come together to select the 2 High Council RuneScape 3 Clan Leader Discord Server Manager -The Clan Council will be denoted by the Discord ranks of <@&{{clan_council_id}}> and <@&{{high_council_id}}> and in-game with the following ranks. +The Clan Council will be denoted by the Discord ranks of <@&{clan_council_id}> and <@&{high_council_id}> and in-game with the following ranks. <:Rank_Admin:479008185240518656> Admin <:Rank_Organiser:479008215024009216> Organizer <:Rank_Coordinator:479008238654849045> Coordinator <:Rank_Overseer:479008269382451201> Overseer <:Rank_Deputy:479008295319896064> Deputy Owner <:Rank_Owner:479008325137072155> Clan Leader -{{message_break}} +---message_break--- __Here's the timeline:__ -Election begins: {{election_start_date}} -Term: {{term_start_date}} - {{term_end_date}} +Election begins: {election_start_date} +Term: {term_start_date} - {term_end_date} -**{{election_start_date}}**: Post here to announce candidacy -**{{voting_start_date}}**: Voting begins -**{{voting_end_date}}**: Votes are tallied and winners are announced -**{{highcouncil_election_date}}**: The new Clan Council votes who among them shall receive the two High Council positions of: Clan Leader and Discord Server Manager. -**{{term_start_date}}**: The new Clan Council is officially inaugurated and a drop party (or a giveaway event) begins to commemorate the new leadership. +**{election_start_date}**: Post here to announce candidacy +**{voting_start_date}**: Voting begins +**{voting_end_date}**: Votes are tallied and winners are announced +**{highcouncil_election_date}**: The new Clan Council votes who among them shall receive the two High Council positions of: Clan Leader and Discord Server Manager. +**{term_start_date}**: The new Clan Council is officially inaugurated and a drop party (or a giveaway event) begins to commemorate the new leadership. -This process will begin again next quarter on {{next_election_start_date}} with the inauguration scheduled for {{term_end_date}}. -{{message_break}} +This process will begin again next quarter on {next_election_start_date} with the inauguration scheduled for {term_end_date}. +---message_break--- **You may now announce your candidacy for a seat on the Dakiny Clan Council! ** -You may include a speech with your announcement, as well as specific pertinent facts such as total recruits, time in the clan, past Clan Council experience, and donations made. This data is available upon request via <@&{{clan_council_id}}>. \ No newline at end of file +You may include a speech with your announcement, as well as specific pertinent facts such as total recruits, time in the clan, past Clan Council experience, and donations made. This data is available upon request via <@&{clan_council_id}>. \ No newline at end of file diff --git a/classes/message_builder.py b/classes/message_builder.py index 559130b..0804a7c 100644 --- a/classes/message_builder.py +++ b/classes/message_builder.py @@ -17,7 +17,29 @@ class MessageBuilder: self.config = config self.cycle = cycle - def get_vote_start(self) -> str: + def build_election_announcement(self) -> list[str]: + """ + Builds the messages to announce the election is starting. + + Returns: + list[str]: A list of message contents in the correct order. + """ + + with open("bot_messages/election_announcement.txt", mode="r", encoding="utf8") as file: + message = file.read() + election_start: datetime = get_datetime(self.config.election_cycles[self.cycle].election_start) + term_start: datetime = get_datetime(self.config.election_cycles[self.cycle].term_start) + + message = message.format( + clan_council_id=self.config.params.clan_council_role_id, + high_council_id=self.config.params.high_council_role_id, + election_start_date=election_start.strftime("%B %d"), + term_start_date=term_start.strftime("%B %d"), + ) + + # TODO make sure to split the message on message break. + + def build_vote_start(self) -> str: """ Builds the voting start message out of template and configurations. diff --git a/cogs/commands/test.py b/cogs/commands/test.py index 8a433ce..786e590 100644 --- a/cogs/commands/test.py +++ b/cogs/commands/test.py @@ -35,7 +35,7 @@ class TestCog(commands.Cog, name="Test commands."): """ msg_bulder = MessageBuilder(self.bot.config, 0) - message = msg_bulder.get_vote_start() + message = msg_bulder.build_vote_start() # doesn't tell discord that the message was responded to. Is a test command so I don't really care atm. await interaction.channel.send(content=message) From 1044f8a521432c03a24f75d3b6a31e67138f0e24 Mon Sep 17 00:00:00 2001 From: imcompiling Date: Sun, 2 Jul 2023 10:18:53 -0400 Subject: [PATCH 24/31] Change get_datetime to handle year changes. --- utils.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/utils.py b/utils.py index 7a2fc75..b0f0d92 100644 --- a/utils.py +++ b/utils.py @@ -1,3 +1,7 @@ +""" +File contains util functions. +""" + from datetime import datetime @@ -11,8 +15,12 @@ def get_datetime(day: str) -> datetime: Returns: datetime: Datetime representation of Month/Day. """ - today = datetime.today() + year = datetime.today().year month = int(day.split("/")[0]) day = int(day.split("/")[1]) - return datetime(today.year, month, day) + # ensure that year rolls over if current month is December and the next month is less than 12. + if datetime.today().month == 12 & month < 12: + year += 1 + + return datetime(year, month, day) From f0beb7821fd7841886c78997760cb56f3eba1225 Mon Sep 17 00:00:00 2001 From: imcompiling Date: Sun, 2 Jul 2023 11:04:28 -0400 Subject: [PATCH 25/31] Correct name of file. --- .../{election_annoucement.txt => election_announcement.txt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename bot_messages/{election_annoucement.txt => election_announcement.txt} (100%) diff --git a/bot_messages/election_annoucement.txt b/bot_messages/election_announcement.txt similarity index 100% rename from bot_messages/election_annoucement.txt rename to bot_messages/election_announcement.txt From edc7722f765b6ac37d525c9e06f60ad4808d24d6 Mon Sep 17 00:00:00 2001 From: imcompiling Date: Sun, 2 Jul 2023 11:04:51 -0400 Subject: [PATCH 26/31] Remove message builder, move builders to elecion_cycle.py. --- classes/election_cycle.py | 82 +++++++++++++++++++++++++++++++------- classes/message_builder.py | 59 --------------------------- cogs/commands/test.py | 10 +++-- cogs/loops/daily_loop.py | 8 ++-- 4 files changed, 78 insertions(+), 81 deletions(-) delete mode 100644 classes/message_builder.py diff --git a/classes/election_cycle.py b/classes/election_cycle.py index d02e7db..22098e2 100644 --- a/classes/election_cycle.py +++ b/classes/election_cycle.py @@ -7,6 +7,9 @@ from datetime import datetime, timedelta import nextcord from nextcord.ext import commands +from config import DakinyConfig +from utils import get_datetime + class ElectionCycle: """ @@ -14,7 +17,7 @@ class ElectionCycle: the cycle. """ - def __init__(self, election_start: datetime, term_start: datetime, term_end: datetime, bot) -> None: + def __init__(self, cycle: int, bot) -> None: """ Generates all dates relevant to the election cycle from input election start, term start and end dates. @@ -24,13 +27,62 @@ class ElectionCycle: term_end (datetime): Date the term being elected ends. bot: the discord bot instance """ - self._election_start: datetime = election_start - self._term_start: datetime = term_start - self._term_end: datetime = term_end - self._voting_start: datetime = self._election_start + timedelta(days=1) - self._voting_end: datetime = self._voting_start + timedelta(days=1) - self._highcouncil_election: datetime = self._voting_end + timedelta(days=1) - self._bot: commands.Bot = bot + self.config: DakinyConfig = bot.config + self.cycle = cycle + self.elecion_cycle = self.config.election_cycles[self.cycle] + self.election_start: datetime = get_datetime(self.elecion_cycle.election_start) + self.term_start: datetime = get_datetime(self.elecion_cycle.term_start) + self.term_end: datetime = get_datetime(self.elecion_cycle.term_end) + self.voting_start: datetime = get_datetime(self.elecion_cycle.voting_start) + self.voting_end: datetime = get_datetime(self.elecion_cycle.voting_end) + self.highcouncil_election: datetime = self.voting_end + timedelta(days=1) + self.bot: commands.Bot = bot + + def build_election_announcement(self) -> list[str]: + """ + Builds the messages to announce the election is starting. + + Returns: + list[str]: A list of message contents in the correct order. + """ + + 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) % 4].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, + election_start_date=self.election_start.strftime("%B %d"), + term_start_date=self.term_start.strftime("%B %d"), + term_end_date=self.term_end.strftime("%B %d"), + voting_start_date=self.voting_start.strftime("%B %d"), + voting_end_date=self.voting_end.strftime("%B %d"), + highcouncil_election_date=self.highcouncil_election.strftime("%B %d"), + next_election_start_date=next_election.strftime("%B %d"), + ) + + return message.split("---message_break---") + + def build_vote_start(self) -> str: + """ + Builds the voting start message out of template and configurations. + + Returns: + str: Message to send + """ + + 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) + + return message.format( + clan_member_id=self.config.params.clan_member_role_id, + voting_emote_id=self.config.params.voting_emote_id, + voting_end_date=vote_end.strftime("%B %d"), + voting_end_day=vote_end.strftime("%A"), + ) async def check_date(self) -> None: """ @@ -40,17 +92,17 @@ class ElectionCycle: print("checking the date") # check all dates if action is needed - if today.date() == self._election_start.date(): + if today.date() == self.election_start.date(): await self.election_start_action() - elif today.date() == self._voting_start.date(): + elif today.date() == self.voting_start.date(): await self.voting_end_action() - elif today.date() == self._voting_end.date(): + elif today.date() == self.voting_end.date(): await self.voting_end_action() - elif today.date() == self._highcouncil_election.date(): + elif today.date() == self.highcouncil_election.date(): await self.highcouncil_election_action() - elif today.date() == self._term_start.date(): + elif today.date() == self.term_start.date(): await self.term_start_action() - elif today.date() == self._term_end.date(): + elif today.date() == self.term_end.date(): await self.term_end_action() else: print("today was not a special date") @@ -99,5 +151,5 @@ class ElectionCycle: """ message a string to me """ - imcompiling: nextcord.User = await self._bot.fetch_user(206602713511886849) + imcompiling: nextcord.User = await self.bot.fetch_user(206602713511886849) await imcompiling.send(msg) diff --git a/classes/message_builder.py b/classes/message_builder.py deleted file mode 100644 index 0804a7c..0000000 --- a/classes/message_builder.py +++ /dev/null @@ -1,59 +0,0 @@ -""" -Contains the MessageBuilder class. -""" - -from datetime import datetime - -from config import DakinyConfig -from utils import get_datetime - - -class MessageBuilder: - """ - Contains all the announcements that the bot will send regarding elections. - """ - - def __init__(self, config: DakinyConfig, cycle: int) -> None: - self.config = config - self.cycle = cycle - - def build_election_announcement(self) -> list[str]: - """ - Builds the messages to announce the election is starting. - - Returns: - list[str]: A list of message contents in the correct order. - """ - - with open("bot_messages/election_announcement.txt", mode="r", encoding="utf8") as file: - message = file.read() - election_start: datetime = get_datetime(self.config.election_cycles[self.cycle].election_start) - term_start: datetime = get_datetime(self.config.election_cycles[self.cycle].term_start) - - message = message.format( - clan_council_id=self.config.params.clan_council_role_id, - high_council_id=self.config.params.high_council_role_id, - election_start_date=election_start.strftime("%B %d"), - term_start_date=term_start.strftime("%B %d"), - ) - - # TODO make sure to split the message on message break. - - def build_vote_start(self) -> str: - """ - Builds the voting start message out of template and configurations. - - Returns: - str: Message to send - """ - - 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) - - return message.format( - clan_member_id=self.config.params.clan_member_role_id, - voting_emote_id=self.config.params.voting_emote_id, - voting_end_date=vote_end.strftime("%B %d"), - voting_end_day=vote_end.strftime("%A"), - ) diff --git a/cogs/commands/test.py b/cogs/commands/test.py index 786e590..2d6e032 100644 --- a/cogs/commands/test.py +++ b/cogs/commands/test.py @@ -5,7 +5,7 @@ Commands for testing. import nextcord from nextcord.ext import commands -from classes.message_builder import MessageBuilder +from classes.election_cycle import ElectionCycle class TestCog(commands.Cog, name="Test commands."): @@ -34,8 +34,10 @@ class TestCog(commands.Cog, name="Test commands."): interaction (nextcord.Interaction): Interaction that invoked the command. """ - msg_bulder = MessageBuilder(self.bot.config, 0) - message = msg_bulder.build_vote_start() + 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. - await interaction.channel.send(content=message) + for m in message: + await interaction.channel.send(content=m) diff --git a/cogs/loops/daily_loop.py b/cogs/loops/daily_loop.py index 5112d4d..eed954b 100644 --- a/cogs/loops/daily_loop.py +++ b/cogs/loops/daily_loop.py @@ -2,7 +2,7 @@ _summary_ """ -from datetime import datetime, time +from datetime import time from nextcord.ext import commands, tasks from pytz import timezone @@ -17,7 +17,8 @@ class DailyLoop(commands.Cog, name="Daily Loop"): def __init__(self, bot: commands.Bot) -> None: self.bot = bot - self.election = ElectionCycle(datetime(2023, 5, 25), datetime(2023, 5, 29), datetime(2023, 5, 30), self.bot) + # TODO need to figure out how to determine the current cycle + self.current_election = ElectionCycle(0, self.bot) @commands.Cog.listener() async def on_ready(self): @@ -32,4 +33,5 @@ class DailyLoop(commands.Cog, name="Daily Loop"): async def do_thing(self): """ """ print("loop") - await self.election.check_date() + await self.current_election.check_date() + # TODO need to figure out how to change the election cycle after current one is over From 9914d77d526aa9f1f87c1980d91140aa9888f07f Mon Sep 17 00:00:00 2001 From: imcompiling Date: Sun, 2 Jul 2023 11:29:23 -0400 Subject: [PATCH 27/31] Start working on highcouncl election start message. --- bot_messages/highcouncil_election.txt | 13 +++++++++++++ classes/election_cycle.py | 21 ++++++++++++++++++--- 2 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 bot_messages/highcouncil_election.txt diff --git a/bot_messages/highcouncil_election.txt b/bot_messages/highcouncil_election.txt new file mode 100644 index 0000000..50f5422 --- /dev/null +++ b/bot_messages/highcouncil_election.txt @@ -0,0 +1,13 @@ +`Leadership Elections - candidacy time` + + +<@&{clan_council_elect_role_id}>, welcome. We are starting the second part of our elections. On {highcouncil_election_announce} this channel will be open to write for all of you. Everyone else will be able to read this channel, but not write in it. Only members of the Clan Council Elected (CCE) can vote now. Any votes from others will be annulled. + +From {highcouncil_election_announce} to {highcouncil_election_start}, you will be able to announce your candidacy for High Council. The positions are: + Clan Leader + Discord Server Manager + +Each member of the CCE can cast one for each category by using the emotes: <:Rank_Owner:479008325137072155> for Clan Leader and <:Rank_Deputy:479008295319896064> for Discord Server Manager. +If needed, the icon 🤷 can be used to abstain to vote in any or both categories and it should be apply to all candidates of that category. + +Votes can be cast until the {highcouncil_election_end}, so we can announce and have everyone set up to start the term by {term_start} \ No newline at end of file diff --git a/classes/election_cycle.py b/classes/election_cycle.py index 22098e2..09f4e1d 100644 --- a/classes/election_cycle.py +++ b/classes/election_cycle.py @@ -35,7 +35,9 @@ class ElectionCycle: self.term_end: datetime = get_datetime(self.elecion_cycle.term_end) self.voting_start: datetime = get_datetime(self.elecion_cycle.voting_start) self.voting_end: datetime = get_datetime(self.elecion_cycle.voting_end) - self.highcouncil_election: datetime = self.voting_end + timedelta(days=1) + self.highcouncil_election_announce: datetime = self.voting_end + timedelta(days=1) + self.highcouncil_election_start: datetime = self.highcouncil_election_announce + timedelta(days=3) + self.highcouncil_election_end: datetime = self.term_start - timedelta(days=1) self.bot: commands.Bot = bot def build_election_announcement(self) -> list[str]: @@ -59,12 +61,25 @@ class ElectionCycle: term_end_date=self.term_end.strftime("%B %d"), voting_start_date=self.voting_start.strftime("%B %d"), voting_end_date=self.voting_end.strftime("%B %d"), - highcouncil_election_date=self.highcouncil_election.strftime("%B %d"), + highcouncil_election_date=self.highcouncil_election_start.strftime("%B %d"), next_election_start_date=next_election.strftime("%B %d"), ) return message.split("---message_break---") + def build_highcouncil_election(self) -> str: + """ + Builds message to announce highcouncil election start. + + Returns: + str: Message to send. + """ + + with open("bot_messages/highcouncil_election.txt", mode="r", encoding="utf8") as file: + message = file.read() + + return message.format() + def build_vote_start(self) -> str: """ Builds the voting start message out of template and configurations. @@ -98,7 +113,7 @@ class ElectionCycle: await self.voting_end_action() elif today.date() == self.voting_end.date(): await self.voting_end_action() - elif today.date() == self.highcouncil_election.date(): + elif today.date() == self.highcouncil_election_start.date(): await self.highcouncil_election_action() elif today.date() == self.term_start.date(): await self.term_start_action() From b270922a7f303a2793d557ced981a4446546c247 Mon Sep 17 00:00:00 2001 From: imcompiling Date: Mon, 10 Jul 2023 15:55:43 -0400 Subject: [PATCH 28/31] Add a clan info channel. --- config.py | 1 + 1 file changed, 1 insertion(+) diff --git a/config.py b/config.py index da55e69..80c86f0 100644 --- a/config.py +++ b/config.py @@ -15,6 +15,7 @@ class Params: archive_channel_group_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 high_council_role_id: int From 5c0f014eaa459ba8f3c6de7b87d7b968b312db26 Mon Sep 17 00:00:00 2001 From: imcompiling Date: Mon, 10 Jul 2023 16:00:26 -0400 Subject: [PATCH 29/31] Add text template for council member info message. --- bot_messages/council_members.txt | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 bot_messages/council_members.txt diff --git a/bot_messages/council_members.txt b/bot_messages/council_members.txt new file mode 100644 index 0000000..093aee1 --- /dev/null +++ b/bot_messages/council_members.txt @@ -0,0 +1,7 @@ +Here's the {quarter} Clan Council: + +Dakiny Council: +{council} + +Dakiny High Council: +{high_council} \ No newline at end of file From 13325616fc38587733d5d20cc52c61bbc0e87a7c Mon Sep 17 00:00:00 2001 From: imcompiling Date: Mon, 10 Jul 2023 16:26:29 -0400 Subject: [PATCH 30/31] registry location update --- .gitea/workflows/build_develop.yml | 4 ++-- .gitea/workflows/build_latest.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitea/workflows/build_develop.yml b/.gitea/workflows/build_develop.yml index 18334dc..a9f721a 100644 --- a/.gitea/workflows/build_develop.yml +++ b/.gitea/workflows/build_develop.yml @@ -13,6 +13,6 @@ jobs: - name: Install Docker uses: https://github.com/papodaca/install-docker-action@v1 - name: Build Docker Image - run: docker build -t viker.cheezers.net:5000/dakiny-bot:develop . + run: docker build -t gitea.cheezers.org/imcompiling/dakiny-bot:develop . - name: Push Docker Image - run: docker push viker.cheezers.net:5000/dakiny-bot:develop + run: docker push gitea.cheezers.org/imcompiling/dakiny-bot:develop diff --git a/.gitea/workflows/build_latest.yml b/.gitea/workflows/build_latest.yml index 9e06f62..a04a2fc 100644 --- a/.gitea/workflows/build_latest.yml +++ b/.gitea/workflows/build_latest.yml @@ -15,6 +15,6 @@ jobs: - name: Install Docker uses: https://github.com/papodaca/install-docker-action@v1 - name: Build Docker Image - run: docker build -t viker.cheezers.net:5000/dakiny-bot:latest . + run: docker build -t gitea.cheezers.org/imcompiling/dakiny-bot:latest . - name: Push Docker Image - run: docker push viker.cheezers.net:5000/dakiny-bot:latest + run: docker push gitea.cheezers.org/imcompiling/dakiny-bot:latest From 797f8a46aa68751c51dc7b312af2302c1b236918 Mon Sep 17 00:00:00 2001 From: imcompiling Date: Fri, 14 Jul 2023 11:31:52 -0400 Subject: [PATCH 31/31] Flushed out election start. Added a file to track what permissions the bot needs. --- classes/election_cycle.py | 18 +++++++++++++++--- cogs/commands/test.py | 12 ++++++++++++ permissions_required.txt | 3 +++ 3 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 permissions_required.txt diff --git a/classes/election_cycle.py b/classes/election_cycle.py index 09f4e1d..5d4a9e8 100644 --- a/classes/election_cycle.py +++ b/classes/election_cycle.py @@ -45,7 +45,8 @@ class ElectionCycle: Builds the messages to announce the election is starting. Returns: - list[str]: A list of message contents in the correct order. + list[str]: A list of message contents in the correct order. Must be a string because the message is too + long. """ with open("bot_messages/election_announcement.txt", mode="r", encoding="utf8") as file: @@ -128,8 +129,19 @@ class ElectionCycle: Performs required actions for when the election cycle starts. """ print("election cycle has started") - await self.message_me("election cycle has started") - # TODO get message, input variables, split into multiple messages, send to correct channel. + # create new channel for election + category: nextcord.CategoryChannel = self.bot.get_channel(self.config.params.active_category_id) + quarter_name: str = str(datetime.today().year) + "-" + self.elecion_cycle.name + "-election" + # TODO need to store this id in the sqlite database + election_channel = await category.create_text_channel(quarter_name) + + # post message announcing election + # election start message is returned as an array of string because it is too long. send each string + # individually. + messages = self.build_election_announcement() + + for message in messages: + await election_channel.send(message) async def voting_start_action(self) -> None: """ diff --git a/cogs/commands/test.py b/cogs/commands/test.py index 2d6e032..95bcbbc 100644 --- a/cogs/commands/test.py +++ b/cogs/commands/test.py @@ -41,3 +41,15 @@ class TestCog(commands.Cog, name="Test commands."): # 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() + async def election_start(self, interaction: nextcord.Interaction): + """ + idc + + Args: + interaction (nextcord.Interaction): _description_ + """ + + cycle = ElectionCycle(0, self.bot) + await cycle.election_start_action() diff --git a/permissions_required.txt b/permissions_required.txt new file mode 100644 index 0000000..dc1e30b --- /dev/null +++ b/permissions_required.txt @@ -0,0 +1,3 @@ +Discord permissions required for bot to operate: + +manage channels \ No newline at end of file