aboutsummaryrefslogtreecommitdiffstats
path: root/cogs/plot.py
blob: 65dcae4649b4be97e7fb6a8aa73fbc5902291eaf (plain) (blame)
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
# 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))