Compare commits

...
Author SHA1 Message Date
imcompiling 797f8a46aa Flushed out election start. Added a file to track what permissions the bot needs. 2023-07-14 11:31:52 -04:00
imcompiling 13325616fc registry location update
build_develop / build (push) Failing after 2m45s
2023-07-10 16:26:29 -04:00
imcompiling 5c0f014eaa Add text template for council member info message. 2023-07-10 16:00:26 -04:00
imcompiling b270922a7f Add a clan info channel.
build_develop / build (push) Has been cancelled
2023-07-10 15:55:43 -04:00
imcompiling 9914d77d52 Start working on highcouncl election start message.
build
2023-07-02 11:29:23 -04:00
imcompiling edc7722f76 Remove message builder, move builders to elecion_cycle.py.
build
2023-07-02 11:04:51 -04:00
imcompiling f0beb7821f Correct name of file. 2023-07-02 11:04:28 -04:00
imcompiling 1044f8a521 Change get_datetime to handle year changes.
build
2023-07-02 10:18:53 -04:00
imcompiling 9f3b625cd2 Start working on election announcement builder.
build
2023-07-01 23:22:17 -04:00
imcompiling abd9be6341 Rework message builder to format from text file. 2023-07-01 23:10:48 -04:00
imcompiling cb6530ff57 Fix formatting and emote usage of vote start message.
build
2023-07-01 22:17:42 -04:00
imcompiling d223a6b14e Update message builder and add a test command.
build
2023-07-01 22:05:18 -04:00
imcompiling 8a48c193ab Rework the config class to match what is required. 2023-07-01 22:04:47 -04:00
imcompiling bb6bf2b3d8 Add message builder.
build
2023-06-28 20:59:01 -04:00
imcompiling 52aa4742e5 Throw bot config into the bot object.
build
2023-06-17 16:45:30 -04:00
imcompiling bca5b0d84b Merge branch 'main' into 2-election-cycle-class
build
2023-06-17 16:39:48 -04:00
imcompiling 3a2b2d7858 Don't need the config.yml file in the docker image.
build
2023-06-17 16:39:08 -04:00
imcompiling 7bbb6b237c Merge branch '4-configuration-options'
build
2023-06-17 16:36:20 -04:00
imcompiling 0f05751ce8 Add hydra configs.
build
2023-06-17 16:25:45 -04:00
imcompiling d32c27c291 Remove config.py. Can place the discord token into the config.yml.
build
2023-05-28 09:42:55 -04:00
imcompiling 391c36e7f2 Copy the config yml into the docker container. 2023-05-28 09:42:05 -04:00
imcompiling 846df89510 Add hydra configs to ignore. 2023-05-28 09:24:33 -04:00
imcompiling 79525c64d2 Add vote start message. Add comments.
build
2023-05-27 12:31:00 -04:00
imcompiling d95d6238d0 Add election announcement message. 2023-05-27 12:24:13 -04:00
imcompiling b08d3e3863 Merge branch 'election-class-test' into 2-election-cycle-class
build
2023-05-26 17:19:01 -04:00
16 changed files with 348 additions and 33 deletions
+2 -2
View File
@@ -13,6 +13,6 @@ jobs:
- name: Install Docker - name: Install Docker
uses: https://github.com/papodaca/install-docker-action@v1 uses: https://github.com/papodaca/install-docker-action@v1
- name: Build Docker Image - 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 - name: Push Docker Image
run: docker push viker.cheezers.net:5000/dakiny-bot:develop run: docker push gitea.cheezers.org/imcompiling/dakiny-bot:develop
+2 -2
View File
@@ -15,6 +15,6 @@ jobs:
- name: Install Docker - name: Install Docker
uses: https://github.com/papodaca/install-docker-action@v1 uses: https://github.com/papodaca/install-docker-action@v1
- name: Build Docker Image - 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 - name: Push Docker Image
run: docker push viker.cheezers.net:5000/dakiny-bot:latest run: docker push gitea.cheezers.org/imcompiling/dakiny-bot:latest
+2
View File
@@ -137,3 +137,5 @@ deleted_backups/
highscores_data/ highscores_data/
TODO.txt TODO.txt
sql_tester.py sql_tester.py
conf
outputs
+7
View File
@@ -0,0 +1,7 @@
Here's the {quarter} Clan Council:
Dakiny Council:
{council}
Dakiny High Council:
{high_council}
+50
View File
@@ -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}>.
+13
View File
@@ -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}
+14
View File
@@ -0,0 +1,14 @@
====================
<@&{clan_member_id}>, it's time to vote for clan council.
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.
RS3 Clan Members
OSRS Clan Members
+96 -16
View File
@@ -7,6 +7,9 @@ from datetime import datetime, timedelta
import nextcord import nextcord
from nextcord.ext import commands from nextcord.ext import commands
from config import DakinyConfig
from utils import get_datetime
class ElectionCycle: class ElectionCycle:
""" """
@@ -14,7 +17,7 @@ class ElectionCycle:
the cycle. 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. Generates all dates relevant to the election cycle from input election start, term start and end dates.
@@ -24,13 +27,78 @@ class ElectionCycle:
term_end (datetime): Date the term being elected ends. term_end (datetime): Date the term being elected ends.
bot: the discord bot instance bot: the discord bot instance
""" """
self._election_start: datetime = election_start self.config: DakinyConfig = bot.config
self._term_start: datetime = term_start self.cycle = cycle
self._term_end: datetime = term_end # might not be needed self.elecion_cycle = self.config.election_cycles[self.cycle]
self._voting_start: datetime = self._election_start + timedelta(days=1) self.election_start: datetime = get_datetime(self.elecion_cycle.election_start)
self._voting_end: datetime = self._voting_start + timedelta(days=1) self.term_start: datetime = get_datetime(self.elecion_cycle.term_start)
self._highcouncil_election: datetime = self._voting_end + timedelta(days=1) self.term_end: datetime = get_datetime(self.elecion_cycle.term_end)
self._bot: commands.Bot = bot 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_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]:
"""
Builds the messages to announce the election is starting.
Returns:
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:
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_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.
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: async def check_date(self) -> None:
""" """
@@ -40,17 +108,17 @@ class ElectionCycle:
print("checking the date") print("checking the date")
# check all dates if action is needed # 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() await self.election_start_action()
elif today.date() == self._voting_start.date(): elif today.date() == self.voting_start.date():
await self.voting_end_action() await self.voting_end_action()
elif today.date() == self._voting_end.date(): elif today.date() == self.voting_end.date():
await self.voting_end_action() 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() await self.highcouncil_election_action()
elif today.date() == self._term_start.date(): elif today.date() == self.term_start.date():
await self.term_start_action() await self.term_start_action()
elif today.date() == self._term_end.date(): elif today.date() == self.term_end.date():
await self.term_end_action() await self.term_end_action()
else: else:
print("today was not a special date") print("today was not a special date")
@@ -61,7 +129,19 @@ class ElectionCycle:
Performs required actions for when the election cycle starts. Performs required actions for when the election cycle starts.
""" """
print("election cycle has started") print("election cycle has started")
await self.message_me("election cycle has started") # 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: async def voting_start_action(self) -> None:
""" """
@@ -98,5 +178,5 @@ class ElectionCycle:
""" """
message a string to me 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) await imcompiling.send(msg)
+16
View File
@@ -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))
+55
View File
@@ -0,0 +1,55 @@
"""
Commands for testing.
"""
import nextcord
from nextcord.ext import commands
from classes.election_cycle import ElectionCycle
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.
"""
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()
async def election_start(self, interaction: nextcord.Interaction):
"""
idc
Args:
interaction (nextcord.Interaction): _description_
"""
cycle = ElectionCycle(0, self.bot)
await cycle.election_start_action()
+5 -4
View File
@@ -2,7 +2,7 @@
_summary_ _summary_
""" """
from datetime import datetime, time from datetime import time
from nextcord.ext import commands, tasks from nextcord.ext import commands, tasks
from pytz import timezone from pytz import timezone
@@ -17,14 +17,14 @@ class DailyLoop(commands.Cog, name="Daily Loop"):
def __init__(self, bot: commands.Bot) -> None: def __init__(self, bot: commands.Bot) -> None:
self.bot = bot 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() @commands.Cog.listener()
async def on_ready(self): async def on_ready(self):
""" """
Starts the loop when bot is ready. Starts the loop when bot is ready.
""" """
print("on ready")
await self.bot.wait_until_ready() await self.bot.wait_until_ready()
# pylint doesn't recognize that periodic_update_guild_data is subclassed from tasks.loop. # pylint doesn't recognize that periodic_update_guild_data is subclassed from tasks.loop.
await self.do_thing.start() # pylint: disable=no-member await self.do_thing.start() # pylint: disable=no-member
@@ -33,4 +33,5 @@ class DailyLoop(commands.Cog, name="Daily Loop"):
async def do_thing(self): async def do_thing(self):
""" """ """ """
print("loop") 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
+43 -5
View File
@@ -1,8 +1,46 @@
import os """
File contains classes defining the Hydra configuraton structure.
"""
from dataclasses import dataclass
from dotenv.main import load_dotenv
load_dotenv() @dataclass
class Params:
"""
Class defines the various configuration parameters.
"""
# Discord config active_category_id: int
DISCORD_TOKEN = os.getenv("DISCORD_TOKEN", "") annoucement_category_id: int
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
voting_emote_id: str
@dataclass
class ElectionCycle:
"""
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
+12 -3
View File
@@ -1,12 +1,19 @@
import os import os
import sys
import hydra
import nextcord import nextcord
from hydra.core.config_store import ConfigStore
from nextcord.ext import commands from nextcord.ext import commands
import config from config import DakinyConfig
cs = ConfigStore.instance()
cs.store(name="dakiny_config", node=DakinyConfig)
def main(): @hydra.main(config_path="conf", config_name="config")
def main(cfg: DakinyConfig):
# Allows privledged intents for monitoring members joining, roles editing, and role assignments # Allows privledged intents for monitoring members joining, roles editing, and role assignments
# These need to be enabled in the developer portal as well # These need to be enabled in the developer portal as well
intents = nextcord.Intents.default() intents = nextcord.Intents.default()
@@ -21,6 +28,7 @@ def main():
# intents.members = True # intents.members = True
bot = commands.Bot(intents=intents) bot = commands.Bot(intents=intents)
bot.config = cfg
# Get the modules of all cogs whose directory structure is ./cogs/<module_name> # Get the modules of all cogs whose directory structure is ./cogs/<module_name>
for folder in os.listdir("cogs"): for folder in os.listdir("cogs"):
@@ -33,8 +41,9 @@ def main():
print(f"{bot.user.name} has connected to Discord!") print(f"{bot.user.name} has connected to Discord!")
# Run Discord bot # Run Discord bot
bot.run(config.DISCORD_TOKEN) bot.run(bot.config.params.discord_token)
if __name__ == "__main__": if __name__ == "__main__":
sys.argv.append("hydra.job.chdir=False")
main() main()
+3
View File
@@ -0,0 +1,3 @@
Discord permissions required for bot to operate:
manage channels
+1
View File
@@ -1,3 +1,4 @@
nextcord==2.4.2 nextcord==2.4.2
python-dotenv==1.0.0 python-dotenv==1.0.0
pytz==2022.7.1 pytz==2022.7.1
hydra-core==1.3.2
+26
View File
@@ -0,0 +1,26 @@
"""
File contains util functions.
"""
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.
"""
year = datetime.today().year
month = int(day.split("/")[0])
day = int(day.split("/")[1])
# 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)