Compare commits

...
Author SHA1 Message Date
imcompiling cbfd6e20bc Move limit object management into separate function. Need to also get allowed points when creating a new quarter's event point tracker.
build_develop / build (push) Successful in 1m27s
2024-05-19 22:27:47 -04:00
imcompiling 34da664af0 Add a limit to the amount of event points one can earn weekly. 2024-05-19 22:09:10 -04:00
imcompiling c6c44afb76 Add table for limiting event points. 2024-05-19 12:05:43 -04:00
imcompiling c2d8239911 Revert "Rename ParticipationPoints table to EventPoints"
This reverts commit bec6494697.
2024-05-19 11:59:56 -04:00
imcompiling bec6494697 Rename ParticipationPoints table to EventPoints
build_develop / build (push) Successful in 1m21s
2024-05-19 11:52:25 -04:00
imcompiling 29a6b000ac Merge branch '49-view-votes-cant-see-HC-elections'
build_latest / build (push) Successful in 17s
build_release / build (push) Successful in 17s
2024-05-19 11:29:41 -04:00
imcompiling dbd2863aab Pass VoteType str instead of manual one in VoteSelect.
build_develop / build (push) Successful in 1m24s
2024-05-19 11:29:04 -04:00
imcompiling 426f009ca7 Use VoteType in utils. Remove old functions of getting clan leader and discord manager election names. 2024-05-19 10:50:23 -04:00
imcompiling ed07b2e231 Use VoteType in view command. 2024-05-19 10:46:05 -04:00
imcompiling 0caa41cc90 use VoteType in voting views. 2024-05-19 10:40:41 -04:00
imcompiling e93f37dcf0 Use VoteType in voting highcouncil views. 2024-05-19 10:38:10 -04:00
imcompiling 44c3961c46 Use VoteType in election_cycle 2024-05-19 10:31:16 -04:00
imcompiling 0dcdd038b8 Make the VoteType StrEnum and edit column in database to enforce only 3 values.
build_develop / build (push) Successful in 1m21s
2024-05-18 23:08:01 -04:00
imcompiling eb5d465ac9 Merge branch 'main' into 49--view-votes-can't-see-HC-elections
build_develop / build (push) Failing after 16s
2024-05-18 22:41:11 -04:00
imcompiling eb1c87fbf4 install sqlite3 onto container. Using it a lot for debugging/fixes.
build_latest / build (push) Successful in 1m29s
build_release / build (push) Successful in 22s
2024-05-18 22:38:57 -04:00
imcompiling cbd73a104e Merge branch '61-Create-virtual-environment-and-update-python-version' 2024-05-18 22:34:35 -04:00
imcompiling 95ad47368c Remove requirements.txt
build_develop / build (push) Successful in 22s
2024-05-18 22:34:13 -04:00
imcompiling dea3f7cabf Update dockerfile to use poetry for dependencies.
build_develop / build (push) Successful in 1m35s
Decided to just use poetry to just export requirements instead of building package.
2024-05-18 22:29:18 -04:00
imcompiling 6a71e9ab1d Add poetry for dependency management.
Update python version.
Using virtual environment in vscode.
2024-05-18 21:56:11 -04:00
imcompiling bc56830396 Merge branch '49--view-votes-can't-see-HC-elections' of https://gitea.cheezers.org/imcompiling/dakiny-bot into 49--view-votes-can't-see-HC-elections
build_develop / build (push) Failing after 17s
2024-05-18 16:36:03 -04:00
imcompiling 1d14f8171d Add vote type to one statement. 2024-05-18 16:34:33 -04:00
imcompiling 2efd380a7e Add VoteType Enum. 2024-05-18 16:26:11 -04:00
imcompiling 475170025a Edit database to add a type field to Votes. 2024-05-18 16:25:19 -04:00
imcompiling f4f3bfe103 Edit database to add a type field to Votes.
build_develop / build (push) Failing after 18s
2024-05-18 15:39:13 -04:00
imcompiling 6827658b4a Change sqlalchemy req.
build_latest / build (push) Successful in 1m2s
build_develop / build (push) Successful in 25s
2024-05-17 23:41:57 -04:00
imcompiling 85a3ea0418 Merge branch '54-Only-edit-changed-channels'
build_latest / build (push) Successful in 17s
build_release / build (push) Successful in 16s
2024-05-16 22:44:08 -04:00
imcompiling 0b27c60bea Clarify some method names.
build_develop / build (push) Successful in 57s
current election cycle shoud return quarter cycle if it can't find a cycle.
2024-05-16 22:41:27 -04:00
imcompiling 1a6f373e03 Merge branch 'main' into 54-Only-edit-changed-channels
build_develop / build (push) Successful in 57s
2024-05-16 22:02:56 -04:00
imcompiling 00c8227b8e Update refresh messages command to be able to choose a channel.
build_latest / build (push) Failing after 17s
build_develop / build (push) Failing after 17s
2024-05-16 21:45:36 -04:00
imcompiling 04c44bc470 only trigger updating clan messages in updated directories.
Update clan info message if the template was updated.
2024-05-16 21:37:10 -04:00
imcompiling 3761d3bb77 Add BaseModel class for the gitea webhook packet. 2024-05-16 18:01:52 -04:00
18 changed files with 481 additions and 176 deletions
+1
View File
@@ -105,6 +105,7 @@ venv/
ENV/
env.bak/
venv.bak/
poetry.lock
# Spyder project settings
.spyderproject
+4 -7
View File
@@ -1,22 +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 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"]
+27 -22
View File
@@ -22,13 +22,12 @@ from database_setup.sql_data_classes import (
)
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,
)
@@ -199,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)
@@ -247,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
@@ -277,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
@@ -295,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():
@@ -333,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
@@ -360,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())
)
@@ -383,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 = (
@@ -422,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)
@@ -451,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)
+195
View File
@@ -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
+5 -5
View File
@@ -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:
+4 -4
View File
@@ -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:
+32 -34
View File
@@ -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
View File
@@ -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(
+26 -3
View File
@@ -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
View File
@@ -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
+23 -3
View File
@@ -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
View File
@@ -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.
+1
View File
@@ -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
+9 -1
View File
@@ -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)
);
+1
View File
@@ -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
+22
View File
@@ -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"
-8
View File
@@ -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
+93 -78
View File
@@ -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,
@@ -23,6 +26,8 @@ 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:
"""
@@ -48,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
@@ -64,7 +69,7 @@ def get_election_cycle_name(config: DakinyConfig) -> str:
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
@@ -75,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.
@@ -129,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
@@ -168,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(
@@ -179,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):
@@ -208,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))
)
@@ -221,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:
@@ -229,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))
)
@@ -239,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:
@@ -326,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.
@@ -407,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}")
]
@@ -544,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)
@@ -570,7 +585,7 @@ 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(
f"{TEMPLATES_DIR}/council_members.txt", mode="r", encoding="utf8"