pdf_files.py 1.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
  1. """Lecture des PDF joints aux messages : envoyés tels quels à Claude, qui en lit le texte et les pages."""
  2. from __future__ import annotations
  3. import io
  4. import logging
  5. from pypdf import PdfReader
  6. from .claude_client import PDF_MEDIA_TYPE, ImageData
  7. logger = logging.getLogger("chatbot.pdf")
  8. logging.getLogger("pypdf").setLevel(logging.ERROR) # avertissements sur les PDF mal formés, sans gravité
  9. PDF_EXTENSIONS = (".pdf",)
  10. # Limite de l'API : 32 Mo par requête, une fois encodé en base64 (+33 %), historique compris.
  11. PDF_MAX_BYTES = 20_000_000
  12. class PdfError(Exception):
  13. """PDF illisible ou refusé, avec un message affichable sur Discord."""
  14. def __init__(self, user_message: str):
  15. super().__init__(user_message)
  16. self.user_message = user_message
  17. def prepare_pdf(name: str, data: bytes, max_pages: int) -> ImageData:
  18. """Vérifie un PDF avant l'envoi à Claude : lisible, sans mot de passe, max_pages pages au plus.
  19. Fonction bloquante (lecture du PDF) : à appeler via asyncio.to_thread.
  20. """
  21. try:
  22. reader = PdfReader(io.BytesIO(data))
  23. if reader.is_encrypted and not reader.decrypt(""):
  24. raise PdfError("Ce PDF est protégé par un mot de passe, je ne peux pas le lire.")
  25. pages = len(reader.pages)
  26. except PdfError:
  27. raise
  28. except Exception as exc: # fichier abîmé ou pas un PDF : pypdf lève des erreurs très variées
  29. logger.info("PDF %s illisible : %s", name, exc)
  30. raise PdfError("Je n'arrive pas à lire ce PDF.")
  31. if pages == 0:
  32. raise PdfError("Ce PDF ne contient aucune page.")
  33. if pages > max_pages:
  34. raise PdfError(f"Ce PDF a {pages} pages : je n'en lis que {max_pages} au maximum. "
  35. "Envoie-moi seulement les pages utiles.")
  36. logger.info("PDF %s : %d page(s), %.1f Mo", name, pages, len(data) / 1e6)
  37. return ImageData(PDF_MEDIA_TYPE, data, name=name)