mirror of
https://github.com/Ladebeze66/llm_ticket3.git
synced 2025-12-13 17:37:05 +01:00
449 lines
19 KiB
Python
449 lines
19 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
Module pour extraire les images intégrées dans les messages HTML d'Odoo.
|
|
Complémentaire à l'extracteur d'images actuel, il détecte spécifiquement les images
|
|
référencées dans le HTML avec des balises <img> et les associe aux pièces jointes.
|
|
"""
|
|
|
|
import os
|
|
import re
|
|
import logging
|
|
import json
|
|
from typing import Dict, List, Any, Optional, Set, Tuple
|
|
from bs4 import BeautifulSoup
|
|
|
|
class HtmlImageExtractor:
|
|
"""
|
|
Extracteur d'images intégrées dans les messages HTML d'Odoo.
|
|
"""
|
|
|
|
def __init__(self, ticket_dir: str):
|
|
"""
|
|
Initialise l'extracteur d'images HTML.
|
|
|
|
Args:
|
|
ticket_dir: Répertoire du ticket contenant les messages et pièces jointes
|
|
"""
|
|
self.ticket_dir = ticket_dir
|
|
self.messages_file = os.path.join(ticket_dir, "messages_raw.json")
|
|
self.attachments_file = os.path.join(ticket_dir, "attachments_info.json")
|
|
self.output_file = os.path.join(ticket_dir, "embedded_images.json")
|
|
|
|
# Cache pour les données
|
|
self._messages = None
|
|
self._attachments = None
|
|
|
|
def _load_messages(self) -> List[Dict[str, Any]]:
|
|
"""
|
|
Charge les messages du ticket.
|
|
|
|
Returns:
|
|
Liste des messages
|
|
"""
|
|
if self._messages is not None:
|
|
return self._messages
|
|
|
|
# Essayer différents fichiers potentiels contenant les messages
|
|
possible_message_files = [
|
|
self.messages_file, # messages_raw.json
|
|
os.path.join(self.ticket_dir, "messages.json"),
|
|
os.path.join(self.ticket_dir, "all_messages.json")
|
|
]
|
|
|
|
for msg_file in possible_message_files:
|
|
if os.path.exists(msg_file):
|
|
try:
|
|
with open(msg_file, 'r', encoding='utf-8') as f:
|
|
data = json.load(f)
|
|
|
|
# Gérer différents formats de fichiers de messages
|
|
if isinstance(data, dict):
|
|
if "messages" in data:
|
|
self._messages = data["messages"]
|
|
return self._messages
|
|
# Si pas de clé messages mais d'autres clés, essayer de trouver les messages
|
|
for key, value in data.items():
|
|
if isinstance(value, list) and len(value) > 0:
|
|
# Vérifier si ça ressemble à des messages (ont une clé body ou content)
|
|
if isinstance(value[0], dict) and any(k in value[0] for k in ["body", "content"]):
|
|
self._messages = value
|
|
return self._messages
|
|
|
|
# Si c'est directement une liste, supposer que ce sont des messages
|
|
elif isinstance(data, list):
|
|
self._messages = data
|
|
return self._messages
|
|
|
|
except Exception as e:
|
|
logging.error(f"Erreur lors du chargement du fichier {msg_file}: {e}")
|
|
|
|
# Si on arrive ici, aucun fichier valide n'a été trouvé
|
|
logging.error(f"Aucun fichier de messages valide trouvé dans: {self.ticket_dir}")
|
|
return []
|
|
|
|
def _load_attachments(self) -> Dict[int, Dict[str, Any]]:
|
|
"""
|
|
Charge les pièces jointes du ticket.
|
|
|
|
Returns:
|
|
Dictionnaire des pièces jointes indexées par ID
|
|
"""
|
|
if self._attachments is not None:
|
|
return self._attachments
|
|
|
|
# Essayer différents fichiers potentiels de pièces jointes
|
|
possible_attachment_files = [
|
|
self.attachments_file, # attachments_info.json
|
|
os.path.join(self.ticket_dir, "attachments.json")
|
|
]
|
|
|
|
for att_file in possible_attachment_files:
|
|
if os.path.exists(att_file):
|
|
try:
|
|
with open(att_file, 'r', encoding='utf-8') as f:
|
|
attachments = json.load(f)
|
|
|
|
if not isinstance(attachments, list):
|
|
# Si ce n'est pas une liste mais un dict, essayer de trouver une liste
|
|
for key, value in attachments.items():
|
|
if isinstance(value, list) and len(value) > 0:
|
|
attachments = value
|
|
break
|
|
|
|
if not isinstance(attachments, list):
|
|
continue
|
|
|
|
# Indexer par ID pour un accès rapide
|
|
self._attachments = {}
|
|
for att in attachments:
|
|
if "id" in att:
|
|
self._attachments[att["id"]] = att
|
|
elif "attachment_id" in att:
|
|
self._attachments[att["attachment_id"]] = att
|
|
|
|
if self._attachments:
|
|
return self._attachments
|
|
except Exception as e:
|
|
logging.error(f"Erreur lors du chargement des pièces jointes depuis {att_file}: {e}")
|
|
|
|
# Si on arrive ici, aucun fichier valide n'a été trouvé
|
|
logging.error(f"Aucun fichier de pièces jointes valide trouvé dans: {self.ticket_dir}")
|
|
return {}
|
|
|
|
def _get_tag_attribute(self, tag: Any, attr_name: str, default: str = "") -> str:
|
|
"""
|
|
Récupère un attribut d'une balise HTML de manière sécurisée.
|
|
|
|
Args:
|
|
tag: Balise HTML
|
|
attr_name: Nom de l'attribut à récupérer
|
|
default: Valeur par défaut si l'attribut n'existe pas
|
|
|
|
Returns:
|
|
Valeur de l'attribut
|
|
"""
|
|
# Vérifier que c'est bien une balise et qu'elle a la méthode get
|
|
if not hasattr(tag, 'get'):
|
|
return default
|
|
|
|
# Récupérer l'attribut si présent
|
|
try:
|
|
value = tag.get(attr_name)
|
|
return str(value) if value is not None else default
|
|
except (AttributeError, KeyError, TypeError):
|
|
return default
|
|
|
|
def extract_image_references(self) -> Dict[str, Any]:
|
|
"""
|
|
Extrait les références aux images dans le HTML des messages.
|
|
|
|
Returns:
|
|
Dictionnaire contenant les références d'images trouvées
|
|
"""
|
|
messages = self._load_messages()
|
|
attachments_by_id = self._load_attachments()
|
|
|
|
if not messages:
|
|
logging.error("Aucun message trouvé pour l'extraction d'images")
|
|
return {"status": "error", "message": "Aucun message trouvé", "references": []}
|
|
|
|
# Stocker les références trouvées
|
|
image_references = []
|
|
|
|
# Ensemble pour dédupliquer
|
|
processed_ids = set()
|
|
|
|
for message in messages:
|
|
# Déterminer quel champ contient le contenu HTML
|
|
message_body = None
|
|
message_id = None
|
|
|
|
# Différentes possibilités de noms pour le contenu HTML et l'ID
|
|
for body_key in ["body", "content", "html_content", "message"]:
|
|
if body_key in message and message[body_key]:
|
|
message_body = message[body_key]
|
|
break
|
|
|
|
for id_key in ["id", "message_id", "uid"]:
|
|
if id_key in message:
|
|
message_id = message[id_key]
|
|
break
|
|
|
|
if not message_body or not message_id:
|
|
continue
|
|
|
|
# Méthode 1: Extraction depuis les balises <img> dans le HTML
|
|
try:
|
|
# Analyser le HTML du message
|
|
soup = BeautifulSoup(message_body, "html.parser")
|
|
|
|
# Trouver toutes les balises <img>
|
|
img_tags = soup.find_all("img")
|
|
|
|
for img in img_tags:
|
|
# Utiliser la méthode sécurisée pour récupérer les attributs
|
|
src = self._get_tag_attribute(img, "src")
|
|
|
|
# Ignorer les images vides ou data URLs
|
|
if not src or src.startswith("data:"):
|
|
continue
|
|
|
|
# Différents patterns de référence d'images
|
|
image_id = None
|
|
|
|
# Pattern 1: /web/image/ID?access_token=...
|
|
match = re.search(r"/web/image/(\d+)", src)
|
|
if match:
|
|
image_id = int(match.group(1))
|
|
|
|
# Pattern 2: /web/content/ID?...
|
|
if not image_id:
|
|
match = re.search(r"/web/content/(\d+)", src)
|
|
if match:
|
|
image_id = int(match.group(1))
|
|
|
|
# Pattern 3: /web/static/ID?...
|
|
if not image_id:
|
|
match = re.search(r"/web/static/(\d+)", src)
|
|
if match:
|
|
image_id = int(match.group(1))
|
|
|
|
# Pattern 4: /web/binary/image?id=ID&...
|
|
if not image_id:
|
|
match = re.search(r"[?&]id=(\d+)", src)
|
|
if match:
|
|
image_id = int(match.group(1))
|
|
|
|
if image_id:
|
|
self._ajouter_reference_image(image_id, message_id, attachments_by_id, img, processed_ids, image_references)
|
|
|
|
except Exception as e:
|
|
logging.error(f"Erreur lors de l'analyse des balises <img> dans le message {message_id}: {e}")
|
|
|
|
# Méthode 2: Extraction depuis les références textuelles à la fin du message
|
|
# Format: "- Nom_image.ext (image/type) [ID: 12345]"
|
|
try:
|
|
# Chercher des références d'images dans le texte du message
|
|
# Pattern des références: "- Nom_fichier.ext (type/mime) [ID: 12345]"
|
|
img_ref_pattern = r"- ([^\(\)]+) \(([^\(\)]*)\) \[ID: (\d+)\]"
|
|
for match in re.finditer(img_ref_pattern, message_body):
|
|
try:
|
|
nom_fichier = match.group(1).strip()
|
|
mimetype = match.group(2).strip()
|
|
image_id = int(match.group(3))
|
|
|
|
if image_id in processed_ids:
|
|
continue
|
|
|
|
processed_ids.add(image_id)
|
|
|
|
# Vérifier si cette image est dans nos pièces jointes
|
|
if image_id in attachments_by_id:
|
|
attachment = attachments_by_id[image_id]
|
|
|
|
# Récupérer le chemin local de l'image
|
|
local_path = attachment.get("local_path")
|
|
|
|
# Vérifier que le fichier existe réellement
|
|
if local_path and not os.path.exists(local_path):
|
|
# Essayer de chercher dans d'autres endroits potentiels du répertoire
|
|
for root, dirs, files in os.walk(self.ticket_dir):
|
|
for file in files:
|
|
if os.path.basename(local_path) == file:
|
|
local_path = os.path.join(root, file)
|
|
break
|
|
if os.path.exists(local_path):
|
|
break
|
|
|
|
image_ref = {
|
|
"image_id": image_id,
|
|
"message_id": message_id,
|
|
"attachment_id": attachment.get("id", attachment.get("attachment_id", 0)),
|
|
"name": nom_fichier, # Utiliser le nom trouvé dans le texte
|
|
"mimetype": mimetype, # Utiliser le type MIME trouvé dans le texte
|
|
"file_size": attachment.get("file_size", 0),
|
|
"local_path": local_path,
|
|
"source": "text_reference"
|
|
}
|
|
|
|
image_references.append(image_ref)
|
|
logging.info(f"Image référencée trouvée dans le texte: ID {image_id} dans message {message_id}")
|
|
except Exception as e:
|
|
logging.error(f"Erreur lors du traitement de la référence d'image textuelle: {e}")
|
|
|
|
except Exception as e:
|
|
logging.error(f"Erreur lors de l'analyse des références textuelles dans le message {message_id}: {e}")
|
|
|
|
# Sauvegarder les références trouvées
|
|
result = {
|
|
"status": "success" if image_references else "warning",
|
|
"message": f"Trouvé {len(image_references)} références d'images intégrées",
|
|
"references": image_references
|
|
}
|
|
|
|
try:
|
|
with open(self.output_file, 'w', encoding='utf-8') as f:
|
|
json.dump(result, f, indent=2, ensure_ascii=False)
|
|
logging.info(f"Références d'images sauvegardées dans: {self.output_file}")
|
|
except Exception as e:
|
|
logging.error(f"Erreur lors de la sauvegarde des références d'images: {e}")
|
|
|
|
return result
|
|
|
|
def _ajouter_reference_image(self, image_id: int, message_id: str, attachments_by_id: Dict[int, Dict[str, Any]],
|
|
img: Any, processed_ids: Set[int], image_references: List[Dict[str, Any]]) -> None:
|
|
"""
|
|
Ajoute une référence d'image à la liste des références
|
|
|
|
Args:
|
|
image_id: ID de l'image
|
|
message_id: ID du message
|
|
attachments_by_id: Dictionnaire des pièces jointes indexées par ID
|
|
img: Balise HTML de l'image
|
|
processed_ids: Ensemble des IDs déjà traités
|
|
image_references: Liste des références d'images à enrichir
|
|
"""
|
|
# Éviter les duplications
|
|
if image_id in processed_ids:
|
|
return
|
|
|
|
processed_ids.add(image_id)
|
|
|
|
# Vérifier si cette image est dans nos pièces jointes
|
|
if image_id in attachments_by_id:
|
|
attachment = attachments_by_id[image_id]
|
|
|
|
# Récupérer les attributs de manière sécurisée
|
|
width = self._get_tag_attribute(img, "width")
|
|
height = self._get_tag_attribute(img, "height")
|
|
alt = self._get_tag_attribute(img, "alt")
|
|
|
|
# Récupérer le chemin local de l'image
|
|
local_path = attachment.get("local_path")
|
|
|
|
# Vérifier que le fichier existe réellement
|
|
if local_path and not os.path.exists(local_path):
|
|
# Essayer de chercher dans d'autres endroits potentiels du répertoire
|
|
for root, dirs, files in os.walk(self.ticket_dir):
|
|
for file in files:
|
|
if os.path.basename(local_path) == file:
|
|
local_path = os.path.join(root, file)
|
|
break
|
|
if os.path.exists(local_path):
|
|
break
|
|
|
|
image_ref = {
|
|
"image_id": image_id,
|
|
"message_id": message_id,
|
|
"attachment_id": attachment.get("id", attachment.get("attachment_id", 0)),
|
|
"name": attachment.get("name", ""),
|
|
"mimetype": attachment.get("mimetype", ""),
|
|
"file_size": attachment.get("file_size", 0),
|
|
"local_path": local_path,
|
|
"img_width": width,
|
|
"img_height": height,
|
|
"img_alt": alt,
|
|
"source": "html_img_tag"
|
|
}
|
|
|
|
image_references.append(image_ref)
|
|
logging.info(f"Image intégrée trouvée: ID {image_id} dans message {message_id}")
|
|
|
|
def get_image_paths(self) -> List[str]:
|
|
"""
|
|
Récupère les chemins des images référencées.
|
|
|
|
Returns:
|
|
Liste des chemins locaux des images référencées
|
|
"""
|
|
try:
|
|
# Tenter d'extraire les images
|
|
data = self.extract_image_references()
|
|
|
|
# Récupérer les chemins locaux des images
|
|
paths = []
|
|
for ref in data.get("references", []):
|
|
path = ref.get("local_path")
|
|
if path and os.path.exists(path):
|
|
paths.append(path)
|
|
|
|
if not paths:
|
|
logging.warning("Aucune image intégrée trouvée ou les chemins sont invalides")
|
|
|
|
return paths
|
|
|
|
except Exception as e:
|
|
logging.error(f"Erreur lors de la récupération des chemins d'images: {e}")
|
|
return []
|
|
|
|
def extract_images_from_ticket(ticket_dir: str) -> List[str]:
|
|
"""
|
|
Fonction utilitaire pour extraire les images intégrées dans les messages HTML d'un ticket.
|
|
|
|
Args:
|
|
ticket_dir: Répertoire du ticket contenant les messages et pièces jointes
|
|
|
|
Returns:
|
|
Liste des chemins locaux des images référencées
|
|
"""
|
|
extractor = HtmlImageExtractor(ticket_dir)
|
|
return extractor.get_image_paths()
|
|
|
|
if __name__ == "__main__":
|
|
# Test avec un répertoire de ticket spécifique
|
|
import sys
|
|
|
|
if len(sys.argv) > 1:
|
|
ticket_dir = sys.argv[1]
|
|
else:
|
|
# Utiliser un répertoire de test par défaut
|
|
ticket_dir = "./output/ticket_T0241/T0241_20250409_141018"
|
|
|
|
if not os.path.exists(ticket_dir):
|
|
print(f"Répertoire introuvable: {ticket_dir}")
|
|
sys.exit(1)
|
|
|
|
print(f"Extraction des images intégrées dans le HTML pour le ticket: {os.path.basename(ticket_dir)}")
|
|
|
|
extractor = HtmlImageExtractor(ticket_dir)
|
|
result = extractor.extract_image_references()
|
|
|
|
print(f"Statut: {result['status']}")
|
|
print(f"Message: {result['message']}")
|
|
print(f"Nombre de références trouvées: {len(result['references'])}")
|
|
|
|
for i, ref in enumerate(result["references"]):
|
|
print(f"\nImage {i+1}:")
|
|
print(f" ID: {ref['image_id']}")
|
|
print(f" Nom: {ref['name']}")
|
|
print(f" Type: {ref['mimetype']}")
|
|
print(f" Taille: {ref['file_size']} octets")
|
|
print(f" Chemin local: {ref['local_path']}")
|
|
|
|
# Récupérer les chemins des images
|
|
paths = extractor.get_image_paths()
|
|
print(f"\nChemins des images ({len(paths)}):")
|
|
for path in paths:
|
|
print(f" {path}") |