aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--cogs/plot.py128
-rw-r--r--requirements.txt8
3 files changed, 138 insertions, 1 deletions
diff --git a/.gitignore b/.gitignore
index 72bed63..511ed04 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,4 +5,5 @@ cogs/__pycache__
5cogs/music/__pycache__ 5cogs/music/__pycache__
6data/ 6data/
7.idea/ 7.idea/
8.venv/ \ No newline at end of file 8.venv/
9.zed
diff --git a/cogs/plot.py b/cogs/plot.py
new file mode 100644
index 0000000..1516bc1
--- /dev/null
+++ b/cogs/plot.py
@@ -0,0 +1,128 @@
1# This file is part of Senator Sperma
2# Licensed under the GPL-3.0 License
3# Copyright (C) 2026 Salmonik @ salmonik@tuta.io
4# https://codeberg.org/salmonik/senator-sperma
5
6import asyncio
7import logging
8import math
9import os
10import re
11
12import discord
13from discord.ext import commands
14
15logger = logging.getLogger(__name__)
16
17try:
18 import matplotlib
19
20 matplotlib.use("Agg")
21 import matplotlib.pyplot as plt
22
23 PLOT_AVAILABLE = True
24except ImportError:
25 PLOT_AVAILABLE = False
26 logger.warning("matplotlib not available, plot command will be disabled")
27
28
29def parse_expression(expr: str) -> str:
30 """Convert user expression (x^3) to Python (x**3)"""
31 expr = expr.replace("^", "**")
32 expr = re.sub(r"(\d)([x])", r"\1*\2", expr)
33 expr = re.sub(r"([x])(\d)", r"\1*\2", expr)
34 return expr
35
36
37class Plot(commands.Cog):
38 def __init__(self, bot):
39 self.bot = bot
40
41 def _generate_plot(self, func_expr: str, output_path: str):
42 """Generate plot (runs in executor)"""
43 parsed_expr = parse_expression(func_expr)
44
45 x = [i * 0.1 for i in range(-100, 101)]
46 y = []
47
48 for xi in x:
49 try:
50 result = eval(
51 parsed_expr,
52 {
53 "x": xi,
54 "math": math,
55 "sin": math.sin,
56 "cos": math.cos,
57 "tan": math.tan,
58 "sqrt": math.sqrt,
59 "log": math.log,
60 "exp": math.exp,
61 "pi": math.pi,
62 "e": math.e,
63 },
64 )
65 if math.isfinite(result):
66 y.append(result)
67 else:
68 y.append(float("nan"))
69 except Exception:
70 y.append(float("nan"))
71
72 fig, ax = plt.subplots(figsize=(10, 6))
73 ax.plot(x, y, linewidth=2, color="#00ff00")
74 ax.set_title(f"y = {func_expr}", fontsize=14)
75 ax.set_xlabel("x", fontsize=12)
76 ax.set_ylabel("y", fontsize=12)
77 ax.grid(True, alpha=0.3)
78 ax.axhline(y=0, color="white", linewidth=0.5)
79 ax.axvline(x=0, color="white", linewidth=0.5)
80 ax.set_facecolor("#1a1a1a")
81 fig.patch.set_facecolor("#1a1a1a")
82
83 plt.savefig(output_path, dpi=100, bbox_inches="tight")
84 plt.close()
85
86 @commands.slash_command(
87 name="funkcja", description="Generuje wykres funkcji matematycznej"
88 )
89 async def plot(
90 self,
91 ctx,
92 funkcja: str = discord.Option(
93 str, "Funkcja np. x^3, sin(x), x^2 + 2*x - 1", required=True
94 ),
95 ):
96 if not PLOT_AVAILABLE:
97 await ctx.respond(
98 "Generowanie wykresów nie jest dostępne (brak matplotlib)",
99 ephemeral=True,
100 )
101 return
102
103 await ctx.defer()
104
105 temp_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "temp")
106 os.makedirs(temp_dir, exist_ok=True)
107 temp_path = os.path.join(
108 temp_dir, f"plot_{os.getpid()}_{asyncio.get_event_loop().time()}.png"
109 )
110
111 try:
112 loop = asyncio.get_event_loop()
113 await loop.run_in_executor(None, self._generate_plot, funkcja, temp_path)
114 await ctx.respond(file=discord.File(temp_path, filename="plot.png"))
115 logger.info(f"Generated plot for function: {funkcja}")
116 except Exception as e:
117 logger.error(f"Plot generation error: {e}")
118 await ctx.respond(f"Błąd generowania wykresu: {str(e)}", ephemeral=True)
119 finally:
120 try:
121 if os.path.exists(temp_path):
122 os.unlink(temp_path)
123 except Exception as e:
124 logger.error(f"Error cleaning up temp file: {e}")
125
126
127def setup(bot):
128 bot.add_cog(Plot(bot))
diff --git a/requirements.txt b/requirements.txt
index e5dc093..863badd 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -10,22 +10,30 @@ certifi==2026.2.25
10cffi==2.0.0 10cffi==2.0.0
11charset-normalizer==3.4.6 11charset-normalizer==3.4.6
12click==8.1.8 12click==8.1.8
13contourpy==1.3.3
13croniter==6.2.2 14croniter==6.2.2
15cycler==0.12.1
14davey==0.1.4 16davey==0.1.4
15dotenv==0.9.9 17dotenv==0.9.9
18fonttools==4.62.1
16frozenlist==1.8.0 19frozenlist==1.8.0
17gTTS==2.5.4 20gTTS==2.5.4
18h11==0.16.0 21h11==0.16.0
19httpcore==1.0.9 22httpcore==1.0.9
20httpx==0.28.1 23httpx==0.28.1
21idna==3.11 24idna==3.11
25kiwisolver==1.5.0
26matplotlib==3.10.8
22multidict==6.7.1 27multidict==6.7.1
23mutagen==1.47.0 28mutagen==1.47.0
29numpy==2.4.4
30packaging==26.1
24pillow==12.2.0 31pillow==12.2.0
25propcache==0.4.1 32propcache==0.4.1
26py-cord @ file:///home/salmon/Documents/botcode/senator-sperma/pycord 33py-cord @ file:///home/salmon/Documents/botcode/senator-sperma/pycord
27pycparser==3.0 34pycparser==3.0
28PyNaCl==1.6.2 35PyNaCl==1.6.2
36pyparsing==3.3.2
29python-dateutil==2.9.0.post0 37python-dateutil==2.9.0.post0
30python-dotenv==1.2.2 38python-dotenv==1.2.2
31pytube==15.0.0 39pytube==15.0.0