Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions src/bot/cogs/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
LATEX_COLORS = {
"emerald": "Emerald",
"violet": "Violet",
"blueviolet": "BlueViolet",
"skyblue": "SkyBlue",
"wildstrawberry": "WildStrawberry",
"red": "Red",
"periwinkle": "Periwinkle",
"sepia": "Sepia",
"magenta": "Magenta",
"peach": "Peach",
"cyan": "Cyan",
"bluegreen": "BlueGreen",
"orangered": "OrangeRed",
"limegreen": "LimeGreen",
"royalpurple": "RoyalPurple",
"fuchsia": "Fuchsia",
"teal": "Teal",
"cornflowerblue": "CornflowerBlue",
"turquoise": "Turquoise",
"yelloworange": "YellowOrange",
"mulberry": "Mulberry",
"white": "White",
"violetred": "VioletRed",
"brown": "Brown",
"redviolet": "RedViolet",
"tealblue": "TealBlue",
"green": "Green",
"navyblue": "NavyBlue",
"tan": "Tan",
"carnationpink": "CarnationPink",
"burntorange": "BurntOrange",
"maroon": "Maroon",
"goldenrod": "Goldenrod",
"orchid": "Orchid",
"rubinered": "RubineRed",
"processblue": "ProcessBlue",
"olivegreen": "OliveGreen",
"yellowgreen": "YellowGreen",
"salmon": "Salmon",
"junglegreen": "JungleGreen",
"rawsienna": "RawSienna",
"thistle": "Thistle",
"greenyellow": "GreenYellow",
"melon": "Melon",
"pinegreen": "PineGreen",
"lavender": "Lavender",
"aquamarine": "Aquamarine",
"springgreen": "SpringGreen",
"darkred": "DarkRed",
"darkblue": "DarkBlue",
"forestgreen": "ForestGreen",
"darkorchid": "DarkOrchid",
"orange": "Orange",
"yellow": "Yellow",
"royalblue": "RoyalBlue",
"gray": "Gray",
"midnightblue": "MidnightBlue",
"redorange": "RedOrange",
"darkorange": "DarkOrange",
"cadetblue": "CadetBlue",
"cerulean": "Cerulean",
"apricot": "Apricot",
"mahogany": "Mahogany",
"bittersweet": "Bittersweet",
"blue": "Blue",
"purple": "Purple",
"darkgreen": "DarkGreen",
"plum": "Plum",
"dandelion": "Dandelion",
"seagreen": "SeaGreen",
"black": "Black",
"brickred": "BrickRed",
}
155 changes: 155 additions & 0 deletions src/bot/cogs/features/latex.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
"""
LaTeX command to generate mathematical equations.
"""

import logging
from pathlib import Path

import aiohttp
import discord
from discord import app_commands
from discord.ext import commands

from ..constants import LATEX_COLORS

logger = logging.getLogger(__name__)


def embed_info(title: str, message: str, image_url: str | None = None) -> discord.Embed:
"""
Embedding for general things
"""
embed = discord.Embed(
title=title, description=message, color=discord.Color.dark_purple()
)

if image_url:
embed.set_image(url=image_url)

return embed


class LaTeX(commands.Cog):
"""
# Generates a LaTeX equation image using the CodeCogs API and displays it in an embed.
"""

def __init__(self, bot: commands.Bot):
self.bot = bot
self.session: aiohttp.ClientSession | None = None

async def cog_load(self) -> None:
self.session = aiohttp.ClientSession()

async def cog_unload(self) -> None:
if self.session:
await self.session.close()

@app_commands.command(description="Show the available LaTeX colors.")
async def latex_colors(self, interaction: discord.Interaction) -> None:
file = discord.File(
Path.cwd().joinpath("src", "bot", "static", "images", "colors.png"),
filename="colors.png",
)
embed = embed_info(
"LaTeX Colors",
"The following colors are available:",
"attachment://colors.png",
)
await interaction.response.send_message(file=file, embed=embed, ephemeral=True)

async def latex_available(self, image_path: str) -> bool:
try:
if not self.session:
return False
async with self.session.get(image_path) as response:
return response.status == 200
except aiohttp.ClientError:
logger.exception("Failed to fetch LaTeX image from CodeCogs")
return False

def normalize_color(self, value: str, default: str) -> str:
normalized = value.strip().replace(" ", "").lower()
return LATEX_COLORS.get(normalized, default)

@app_commands.command()
async def latex(
self,
interaction: discord.Interaction,
equation: str,
*,
bg_color: str = "Black",
txt_color: str = "White",
dpi: int = 300,
) -> None:
"""
Send a mathematical equation using LaTeX.

Parameters
----------
equation : LaTeX equation (do not include `$` delimiters).
bg_color : Background color. See `/latex_colors` for available colors.
txt_color : Text color. See `/latex_colors` for available colors.
dpi : Image resolution in DPI (200–500).
"""
logger.info(
"%s used the %s command.",
interaction.user.display_name,
interaction.command,
)

# check for valid user specified color for text and background
bg_color = self.normalize_color(bg_color, "Black")
txt_color = self.normalize_color(txt_color, "White")
if txt_color == bg_color:
txt_color = "White" if bg_color == "Black" else "Black"

# handle necessary character replacements
character_replacements = {
"\n": r"\n",
"\r": r"\r",
"\t": r"\t",
"\x08": r"\b",
"\x0c": r"\f",
"\x0b": r"\v",
"\x07": r"\a",
"%": r"\%",
"&": r"\&",
"#": r"%23",
" ": "&space;",
}

for e, c in character_replacements.items():
equation = equation.replace(e, c)

# prevent user specified dpi from being too big/small
dpi = max(200, min(dpi, 500))

image_path = (
rf"https://latex.codecogs.com/png.image?"
rf"\dpi{{{dpi}}}"
rf"\color{{{txt_color}}}"
r"\setlength{\fboxsep}{6pt}"
r"\setlength{\fboxrule}{0pt}"
rf"\colorbox{{{bg_color}}}{{${equation}$}}"
)

if await self.latex_available(image_path):
embed = embed_info(
"LaTeX",
equation.replace("&space;", " "),
image_path,
)
# Send the embed with the image
await interaction.response.send_message(embed=embed)
else:
await interaction.response.send_message(
f"Failed to fetch the image ({image_path}) from the API", ephemeral=True
)


async def setup(bot: commands.Bot) -> None:
"""
Required.
"""
await bot.add_cog(LaTeX(bot))
Binary file added src/bot/static/images/colors.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading