1
0

pdf_files.py 2.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
  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. # 26,7 Mo une fois encodé en base64 (+33 %) : bien en dessous de la limite de l'API (128 Mo par requête).
  12. # Plusieurs PDF arrivés pendant une réponse partent au besoin dans des requêtes séparées (voir
  13. # media.REQUEST_MAX_BASE64) ; seul, un PDF peut dépasser ce budget.
  14. PDF_MAX_BYTES = 20_000_000
  15. class PdfError(UserFacingError):
  16. """PDF illisible ou refusé, avec un message affichable sur Discord."""
  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, pages=pages)