Skip to content

Commit 5ebf153

Browse files
authored
Merge branch 'main' into facts-revamp
2 parents fee0111 + fc64eae commit 5ebf153

File tree

9 files changed

+111
-2
lines changed

9 files changed

+111
-2
lines changed

.env.example

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ PROD_COG_IGNORE_LIST="rolecount,mail,git" # Default ignores ATL-specific cogs
4242
# Sentry (Error Tracking)
4343
# SENTRY_URL=""
4444

45+
# Wolfram Alpha (Math/Science Queries)
46+
# MAKE SURE THIS IS FOR THE SIMPLE API OR IT WILL NOT WORK
47+
# WOLFRAM_APP_ID=""
48+
4549
# InfluxDB (Metrics/Logging)
4650
# ------------------
4751

DEVELOPER.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,6 @@ Explore the following pages for more detailed information on specific developmen
2626
* **[Database Controller Patterns](./docs/content/dev/database_patterns.md)**
2727
* Using controllers for CRUD, transactions, relations.
2828
* Best practices for database interactions in code.
29-
* **[Docker Environment](./docs/content/dev/docker_environment.md)** (Optional)
29+
* **[Docker Environment](./docs/content/dev/docker_development.md)** (Optional)
3030
* Setting up and using the Docker-based development environment.
3131
* Running commands within Docker containers.

tux/cog_loader.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ def __init__(self, bot: commands.Bot) -> None:
4444
"utility": 30,
4545
"info": 20,
4646
"fun": 10,
47+
"tools": 5,
4748
}
4849

4950
async def is_cog_eligible(self, filepath: Path) -> bool:

tux/cogs/services/influxdblogger.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ async def logger(self) -> None:
6666
guild_id = int(guild.guild_id)
6767

6868
# Collect data by querying controllers
69-
starboard_stats = await self.db.starboard_message.find_many(where={"guild_id": guild_id})
69+
starboard_stats = await self.db.starboard_message.find_many(where={"message_guild_id": guild_id})
7070

7171
snippet_stats = await self.db.snippet.find_many(where={"guild_id": guild_id})
7272

tux/cogs/tools/__init__.py

Whitespace-only changes.

tux/cogs/tools/wolfram.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import asyncio
2+
import io
3+
from urllib.parse import quote_plus
4+
5+
import aiohttp
6+
import discord
7+
from discord import app_commands
8+
from discord.ext import commands
9+
from loguru import logger
10+
from PIL import Image
11+
12+
from tux.bot import Tux
13+
from tux.ui.embeds import EmbedCreator
14+
from tux.utils.config import CONFIG
15+
16+
17+
class Wolfram(commands.Cog):
18+
def __init__(self, bot: Tux) -> None:
19+
self.bot = bot
20+
21+
# Verify AppID configuration; unload cog if missing
22+
if not CONFIG.WOLFRAM_APP_ID:
23+
logger.warning("Wolfram Alpha API ID is not set. Some Science/Math commands will not work.")
24+
# Store the task reference
25+
self._unload_task = asyncio.create_task(self._unload_self())
26+
else:
27+
logger.info("Wolfram Alpha API ID is set, Science/Math commands that depend on it will work.")
28+
29+
async def _unload_self(self):
30+
"""Unload this cog if configuration is missing."""
31+
try:
32+
await self.bot.unload_extension("tux.cogs.tools.wolfram")
33+
logger.info("Wolfram cog has been unloaded due to missing configuration")
34+
except Exception as e:
35+
logger.error(f"Failed to unload Wolfram cog: {e}")
36+
37+
@commands.hybrid_command(name="wolfram", description="Query Wolfram|Alpha Simple API and return an image result.")
38+
@app_commands.describe(
39+
query="The input query for Wolfram|Alpha, e.g. 'integrate x^2' or 'What is the capital of France?'",
40+
)
41+
async def wolfram(self, ctx: commands.Context[Tux], *, query: str) -> None:
42+
"""
43+
Send a query to Wolfram|Alpha Simple API and return the visual result.
44+
45+
Parameters
46+
----------
47+
ctx : commands.Context[Tux]
48+
Invocation context for the command.
49+
query : str
50+
Input string for the Wolfram|Alpha query, e.g. 'integrate x^2'.
51+
"""
52+
53+
await ctx.defer()
54+
55+
# Build the Simple API endpoint URL with URL-encoded query
56+
encoded = quote_plus(query)
57+
url = f"https://api.wolframalpha.com/v1/simple?appid={CONFIG.WOLFRAM_APP_ID}&i={encoded}"
58+
59+
try:
60+
# Perform async HTTP GET with a 10-second timeout
61+
timeout = aiohttp.ClientTimeout(total=10)
62+
async with aiohttp.ClientSession() as session, session.get(url, timeout=timeout) as resp:
63+
resp.raise_for_status()
64+
img_data = await resp.read()
65+
except Exception:
66+
# On error, notify user via an error embed
67+
embed = EmbedCreator.create_embed(
68+
bot=self.bot,
69+
embed_type=EmbedCreator.ERROR,
70+
user_name=ctx.author.name,
71+
user_display_avatar=ctx.author.display_avatar.url,
72+
description="Failed to retrieve image from Wolfram|Alpha Simple API.",
73+
)
74+
await ctx.send(embed=embed)
75+
return
76+
77+
# Crop the top 80 pixels from the fetched image
78+
image = Image.open(io.BytesIO(img_data))
79+
width, height = image.size
80+
cropped = image.crop((0, 80, width, height))
81+
buffer = io.BytesIO()
82+
cropped.save(buffer, format=image.format or "PNG")
83+
buffer.seek(0)
84+
image_file = discord.File(buffer, filename="wolfram.png")
85+
86+
embed = EmbedCreator.create_embed(
87+
bot=self.bot,
88+
embed_type=EmbedCreator.INFO,
89+
user_name=ctx.author.name,
90+
user_display_avatar=ctx.author.display_avatar.url,
91+
title=f"Wolfram|Alpha: {query}",
92+
)
93+
# Display the image via embed attachment URL
94+
embed.set_image(url="attachment://wolfram.png")
95+
await ctx.send(embed=embed, file=image_file)
96+
97+
98+
async def setup(bot: Tux) -> None:
99+
await bot.add_cog(Wolfram(bot))

tux/cogs/utility/poll.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ async def on_reaction_add(self, reaction: discord.Reaction, user: discord.User)
8181
and embed.author.name.startswith("Poll")
8282
and reaction.emoji not in [f"{num + 1}\u20e3" for num in range(9)]
8383
):
84+
await reaction.remove(user)
8485
await reaction.clear()
8586

8687
@app_commands.command(name="poll", description="Creates a poll.")

tux/help.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,7 @@ async def _create_category_options(self) -> list[discord.SelectOption]:
413413
"levels": "📈",
414414
"services": "🔌",
415415
"guild": "🏰",
416+
"tools": "🛠",
416417
}
417418

418419
options: list[discord.SelectOption] = []

tux/utils/config.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ def BOT_TOKEN(self) -> str: # noqa: N802
8484
# before this property is accessed.
8585
return get_bot_token() # Get token based on manager's current env
8686

87+
# Wolfram
88+
WOLFRAM_APP_ID: Final[str] = os.getenv("WOLFRAM_APP_ID", "")
89+
8790
# InfluxDB
8891
INFLUXDB_TOKEN: Final[str] = os.getenv("INFLUXDB_TOKEN", "")
8992
INFLUXDB_URL: Final[str] = os.getenv("INFLUXDB_URL", "")

0 commit comments

Comments
 (0)