1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
|
# This file is part of Senator Sperma
# Licensed under the GPL-3.0 License
# Copyright (C) 2026 Salmonik @ salmonik@tuta.io
# https://codeberg.org/salmonik/senator-sperma
import asyncio
import logging
import math
import os
import re
import discord
from discord.ext import commands
logger = logging.getLogger(__name__)
try:
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
PLOT_AVAILABLE = True
except ImportError:
PLOT_AVAILABLE = False
logger.warning("matplotlib not available, plot command will be disabled")
def parse_expression(expr: str) -> str:
"""Convert user expression (x^3) to Python (x**3)"""
expr = expr.replace("^", "**")
expr = re.sub(r"(\d)([x])", r"\1*\2", expr)
expr = re.sub(r"([x])(\d)", r"\1*\2", expr)
return expr
class Plot(commands.Cog):
def __init__(self, bot):
self.bot = bot
def _generate_plot(self, func_expr: str, output_path: str):
"""Generate plot (runs in executor)"""
parsed_expr = parse_expression(func_expr)
x = [i * 0.1 for i in range(-100, 101)]
y = []
for xi in x:
try:
result = eval(
parsed_expr,
{
"x": xi,
"math": math,
"sin": math.sin,
"cos": math.cos,
"tan": math.tan,
"sqrt": math.sqrt,
"log": math.log,
"exp": math.exp,
"pi": math.pi,
"e": math.e,
},
)
if math.isfinite(result):
y.append(result)
else:
y.append(float("nan"))
except Exception:
y.append(float("nan"))
fig, ax = plt.subplots(figsize=(10, 6))
ax.plot(x, y, linewidth=2, color="#00ff00")
ax.set_title(f"y = {func_expr}", fontsize=14)
ax.set_xlabel("x", fontsize=12)
ax.set_ylabel("y", fontsize=12)
ax.grid(True, alpha=0.3)
ax.axhline(y=0, color="white", linewidth=0.5)
ax.axvline(x=0, color="white", linewidth=0.5)
ax.set_facecolor("#1a1a1a")
fig.patch.set_facecolor("#1a1a1a")
plt.savefig(output_path, dpi=100, bbox_inches="tight")
plt.close()
@commands.slash_command(
name="funkcja", description="Generuje wykres funkcji matematycznej"
)
async def plot(
self,
ctx,
funkcja: str = discord.Option(
str, "Funkcja np. x^3, sin(x), x^2 + 2*x - 1", required=True
),
):
if not PLOT_AVAILABLE:
await ctx.respond(
"Generowanie wykresów nie jest dostępne (brak matplotlib)",
ephemeral=True,
)
return
await ctx.defer()
temp_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "temp")
os.makedirs(temp_dir, exist_ok=True)
temp_path = os.path.join(
temp_dir, f"plot_{os.getpid()}_{asyncio.get_event_loop().time()}.png"
)
try:
loop = asyncio.get_event_loop()
await loop.run_in_executor(None, self._generate_plot, funkcja, temp_path)
await ctx.respond(file=discord.File(temp_path, filename="plot.png"))
logger.info(f"Generated plot for function: {funkcja}")
except Exception as e:
logger.error(f"Plot generation error: {e}")
await ctx.respond(f"Błąd generowania wykresu: {str(e)}", ephemeral=True)
finally:
try:
if os.path.exists(temp_path):
os.unlink(temp_path)
except Exception as e:
logger.error(f"Error cleaning up temp file: {e}")
def setup(bot):
bot.add_cog(Plot(bot))
|