Files
conjurer/latex_commands.py
T

133 lines
6.6 KiB
Python

# latex_commands.py
# -*- coding: utf-8 -*-
from __future__ import annotations
import io
from typing import Optional, List
import discord
from discord import app_commands
from discord.ext import commands
from constants import (
LATEX_TEX_ENGINE, LATEX_MAX_COMPILE_SECONDS, LATEX_MAX_ATTACH_MB, LATEX_MAX_ZIP_MB,
OPENAI_API_KEY, OPENAI_MODEL, LATEX_ALLOWED_ROLES, LATEX_GUILD_ID
)
from latex_functions import (
is_tex_attachment, compile_single_tex_bytes, compile_zip_to_zip, ask_openai_diagnosis
)
class LatexModule(commands.Cog):
def __init__(self, bot: commands.Bot, logger_name: str):
self.bot = bot
import logging
self.logger = logging.getLogger(logger_name)
# -------- /tex (PDF + diagnoza; log -> debug logger) --------
@commands.hybrid_command(
nsfw=False,
name="tex",
description="Kompiluje załączone .tex; PDF-y odsyła. Błędy: diagnoza z OpenAI.",
guild=None if LATEX_GUILD_ID is None else discord.Object(id=LATEX_GUILD_ID),
)
@commands.has_any_role(*LATEX_ALLOWED_ROLES)
async def tex(self, ctx: commands.Context, include_source_excerpt: Optional[bool] = True):
ch = ctx.message.channel
async with ch.typing():
atts = [a for a in (ctx.message.attachments or []) if is_tex_attachment(a, LATEX_MAX_ATTACH_MB)]
if not atts:
return await ctx.reply(f"Dołącz .tex (≤{LATEX_MAX_ATTACH_MB} MiB).", mention_author=False)
files: List[discord.File] = []
parts: List[str] = []
for att in atts:
try:
data = await att.read()
res = await compile_single_tex_bytes(data, att.filename, LATEX_TEX_ENGINE, LATEX_MAX_COMPILE_SECONDS, logger=self.logger)
self.logger.debug("LaTeX log (%s)\n%s", att.filename, (res["log_text"] or "")[-2000:])
if res["ok"] and res["pdf_bytes"]:
b = io.BytesIO(res["pdf_bytes"])
b.seek(0)
b.name = res["pdf_name"]
files.append(discord.File(b, filename=res["pdf_name"]))
parts.append(f"✅ `{att.filename}` → `{res['pdf_name']}`")
else:
excerpt = ""
if include_source_excerpt:
try:
excerpt = data.decode("utf-8", errors="ignore")[:4000]
except Exception:
excerpt = ""
advice = await ask_openai_diagnosis(res["log_text"] or "", excerpt, OPENAI_API_KEY, OPENAI_MODEL, logger=self.logger)
parts.append(f"❌ `{att.filename}` — błąd kompilacji.\nDiagnoza:\n{advice}")
except Exception as e:
self.logger.debug("Exception %s: %s", att.filename, e)
parts.append(f"⚠️ `{att.filename}` — wyjątek (szczegóły w logu).")
content = f"**LaTeX** — {len(atts)} plik(ów). Model: `{OPENAI_MODEL}`\n\n" + "\n\n".join(parts)
await ctx.reply(content if len(content)<1900 else content[:1900]+"…", files=files, mention_author=False)
# -------- /latexclean (PDF only; log -> debug logger) --------
@commands.hybrid_command(
nsfw=False,
name="latexclean",
description="Kompiluje .tex i odsyła TYLKO PDF (log w debug).",
guild=None if LATEX_GUILD_ID is None else discord.Object(id=LATEX_GUILD_ID),
)
@commands.has_any_role(*LATEX_ALLOWED_ROLES)
async def latexclean(self, ctx: commands.Context):
ch = ctx.message.channel
async with ch.typing():
atts = [a for a in (ctx.message.attachments or []) if is_tex_attachment(a, LATEX_MAX_ATTACH_MB)]
if not atts:
return await ctx.reply(f"Dołącz .tex (≤{LATEX_MAX_ATTACH_MB} MiB).", mention_author=False)
files: List[discord.File] = []
parts: List[str] = []
for att in atts:
try:
data = await att.read()
res = await compile_single_tex_bytes(data, att.filename, LATEX_TEX_ENGINE, LATEX_MAX_COMPILE_SECONDS, logger=self.logger)
self.logger.debug("LaTeX CLEAN (%s)\n%s", att.filename, (res["log_text"] or "")[-2000:])
if res["ok"] and res["pdf_bytes"]:
b = io.BytesIO(res["pdf_bytes"])
b.seek(0)
b.name = res["pdf_name"]
files.append(discord.File(b, filename=res["pdf_name"]))
parts.append(f"✅ `{att.filename}`")
else:
parts.append(f"❌ `{att.filename}` — błąd (log w debug).")
except Exception as e:
self.logger.debug("CLEAN exception %s: %s", att.filename, e)
parts.append(f"⚠️ `{att.filename}` — wyjątek (log w debug).")
await ctx.reply("**LaTeX clean** — " + ", ".join(parts), files=files, mention_author=False)
# -------- /texbatch (ZIP -> ZIP) --------
@app_commands.command(
name="texbatch",
description="Wyślij ZIP z .tex → odeślę ZIP z PDF-ami (tylko udane)."
)
@app_commands.describe(archive=f"Archiwum .zip (max ~{LATEX_MAX_ZIP_MB} MiB)")
@app_commands.checks.has_any_role(*LATEX_ALLOWED_ROLES)
async def texbatch(self, interaction: discord.Interaction, archive: discord.Attachment):
await interaction.response.defer()
if not archive or not archive.filename.lower().endswith(".zip"):
return await interaction.followup.send("Dołącz ZIP (.zip) z plikami .tex.", ephemeral=True)
if archive.size > LATEX_MAX_ZIP_MB * 1024 * 1024:
return await interaction.followup.send(f"ZIP > {LATEX_MAX_ZIP_MB} MiB — za duży.", ephemeral=True)
data = await archive.read()
out_zip_bytes, failed = await compile_zip_to_zip(data, LATEX_TEX_ENGINE, LATEX_MAX_COMPILE_SECONDS, logger=self.logger)
if not out_zip_bytes and failed:
return await interaction.followup.send("Brak PDF-ów. " + "; ".join(failed[:10]), ephemeral=True)
bio = io.BytesIO(out_zip_bytes)
bio.seek(0)
bio.name = "compiled_pdfs.zip"
if failed:
self.logger.debug("BATCH failed: %s", ", ".join(failed))
await interaction.followup.send("✅ Gotowe (ZIP w załączniku)." + (f" ❌ Błędy: {len(failed)}" if failed else ""), files=[discord.File(bio, filename=bio.name)])
async def setup(bot: commands.Bot):
await bot.add_cog(LatexModule(bot, logger_name="latex"))