Compare commits
61
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0eae8afa2a | ||
|
|
d79c990558 | ||
|
|
3465698b85 | ||
|
|
1731bd46a6 | ||
|
|
1b4c5780df | ||
|
|
d67940aa26 | ||
|
|
9867f91268 | ||
|
|
4763651e93 | ||
|
|
1c76e6806c | ||
|
|
5cb9be7e37 | ||
|
|
d573595114 | ||
|
|
7bded4aa57 | ||
|
|
a6c6a80f8f | ||
|
|
a52979a847 | ||
|
|
8eae82536e | ||
|
|
e5e638edc9 | ||
|
|
fc9eb7b151 | ||
|
|
1cb16d5a39 | ||
|
|
797f8a46aa | ||
|
|
13325616fc | ||
|
|
5c0f014eaa | ||
|
|
b270922a7f | ||
|
|
9914d77d52 | ||
|
|
edc7722f76 | ||
|
|
f0beb7821f | ||
|
|
1044f8a521 | ||
|
|
9f3b625cd2 | ||
|
|
abd9be6341 | ||
|
|
cb6530ff57 | ||
|
|
d223a6b14e | ||
|
|
8a48c193ab | ||
|
|
bb6bf2b3d8 | ||
|
|
52aa4742e5 | ||
|
|
bca5b0d84b | ||
|
|
3a2b2d7858 | ||
|
|
7bbb6b237c | ||
|
|
0f05751ce8 | ||
|
|
d32c27c291 | ||
|
|
391c36e7f2 | ||
|
|
846df89510 | ||
|
|
79525c64d2 | ||
|
|
d95d6238d0 | ||
|
|
b08d3e3863 | ||
|
|
afc4430b31 | ||
|
|
00580460c6 | ||
|
|
512fa23387 | ||
|
|
8bbe7388c6 | ||
|
|
811317fc0a | ||
|
|
f357df807b | ||
|
|
42cfbc78eb | ||
|
|
17cd9c1f6d | ||
|
|
99ff3fc83a | ||
|
|
f85d1aafd0 | ||
|
|
1eaa90e9cf | ||
|
|
43414abb71 | ||
|
|
dfece4adf6 | ||
|
|
90c5fea67c | ||
|
|
6c9de43e73 | ||
|
|
d3858a7ba1 | ||
|
|
f62f88113d | ||
|
|
9d2382bf1d |
@@ -1,8 +1,6 @@
|
|||||||
name: build_develop
|
name: build_develop
|
||||||
|
|
||||||
on:
|
on: [push]
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -15,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
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
name: build_develop
|
name: build_latest
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -14,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
|
||||||
|
|||||||
@@ -137,3 +137,6 @@ deleted_backups/
|
|||||||
highscores_data/
|
highscores_data/
|
||||||
TODO.txt
|
TODO.txt
|
||||||
sql_tester.py
|
sql_tester.py
|
||||||
|
conf
|
||||||
|
outputs
|
||||||
|
dakiny.db
|
||||||
+2
-1
@@ -11,10 +11,11 @@ COPY cogs cogs/
|
|||||||
COPY dakiny_bot.py dakiny_bot.py
|
COPY dakiny_bot.py dakiny_bot.py
|
||||||
COPY requirements.txt requirements.txt
|
COPY requirements.txt requirements.txt
|
||||||
COPY config.py config.py
|
COPY config.py config.py
|
||||||
|
COPY classes classes/
|
||||||
|
|
||||||
RUN pip install -r requirements.txt
|
RUN pip install -r requirements.txt
|
||||||
|
|
||||||
# remove build deps that aren't needed to run
|
# remove build deps that aren't needed to run
|
||||||
# RUN apt-get -y remove python3-dev build-essential && apt-get -y autoremove
|
# RUN apt-get -y remove python3-dev build-essential && apt-get -y autoremove
|
||||||
|
|
||||||
CMD ["python3", "./dakiny_bot.py"]
|
CMD ["python3", "-u", "./dakiny_bot.py"]
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
Here's the {quarter} Clan Council:
|
||||||
|
|
||||||
|
Dakiny Council:
|
||||||
|
{council}
|
||||||
|
|
||||||
|
Dakiny High Council:
|
||||||
|
{high_council}
|
||||||
@@ -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}>.
|
||||||
@@ -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}.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<@&{clan_council_elect_role_id}> it is now time to vote for the high council! You have until {term_start} to complete the vote.
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,230 @@
|
|||||||
|
"""
|
||||||
|
Contains the election cycle class
|
||||||
|
"""
|
||||||
|
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
import nextcord
|
||||||
|
from nextcord.ext import commands
|
||||||
|
from sqlalchemy import select
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from config import DakinyConfig
|
||||||
|
from database.sql_data_classes import ElectionInformation, engine
|
||||||
|
from utils import get_datetime
|
||||||
|
|
||||||
|
|
||||||
|
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, cycle: int, bot: commands.Bot) -> 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.
|
||||||
|
bot: the discord bot instance
|
||||||
|
"""
|
||||||
|
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_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.guild: nextcord.Guild = bot.get_guild(self.config.params.guild_id)
|
||||||
|
|
||||||
|
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_announce(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(
|
||||||
|
clan_council_elect_role_id=self.config.params.clan_council_elect_role_id,
|
||||||
|
highcouncil_election_announce=self.highcouncil_election_announce.strftime("%B %d"),
|
||||||
|
highcouncil_election_start=self.highcouncil_election_start.strftime("%B %d"),
|
||||||
|
highcouncil_election_end=self.highcouncil_election_end.strftime("%B %d"),
|
||||||
|
term_start_date=self.term_start.strftime("%B %d"),
|
||||||
|
)
|
||||||
|
|
||||||
|
def build_highcouncil_vote_start(self) -> str:
|
||||||
|
"""
|
||||||
|
Builds the message to announce that highcouncil voting has started.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Message to send.
|
||||||
|
"""
|
||||||
|
|
||||||
|
with open("bot_messages/highcouncil_vote.txt", mode="r", encoding="utf8") as file:
|
||||||
|
message = file.read()
|
||||||
|
|
||||||
|
return message.format(
|
||||||
|
clan_council_elect_role_id=self.config.params.clan_council_elect_role_id,
|
||||||
|
term_start_date=self.term_start.strftime("%B %d"),
|
||||||
|
)
|
||||||
|
|
||||||
|
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:
|
||||||
|
"""
|
||||||
|
Function checks current date to see if something needs to be run.
|
||||||
|
"""
|
||||||
|
today = datetime.today()
|
||||||
|
|
||||||
|
# check all dates if action is needed
|
||||||
|
if today.date() == self.election_start.date():
|
||||||
|
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_start.date():
|
||||||
|
await self.highcouncil_election_start_action()
|
||||||
|
elif today.date() == self.term_start.date():
|
||||||
|
await self.term_start_action()
|
||||||
|
|
||||||
|
async def election_start_action(self) -> None:
|
||||||
|
"""
|
||||||
|
Performs required actions for when the election cycle starts.
|
||||||
|
"""
|
||||||
|
# create new channel for election
|
||||||
|
category: nextcord.CategoryChannel = self.guild.get_channel(self.config.params.active_category_id)
|
||||||
|
quarter_name: str = self.get_election_cycle_name()
|
||||||
|
|
||||||
|
# create new entry into database.
|
||||||
|
election_channel: nextcord.TextChannel = await category.create_text_channel(quarter_name)
|
||||||
|
with Session(engine) as session:
|
||||||
|
new_cycle = ElectionInformation(ElectionCycle=quarter_name, ElectionChannel=election_channel.id)
|
||||||
|
session.add(new_cycle)
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
# 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:
|
||||||
|
"""
|
||||||
|
Sends message informing members that voting has started.
|
||||||
|
Removes write permission from the general clan member role.
|
||||||
|
Adds the voting emote to all messages not sent by the vote. (This is temporary until voting is managed.)
|
||||||
|
"""
|
||||||
|
message = self.build_vote_start()
|
||||||
|
clan_member_role: nextcord.Role = self.guild.get_role(self.config.params.clan_member_role_id)
|
||||||
|
election_channel: nextcord.TextChannel = self.guild.get_channel(self.get_election_info().ElectionChannel)
|
||||||
|
await election_channel.send(message)
|
||||||
|
await election_channel.set_permissions(clan_member_role, send_messages=False)
|
||||||
|
|
||||||
|
async def voting_end_action(self) -> None:
|
||||||
|
"""
|
||||||
|
Performs required actions for when voting time has ended.
|
||||||
|
"""
|
||||||
|
message = self.build_highcouncil_election_announce()
|
||||||
|
council_elect_role: nextcord.Role = self.guild.get_role(self.config.params.clan_council_elect_role_id)
|
||||||
|
election_channel: nextcord.TextChannel = self.guild.get_channel(self.get_election_info().ElectionChannel)
|
||||||
|
await election_channel.send(message)
|
||||||
|
await election_channel.set_permissions(council_elect_role, send_messages=True)
|
||||||
|
|
||||||
|
async def highcouncil_election_start_action(self) -> None:
|
||||||
|
"""
|
||||||
|
Performs required actions for when the highcouncil is elected.
|
||||||
|
"""
|
||||||
|
|
||||||
|
message = self.build_highcouncil_vote_start()
|
||||||
|
election_channel: nextcord.TextChannel = self.guild.get_channel(self.get_election_info().ElectionChannel)
|
||||||
|
await election_channel.send(message)
|
||||||
|
|
||||||
|
async def term_start_action(self) -> None:
|
||||||
|
"""
|
||||||
|
Moves the election channel into the archive category.
|
||||||
|
"""
|
||||||
|
election_channel: nextcord.TextChannel = self.guild.get_channel(self.get_election_info().ElectionChannel)
|
||||||
|
archive: nextcord.CategoryChannel = self.guild.get_channel(self.config.params.archive_channel_group_id)
|
||||||
|
|
||||||
|
await election_channel.move(beginning=True, category=archive)
|
||||||
|
|
||||||
|
def get_election_cycle_name(self) -> str:
|
||||||
|
"""
|
||||||
|
Builds the unique name of the current election cycle
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: the name of the election cycle
|
||||||
|
"""
|
||||||
|
|
||||||
|
return f"{self.election_start.year} {self.elecion_cycle.name} election"
|
||||||
|
|
||||||
|
def get_election_info(self) -> ElectionInformation:
|
||||||
|
"""
|
||||||
|
Gets information for current election.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ElectionInformation: The information from the database about the current election.
|
||||||
|
"""
|
||||||
|
|
||||||
|
with Session(engine) as session:
|
||||||
|
stmt = select(ElectionInformation).where(
|
||||||
|
ElectionInformation.ElectionCycle == self.get_election_cycle_name()
|
||||||
|
)
|
||||||
|
return session.scalar(stmt)
|
||||||
@@ -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))
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
"""
|
||||||
|
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()
|
||||||
|
|
||||||
|
@test.subcommand()
|
||||||
|
async def voting_start(self, interaction: nextcord.Interaction):
|
||||||
|
"""
|
||||||
|
tests the voting start action
|
||||||
|
|
||||||
|
Args:
|
||||||
|
interaction (nextcord.Interaction): _description_
|
||||||
|
"""
|
||||||
|
cycle = ElectionCycle(0, self.bot)
|
||||||
|
await cycle.voting_start_action()
|
||||||
@@ -2,11 +2,13 @@
|
|||||||
_summary_
|
_summary_
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import nextcord
|
|
||||||
from nextcord.ext import commands, tasks
|
|
||||||
from datetime import time
|
from datetime import time
|
||||||
|
|
||||||
|
from nextcord.ext import commands, tasks
|
||||||
from pytz import timezone
|
from pytz import timezone
|
||||||
|
|
||||||
|
from classes.election_cycle import ElectionCycle
|
||||||
|
|
||||||
|
|
||||||
class DailyLoop(commands.Cog, name="Daily Loop"):
|
class DailyLoop(commands.Cog, name="Daily Loop"):
|
||||||
"""
|
"""
|
||||||
@@ -15,6 +17,8 @@ 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
|
||||||
|
# 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):
|
||||||
@@ -28,5 +32,6 @@ class DailyLoop(commands.Cog, name="Daily Loop"):
|
|||||||
@tasks.loop(time=time(hour=0, tzinfo=timezone("GMT")))
|
@tasks.loop(time=time(hour=0, tzinfo=timezone("GMT")))
|
||||||
async def do_thing(self):
|
async def do_thing(self):
|
||||||
""" """
|
""" """
|
||||||
imcompiling: nextcord.User = await self.bot.fetch_user(206602713511886849)
|
print("loop")
|
||||||
await imcompiling.send("its reset in runescape")
|
await self.current_election.check_date()
|
||||||
|
# TODO need to figure out how to change the election cycle after current one is over
|
||||||
|
|||||||
@@ -1,8 +1,47 @@
|
|||||||
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
|
||||||
|
guild_id: int
|
||||||
|
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
|
||||||
|
|||||||
+14
-3
@@ -1,12 +1,20 @@
|
|||||||
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
|
import database.create_database as create_database
|
||||||
|
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 +29,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"):
|
||||||
@@ -30,11 +39,13 @@ def main():
|
|||||||
async def on_ready():
|
async def on_ready():
|
||||||
"""When Discord is connected"""
|
"""When Discord is connected"""
|
||||||
assert bot.user is not None
|
assert bot.user is not None
|
||||||
|
create_database # pylint: disable=pointless-statement
|
||||||
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()
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
"""
|
||||||
|
Small script to create the database if it doesn't already exist.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sqlite3
|
||||||
|
from os.path import exists
|
||||||
|
|
||||||
|
if not exists("dakiny.db"):
|
||||||
|
with sqlite3.connect("dakiny.db") as con:
|
||||||
|
cur = con.cursor()
|
||||||
|
|
||||||
|
with open("database/database.sql", mode="r", encoding="utf-8") as file:
|
||||||
|
database_script = file.read()
|
||||||
|
cur.executescript(database_script)
|
||||||
|
con.commit()
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
-- CREATE DATABASE dakiny;
|
||||||
|
-- use dakiny;
|
||||||
|
|
||||||
|
CREATE TABLE ElectionInformation(
|
||||||
|
ElectionCycle varchar(30) not null unique,
|
||||||
|
ElectionChannel bigint unsigned not null,
|
||||||
|
PRIMARY KEY (ElectionCycle)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE CouncilMembers(
|
||||||
|
ElectionCycle varchar(30) not null unique,
|
||||||
|
MemberId bigint unsigned not null,
|
||||||
|
Elected boolean DEFAULT false,
|
||||||
|
HighCouncilElect boolean DEFAULT false,
|
||||||
|
DiscordManager boolean DEFAULT false,
|
||||||
|
ClanLeader boolean DEFAULT false,
|
||||||
|
FOREIGN KEY (ElectionCycle) REFERENCES ElectionInformation(ElectionCycle),
|
||||||
|
PRIMARY KEY (ElectionCycle, MemberId)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE Votes(
|
||||||
|
ElectionCycle varchar(30) not null unique,
|
||||||
|
VoterId bigint unsigned not null,
|
||||||
|
VotedForId bigint unsigned not null,
|
||||||
|
FOREIGN KEY (ElectionCycle) REFERENCES ElectionInformation(ElectionCycle),
|
||||||
|
PRIMARY KEY (ElectionCycle, VoterId)
|
||||||
|
);
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
Simple file to set up the sqlalchemy connection to database.
|
||||||
|
"""
|
||||||
|
from sqlalchemy import create_engine
|
||||||
|
from sqlalchemy.ext.automap import automap_base
|
||||||
|
|
||||||
|
# connect with sql alchemy and autoload schema
|
||||||
|
URL = "sqlite+pysqlite:///dakiny.db"
|
||||||
|
engine = create_engine(URL)
|
||||||
|
Base = automap_base()
|
||||||
|
Base.prepare(autoload_with=engine)
|
||||||
|
|
||||||
|
# define the classes
|
||||||
|
ElectionInformation = Base.classes.ElectionInformation
|
||||||
|
CouncilMembers = Base.classes.CouncilMembers
|
||||||
|
Votes = Base.classes.Votes
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
Discord permissions required for bot to operate:
|
||||||
|
|
||||||
|
manage channels
|
||||||
|
manage roles
|
||||||
+3
-1
@@ -1,3 +1,5 @@
|
|||||||
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
|
||||||
|
sqlalchemy<2.0
|
||||||
@@ -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)
|
||||||
Reference in New Issue
Block a user