From 4fc569f4a35d1b8e27366c1340fe4ead3ca7142c Mon Sep 17 00:00:00 2001 From: imcompiling Date: Wed, 22 May 2024 12:15:06 -0400 Subject: [PATCH 01/17] Add ruff configuration. --- pyproject.toml | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index a31b9fb..9d89a20 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,3 +20,82 @@ sqlalchemy = "1.4.46" [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" + +[tool.ruff] +# Exclude a variety of commonly ignored directories. +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".ipynb_checkpoints", + ".mypy_cache", + ".nox", + ".pants.d", + ".pyenv", + ".pytest_cache", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + ".vscode", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "site-packages", + "venv", +] + +# Same as Black. +line-length = 120 +indent-width = 4 + +# Assume Python 3.8 +target-version = "py38" + +[tool.ruff.lint] +# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. +# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or +# McCabe complexity (`C901`) by default. +select = ["E4", "E7", "E9", "F", "D100", "D1"] +ignore = ["D107"] + +# Allow fix for all enabled rules (when `--fix`) is provided. +fixable = ["ALL"] +unfixable = [] + +# Allow unused variables when underscore-prefixed. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +[tool.ruff.format] +# Like Black, use double quotes for strings. +quote-style = "double" + +# Like Black, indent with spaces, rather than tabs. +indent-style = "space" + +# Like Black, respect magic trailing commas. +skip-magic-trailing-comma = false + +# Like Black, automatically detect the appropriate line ending. +line-ending = "auto" + +# Enable auto-formatting of code examples in docstrings. Markdown, +# reStructuredText code/literal blocks and doctests are all supported. +# +# This is currently disabled by default, but it is planned for this +# to be opt-out in the future. +docstring-code-format = true + +# Set the line length limit used when formatting code snippets in +# docstrings. +# +# This only has an effect when the `docstring-code-format` setting is +# enabled. +docstring-code-line-length = "dynamic" From 3ac164ab65d5c6cce4c0ce22fc86be805cf702c3 Mon Sep 17 00:00:00 2001 From: imcompiling Date: Wed, 22 May 2024 13:28:10 -0400 Subject: [PATCH 02/17] Add .vscode dir --- .gitignore | 3 --- .vscode/extensions.json | 9 +++++++++ .vscode/launch.json | 15 +++++++++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 .vscode/extensions.json create mode 100644 .vscode/launch.json diff --git a/.gitignore b/.gitignore index 950b5c7..e3bfada 100644 --- a/.gitignore +++ b/.gitignore @@ -125,9 +125,6 @@ dmypy.json # Pyre type checker .pyre/ -# IDE -.vscode/ - # logging nextcord.log diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..52017d3 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "ms-python.vscode-pylance", + "ms-python.python", + "ms-python.debugpy", + "charliermarsh.ruff", + "alexcvzz.vscode-sqlite" + ] +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..bee0e61 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Python: Module", + "type": "debugpy", + "request": "launch", + "module": "main", + "justMyCode": true + } + ] +} \ No newline at end of file From 796b052bfc0b5a59b4a224a7af84ca722482b685 Mon Sep 17 00:00:00 2001 From: imcompiling Date: Wed, 22 May 2024 15:06:39 -0400 Subject: [PATCH 03/17] Turned on all ruff rules. Formatted all files. --- classes/election_cycle.py | 118 +++++++------------------- classes/nominate_highcouncil_views.py | 50 +++-------- classes/nominate_views.py | 21 ++--- classes/voting_highcouncil_views.py | 32 ++----- classes/voting_views.py | 21 ++--- cogs/clan_messages/__init__.py | 26 ++---- cogs/commands/record.py | 49 +++-------- cogs/commands/refresh.py | 12 +-- cogs/commands/remove.py | 20 ++--- cogs/commands/request.py | 41 +++------ cogs/commands/set.py | 32 ++----- cogs/commands/view.py | 63 +++----------- cogs/loops/bot_presence.py | 12 +-- cogs/loops/daily_loop.py | 8 +- cogs/message_listener/__init__.py | 4 +- main.py | 4 +- pyproject.toml | 22 ++++- utils.py | 95 +++++---------------- 18 files changed, 161 insertions(+), 469 deletions(-) diff --git a/classes/election_cycle.py b/classes/election_cycle.py index 6cd6946..c10b672 100644 --- a/classes/election_cycle.py +++ b/classes/election_cycle.py @@ -55,15 +55,9 @@ class ElectionCycle: self.term_end: datetime = get_datetime(self.election_cycle.term_end) self.voting_start: datetime = get_datetime(self.election_cycle.voting_start) self.voting_end: datetime = get_datetime(self.election_cycle.voting_end) - self.highcouncil_election_announce: datetime = get_datetime( - self.election_cycle.highcouncil_election_announce - ) - self.highcouncil_election_start: datetime = get_datetime( - self.election_cycle.highcouncil_election_start - ) - self.highcouncil_election_end: datetime = get_datetime( - self.election_cycle.highcouncil_election_end - ) + self.highcouncil_election_announce: datetime = get_datetime(self.election_cycle.highcouncil_election_announce) + self.highcouncil_election_start: datetime = get_datetime(self.election_cycle.highcouncil_election_start) + self.highcouncil_election_end: datetime = get_datetime(self.election_cycle.highcouncil_election_end) self.guild: nextcord.Guild = guild self.election_over: bool = False @@ -76,15 +70,11 @@ class ElectionCycle: long. """ - with open( - f"{TEMPLATES_DIR}/election_announcement.txt", mode="r", encoding="utf8" - ) as file: + with open(f"{TEMPLATES_DIR}/election_announcement.txt", mode="r", encoding="utf8") as file: message = file.read() next_election = get_datetime( - self.config.election_cycles[ - (self.cycle + 1) % len(self.config.election_cycles) - ].election_start + self.config.election_cycles[(self.cycle + 1) % len(self.config.election_cycles)].election_start ) message = message.format( @@ -109,22 +99,14 @@ class ElectionCycle: str: Message to send. """ - with open( - f"{TEMPLATES_DIR}/highcouncil_election.txt", mode="r", encoding="utf8" - ) as file: + with open(f"{TEMPLATES_DIR}/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" - ), + 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=self.term_start.strftime("%B %d"), ) @@ -136,9 +118,7 @@ class ElectionCycle: str: Message to send. """ - with open( - f"{TEMPLATES_DIR}/highcouncil_vote.txt", mode="r", encoding="utf8" - ) as file: + with open(f"{TEMPLATES_DIR}/highcouncil_vote.txt", mode="r", encoding="utf8") as file: message = file.read() return message.format( @@ -156,9 +136,7 @@ class ElectionCycle: 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 - ) + 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, @@ -177,11 +155,7 @@ class ElectionCycle: await self.election_start_action() elif today.date() == self.voting_start.date(): await self.voting_start_action() - elif ( - (self.voting_end - timedelta(3)).date() - <= today.date() - < self.voting_end.date() - ): + elif (self.voting_end - timedelta(3)).date() <= today.date() < self.voting_end.date(): await self.election_reminder() elif today.date() == self.voting_end.date(): await self.voting_end_action() @@ -195,16 +169,12 @@ class ElectionCycle: Performs required actions for when the election cycle starts. """ # create new channel for election - parent: nextcord.TextChannel = self.guild.get_channel( - self.config.params.voting_parent_id - ) + parent: nextcord.TextChannel = self.guild.get_channel(self.config.params.voting_parent_id) quarter_name: str = get_election_name(self.config) thread_msg: nextcord.Message = await parent.send(content=quarter_name) - thread: nextcord.Thread = await parent.create_thread( - name=quarter_name, message=thread_msg - ) + thread: nextcord.Thread = await parent.create_thread(name=quarter_name, message=thread_msg) # post message announcing election # election start message is returned as an array of string because it is too long. send each string @@ -213,9 +183,7 @@ class ElectionCycle: for index, message in enumerate(messages): # if last item, add the nominate button if index + 1 == len(messages): - nominate_msg: nextcord.Message = await thread.send( - message, view=NominateView(self.config, self.guild) - ) + nominate_msg: nextcord.Message = await thread.send(message, view=NominateView(self.config, self.guild)) else: await thread.send(message) @@ -235,9 +203,7 @@ class ElectionCycle: Removes write permission from the general clan member role. """ message = self.build_vote_start() - election_thread: nextcord.Thread = self.guild.get_thread( - self.get_election_info().ThreadId - ) + election_thread: nextcord.Thread = self.guild.get_thread(self.get_election_info().ThreadId) await election_thread.send(message) vote_msg = await election_thread.send( build_votes_str(self.config, self.guild), @@ -253,9 +219,7 @@ class ElectionCycle: session.commit() thread: nextcord.Thread = self.guild.get_thread(info.ThreadId) - nominate_msg: nextcord.Message = await thread.fetch_message( - info.CouncilNominationMsgId - ) + nominate_msg: nextcord.Message = await thread.fetch_message(info.CouncilNominationMsgId) await nominate_msg.edit(view=None) async def voting_end_action(self) -> None: @@ -264,12 +228,8 @@ class ElectionCycle: """ message = self.build_highcouncil_election_announce() - council_elect_role: nextcord.Role = self.guild.get_role( - self.config.params.clan_council_elect_role_id - ) - election_thread: nextcord.Thread = self.guild.get_thread( - self.get_election_info().ThreadId - ) + council_elect_role: nextcord.Role = self.guild.get_role(self.config.params.clan_council_elect_role_id) + election_thread: nextcord.Thread = self.guild.get_thread(self.get_election_info().ThreadId) nominate_msg: nextcord.Message = await election_thread.send( message, view=NominateHighCouncilView(self.config, self.guild) @@ -279,13 +239,9 @@ class ElectionCycle: with Session(engine) as session: # remove vote button from vote message - stmt = select(ElectionInformation).where( - ElectionInformation.ElectionCycle == election_name - ) + stmt = select(ElectionInformation).where(ElectionInformation.ElectionCycle == election_name) info = session.scalar(stmt) - vote_msg: nextcord.Message = await election_thread.fetch_message( - info.CouncilVoteMsgId - ) + vote_msg: nextcord.Message = await election_thread.fetch_message(info.CouncilVoteMsgId) await vote_msg.edit(view=None) # add the highcouncil nomination message to election information @@ -324,9 +280,7 @@ class ElectionCycle: """ message = self.build_highcouncil_vote_start() - election_thread: nextcord.Thread = self.guild.get_thread( - self.get_election_info().ThreadId - ) + election_thread: nextcord.Thread = self.guild.get_thread(self.get_election_info().ThreadId) await election_thread.send(message) vote_msg = await election_thread.send( build_highcouncil_votes_str(self.config, self.guild), @@ -342,9 +296,7 @@ class ElectionCycle: session.commit() # remove highcouncil nomination view - nomination_msg: nextcord.Message = await election_thread.fetch_message( - info.HighCouncilNominationMsgId - ) + nomination_msg: nextcord.Message = await election_thread.fetch_message(info.HighCouncilNominationMsgId) await nomination_msg.edit(view=None) async def term_start_action(self) -> None: @@ -354,12 +306,8 @@ class ElectionCycle: self.election_over = True - elect_role: nextcord.Role = self.guild.get_role( - self.config.params.clan_council_elect_role_id - ) - council_role: nextcord.Role = self.guild.get_role( - self.config.params.clan_council_role_id - ) + elect_role: nextcord.Role = self.guild.get_role(self.config.params.clan_council_elect_role_id) + council_role: nextcord.Role = self.guild.get_role(self.config.params.clan_council_role_id) election_cycle = get_election_name(self.config) @@ -422,9 +370,7 @@ class ElectionCycle: session.commit() - election_thread: nextcord.Thread = self.guild.get_thread( - self.get_election_info().ThreadId - ) + election_thread: nextcord.Thread = self.guild.get_thread(self.get_election_info().ThreadId) # clear view from vote message stmt = select(ElectionInformation.HighCouncilVoteMsgId).where( @@ -434,15 +380,11 @@ class ElectionCycle: 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!\nClan Leader: <@{cl_winner}>\n" - ) + highcouncil_msg: str = 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) - await election_thread.send( - "This election cycle is now over. Thank you everyone for participating!" - ) + await election_thread.send("This election cycle is now over. Thank you everyone for participating!") await election_thread.edit(archived=True, locked=True) await edit_clan_council_info(self.guild, self.config) @@ -466,9 +408,7 @@ class ElectionCycle: Sends a message to general chat reminder clan about election. """ - general: nextcord.TextChannel = self.guild.get_channel( - self.config.params.general_channel_id - ) + general: nextcord.TextChannel = self.guild.get_channel(self.config.params.general_channel_id) message = f"Reminder to <@&{self.config.params.clan_member_role_id}>! Clan elections are happening! Head over " message += f"to <#{self.get_election_info().ThreadId}> to cast your vote!" diff --git a/classes/nominate_highcouncil_views.py b/classes/nominate_highcouncil_views.py index 9a32fa8..8d7d363 100644 --- a/classes/nominate_highcouncil_views.py +++ b/classes/nominate_highcouncil_views.py @@ -74,14 +74,8 @@ class NominateDMModal(nextcord.ui.Modal): council_elect.DiscordManagerSpeechId = msg.id session.commit() else: - msg: nextcord.Message = await interaction.channel.fetch_message( - council_elect.DiscordManagerSpeechId - ) - await msg.edit( - content=decorate_dm_nomination( - interaction.user.id, self.speech.value - ) - ) + msg: nextcord.Message = await interaction.channel.fetch_message(council_elect.DiscordManagerSpeechId) + await msg.edit(content=decorate_dm_nomination(interaction.user.id, self.speech.value)) class NominateCLModal(nextcord.ui.Modal): @@ -141,14 +135,8 @@ class NominateCLModal(nextcord.ui.Modal): council_elect.ClanLeaderSpeechId = msg.id session.commit() else: - msg: nextcord.Message = await interaction.channel.fetch_message( - council_elect.ClanLeaderSpeechId - ) - await msg.edit( - content=decorate_cl_nomination( - interaction.user.id, self.speech.value - ) - ) + msg: nextcord.Message = await interaction.channel.fetch_message(council_elect.ClanLeaderSpeechId) + await msg.edit(content=decorate_cl_nomination(interaction.user.id, self.speech.value)) class NominateHighCouncilView(nextcord.ui.View): @@ -166,9 +154,7 @@ class NominateHighCouncilView(nextcord.ui.View): style=nextcord.ButtonStyle.primary, custom_id="nominate-dm", ) - async def nominate_dm( - self, button: nextcord.ui.Button, interaction: nextcord.Interaction - ) -> None: + async def nominate_dm(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: """ Gathers previous information about the user if they have already nominated themselves, then launches the nomination modal to get their speech. @@ -179,10 +165,7 @@ class NominateHighCouncilView(nextcord.ui.View): """ # enforce council elect only - if ( - self.guild.get_role(self.config.params.clan_council_elect_role_id) - not in interaction.user.roles - ): + if self.guild.get_role(self.config.params.clan_council_elect_role_id) not in interaction.user.roles: await interaction.response.send_message( f"You must have the <@&{self.config.params.clan_council_elect_role_id}> role to vote.", ephemeral=True, @@ -203,15 +186,11 @@ class NominateHighCouncilView(nextcord.ui.View): # user needs to exist to get here anyways... if obj.DiscordManagerSpeechId is not None: usr_nominated = True - msg = await interaction.channel.fetch_message( - obj.DiscordManagerSpeechId - ) + msg = await interaction.channel.fetch_message(obj.DiscordManagerSpeechId) speech_content = msg.content.split(NOMINEE_SPLITTER)[1] await interaction.response.send_modal( - NominateDMModal( - self.config, interaction.user.id, usr_nominated, speech_content - ) + NominateDMModal(self.config, interaction.user.id, usr_nominated, speech_content) ) @nextcord.ui.button( @@ -219,9 +198,7 @@ class NominateHighCouncilView(nextcord.ui.View): style=nextcord.ButtonStyle.primary, custom_id="nominate-cl", ) - async def nominate_cl( - self, button: nextcord.ui.Button, interaction: nextcord.Interaction - ) -> None: + async def nominate_cl(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: """ Gathers previous information about the user if they have already nominated themselves, then launches the nomination modal to get their speech. @@ -232,10 +209,7 @@ class NominateHighCouncilView(nextcord.ui.View): """ # enforce council elect only - if ( - self.guild.get_role(self.config.params.clan_council_elect_role_id) - not in interaction.user.roles - ): + if self.guild.get_role(self.config.params.clan_council_elect_role_id) not in interaction.user.roles: await interaction.response.send_message( f"You must have the <@&{self.config.params.clan_council_elect_role_id}> role to vote.", ephemeral=True, @@ -260,7 +234,5 @@ class NominateHighCouncilView(nextcord.ui.View): speech_content = msg.content.split(NOMINEE_SPLITTER)[1] await interaction.response.send_modal( - NominateCLModal( - self.config, interaction.user.id, usr_nominated, speech_content - ) + NominateCLModal(self.config, interaction.user.id, usr_nominated, speech_content) ) diff --git a/classes/nominate_views.py b/classes/nominate_views.py index cc26bb6..1b253af 100644 --- a/classes/nominate_views.py +++ b/classes/nominate_views.py @@ -74,12 +74,8 @@ class NominateModal(nextcord.ui.Modal): ) obj = session.scalar(stmt) - msg: nextcord.Message = await interaction.channel.fetch_message( - obj.SpeechMsgId - ) - await msg.edit( - content=decorate_nomination(interaction.user.id, self.speech.value) - ) + msg: nextcord.Message = await interaction.channel.fetch_message(obj.SpeechMsgId) + await msg.edit(content=decorate_nomination(interaction.user.id, self.speech.value)) class NominateView(nextcord.ui.View): @@ -97,9 +93,7 @@ class NominateView(nextcord.ui.View): style=nextcord.ButtonStyle.primary, custom_id="nominate", ) - async def nominate( - self, button: nextcord.ui.Button, interaction: nextcord.Interaction - ) -> None: + async def nominate(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: """ Gathers previous information about the user if they have already nominated themselves, then launches the nomination modal to get their speech. @@ -110,10 +104,7 @@ class NominateView(nextcord.ui.View): """ # enforce members only - if ( - self.guild.get_role(self.config.params.clan_member_role_id) - not in interaction.user.roles - ): + if self.guild.get_role(self.config.params.clan_member_role_id) not in interaction.user.roles: await interaction.response.send_message( f"You must have the <@&{self.config.params.clan_member_role_id}> role to vote.", ephemeral=True, @@ -136,7 +127,5 @@ class NominateView(nextcord.ui.View): speech_content = msg.content.split(NOMINEE_SPLITTER)[1] await interaction.response.send_modal( - NominateModal( - self.config, interaction.user.id, usr_nominated, speech_content - ) + NominateModal(self.config, interaction.user.id, usr_nominated, speech_content) ) diff --git a/classes/voting_highcouncil_views.py b/classes/voting_highcouncil_views.py index f52ef5b..bf74387 100644 --- a/classes/voting_highcouncil_views.py +++ b/classes/voting_highcouncil_views.py @@ -21,9 +21,7 @@ from utils import VoteType, build_highcouncil_votes_str, get_election_name class VoteSelect(nextcord.ui.StringSelect): - def __init__( - self, options: List["SelectOption"], config: DakinyConfig, election_type: str - ) -> None: + def __init__(self, options: List["SelectOption"], config: DakinyConfig, election_type: str) -> None: super().__init__( placeholder="Choose 1", min_values=1, @@ -71,9 +69,7 @@ class VoteSelect(nextcord.ui.StringSelect): session.commit() vote_message_str = build_highcouncil_votes_str(self.config, interaction.guild) - vote_msg: nextcord.Message = await interaction.channel.fetch_message( - vote_msg_id - ) + vote_msg: nextcord.Message = await interaction.channel.fetch_message(vote_msg_id) await vote_msg.edit(content=vote_message_str) if voted_for is not None: @@ -99,9 +95,7 @@ class VoteHighcouncilView(nextcord.ui.View): style=nextcord.ButtonStyle.primary, custom_id="vote-cl", ) - async def vote_cl( - self, button: nextcord.ui.Button, interaction: nextcord.Interaction - ) -> None: + async def vote_cl(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: """ Enforces only council elect vote. Gets election cycle, creates voting options for clan leader (checking if user has voted already), @@ -113,10 +107,7 @@ class VoteHighcouncilView(nextcord.ui.View): """ # enforce council elect only - if ( - self.guild.get_role(self.config.params.clan_council_elect_role_id) - not in interaction.user.roles - ): + if self.guild.get_role(self.config.params.clan_council_elect_role_id) not in interaction.user.roles: await interaction.response.send_message( f"You must have the <@&{self.config.params.clan_council_elect_role_id}> role to vote.", ephemeral=True, @@ -162,11 +153,7 @@ class VoteHighcouncilView(nextcord.ui.View): ) view = nextcord.ui.View() - view.add_item( - VoteSelect( - options=options, config=self.config, election_type=VoteType.CLANLEADER - ) - ) + view.add_item(VoteSelect(options=options, config=self.config, election_type=VoteType.CLANLEADER)) await interaction.response.send_message( content="Please vote for a Clan Leader!", @@ -179,9 +166,7 @@ class VoteHighcouncilView(nextcord.ui.View): style=nextcord.ButtonStyle.primary, custom_id="vote-dm", ) - async def vote_dm( - self, button: nextcord.ui.Button, interaction: nextcord.Interaction - ) -> None: + async def vote_dm(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: """ Enforces only council elect vote. Gets election cycle, creates voting options for discord manager (checking if user has voted already), @@ -193,10 +178,7 @@ class VoteHighcouncilView(nextcord.ui.View): """ # enforce council elect only - if ( - self.guild.get_role(self.config.params.clan_council_elect_role_id) - not in interaction.user.roles - ): + if self.guild.get_role(self.config.params.clan_council_elect_role_id) not in interaction.user.roles: await interaction.response.send_message( f"You must have the <@&{self.config.params.clan_council_elect_role_id}> role to vote.", ephemeral=True, diff --git a/classes/voting_views.py b/classes/voting_views.py index cdfc8ca..7c9420a 100644 --- a/classes/voting_views.py +++ b/classes/voting_views.py @@ -67,9 +67,7 @@ class VoteSelect(nextcord.ui.StringSelect): session.commit() vote_message_str = build_votes_str(self.config, interaction.guild) - vote_msg: nextcord.Message = await interaction.channel.fetch_message( - vote_msg_id - ) + vote_msg: nextcord.Message = await interaction.channel.fetch_message(vote_msg_id) await vote_msg.edit(content=vote_message_str) if voted_for is not None: @@ -90,12 +88,8 @@ class VoteView(nextcord.ui.View): self.config = config self.guild = guild - @nextcord.ui.button( - label="Vote", style=nextcord.ButtonStyle.primary, custom_id="vote" - ) - async def vote( - self, button: nextcord.ui.Button, interaction: nextcord.Interaction - ) -> None: + @nextcord.ui.button(label="Vote", style=nextcord.ButtonStyle.primary, custom_id="vote") + async def vote(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: """ Enforces only members vote. Gets election cycle, creates voting options (checking if user has voted already), then sends the voting view. @@ -106,10 +100,7 @@ class VoteView(nextcord.ui.View): """ # enforce members only - if ( - self.guild.get_role(self.config.params.clan_member_role_id) - not in interaction.user.roles - ): + if self.guild.get_role(self.config.params.clan_member_role_id) not in interaction.user.roles: await interaction.response.send_message( f"You must have the <@&{self.config.params.clan_member_role_id}> role to vote.", ephemeral=True, @@ -127,9 +118,7 @@ class VoteView(nextcord.ui.View): ) voted_for = session.scalar(stmt) - stmt = select(CouncilMembers.MemberId).where( - CouncilMembers.ElectionCycle == election - ) + stmt = select(CouncilMembers.MemberId).where(CouncilMembers.ElectionCycle == election) options = [ SelectOption( diff --git a/cogs/clan_messages/__init__.py b/cogs/clan_messages/__init__.py index 3db7212..619685b 100644 --- a/cogs/clan_messages/__init__.py +++ b/cogs/clan_messages/__init__.py @@ -47,19 +47,11 @@ class ClanMessages(commands.Cog, name="Clan Messages"): # 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 + 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 - } + 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) @@ -68,9 +60,7 @@ class ClanMessages(commands.Cog, name="Clan Messages"): # 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 - ) + await edit_clan_council_info(self.bot.get_guild(self.config.params.guild_id), self.config) return {"message": "Successfully updated all discord messages."} @@ -87,13 +77,9 @@ class ClanMessages(commands.Cog, name="Clan Messages"): self.origin = self.repo.remote("origin") if self.config.params.git_branch == "test": - self.repo.create_head("test", self.origin.refs.test).set_tracking_branch( - self.origin.refs.test - ).checkout() + self.repo.create_head("test", self.origin.refs.test).set_tracking_branch(self.origin.refs.test).checkout() else: - self.repo.create_head("main", self.origin.refs.main).set_tracking_branch( - self.origin.refs.main - ).checkout() + self.repo.create_head("main", self.origin.refs.main).set_tracking_branch(self.origin.refs.main).checkout() self.origin.pull() diff --git a/cogs/commands/record.py b/cogs/commands/record.py index 2e53b23..435eab9 100644 --- a/cogs/commands/record.py +++ b/cogs/commands/record.py @@ -32,9 +32,7 @@ class RecordCog(commands.Cog, name="Record Commands"): async def event( self, interaction: nextcord.Interaction, - name: str = nextcord.SlashOption( - required=True, description="Name of the event that was hosted." - ), + name: str = nextcord.SlashOption(required=True, description="Name of the event that was hosted."), host: nextcord.Member = nextcord.SlashOption( required=False, description="An alternative host if you are recording event for someone else.", @@ -51,10 +49,7 @@ class RecordCog(commands.Cog, name="Record Commands"): """ # check role - if ( - interaction.guild.get_role(self.bot.config.params.event_host_role_id) - not in interaction.user.roles - ): + if interaction.guild.get_role(self.bot.config.params.event_host_role_id) not in interaction.user.roles: await interaction.response.send_message( f"You must have the <@&{self.bot.config.params.event_host_role_id}> role to record an event.", ephemeral=True, @@ -95,15 +90,11 @@ class EventView(nextcord.ui.View): self.add_item(self.event_participants) @nextcord.ui.button(label="Cancel", style=nextcord.ButtonStyle.red, row=1) - async def cancel( - self, button: nextcord.ui.Button, interaction: nextcord.Interaction - ): + async def cancel(self, button: nextcord.ui.Button, interaction: nextcord.Interaction): """ Cancels event submission. Removes embed and view. """ - await interaction.response.edit_message( - "Canceled", ephemeral=True, embed=None, view=None - ) + await interaction.response.edit_message("Canceled", ephemeral=True, embed=None, view=None) self.stop() @nextcord.ui.button(label="Edit", style=nextcord.ButtonStyle.blurple, row=1) @@ -111,14 +102,10 @@ class EventView(nextcord.ui.View): """ Calls modal to update the text input information of the event. """ - await interaction.response.send_modal( - ParticipantModal(interaction.message.embeds[0]) - ) + await interaction.response.send_modal(ParticipantModal(interaction.message.embeds[0])) @nextcord.ui.button(label="Confirm", style=nextcord.ButtonStyle.green, row=1) - async def confirm( - self, button: nextcord.ui.Button, interaction: nextcord.Interaction - ): + async def confirm(self, button: nextcord.ui.Button, interaction: nextcord.Interaction): """ Submits the event embed to the event channel. Removes view and embed from recording message. Sends point information to database. @@ -126,9 +113,7 @@ class EventView(nextcord.ui.View): discord_users = self.event_participants.values.members if discord_users: - discord_users_list: list[str] = [ - str(user.id) for user in discord_users if user.id is not self.host_id - ] + discord_users_list: list[str] = [str(user.id) for user in discord_users if user.id is not self.host_id] record_event_points( self.config, discord_users_list, @@ -146,17 +131,11 @@ class EventView(nextcord.ui.View): self.config.params.event_participate_points, ) - record_event_points( - self.config, [self.host_id], True, self.config.params.event_host_points - ) + 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 - ) + event_channel: nextcord.TextChannel = interaction.guild.get_channel(self.config.params.event_post_channel_id) await event_channel.send(embed=interaction.message.embeds[0]) - await interaction.response.edit_message( - content="Event has been submitted.", view=None, embed=None - ) + await interaction.response.edit_message(content="Event has been submitted.", view=None, embed=None) self.stop() @@ -201,9 +180,7 @@ class ParticipantModal(nextcord.ui.Modal): async def callback(self, interaction: nextcord.Interaction): self.embed.title = self.name.value self.embed.description = self.description.value - self.embed.set_field_at( - index=1, name="Non-Discord Participants", value=self.participants.value - ) + self.embed.set_field_at(index=1, name="Non-Discord Participants", value=self.participants.value) await interaction.response.edit_message(embed=self.embed) @@ -223,8 +200,6 @@ class ParticipantSelect(nextcord.ui.UserSelect): async def callback(self, interaction: nextcord.Interaction): embed: nextcord.Embed = interaction.message.embeds[0] - participants: str = "\n".join( - [f"<@{participant.id}>" for participant in self.values.members] - ) + participants: str = "\n".join([f"<@{participant.id}>" for participant in self.values.members]) embed.set_field_at(0, name="Participants", value=participants) await interaction.response.edit_message(embed=embed) diff --git a/cogs/commands/refresh.py b/cogs/commands/refresh.py index ba92dd0..1bc30bc 100644 --- a/cogs/commands/refresh.py +++ b/cogs/commands/refresh.py @@ -22,9 +22,7 @@ class RefreshCog(commands.Cog, name="Refresh Commands"): super().__init__() self.bot: commands.Bot = bot - @nextcord.slash_command( - default_member_permissions=nextcord.Permissions(administrator=True) - ) + @nextcord.slash_command(default_member_permissions=nextcord.Permissions(administrator=True)) async def refresh(self, interaction: nextcord.Interaction): """ Parent slash command for refresh. Never called. @@ -50,9 +48,7 @@ class RefreshCog(commands.Cog, name="Refresh Commands"): 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}") + dir_name for dir_name in os.listdir(MESSAGES_DIR) if os.path.isdir(f"{MESSAGES_DIR}/{dir_name}") ] else: channel_updates = [channel.name] @@ -69,6 +65,4 @@ class RefreshCog(commands.Cog, name="Refresh Commands"): interaction (nextcord.Interaction): Interaction with the command. """ await edit_clan_council_info(interaction.guild, self.bot.config) - await interaction.response.send_message( - "Clan Council Info message updated.", ephemeral=True - ) + await interaction.response.send_message("Clan Council Info message updated.", ephemeral=True) diff --git a/cogs/commands/remove.py b/cogs/commands/remove.py index 1f86346..43715ba 100644 --- a/cogs/commands/remove.py +++ b/cogs/commands/remove.py @@ -32,9 +32,7 @@ class RemoveCog(commands.Cog, name="Remove Commands"): async def presence( self, interaction: nextcord.Interaction, - presence_str: str = nextcord.SlashOption( - description="The presence to remove", required=True - ), + presence_str: str = nextcord.SlashOption(description="The presence to remove", required=True), ): """ Deletes the given presence id from the database. @@ -53,9 +51,7 @@ class RemoveCog(commands.Cog, name="Remove Commands"): await interaction.response.send_message("Presence deleted.", ephemeral=True) @presence.on_autocomplete("presence_str") - async def presence_autocomplete( - self, interaction: nextcord.Interaction, presence_str: str - ): + async def presence_autocomplete(self, interaction: nextcord.Interaction, presence_str: str): """ Collects all presences that start with the presence string. Sends autocomplete dict {Presence Name, Presence Id} @@ -67,20 +63,14 @@ class RemoveCog(commands.Cog, name="Remove Commands"): with Session(engine) as session: stmt = select(Presences) - presences_dict: "dict[str,str]" = { - pres.Presence: str(pres.Id) for pres in session.scalars(stmt) - } + presences_dict: "dict[str,str]" = {pres.Presence: str(pres.Id) for pres in session.scalars(stmt)} # if empty compare string if not presence_str: - await interaction.response.send_autocomplete( - dict(list(presences_dict.items())[:25]) - ) + await interaction.response.send_autocomplete(dict(list(presences_dict.items())[:25])) return autofilled_presences: "dict[str,str]" = { - pres: pres_id - for pres, pres_id in presences_dict.items() - if presence_str.lower() in pres.lower() + pres: pres_id for pres, pres_id in presences_dict.items() if presence_str.lower() in pres.lower() } await interaction.response.send_autocomplete(autofilled_presences[:25]) diff --git a/cogs/commands/request.py b/cogs/commands/request.py index 14924d2..b1b0401 100644 --- a/cogs/commands/request.py +++ b/cogs/commands/request.py @@ -27,9 +27,7 @@ class RequestCog(commands.Cog, name="Request Commands"): Parent slash command for request. Never called. """ - @request.subcommand( - description="Request to add a presence to the bot's presence list." - ) + @request.subcommand(description="Request to add a presence to the bot's presence list.") async def add_presence( self, interaction: nextcord.Interaction, @@ -51,12 +49,8 @@ class RequestCog(commands.Cog, name="Request Commands"): config: DakinyConfig = self.bot.config content = f'<@{interaction.user.id}> would like to add presence "{presence}."' view = PresenceChange(presence) - await self.bot.get_channel(config.params.admin_channel_id).send( - content=content, view=view - ) - await interaction.response.send_message( - "Request sent to admins.", ephemeral=True - ) + await self.bot.get_channel(config.params.admin_channel_id).send(content=content, view=view) + await interaction.response.send_message("Request sent to admins.", ephemeral=True) @request.subcommand(description="Request to change your Dakiny nickname") async def change_nickname( @@ -80,12 +74,8 @@ class RequestCog(commands.Cog, name="Request Commands"): config: DakinyConfig = self.bot.config content = f'<@{interaction.user.id}> would like to change their username to "{new_nickname}."' view = UsernameRequest(interaction.user.id, new_nickname) - await self.bot.get_channel(config.params.admin_channel_id).send( - content=content, view=view - ) - await interaction.response.send_message( - "Request sent to admins.", ephemeral=True - ) + await self.bot.get_channel(config.params.admin_channel_id).send(content=content, view=view) + await interaction.response.send_message("Request sent to admins.", ephemeral=True) class UsernameRequest(nextcord.ui.View): @@ -103,9 +93,7 @@ class UsernameRequest(nextcord.ui.View): self.new_nickname = new_nickname @nextcord.ui.button(label="Approve", style=nextcord.ButtonStyle.green) - async def approve( - self, button: nextcord.ui.Button, interaction: nextcord.Interaction - ): + async def approve(self, button: nextcord.ui.Button, interaction: nextcord.Interaction): """ Approve button for a nickname change request. Changes the target's nickname to self.new_nickname. Catches an error if the target as a higher role than the bot. @@ -119,10 +107,7 @@ class UsernameRequest(nextcord.ui.View): if user is not None: try: await user.edit(nick=self.new_nickname) - content = ( - interaction.message.content - + f"\nApproved by <@{interaction.user.id}>." - ) + content = interaction.message.content + f"\nApproved by <@{interaction.user.id}>." await interaction.response.edit_message(content=content, view=None) except nextcord.errors.Forbidden: await interaction.response.send_message( @@ -133,9 +118,7 @@ class UsernameRequest(nextcord.ui.View): interaction.message.content + "\n\nFailed. User has too high permissions for bot to change nickname." ) - await interaction.followup.edit_message( - interaction.message.id, content=content, view=None - ) + await interaction.followup.edit_message(interaction.message.id, content=content, view=None) else: await interaction.response.edit_message( content="An error occured while changing the user's nickname.", @@ -168,9 +151,7 @@ class PresenceChange(nextcord.ui.View): self.presence = presence @nextcord.ui.button(label="Approve", style=nextcord.ButtonStyle.green) - async def approve( - self, button: nextcord.ui.Button, interaction: nextcord.Interaction - ): + async def approve(self, button: nextcord.ui.Button, interaction: nextcord.Interaction): """ Approve button for a presence add request. Adds the presence to the database. Removes the view from the interaction message. @@ -184,9 +165,7 @@ class PresenceChange(nextcord.ui.View): session.add(Presences(Presence=self.presence)) session.commit() - content = ( - interaction.message.content + f"\nApproved by <@{interaction.user.id}>." - ) + content = interaction.message.content + f"\nApproved by <@{interaction.user.id}>." await interaction.response.edit_message(content=content, view=None) @nextcord.ui.button(label="Deny", style=nextcord.ButtonStyle.red) diff --git a/cogs/commands/set.py b/cogs/commands/set.py index 5a9f338..4331e41 100644 --- a/cogs/commands/set.py +++ b/cogs/commands/set.py @@ -30,18 +30,12 @@ class setCog(commands.Cog, name="Set Commands"): Parent slash command for set. Never called. """ - @set.subcommand( - description="Sets your birth month and day for the Birthday Fun. Use 0/0 to remove yourself." - ) + @set.subcommand(description="Sets your birth month and day for the Birthday Fun. Use 0/0 to remove yourself.") async def birthday( self, interaction: nextcord.Interaction, - month: int = nextcord.SlashOption( - required=True, min_value=0, max_value=12, description="Your birth month." - ), - day: int = nextcord.SlashOption( - required=True, min_value=0, max_value=31, description="Your birth day." - ), + month: int = nextcord.SlashOption(required=True, min_value=0, max_value=12, description="Your birth month."), + day: int = nextcord.SlashOption(required=True, min_value=0, max_value=31, description="Your birth day."), ): """ Sets a user's birthday in the database. Removes on input 0/0. If birthday is today, adds to the birthday_list. @@ -56,9 +50,7 @@ class setCog(commands.Cog, name="Set Commands"): await interaction.response.defer(ephemeral=True) if month == 0 and day == 0: - stmt = delete(UserBirthday).where( - UserBirthday.UserId == interaction.user.id - ) + stmt = delete(UserBirthday).where(UserBirthday.UserId == interaction.user.id) with Session(engine) as session: session.execute(stmt) session.commit() @@ -70,18 +62,14 @@ class setCog(commands.Cog, name="Set Commands"): try: birthday = datetime(today.year, month, day) except ValueError: - await interaction.followup.send( - f"Invalid date: {month}/{day}.", ephemeral=True - ) + await interaction.followup.send(f"Invalid date: {month}/{day}.", ephemeral=True) return # If the birthday is today, add to birthday_list if today.date() == birthday.date(): birthday_list.append(interaction.user.id) - new_birthday = UserBirthday( - UserId=interaction.user.id, Birthday=f"{month}/{day}" - ) + new_birthday = UserBirthday(UserId=interaction.user.id, Birthday=f"{month}/{day}") with Session(engine) as session: try: session.add(new_birthday) @@ -89,13 +77,9 @@ class setCog(commands.Cog, name="Set Commands"): except IntegrityError: # User already has birthday, change it instead. session.rollback() - stmt = select(UserBirthday).where( - UserBirthday.UserId == interaction.user.id - ) + stmt = select(UserBirthday).where(UserBirthday.UserId == interaction.user.id) new_birthday = session.scalar(stmt) new_birthday.Birthday = f"{month}/{day}" session.commit() - await interaction.followup.send( - f"Birthday set to {month}/{day}.", ephemeral=True - ) + await interaction.followup.send(f"Birthday set to {month}/{day}.", ephemeral=True) diff --git a/cogs/commands/view.py b/cogs/commands/view.py index b186812..fe37d04 100644 --- a/cogs/commands/view.py +++ b/cogs/commands/view.py @@ -60,9 +60,7 @@ class ViewCog(commands.Cog, name="View Commands"): ) if df.empty: - await interaction.followup.send( - f'Quarter "{quarter_name}" is invalid.', ephemeral=True - ) + await interaction.followup.send(f'Quarter "{quarter_name}" is invalid.', ephemeral=True) return cycles = df.groupby("Cycle") @@ -74,32 +72,19 @@ class ViewCog(commands.Cog, name="View Commands"): non_members = unsorted_all.get_group(False) sorted_members = members.sort_values( by="UserId", - key=lambda user_ids: ( - [ - interaction.guild.get_member(int(id)).display_name - for id in user_ids - ] - ), + key=lambda user_ids: ([interaction.guild.get_member(int(id)).display_name for id in user_ids]), ) sorted_non_members = non_members.sort_values(by="UserId") - sorted_all = pd.concat( - [sorted_members, sorted_non_members], ignore_index=True, sort=False - ) + sorted_all = pd.concat([sorted_members, sorted_non_members], ignore_index=True, sort=False) for row in sorted_all.itertuples(): userid = row.UserId points = row.Points discord_member = row.DiscordMember - content += ( - f"<@{userid}>: {points}\n" - if discord_member - else f"{userid}: {points}\n" - ) + content += f"<@{userid}>: {points}\n" if discord_member else f"{userid}: {points}\n" content += "\n" if len(content) > 2000: - await interaction.followup.send( - "Message length is too long, please select search to limit results." - ) + await interaction.followup.send("Message length is too long, please select search to limit results.") return await interaction.followup.send(content=content, ephemeral=True) @@ -128,21 +113,13 @@ class ViewCog(commands.Cog, name="View Commands"): presences = session.scalars(stmt) if search: - message = "\n".join( - [ - presence - for presence in presences - if search.lower() in presence.lower() - ] - ) + message = "\n".join([presence for presence in presences if search.lower() in presence.lower()]) else: message = "\n".join(list(presences)) await interaction.followup.send(content=message, ephemeral=True) - @view.subcommand( - description="Shows all voters and who they voted for in a given election." - ) + @view.subcommand(description="Shows all voters and who they voted for in a given election.") async def votes( self, interaction: nextcord.Interaction, @@ -165,18 +142,12 @@ 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) - .where(Votes.Type == election_type) - ) + stmt = select(Votes).where(Votes.ElectionCycle == election_name).where(Votes.Type == election_type) with Session(engine) as session: # Check if election exists. if session.scalar(stmt) is None: - await interaction.response.send_message( - f"Invalid election: {election_name}.", ephemeral=True - ) + await interaction.response.send_message(f"Invalid election: {election_name}.", ephemeral=True) return votes = session.scalars(stmt) @@ -191,9 +162,7 @@ class ViewCog(commands.Cog, name="View Commands"): await interaction.response.send_message(content=content, ephemeral=True) @votes.on_autocomplete("election_name") - async def election_autocomplete( - self, interaction: nextcord.Interaction, election_name: str - ): + async def election_autocomplete(self, interaction: nextcord.Interaction, election_name: str): """ Collects all elections that start with the election string. @@ -212,16 +181,12 @@ class ViewCog(commands.Cog, name="View Commands"): return autofilled_elections: "list[str]" = [ - election - for election in elections - if election.lower().startswith(election_name.lower()) + election for election in elections if election.lower().startswith(election_name.lower()) ] await interaction.response.send_autocomplete(autofilled_elections[:25]) @event_points.on_autocomplete("quarter_name") - async def event_points_autocomplete( - self, interaction: nextcord.Interaction, quarter_name: str - ): + async def event_points_autocomplete(self, interaction: nextcord.Interaction, quarter_name: str): """ Collects all elections that start with the election string. @@ -240,8 +205,6 @@ class ViewCog(commands.Cog, name="View Commands"): return autofilled_quarters: "list[str]" = [ - quarter - for quarter in quarters - if quarter.lower().startswith(quarter_name.lower()) + quarter for quarter in quarters if quarter.lower().startswith(quarter_name.lower()) ] await interaction.response.send_autocomplete(autofilled_quarters[:25]) diff --git a/cogs/loops/bot_presence.py b/cogs/loops/bot_presence.py index f026809..ada7f3d 100644 --- a/cogs/loops/bot_presence.py +++ b/cogs/loops/bot_presence.py @@ -17,9 +17,7 @@ class BotPresence(commands.Cog, name="Bot Presence"): self.bot = bot @commands.Cog.listener() - async def on_guild_join( - self, guild: nextcord.Guild - ): # pylint: disable=unused-argument + async def on_guild_join(self, guild: nextcord.Guild): # pylint: disable=unused-argument """Listener for Guild Join. Updated presence on guild join.""" await self.change_presence() @@ -39,10 +37,6 @@ class BotPresence(commands.Cog, name="Bot Presence"): """Randomly chooses a presence and updates bot's presence.""" with Session(engine) as session: - presence = session.scalar( - select(Presences.Presence).order_by(func.random()) - ) + presence = session.scalar(select(Presences.Presence).order_by(func.random())) - await self.bot.change_presence( - activity=nextcord.CustomActivity(name="Custom Status", state=presence) - ) + await self.bot.change_presence(activity=nextcord.CustomActivity(name="Custom Status", state=presence)) diff --git a/cogs/loops/daily_loop.py b/cogs/loops/daily_loop.py index 81fc81c..4dd1dd0 100644 --- a/cogs/loops/daily_loop.py +++ b/cogs/loops/daily_loop.py @@ -111,14 +111,10 @@ class DailyLoop(commands.Cog, name="Daily Loop"): today = datetime.today() guild: nextcord.Guild = self.bot.get_guild(self.bot.config.params.guild_id) - announce_channel: nextcord.TextChannel = guild.get_channel( - self.bot.config.params.general_channel_id - ) + announce_channel: nextcord.TextChannel = guild.get_channel(self.bot.config.params.general_channel_id) cakedays: list[nextcord.Member] = [ - human - for human in guild.humans - if human.joined_at.month == today.month and human.joined_at.day == today.day + human for human in guild.humans if human.joined_at.month == today.month and human.joined_at.day == today.day ] for member in cakedays: diff --git a/cogs/message_listener/__init__.py b/cogs/message_listener/__init__.py index 0da2bce..38d745e 100644 --- a/cogs/message_listener/__init__.py +++ b/cogs/message_listener/__init__.py @@ -39,7 +39,5 @@ async def birthday_action(message: nextcord.Message) -> None: message (nextcord.Message): The message to check for a birthday person. """ - if message.author.id in birthday_list or bool( - set(message.raw_mentions) & set(birthday_list) - ): + if message.author.id in birthday_list or bool(set(message.raw_mentions) & set(birthday_list)): await message.add_reaction("🎂") diff --git a/main.py b/main.py index b1679fb..49cd47e 100644 --- a/main.py +++ b/main.py @@ -22,9 +22,7 @@ birthday_list: list[int] = [] logger = logging.getLogger("nextcord") logger.setLevel(logging.DEBUG) handler = logging.FileHandler(filename="nextcord.log", encoding="utf-8", mode="w") -handler.setFormatter( - logging.Formatter("%(asctime)s:%(levelname)s:%(name)s: %(message)s") -) +handler.setFormatter(logging.Formatter("%(asctime)s:%(levelname)s:%(name)s: %(message)s")) logger.addHandler(handler) diff --git a/pyproject.toml b/pyproject.toml index 9d89a20..7cdc9f1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,8 +63,26 @@ target-version = "py38" # Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. # Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or # McCabe complexity (`C901`) by default. -select = ["E4", "E7", "E9", "F", "D100", "D1"] -ignore = ["D107"] +select = ["ALL"] +ignore = [ + "D107", + "W191", + "E111", + "E114", + "E117", + "D203", + "D206", + "D212", + "D300", + "Q000", + "Q001", + "Q002", + "Q003", + "COM812", + "COM819", + "ISC001", + "ISC002", +] # Allow fix for all enabled rules (when `--fix`) is provided. fixable = ["ALL"] diff --git a/utils.py b/utils.py index 18d9973..389a478 100644 --- a/utils.py +++ b/utils.py @@ -149,9 +149,7 @@ def build_votes_str(config: DakinyConfig, guild: nextcord.Guild) -> str: election_name = get_election_name(config) with Session(engine) as session: - stmt = select(CouncilMembers).where( - CouncilMembers.ElectionCycle == election_name - ) + stmt = select(CouncilMembers).where(CouncilMembers.ElectionCycle == election_name) for nominee in sorted( session.scalars(stmt), @@ -159,11 +157,7 @@ def build_votes_str(config: DakinyConfig, guild: nextcord.Guild) -> str: ): votes[nominee.MemberId] = 0 - stmt = ( - select(Votes) - .where(Votes.ElectionCycle == election_name) - .where(Votes.Type == VoteType.COUNCIL) - ) + stmt = select(Votes).where(Votes.ElectionCycle == election_name).where(Votes.Type == VoteType.COUNCIL) for vote in session.scalars(stmt): if vote.VotedForId is not None: @@ -205,11 +199,7 @@ def build_highcouncil_votes_str(config: DakinyConfig, guild: nextcord.Guild) -> ): cl_votes[nominee.MemberId] = 0 - stmt = ( - select(Votes) - .where(Votes.ElectionCycle == election_name) - .where(Votes.Type == VoteType.CLANLEADER) - ) + 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: @@ -227,11 +217,7 @@ def build_highcouncil_votes_str(config: DakinyConfig, guild: nextcord.Guild) -> ): dm_votes[nominee.MemberId] = 0 - stmt = ( - select(Votes) - .where(Votes.ElectionCycle == election_name) - .where(Votes.Type == VoteType.DISCORDMANAGER) - ) + 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: @@ -318,9 +304,7 @@ def decorate_dm_nomination(user_id: int, speech: str) -> str: return msg -def record_event_points( - config: DakinyConfig, users: Sequence[str], is_member: bool, add_points: int -): +def record_event_points(config: DakinyConfig, users: Sequence[str], is_member: bool, add_points: int): """ 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. @@ -343,9 +327,7 @@ def record_event_points( points = session.scalar(stmt) if points is None: - add_points = get_allowed_points( - session, config.params.event_points_weekly_limit, user, add_points - ) + add_points = get_allowed_points(session, config.params.event_points_weekly_limit, user, add_points) new_points = ParticipationPoints( Cycle=quarter_name, UserId=user, @@ -354,17 +336,13 @@ def record_event_points( ) session.add(new_points) else: - add_points = get_allowed_points( - session, config.params.event_points_weekly_limit, user, add_points - ) + add_points = get_allowed_points(session, config.params.event_points_weekly_limit, user, add_points) points.Points += add_points session.commit() -def get_allowed_points( - session: Session, points_limit: int, user: str, points: int -) -> int: +def get_allowed_points(session: Session, points_limit: int, user: str, points: int) -> int: """ 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 @@ -380,14 +358,8 @@ def get_allowed_points( int: how many points are allowed to be given to the user. """ # 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) - ) + 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. @@ -424,11 +396,7 @@ async def update_clan_messages(guild: nextcord.Guild, channel_updates: Sequence[ channel_updates: (Sequence[str]): a sequence of the channels to update. """ - dirs = [ - dir_name - for dir_name in channel_updates - if os.path.isdir(f"{MESSAGES_DIR}/{dir_name}") - ] + dirs = [dir_name for dir_name in channel_updates if os.path.isdir(f"{MESSAGES_DIR}/{dir_name}")] with Session(engine) as session: for dir_name in dirs: @@ -443,14 +411,10 @@ async def update_clan_messages(guild: nextcord.Guild, channel_updates: Sequence[ message_name = f"{dir_name}/{file_name}" file_path = f"{dir_path}/{file_name}" with open(file_path, "r", encoding="utf8") as reader: - await edit_clan_message( - session, channel, message_name, reader.read(2000) - ) + await edit_clan_message(session, channel, message_name, reader.read(2000)) -async def get_clan_message_channel( - session: Session, channel_name: str, guild: nextcord.Guild -) -> nextcord.TextChannel: +async def get_clan_message_channel(session: Session, channel_name: str, guild: nextcord.Guild) -> nextcord.TextChannel: """ Used to obtain a channel from the guild object. Searches database for an id to get the channel. If that is not found, then searches through all channels in the guild to find one with a matching name. If that also doesn't @@ -472,10 +436,7 @@ async def get_clan_message_channel( # channel is not tracked. see if it exists channels = await guild.fetch_channels() for channel in channels: - if ( - channel.name == channel_name - and channel.__class__ is nextcord.TextChannel - ): + if channel.name == channel_name and channel.__class__ is nextcord.TextChannel: ret_channel = channel if ret_channel is None: @@ -499,11 +460,7 @@ async def get_clan_message_channel( channel_name, reason="Creating channel as required for by design of Dakiny-Messages repo.", ) - stmt = ( - update(Messages) - .where(Messages.Path == channel_name) - .values(Id=ret_channel.id) - ) + stmt = update(Messages).where(Messages.Path == channel_name).values(Id=ret_channel.id) session.execute(stmt) session.commit() return ret_channel @@ -540,9 +497,7 @@ async def edit_clan_message( await msg.edit(message_content) except nextcord.NotFound: msg = await channel.send(message_content) - stmt = ( - update(Messages).where(Messages.Path == message_name).values(Id=msg.id) - ) + stmt = update(Messages).where(Messages.Path == message_name).values(Id=msg.id) session.execute(stmt) session.commit() @@ -555,9 +510,7 @@ async def edit_clan_council_info(guild: nextcord.Guild, config: DakinyConfig): guild (nextcord.Guild): Guild object that to use to get channel. config (DakinyConfig): Configs """ - clan_info_channel: nextcord.TextChannel = await guild.fetch_channel( - config.params.clan_info_channel_id - ) + clan_info_channel: nextcord.TextChannel = await guild.fetch_channel(config.params.clan_info_channel_id) election = get_election_name(config) with Session(engine) as session: @@ -579,17 +532,11 @@ async def edit_clan_council_info(guild: nextcord.Guild, config: DakinyConfig): .where(CouncilMembers.Elected == true()) ) - council_members_strs = [ - f"<@{id}>" - for id in session.scalars(stmt) - if id not in (discord_manager, clan_leader) - ] + council_members_strs = [f"<@{id}>" for id in session.scalars(stmt) if id not in (discord_manager, clan_leader)] quarter = get_quarter_name(config) - with open( - f"{TEMPLATES_DIR}/council_members.txt", mode="r", encoding="utf8" - ) as file: + with open(f"{TEMPLATES_DIR}/council_members.txt", mode="r", encoding="utf8") as file: message = file.read() message = message.format( quarter=quarter, @@ -598,6 +545,4 @@ async def edit_clan_council_info(guild: nextcord.Guild, config: DakinyConfig): discord_manager=discord_manager, ) - await edit_clan_message( - session, clan_info_channel, "council_members.txt", message - ) + await edit_clan_message(session, clan_info_channel, "council_members.txt", message) From ccbe86623af9c51b2f480bbb3bb2f11e246f868a Mon Sep 17 00:00:00 2001 From: imcompiling Date: Wed, 22 May 2024 15:18:44 -0400 Subject: [PATCH 04/17] Run ruff linter with autofixing. --- classes/election_cycle.py | 18 ++++-------------- classes/gitea_webhook.py | 16 +++++++--------- classes/nominate_highcouncil_views.py | 4 ---- classes/nominate_views.py | 2 -- classes/voting_highcouncil_views.py | 5 +---- classes/voting_views.py | 4 +--- cogs/clan_messages/__init__.py | 1 - cogs/commands/__init__.py | 1 - cogs/commands/record.py | 2 -- cogs/commands/remove.py | 2 -- cogs/commands/request.py | 3 --- cogs/commands/set.py | 1 - cogs/commands/test.py | 1 - cogs/commands/view.py | 4 ---- cogs/loops/__init__.py | 1 - cogs/loops/bot_presence.py | 1 - cogs/loops/daily_loop.py | 4 ---- cogs/message_listener/__init__.py | 1 - database_setup/create_database.py | 2 +- main.py | 2 +- pyproject.toml | 5 ++++- utils.py | 14 +++----------- 22 files changed, 22 insertions(+), 72 deletions(-) diff --git a/classes/election_cycle.py b/classes/election_cycle.py index c10b672..cdda7c2 100644 --- a/classes/election_cycle.py +++ b/classes/election_cycle.py @@ -46,7 +46,6 @@ class ElectionCycle: config (DakinyConfig): The entire config object. guild (nextcord.Guild): The guild that this election is running in. """ - self.config: DakinyConfig = config self.cycle = cycle self.election_cycle = self.config.election_cycles[self.cycle] @@ -69,8 +68,7 @@ class ElectionCycle: list[str]: A list of message contents in the correct order. Must be a string because the message is too long. """ - - with open(f"{TEMPLATES_DIR}/election_announcement.txt", mode="r", encoding="utf8") as file: + with open(f"{TEMPLATES_DIR}/election_announcement.txt", encoding="utf8") as file: message = file.read() next_election = get_datetime( @@ -98,8 +96,7 @@ class ElectionCycle: Returns: str: Message to send. """ - - with open(f"{TEMPLATES_DIR}/highcouncil_election.txt", mode="r", encoding="utf8") as file: + with open(f"{TEMPLATES_DIR}/highcouncil_election.txt", encoding="utf8") as file: message = file.read() return message.format( @@ -117,8 +114,7 @@ class ElectionCycle: Returns: str: Message to send. """ - - with open(f"{TEMPLATES_DIR}/highcouncil_vote.txt", mode="r", encoding="utf8") as file: + with open(f"{TEMPLATES_DIR}/highcouncil_vote.txt", encoding="utf8") as file: message = file.read() return message.format( @@ -133,8 +129,7 @@ class ElectionCycle: Returns: str: Message to send """ - - with open(f"{TEMPLATES_DIR}/vote_start.txt", mode="r", encoding="utf8") as file: + with open(f"{TEMPLATES_DIR}/vote_start.txt", encoding="utf8") as file: message = file.read() vote_end: datetime = get_datetime(self.config.election_cycles[self.cycle].voting_end) @@ -226,7 +221,6 @@ class ElectionCycle: """ 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_thread: nextcord.Thread = self.guild.get_thread(self.get_election_info().ThreadId) @@ -278,7 +272,6 @@ class ElectionCycle: """ Performs required actions for when the highcouncil is elected. """ - message = self.build_highcouncil_vote_start() election_thread: nextcord.Thread = self.guild.get_thread(self.get_election_info().ThreadId) await election_thread.send(message) @@ -303,7 +296,6 @@ class ElectionCycle: """ Marks the election as over. """ - self.election_over = True elect_role: nextcord.Role = self.guild.get_role(self.config.params.clan_council_elect_role_id) @@ -396,7 +388,6 @@ class ElectionCycle: Returns: ElectionInformation: The information from the database about the current election. """ - with Session(engine) as session: stmt = select(ElectionInformation).where( ElectionInformation.ElectionCycle == get_election_name(self.config) @@ -407,7 +398,6 @@ class ElectionCycle: """ Sends a message to general chat reminder clan about election. """ - general: nextcord.TextChannel = self.guild.get_channel(self.config.params.general_channel_id) message = f"Reminder to <@&{self.config.params.clan_member_role_id}>! Clan elections are happening! Head over " diff --git a/classes/gitea_webhook.py b/classes/gitea_webhook.py index 2e587ab..ce0541a 100644 --- a/classes/gitea_webhook.py +++ b/classes/gitea_webhook.py @@ -4,8 +4,6 @@ from __future__ import annotations -from typing import List - from pydantic import BaseModel @@ -29,9 +27,9 @@ class Commit(BaseModel): committer: Committer verification: None timestamp: str - added: List[str] - removed: List[str] - modified: List[str] + added: list[str] + removed: list[str] + modified: list[str] class HeadCommit(BaseModel): @@ -42,9 +40,9 @@ class HeadCommit(BaseModel): committer: Committer verification: None timestamp: str - added: List[str] - removed: List[str] - modified: List[str] + added: list[str] + removed: list[str] + modified: list[str] class Owner(BaseModel): @@ -187,7 +185,7 @@ class GiteaWebhook(BaseModel): before: str after: str compare_url: str - commits: List[Commit] + commits: list[Commit] total_commits: int head_commit: HeadCommit repository: Repository diff --git a/classes/nominate_highcouncil_views.py b/classes/nominate_highcouncil_views.py index 8d7d363..49e829f 100644 --- a/classes/nominate_highcouncil_views.py +++ b/classes/nominate_highcouncil_views.py @@ -55,7 +55,6 @@ class NominateDMModal(nextcord.ui.Modal): interaction (Interaction): Interaction with user. """ - speech = decorate_dm_nomination(interaction.user.id, self.speech.value) with Session(engine) as session: @@ -116,7 +115,6 @@ class NominateCLModal(nextcord.ui.Modal): interaction (Interaction): Interaction with user. """ - speech = decorate_cl_nomination(interaction.user.id, self.speech.value) with Session(engine) as session: @@ -163,7 +161,6 @@ class NominateHighCouncilView(nextcord.ui.View): button (nextcord.ui.Button): Unused. idk why its needed in the decorator interaction (nextcord.Interaction): Interaction of user who pressed the button """ - # enforce council elect only if self.guild.get_role(self.config.params.clan_council_elect_role_id) not in interaction.user.roles: await interaction.response.send_message( @@ -207,7 +204,6 @@ class NominateHighCouncilView(nextcord.ui.View): button (nextcord.ui.Button): Unused. idk why its needed in the decorator interaction (nextcord.Interaction): Interaction of user who pressed the button """ - # enforce council elect only if self.guild.get_role(self.config.params.clan_council_elect_role_id) not in interaction.user.roles: await interaction.response.send_message( diff --git a/classes/nominate_views.py b/classes/nominate_views.py index 1b253af..8211288 100644 --- a/classes/nominate_views.py +++ b/classes/nominate_views.py @@ -50,7 +50,6 @@ class NominateModal(nextcord.ui.Modal): interaction (Interaction): Interaction with user. """ - speech = decorate_nomination(interaction.user.id, self.speech.value) with Session(engine) as session: @@ -102,7 +101,6 @@ class NominateView(nextcord.ui.View): button (nextcord.ui.Button): Unused. idk why its needed in the decorator interaction (nextcord.Interaction): Interaction of user who pressed the button """ - # enforce members only if self.guild.get_role(self.config.params.clan_member_role_id) not in interaction.user.roles: await interaction.response.send_message( diff --git a/classes/voting_highcouncil_views.py b/classes/voting_highcouncil_views.py index bf74387..a0e9350 100644 --- a/classes/voting_highcouncil_views.py +++ b/classes/voting_highcouncil_views.py @@ -2,7 +2,6 @@ Contains various views to be used while voting for high council. Controls the logic when performing the vote. """ -from typing import List import nextcord from nextcord import SelectOption @@ -21,7 +20,7 @@ from utils import VoteType, build_highcouncil_votes_str, get_election_name class VoteSelect(nextcord.ui.StringSelect): - def __init__(self, options: List["SelectOption"], config: DakinyConfig, election_type: str) -> None: + def __init__(self, options: list["SelectOption"], config: DakinyConfig, election_type: str) -> None: super().__init__( placeholder="Choose 1", min_values=1, @@ -105,7 +104,6 @@ class VoteHighcouncilView(nextcord.ui.View): button (nextcord.ui.Button): Unused. idk why its needed in the decorator interaction (nextcord.Interaction): Interaction of user who pressed the button """ - # enforce council elect only if self.guild.get_role(self.config.params.clan_council_elect_role_id) not in interaction.user.roles: await interaction.response.send_message( @@ -176,7 +174,6 @@ class VoteHighcouncilView(nextcord.ui.View): button (nextcord.ui.Button): Unused. idk why its needed in the decorator interaction (nextcord.Interaction): Interaction of user who pressed the button """ - # enforce council elect only if self.guild.get_role(self.config.params.clan_council_elect_role_id) not in interaction.user.roles: await interaction.response.send_message( diff --git a/classes/voting_views.py b/classes/voting_views.py index 7c9420a..4e8491a 100644 --- a/classes/voting_views.py +++ b/classes/voting_views.py @@ -2,7 +2,6 @@ Contains various views to be used while voting. Controls the logic when performing the vote. """ -from typing import List import nextcord from nextcord import SelectOption @@ -21,7 +20,7 @@ from utils import VoteType, build_votes_str, get_election_name class VoteSelect(nextcord.ui.StringSelect): - def __init__(self, options: List["SelectOption"], config: DakinyConfig) -> None: + def __init__(self, options: list["SelectOption"], config: DakinyConfig) -> None: super().__init__( placeholder="Choose 1", min_values=1, @@ -98,7 +97,6 @@ class VoteView(nextcord.ui.View): button (nextcord.ui.Button): Unused. idk why its needed in the decorator interaction (nextcord.Interaction): Interaction of user who pressed the button """ - # enforce members only if self.guild.get_role(self.config.params.clan_member_role_id) not in interaction.user.roles: await interaction.response.send_message( diff --git a/cogs/clan_messages/__init__.py b/cogs/clan_messages/__init__.py index 619685b..33326a2 100644 --- a/cogs/clan_messages/__init__.py +++ b/cogs/clan_messages/__init__.py @@ -44,7 +44,6 @@ class ClanMessages(commands.Cog, name="Clan Messages"): 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 diff --git a/cogs/commands/__init__.py b/cogs/commands/__init__.py index df6a0c2..6dab456 100644 --- a/cogs/commands/__init__.py +++ b/cogs/commands/__init__.py @@ -19,7 +19,6 @@ def setup(bot: commands.Bot): """ Adds all command classes to bot. """ - bot.add_cog(RecordCog(bot)) bot.add_cog(RefreshCog(bot)) bot.add_cog(RemoveCog()) diff --git a/cogs/commands/record.py b/cogs/commands/record.py index 435eab9..93fa223 100644 --- a/cogs/commands/record.py +++ b/cogs/commands/record.py @@ -47,7 +47,6 @@ class RecordCog(commands.Cog, name="Record Commands"): name (str): Name of the event. host (nextcord.Member, optional): Alternative host to user using command. """ - # check role if interaction.guild.get_role(self.bot.config.params.event_host_role_id) not in interaction.user.roles: await interaction.response.send_message( @@ -110,7 +109,6 @@ class EventView(nextcord.ui.View): Submits the event embed to the event channel. Removes view and embed from recording message. Sends point information to database. """ - discord_users = self.event_participants.values.members if discord_users: discord_users_list: list[str] = [str(user.id) for user in discord_users if user.id is not self.host_id] diff --git a/cogs/commands/remove.py b/cogs/commands/remove.py index 43715ba..9dde841 100644 --- a/cogs/commands/remove.py +++ b/cogs/commands/remove.py @@ -41,7 +41,6 @@ class RemoveCog(commands.Cog, name="Remove Commands"): interaction (nextcord.Interaction): Interaction that invoked the command. presence (str): Is SlashOption. The presence to remove. Is the ID of the presence. """ - presence_id = int(presence_str) with Session(engine) as session: stmt = delete(Presences).where(Presences.Id == presence_id) @@ -60,7 +59,6 @@ class RemoveCog(commands.Cog, name="Remove Commands"): interaction (nextcord.Interaction): Interaction with command. presence_str (str): String to run autocomplete on. """ - with Session(engine) as session: stmt = select(Presences) presences_dict: "dict[str,str]" = {pres.Presence: str(pres.Id) for pres in session.scalars(stmt)} diff --git a/cogs/commands/request.py b/cogs/commands/request.py index b1b0401..2e2638b 100644 --- a/cogs/commands/request.py +++ b/cogs/commands/request.py @@ -45,7 +45,6 @@ class RequestCog(commands.Cog, name="Request Commands"): interaction (nextcord.Interaction): Interaction that invoked the command. presence (str): The presence to add. """ - config: DakinyConfig = self.bot.config content = f'<@{interaction.user.id}> would like to add presence "{presence}."' view = PresenceChange(presence) @@ -70,7 +69,6 @@ class RequestCog(commands.Cog, name="Request Commands"): interaction (nextcord.Interaction): Interaction that invoked the command. new_username(str): The username to change to. """ - config: DakinyConfig = self.bot.config content = f'<@{interaction.user.id}> would like to change their username to "{new_nickname}."' view = UsernameRequest(interaction.user.id, new_nickname) @@ -160,7 +158,6 @@ class PresenceChange(nextcord.ui.View): button (nextcord.ui.Button): Unused. interaction (nextcord.Interaction): The interaction of the user clicking the button. """ - with Session(engine) as session: session.add(Presences(Presence=self.presence)) session.commit() diff --git a/cogs/commands/set.py b/cogs/commands/set.py index 4331e41..f938ff4 100644 --- a/cogs/commands/set.py +++ b/cogs/commands/set.py @@ -45,7 +45,6 @@ class setCog(commands.Cog, name="Set Commands"): month (int, optional): The month a user was born. Is SlashOption. day (int, optional): The day a user was born. Is SlashOption. """ - # Must be deferred, apparently datetime takes a little bit on invalid dates. await interaction.response.defer(ephemeral=True) diff --git a/cogs/commands/test.py b/cogs/commands/test.py index d1a4a8b..51fee65 100644 --- a/cogs/commands/test.py +++ b/cogs/commands/test.py @@ -47,7 +47,6 @@ class TestCog(commands.Cog, name="Test commands."): Args: interaction (nextcord.Interaction): _description_ """ - config: DakinyConfig = self.bot.config cycle = ElectionCycle( get_current_election_cycle(config), diff --git a/cogs/commands/view.py b/cogs/commands/view.py index fe37d04..9887081 100644 --- a/cogs/commands/view.py +++ b/cogs/commands/view.py @@ -104,7 +104,6 @@ class ViewCog(commands.Cog, name="View Commands"): Args: interaction (nextcord.Integration): User's interaction with the command. """ - # might take a while depending on number of presences await interaction.response.defer(ephemeral=True) @@ -141,7 +140,6 @@ class ViewCog(commands.Cog, name="View Commands"): interaction (nextcord.Interaction): User's interaction with the command. election_name (str): Is SlashOption. The name of the election to view votes for. """ - stmt = select(Votes).where(Votes.ElectionCycle == election_name).where(Votes.Type == election_type) with Session(engine) as session: @@ -170,7 +168,6 @@ class ViewCog(commands.Cog, name="View Commands"): interaction (nextcord.Interaction): Interaction with command. election_name (str): String to run autocomplete on. """ - with Session(engine) as session: stmt = select(Votes.ElectionCycle).distinct() elections = session.scalars(stmt) @@ -194,7 +191,6 @@ class ViewCog(commands.Cog, name="View Commands"): interaction (nextcord.Interaction): Interaction with command. quarter_name (str): String to run autocomplete on. """ - with Session(engine) as session: stmt = select(ParticipationPoints.Cycle).distinct() quarters = session.scalars(stmt) diff --git a/cogs/loops/__init__.py b/cogs/loops/__init__.py index dc7c70d..3b1e442 100644 --- a/cogs/loops/__init__.py +++ b/cogs/loops/__init__.py @@ -12,6 +12,5 @@ def setup(bot: commands.Bot): """ Adds functions to bot. """ - bot.add_cog(BotPresence(bot)) bot.add_cog(DailyLoop(bot)) diff --git a/cogs/loops/bot_presence.py b/cogs/loops/bot_presence.py index ada7f3d..6dc3f30 100644 --- a/cogs/loops/bot_presence.py +++ b/cogs/loops/bot_presence.py @@ -35,7 +35,6 @@ class BotPresence(commands.Cog, name="Bot Presence"): async def change_presence(self): """Randomly chooses a presence and updates bot's presence.""" - with Session(engine) as session: presence = session.scalar(select(Presences.Presence).order_by(func.random())) diff --git a/cogs/loops/daily_loop.py b/cogs/loops/daily_loop.py index 4dd1dd0..ccc19c4 100644 --- a/cogs/loops/daily_loop.py +++ b/cogs/loops/daily_loop.py @@ -38,7 +38,6 @@ class DailyLoop(commands.Cog, name="Daily Loop"): an active election, runs the check_date() function for the election to perform actions. Finally checks if the election is over to delete the current election object. """ - if self.current_election is None: self.current_election = self.get_election_cycle() @@ -61,7 +60,6 @@ class DailyLoop(commands.Cog, name="Daily Loop"): """ Actions run before the loop starts. Want to wait for bot to be ready and get the current cycle. """ - await self.bot.wait_until_ready() # Bot needs to know guilds to get the current cycle. @@ -75,7 +73,6 @@ class DailyLoop(commands.Cog, name="Daily Loop"): Returns: ElectionCycle: Current election cycle, None if there is no election happening. """ - config: DakinyConfig = self.bot.config cycle = get_current_election_cycle(config) @@ -108,7 +105,6 @@ class DailyLoop(commands.Cog, name="Daily Loop"): Loops through the guild's humans checking for joined_at dates that match today's date. Sends a message saying happy cakeday. """ - today = datetime.today() guild: nextcord.Guild = self.bot.get_guild(self.bot.config.params.guild_id) announce_channel: nextcord.TextChannel = guild.get_channel(self.bot.config.params.general_channel_id) diff --git a/cogs/message_listener/__init__.py b/cogs/message_listener/__init__.py index 38d745e..c073f17 100644 --- a/cogs/message_listener/__init__.py +++ b/cogs/message_listener/__init__.py @@ -38,6 +38,5 @@ async def birthday_action(message: nextcord.Message) -> None: Args: message (nextcord.Message): The message to check for a birthday person. """ - if message.author.id in birthday_list or bool(set(message.raw_mentions) & set(birthday_list)): await message.add_reaction("🎂") diff --git a/database_setup/create_database.py b/database_setup/create_database.py index 7b975ca..912f729 100644 --- a/database_setup/create_database.py +++ b/database_setup/create_database.py @@ -9,7 +9,7 @@ if not exists("database/dakiny.db"): with sqlite3.connect("database/dakiny.db") as con: cur = con.cursor() - with open("database_setup/database.sql", mode="r", encoding="utf-8") as file: + with open("database_setup/database.sql", encoding="utf-8") as file: database_script = file.read() cur.executescript(database_script) con.commit() diff --git a/main.py b/main.py index 49cd47e..36e4d99 100644 --- a/main.py +++ b/main.py @@ -7,12 +7,12 @@ import nextcord from hydra.core.config_store import ConfigStore from nextcord.ext import commands -import database_setup.create_database as create_database from classes.nominate_highcouncil_views import NominateHighCouncilView from classes.nominate_views import NominateView from classes.voting_highcouncil_views import VoteHighcouncilView from classes.voting_views import VoteView from config import DakinyConfig +from database_setup import create_database cs = ConfigStore.instance() cs.store(name="dakiny_config", node=DakinyConfig) diff --git a/pyproject.toml b/pyproject.toml index 7cdc9f1..fd86fb0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,7 +57,7 @@ line-length = 120 indent-width = 4 # Assume Python 3.8 -target-version = "py38" +target-version = "py311" [tool.ruff.lint] # Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. @@ -91,6 +91,9 @@ unfixable = [] # Allow unused variables when underscore-prefixed. dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" +[tool.ruff.lint.pydocstyle] +convention = "google" + [tool.ruff.format] # Like Black, use double quotes for strings. quote-style = "double" diff --git a/utils.py b/utils.py index 389a478..a0f5aa5 100644 --- a/utils.py +++ b/utils.py @@ -3,9 +3,9 @@ File contains util functions. """ import os +from collections.abc import Sequence from datetime import datetime from enum import StrEnum -from typing import Sequence import nextcord from sqlalchemy import select, true, update @@ -63,7 +63,6 @@ def get_election_name(config: DakinyConfig) -> str: Returns: str: the name of the election cycle """ - 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}" @@ -79,7 +78,6 @@ def get_quarter_name(config: DakinyConfig) -> str: Returns: str: the name of the cycle. """ - cycle = get_quarter_cycle(config) current_cycle = config.election_cycles[cycle] @@ -96,7 +94,6 @@ def get_current_election_cycle(config: DakinyConfig) -> int: Returns: int: cycle index for active election. None for no active cycle """ - today = datetime.today() for cycle, election in enumerate(config.election_cycles): @@ -120,7 +117,6 @@ def get_quarter_cycle(config: DakinyConfig) -> int: Returns: int: cycle index for current cycle. """ - today = datetime.today() for cycle, election in enumerate(config.election_cycles): if ( @@ -143,7 +139,6 @@ def build_votes_str(config: DakinyConfig, guild: nextcord.Guild) -> str: Returns: str: String that contains all nominees and their votes. """ - votes: dict[int, int] = {} election_name = get_election_name(config) @@ -180,7 +175,6 @@ def build_highcouncil_votes_str(config: DakinyConfig, guild: nextcord.Guild) -> str: String that contains all nominees and their votes. guild (nextcord.Guild): The guild object. Used to get display names for sorting. """ - cl_votes: dict[int, int] = {} dm_votes: dict[int, int] = {} @@ -315,7 +309,6 @@ def record_event_points(config: DakinyConfig, users: Sequence[str], is_member: b is_member (bool): whether the users are a discord member or not points (int): number of points to add """ - quarter_name = get_quarter_name(config) with Session(engine) as session: for user in users: @@ -395,7 +388,6 @@ async def update_clan_messages(guild: nextcord.Guild, channel_updates: Sequence[ 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 channel_updates if os.path.isdir(f"{MESSAGES_DIR}/{dir_name}")] with Session(engine) as session: @@ -410,7 +402,7 @@ async def update_clan_messages(guild: nextcord.Guild, channel_updates: Sequence[ for file_name in sorted_files: message_name = f"{dir_name}/{file_name}" file_path = f"{dir_path}/{file_name}" - with open(file_path, "r", encoding="utf8") as reader: + with open(file_path, encoding="utf8") as reader: await edit_clan_message(session, channel, message_name, reader.read(2000)) @@ -536,7 +528,7 @@ async def edit_clan_council_info(guild: nextcord.Guild, config: DakinyConfig): quarter = get_quarter_name(config) - with open(f"{TEMPLATES_DIR}/council_members.txt", mode="r", encoding="utf8") as file: + with open(f"{TEMPLATES_DIR}/council_members.txt", encoding="utf8") as file: message = file.read() message = message.format( quarter=quarter, From af37ce651842a6e08a4bb2638d9b5a6842cdd471 Mon Sep 17 00:00:00 2001 From: imcompiling Date: Wed, 22 May 2024 15:28:54 -0400 Subject: [PATCH 05/17] Minor ruff config changes. --- pyproject.toml | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index fd86fb0..c1524b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,23 +65,24 @@ target-version = "py311" # McCabe complexity (`C901`) by default. select = ["ALL"] ignore = [ - "D107", - "W191", - "E111", - "E114", - "E117", + "ANN101", + "COM812", + "COM819", + "D107", # don't want docstrings on init functions "D203", "D206", "D212", "D300", + "E111", + "E114", + "E117", + "ISC001", + "ISC002", "Q000", "Q001", "Q002", "Q003", - "COM812", - "COM819", - "ISC001", - "ISC002", + "W191", ] # Allow fix for all enabled rules (when `--fix`) is provided. @@ -94,6 +95,9 @@ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" [tool.ruff.lint.pydocstyle] convention = "google" +[tool.ruff.lint.flake8-annotations] +mypy-init-return = true + [tool.ruff.format] # Like Black, use double quotes for strings. quote-style = "double" From 02c313aee52d87e1bb3cef6877295bf4e3800158 Mon Sep 17 00:00:00 2001 From: imcompiling Date: Wed, 22 May 2024 15:40:50 -0400 Subject: [PATCH 06/17] Fix lint errors in voting_views. --- classes/pyproject.toml | 7 ++++++ classes/voting_views.py | 47 +++++++++++++++++++++-------------------- 2 files changed, 31 insertions(+), 23 deletions(-) create mode 100644 classes/pyproject.toml diff --git a/classes/pyproject.toml b/classes/pyproject.toml new file mode 100644 index 0000000..e3c5aa1 --- /dev/null +++ b/classes/pyproject.toml @@ -0,0 +1,7 @@ +[tool.ruff] +# Extend the `pyproject.toml` file in the parent directory... +extend = "../pyproject.toml" + +[tool.ruff.lint] +# ignore implicit-namespace-package in this directory. +ignore = ["INP001"] diff --git a/classes/voting_views.py b/classes/voting_views.py index 4e8491a..c480d69 100644 --- a/classes/voting_views.py +++ b/classes/voting_views.py @@ -1,14 +1,6 @@ -""" -Contains various views to be used while voting. Controls the logic when performing the vote. -""" - +"""Contains various views to be used while voting. Controls the logic when performing the vote.""" import nextcord -from nextcord import SelectOption -from nextcord.interactions import Interaction -from sqlalchemy import exc, select, update -from sqlalchemy.orm import Session - from config import DakinyConfig from database_setup.sql_data_classes import ( CouncilMembers, @@ -16,10 +8,16 @@ from database_setup.sql_data_classes import ( Votes, engine, ) +from nextcord import SelectOption +from nextcord.interactions import Interaction +from sqlalchemy import exc, select, update +from sqlalchemy.orm import Session from utils import VoteType, build_votes_str, get_election_name class VoteSelect(nextcord.ui.StringSelect): + """Vote view string select.""" + def __init__(self, options: list["SelectOption"], config: DakinyConfig) -> None: super().__init__( placeholder="Choose 1", @@ -29,7 +27,16 @@ class VoteSelect(nextcord.ui.StringSelect): ) self.config = config - async def callback(self, interaction: Interaction): + async def callback(self, interaction: Interaction) -> None: + """ + Callback override for the StringSelect. + + Submits the vote to database if a user is selected. Handles adding vs editing a vote. Returns message + informing user that vote has been recorded. + + Args: + interaction (Interaction): Interaction with this callback. + """ election_name = get_election_name(self.config) with Session(engine) as session: stmt = select(ElectionInformation.CouncilVoteMsgId).where( @@ -37,10 +44,7 @@ class VoteSelect(nextcord.ui.StringSelect): ) vote_msg_id = session.scalar(stmt) - if self.values[0] == "Abstain": - voted_for = None - else: - voted_for = int(self.values[0]) + voted_for = None if self.values[0] == "Abstain" else int(self.values[0]) new_vote = Votes( ElectionCycle=election_name, @@ -69,18 +73,13 @@ class VoteSelect(nextcord.ui.StringSelect): vote_msg: nextcord.Message = await interaction.channel.fetch_message(vote_msg_id) await vote_msg.edit(content=vote_message_str) - if voted_for is not None: - msg = f"You have voted for <@{voted_for}>" - else: - msg = "You have abstained from voting." + msg = f"You have voted for <@{voted_for}>" if voted_for is not None else "You have abstained from voting." await interaction.edit(content=msg, view=None) class VoteView(nextcord.ui.View): - """ - The button to press to start your nomination. - """ + """The button to press to start your nomination.""" def __init__(self, config: DakinyConfig, guild: nextcord.Guild): super().__init__(timeout=None) @@ -88,9 +87,11 @@ class VoteView(nextcord.ui.View): self.guild = guild @nextcord.ui.button(label="Vote", style=nextcord.ButtonStyle.primary, custom_id="vote") - async def vote(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: + async def vote(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: # noqa: ARG002 """ - Enforces only members vote. Gets election cycle, creates voting options (checking if user has voted already), + Enforces only members vote. + + Gets election cycle, creates voting options (checking if user has voted already), then sends the voting view. Args: From ed067489844bd96a5aabccf91f22a56730e03128 Mon Sep 17 00:00:00 2001 From: imcompiling Date: Wed, 22 May 2024 16:12:20 -0400 Subject: [PATCH 07/17] Fix all smells in utils. --- README.md | 3 +- pyproject.toml | 1 + utils.py | 74 ++++++++++++++++++++++++++++++-------------------- 3 files changed, 47 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index b55ea19..486876f 100644 --- a/README.md +++ b/README.md @@ -6,4 +6,5 @@ Dakiny clan's own discord bot! ### Assumptions - For Elections: Bot assumes that it always did the step before it. If the step before an action didn't happen, behavior is undefined. Object creation in the middle of a cycle is only there for restarting the bot. All relevant information should already -exist in the database. \ No newline at end of file +exist in the database. +- Bot is running on machine that is usint UTC time. All today() calls specify UTC time zone. \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index c1524b2..2d78ccb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,6 +15,7 @@ pandas = "1.5.2" python-dotenv = "1.0.0" pytz = "2022.7.1" sqlalchemy = "1.4.46" +aiofiles = "^23.2.1" [build-system] diff --git a/utils.py b/utils.py index a0f5aa5..1f903ad 100644 --- a/utils.py +++ b/utils.py @@ -1,12 +1,13 @@ -""" -File contains util functions. -""" +"""File contains util functions.""" +import calendar +import datetime import os from collections.abc import Sequence -from datetime import datetime from enum import StrEnum +from pathlib import Path +import aiofiles import nextcord from sqlalchemy import select, true, update from sqlalchemy.orm import Session @@ -29,7 +30,7 @@ TEMPLATES_DIR = f"{REPO_DIR}/message_templates" VoteType = StrEnum("VoteType", ["COUNCIL", "DISCORDMANAGER", "CLANLEADER"]) -def get_datetime(day: str) -> datetime: +def get_datetime(day: str) -> datetime.datetime: """ Takes in a string month and day and returns a datetime object of that Month/Day this year. @@ -39,23 +40,24 @@ def get_datetime(day: str) -> datetime: Returns: datetime: Datetime representation of Month/Day. """ - year = datetime.today().year + today = datetime.datetime.now(tz=datetime.UTC).date() + year = 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 and month < 12: + if today.month == calendar.DECEMBER and month < calendar.DECEMBER: year += 1 - if datetime.today().month == 1 and month > 1: + if today.month == calendar.JANUARY and month > calendar.JANUARY: year -= 1 - return datetime(year, month, day) + return datetime.datetime(year, month, day, tzinfo=datetime.UTC) def get_election_name(config: DakinyConfig) -> str: """ - Builds the unique name of the current election cycle + Builds the unique name of the current election cycle. Args: config (DakinyConfig): the config used to know the election cycle. @@ -70,7 +72,7 @@ def get_election_name(config: DakinyConfig) -> str: def get_quarter_name(config: DakinyConfig) -> str: """ - Builds the name of the current cycle/quarter + Builds the name of the current cycle/quarter. Args: config (DakinyConfig): the config used to know the election cycle. @@ -94,7 +96,7 @@ def get_current_election_cycle(config: DakinyConfig) -> int: Returns: int: cycle index for active election. None for no active cycle """ - today = datetime.today() + today = datetime.datetime.now(tz=datetime.UTC).date() for cycle, election in enumerate(config.election_cycles): if ( @@ -109,7 +111,7 @@ def get_current_election_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 + Returns the index for the current cycle/quarter. Defined as between term start and end. Shouldn't return none. Args: config (DakinyConfig): Config object to use @@ -117,7 +119,7 @@ def get_quarter_cycle(config: DakinyConfig) -> int: Returns: int: cycle index for current cycle. """ - today = datetime.today() + today = datetime.datetime.now(tz=datetime.UTC).date() for cycle, election in enumerate(config.election_cycles): if ( today.date() >= get_datetime(election.term_start).date() @@ -170,10 +172,10 @@ def build_highcouncil_votes_str(config: DakinyConfig, guild: nextcord.Guild) -> Args: config (DakinyConfig): Daking config object + guild (nextcord.Guild): The guild object. Used to get display names for sorting. Returns: str: String that contains all nominees and their votes. - guild (nextcord.Guild): The guild object. Used to get display names for sorting. """ cl_votes: dict[int, int] = {} dm_votes: dict[int, int] = {} @@ -298,16 +300,18 @@ def decorate_dm_nomination(user_id: int, speech: str) -> str: return msg -def record_event_points(config: DakinyConfig, users: Sequence[str], is_member: bool, add_points: int): +def record_event_points(config: DakinyConfig, users: Sequence[str], is_member: bool, add_points: int) -> None: # noqa: FBT001 """ - Adds event points to all users listed. Checks if they don't exist in order to make a new entry, otherwise + 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 (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 + add_points (int): number of points to add """ quarter_name = get_quarter_name(config) with Session(engine) as session: @@ -337,7 +341,9 @@ def record_event_points(config: DakinyConfig, users: Sequence[str], is_member: b def get_allowed_points(session: Session, points_limit: int, user: str, points: int) -> int: """ - Gets the maximum number of points a user can be given. Searches how many points they have been given + 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. @@ -351,7 +357,8 @@ def get_allowed_points(session: Session, points_limit: int, user: str, points: i int: how many points are allowed to be given to the user. """ # check for limit - this_week = f"{datetime.today().isocalendar().year}-{datetime.today().isocalendar().week}" + today = datetime.datetime.now(tz=datetime.UTC).date() + this_week = f"{today.isocalendar().year}-{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 @@ -374,12 +381,13 @@ def get_allowed_points(session: Session, points_limit: int, user: str, points: i return add_points -async def update_clan_messages(guild: nextcord.Guild, channel_updates: Sequence[str]): +async def update_clan_messages(guild: nextcord.Guild, channel_updates: Sequence[str]) -> None: """ Updates all clan messages to contents in MESSAGES_DIR. + Does not to any git work. Is expected to be called after git is pulled. - for ever directory, sort the files by number then edits/sends the message. + For every directory, sort the files by number then edits/sends the message. Channel fetching/management is handled by another function. Message editing and sending is handled by another function. @@ -388,7 +396,7 @@ async def update_clan_messages(guild: nextcord.Guild, channel_updates: Sequence[ 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 channel_updates if os.path.isdir(f"{MESSAGES_DIR}/{dir_name}")] + dirs = [dir_name for dir_name in channel_updates if Path.is_dir(f"{MESSAGES_DIR}/{dir_name}")] with Session(engine) as session: for dir_name in dirs: @@ -402,13 +410,15 @@ async def update_clan_messages(guild: nextcord.Guild, channel_updates: Sequence[ for file_name in sorted_files: message_name = f"{dir_name}/{file_name}" file_path = f"{dir_path}/{file_name}" - with open(file_path, encoding="utf8") as reader: + with aiofiles.open(file_path, encoding="utf8") as reader: await edit_clan_message(session, channel, message_name, reader.read(2000)) async def get_clan_message_channel(session: Session, channel_name: str, guild: nextcord.Guild) -> nextcord.TextChannel: """ - Used to obtain a channel from the guild object. Searches database for an id to get the channel. If that is not + Used to obtain a channel from the guild object. + + Searches database for an id to get the channel. If that is not found, then searches through all channels in the guild to find one with a matching name. If that also doesn't turn up a channel, then creates it. Adds an entry into database for last two paths. Assumes that there is an open session to the database. @@ -463,9 +473,11 @@ async def edit_clan_message( channel: nextcord.TextChannel, message_name: str, message_content: str, -): +) -> None: """ - Obtains a message id from database. If no id, then need to send a new message and record the id. + Obtains a message id from database. + + If no id, then need to send a new message and record the id. If there is a message, then edit the message. If that message is not found in discord, then need to send a new message and record the id in the database. @@ -494,7 +506,7 @@ async def edit_clan_message( session.commit() -async def edit_clan_council_info(guild: nextcord.Guild, config: DakinyConfig): +async def edit_clan_council_info(guild: nextcord.Guild, config: DakinyConfig) -> None: """ Gets the winners of the elections and updates the perminant message that states the current council. @@ -524,11 +536,13 @@ async def edit_clan_council_info(guild: nextcord.Guild, config: DakinyConfig): .where(CouncilMembers.Elected == true()) ) - council_members_strs = [f"<@{id}>" for id in session.scalars(stmt) if id not in (discord_manager, clan_leader)] + council_members_strs = [ + f"<@{user_id}>" for user_id in session.scalars(stmt) if user_id not in (discord_manager, clan_leader) + ] quarter = get_quarter_name(config) - with open(f"{TEMPLATES_DIR}/council_members.txt", encoding="utf8") as file: + with aiofiles.open(f"{TEMPLATES_DIR}/council_members.txt", encoding="utf8") as file: message = file.read() message = message.format( quarter=quarter, From 1fd1be5576912ed6d0abe6358a8d42e5a41cc8a5 Mon Sep 17 00:00:00 2001 From: imcompiling Date: Wed, 22 May 2024 16:23:53 -0400 Subject: [PATCH 08/17] Use datetime.now().date() instead of today(). --- classes/election_cycle.py | 9 ++++----- cogs/commands/set.py | 4 ++-- cogs/loops/daily_loop.py | 6 +++--- utils.py | 14 +++++++------- 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/classes/election_cycle.py b/classes/election_cycle.py index cdda7c2..2a8e49d 100644 --- a/classes/election_cycle.py +++ b/classes/election_cycle.py @@ -3,12 +3,9 @@ Contains the election cycle class """ import collections -from datetime import datetime, timedelta +from datetime import UTC, datetime, timedelta import nextcord -from sqlalchemy import select, true, update -from sqlalchemy.orm import Session - from classes.nominate_highcouncil_views import NominateHighCouncilView from classes.nominate_views import NominateView from classes.voting_highcouncil_views import VoteHighcouncilView @@ -20,6 +17,8 @@ from database_setup.sql_data_classes import ( Votes, engine, ) +from sqlalchemy import select, true, update +from sqlalchemy.orm import Session from utils import ( TEMPLATES_DIR, VoteType, @@ -143,7 +142,7 @@ class ElectionCycle: """ Function checks current date to see if something needs to be run. """ - today = datetime.today() + today = datetime.now(tz=UTC).date() # check all dates if action is needed if today.date() == self.election_start.date(): diff --git a/cogs/commands/set.py b/cogs/commands/set.py index f938ff4..30f8f9c 100644 --- a/cogs/commands/set.py +++ b/cogs/commands/set.py @@ -2,7 +2,7 @@ Contains commands and views for set commands. """ -from datetime import datetime +from datetime import UTC, datetime import nextcord from nextcord.ext import commands @@ -57,7 +57,7 @@ class setCog(commands.Cog, name="Set Commands"): return # check if valid day - today = datetime.today() + today = datetime.now(tz=UTC).date() try: birthday = datetime(today.year, month, day) except ValueError: diff --git a/cogs/loops/daily_loop.py b/cogs/loops/daily_loop.py index ccc19c4..e3d7c08 100644 --- a/cogs/loops/daily_loop.py +++ b/cogs/loops/daily_loop.py @@ -3,7 +3,7 @@ Defines the daily loop that runs the Dakiny bot functions. """ import random -from datetime import datetime, time +from datetime import UTC, datetime, time import nextcord from nextcord.ext import commands, tasks @@ -92,7 +92,7 @@ class DailyLoop(commands.Cog, name="Daily Loop"): if random.randint(0, 99) == 0: birthday_list.append(239547993106284544) - today = datetime.today() + today = datetime.now(tz=UTC).date() birthday = f"{today.month}/{today.day}" stmt = select(UserBirthday.UserId).where(UserBirthday.Birthday == birthday) @@ -105,7 +105,7 @@ class DailyLoop(commands.Cog, name="Daily Loop"): Loops through the guild's humans checking for joined_at dates that match today's date. Sends a message saying happy cakeday. """ - today = datetime.today() + today = datetime.now(tz=UTC).date() guild: nextcord.Guild = self.bot.get_guild(self.bot.config.params.guild_id) announce_channel: nextcord.TextChannel = guild.get_channel(self.bot.config.params.general_channel_id) diff --git a/utils.py b/utils.py index 1f903ad..a43c969 100644 --- a/utils.py +++ b/utils.py @@ -1,9 +1,9 @@ """File contains util functions.""" import calendar -import datetime import os from collections.abc import Sequence +from datetime import UTC, datetime from enum import StrEnum from pathlib import Path @@ -30,7 +30,7 @@ TEMPLATES_DIR = f"{REPO_DIR}/message_templates" VoteType = StrEnum("VoteType", ["COUNCIL", "DISCORDMANAGER", "CLANLEADER"]) -def get_datetime(day: str) -> datetime.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. @@ -40,7 +40,7 @@ def get_datetime(day: str) -> datetime.datetime: Returns: datetime: Datetime representation of Month/Day. """ - today = datetime.datetime.now(tz=datetime.UTC).date() + today = datetime.now(tz=UTC).date() year = today.year month = int(day.split("/")[0]) day = int(day.split("/")[1]) @@ -52,7 +52,7 @@ def get_datetime(day: str) -> datetime.datetime: if today.month == calendar.JANUARY and month > calendar.JANUARY: year -= 1 - return datetime.datetime(year, month, day, tzinfo=datetime.UTC) + return datetime(year, month, day, tzinfo=UTC) def get_election_name(config: DakinyConfig) -> str: @@ -96,7 +96,7 @@ def get_current_election_cycle(config: DakinyConfig) -> int: Returns: int: cycle index for active election. None for no active cycle """ - today = datetime.datetime.now(tz=datetime.UTC).date() + today = datetime.now(tz=UTC).date() for cycle, election in enumerate(config.election_cycles): if ( @@ -119,7 +119,7 @@ def get_quarter_cycle(config: DakinyConfig) -> int: Returns: int: cycle index for current cycle. """ - today = datetime.datetime.now(tz=datetime.UTC).date() + today = datetime.now(tz=UTC).date() for cycle, election in enumerate(config.election_cycles): if ( today.date() >= get_datetime(election.term_start).date() @@ -357,7 +357,7 @@ def get_allowed_points(session: Session, points_limit: int, user: str, points: i int: how many points are allowed to be given to the user. """ # check for limit - today = datetime.datetime.now(tz=datetime.UTC).date() + today = datetime.now(tz=UTC).date() this_week = f"{today.isocalendar().year}-{today.isocalendar().week}" stmt = select(EventPointsLimit).where(EventPointsLimit.UserId == user).where(EventPointsLimit.Week == this_week) From e485c119f61367a0f8438726a415a22b65dcb7e0 Mon Sep 17 00:00:00 2001 From: imcompiling Date: Wed, 22 May 2024 16:33:09 -0400 Subject: [PATCH 09/17] Fix database_setup and main lint smells. --- database_setup/create_database.py | 10 ++++------ database_setup/pyproject.toml | 7 +++++++ database_setup/sql_data_classes.py | 4 +--- main.py | 23 ++++++++++++++--------- 4 files changed, 26 insertions(+), 18 deletions(-) create mode 100644 database_setup/pyproject.toml diff --git a/database_setup/create_database.py b/database_setup/create_database.py index 912f729..612f614 100644 --- a/database_setup/create_database.py +++ b/database_setup/create_database.py @@ -1,15 +1,13 @@ -""" -Small script to create the database if it doesn't already exist. -""" +"""Small script to create the database if it doesn't already exist.""" import sqlite3 -from os.path import exists +from pathlib import Path -if not exists("database/dakiny.db"): +if not Path("database/dakiny.db").exists(): with sqlite3.connect("database/dakiny.db") as con: cur = con.cursor() - with open("database_setup/database.sql", encoding="utf-8") as file: + with Path("database_setup/database.sql", encoding="utf-8").open() as file: database_script = file.read() cur.executescript(database_script) con.commit() diff --git a/database_setup/pyproject.toml b/database_setup/pyproject.toml new file mode 100644 index 0000000..e3c5aa1 --- /dev/null +++ b/database_setup/pyproject.toml @@ -0,0 +1,7 @@ +[tool.ruff] +# Extend the `pyproject.toml` file in the parent directory... +extend = "../pyproject.toml" + +[tool.ruff.lint] +# ignore implicit-namespace-package in this directory. +ignore = ["INP001"] diff --git a/database_setup/sql_data_classes.py b/database_setup/sql_data_classes.py index 67411f3..6bdd89c 100644 --- a/database_setup/sql_data_classes.py +++ b/database_setup/sql_data_classes.py @@ -1,6 +1,4 @@ -""" -Simple file to set up the sqlalchemy connection to database. -""" +"""Simple file to set up the sqlalchemy connection to database.""" from sqlalchemy import create_engine from sqlalchemy.ext.automap import automap_base diff --git a/main.py b/main.py index 36e4d99..7aab9bd 100644 --- a/main.py +++ b/main.py @@ -1,3 +1,5 @@ +"""Main file for bot.""" + import logging import os import sys @@ -27,7 +29,13 @@ logger.addHandler(handler) @hydra.main(config_path="conf", config_name="config") -def main(cfg: DakinyConfig): +def main(cfg: DakinyConfig) -> None: + """ + Builds and runs bot. + + Args: + cfg (DakinyConfig): Passed in hydra configuration. + """ # Allows privledged intents for monitoring members joining, roles editing, and role assignments # These need to be enabled in the developer portal as well intents = nextcord.Intents.default() @@ -35,9 +43,6 @@ def main(cfg: DakinyConfig): # Required in order to read messages (eg. prefix commands) intents.message_content = True - # To enable the guilds priveleged intent: - # intents.guilds = True - # To enable the members priveleged intent: intents.members = True @@ -49,15 +54,15 @@ def main(cfg: DakinyConfig): bot.load_extension(f"cogs.{folder}") @bot.listen() - async def on_ready(): - """When Discord is connected""" - assert bot.user is not None - create_database # pylint: disable=pointless-statement + async def on_ready() -> None: + """When Discord is connected.""" + assert bot.user is not None # noqa: S101 documentation does this + create_database # noqa: B018 bot.add_view(NominateView(cfg, bot.get_guild(cfg.params.guild_id))) bot.add_view(NominateHighCouncilView(cfg, bot.get_guild(cfg.params.guild_id))) bot.add_view(VoteView(cfg, bot.get_guild(cfg.params.guild_id))) bot.add_view(VoteHighcouncilView(cfg, bot.get_guild(cfg.params.guild_id))) - print(f"{bot.user.name} has connected to Discord!") + print(f"{bot.user.name} has connected to Discord!") # noqa: T201 TODO make logging to print my stuff to console # Run Discord bot bot.run(bot.config.params.discord_token) From 6cd82dfcc4a41e66b8032119022d8f0cbbeb8a87 Mon Sep 17 00:00:00 2001 From: imcompiling Date: Wed, 22 May 2024 16:58:27 -0400 Subject: [PATCH 10/17] Fix ruff smells in classes dir and config.py. --- classes/election_cycle.py | 45 +++++++++++------------- classes/nominate_highcouncil_views.py | 33 ++++++++++-------- classes/nominate_views.py | 23 ++++++------- classes/pyproject.toml | 3 ++ classes/voting_highcouncil_views.py | 49 ++++++++++++++------------- config.py | 16 +++------ 6 files changed, 81 insertions(+), 88 deletions(-) diff --git a/classes/election_cycle.py b/classes/election_cycle.py index 2a8e49d..461ebd2 100644 --- a/classes/election_cycle.py +++ b/classes/election_cycle.py @@ -1,9 +1,8 @@ -""" -Contains the election cycle class -""" +"""Contains the election cycle class.""" import collections from datetime import UTC, datetime, timedelta +from pathlib import Path import nextcord from classes.nominate_highcouncil_views import NominateHighCouncilView @@ -29,9 +28,13 @@ from utils import ( get_election_name, ) +REQUIRED_VOTES_TO_BE_ELECTED = 3 + class ElectionCycle: """ + ElectionCycle class. + This class represents all the dates required for an election cycle along with all the functions needed to operate the cycle. """ @@ -67,7 +70,7 @@ class ElectionCycle: list[str]: A list of message contents in the correct order. Must be a string because the message is too long. """ - with open(f"{TEMPLATES_DIR}/election_announcement.txt", encoding="utf8") as file: + with Path(f"{TEMPLATES_DIR}/election_announcement.txt", encoding="utf8").open() as file: message = file.read() next_election = get_datetime( @@ -95,7 +98,7 @@ class ElectionCycle: Returns: str: Message to send. """ - with open(f"{TEMPLATES_DIR}/highcouncil_election.txt", encoding="utf8") as file: + with Path(f"{TEMPLATES_DIR}/highcouncil_election.txt", encoding="utf8").open() as file: message = file.read() return message.format( @@ -113,7 +116,7 @@ class ElectionCycle: Returns: str: Message to send. """ - with open(f"{TEMPLATES_DIR}/highcouncil_vote.txt", encoding="utf8") as file: + with Path(f"{TEMPLATES_DIR}/highcouncil_vote.txt", encoding="utf8").open() as file: message = file.read() return message.format( @@ -128,7 +131,7 @@ class ElectionCycle: Returns: str: Message to send """ - with open(f"{TEMPLATES_DIR}/vote_start.txt", encoding="utf8") as file: + with Path(f"{TEMPLATES_DIR}/vote_start.txt", encoding="utf8").open() as file: message = file.read() vote_end: datetime = get_datetime(self.config.election_cycles[self.cycle].voting_end) @@ -139,9 +142,7 @@ class ElectionCycle: ) async def check_date(self) -> None: - """ - Function checks current date to see if something needs to be run. - """ + """Function checks current date to see if something needs to be run.""" today = datetime.now(tz=UTC).date() # check all dates if action is needed @@ -159,9 +160,7 @@ class ElectionCycle: await self.term_start_action() async def election_start_action(self) -> None: - """ - Performs required actions for when the election cycle starts. - """ + """Performs required actions for when the election cycle starts.""" # create new channel for election parent: nextcord.TextChannel = self.guild.get_channel(self.config.params.voting_parent_id) quarter_name: str = get_election_name(self.config) @@ -193,6 +192,8 @@ class ElectionCycle: async def voting_start_action(self) -> None: """ + Actions performed when starting a vote. + Sends message informing members that voting has started. Removes write permission from the general clan member role. """ @@ -217,9 +218,7 @@ class ElectionCycle: await nominate_msg.edit(view=None) async def voting_end_action(self) -> None: - """ - Performs required actions for when voting time has ended. - """ + """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_thread: nextcord.Thread = self.guild.get_thread(self.get_election_info().ThreadId) @@ -250,7 +249,7 @@ class ElectionCycle: vote_count = collections.Counter(session.scalars(stmt)) for user_id, votes in vote_count.items(): - if votes >= 3: + if votes >= REQUIRED_VOTES_TO_BE_ELECTED: stmt = ( select(CouncilMembers) .where(CouncilMembers.ElectionCycle == election_name) @@ -268,9 +267,7 @@ class ElectionCycle: ) async def highcouncil_election_start_action(self) -> None: - """ - Performs required actions for when the highcouncil is elected. - """ + """Performs required actions for when the highcouncil is elected.""" message = self.build_highcouncil_vote_start() election_thread: nextcord.Thread = self.guild.get_thread(self.get_election_info().ThreadId) await election_thread.send(message) @@ -292,9 +289,7 @@ class ElectionCycle: await nomination_msg.edit(view=None) async def term_start_action(self) -> None: - """ - Marks the election as over. - """ + """Marks the election as over.""" self.election_over = True elect_role: nextcord.Role = self.guild.get_role(self.config.params.clan_council_elect_role_id) @@ -394,9 +389,7 @@ class ElectionCycle: return session.scalar(stmt) async def election_reminder(self) -> None: - """ - Sends a message to general chat reminder clan about election. - """ + """Sends a message to general chat reminder clan about election.""" general: nextcord.TextChannel = self.guild.get_channel(self.config.params.general_channel_id) message = f"Reminder to <@&{self.config.params.clan_member_role_id}>! Clan elections are happening! Head over " diff --git a/classes/nominate_highcouncil_views.py b/classes/nominate_highcouncil_views.py index 49e829f..9fdbcc8 100644 --- a/classes/nominate_highcouncil_views.py +++ b/classes/nominate_highcouncil_views.py @@ -1,14 +1,11 @@ -""" -Contains various views to be used in the high council nomination process. -""" +"""Contains various views to be used in the high council nomination process.""" import nextcord +from config import DakinyConfig +from database_setup.sql_data_classes import CouncilMembers, engine from nextcord.interactions import Interaction from sqlalchemy import select from sqlalchemy.orm import Session - -from config import DakinyConfig -from database_setup.sql_data_classes import CouncilMembers, engine from utils import ( NOMINEE_SPLITTER, decorate_cl_nomination, @@ -19,14 +16,16 @@ from utils import ( class NominateDMModal(nextcord.ui.Modal): """ - Modal for nominating oneself. User writes a speech and then the callback sends the speech into the correct channel. + Modal for nominating oneself. + + User writes a speech and then the callback sends the speech into the correct channel. """ def __init__( self, config: DakinyConfig, usr_id: int, - usr_nominated: bool, + usr_nominated: bool, # noqa: FBT001 speech_content: str, ): super().__init__( @@ -79,14 +78,16 @@ class NominateDMModal(nextcord.ui.Modal): class NominateCLModal(nextcord.ui.Modal): """ - Modal for nominating oneself. User writes a speech and then the callback sends the speech into the correct channel. + Modal for nominating oneself. + + User writes a speech and then the callback sends the speech into the correct channel. """ def __init__( self, config: DakinyConfig, usr_id: int, - usr_nominated: bool, + usr_nominated: bool, # noqa: FBT001 speech_content: str, ): super().__init__( @@ -138,9 +139,7 @@ class NominateCLModal(nextcord.ui.Modal): class NominateHighCouncilView(nextcord.ui.View): - """ - The buttons to press to start your High Council nomination. - """ + """The buttons to press to start your High Council nomination.""" def __init__(self, config: DakinyConfig, guild: nextcord.Guild): super().__init__(timeout=None) @@ -152,8 +151,10 @@ class NominateHighCouncilView(nextcord.ui.View): style=nextcord.ButtonStyle.primary, custom_id="nominate-dm", ) - async def nominate_dm(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: + async def nominate_dm(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: # noqa: ARG002 """ + Button for nominating oneself for Discord Manager. + Gathers previous information about the user if they have already nominated themselves, then launches the nomination modal to get their speech. @@ -195,8 +196,10 @@ class NominateHighCouncilView(nextcord.ui.View): style=nextcord.ButtonStyle.primary, custom_id="nominate-cl", ) - async def nominate_cl(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: + async def nominate_cl(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: # noqa: ARG002 """ + Button for nominating oneself for Clan Leader. + Gathers previous information about the user if they have already nominated themselves, then launches the nomination modal to get their speech. diff --git a/classes/nominate_views.py b/classes/nominate_views.py index 8211288..b6d8578 100644 --- a/classes/nominate_views.py +++ b/classes/nominate_views.py @@ -1,27 +1,26 @@ -""" -Contains various views to be used in the nomination process. -""" +"""Contains various views to be used in the nomination process.""" import nextcord +from config import DakinyConfig +from database_setup.sql_data_classes import CouncilMembers, engine from nextcord.interactions import Interaction from sqlalchemy import select 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_name class NominateModal(nextcord.ui.Modal): """ - Modal for nominating oneself. User writes a speech and then the callback sends the speech into the correct channel. + Modal for nominating oneself. + + User writes a speech and then the callback sends the speech into the correct channel. """ def __init__( self, config: DakinyConfig, usr_id: int, - usr_nominated: bool, + usr_nominated: bool, # noqa: FBT001 speech_content: str, ): super().__init__( @@ -78,9 +77,7 @@ class NominateModal(nextcord.ui.Modal): class NominateView(nextcord.ui.View): - """ - The button to press to start your nomination. - """ + """The button to press to start your nomination.""" def __init__(self, config: DakinyConfig, guild: nextcord.Guild): super().__init__(timeout=None) @@ -92,8 +89,10 @@ class NominateView(nextcord.ui.View): style=nextcord.ButtonStyle.primary, custom_id="nominate", ) - async def nominate(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: + async def nominate(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: # noqa: ARG002 """ + Button to nominate onself for Clan Council. + Gathers previous information about the user if they have already nominated themselves, then launches the nomination modal to get their speech. diff --git a/classes/pyproject.toml b/classes/pyproject.toml index e3c5aa1..357e23b 100644 --- a/classes/pyproject.toml +++ b/classes/pyproject.toml @@ -2,6 +2,9 @@ # Extend the `pyproject.toml` file in the parent directory... extend = "../pyproject.toml" +# Exclude a variety of commonly ignored directories. +exclude = ["gitea_webhook.py"] + [tool.ruff.lint] # ignore implicit-namespace-package in this directory. ignore = ["INP001"] diff --git a/classes/voting_highcouncil_views.py b/classes/voting_highcouncil_views.py index a0e9350..f35677c 100644 --- a/classes/voting_highcouncil_views.py +++ b/classes/voting_highcouncil_views.py @@ -1,14 +1,6 @@ -""" -Contains various views to be used while voting for high council. Controls the logic when performing the vote. -""" - +"""Contains various views to be used while voting for high council. Controls the logic when performing the vote.""" import nextcord -from nextcord import SelectOption -from nextcord.interactions import Interaction -from sqlalchemy import exc, select, update -from sqlalchemy.orm import Session - from config import DakinyConfig from database_setup.sql_data_classes import ( CouncilMembers, @@ -16,10 +8,16 @@ from database_setup.sql_data_classes import ( Votes, engine, ) +from nextcord import SelectOption +from nextcord.interactions import Interaction +from sqlalchemy import exc, select, update +from sqlalchemy.orm import Session from utils import VoteType, build_highcouncil_votes_str, get_election_name class VoteSelect(nextcord.ui.StringSelect): + """Vote view string select.""" + def __init__(self, options: list["SelectOption"], config: DakinyConfig, election_type: str) -> None: super().__init__( placeholder="Choose 1", @@ -30,7 +28,16 @@ class VoteSelect(nextcord.ui.StringSelect): self.config = config self.election_type = election_type - async def callback(self, interaction: Interaction): + async def callback(self, interaction: Interaction) -> None: + """ + Callback override for the StringSelect. + + Submits the High Council vote to database if a user is selected. Handles adding vs editing a vote. + Returns message informing user that vote has been recorded. + + Args: + interaction (Interaction): Interaction with this callback. + """ election_name: str = get_election_name(self.config) with Session(engine) as session: stmt = select(ElectionInformation.HighCouncilVoteMsgId).where( @@ -38,10 +45,7 @@ class VoteSelect(nextcord.ui.StringSelect): ) vote_msg_id = session.scalar(stmt) - if self.values[0] == "Abstain": - voted_for = None - else: - voted_for = int(self.values[0]) + voted_for = None if self.values[0] == "Abstain" else int(self.values[0]) new_vote = Votes( ElectionCycle=election_name, @@ -71,18 +75,13 @@ class VoteSelect(nextcord.ui.StringSelect): vote_msg: nextcord.Message = await interaction.channel.fetch_message(vote_msg_id) await vote_msg.edit(content=vote_message_str) - if voted_for is not None: - msg = f"You have voted for <@{voted_for}>" - else: - msg = "You have abstained from voting." + msg = f"You have voted for <@{voted_for}>" if voted_for is not None else "You have abstained from voting." await interaction.edit(content=msg, view=None) class VoteHighcouncilView(nextcord.ui.View): - """ - The button to press to start voting for HighCouncil. - """ + """The button to press to start voting for HighCouncil.""" def __init__(self, config: DakinyConfig, guild: nextcord.Guild): super().__init__(timeout=None) @@ -94,8 +93,10 @@ class VoteHighcouncilView(nextcord.ui.View): style=nextcord.ButtonStyle.primary, custom_id="vote-cl", ) - async def vote_cl(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: + async def vote_cl(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: # noqa: ARG002 """ + Button to vote for Clan Leader. + Enforces only council elect vote. Gets election cycle, creates voting options for clan leader (checking if user has voted already), then sends the voting view. @@ -164,8 +165,10 @@ class VoteHighcouncilView(nextcord.ui.View): style=nextcord.ButtonStyle.primary, custom_id="vote-dm", ) - async def vote_dm(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: + async def vote_dm(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: # noqa: ARG002 """ + Button to vote for Discord Manager. + Enforces only council elect vote. Gets election cycle, creates voting options for discord manager (checking if user has voted already), then sends the voting view. diff --git a/config.py b/config.py index 84b2144..c7746da 100644 --- a/config.py +++ b/config.py @@ -1,15 +1,11 @@ -""" -File contains classes defining the Hydra configuraton structure. -""" +"""File contains classes defining the Hydra configuraton structure.""" from dataclasses import dataclass @dataclass class Params: - """ - Class defines the various configuration parameters. - """ + """Class defines the various configuration parameters.""" admin_channel_id: int # might want to rename this... announcement_category_id: int @@ -32,9 +28,7 @@ class Params: @dataclass class ElectionCycle: - """ - Class defines relevant election dates. - """ + """Class defines relevant election dates.""" election_start: str highcouncil_election_announce: str @@ -49,9 +43,7 @@ class ElectionCycle: @dataclass class DakinyConfig: - """ - Class defines all configs required for Dakiny Bot. - """ + """Class defines all configs required for Dakiny Bot.""" election_cycles: list[ElectionCycle] params: Params From d9914529ca1f87e018cdfaed7f65855e1aed7406 Mon Sep 17 00:00:00 2001 From: imcompiling Date: Wed, 22 May 2024 17:30:01 -0400 Subject: [PATCH 11/17] Fix Path().is_dir() usage. --- utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils.py b/utils.py index a43c969..c2dd878 100644 --- a/utils.py +++ b/utils.py @@ -396,7 +396,7 @@ async def update_clan_messages(guild: nextcord.Guild, channel_updates: Sequence[ 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 channel_updates if Path.is_dir(f"{MESSAGES_DIR}/{dir_name}")] + dirs = [dir_name for dir_name in channel_updates if Path(f"{MESSAGES_DIR}/{dir_name}").is_dir()] with Session(engine) as session: for dir_name in dirs: From 6500f68d86910316506190d5c4503a47b4190ac0 Mon Sep 17 00:00:00 2001 From: imcompiling Date: Wed, 22 May 2024 17:35:43 -0400 Subject: [PATCH 12/17] Start working on files in cogs. --- cogs/clan_messages/__init__.py | 45 +++++++++++++----------- cogs/commands/__init__.py | 10 ++---- cogs/commands/record.py | 58 +++++++++++++------------------ cogs/loops/__init__.py | 10 ++---- cogs/loops/bot_presence.py | 15 ++++---- cogs/loops/daily_loop.py | 52 ++++++++++++++------------- cogs/message_listener/__init__.py | 10 +++--- pyproject.toml | 4 ++- 8 files changed, 95 insertions(+), 109 deletions(-) diff --git a/cogs/clan_messages/__init__.py b/cogs/clan_messages/__init__.py index 33326a2..5e3d480 100644 --- a/cogs/clan_messages/__init__.py +++ b/cogs/clan_messages/__init__.py @@ -1,26 +1,30 @@ -""" -File contains logic for creating a webhook listener, pulling from git, and updating all messages. -""" +"""File contains logic for creating a webhook listener, pulling from git, and updating all messages.""" -import os +from pathlib import Path +from typing import TYPE_CHECKING -import nextcord import uvicorn 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, edit_clan_council_info, update_clan_messages +if TYPE_CHECKING: + import nextcord + + from config import DakinyConfig + app = FastAPI() REPO_URL = "https://gitea.cheezers.org/imcompiling/dakiny-messages.git" class ClanMessages(commands.Cog, name="Clan Messages"): """ - Cog to be loaded into the bot. Used to be able to have a fastapi post listener and access to the bot at + Cog to be loaded into the bot. + + Used to be able to have a fastapi post listener and access to the bot at the same time. """ @@ -30,23 +34,24 @@ class ClanMessages(commands.Cog, name="Clan Messages"): self.repo_setup() @commands.Cog.listener() - async def on_ready(self): - """ - Triggers when the bot is in ready state. Adds the webhook listener to FastAPI and serves. - """ + async def on_ready(self) -> None: + """Triggers when the bot is in ready state. Adds the webhook listener to FastAPI and serves.""" app.add_api_route("/messages/", self.update_messages, methods=["POST"]) - config = uvicorn.Config(app=app, host="0.0.0.0", port=5000, log_level="info") + # Bind to all interfaces, don't know the docker interface ahead of time. + config = uvicorn.Config(app=app, host="0.0.0.0", port=5000, log_level="info") # noqa: S104 server = uvicorn.Server(config) await server.serve() - async def update_messages(self, hook_info: GiteaWebhook): + async def update_messages(self, hook_info: GiteaWebhook) -> dict: """ + Triggered by webhook. + 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 + file for files in [commit.modified + commit.added for commit in hook_info.commits] for file in files ] template_updates = {file.split("/")[1] for file in modified_files if "message_templates" in file} @@ -63,12 +68,10 @@ class ClanMessages(commands.Cog, name="Clan Messages"): return {"message": "Successfully updated all discord messages."} - def repo_setup(self): - """ - Checks if the repo exists and creates repo object, clones if it does not. - """ + def repo_setup(self) -> None: + """Checks if the repo exists and creates repo object, clones if it does not.""" # check if repo has been cloned. open repo object - if os.path.isdir(REPO_DIR): + if Path(REPO_DIR).is_dir(): self.repo = Repo(REPO_DIR) else: self.repo = Repo.clone_from(REPO_URL, REPO_DIR) @@ -85,6 +88,6 @@ class ClanMessages(commands.Cog, name="Clan Messages"): # 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 function to bot""" +def setup(bot: commands.Bot) -> None: + """Adds function to bot.""" bot.add_cog(ClanMessages(bot)) diff --git a/cogs/commands/__init__.py b/cogs/commands/__init__.py index 6dab456..dff21a5 100644 --- a/cogs/commands/__init__.py +++ b/cogs/commands/__init__.py @@ -1,6 +1,4 @@ -""" -Cog loader. Adds all command cogs to the bot. -""" +"""Cog loader. Adds all command cogs to the bot.""" from nextcord.ext import commands @@ -15,10 +13,8 @@ from cogs.commands.view import ViewCog # 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. - """ +def setup(bot: commands.Bot) -> None: + """Adds all command classes to bot.""" bot.add_cog(RecordCog(bot)) bot.add_cog(RefreshCog(bot)) bot.add_cog(RemoveCog()) diff --git a/cogs/commands/record.py b/cogs/commands/record.py index 93fa223..765bb40 100644 --- a/cogs/commands/record.py +++ b/cogs/commands/record.py @@ -1,6 +1,4 @@ -""" -Contained commands and views for record commands. -""" +"""Contained commands and views for record commands.""" import nextcord from discord import TextInputStyle @@ -11,22 +9,16 @@ from utils import record_event_points class RecordCog(commands.Cog, name="Record Commands"): - """ - Class holds record commands. - """ + """Class holds record commands.""" - def __init__(self, bot) -> None: - """ - Default constructor for RecordCog class. Does nothing. - """ + def __init__(self, bot: commands.Bot) -> None: + """Default constructor for RecordCog class. Does nothing.""" super().__init__() self.bot: commands.Bot = bot @nextcord.slash_command() - async def record(self, interaction: nextcord.Interaction): - """ - Parent slash command for record. Never called. - """ + async def record(self, interaction: nextcord.Interaction) -> None: + """Parent slash command for record. Never called.""" @record.subcommand(description="Record an event and its participants.") async def event( @@ -37,8 +29,10 @@ class RecordCog(commands.Cog, name="Record Commands"): required=False, description="An alternative host if you are recording event for someone else.", ), - ): + ) -> None: """ + Create and send recording event. + This command creates the base event recording embed and sends a message with it and the EventView for gathering more information on the event. @@ -73,7 +67,9 @@ class RecordCog(commands.Cog, name="Record Commands"): class EventView(nextcord.ui.View): """ - Class handles gathering information on an event. Has buttons to edit the information, cancel, and submit. + Class handles gathering information on an event. + + Has buttons to edit the information, cancel, and submit. Also contains a UserSelect object to pick discord members. """ @@ -89,33 +85,31 @@ class EventView(nextcord.ui.View): self.add_item(self.event_participants) @nextcord.ui.button(label="Cancel", style=nextcord.ButtonStyle.red, row=1) - async def cancel(self, button: nextcord.ui.Button, interaction: nextcord.Interaction): - """ - Cancels event submission. Removes embed and view. - """ + async def cancel(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: # noqa: ARG002 + """Cancels event submission. Removes embed and view.""" await interaction.response.edit_message("Canceled", ephemeral=True, embed=None, view=None) self.stop() @nextcord.ui.button(label="Edit", style=nextcord.ButtonStyle.blurple, row=1) - async def edit(self, button: nextcord.ui.Button, interaction: nextcord.Interaction): - """ - Calls modal to update the text input information of the event. - """ + async def edit(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: # noqa: ARG002 + """Calls modal to update the text input information of the event.""" await interaction.response.send_modal(ParticipantModal(interaction.message.embeds[0])) @nextcord.ui.button(label="Confirm", style=nextcord.ButtonStyle.green, row=1) - async def confirm(self, button: nextcord.ui.Button, interaction: nextcord.Interaction): + async def confirm(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: # noqa: ARG002 """ - Submits the event embed to the event channel. Removes view and embed from recording message. + Submits the event embed to the event channel. + + Removes view and embed from recording message. Sends point information to database. """ - discord_users = self.event_participants.values.members + discord_users = self.event_participants.values.members # noqa: PD011 not a pandas object if discord_users: discord_users_list: list[str] = [str(user.id) for user in discord_users if user.id is not self.host_id] record_event_points( self.config, discord_users_list, - True, + True, # noqa: FBT003 self.config.params.event_participate_points, ) @@ -175,7 +169,7 @@ class ParticipantModal(nextcord.ui.Modal): self.add_item(self.description) self.add_item(self.participants) - async def callback(self, interaction: nextcord.Interaction): + async def callback(self, interaction: nextcord.Interaction) -> None: self.embed.title = self.name.value self.embed.description = self.description.value self.embed.set_field_at(index=1, name="Non-Discord Participants", value=self.participants.value) @@ -184,9 +178,7 @@ class ParticipantModal(nextcord.ui.Modal): class ParticipantSelect(nextcord.ui.UserSelect): - """ - A selection drop down for discord members. Updates the embed with all users selected. - """ + """A selection drop down for discord members. Updates the embed with all users selected.""" def __init__(self) -> None: super().__init__( @@ -196,7 +188,7 @@ class ParticipantSelect(nextcord.ui.UserSelect): row=0, ) - async def callback(self, interaction: nextcord.Interaction): + async def callback(self, interaction: nextcord.Interaction) -> None: embed: nextcord.Embed = interaction.message.embeds[0] participants: str = "\n".join([f"<@{participant.id}>" for participant in self.values.members]) embed.set_field_at(0, name="Participants", value=participants) diff --git a/cogs/loops/__init__.py b/cogs/loops/__init__.py index 3b1e442..56102bf 100644 --- a/cogs/loops/__init__.py +++ b/cogs/loops/__init__.py @@ -1,6 +1,4 @@ -""" -Initializing code for all loops. -""" +"""Initializing code for all loops.""" from nextcord.ext import commands @@ -8,9 +6,7 @@ from cogs.loops.bot_presence import BotPresence from cogs.loops.daily_loop import DailyLoop -def setup(bot: commands.Bot): - """ - Adds functions to bot. - """ +def setup(bot: commands.Bot) -> None: + """Adds functions to bot.""" bot.add_cog(BotPresence(bot)) bot.add_cog(DailyLoop(bot)) diff --git a/cogs/loops/bot_presence.py b/cogs/loops/bot_presence.py index 6dc3f30..b9dbaa9 100644 --- a/cogs/loops/bot_presence.py +++ b/cogs/loops/bot_presence.py @@ -1,6 +1,4 @@ -""" -Functions for controlling the bot's presence. Also contains the loop. -""" +"""Functions for controlling the bot's presence. Also contains the loop.""" import nextcord from nextcord.ext import commands, tasks @@ -17,23 +15,22 @@ class BotPresence(commands.Cog, name="Bot Presence"): self.bot = bot @commands.Cog.listener() - async def on_guild_join(self, guild: nextcord.Guild): # pylint: disable=unused-argument + async def on_guild_join(self, guild: nextcord.Guild) -> None: # noqa: ARG002 """Listener for Guild Join. Updated presence on guild join.""" await self.change_presence() @commands.Cog.listener() - async def on_ready(self): + async def on_ready(self) -> None: """Starts the loop when bot is ready.""" await self.bot.wait_until_ready() - # pylint doesn't recognize that periodic_change_presense is subclassed from tasks.loop. - self.periodic_change_presence.start() # pylint: disable=no-member + self.periodic_change_presence.start() @tasks.loop(minutes=5) - async def periodic_change_presence(self): + async def periodic_change_presence(self) -> None: """Loops every 5 minutes and changes presence.""" await self.change_presence() - async def change_presence(self): + async def change_presence(self) -> None: """Randomly chooses a presence and updates bot's presence.""" with Session(engine) as session: presence = session.scalar(select(Presences.Presence).order_by(func.random())) diff --git a/cogs/loops/daily_loop.py b/cogs/loops/daily_loop.py index e3d7c08..9cdd1be 100644 --- a/cogs/loops/daily_loop.py +++ b/cogs/loops/daily_loop.py @@ -1,27 +1,29 @@ -""" -Defines the daily loop that runs the Dakiny bot functions. -""" +"""Defines the daily loop that runs the Dakiny bot functions.""" import random from datetime import UTC, datetime, time +from typing import TYPE_CHECKING -import nextcord from nextcord.ext import commands, tasks from pytz import timezone from sqlalchemy import select from sqlalchemy.orm import Session from classes.election_cycle import ElectionCycle -from config import DakinyConfig from database_setup.sql_data_classes import UserBirthday, engine from main import birthday_list from utils import get_current_election_cycle +if TYPE_CHECKING: + import nextcord + + from config import DakinyConfig + +KANDY = 239547993106284544 + class DailyLoop(commands.Cog, name="Daily Loop"): - """ - Class for performing daily functions. - """ + """Class for performing daily functions.""" def __init__(self, bot: commands.Bot) -> None: self.bot = bot @@ -31,10 +33,12 @@ class DailyLoop(commands.Cog, name="Daily Loop"): # pylint doesn't recognize that daily_loop is subclassed from tasks.loop. self.daily_loop.start() # pylint: disable=no-member - @tasks.loop(time=time(hour=0, tzinfo=timezone("GMT"))) - async def daily_loop(self): + @tasks.loop(time=time(hour=0, tzinfo=timezone("UTC"))) + async def daily_loop(self) -> None: """ - Runs daily. Checks if the current election is None. If so, checks if there is an active election. If there is + Runs daily. + + Checks if the current election is None. If so, checks if there is an active election. If there is an active election, runs the check_date() function for the election to perform actions. Finally checks if the election is over to delete the current election object. """ @@ -48,18 +52,16 @@ class DailyLoop(commands.Cog, name="Daily Loop"): self.update_birthdays() - if 239547993106284544 in birthday_list: + if KANDY in birthday_list: # inform kandy that it is her "birthday" - kandy: nextcord.User = await self.bot.fetch_user(239547993106284544) + kandy: nextcord.User = await self.bot.fetch_user(KANDY) await kandy.send('Happy "Birthday"!') await self.cakedays() @daily_loop.before_loop - async def before_loop(self): - """ - Actions run before the loop starts. Want to wait for bot to be ready and get the current cycle. - """ + async def before_loop(self) -> None: + """Actions run before the loop starts. Want to wait for bot to be ready and get the current cycle.""" await self.bot.wait_until_ready() # Bot needs to know guilds to get the current cycle. @@ -67,8 +69,9 @@ class DailyLoop(commands.Cog, name="Daily Loop"): def get_election_cycle(self) -> ElectionCycle: """ - Creates an object for the current election cycle if one is active. An election is considered active - between the election start and the term start. + Creates an object for the current election cycle if one is active. + + An election is considered active between the election start and the term start. Returns: ElectionCycle: Current election cycle, None if there is no election happening. @@ -83,13 +86,11 @@ class DailyLoop(commands.Cog, name="Daily Loop"): return ElectionCycle(cycle, config, self.bot.get_guild(config.params.guild_id)) def update_birthdays(self) -> None: - """ - Goes through database to collect all birthdays that are today. Adds user's id to birthday list. - """ + """Goes through database to collect all birthdays that are today. Adds user's id to birthday list.""" birthday_list.clear() # kandy's 1% chance - if random.randint(0, 99) == 0: + if random.randint(0, 99) == 0: # noqa: S311 isn't cryptography related. birthday_list.append(239547993106284544) today = datetime.now(tz=UTC).date() @@ -102,8 +103,9 @@ class DailyLoop(commands.Cog, name="Daily Loop"): async def cakedays(self) -> None: """ - Loops through the guild's humans checking for joined_at dates that match today's date. Sends a message - saying happy cakeday. + Loops through the guild's humans checking for joined_at dates that match today's date. + + Sends a message saying happy cakeday. """ today = datetime.now(tz=UTC).date() guild: nextcord.Guild = self.bot.get_guild(self.bot.config.params.guild_id) diff --git a/cogs/message_listener/__init__.py b/cogs/message_listener/__init__.py index c073f17..a53ef34 100644 --- a/cogs/message_listener/__init__.py +++ b/cogs/message_listener/__init__.py @@ -1,6 +1,4 @@ -""" -Defines cog for on message listener. -""" +"""Defines cog for on message listener.""" import nextcord from nextcord.ext import commands @@ -15,7 +13,7 @@ class SubmissionListener(commands.Cog, name="Message Listener"): self.bot = bot @commands.Cog.listener() - async def on_message(self, message: nextcord.Message): + async def on_message(self, message: nextcord.Message) -> None: """Listener for message creation. Adds score and attachments to submission.""" # don't want to process any messages from bot. if message.author.id == self.bot.user.id: @@ -26,8 +24,8 @@ class SubmissionListener(commands.Cog, name="Message Listener"): # 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 function to bot""" +def setup(bot: commands.Bot) -> None: + """Adds function to bot.""" bot.add_cog(SubmissionListener(bot)) diff --git a/pyproject.toml b/pyproject.toml index 2d78ccb..203600b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,6 +51,7 @@ exclude = [ "node_modules", "site-packages", "venv", + "dakiny-messages", # Don't need to lint this git repo. ] # Same as Black. @@ -67,9 +68,10 @@ target-version = "py311" select = ["ALL"] ignore = [ "ANN101", + "B008", # I build SlashOptions as arguments. "COM812", "COM819", - "D107", # don't want docstrings on init functions + "D107", # don't want docstrings on init functions "D203", "D206", "D212", From 9331e7ba4ffdb2bc3ae6dd87e7bda921596ce38d Mon Sep 17 00:00:00 2001 From: imcompiling Date: Wed, 22 May 2024 20:06:50 -0400 Subject: [PATCH 13/17] aiofiles is async. Need to use it properly. --- utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/utils.py b/utils.py index c2dd878..b792300 100644 --- a/utils.py +++ b/utils.py @@ -410,8 +410,8 @@ async def update_clan_messages(guild: nextcord.Guild, channel_updates: Sequence[ for file_name in sorted_files: message_name = f"{dir_name}/{file_name}" file_path = f"{dir_path}/{file_name}" - with aiofiles.open(file_path, encoding="utf8") as reader: - await edit_clan_message(session, channel, message_name, reader.read(2000)) + async with aiofiles.open(file_path, encoding="utf8") as reader: + await edit_clan_message(session, channel, message_name, await reader.read(2000)) async def get_clan_message_channel(session: Session, channel_name: str, guild: nextcord.Guild) -> nextcord.TextChannel: @@ -542,8 +542,8 @@ async def edit_clan_council_info(guild: nextcord.Guild, config: DakinyConfig) -> quarter = get_quarter_name(config) - with aiofiles.open(f"{TEMPLATES_DIR}/council_members.txt", encoding="utf8") as file: - message = file.read() + async with aiofiles.open(f"{TEMPLATES_DIR}/council_members.txt", encoding="utf8") as file: + message = await file.read() message = message.format( quarter=quarter, council="\n".join(council_members_strs), From b9621a67c90e762ef18c0a94152b79e5417b3aff Mon Sep 17 00:00:00 2001 From: imcompiling Date: Wed, 22 May 2024 20:45:25 -0400 Subject: [PATCH 14/17] Finish up smells in the commands directory. Add a discord max message length constant. Move boolean argument to not be positional. --- cogs/commands/__init__.py | 4 +- cogs/commands/record.py | 25 +++++++--- cogs/commands/refresh.py | 31 ++++++------ cogs/commands/remove.py | 25 ++++------ cogs/commands/request.py | 56 ++++++++++----------- cogs/commands/set.py | 24 +++------ cogs/commands/test.py | 100 +++++++++++++++++++++++--------------- cogs/commands/view.py | 61 +++++++++++++---------- utils.py | 9 ++-- 9 files changed, 180 insertions(+), 155 deletions(-) diff --git a/cogs/commands/__init__.py b/cogs/commands/__init__.py index dff21a5..dbe5bde 100644 --- a/cogs/commands/__init__.py +++ b/cogs/commands/__init__.py @@ -6,7 +6,7 @@ from cogs.commands.record import RecordCog from cogs.commands.refresh import RefreshCog from cogs.commands.remove import RemoveCog from cogs.commands.request import RequestCog -from cogs.commands.set import setCog +from cogs.commands.set import SetCog from cogs.commands.test import TestCog from cogs.commands.view import ViewCog @@ -21,4 +21,4 @@ def setup(bot: commands.Bot) -> None: bot.add_cog(RequestCog(bot)) bot.add_cog(TestCog(bot)) bot.add_cog(ViewCog()) - bot.add_cog(setCog()) + bot.add_cog(SetCog()) diff --git a/cogs/commands/record.py b/cogs/commands/record.py index 765bb40..62d09ce 100644 --- a/cogs/commands/record.py +++ b/cogs/commands/record.py @@ -119,11 +119,11 @@ class EventView(nextcord.ui.View): record_event_points( self.config, non_discord_users_list, - False, self.config.params.event_participate_points, + is_member=False, ) - record_event_points(self.config, [self.host_id], True, self.config.params.event_host_points) + record_event_points(self.config, [self.host_id], self.config.params.event_host_points, is_member=True) event_channel: nextcord.TextChannel = interaction.guild.get_channel(self.config.params.event_post_channel_id) await event_channel.send(embed=interaction.message.embeds[0]) @@ -134,12 +134,13 @@ class EventView(nextcord.ui.View): class ParticipantModal(nextcord.ui.Modal): """ - This modal contains all the text edits for an event. You can change the name, description, and add the usernames - of participants who are not in the discord. + This modal contains all the text edits for an event. + + You can change the name, description, and add the usernames of participants who are not in the discord. """ - # TODO turn this embed into a dict - def __init__(self, embed): + # consider turning this embed into a dict + def __init__(self, embed: nextcord.Embed): super().__init__( "Edit the event.", timeout=5 * 60, @@ -170,6 +171,12 @@ class ParticipantModal(nextcord.ui.Modal): self.add_item(self.participants) async def callback(self, interaction: nextcord.Interaction) -> None: + """ + Edits the interaction message to contain the Non-Discord participants. + + Args: + interaction (nextcord.Interaction): Interaction of the callback. + """ self.embed.title = self.name.value self.embed.description = self.description.value self.embed.set_field_at(index=1, name="Non-Discord Participants", value=self.participants.value) @@ -189,6 +196,12 @@ class ParticipantSelect(nextcord.ui.UserSelect): ) async def callback(self, interaction: nextcord.Interaction) -> None: + """ + Edits the interaction message to contain the Discord participants. + + Args: + interaction (nextcord.Interaction): Interaction of the callback. + """ embed: nextcord.Embed = interaction.message.embeds[0] participants: str = "\n".join([f"<@{participant.id}>" for participant in self.values.members]) embed.set_field_at(0, name="Participants", value=participants) diff --git a/cogs/commands/refresh.py b/cogs/commands/refresh.py index 1bc30bc..2831c1d 100644 --- a/cogs/commands/refresh.py +++ b/cogs/commands/refresh.py @@ -1,8 +1,7 @@ -""" -Contained commands and views for refresh commands. -""" +"""Contained commands and views for refresh commands.""" import os +from pathlib import Path import nextcord from nextcord.ext import commands @@ -11,22 +10,16 @@ from utils import MESSAGES_DIR, edit_clan_council_info, update_clan_messages class RefreshCog(commands.Cog, name="Refresh Commands"): - """ - Class holds refresh commands. - """ + """Class holds refresh commands.""" - def __init__(self, bot) -> None: - """ - Default constructor for RefreshCog class. Does nothing. - """ + def __init__(self, bot: commands.Bot) -> None: + """Default constructor for RefreshCog class. Does nothing.""" super().__init__() self.bot: commands.Bot = bot @nextcord.slash_command(default_member_permissions=nextcord.Permissions(administrator=True)) - async def refresh(self, interaction: nextcord.Interaction): - """ - Parent slash command for refresh. Never called. - """ + async def refresh(self, interaction: nextcord.Interaction) -> None: + """Parent slash command for refresh. Never called.""" @refresh.subcommand(description="Force refresh all of the bot managed messages.") async def messages( @@ -36,8 +29,10 @@ class RefreshCog(commands.Cog, name="Refresh Commands"): description="Channel to refresh messages in.", required=False, ), - ): + ) -> None: """ + Updates all messages. + Calls update_clan_messages() to update all messages. Args: @@ -48,7 +43,7 @@ class RefreshCog(commands.Cog, name="Refresh Commands"): 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}") + dir_name for dir_name in os.listdir(MESSAGES_DIR) if Path(f"{MESSAGES_DIR}/{dir_name}").is_dir() ] else: channel_updates = [channel.name] @@ -57,8 +52,10 @@ class RefreshCog(commands.Cog, name="Refresh Commands"): await interaction.followup.send("Messages have been refreshed.", ephemeral=True) @refresh.subcommand(description="Updates the current council info message.") - async def council_info(self, interaction: nextcord.Interaction): + async def council_info(self, interaction: nextcord.Interaction) -> None: """ + Edits the clan council info message. + Calls edit_clan_council_info(). Args: diff --git a/cogs/commands/remove.py b/cogs/commands/remove.py index 9dde841..81cd49e 100644 --- a/cogs/commands/remove.py +++ b/cogs/commands/remove.py @@ -1,6 +1,4 @@ -""" -Commands for removing information in the database. -""" +"""Commands for removing information in the database.""" import nextcord from nextcord.ext import commands @@ -11,35 +9,29 @@ from database_setup.sql_data_classes import Presences, engine class RemoveCog(commands.Cog, name="Remove Commands"): - """ - Holds the view commands. - """ + """Holds the view commands.""" def __init__(self) -> None: - """ - Default constructor for RemoveCog class. Does nothing. - """ + """Default constructor for RemoveCog class. Does nothing.""" @nextcord.slash_command( default_member_permissions=nextcord.Permissions(administrator=True), ) - async def remove(self, interaction: nextcord.Interaction): - """ - Parent slash command for set. Never called. - """ + async def remove(self, interaction: nextcord.Interaction) -> None: + """Parent slash command for set. Never called.""" @remove.subcommand(description="Remove a presence from the database.") async def presence( self, interaction: nextcord.Interaction, presence_str: str = nextcord.SlashOption(description="The presence to remove", required=True), - ): + ) -> None: """ Deletes the given presence id from the database. Args: interaction (nextcord.Interaction): Interaction that invoked the command. - presence (str): Is SlashOption. The presence to remove. Is the ID of the presence. + presence_str (str): Is SlashOption. The presence to remove. Is the ID of the presence. """ presence_id = int(presence_str) with Session(engine) as session: @@ -50,9 +42,10 @@ class RemoveCog(commands.Cog, name="Remove Commands"): await interaction.response.send_message("Presence deleted.", ephemeral=True) @presence.on_autocomplete("presence_str") - async def presence_autocomplete(self, interaction: nextcord.Interaction, presence_str: str): + async def presence_autocomplete(self, interaction: nextcord.Interaction, presence_str: str) -> None: """ Collects all presences that start with the presence string. + Sends autocomplete dict {Presence Name, Presence Id} Args: diff --git a/cogs/commands/request.py b/cogs/commands/request.py index 2e2638b..c80e39d 100644 --- a/cogs/commands/request.py +++ b/cogs/commands/request.py @@ -1,31 +1,27 @@ -""" -Contains commands and views for request commands. -""" +"""Contains commands and views for request commands.""" + +from typing import TYPE_CHECKING import nextcord from nextcord.ext import commands from sqlalchemy.orm import Session -from config import DakinyConfig from database_setup.sql_data_classes import Presences, engine +if TYPE_CHECKING: + from config import DakinyConfig + class RequestCog(commands.Cog, name="Request Commands"): - """ - Class holds request commands. - """ + """Class holds request commands.""" - def __init__(self, bot) -> None: - """ - Default constructor for RequestCog class. Does nothing. - """ + def __init__(self, bot: commands.Bot) -> None: + """Default constructor for RequestCog class. Does nothing.""" self.bot: commands.Bot = bot @nextcord.slash_command() - async def request(self, interaction: nextcord.Interaction): - """ - Parent slash command for request. Never called. - """ + async def request(self, interaction: nextcord.Interaction) -> None: + """Parent slash command for request. Never called.""" @request.subcommand(description="Request to add a presence to the bot's presence list.") async def add_presence( @@ -37,7 +33,7 @@ class RequestCog(commands.Cog, name="Request Commands"): max_length=100, description="The presence you want to add.", ), - ): + ) -> None: """ Sends a message to admins to request to add a presence. @@ -61,13 +57,13 @@ class RequestCog(commands.Cog, name="Request Commands"): max_length=32, description="Your requested new nickname.", ), - ): + ) -> None: """ Sends a message to admins to request a nickname change. Args: interaction (nextcord.Interaction): Interaction that invoked the command. - new_username(str): The username to change to. + new_nickname(str): The username to change to. """ config: DakinyConfig = self.bot.config content = f'<@{interaction.user.id}> would like to change their username to "{new_nickname}."' @@ -77,9 +73,7 @@ class RequestCog(commands.Cog, name="Request Commands"): class UsernameRequest(nextcord.ui.View): - """ - View provides approve and deny buttons and action for a username change request. - """ + """View provides approve and deny buttons and action for a username change request.""" def __init__( self, @@ -91,9 +85,11 @@ class UsernameRequest(nextcord.ui.View): self.new_nickname = new_nickname @nextcord.ui.button(label="Approve", style=nextcord.ButtonStyle.green) - async def approve(self, button: nextcord.ui.Button, interaction: nextcord.Interaction): + async def approve(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: # noqa: ARG002 """ - Approve button for a nickname change request. Changes the target's nickname to self.new_nickname. + Approve button for a nickname change request. + + Changes the target's nickname to self.new_nickname. Catches an error if the target as a higher role than the bot. Removes the view from the interaction message. @@ -124,7 +120,7 @@ class UsernameRequest(nextcord.ui.View): ) @nextcord.ui.button(label="Deny", style=nextcord.ButtonStyle.red) - async def deny(self, button: nextcord.ui.Button, interaction: nextcord.Interaction): + async def deny(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: # noqa: ARG002 """ Deny button for a nickname request. Just removes the view and logs who denied the request. @@ -137,9 +133,7 @@ class UsernameRequest(nextcord.ui.View): class PresenceChange(nextcord.ui.View): - """ - View provides approve and deny buttons and action for a username change request. - """ + """View provides approve and deny buttons and action for a username change request.""" def __init__( self, @@ -149,9 +143,11 @@ class PresenceChange(nextcord.ui.View): self.presence = presence @nextcord.ui.button(label="Approve", style=nextcord.ButtonStyle.green) - async def approve(self, button: nextcord.ui.Button, interaction: nextcord.Interaction): + async def approve(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: # noqa: ARG002 """ - Approve button for a presence add request. Adds the presence to the database. + Approve button for a presence add request. + + Adds the presence to the database. Removes the view from the interaction message. Args: @@ -166,7 +162,7 @@ class PresenceChange(nextcord.ui.View): await interaction.response.edit_message(content=content, view=None) @nextcord.ui.button(label="Deny", style=nextcord.ButtonStyle.red) - async def deny(self, button: nextcord.ui.Button, interaction: nextcord.Interaction): + async def deny(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: # noqa: ARG002 """ Deny button for a presence add request. Just removes the view and logs who denied the request. diff --git a/cogs/commands/set.py b/cogs/commands/set.py index 30f8f9c..01c35ed 100644 --- a/cogs/commands/set.py +++ b/cogs/commands/set.py @@ -1,6 +1,4 @@ -""" -Contains commands and views for set commands. -""" +"""Contains commands and views for set commands.""" from datetime import UTC, datetime @@ -14,21 +12,15 @@ from database_setup.sql_data_classes import UserBirthday, engine from main import birthday_list -class setCog(commands.Cog, name="Set Commands"): - """ - Class holds set commands. - """ +class SetCog(commands.Cog, name="Set Commands"): + """Class holds set commands.""" def __init__(self) -> None: - """ - Default constructor for SetCog class. Does nothing. - """ + """Default constructor for SetCog class. Does nothing.""" @nextcord.slash_command() - async def set(self, interaction: nextcord.Interaction): - """ - Parent slash command for set. Never called. - """ + async def set(self, interaction: nextcord.Interaction) -> None: + """Parent slash command for set. Never called.""" @set.subcommand(description="Sets your birth month and day for the Birthday Fun. Use 0/0 to remove yourself.") async def birthday( @@ -36,7 +28,7 @@ class setCog(commands.Cog, name="Set Commands"): interaction: nextcord.Interaction, month: int = nextcord.SlashOption(required=True, min_value=0, max_value=12, description="Your birth month."), day: int = nextcord.SlashOption(required=True, min_value=0, max_value=31, description="Your birth day."), - ): + ) -> None: """ Sets a user's birthday in the database. Removes on input 0/0. If birthday is today, adds to the birthday_list. @@ -59,7 +51,7 @@ class setCog(commands.Cog, name="Set Commands"): # check if valid day today = datetime.now(tz=UTC).date() try: - birthday = datetime(today.year, month, day) + birthday = datetime(today.year, month, day, tzinfo=UTC) except ValueError: await interaction.followup.send(f"Invalid date: {month}/{day}.", ephemeral=True) return diff --git a/cogs/commands/test.py b/cogs/commands/test.py index 51fee65..bc13e73 100644 --- a/cogs/commands/test.py +++ b/cogs/commands/test.py @@ -1,51 +1,50 @@ -""" -Commands for testing. -""" +"""Commands for testing.""" + +from typing import TYPE_CHECKING import nextcord from nextcord.ext import application_checks, commands +from nextcord.ext.application_checks.core import AC from classes.election_cycle import ElectionCycle -from config import DakinyConfig from utils import get_current_election_cycle +if TYPE_CHECKING: + from config import DakinyConfig -def is_me(): - """ - predicate that makes sure only I can run the test commands. - """ - def predicate(interaction: nextcord.Interaction): - return interaction.user.id == 206602713511886849 +ME = 206602713511886849 +GUILDS = [1000247361639030804] + + +def is_me() -> AC: + """Predicate that makes sure only I can run the test commands.""" + + def predicate(interaction: nextcord.Interaction) -> bool: + return interaction.user.id == ME return application_checks.check(predicate) class TestCog(commands.Cog, name="Test commands."): - """ - Class holds commands for testing. - """ + """Class holds commands for testing.""" - def __init__(self, bot) -> None: - """ - Default constructor for HelpCog class. Does nothing. - """ - self.bot = bot + def __init__(self, bot: commands.Bot) -> None: + """Default constructor for HelpCog class. Does nothing.""" + self.bot: commands.Bot = bot - @nextcord.slash_command(guild_ids=[1000247361639030804]) - async def test(self, interaction: nextcord.Interaction): - """ - Parent slash command for help. Never called. - """ + @nextcord.slash_command(guild_ids=GUILDS) + async def test(self, interaction: nextcord.Interaction) -> None: + """Parent slash command for help. Never called.""" @test.subcommand() @is_me() - async def election_start(self, interaction: nextcord.Interaction): + async def election_start(self, interaction: nextcord.Interaction) -> None: """ - idc + Force starts election. Args: - interaction (nextcord.Interaction): _description_ + interaction (nextcord.Interaction): Interaction with the message. """ config: DakinyConfig = self.bot.config cycle = ElectionCycle( @@ -54,15 +53,16 @@ class TestCog(commands.Cog, name="Test commands."): self.bot.get_guild(config.params.guild_id), ) await cycle.election_start_action() + await interaction.send("Test command sent.") @test.subcommand() @is_me() - async def voting_start(self, interaction: nextcord.Interaction): + async def voting_start(self, interaction: nextcord.Interaction) -> None: """ - tests the voting start action + Force starts voting. Args: - interaction (nextcord.Interaction): _description_ + interaction (nextcord.Interaction): Interaction with the message. """ config: DakinyConfig = self.bot.config cycle = ElectionCycle( @@ -71,15 +71,16 @@ class TestCog(commands.Cog, name="Test commands."): self.bot.get_guild(config.params.guild_id), ) await cycle.voting_start_action() + await interaction.send("Test command sent.") @test.subcommand() @is_me() - async def voting_end(self, interaction: nextcord.Interaction): + async def voting_end(self, interaction: nextcord.Interaction) -> None: """ - tests the voting start action + Force ends voting. Args: - interaction (nextcord.Interaction): _description_ + interaction (nextcord.Interaction): Interaction with the message. """ config: DakinyConfig = self.bot.config cycle = ElectionCycle( @@ -88,15 +89,16 @@ class TestCog(commands.Cog, name="Test commands."): self.bot.get_guild(config.params.guild_id), ) await cycle.voting_end_action() + await interaction.send("Test command sent.") @test.subcommand() @is_me() - async def highcouncil_vote(self, interaction: nextcord.Interaction): + async def highcouncil_vote(self, interaction: nextcord.Interaction) -> None: """ - tests the voting start action + Force starts highcouncil voting. Args: - interaction (nextcord.Interaction): _description_ + interaction (nextcord.Interaction): Interaction with the message. """ config: DakinyConfig = self.bot.config cycle = ElectionCycle( @@ -105,10 +107,17 @@ class TestCog(commands.Cog, name="Test commands."): self.bot.get_guild(config.params.guild_id), ) await cycle.highcouncil_election_start_action() + await interaction.send("Test command sent.") @test.subcommand() @is_me() - async def election_reminder(self, interaction: nextcord.Interaction): + async def election_reminder(self, interaction: nextcord.Interaction) -> None: + """ + Forces election reminder. + + Args: + interaction (nextcord.Interaction): Interaction with the message. + """ config: DakinyConfig = self.bot.config cycle = ElectionCycle( get_current_election_cycle(config), @@ -116,10 +125,17 @@ class TestCog(commands.Cog, name="Test commands."): self.bot.get_guild(config.params.guild_id), ) await cycle.election_reminder() + await interaction.send("Test command sent.") @test.subcommand() @is_me() - async def daily(self, interaction: nextcord.Interaction): + async def daily(self, interaction: nextcord.Interaction) -> None: + """ + Forces daily function. + + Args: + interaction (nextcord.Interaction): Interaction with the message. + """ config: DakinyConfig = self.bot.config cycle = ElectionCycle( get_current_election_cycle(config), @@ -127,10 +143,17 @@ class TestCog(commands.Cog, name="Test commands."): self.bot.get_guild(config.params.guild_id), ) await cycle.check_date() + await interaction.send("Test command sent.") @test.subcommand() @is_me() - async def term_start(self, interaction: nextcord.Interaction): + async def term_start(self, interaction: nextcord.Interaction) -> None: + """ + Forces term start. + + Args: + interaction (nextcord.Interaction): Interaction with the message. + """ config: DakinyConfig = self.bot.config cycle = ElectionCycle( get_current_election_cycle(config), @@ -138,3 +161,4 @@ class TestCog(commands.Cog, name="Test commands."): self.bot.get_guild(config.params.guild_id), ) await cycle.term_start_action() + await interaction.send("Test command sent.") diff --git a/cogs/commands/view.py b/cogs/commands/view.py index 9887081..806add8 100644 --- a/cogs/commands/view.py +++ b/cogs/commands/view.py @@ -1,6 +1,4 @@ -""" -Commands for viewing information in the database. -""" +"""Commands for viewing information in the database.""" import nextcord import pandas as pd @@ -14,24 +12,18 @@ from database_setup.sql_data_classes import ( Votes, engine, ) -from utils import VoteType +from utils import MAX_DISCORD_MESSAGE_LENGTH, VoteType class ViewCog(commands.Cog, name="View Commands"): - """ - Holds the view commands. - """ + """Holds the view commands.""" def __init__(self) -> None: - """ - Default constructor for ViewCog class. Does nothing. - """ + """Default constructor for ViewCog class. Does nothing.""" @nextcord.slash_command() - async def view(self, interaction: nextcord.Interaction): - """ - Parent slash command for set. Never called. - """ + async def view(self, interaction: nextcord.Interaction) -> None: + """Parent slash command for set. Never called.""" @view.subcommand(description="Show event points.") async def event_points( @@ -41,38 +33,49 @@ class ViewCog(commands.Cog, name="View Commands"): description="The quarter to view", required=False, ), - ): + ) -> None: + """ + View the event points. + + Builds a message with the event points for the quarter that matches with quarter_name. + + Args: + interaction (nextcord.Interaction): Interaction with the message. + quarter_name (str, optional): Same of quarter to autofill with. Is SlashOption. + """ # just in case await interaction.response.defer(ephemeral=True) with Session(engine) as session: if quarter_name: - df = pd.read_sql_query( + ppdf = pd.read_sql_query( sql=session.query(ParticipationPoints) .where(ParticipationPoints.Cycle.startswith(quarter_name)) .statement, con=engine, ) else: - df = pd.read_sql_query( + ppdf = pd.read_sql_query( sql=session.query(ParticipationPoints).statement, con=engine, ) - if df.empty: + if ppdf.empty: await interaction.followup.send(f'Quarter "{quarter_name}" is invalid.', ephemeral=True) return - cycles = df.groupby("Cycle") + cycles = ppdf.groupby("Cycle") content = "" for cycle in cycles.groups: content += f"**{cycle}**\n" unsorted_all = cycles.get_group(cycle).groupby("DiscordMember") - members = unsorted_all.get_group(True) - non_members = unsorted_all.get_group(False) + members = unsorted_all.get_group(name=True) + non_members = unsorted_all.get_group(name=False) sorted_members = members.sort_values( by="UserId", - key=lambda user_ids: ([interaction.guild.get_member(int(id)).display_name for id in user_ids]), + key=lambda user_ids: ( + [interaction.guild.get_member(int(user_id)).display_name for user_id in user_ids] + ), ) sorted_non_members = non_members.sort_values(by="UserId") sorted_all = pd.concat([sorted_members, sorted_non_members], ignore_index=True, sort=False) @@ -83,7 +86,7 @@ class ViewCog(commands.Cog, name="View Commands"): content += f"<@{userid}>: {points}\n" if discord_member else f"{userid}: {points}\n" content += "\n" - if len(content) > 2000: + if len(content) > MAX_DISCORD_MESSAGE_LENGTH: await interaction.followup.send("Message length is too long, please select search to limit results.") return @@ -97,12 +100,13 @@ class ViewCog(commands.Cog, name="View Commands"): description="A word or string to search for in the presences.", required=False, ), - ): + ) -> None: """ Gathers all presences from the database and responds with a message showing them. Args: interaction (nextcord.Integration): User's interaction with the command. + search (str): string to search for in presences. Is SlashOption. """ # might take a while depending on number of presences await interaction.response.defer(ephemeral=True) @@ -130,8 +134,10 @@ class ViewCog(commands.Cog, name="View Commands"): description="The election type. Council, Discord Manager, or Clan Leader.", choices=VoteType, ), - ): + ) -> None: """ + View all voters and who they voted for. + Takes in an election name and sends a response message to the user with a list of people who voted in that election and who they voted for. Notes those tho abstained. Tells a user if the election is invalid. Uses autocompletion to assist with getting elections. @@ -139,6 +145,7 @@ class ViewCog(commands.Cog, name="View Commands"): Args: interaction (nextcord.Interaction): User's interaction with the command. election_name (str): Is SlashOption. The name of the election to view votes for. + election_type(str): Is SlashOption. The type of election to search for. Choice from StrEnum VoteType. """ stmt = select(Votes).where(Votes.ElectionCycle == election_name).where(Votes.Type == election_type) @@ -160,7 +167,7 @@ class ViewCog(commands.Cog, name="View Commands"): await interaction.response.send_message(content=content, ephemeral=True) @votes.on_autocomplete("election_name") - async def election_autocomplete(self, interaction: nextcord.Interaction, election_name: str): + async def election_autocomplete(self, interaction: nextcord.Interaction, election_name: str) -> None: """ Collects all elections that start with the election string. @@ -183,7 +190,7 @@ class ViewCog(commands.Cog, name="View Commands"): await interaction.response.send_autocomplete(autofilled_elections[:25]) @event_points.on_autocomplete("quarter_name") - async def event_points_autocomplete(self, interaction: nextcord.Interaction, quarter_name: str): + async def event_points_autocomplete(self, interaction: nextcord.Interaction, quarter_name: str) -> None: """ Collects all elections that start with the election string. diff --git a/utils.py b/utils.py index b792300..9ad7d76 100644 --- a/utils.py +++ b/utils.py @@ -26,6 +26,7 @@ NOMINEE_SPLITTER = "========================================\n\n" REPO_DIR = "./dakiny-messages" MESSAGES_DIR = f"{REPO_DIR}/messages" TEMPLATES_DIR = f"{REPO_DIR}/message_templates" +MAX_DISCORD_MESSAGE_LENGTH = 2000 VoteType = StrEnum("VoteType", ["COUNCIL", "DISCORDMANAGER", "CLANLEADER"]) @@ -300,7 +301,7 @@ def decorate_dm_nomination(user_id: int, speech: str) -> str: return msg -def record_event_points(config: DakinyConfig, users: Sequence[str], is_member: bool, add_points: int) -> None: # noqa: FBT001 +def record_event_points(config: DakinyConfig, users: Sequence[str], add_points: int, *, is_member: bool) -> None: """ Adds event points to all users listed. @@ -310,8 +311,8 @@ def record_event_points(config: DakinyConfig, users: Sequence[str], is_member: b Args: config (DakinyConfig): Dakiny config for getting points users (Sequence[int]): users that participated and need points. - is_member (bool): whether the users are a discord member or not add_points (int): number of points to add + is_member (bool): whether the users are a discord member or not """ quarter_name = get_quarter_name(config) with Session(engine) as session: @@ -411,7 +412,9 @@ async def update_clan_messages(guild: nextcord.Guild, channel_updates: Sequence[ message_name = f"{dir_name}/{file_name}" file_path = f"{dir_path}/{file_name}" async with aiofiles.open(file_path, encoding="utf8") as reader: - await edit_clan_message(session, channel, message_name, await reader.read(2000)) + await edit_clan_message( + session, channel, message_name, await reader.read(MAX_DISCORD_MESSAGE_LENGTH) + ) async def get_clan_message_channel(session: Session, channel_name: str, guild: nextcord.Guild) -> nextcord.TextChannel: From 9c175f3e524b77c4231d1c49f2a9f38fa26f06d1 Mon Sep 17 00:00:00 2001 From: imcompiling Date: Wed, 22 May 2024 21:15:37 -0400 Subject: [PATCH 15/17] Fix issues found while live testing. --- classes/election_cycle.py | 2 +- cogs/commands/set.py | 2 +- cogs/commands/test.py | 17 ++++++++--------- utils.py | 10 ++++++---- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/classes/election_cycle.py b/classes/election_cycle.py index 461ebd2..6b3dc89 100644 --- a/classes/election_cycle.py +++ b/classes/election_cycle.py @@ -143,7 +143,7 @@ class ElectionCycle: async def check_date(self) -> None: """Function checks current date to see if something needs to be run.""" - today = datetime.now(tz=UTC).date() + today = datetime.now(tz=UTC) # check all dates if action is needed if today.date() == self.election_start.date(): diff --git a/cogs/commands/set.py b/cogs/commands/set.py index 01c35ed..2165241 100644 --- a/cogs/commands/set.py +++ b/cogs/commands/set.py @@ -49,7 +49,7 @@ class SetCog(commands.Cog, name="Set Commands"): return # check if valid day - today = datetime.now(tz=UTC).date() + today = datetime.now(tz=UTC) try: birthday = datetime(today.year, month, day, tzinfo=UTC) except ValueError: diff --git a/cogs/commands/test.py b/cogs/commands/test.py index bc13e73..1e93b1e 100644 --- a/cogs/commands/test.py +++ b/cogs/commands/test.py @@ -4,7 +4,6 @@ from typing import TYPE_CHECKING import nextcord from nextcord.ext import application_checks, commands -from nextcord.ext.application_checks.core import AC from classes.election_cycle import ElectionCycle from utils import get_current_election_cycle @@ -17,7 +16,7 @@ ME = 206602713511886849 GUILDS = [1000247361639030804] -def is_me() -> AC: +def is_me(): # noqa: ANN201 """Predicate that makes sure only I can run the test commands.""" def predicate(interaction: nextcord.Interaction) -> bool: @@ -53,7 +52,7 @@ class TestCog(commands.Cog, name="Test commands."): self.bot.get_guild(config.params.guild_id), ) await cycle.election_start_action() - await interaction.send("Test command sent.") + await interaction.send("Test command sent.", ephemeral=True) @test.subcommand() @is_me() @@ -71,7 +70,7 @@ class TestCog(commands.Cog, name="Test commands."): self.bot.get_guild(config.params.guild_id), ) await cycle.voting_start_action() - await interaction.send("Test command sent.") + await interaction.send("Test command sent.", ephemeral=True) @test.subcommand() @is_me() @@ -89,7 +88,7 @@ class TestCog(commands.Cog, name="Test commands."): self.bot.get_guild(config.params.guild_id), ) await cycle.voting_end_action() - await interaction.send("Test command sent.") + await interaction.send("Test command sent.", ephemeral=True) @test.subcommand() @is_me() @@ -107,7 +106,7 @@ class TestCog(commands.Cog, name="Test commands."): self.bot.get_guild(config.params.guild_id), ) await cycle.highcouncil_election_start_action() - await interaction.send("Test command sent.") + await interaction.send("Test command sent.", ephemeral=True) @test.subcommand() @is_me() @@ -125,7 +124,7 @@ class TestCog(commands.Cog, name="Test commands."): self.bot.get_guild(config.params.guild_id), ) await cycle.election_reminder() - await interaction.send("Test command sent.") + await interaction.send("Test command sent.", ephemeral=True) @test.subcommand() @is_me() @@ -143,7 +142,7 @@ class TestCog(commands.Cog, name="Test commands."): self.bot.get_guild(config.params.guild_id), ) await cycle.check_date() - await interaction.send("Test command sent.") + await interaction.send("Test command sent.", ephemeral=True) @test.subcommand() @is_me() @@ -161,4 +160,4 @@ class TestCog(commands.Cog, name="Test commands."): self.bot.get_guild(config.params.guild_id), ) await cycle.term_start_action() - await interaction.send("Test command sent.") + await interaction.send("Test command sent.", ephemeral=True) diff --git a/utils.py b/utils.py index 9ad7d76..13e17e9 100644 --- a/utils.py +++ b/utils.py @@ -47,10 +47,12 @@ def get_datetime(day: str) -> datetime: day = int(day.split("/")[1]) # ensure that year rolls over if current month is December and the next month is less than 12. - if today.month == calendar.DECEMBER and month < calendar.DECEMBER: + # TODO(imcompiling): replace with Calendar.DECEMBER when updating to python 3.12 + # https://gitea.cheezers.org/imcompiling/dakiny-bot/issues/52 + if today.month == 12 and month < 12: year += 1 - if today.month == calendar.JANUARY and month > calendar.JANUARY: + if today.month == 1 and month > 1: year -= 1 return datetime(year, month, day, tzinfo=UTC) @@ -97,7 +99,7 @@ def get_current_election_cycle(config: DakinyConfig) -> int: Returns: int: cycle index for active election. None for no active cycle """ - today = datetime.now(tz=UTC).date() + today = datetime.now(tz=UTC) for cycle, election in enumerate(config.election_cycles): if ( @@ -120,7 +122,7 @@ def get_quarter_cycle(config: DakinyConfig) -> int: Returns: int: cycle index for current cycle. """ - today = datetime.now(tz=UTC).date() + today = datetime.now(tz=UTC) for cycle, election in enumerate(config.election_cycles): if ( today.date() >= get_datetime(election.term_start).date() From 985e253954ae8b82a93b30f7a1a9cbd2aa6e5eef Mon Sep 17 00:00:00 2001 From: imcompiling Date: Wed, 22 May 2024 21:59:12 -0400 Subject: [PATCH 16/17] edits to the pyproject. --- pyproject.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 203600b..a20df62 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,6 +4,7 @@ version = "0.1.0" description = "Dakiny's Discord Bot." authors = ["imcompiling "] readme = "README.md" +package-mode = false [tool.poetry.dependencies] python = "^3.11" @@ -18,6 +19,9 @@ sqlalchemy = "1.4.46" aiofiles = "^23.2.1" +[tool.poetry.group.dev.dependencies] +ruff = "^0.4.5" + [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" From ed0895056d476d387bb282cc219e5129e9a5996b Mon Sep 17 00:00:00 2001 From: imcompiling Date: Wed, 22 May 2024 22:48:54 -0400 Subject: [PATCH 17/17] remove quotes from type annotation. --- cogs/commands/remove.py | 4 ++-- cogs/commands/view.py | 4 ++-- utils.py | 1 - 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/cogs/commands/remove.py b/cogs/commands/remove.py index 81cd49e..d5b72e2 100644 --- a/cogs/commands/remove.py +++ b/cogs/commands/remove.py @@ -54,14 +54,14 @@ class RemoveCog(commands.Cog, name="Remove Commands"): """ with Session(engine) as session: stmt = select(Presences) - presences_dict: "dict[str,str]" = {pres.Presence: str(pres.Id) for pres in session.scalars(stmt)} + presences_dict: dict[str,str] = {pres.Presence: str(pres.Id) for pres in session.scalars(stmt)} # if empty compare string if not presence_str: await interaction.response.send_autocomplete(dict(list(presences_dict.items())[:25])) return - autofilled_presences: "dict[str,str]" = { + autofilled_presences: dict[str,str] = { pres: pres_id for pres, pres_id in presences_dict.items() if presence_str.lower() in pres.lower() } await interaction.response.send_autocomplete(autofilled_presences[:25]) diff --git a/cogs/commands/view.py b/cogs/commands/view.py index 806add8..654643c 100644 --- a/cogs/commands/view.py +++ b/cogs/commands/view.py @@ -184,7 +184,7 @@ class ViewCog(commands.Cog, name="View Commands"): await interaction.response.send_autocomplete(list(elections)[:25]) return - autofilled_elections: "list[str]" = [ + autofilled_elections: list[str] = [ election for election in elections if election.lower().startswith(election_name.lower()) ] await interaction.response.send_autocomplete(autofilled_elections[:25]) @@ -207,7 +207,7 @@ class ViewCog(commands.Cog, name="View Commands"): await interaction.response.send_autocomplete(list(quarters)[:25]) return - autofilled_quarters: "list[str]" = [ + autofilled_quarters: list[str] = [ quarter for quarter in quarters if quarter.lower().startswith(quarter_name.lower()) ] await interaction.response.send_autocomplete(autofilled_quarters[:25]) diff --git a/utils.py b/utils.py index 13e17e9..7a8f64e 100644 --- a/utils.py +++ b/utils.py @@ -1,6 +1,5 @@ """File contains util functions.""" -import calendar import os from collections.abc import Sequence from datetime import UTC, datetime