Start working on files in cogs.
build_develop / build (push) Successful in 1m24s

This commit is contained in:
2024-05-22 17:35:43 -04:00
parent d9914529ca
commit 6500f68d86
8 changed files with 95 additions and 109 deletions
+24 -21
View File
@@ -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 import uvicorn
from fastapi import FastAPI from fastapi import FastAPI
from git import Repo from git import Repo
from nextcord.ext import commands from nextcord.ext import commands
from classes.gitea_webhook import GiteaWebhook from classes.gitea_webhook import GiteaWebhook
from config import DakinyConfig
from utils import REPO_DIR, edit_clan_council_info, update_clan_messages from utils import REPO_DIR, edit_clan_council_info, update_clan_messages
if TYPE_CHECKING:
import nextcord
from config import DakinyConfig
app = FastAPI() app = FastAPI()
REPO_URL = "https://gitea.cheezers.org/imcompiling/dakiny-messages.git" REPO_URL = "https://gitea.cheezers.org/imcompiling/dakiny-messages.git"
class ClanMessages(commands.Cog, name="Clan Messages"): 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. the same time.
""" """
@@ -30,23 +34,24 @@ class ClanMessages(commands.Cog, name="Clan Messages"):
self.repo_setup() self.repo_setup()
@commands.Cog.listener() @commands.Cog.listener()
async def on_ready(self): async def on_ready(self) -> None:
""" """Triggers when the bot is in ready state. Adds the webhook listener to FastAPI and serves."""
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"]) 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) server = uvicorn.Server(config)
await server.serve() 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 Pulls from git and then calls method to update all the discord messages as described by the
files in the git repo. files in the git repo.
""" """
# Get all modified/added files # Get all modified/added files
modified_files: list[str] = [ 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} 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."} return {"message": "Successfully updated all discord messages."}
def repo_setup(self): def repo_setup(self) -> None:
""" """Checks if the repo exists and creates repo object, clones if it does not."""
Checks if the repo exists and creates repo object, clones if it does not.
"""
# check if repo has been cloned. open repo object # 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) self.repo = Repo(REPO_DIR)
else: else:
self.repo = Repo.clone_from(REPO_URL, REPO_DIR) 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. # This function will be called when this extension is loaded.
# It is necessary to add these functions to the bot. # It is necessary to add these functions to the bot.
def setup(bot: commands.Bot): def setup(bot: commands.Bot) -> None:
"""Adds function to bot""" """Adds function to bot."""
bot.add_cog(ClanMessages(bot)) bot.add_cog(ClanMessages(bot))
+3 -7
View File
@@ -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 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. # This function will be called when this extension is loaded.
# It is necessary to add these functions to the bot. # It is necessary to add these functions to the bot.
def setup(bot: commands.Bot): def setup(bot: commands.Bot) -> None:
""" """Adds all command classes to bot."""
Adds all command classes to bot.
"""
bot.add_cog(RecordCog(bot)) bot.add_cog(RecordCog(bot))
bot.add_cog(RefreshCog(bot)) bot.add_cog(RefreshCog(bot))
bot.add_cog(RemoveCog()) bot.add_cog(RemoveCog())
+25 -33
View File
@@ -1,6 +1,4 @@
""" """Contained commands and views for record commands."""
Contained commands and views for record commands.
"""
import nextcord import nextcord
from discord import TextInputStyle from discord import TextInputStyle
@@ -11,22 +9,16 @@ from utils import record_event_points
class RecordCog(commands.Cog, name="Record Commands"): class RecordCog(commands.Cog, name="Record Commands"):
""" """Class holds record commands."""
Class holds record commands.
"""
def __init__(self, bot) -> None: def __init__(self, bot: commands.Bot) -> None:
""" """Default constructor for RecordCog class. Does nothing."""
Default constructor for RecordCog class. Does nothing.
"""
super().__init__() super().__init__()
self.bot: commands.Bot = bot self.bot: commands.Bot = bot
@nextcord.slash_command() @nextcord.slash_command()
async def record(self, interaction: nextcord.Interaction): async def record(self, interaction: nextcord.Interaction) -> None:
""" """Parent slash command for record. Never called."""
Parent slash command for record. Never called.
"""
@record.subcommand(description="Record an event and its participants.") @record.subcommand(description="Record an event and its participants.")
async def event( async def event(
@@ -37,8 +29,10 @@ class RecordCog(commands.Cog, name="Record Commands"):
required=False, required=False,
description="An alternative host if you are recording event for someone else.", 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 This command creates the base event recording embed and sends a message with it and the EventView
for gathering more information on the event. for gathering more information on the event.
@@ -73,7 +67,9 @@ class RecordCog(commands.Cog, name="Record Commands"):
class EventView(nextcord.ui.View): 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. Also contains a UserSelect object to pick discord members.
""" """
@@ -89,33 +85,31 @@ class EventView(nextcord.ui.View):
self.add_item(self.event_participants) self.add_item(self.event_participants)
@nextcord.ui.button(label="Cancel", style=nextcord.ButtonStyle.red, row=1) @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) -> None: # noqa: ARG002
""" """Cancels event submission. Removes embed and view."""
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() self.stop()
@nextcord.ui.button(label="Edit", style=nextcord.ButtonStyle.blurple, row=1) @nextcord.ui.button(label="Edit", style=nextcord.ButtonStyle.blurple, row=1)
async def edit(self, button: nextcord.ui.Button, interaction: nextcord.Interaction): 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."""
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) @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. 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: 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( record_event_points(
self.config, self.config,
discord_users_list, discord_users_list,
True, True, # noqa: FBT003
self.config.params.event_participate_points, self.config.params.event_participate_points,
) )
@@ -175,7 +169,7 @@ class ParticipantModal(nextcord.ui.Modal):
self.add_item(self.description) self.add_item(self.description)
self.add_item(self.participants) 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.title = self.name.value
self.embed.description = self.description.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)
@@ -184,9 +178,7 @@ class ParticipantModal(nextcord.ui.Modal):
class ParticipantSelect(nextcord.ui.UserSelect): 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: def __init__(self) -> None:
super().__init__( super().__init__(
@@ -196,7 +188,7 @@ class ParticipantSelect(nextcord.ui.UserSelect):
row=0, row=0,
) )
async def callback(self, interaction: nextcord.Interaction): async def callback(self, interaction: nextcord.Interaction) -> None:
embed: nextcord.Embed = interaction.message.embeds[0] 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) embed.set_field_at(0, name="Participants", value=participants)
+3 -7
View File
@@ -1,6 +1,4 @@
""" """Initializing code for all loops."""
Initializing code for all loops.
"""
from nextcord.ext import commands from nextcord.ext import commands
@@ -8,9 +6,7 @@ from cogs.loops.bot_presence import BotPresence
from cogs.loops.daily_loop import DailyLoop from cogs.loops.daily_loop import DailyLoop
def setup(bot: commands.Bot): def setup(bot: commands.Bot) -> None:
""" """Adds functions to bot."""
Adds functions to bot.
"""
bot.add_cog(BotPresence(bot)) bot.add_cog(BotPresence(bot))
bot.add_cog(DailyLoop(bot)) bot.add_cog(DailyLoop(bot))
+6 -9
View File
@@ -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 import nextcord
from nextcord.ext import commands, tasks from nextcord.ext import commands, tasks
@@ -17,23 +15,22 @@ class BotPresence(commands.Cog, name="Bot Presence"):
self.bot = bot self.bot = bot
@commands.Cog.listener() @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.""" """Listener for Guild Join. Updated presence on guild join."""
await self.change_presence() await self.change_presence()
@commands.Cog.listener() @commands.Cog.listener()
async def on_ready(self): async def on_ready(self) -> None:
"""Starts the loop when bot is ready.""" """Starts the loop when bot is ready."""
await self.bot.wait_until_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()
self.periodic_change_presence.start() # pylint: disable=no-member
@tasks.loop(minutes=5) @tasks.loop(minutes=5)
async def periodic_change_presence(self): async def periodic_change_presence(self) -> None:
"""Loops every 5 minutes and changes presence.""" """Loops every 5 minutes and changes presence."""
await self.change_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.""" """Randomly chooses a presence and updates bot's presence."""
with Session(engine) as session: 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()))
+27 -25
View File
@@ -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 import random
from datetime import UTC, datetime, time from datetime import UTC, datetime, time
from typing import TYPE_CHECKING
import nextcord
from nextcord.ext import commands, tasks from nextcord.ext import commands, tasks
from pytz import timezone from pytz import timezone
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from classes.election_cycle import ElectionCycle from classes.election_cycle import ElectionCycle
from config import DakinyConfig
from database_setup.sql_data_classes import UserBirthday, engine from database_setup.sql_data_classes import UserBirthday, engine
from main import birthday_list from main import birthday_list
from utils import get_current_election_cycle 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 DailyLoop(commands.Cog, name="Daily Loop"):
""" """Class for performing daily functions."""
Class for performing daily functions.
"""
def __init__(self, bot: commands.Bot) -> None: def __init__(self, bot: commands.Bot) -> None:
self.bot = bot 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. # pylint doesn't recognize that daily_loop is subclassed from tasks.loop.
self.daily_loop.start() # pylint: disable=no-member self.daily_loop.start() # pylint: disable=no-member
@tasks.loop(time=time(hour=0, tzinfo=timezone("GMT"))) @tasks.loop(time=time(hour=0, tzinfo=timezone("UTC")))
async def daily_loop(self): 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 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. election is over to delete the current election object.
""" """
@@ -48,18 +52,16 @@ class DailyLoop(commands.Cog, name="Daily Loop"):
self.update_birthdays() self.update_birthdays()
if 239547993106284544 in birthday_list: if KANDY in birthday_list:
# inform kandy that it is her "birthday" # 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 kandy.send('Happy "Birthday"!')
await self.cakedays() await self.cakedays()
@daily_loop.before_loop @daily_loop.before_loop
async def before_loop(self): 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."""
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() await self.bot.wait_until_ready()
# Bot needs to know guilds to get the current cycle. # 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: def get_election_cycle(self) -> ElectionCycle:
""" """
Creates an object for the current election cycle if one is active. An election is considered active Creates an object for the current election cycle if one is active.
between the election start and the term start.
An election is considered active between the election start and the term start.
Returns: Returns:
ElectionCycle: Current election cycle, None if there is no election happening. 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)) return ElectionCycle(cycle, config, self.bot.get_guild(config.params.guild_id))
def update_birthdays(self) -> None: 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() birthday_list.clear()
# kandy's 1% chance # 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) birthday_list.append(239547993106284544)
today = datetime.now(tz=UTC).date() today = datetime.now(tz=UTC).date()
@@ -102,8 +103,9 @@ class DailyLoop(commands.Cog, name="Daily Loop"):
async def cakedays(self) -> None: async def cakedays(self) -> None:
""" """
Loops through the guild's humans checking for joined_at dates that match today's date. Sends a message Loops through the guild's humans checking for joined_at dates that match today's date.
saying happy cakeday.
Sends a message saying happy cakeday.
""" """
today = datetime.now(tz=UTC).date() today = datetime.now(tz=UTC).date()
guild: nextcord.Guild = self.bot.get_guild(self.bot.config.params.guild_id) guild: nextcord.Guild = self.bot.get_guild(self.bot.config.params.guild_id)
+4 -6
View File
@@ -1,6 +1,4 @@
""" """Defines cog for on message listener."""
Defines cog for on message listener.
"""
import nextcord import nextcord
from nextcord.ext import commands from nextcord.ext import commands
@@ -15,7 +13,7 @@ class SubmissionListener(commands.Cog, name="Message Listener"):
self.bot = bot self.bot = bot
@commands.Cog.listener() @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.""" """Listener for message creation. Adds score and attachments to submission."""
# don't want to process any messages from bot. # don't want to process any messages from bot.
if message.author.id == self.bot.user.id: 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. # This function will be called when this extension is loaded.
# It is necessary to add these functions to the bot. # It is necessary to add these functions to the bot.
def setup(bot: commands.Bot): def setup(bot: commands.Bot) -> None:
"""Adds function to bot""" """Adds function to bot."""
bot.add_cog(SubmissionListener(bot)) bot.add_cog(SubmissionListener(bot))
+3 -1
View File
@@ -51,6 +51,7 @@ exclude = [
"node_modules", "node_modules",
"site-packages", "site-packages",
"venv", "venv",
"dakiny-messages", # Don't need to lint this git repo.
] ]
# Same as Black. # Same as Black.
@@ -67,9 +68,10 @@ target-version = "py311"
select = ["ALL"] select = ["ALL"]
ignore = [ ignore = [
"ANN101", "ANN101",
"B008", # I build SlashOptions as arguments.
"COM812", "COM812",
"COM819", "COM819",
"D107", # don't want docstrings on init functions "D107", # don't want docstrings on init functions
"D203", "D203",
"D206", "D206",
"D212", "D212",