This commit is contained in:
2025-08-16 19:12:47 +02:00
parent 6dc4c9d1b3
commit ab1ebed5f4
3 changed files with 110 additions and 41 deletions
+81 -27
View File
@@ -3,11 +3,10 @@
from __future__ import annotations from __future__ import annotations
import asyncio import asyncio
import logging
import re import re
import shlex
import tempfile import tempfile
import zipfile import zipfile
import logging
from pathlib import Path from pathlib import Path
from typing import Dict, List, Optional, Tuple from typing import Dict, List, Optional, Tuple
@@ -29,25 +28,67 @@ def is_tex_attachment(att, max_mb: int) -> bool:
) )
async def run_latex_two_passes( def _build_cmd(engine: str, tex_filename: str) -> list[str]:
tex_filename: str, workdir: Path, tex_engine: str, timeout_sec: int, logger:str=None base = (engine or "").strip().lower()
) -> Tuple[bool, str, Path]: if base in ("pdflatex", "xelatex", "lualatex"):
cmd = f"{tex_engine} -interaction=nonstopmode -halt-on-error -file-line-error -no-shell-escape {shlex.quote(tex_filename)}" return [
logger = logging.getLogger(logger) if logger else None base,
if logger: "-interaction=nonstopmode",
logger.debug("LaTeX cmd: %s (cwd=%s)", cmd, workdir) "-halt-on-error",
"-file-line-error",
"-no-shell-escape",
tex_filename,
]
if base == "latexmk":
return [
"latexmk",
"-pdf",
"-interaction=nonstopmode",
"-halt-on-error",
"-file-line-error",
tex_filename,
]
if base == "tectonic":
return [
"tectonic",
"-X",
"compile",
"--keep-logs",
"--outdir",
".",
tex_filename,
]
return [
"pdflatex",
"-interaction=nonstopmode",
"-halt-on-error",
"-file-line-error",
"-no-shell-escape",
tex_filename,
]
async def _run_once() -> Tuple[int, str]:
proc = await asyncio.create_subprocess_shell( async def run_latex_two_passes(
cmd, tex_filename: str,
workdir: Path,
tex_engine: str,
timeout_sec: int,
logger: str = None,
):
logger = logging.getLogger(logger) if logger else None
async def run_once(cmd: list[str]) -> tuple[int, str]:
proc = await asyncio.create_subprocess_exec(
*cmd,
cwd=str(workdir), cwd=str(workdir),
stdout=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.STDOUT, stderr=asyncio.subprocess.STDOUT,
) )
try: try:
out_b, _ = await asyncio.wait_for(proc.communicate(), timeout=timeout_sec) out_b, _ = await asyncio.wait_for(proc.communicate(), timeout=timeout_sec)
out_t = (out_b or b"").decode("utf-8", errors="replace") return (proc.returncode or 0), (out_b or b"").decode(
return proc.returncode or 0, out_t "utf-8", errors="replace"
)
except asyncio.TimeoutError: except asyncio.TimeoutError:
try: try:
proc.kill() proc.kill()
@@ -55,27 +96,39 @@ async def run_latex_two_passes(
raise raise
return 124, f"[Timeout] Compilation exceeded {timeout_sec}s.\n" return 124, f"[Timeout] Compilation exceeded {timeout_sec}s.\n"
logs: List[str] = [] logs = []
rc1, out1 = await _run_once() cmd = _build_cmd(tex_engine, tex_filename)
if logger:
logger.debug("LaTeX cmd: %s (cwd=%s)", " ".join(cmd), workdir)
rc1, out1 = await run_once(cmd)
logs.append(out1) logs.append(out1)
second_pass = not ((tex_engine or "").strip().lower() == "tectonic")
if rc1 != 0: if rc1 != 0:
log_text = "".join(logs) log_text = "".join(logs)
if logger: pdf = workdir / (Path(tex_filename).stem + ".pdf")
logger.debug("LaTeX pass1 FAIL %s\n%s", tex_filename, log_text[-2000:]) return False, log_text, pdf
return False, log_text, workdir / (Path(tex_filename).stem + ".pdf")
if second_pass:
rc2, out2 = await run_once(cmd)
logs.append(out2)
ok = rc2 == 0
else:
ok = rc1 == 0
rc2, out2 = await _run_once()
logs.append(out2)
log_text = "".join(logs) log_text = "".join(logs)
pdf = workdir / (Path(tex_filename).stem + ".pdf") pdf = workdir / (Path(tex_filename).stem + ".pdf")
ok = (rc2 == 0) and pdf.exists() ok = ok and pdf.exists()
if logger:
logger.debug("LaTeX pass2 %s ok=%s\n%s", tex_filename, ok, log_text[-2000:])
return ok, log_text, pdf return ok, log_text, pdf
async def compile_single_tex_bytes( async def compile_single_tex_bytes(
tex_bytes: bytes, filename: str, tex_engine: str, timeout_sec: int, logger:str=None tex_bytes: bytes,
filename: str,
tex_engine: str,
timeout_sec: int,
logger: str = None,
) -> Dict[str, Optional[object]]: ) -> Dict[str, Optional[object]]:
if not is_safe_tex_name(filename): if not is_safe_tex_name(filename):
return { return {
@@ -107,7 +160,7 @@ async def compile_single_tex_bytes(
async def compile_zip_to_zip( async def compile_zip_to_zip(
zip_bytes: bytes, tex_engine: str, timeout_sec: int, logger:str=None zip_bytes: bytes, tex_engine: str, timeout_sec: int, logger: str = None
) -> Tuple[bytes, List[str]]: ) -> Tuple[bytes, List[str]]:
failed: List[str] = [] failed: List[str] = []
out_pdf_paths: List[Path] = [] out_pdf_paths: List[Path] = []
@@ -143,6 +196,7 @@ async def compile_zip_to_zip(
failed.append(f"{tex.name} (exception: {e})") failed.append(f"{tex.name} (exception: {e})")
if logger: if logger:
logger.debug("BATCH EXC %s: %s", tex.name, e) logger.debug("BATCH EXC %s: %s", tex.name, e)
raise
out_zip = td_path / "compiled_pdfs.zip" out_zip = td_path / "compiled_pdfs.zip"
with zipfile.ZipFile(out_zip, "w", compression=zipfile.ZIP_DEFLATED) as zf: with zipfile.ZipFile(out_zip, "w", compression=zipfile.ZIP_DEFLATED) as zf:
@@ -153,7 +207,7 @@ async def compile_zip_to_zip(
async def ask_openai_diagnosis( async def ask_openai_diagnosis(
log_text: str, tex_excerpt: str, model: str, logger:str = None log_text: str, tex_excerpt: str, model: str, logger: str = None
) -> str: ) -> str:
logger = logging.getLogger(logger) if logger else None logger = logging.getLogger(logger) if logger else None
sys = ( sys = (
+28 -14
View File
@@ -8,6 +8,7 @@ from queue import Empty
import discord import discord
import pdf2image import pdf2image
import fitz
import PyPDF2 import PyPDF2
import requests import requests
from discord.ext import commands, tasks from discord.ext import commands, tasks
@@ -53,20 +54,33 @@ class DataModule(commands.Cog):
filename = res[random.randrange(0, len(res) - 1)] filename = res[random.randrange(0, len(res) - 1)]
# select random page # select random page
file = open(DIR_PATH_SADOX + filename, "rb") file = open(DIR_PATH_SADOX + filename, "rb")
readpdf = PyPDF2.PdfReader(file) if True:
totalpages = len(readpdf.pages) doc = fitz.open(DIR_PATH_SADOX + filename)
# trunk-ignore(bandit/B311) totalpages = len(doc)
page = random.randrange(1, totalpages) page_index = random.randrange(0, totalpages)
# convert page to image page = doc.load_page(page_index)
image = pdf2image.convert_from_path( mat = fitz.Matrix(2.0, 2.0) # powiększenie
DIR_PATH_SADOX + filename, first_page=page, last_page=page pix = page.get_pixmap(matrix=mat, alpha=False)
)
byte_io_stream = io.BytesIO() byte_io_stream = io.BytesIO(pix.tobytes("png"))
image[0].save(byte_io_stream, "JPEG") byte_io_stream.seek(0)
byte_io_stream.seek(0) byte_io_stream.name = "image.png"
byte_io_stream.name = "image.jpg" await ctx.send(file=discord.File(byte_io_stream))
file = discord.File(byte_io_stream) else: #legacy
await ctx.send(file=file) readpdf = PyPDF2.PdfReader(file)
totalpages = len(readpdf.pages)
# trunk-ignore(bandit/B311)
page = random.randrange(1, totalpages)
# convert page to image
image = pdf2image.convert_from_path(
DIR_PATH_SADOX + filename, first_page=page, last_page=page
)
byte_io_stream = io.BytesIO()
image[0].save(byte_io_stream, "JPEG")
byte_io_stream.seek(0)
byte_io_stream.name = "image.jpg"
file = discord.File(byte_io_stream)
await ctx.send(file=file)
self.logger.info("Get sadox completed") self.logger.info("Get sadox completed")
@tasks.loop(seconds=3) @tasks.loop(seconds=3)
+1
View File
@@ -13,6 +13,7 @@ spotipy
tiktoken tiktoken
PyNaCl PyNaCl
flask[async] flask[async]
fitz
waitress waitress
clickupython clickupython
assemblyai[extras] assemblyai[extras]