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",