# 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__) _plt_module = None _plot_available = False def _ensure_plot(): global _plt_module, _plot_available if _plt_module is not None: return _plot_available try: import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as _plt_module _plot_available = True except ImportError: _plot_available = False logger.warning("matplotlib not available, plot command will be disabled") return _plot_available def parse_expression(expr: str) -> str: 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): global _plt_module if not _ensure_plot(): raise RuntimeError("matplotlib not available") plt = _plt_module 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 _ensure_plot(): await ctx.respond("Generowanie wykresów nie jest dostępne (brak matplotlib)", ephemeral=True) return await ctx.defer() from cogs.utils.temp import get_temp_dir temp_path = os.path.join(get_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))