Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ballsdex] Add long comments to most functions #498

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
35 changes: 34 additions & 1 deletion ballsdex/core/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,19 @@


def owner_check(ctx: commands.Context[BallsDexBot]):
"""
Checks who the owner of the bot is.
"""
return ctx.bot.is_owner(ctx.author)


class Translator(app_commands.Translator):
async def translate(
self, string: locale_str, locale: Locale, context: TranslationContextTypes
) -> str | None:
"""
Translate the given string to the specified locale.
"""
return string.message.replace("countryball", settings.collectible_name).replace(
"BallsDex", settings.bot_name
)
Expand Down Expand Up @@ -175,6 +181,9 @@ def __init__(
self.owner_ids: set

async def start_prometheus_server(self):
"""
Start the Prometheus server for metrics.
"""
self.prometheus_server = PrometheusServer(
self, settings.prometheus_host, settings.prometheus_port
)
Expand All @@ -183,6 +192,9 @@ async def start_prometheus_server(self):
def assign_ids_to_app_groups(
self, group: app_commands.Group, synced_commands: list[app_commands.AppCommandGroup]
):
"""
Assign the IDs to the app commands in the group.
"""
for synced_command in synced_commands:
bot_command = group.get_command(synced_command.name)
if not bot_command:
Expand All @@ -194,6 +206,9 @@ def assign_ids_to_app_groups(
)

def assign_ids_to_app_commands(self, synced_commands: list[app_commands.AppCommand]):
"""
Assign the IDs to the app commands.
"""
for synced_command in synced_commands:
bot_command = self.tree.get_command(synced_command.name, type=synced_command.type)
if not bot_command:
Expand All @@ -205,6 +220,9 @@ def assign_ids_to_app_commands(self, synced_commands: list[app_commands.AppComma
)

async def load_cache(self):
"""
Load the cache with database models and format a summary table.
"""
table = Table(box=box.SIMPLE)
table.add_column("Model", style="cyan")
table.add_column("Count", justify="right", style="green")
Expand Down Expand Up @@ -244,7 +262,9 @@ async def load_cache(self):
console.print(table)

async def gateway_healthy(self) -> bool:
"""Check whether or not the gateway proxy is ready and healthy."""
"""
Check whether or not the gateway proxy is ready and healthy.
"""
if settings.gateway_url is None:
raise RuntimeError("This is only available on the production bot instance.")

Expand All @@ -261,6 +281,9 @@ async def gateway_healthy(self) -> bool:
return False

async def setup_hook(self) -> None:
"""
Setup the bot before starting up.
"""
await self.tree.set_translator(Translator())
log.info("Starting up with %s shards...", self.shard_count)
if settings.gateway_url is None:
Expand Down Expand Up @@ -361,6 +384,9 @@ async def on_ready(self):
)

async def blacklist_check(self, interaction: discord.Interaction) -> bool:
"""
Check if the user or guild is blacklisted from the bot.
"""
if interaction.user.id in self.blacklist:
if interaction.type != discord.InteractionType.autocomplete:
await interaction.response.send_message(
Expand Down Expand Up @@ -391,6 +417,9 @@ async def blacklist_check(self, interaction: discord.Interaction) -> bool:
async def on_command_error(
self, context: commands.Context, exception: commands.errors.CommandError
):
"""
Handle command errors, and give feedback to the user.
"""
if isinstance(exception, (commands.CommandNotFound, commands.DisabledCommand)):
return

Expand Down Expand Up @@ -433,6 +462,10 @@ async def on_command_error(
async def on_application_command_error(
self, interaction: discord.Interaction, error: app_commands.AppCommandError
):
"""
Handle errors in application commands and give feedback to the user.
"""

async def send(content: str):
if interaction.response.is_done():
await interaction.followup.send(content, ephemeral=True)
Expand Down
7 changes: 5 additions & 2 deletions ballsdex/core/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,15 @@ async def ping(self, ctx: commands.Context):
@commands.is_owner()
async def reloadtree(self, ctx: commands.Context):
"""
Sync the application commands with Discord
Sync the application commands with Discord.
"""
await self.bot.tree.sync()
await ctx.send("Application commands tree reloaded.")

async def reload_package(self, package: str, *, with_prefix=False):
"""
Reload a package by name.
"""
try:
try:
await self.bot.reload_extension(package)
Expand All @@ -50,7 +53,7 @@ async def reload_package(self, package: str, *, with_prefix=False):
@commands.is_owner()
async def reload(self, ctx: commands.Context, package: str):
"""
Reload an extension
Reload an extension.
"""
try:
await self.reload_package(package)
Expand Down
51 changes: 42 additions & 9 deletions ballsdex/core/dev.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,18 @@


def box(text: str, lang: str = "") -> str:
"""
Formats a given text string inside a code block for Discord.
"""
return f"```{lang}\n{text}\n```"


def text_to_file(
text: str, filename: str = "file.txt", *, spoiler: bool = False, encoding: str = "utf-8"
) -> discord.File:
"""
Converts a string of text into a Discord file object for attachment.
"""
file = BytesIO(text.encode(encoding))
return discord.File(file, filename, spoiler=spoiler)

Expand Down Expand Up @@ -103,6 +109,9 @@ async def send_interactive(
result = 0

def predicate(m: discord.Message):
"""
Evaluates whether a given Discord message satisfies specific criteria.
"""
nonlocal result
if (ctx.author.id != m.author.id) or ctx.channel.id != m.channel.id:
return False
Expand Down Expand Up @@ -163,7 +172,9 @@ def predicate(m: discord.Message):


class Dev(commands.Cog):
"""Various development focused utilities."""
"""
Various development focused utilities.
"""

def __init__(self):
super().__init__()
Expand All @@ -173,10 +184,17 @@ def __init__(self):

@staticmethod
def async_compile(source, filename, mode):
"""
Compiles the given source code into a code object that supports `await` expressions.
"""
return compile(source, filename, mode, flags=ast.PyCF_ALLOW_TOP_LEVEL_AWAIT, optimize=0)

@staticmethod
async def maybe_await(coro):
"""
Resolves a coroutine or value, returning the final result. If the input is a coroutine,
it is awaited up to two times.
"""
for i in range(2):
if inspect.isawaitable(coro):
coro = await coro
Expand All @@ -186,7 +204,9 @@ async def maybe_await(coro):

@staticmethod
def cleanup_code(content):
"""Automatically removes code blocks from the code."""
"""
Automatically removes code blocks from the code.
"""
# remove ```py\n```
if content.startswith("```") and content.endswith("```"):
return START_CODE_BLOCK_RE.sub("", content)[:-3]
Expand All @@ -196,7 +216,8 @@ def cleanup_code(content):

@classmethod
def get_syntax_error(cls, e):
"""Format a syntax error to send to the user.
"""
Format a syntax error to send to the user.

Returns a string representation of the error formatted as a codeblock.
"""
Expand All @@ -208,16 +229,24 @@ def get_syntax_error(cls, e):

@staticmethod
def get_pages(msg: str):
"""Pagify the given message for output to the user."""
"""
Pagify the given message for output to the user.
"""
return pagify(msg, delims=["\n", " "], priority=True, shorten_by=10)

@staticmethod
def sanitize_output(ctx: commands.Context, input_: str) -> str:
"""Hides the bot's token from a string."""
"""
Hides the bot's token from a string.
"""
token = ctx.bot.http.token
return re.sub(re.escape(token), "[EXPUNGED]", input_, re.I)

def get_environment(self, ctx: commands.Context) -> dict:
"""
Constructs an execution environment for dynamic code evaluation. The environment includes
references to various bot-related objects, models, policies, and utility functions.
"""
env = {
"bot": ctx.bot,
"ctx": ctx,
Expand Down Expand Up @@ -263,7 +292,8 @@ def get_environment(self, ctx: commands.Context) -> dict:
@commands.command()
@commands.is_owner()
async def debug(self, ctx: commands.Context, *, code):
"""Evaluate a statement of python code.
"""
Evaluate a statement of python code.

The bot will always respond with the return value of the code.
If the return value of the code is a coroutine, it will be awaited,
Expand Down Expand Up @@ -305,7 +335,8 @@ async def debug(self, ctx: commands.Context, *, code):
@commands.command(name="eval")
@commands.is_owner()
async def _eval(self, ctx: commands.Context, *, body: str):
"""Execute asynchronous code.
"""
Execute asynchronous code.

This command wraps code into the body of an async function and then
calls and awaits it. The bot will respond with anything printed to
Expand Down Expand Up @@ -359,7 +390,8 @@ async def _eval(self, ctx: commands.Context, *, body: str):
@commands.command()
@commands.is_owner()
async def mock(self, ctx: commands.Context, user: discord.Member, *, command):
"""Mock another user invoking a command.
"""
Mock another user invoking a command.

The prefix must not be entered.
"""
Expand All @@ -372,7 +404,8 @@ async def mock(self, ctx: commands.Context, user: discord.Member, *, command):
@commands.command(name="mockmsg")
@commands.is_owner()
async def mock_msg(self, ctx: commands.Context, user: discord.Member, *, content: str):
"""Dispatch a message event as if it were sent by a different user.
"""
Dispatch a message event as if it were sent by a different user.

Only reads the raw content of the message. Attachments, embeds etc. are
ignored.
Expand Down
3 changes: 3 additions & 0 deletions ballsdex/core/image_generator/image_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@


def draw_card(ball_instance: "BallInstance", media_path: str = "./admin_panel/media/"):
"""
Draw the card for a countryball instance.
"""
ball = ball_instance.countryball
ball_health = (237, 115, 101, 255)
ball_credits = ball.credits
Expand Down
18 changes: 18 additions & 0 deletions ballsdex/core/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ def __init__(self, bot: "BallsDexBot", host: str = "localhost", port: int = 1526
)

async def collect_metrics(self):
"""
This function collects various metrics about the bot's performance
and activity for Prometheus monitoring. It categorizes the guilds the bot is in based
on their member count, grouping them by powers of ten.
"""
guilds: dict[int, int] = defaultdict(int)
for guild in self.bot.guilds:
if not guild.member_count:
Expand All @@ -82,24 +87,37 @@ async def collect_metrics(self):
self.asyncio_delay.observe((t2 - t1).total_seconds() - 1)

async def get(self, request: web.Request) -> web.Response:
"""
This function handles incoming HTTP GET requests to the Prometheus metrics endpoint.
"""
log.debug("Request received")
await self.collect_metrics()
response = web.Response(body=generate_latest())
response.content_type = CONTENT_TYPE_LATEST
return response

async def setup(self):
"""
This function initializes and configures the Prometheus server.
"""
self.runner = web.AppRunner(self.app)
await self.runner.setup()
self.site = web.TCPSite(self.runner, host=self.host, port=self.port)
self._inited = True

async def run(self):
"""
This function starts the Prometheus server after setting it up. It ensures the server is
non-blocking, allowing other operations to continue while the server runs.
"""
await self.setup()
await self.site.start() # this call isn't blocking
log.info(f"Prometheus server started on http://{self.site._host}:{self.site._port}/")

async def stop(self):
"""
This function stops the Prometheus server gracefully.
"""
if self._inited:
await self.site.stop()
await self.runner.cleanup()
Expand Down
4 changes: 4 additions & 0 deletions ballsdex/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@


def init_logger(disable_rich: bool = False, debug: bool = False) -> logging.handlers.QueueListener:
"""
Initializes and configures the logging system for the application. This includes setting up
handlers for streaming logs to the console and saving them to a file.
"""
formatter = logging.Formatter(
"[{asctime}] {levelname} {name}: {message}", datefmt="%Y-%m-%d %H:%M:%S", style="{"
)
Expand Down
5 changes: 5 additions & 0 deletions ballsdex/packages/admin/balls.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@


async def save_file(attachment: discord.Attachment) -> Path:
"""
Saves an uploaded Discord attachment to the server's file system.
Ensures that the file does not overwrite any existing files by appending
a numeric suffix to the filename if necessary.
"""
path = Path(f"./static/uploads/{attachment.filename}")
match = FILENAME_RE.match(attachment.filename)
if not match:
Expand Down
3 changes: 3 additions & 0 deletions ballsdex/packages/admin/menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ def __init__(self, entries: Iterable[BlacklistHistory], user_id: int, bot: "Ball
super().__init__(entries, per_page=1)

async def format_page(self, menu: Pages, blacklist: BlacklistHistory) -> discord.Embed:
"""
Formats an embed page to display blacklist history for a user.
"""
embed = discord.Embed(
title=f"Blacklist History for {self.header}",
description=f"Type: {blacklist.action_type}\nReason: {blacklist.reason}",
Expand Down
Loading
Loading