1
0

pdf_files.py 1.8 KB

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