Compare commits
38
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cbfd6e20bc | ||
|
|
34da664af0 | ||
|
|
c6c44afb76 | ||
|
|
c2d8239911 | ||
|
|
bec6494697 | ||
|
|
29a6b000ac | ||
|
|
dbd2863aab | ||
|
|
426f009ca7 | ||
|
|
ed07b2e231 | ||
|
|
0caa41cc90 | ||
|
|
e93f37dcf0 | ||
|
|
44c3961c46 | ||
|
|
0dcdd038b8 | ||
|
|
eb5d465ac9 | ||
|
|
eb1c87fbf4 | ||
|
|
cbd73a104e | ||
|
|
95ad47368c | ||
|
|
dea3f7cabf | ||
|
|
6a71e9ab1d | ||
|
|
bc56830396 | ||
|
|
1d14f8171d | ||
|
|
2efd380a7e | ||
|
|
475170025a | ||
|
|
f4f3bfe103 | ||
|
|
6827658b4a | ||
|
|
85a3ea0418 | ||
|
|
0b27c60bea | ||
|
|
1a6f373e03 | ||
|
|
0ff13453be | ||
|
|
6b29e26140 | ||
|
|
bbcb4e4dff | ||
|
|
00c8227b8e | ||
|
|
04c44bc470 | ||
|
|
3761d3bb77 | ||
|
|
a76bc02d4a | ||
|
|
3890c30d1f | ||
|
|
1e1407c918 | ||
|
|
be408307aa |
@@ -105,6 +105,7 @@ venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
poetry.lock
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
|
||||
+4
-8
@@ -1,23 +1,19 @@
|
||||
FROM python:3.10.6-slim
|
||||
FROM python:3.11.9-slim
|
||||
|
||||
# install dependencies
|
||||
RUN apt-get update && apt-get -y upgrade && apt-get -y install git
|
||||
|
||||
RUN pip install --upgrade pip
|
||||
RUN apt-get update && apt-get -y upgrade && apt-get -y install git sqlite3
|
||||
|
||||
WORKDIR /home
|
||||
|
||||
COPY requirements.txt requirements.txt
|
||||
|
||||
COPY config.py config.py
|
||||
COPY main.py main.py
|
||||
COPY utils.py utils.py
|
||||
COPY pyproject.toml pyproject.toml
|
||||
|
||||
COPY bot_messages bot_messages/
|
||||
COPY classes classes/
|
||||
COPY cogs cogs/
|
||||
COPY database_setup database_setup/
|
||||
|
||||
RUN pip install -r requirements.txt
|
||||
RUN pip install --upgrade pip && pip install poetry && poetry export -f requirements.txt --output requirements.txt && pip install -r requirements.txt
|
||||
|
||||
CMD ["python3", "-u", "./main.py"]
|
||||
@@ -1,8 +0,0 @@
|
||||
Here's the **{quarter}** Clan Council:
|
||||
|
||||
Dakiny High Council:
|
||||
<@{clan_leader}> - Clan Leader
|
||||
<@{discord_manager}> - Discord Manager
|
||||
|
||||
Dakiny Council:
|
||||
{council}
|
||||
@@ -1,54 +0,0 @@
|
||||
```fix
|
||||
Dakiny's Clan Council election
|
||||
```
|
||||
Dakiny is an organization built by and for its members. We are a community of people who come together to support each other in the creation of this magical and unique experience. To sustain what we've created here, and to grow and involve into the future, we vote in our leadership every 3 months to provide an opportunity for new clan members to represent their faction of Dakiny in the Clan Council.
|
||||
|
||||
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 at least 13 weeks (enough time to have participated in at least one election);
|
||||
|
||||
**ii)** have no warnings under their name in the last 6 months and
|
||||
|
||||
**iii)** have been active in the clan for the past 13 weeks prior to the first day of elections. To be considered active in that timeframe, one must fulfill the following criteria: hosted/participated in at least three clan events, each one in a different week;
|
||||
|
||||
**iv)** are not running under aliases that hide their true identity.
|
||||
|
||||
Candidates are permitted to vote for themselves. Once a candidate reaches 3 votes from clan members, they have earned their seat, meaning no more votes can be cast for them.
|
||||
|
||||
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}
|
||||
Term: {term_start} - {term_end}
|
||||
|
||||
**{election_start}**: Post here to announce candidacy
|
||||
**{voting_start}**: Voting begins
|
||||
**{voting_end}**: Votes are tallied and winners are announced
|
||||
**{highcouncil_election}**: The new Clan Council votes who among them shall receive the two High Council positions of: Clan Leader and Discord Server Manager.
|
||||
**{term_start}**: 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} with the inauguration scheduled for {term_end}.
|
||||
---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}>.
|
||||
@@ -1,12 +0,0 @@
|
||||
`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 vote once in each category (Discord Server Manager and Clan Leader). Alternatively, you may choose to intentionally abstain.
|
||||
|
||||
Votes can be cast until {highcouncil_election_end}, so we can announce and have everyone set up to start the term by {term_start}.
|
||||
@@ -1 +0,0 @@
|
||||
<@&{clan_council_elect_role_id}> it is now time to vote for the high council! You have until {term_start} to complete the vote.
|
||||
@@ -1,14 +0,0 @@
|
||||
====================
|
||||
<@&{clan_member_id}>, it's time to vote for clan council.
|
||||
|
||||
Press the Vote button to vote!
|
||||
Voting ends next {voting_end_day}, {voting_end}.
|
||||
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
|
||||
+32
-26
@@ -21,13 +21,13 @@ from database_setup.sql_data_classes import (
|
||||
engine,
|
||||
)
|
||||
from utils import (
|
||||
TEMPLATES_DIR,
|
||||
VoteType,
|
||||
build_highcouncil_votes_str,
|
||||
build_votes_str,
|
||||
edit_clan_council_info,
|
||||
get_cl_election_name,
|
||||
get_datetime,
|
||||
get_dm_election_name,
|
||||
get_election_cycle_name,
|
||||
get_election_name,
|
||||
)
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ class ElectionCycle:
|
||||
"""
|
||||
|
||||
with open(
|
||||
"bot_messages/election_announcement.txt", mode="r", encoding="utf8"
|
||||
f"{TEMPLATES_DIR}/election_announcement.txt", mode="r", encoding="utf8"
|
||||
) as file:
|
||||
message = file.read()
|
||||
|
||||
@@ -110,7 +110,7 @@ class ElectionCycle:
|
||||
"""
|
||||
|
||||
with open(
|
||||
"bot_messages/highcouncil_election.txt", mode="r", encoding="utf8"
|
||||
f"{TEMPLATES_DIR}/highcouncil_election.txt", mode="r", encoding="utf8"
|
||||
) as file:
|
||||
message = file.read()
|
||||
|
||||
@@ -137,7 +137,7 @@ class ElectionCycle:
|
||||
"""
|
||||
|
||||
with open(
|
||||
"bot_messages/highcouncil_vote.txt", mode="r", encoding="utf8"
|
||||
f"{TEMPLATES_DIR}/highcouncil_vote.txt", mode="r", encoding="utf8"
|
||||
) as file:
|
||||
message = file.read()
|
||||
|
||||
@@ -154,7 +154,7 @@ class ElectionCycle:
|
||||
str: Message to send
|
||||
"""
|
||||
|
||||
with open("bot_messages/vote_start.txt", mode="r", encoding="utf8") as file:
|
||||
with open(f"{TEMPLATES_DIR}/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
|
||||
@@ -198,7 +198,7 @@ class ElectionCycle:
|
||||
parent: nextcord.TextChannel = self.guild.get_channel(
|
||||
self.config.params.voting_parent_id
|
||||
)
|
||||
quarter_name: str = get_election_cycle_name(self.config)
|
||||
quarter_name: str = get_election_name(self.config)
|
||||
|
||||
thread_msg: nextcord.Message = await parent.send(content=quarter_name)
|
||||
|
||||
@@ -246,8 +246,7 @@ class ElectionCycle:
|
||||
|
||||
with Session(engine) as session:
|
||||
stmt = select(ElectionInformation).where(
|
||||
ElectionInformation.ElectionCycle
|
||||
== get_election_cycle_name(self.config)
|
||||
ElectionInformation.ElectionCycle == get_election_name(self.config)
|
||||
)
|
||||
info = session.scalar(stmt)
|
||||
info.CouncilVoteMsgId = vote_msg.id
|
||||
@@ -276,7 +275,7 @@ class ElectionCycle:
|
||||
message, view=NominateHighCouncilView(self.config, self.guild)
|
||||
)
|
||||
|
||||
election_name = get_election_cycle_name(self.config)
|
||||
election_name = get_election_name(self.config)
|
||||
|
||||
with Session(engine) as session:
|
||||
# remove vote button from vote message
|
||||
@@ -294,7 +293,11 @@ class ElectionCycle:
|
||||
session.commit()
|
||||
|
||||
# add the highcouncil role to everyone with 3 or more votes. set elected to true in database
|
||||
stmt = select(Votes.VotedForId).where(Votes.ElectionCycle == election_name)
|
||||
stmt = (
|
||||
select(Votes.VotedForId)
|
||||
.where(Votes.ElectionCycle == election_name)
|
||||
.where(Votes.Type == VoteType.COUNCIL)
|
||||
)
|
||||
vote_count = collections.Counter(session.scalars(stmt))
|
||||
|
||||
for user_id, votes in vote_count.items():
|
||||
@@ -332,8 +335,7 @@ class ElectionCycle:
|
||||
|
||||
with Session(engine) as session:
|
||||
stmt = select(ElectionInformation).where(
|
||||
ElectionInformation.ElectionCycle
|
||||
== get_election_cycle_name(self.config)
|
||||
ElectionInformation.ElectionCycle == get_election_name(self.config)
|
||||
)
|
||||
info = session.scalar(stmt)
|
||||
info.HighCouncilVoteMsgId = vote_msg.id
|
||||
@@ -359,13 +361,13 @@ class ElectionCycle:
|
||||
self.config.params.clan_council_role_id
|
||||
)
|
||||
|
||||
election_cycle = get_election_name(self.config)
|
||||
|
||||
with Session(engine) as session:
|
||||
# role management
|
||||
stmt = (
|
||||
select(CouncilMembers.MemberId)
|
||||
.where(
|
||||
CouncilMembers.ElectionCycle == get_election_cycle_name(self.config)
|
||||
)
|
||||
.where(CouncilMembers.ElectionCycle == election_cycle)
|
||||
.where(CouncilMembers.Elected == true())
|
||||
)
|
||||
|
||||
@@ -382,14 +384,19 @@ class ElectionCycle:
|
||||
|
||||
# announce high council members
|
||||
# ties are undefined behavior.
|
||||
dm_election = get_dm_election_name(self.config)
|
||||
cl_election = get_cl_election_name(self.config)
|
||||
stmt = select(Votes.VotedForId).where(Votes.ElectionCycle == dm_election)
|
||||
stmt = (
|
||||
select(Votes.VotedForId)
|
||||
.where(Votes.ElectionCycle == election_cycle)
|
||||
.where(Votes.Type == VoteType.DISCORDMANAGER)
|
||||
)
|
||||
dm_votes = collections.Counter(session.scalars(stmt))
|
||||
stmt = select(Votes.VotedForId).where(Votes.ElectionCycle == cl_election)
|
||||
stmt = (
|
||||
select(Votes.VotedForId)
|
||||
.where(Votes.ElectionCycle == election_cycle)
|
||||
.where(Votes.Type == VoteType.CLANLEADER)
|
||||
)
|
||||
cl_votes = collections.Counter(session.scalars(stmt))
|
||||
|
||||
election_cycle = get_election_cycle_name(self.config)
|
||||
try:
|
||||
dm_winner = max(dm_votes, key=dm_votes.get)
|
||||
stmt = (
|
||||
@@ -421,14 +428,14 @@ class ElectionCycle:
|
||||
|
||||
# clear view from vote message
|
||||
stmt = select(ElectionInformation.HighCouncilVoteMsgId).where(
|
||||
ElectionInformation.ElectionCycle == get_election_cycle_name(self.config)
|
||||
ElectionInformation.ElectionCycle == get_election_name(self.config)
|
||||
)
|
||||
vote_msg_id = session.scalar(stmt)
|
||||
vote_msg: nextcord.Message = await election_thread.fetch_message(vote_msg_id)
|
||||
await vote_msg.edit(view=None)
|
||||
|
||||
highcouncil_msg: str = (
|
||||
f"Congratulations for our high council members!\n Clan Leader: <@{cl_winner}>\n"
|
||||
f"Congratulations for our high council members!\nClan Leader: <@{cl_winner}>\n"
|
||||
)
|
||||
highcouncil_msg += f"Discord Manager: <@{dm_winner}>"
|
||||
await election_thread.send(highcouncil_msg)
|
||||
@@ -450,8 +457,7 @@ class ElectionCycle:
|
||||
|
||||
with Session(engine) as session:
|
||||
stmt = select(ElectionInformation).where(
|
||||
ElectionInformation.ElectionCycle
|
||||
== get_election_cycle_name(self.config)
|
||||
ElectionInformation.ElectionCycle == get_election_name(self.config)
|
||||
)
|
||||
return session.scalar(stmt)
|
||||
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: gitea_webhook.json
|
||||
# timestamp: 2024-05-16T21:15:27+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Author(BaseModel):
|
||||
name: str
|
||||
email: str
|
||||
username: str
|
||||
|
||||
|
||||
class Committer(BaseModel):
|
||||
name: str
|
||||
email: str
|
||||
username: str
|
||||
|
||||
|
||||
class Commit(BaseModel):
|
||||
id: str
|
||||
message: str
|
||||
url: str
|
||||
author: Author
|
||||
committer: Committer
|
||||
verification: None
|
||||
timestamp: str
|
||||
added: List[str]
|
||||
removed: List[str]
|
||||
modified: List[str]
|
||||
|
||||
|
||||
class HeadCommit(BaseModel):
|
||||
id: str
|
||||
message: str
|
||||
url: str
|
||||
author: Author
|
||||
committer: Committer
|
||||
verification: None
|
||||
timestamp: str
|
||||
added: List[str]
|
||||
removed: List[str]
|
||||
modified: List[str]
|
||||
|
||||
|
||||
class Owner(BaseModel):
|
||||
id: int
|
||||
login: str
|
||||
login_name: str
|
||||
full_name: str
|
||||
email: str
|
||||
avatar_url: str
|
||||
language: str
|
||||
is_admin: bool
|
||||
last_login: str
|
||||
created: str
|
||||
restricted: bool
|
||||
active: bool
|
||||
prohibit_login: bool
|
||||
location: str
|
||||
website: str
|
||||
description: str
|
||||
visibility: str
|
||||
followers_count: int
|
||||
following_count: int
|
||||
starred_repos_count: int
|
||||
username: str
|
||||
|
||||
|
||||
class Permissions(BaseModel):
|
||||
admin: bool
|
||||
push: bool
|
||||
pull: bool
|
||||
|
||||
|
||||
class Repository(BaseModel):
|
||||
id: int
|
||||
owner: Owner
|
||||
name: str
|
||||
full_name: str
|
||||
description: str
|
||||
empty: bool
|
||||
private: bool
|
||||
fork: bool
|
||||
template: bool
|
||||
parent: None
|
||||
mirror: bool
|
||||
size: int
|
||||
language: str
|
||||
languages_url: str
|
||||
html_url: str
|
||||
url: str
|
||||
link: str
|
||||
ssh_url: str
|
||||
clone_url: str
|
||||
original_url: str
|
||||
website: str
|
||||
stars_count: int
|
||||
forks_count: int
|
||||
watchers_count: int
|
||||
open_issues_count: int
|
||||
open_pr_counter: int
|
||||
release_counter: int
|
||||
default_branch: str
|
||||
archived: bool
|
||||
created_at: str
|
||||
updated_at: str
|
||||
archived_at: str
|
||||
permissions: Permissions
|
||||
has_issues: bool
|
||||
has_wiki: bool
|
||||
has_pull_requests: bool
|
||||
has_projects: bool
|
||||
has_releases: bool
|
||||
has_packages: bool
|
||||
has_actions: bool
|
||||
ignore_whitespace_conflicts: bool
|
||||
allow_merge_commits: bool
|
||||
allow_rebase: bool
|
||||
allow_rebase_explicit: bool
|
||||
allow_squash_merge: bool
|
||||
allow_rebase_update: bool
|
||||
default_delete_branch_after_merge: bool
|
||||
default_merge_style: str
|
||||
default_allow_maintainer_edit: bool
|
||||
avatar_url: str
|
||||
internal: bool
|
||||
mirror_interval: str
|
||||
mirror_updated: str
|
||||
repo_transfer: None
|
||||
|
||||
|
||||
class Pusher(BaseModel):
|
||||
id: int
|
||||
login: str
|
||||
login_name: str
|
||||
full_name: str
|
||||
email: str
|
||||
avatar_url: str
|
||||
language: str
|
||||
is_admin: bool
|
||||
last_login: str
|
||||
created: str
|
||||
restricted: bool
|
||||
active: bool
|
||||
prohibit_login: bool
|
||||
location: str
|
||||
website: str
|
||||
description: str
|
||||
visibility: str
|
||||
followers_count: int
|
||||
following_count: int
|
||||
starred_repos_count: int
|
||||
username: str
|
||||
|
||||
|
||||
class Sender(BaseModel):
|
||||
id: int
|
||||
login: str
|
||||
login_name: str
|
||||
full_name: str
|
||||
email: str
|
||||
avatar_url: str
|
||||
language: str
|
||||
is_admin: bool
|
||||
last_login: str
|
||||
created: str
|
||||
restricted: bool
|
||||
active: bool
|
||||
prohibit_login: bool
|
||||
location: str
|
||||
website: str
|
||||
description: str
|
||||
visibility: str
|
||||
followers_count: int
|
||||
following_count: int
|
||||
starred_repos_count: int
|
||||
username: str
|
||||
|
||||
|
||||
class GiteaWebhook(BaseModel):
|
||||
ref: str
|
||||
before: str
|
||||
after: str
|
||||
compare_url: str
|
||||
commits: List[Commit]
|
||||
total_commits: int
|
||||
head_commit: HeadCommit
|
||||
repository: Repository
|
||||
pusher: Pusher
|
||||
sender: Sender
|
||||
@@ -13,7 +13,7 @@ from utils import (
|
||||
NOMINEE_SPLITTER,
|
||||
decorate_cl_nomination,
|
||||
decorate_dm_nomination,
|
||||
get_election_cycle_name,
|
||||
get_election_name,
|
||||
)
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ class NominateDMModal(nextcord.ui.Modal):
|
||||
|
||||
with Session(engine) as session:
|
||||
# user already in table
|
||||
cycle = get_election_cycle_name(self.config)
|
||||
cycle = get_election_name(self.config)
|
||||
stmt = (
|
||||
select(CouncilMembers)
|
||||
.where(CouncilMembers.ElectionCycle == cycle)
|
||||
@@ -127,7 +127,7 @@ class NominateCLModal(nextcord.ui.Modal):
|
||||
|
||||
with Session(engine) as session:
|
||||
# user already in table
|
||||
cycle = get_election_cycle_name(self.config)
|
||||
cycle = get_election_name(self.config)
|
||||
stmt = (
|
||||
select(CouncilMembers)
|
||||
.where(CouncilMembers.ElectionCycle == cycle)
|
||||
@@ -189,7 +189,7 @@ class NominateHighCouncilView(nextcord.ui.View):
|
||||
)
|
||||
return
|
||||
|
||||
cycle = get_election_cycle_name(self.config)
|
||||
cycle = get_election_name(self.config)
|
||||
usr_nominated = False
|
||||
speech_content = ""
|
||||
with Session(engine) as session:
|
||||
@@ -242,7 +242,7 @@ class NominateHighCouncilView(nextcord.ui.View):
|
||||
)
|
||||
return
|
||||
|
||||
cycle = get_election_cycle_name(self.config)
|
||||
cycle = get_election_name(self.config)
|
||||
usr_nominated = False
|
||||
speech_content = ""
|
||||
with Session(engine) as session:
|
||||
|
||||
@@ -9,7 +9,7 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from config import DakinyConfig
|
||||
from database_setup.sql_data_classes import CouncilMembers, engine
|
||||
from utils import NOMINEE_SPLITTER, decorate_nomination, get_election_cycle_name
|
||||
from utils import NOMINEE_SPLITTER, decorate_nomination, get_election_name
|
||||
|
||||
|
||||
class NominateModal(nextcord.ui.Modal):
|
||||
@@ -58,7 +58,7 @@ class NominateModal(nextcord.ui.Modal):
|
||||
msg = await interaction.channel.send(speech)
|
||||
|
||||
council_member = CouncilMembers(
|
||||
ElectionCycle=get_election_cycle_name(self.config),
|
||||
ElectionCycle=get_election_name(self.config),
|
||||
MemberId=interaction.user.id,
|
||||
SpeechMsgId=msg.id,
|
||||
)
|
||||
@@ -66,7 +66,7 @@ class NominateModal(nextcord.ui.Modal):
|
||||
session.commit()
|
||||
else:
|
||||
# user already nominated, just need to update the election speech
|
||||
cycle = get_election_cycle_name(self.config)
|
||||
cycle = get_election_name(self.config)
|
||||
stmt = (
|
||||
select(CouncilMembers)
|
||||
.where(CouncilMembers.ElectionCycle == cycle)
|
||||
@@ -120,7 +120,7 @@ class NominateView(nextcord.ui.View):
|
||||
)
|
||||
return
|
||||
|
||||
cycle = get_election_cycle_name(self.config)
|
||||
cycle = get_election_name(self.config)
|
||||
usr_nominated = False
|
||||
speech_content = ""
|
||||
with Session(engine) as session:
|
||||
|
||||
@@ -17,12 +17,7 @@ from database_setup.sql_data_classes import (
|
||||
Votes,
|
||||
engine,
|
||||
)
|
||||
from utils import (
|
||||
build_highcouncil_votes_str,
|
||||
get_cl_election_name,
|
||||
get_dm_election_name,
|
||||
get_election_cycle_name,
|
||||
)
|
||||
from utils import VoteType, build_highcouncil_votes_str, get_election_name
|
||||
|
||||
|
||||
class VoteSelect(nextcord.ui.StringSelect):
|
||||
@@ -39,20 +34,10 @@ class VoteSelect(nextcord.ui.StringSelect):
|
||||
self.election_type = election_type
|
||||
|
||||
async def callback(self, interaction: Interaction):
|
||||
# decide on election type
|
||||
match self.election_type:
|
||||
case "cl":
|
||||
election_name: str = get_cl_election_name(self.config)
|
||||
case "dm":
|
||||
election_name: str = get_dm_election_name(self.config)
|
||||
case _:
|
||||
# should never happen
|
||||
raise ValueError('election type was not "cl" or "dm"')
|
||||
|
||||
cycle: str = get_election_cycle_name(self.config)
|
||||
election_name: str = get_election_name(self.config)
|
||||
with Session(engine) as session:
|
||||
stmt = select(ElectionInformation.HighCouncilVoteMsgId).where(
|
||||
ElectionInformation.ElectionCycle == cycle
|
||||
ElectionInformation.ElectionCycle == election_name
|
||||
)
|
||||
vote_msg_id = session.scalar(stmt)
|
||||
|
||||
@@ -65,6 +50,7 @@ class VoteSelect(nextcord.ui.StringSelect):
|
||||
ElectionCycle=election_name,
|
||||
VoterId=interaction.user.id,
|
||||
VotedForId=voted_for,
|
||||
Type=self.election_type,
|
||||
)
|
||||
|
||||
try:
|
||||
@@ -72,11 +58,13 @@ class VoteSelect(nextcord.ui.StringSelect):
|
||||
session.commit()
|
||||
|
||||
except exc.IntegrityError:
|
||||
# user already has a vote in the election and election type
|
||||
session.rollback()
|
||||
stmt = (
|
||||
update(Votes)
|
||||
.where(Votes.ElectionCycle == election_name)
|
||||
.where(Votes.VoterId == interaction.user.id)
|
||||
.where(Votes.Type == self.election_type)
|
||||
.values(VotedForId=voted_for)
|
||||
)
|
||||
session.execute(stmt)
|
||||
@@ -135,28 +123,30 @@ class VoteHighcouncilView(nextcord.ui.View):
|
||||
)
|
||||
return
|
||||
|
||||
cycle: str = get_election_cycle_name(self.config)
|
||||
election: str = get_cl_election_name(self.config)
|
||||
election_name: str = get_election_name(self.config)
|
||||
|
||||
with Session(engine) as session:
|
||||
stmt = (
|
||||
select(Votes.VotedForId)
|
||||
.where(Votes.ElectionCycle == election)
|
||||
.where(Votes.ElectionCycle == election_name)
|
||||
.where(Votes.VoterId == interaction.user.id)
|
||||
.where(Votes.Type == VoteType.CLANLEADER)
|
||||
)
|
||||
voted_for = session.scalar(stmt)
|
||||
|
||||
stmt = (
|
||||
select(CouncilMembers.MemberId)
|
||||
.where(CouncilMembers.ElectionCycle == cycle)
|
||||
.where(CouncilMembers.ElectionCycle == election_name)
|
||||
.filter(CouncilMembers.ClanLeaderSpeechId.is_not(None))
|
||||
)
|
||||
|
||||
options = [
|
||||
SelectOption(
|
||||
label=f"{self.guild.get_member(nominee).display_name} ☑️"
|
||||
if voted_for == nominee
|
||||
else f"{self.guild.get_member(nominee).display_name}",
|
||||
label=(
|
||||
f"{self.guild.get_member(nominee).display_name} ☑️"
|
||||
if voted_for == nominee
|
||||
else f"{self.guild.get_member(nominee).display_name}"
|
||||
),
|
||||
value=str(nominee),
|
||||
)
|
||||
for nominee in sorted(
|
||||
@@ -173,7 +163,9 @@ class VoteHighcouncilView(nextcord.ui.View):
|
||||
|
||||
view = nextcord.ui.View()
|
||||
view.add_item(
|
||||
VoteSelect(options=options, config=self.config, election_type="cl")
|
||||
VoteSelect(
|
||||
options=options, config=self.config, election_type=VoteType.CLANLEADER
|
||||
)
|
||||
)
|
||||
|
||||
await interaction.response.send_message(
|
||||
@@ -211,28 +203,30 @@ class VoteHighcouncilView(nextcord.ui.View):
|
||||
)
|
||||
return
|
||||
|
||||
cycle: str = get_election_cycle_name(self.config)
|
||||
election: str = get_dm_election_name(self.config)
|
||||
election_name: str = get_election_name(self.config)
|
||||
|
||||
with Session(engine) as session:
|
||||
stmt = (
|
||||
select(Votes.VotedForId)
|
||||
.where(Votes.ElectionCycle == election)
|
||||
.where(Votes.ElectionCycle == election_name)
|
||||
.where(Votes.VoterId == interaction.user.id)
|
||||
.where(Votes.Type == VoteType.DISCORDMANAGER)
|
||||
)
|
||||
voted_for = session.scalar(stmt)
|
||||
|
||||
stmt = (
|
||||
select(CouncilMembers.MemberId)
|
||||
.where(CouncilMembers.ElectionCycle == cycle)
|
||||
.where(CouncilMembers.ElectionCycle == election_name)
|
||||
.filter(CouncilMembers.DiscordManagerSpeechId.is_not(None))
|
||||
)
|
||||
|
||||
options = [
|
||||
SelectOption(
|
||||
label=f"{self.guild.get_member(nominee).display_name} ☑️"
|
||||
if voted_for == nominee
|
||||
else f"{self.guild.get_member(nominee).display_name}",
|
||||
label=(
|
||||
f"{self.guild.get_member(nominee).display_name} ☑️"
|
||||
if voted_for == nominee
|
||||
else f"{self.guild.get_member(nominee).display_name}"
|
||||
),
|
||||
value=str(nominee),
|
||||
)
|
||||
for nominee in sorted(
|
||||
@@ -249,7 +243,11 @@ class VoteHighcouncilView(nextcord.ui.View):
|
||||
|
||||
view = nextcord.ui.View()
|
||||
view.add_item(
|
||||
VoteSelect(options=options, config=self.config, election_type="dm")
|
||||
VoteSelect(
|
||||
options=options,
|
||||
config=self.config,
|
||||
election_type=VoteType.DISCORDMANAGER,
|
||||
)
|
||||
)
|
||||
|
||||
await interaction.response.send_message(
|
||||
|
||||
+11
-6
@@ -17,7 +17,7 @@ from database_setup.sql_data_classes import (
|
||||
Votes,
|
||||
engine,
|
||||
)
|
||||
from utils import build_votes_str, get_election_cycle_name
|
||||
from utils import VoteType, build_votes_str, get_election_name
|
||||
|
||||
|
||||
class VoteSelect(nextcord.ui.StringSelect):
|
||||
@@ -31,7 +31,7 @@ class VoteSelect(nextcord.ui.StringSelect):
|
||||
self.config = config
|
||||
|
||||
async def callback(self, interaction: Interaction):
|
||||
election_name = get_election_cycle_name(self.config)
|
||||
election_name = get_election_name(self.config)
|
||||
with Session(engine) as session:
|
||||
stmt = select(ElectionInformation.CouncilVoteMsgId).where(
|
||||
ElectionInformation.ElectionCycle == election_name
|
||||
@@ -47,6 +47,7 @@ class VoteSelect(nextcord.ui.StringSelect):
|
||||
ElectionCycle=election_name,
|
||||
VoterId=interaction.user.id,
|
||||
VotedForId=voted_for,
|
||||
Type=VoteType.COUNCIL,
|
||||
)
|
||||
|
||||
try:
|
||||
@@ -59,6 +60,7 @@ class VoteSelect(nextcord.ui.StringSelect):
|
||||
update(Votes)
|
||||
.where(Votes.ElectionCycle == election_name)
|
||||
.where(Votes.VoterId == interaction.user.id)
|
||||
.where(Votes.Type == VoteType.COUNCIL)
|
||||
.values(VotedForId=voted_for)
|
||||
)
|
||||
session.execute(stmt)
|
||||
@@ -114,13 +116,14 @@ class VoteView(nextcord.ui.View):
|
||||
)
|
||||
return
|
||||
|
||||
election: str = get_election_cycle_name(self.config)
|
||||
election: str = get_election_name(self.config)
|
||||
|
||||
with Session(engine) as session:
|
||||
stmt = (
|
||||
select(Votes.VotedForId)
|
||||
.where(Votes.ElectionCycle == election)
|
||||
.where(Votes.VoterId == interaction.user.id)
|
||||
.where(Votes.Type == VoteType.COUNCIL)
|
||||
)
|
||||
voted_for = session.scalar(stmt)
|
||||
|
||||
@@ -130,9 +133,11 @@ class VoteView(nextcord.ui.View):
|
||||
|
||||
options = [
|
||||
SelectOption(
|
||||
label=f"{self.guild.get_member(nominee).display_name} ☑️"
|
||||
if voted_for == nominee
|
||||
else f"{self.guild.get_member(nominee).display_name}",
|
||||
label=(
|
||||
f"{self.guild.get_member(nominee).display_name} ☑️"
|
||||
if voted_for == nominee
|
||||
else f"{self.guild.get_member(nominee).display_name}"
|
||||
),
|
||||
value=str(nominee),
|
||||
)
|
||||
for nominee in sorted(
|
||||
|
||||
@@ -10,8 +10,9 @@ from fastapi import FastAPI
|
||||
from git import Repo
|
||||
from nextcord.ext import commands
|
||||
|
||||
from classes.gitea_webhook import GiteaWebhook
|
||||
from config import DakinyConfig
|
||||
from utils import REPO_DIR, update_clan_messages
|
||||
from utils import REPO_DIR, edit_clan_council_info, update_clan_messages
|
||||
|
||||
app = FastAPI()
|
||||
REPO_URL = "https://gitea.cheezers.org/imcompiling/dakiny-messages.git"
|
||||
@@ -38,16 +39,38 @@ class ClanMessages(commands.Cog, name="Clan Messages"):
|
||||
server = uvicorn.Server(config)
|
||||
await server.serve()
|
||||
|
||||
async def update_messages(self):
|
||||
async def update_messages(self, hook_info: GiteaWebhook):
|
||||
"""
|
||||
Pulls from git and then calls method to update all the discord messages as described by the
|
||||
files in the git repo.
|
||||
"""
|
||||
|
||||
# Get all modified/added files
|
||||
modified_files: list[str] = [
|
||||
file
|
||||
for list in [commit.modified + commit.added for commit in hook_info.commits]
|
||||
for file in list
|
||||
]
|
||||
|
||||
template_updates = {
|
||||
file.split("/")[1] for file in modified_files if "message_templates" in file
|
||||
}
|
||||
channel_updates = {
|
||||
file.split("/")[1]
|
||||
for file in modified_files
|
||||
if file.split("/")[1] not in template_updates
|
||||
}
|
||||
|
||||
self.origin.pull()
|
||||
guild: nextcord.Guild = self.bot.get_guild(self.config.params.guild_id)
|
||||
|
||||
await update_clan_messages(guild)
|
||||
await update_clan_messages(guild, channel_updates)
|
||||
|
||||
# if the council members template has changed, update the message
|
||||
if "council_members.txt" in template_updates:
|
||||
await edit_clan_council_info(
|
||||
self.bot.get_guild(self.config.params.guild_id), self.config
|
||||
)
|
||||
|
||||
return {"message": "Successfully updated all discord messages."}
|
||||
|
||||
|
||||
+16
-4
@@ -7,7 +7,7 @@ from discord import TextInputStyle
|
||||
from nextcord.ext import commands
|
||||
|
||||
from config import DakinyConfig
|
||||
from utils import record_event_host, record_event_participation
|
||||
from utils import record_event_points
|
||||
|
||||
|
||||
class RecordCog(commands.Cog, name="Record Commands"):
|
||||
@@ -129,14 +129,26 @@ class EventView(nextcord.ui.View):
|
||||
discord_users_list: list[str] = [
|
||||
str(user.id) for user in discord_users if user.id is not self.host_id
|
||||
]
|
||||
record_event_participation(self.config, discord_users_list, True)
|
||||
record_event_points(
|
||||
self.config,
|
||||
discord_users_list,
|
||||
True,
|
||||
self.config.params.event_participate_points,
|
||||
)
|
||||
|
||||
non_discord_users = interaction.message.embeds[0].fields[1].value
|
||||
if non_discord_users:
|
||||
non_discord_users_list: list[str] = non_discord_users.split("\n")
|
||||
record_event_participation(self.config, non_discord_users_list, False)
|
||||
record_event_points(
|
||||
self.config,
|
||||
non_discord_users_list,
|
||||
False,
|
||||
self.config.params.event_participate_points,
|
||||
)
|
||||
|
||||
record_event_host(self.config, self.host_id)
|
||||
record_event_points(
|
||||
self.config, [self.host_id], True, self.config.params.event_host_points
|
||||
)
|
||||
|
||||
event_channel: nextcord.TextChannel = interaction.guild.get_channel(
|
||||
self.config.params.event_post_channel_id
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
Contained commands and views for refresh commands.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
import nextcord
|
||||
from nextcord.ext import commands
|
||||
|
||||
from utils import edit_clan_council_info, update_clan_messages
|
||||
from utils import MESSAGES_DIR, edit_clan_council_info, update_clan_messages
|
||||
|
||||
|
||||
class RefreshCog(commands.Cog, name="Refresh Commands"):
|
||||
@@ -29,15 +31,33 @@ class RefreshCog(commands.Cog, name="Refresh Commands"):
|
||||
"""
|
||||
|
||||
@refresh.subcommand(description="Force refresh all of the bot managed messages.")
|
||||
async def messages(self, interaction: nextcord.Interaction):
|
||||
async def messages(
|
||||
self,
|
||||
interaction: nextcord.Interaction,
|
||||
channel: nextcord.TextChannel = nextcord.SlashOption(
|
||||
description="Channel to refresh messages in.",
|
||||
required=False,
|
||||
),
|
||||
):
|
||||
"""
|
||||
Calls update_clan_messages() to update all messages.
|
||||
|
||||
Args:
|
||||
interaction (nextcord.Interaction): Interaction with the command.
|
||||
channel (nextcord.TextChannel): channel to refresh messages in.
|
||||
"""
|
||||
await interaction.response.defer(ephemeral=True)
|
||||
await update_clan_messages(interaction.guild)
|
||||
|
||||
if channel is None:
|
||||
channel_updates = [
|
||||
dir_name
|
||||
for dir_name in os.listdir(MESSAGES_DIR)
|
||||
if os.path.isdir(f"{MESSAGES_DIR}/{dir_name}")
|
||||
]
|
||||
else:
|
||||
channel_updates = [channel.name]
|
||||
|
||||
await update_clan_messages(interaction.guild, channel_updates)
|
||||
await interaction.followup.send("Messages have been refreshed.", ephemeral=True)
|
||||
|
||||
@refresh.subcommand(description="Updates the current council info message.")
|
||||
|
||||
+11
-1
@@ -14,6 +14,7 @@ from database_setup.sql_data_classes import (
|
||||
Votes,
|
||||
engine,
|
||||
)
|
||||
from utils import VoteType
|
||||
|
||||
|
||||
class ViewCog(commands.Cog, name="View Commands"):
|
||||
@@ -148,6 +149,11 @@ class ViewCog(commands.Cog, name="View Commands"):
|
||||
election_name: str = nextcord.SlashOption(
|
||||
required=True, description="The election you want to view votes for."
|
||||
),
|
||||
election_type: str = nextcord.SlashOption(
|
||||
required=True,
|
||||
description="The election type. Council, Discord Manager, or Clan Leader.",
|
||||
choices=VoteType,
|
||||
),
|
||||
):
|
||||
"""
|
||||
Takes in an election name and sends a response message to the user with a list of people who voted in that
|
||||
@@ -159,7 +165,11 @@ class ViewCog(commands.Cog, name="View Commands"):
|
||||
election_name (str): Is SlashOption. The name of the election to view votes for.
|
||||
"""
|
||||
|
||||
stmt = select(Votes).where(Votes.ElectionCycle == election_name)
|
||||
stmt = (
|
||||
select(Votes)
|
||||
.where(Votes.ElectionCycle == election_name)
|
||||
.where(Votes.Type == election_type)
|
||||
)
|
||||
|
||||
with Session(engine) as session:
|
||||
# Check if election exists.
|
||||
|
||||
@@ -21,6 +21,7 @@ class Params:
|
||||
event_host_points: int
|
||||
event_host_role_id: int
|
||||
event_participate_points: int
|
||||
event_points_weekly_limit: int
|
||||
event_post_channel_id: int
|
||||
general_channel_id: int
|
||||
git_branch: str
|
||||
|
||||
@@ -25,8 +25,9 @@ CREATE TABLE Votes(
|
||||
ElectionCycle varchar(30) not null,
|
||||
VoterId bigint unsigned not null,
|
||||
VotedForId bigint unsigned,
|
||||
Type varchar(14) not null DEFAULT 'council' check("Type" in ('council', 'discordmanager', 'clanleader')),
|
||||
FOREIGN KEY (ElectionCycle) REFERENCES ElectionInformation(ElectionCycle),
|
||||
PRIMARY KEY (ElectionCycle, VoterId)
|
||||
PRIMARY KEY (ElectionCycle, VoterId, Type)
|
||||
);
|
||||
|
||||
CREATE TABLE UserBirthday (
|
||||
@@ -51,4 +52,11 @@ CREATE TABLE ParticipationPoints (
|
||||
CREATE TABLE Messages (
|
||||
Path varchar(100) PRIMARY KEY,
|
||||
Id bigint unsigned not null
|
||||
);
|
||||
|
||||
CREATE TABLE EventPointsLimit (
|
||||
UserId varchar(32) not null,
|
||||
Points int unsigned DEFAULT 0,
|
||||
Week varchar(7) not null,
|
||||
PRIMARY KEY (UserId, Week)
|
||||
);
|
||||
@@ -14,6 +14,7 @@ Base.prepare(autoload_with=engine)
|
||||
# define the classes
|
||||
CouncilMembers = Base.classes.CouncilMembers
|
||||
ElectionInformation = Base.classes.ElectionInformation
|
||||
EventPointsLimit = Base.classes.EventPointsLimit
|
||||
Messages = Base.classes.Messages
|
||||
ParticipationPoints = Base.classes.ParticipationPoints
|
||||
Presences = Base.classes.Presences
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
[tool.poetry]
|
||||
name = "dakiny-bot"
|
||||
version = "0.1.0"
|
||||
description = "Dakiny's Discord Bot."
|
||||
authors = ["imcompiling <[email protected]>"]
|
||||
readme = "README.md"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.11"
|
||||
gitpython = "3.1.43"
|
||||
fastapi = "0.111.0"
|
||||
hydra-core = "1.3.2"
|
||||
nextcord = "2.4.2"
|
||||
pandas = "1.5.2"
|
||||
python-dotenv = "1.0.0"
|
||||
pytz = "2022.7.1"
|
||||
sqlalchemy = "1.4.46"
|
||||
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
@@ -1,8 +0,0 @@
|
||||
nextcord==2.4.2
|
||||
python-dotenv==1.0.0
|
||||
pytz==2022.7.1
|
||||
hydra-core==1.3.2
|
||||
sqlalchemy<2.0
|
||||
pandas==1.5.2
|
||||
fastapi==0.111.0
|
||||
GitPython==3.1.43
|
||||
@@ -4,6 +4,8 @@ File contains util functions.
|
||||
|
||||
import os
|
||||
from datetime import datetime
|
||||
from enum import StrEnum
|
||||
from typing import Sequence
|
||||
|
||||
import nextcord
|
||||
from sqlalchemy import select, true, update
|
||||
@@ -12,6 +14,7 @@ from sqlalchemy.orm import Session
|
||||
from config import DakinyConfig
|
||||
from database_setup.sql_data_classes import (
|
||||
CouncilMembers,
|
||||
EventPointsLimit,
|
||||
Messages,
|
||||
ParticipationPoints,
|
||||
Votes,
|
||||
@@ -21,6 +24,9 @@ from database_setup.sql_data_classes import (
|
||||
NOMINEE_SPLITTER = "========================================\n\n"
|
||||
REPO_DIR = "./dakiny-messages"
|
||||
MESSAGES_DIR = f"{REPO_DIR}/messages"
|
||||
TEMPLATES_DIR = f"{REPO_DIR}/message_templates"
|
||||
|
||||
VoteType = StrEnum("VoteType", ["COUNCIL", "DISCORDMANAGER", "CLANLEADER"])
|
||||
|
||||
|
||||
def get_datetime(day: str) -> datetime:
|
||||
@@ -47,7 +53,7 @@ def get_datetime(day: str) -> datetime:
|
||||
return datetime(year, month, day)
|
||||
|
||||
|
||||
def get_election_cycle_name(config: DakinyConfig) -> str:
|
||||
def get_election_name(config: DakinyConfig) -> str:
|
||||
"""
|
||||
Builds the unique name of the current election cycle
|
||||
|
||||
@@ -58,10 +64,12 @@ def get_election_cycle_name(config: DakinyConfig) -> str:
|
||||
str: the name of the election cycle
|
||||
"""
|
||||
|
||||
return f"vote-{get_cycle_name(config)}"
|
||||
cycle = get_current_election_cycle(config)
|
||||
current_cycle = config.election_cycles[cycle]
|
||||
return f"vote-{get_datetime(current_cycle.term_start).year}-{current_cycle.name}"
|
||||
|
||||
|
||||
def get_cycle_name(config: DakinyConfig) -> str:
|
||||
def get_quarter_name(config: DakinyConfig) -> str:
|
||||
"""
|
||||
Builds the name of the current cycle/quarter
|
||||
|
||||
@@ -72,40 +80,12 @@ def get_cycle_name(config: DakinyConfig) -> str:
|
||||
str: the name of the cycle.
|
||||
"""
|
||||
|
||||
cycle = get_current_cycle(config)
|
||||
cycle = get_quarter_cycle(config)
|
||||
current_cycle = config.election_cycles[cycle]
|
||||
|
||||
return f"{get_datetime(current_cycle.term_start).year}-{current_cycle.name}"
|
||||
|
||||
|
||||
def get_dm_election_name(config: DakinyConfig) -> str:
|
||||
"""
|
||||
creates the name of the discord manager election
|
||||
|
||||
Args:
|
||||
config (DakinyConfig): Config sent to get_election_cycle_name
|
||||
|
||||
Returns:
|
||||
str: the name of the election
|
||||
"""
|
||||
|
||||
return f"{get_election_cycle_name(config)}-dm"
|
||||
|
||||
|
||||
def get_cl_election_name(config: DakinyConfig) -> str:
|
||||
"""
|
||||
creates the name of the clan leader election
|
||||
|
||||
Args:
|
||||
config (DakinyConfig): Config sent to get_election_cycle_name
|
||||
|
||||
Returns:
|
||||
str: the name of the election
|
||||
"""
|
||||
|
||||
return f"{get_election_cycle_name(config)}-cl"
|
||||
|
||||
|
||||
def get_current_election_cycle(config: DakinyConfig) -> int:
|
||||
"""
|
||||
Returns index for active election. An election is considered active between the election start and the term start.
|
||||
@@ -126,10 +106,11 @@ def get_current_election_cycle(config: DakinyConfig) -> int:
|
||||
):
|
||||
return cycle
|
||||
|
||||
return None
|
||||
# It isn't a current election, get the quarter
|
||||
return get_quarter_cycle(config)
|
||||
|
||||
|
||||
def get_current_cycle(config: DakinyConfig) -> int:
|
||||
def get_quarter_cycle(config: DakinyConfig) -> int:
|
||||
"""
|
||||
Returns the index for the current cycle/quarter. Defined as between term start and end. Shouldn't return none
|
||||
|
||||
@@ -165,9 +146,11 @@ def build_votes_str(config: DakinyConfig, guild: nextcord.Guild) -> str:
|
||||
|
||||
votes: dict[int, int] = {}
|
||||
|
||||
election_name = get_election_name(config)
|
||||
|
||||
with Session(engine) as session:
|
||||
stmt = select(CouncilMembers).where(
|
||||
CouncilMembers.ElectionCycle == get_election_cycle_name(config)
|
||||
CouncilMembers.ElectionCycle == election_name
|
||||
)
|
||||
|
||||
for nominee in sorted(
|
||||
@@ -176,8 +159,10 @@ def build_votes_str(config: DakinyConfig, guild: nextcord.Guild) -> str:
|
||||
):
|
||||
votes[nominee.MemberId] = 0
|
||||
|
||||
stmt = select(Votes).where(
|
||||
Votes.ElectionCycle == get_election_cycle_name(config)
|
||||
stmt = (
|
||||
select(Votes)
|
||||
.where(Votes.ElectionCycle == election_name)
|
||||
.where(Votes.Type == VoteType.COUNCIL)
|
||||
)
|
||||
|
||||
for vote in session.scalars(stmt):
|
||||
@@ -205,10 +190,12 @@ def build_highcouncil_votes_str(config: DakinyConfig, guild: nextcord.Guild) ->
|
||||
cl_votes: dict[int, int] = {}
|
||||
dm_votes: dict[int, int] = {}
|
||||
|
||||
election_name = get_election_name(config)
|
||||
|
||||
with Session(engine) as session:
|
||||
stmt = (
|
||||
select(CouncilMembers)
|
||||
.where(CouncilMembers.ElectionCycle == get_election_cycle_name(config))
|
||||
.where(CouncilMembers.ElectionCycle == election_name)
|
||||
.filter(CouncilMembers.ClanLeaderSpeechId.is_not(None))
|
||||
)
|
||||
|
||||
@@ -218,7 +205,11 @@ def build_highcouncil_votes_str(config: DakinyConfig, guild: nextcord.Guild) ->
|
||||
):
|
||||
cl_votes[nominee.MemberId] = 0
|
||||
|
||||
stmt = select(Votes).where(Votes.ElectionCycle == get_cl_election_name(config))
|
||||
stmt = (
|
||||
select(Votes)
|
||||
.where(Votes.ElectionCycle == election_name)
|
||||
.where(Votes.Type == VoteType.CLANLEADER)
|
||||
)
|
||||
|
||||
for vote in session.scalars(stmt):
|
||||
if vote.VotedForId is not None:
|
||||
@@ -226,7 +217,7 @@ def build_highcouncil_votes_str(config: DakinyConfig, guild: nextcord.Guild) ->
|
||||
|
||||
stmt = (
|
||||
select(CouncilMembers)
|
||||
.where(CouncilMembers.ElectionCycle == get_election_cycle_name(config))
|
||||
.where(CouncilMembers.ElectionCycle == election_name)
|
||||
.filter(CouncilMembers.DiscordManagerSpeechId.is_not(None))
|
||||
)
|
||||
|
||||
@@ -236,7 +227,11 @@ def build_highcouncil_votes_str(config: DakinyConfig, guild: nextcord.Guild) ->
|
||||
):
|
||||
dm_votes[nominee.MemberId] = 0
|
||||
|
||||
stmt = select(Votes).where(Votes.ElectionCycle == get_dm_election_name(config))
|
||||
stmt = (
|
||||
select(Votes)
|
||||
.where(Votes.ElectionCycle == election_name)
|
||||
.where(Votes.Type == VoteType.DISCORDMANAGER)
|
||||
)
|
||||
|
||||
for vote in session.scalars(stmt):
|
||||
if vote.VotedForId is not None:
|
||||
@@ -323,76 +318,98 @@ def decorate_dm_nomination(user_id: int, speech: str) -> str:
|
||||
return msg
|
||||
|
||||
|
||||
def record_event_participation(config: DakinyConfig, users: list[str], is_member: bool):
|
||||
def record_event_points(
|
||||
config: DakinyConfig, users: Sequence[str], is_member: bool, add_points: int
|
||||
):
|
||||
"""
|
||||
Adds participation points to all users listed. Checks if they don't exist in order to make a new entry, otherwise
|
||||
adds points to the existing value.
|
||||
Adds event points to all users listed. Checks if they don't exist in order to make a new entry, otherwise
|
||||
adds points to the existing value. Can be used for hosting or participating.
|
||||
|
||||
Args:
|
||||
config (DakinyConfig): Dakiny config for getting points
|
||||
users (list[int]): users that participated and need points.
|
||||
users (Sequence[int]): users that participated and need points.
|
||||
is_member (bool): whether the users are a discord member or not
|
||||
points (int): number of points to add
|
||||
"""
|
||||
|
||||
cycle = get_cycle_name(config)
|
||||
participation_point_value = config.params.event_participate_points
|
||||
quarter_name = get_quarter_name(config)
|
||||
with Session(engine) as session:
|
||||
for user in users:
|
||||
stmt = (
|
||||
select(ParticipationPoints)
|
||||
.where(ParticipationPoints.UserId == user)
|
||||
.where(ParticipationPoints.Cycle == cycle)
|
||||
.where(ParticipationPoints.Cycle == quarter_name)
|
||||
)
|
||||
|
||||
points = session.scalar(stmt)
|
||||
if points is None:
|
||||
add_points = get_allowed_points(
|
||||
session, config.params.event_points_weekly_limit, user, add_points
|
||||
)
|
||||
new_points = ParticipationPoints(
|
||||
Cycle=cycle,
|
||||
Cycle=quarter_name,
|
||||
UserId=user,
|
||||
DiscordMember=is_member,
|
||||
Points=participation_point_value,
|
||||
Points=add_points,
|
||||
)
|
||||
session.add(new_points)
|
||||
else:
|
||||
points.Points += participation_point_value
|
||||
add_points = get_allowed_points(
|
||||
session, config.params.event_points_weekly_limit, user, add_points
|
||||
)
|
||||
points.Points += add_points
|
||||
|
||||
session.commit()
|
||||
|
||||
|
||||
def record_event_host(config: DakinyConfig, user: int):
|
||||
def get_allowed_points(
|
||||
session: Session, points_limit: int, user: str, points: int
|
||||
) -> int:
|
||||
"""
|
||||
Adds host points to the given user. Checks if they don't exist in order to make a new entry, otherwise adds points
|
||||
to the existing value.
|
||||
Gets the maximum number of points a user can be given. Searches how many points they have been given
|
||||
this week and subtracts that from the weekly limit. If under, returns up to "points" number of points (will not
|
||||
go over points_limit) otherwise returns 0. Updates the limit object or creates a new one if one isn't found.
|
||||
|
||||
Args:
|
||||
config (DakinyConfig): Dakiny config for getting points
|
||||
user (int): Id of user that hosted.
|
||||
session (Session): Open connection to database.
|
||||
points_limit (int): Maximum number of points a user can be given in a week.
|
||||
user (str): user to be given points. Used to check a user's limit.
|
||||
points (int): maximum number of points to give to user.
|
||||
|
||||
Returns:
|
||||
int: how many points are allowed to be given to the user.
|
||||
"""
|
||||
cycle = get_cycle_name(config)
|
||||
host_point_value = config.params.event_host_points
|
||||
with Session(engine) as session:
|
||||
stmt = (
|
||||
select(ParticipationPoints)
|
||||
.where(ParticipationPoints.UserId == str(user))
|
||||
.where(ParticipationPoints.Cycle == cycle)
|
||||
# check for limit
|
||||
this_week = (
|
||||
f"{datetime.today().isocalendar().year}-{datetime.today().isocalendar().week}"
|
||||
)
|
||||
stmt = (
|
||||
select(EventPointsLimit)
|
||||
.where(EventPointsLimit.UserId == user)
|
||||
.where(EventPointsLimit.Week == this_week)
|
||||
)
|
||||
|
||||
# if under limit, can add up to however many points would make the current limit equal to the
|
||||
# allowed limit.
|
||||
limit = session.scalar(stmt)
|
||||
if limit is None:
|
||||
new_limit = EventPointsLimit(UserId=user, Points=points, Week=this_week)
|
||||
add_points = points
|
||||
session.add(new_limit)
|
||||
else:
|
||||
add_points = (
|
||||
points
|
||||
if limit.Points + points <= points_limit
|
||||
else (points_limit - limit.Points if points_limit - limit.Points > 0 else 0)
|
||||
)
|
||||
limit.Points += add_points
|
||||
|
||||
points = session.scalar(stmt)
|
||||
if points is None:
|
||||
new_points = ParticipationPoints(
|
||||
Cycle=cycle,
|
||||
UserId=str(user),
|
||||
DiscordMember=True,
|
||||
Points=host_point_value,
|
||||
)
|
||||
session.add(new_points)
|
||||
else:
|
||||
points.Points += host_point_value
|
||||
session.commit()
|
||||
|
||||
session.commit()
|
||||
return add_points
|
||||
|
||||
|
||||
async def update_clan_messages(guild: nextcord.Guild):
|
||||
async def update_clan_messages(guild: nextcord.Guild, channel_updates: Sequence[str]):
|
||||
"""
|
||||
Updates all clan messages to contents in MESSAGES_DIR.
|
||||
Does not to any git work. Is expected to be called after git is pulled.
|
||||
@@ -404,11 +421,12 @@ async def update_clan_messages(guild: nextcord.Guild):
|
||||
|
||||
Args:
|
||||
guild (nextcord.Guild): Guild object that all channels and messages belong to.
|
||||
channel_updates: (Sequence[str]): a sequence of the channels to update.
|
||||
"""
|
||||
|
||||
dirs = [
|
||||
dir_name
|
||||
for dir_name in os.listdir(MESSAGES_DIR)
|
||||
for dir_name in channel_updates
|
||||
if os.path.isdir(f"{MESSAGES_DIR}/{dir_name}")
|
||||
]
|
||||
|
||||
@@ -541,7 +559,7 @@ async def edit_clan_council_info(guild: nextcord.Guild, config: DakinyConfig):
|
||||
config.params.clan_info_channel_id
|
||||
)
|
||||
|
||||
election = get_election_cycle_name(config)
|
||||
election = get_election_name(config)
|
||||
with Session(engine) as session:
|
||||
stmt = (
|
||||
select(CouncilMembers.MemberId)
|
||||
@@ -567,10 +585,10 @@ async def edit_clan_council_info(guild: nextcord.Guild, config: DakinyConfig):
|
||||
if id not in (discord_manager, clan_leader)
|
||||
]
|
||||
|
||||
quarter = get_cycle_name(config)
|
||||
quarter = get_quarter_name(config)
|
||||
|
||||
with open(
|
||||
"bot_messages/council_members.txt", mode="r", encoding="utf8"
|
||||
f"{TEMPLATES_DIR}/council_members.txt", mode="r", encoding="utf8"
|
||||
) as file:
|
||||
message = file.read()
|
||||
message = message.format(
|
||||
|
||||
Reference in New Issue
Block a user