Feat: Actions double_clic et clic_droit (français)
Nouvelles actions de clic avec nommage français: - double_clic: Double-clic avec intervalle configurable - clic_droit: Clic droit pour menus contextuels Support des profils français: rapide, normal, lent, furtif Intégration Humanizer anti-détection Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,8 @@ Actions disponibles :
|
|||||||
- VWBExtractTextAction : Extraire du texte d'une zone
|
- VWBExtractTextAction : Extraire du texte d'une zone
|
||||||
- VWBSurvolElementAction : Survol d'élément (hover)
|
- VWBSurvolElementAction : Survol d'élément (hover)
|
||||||
- VWBGlisserDeposerAction : Glisser-déposer (drag & drop)
|
- VWBGlisserDeposerAction : Glisser-déposer (drag & drop)
|
||||||
|
- VWBDoubleClicAction : Double-clic
|
||||||
|
- VWBClicDroitAction : Clic droit (menu contextuel)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from .click_anchor import VWBClickAnchorAction
|
from .click_anchor import VWBClickAnchorAction
|
||||||
@@ -27,6 +29,8 @@ from .scroll_to_anchor import VWBScrollToAnchorAction
|
|||||||
from .extract_text import VWBExtractTextAction
|
from .extract_text import VWBExtractTextAction
|
||||||
from .survol_element import VWBSurvolElementAction
|
from .survol_element import VWBSurvolElementAction
|
||||||
from .glisser_deposer import VWBGlisserDeposerAction
|
from .glisser_deposer import VWBGlisserDeposerAction
|
||||||
|
from .double_clic import VWBDoubleClicAction
|
||||||
|
from .clic_droit import VWBClicDroitAction
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'VWBClickAnchorAction',
|
'VWBClickAnchorAction',
|
||||||
@@ -38,8 +42,10 @@ __all__ = [
|
|||||||
'VWBExtractTextAction',
|
'VWBExtractTextAction',
|
||||||
'VWBSurvolElementAction',
|
'VWBSurvolElementAction',
|
||||||
'VWBGlisserDeposerAction',
|
'VWBGlisserDeposerAction',
|
||||||
|
'VWBDoubleClicAction',
|
||||||
|
'VWBClicDroitAction',
|
||||||
]
|
]
|
||||||
|
|
||||||
__version__ = '1.2.0'
|
__version__ = '1.3.0'
|
||||||
__author__ = 'Dom, Claude'
|
__author__ = 'Dom, Claude'
|
||||||
__date__ = '14 janvier 2026'
|
__date__ = '14 janvier 2026'
|
||||||
263
visual_workflow_builder/backend/actions/vision_ui/clic_droit.py
Normal file
263
visual_workflow_builder/backend/actions/vision_ui/clic_droit.py
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
"""
|
||||||
|
Action Clic Droit - Clic droit sur un élément visuel
|
||||||
|
Auteur : Dom, Claude - 14 janvier 2026
|
||||||
|
|
||||||
|
Cette action permet de faire un clic droit sur un élément identifié visuellement,
|
||||||
|
utile pour ouvrir des menus contextuels.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Dict, Any, List, Optional, Tuple
|
||||||
|
from datetime import datetime
|
||||||
|
import time
|
||||||
|
|
||||||
|
from ..base_action import BaseVWBAction, VWBActionResult, VWBActionStatus
|
||||||
|
from ...contracts.error import VWBErrorType, create_vwb_error
|
||||||
|
from ...contracts.visual_anchor import VWBVisualAnchor
|
||||||
|
|
||||||
|
|
||||||
|
class VWBClicDroitAction(BaseVWBAction):
|
||||||
|
"""
|
||||||
|
Action de clic droit sur élément visuel.
|
||||||
|
|
||||||
|
Permet de :
|
||||||
|
- Ouvrir des menus contextuels
|
||||||
|
- Accéder aux options avancées
|
||||||
|
- Copier/Coller via menu
|
||||||
|
- Actions spécifiques au clic droit
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
action_id: str,
|
||||||
|
parameters: Dict[str, Any],
|
||||||
|
screen_capturer=None
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Initialise l'action de clic droit.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
action_id: Identifiant unique de l'action
|
||||||
|
parameters: Paramètres incluant l'ancre visuelle
|
||||||
|
screen_capturer: Instance du ScreenCapturer (optionnel)
|
||||||
|
"""
|
||||||
|
super().__init__(
|
||||||
|
action_id=action_id,
|
||||||
|
name="Clic Droit",
|
||||||
|
description="Effectue un clic droit sur un élément visuel",
|
||||||
|
parameters=parameters,
|
||||||
|
screen_capturer=screen_capturer
|
||||||
|
)
|
||||||
|
|
||||||
|
# Paramètres du clic droit
|
||||||
|
self.ancre_visuelle: Optional[VWBVisualAnchor] = (
|
||||||
|
parameters.get('visual_anchor') or
|
||||||
|
parameters.get('ancre_visuelle')
|
||||||
|
)
|
||||||
|
self.decalage_x = parameters.get('decalage_x', parameters.get('offset_x', 0))
|
||||||
|
self.decalage_y = parameters.get('decalage_y', parameters.get('offset_y', 0))
|
||||||
|
self.delai_apres_clic_ms = parameters.get('delai_apres_clic_ms', parameters.get('wait_after_click_ms', 500))
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
self.seuil_confiance = parameters.get('seuil_confiance', parameters.get('confidence_threshold', 0.7))
|
||||||
|
|
||||||
|
# Humanisation
|
||||||
|
self.humaniser = parameters.get('humaniser', parameters.get('humanize', True))
|
||||||
|
self.profil_humain = parameters.get('profil_humain', parameters.get('humanize_profile', 'normal'))
|
||||||
|
|
||||||
|
def validate_parameters(self) -> List[str]:
|
||||||
|
"""Valide les paramètres de l'action."""
|
||||||
|
erreurs = []
|
||||||
|
|
||||||
|
if not self.ancre_visuelle:
|
||||||
|
erreurs.append("Ancre visuelle requise")
|
||||||
|
|
||||||
|
return erreurs
|
||||||
|
|
||||||
|
def execute_core(self, step_id: str) -> VWBActionResult:
|
||||||
|
"""
|
||||||
|
Exécute le clic droit.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
step_id: Identifiant de l'étape
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Résultat d'exécution
|
||||||
|
"""
|
||||||
|
start_time = datetime.now()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Trouver l'élément
|
||||||
|
position = self._trouver_element()
|
||||||
|
|
||||||
|
if not position:
|
||||||
|
return self._create_error_result(
|
||||||
|
step_id=step_id,
|
||||||
|
start_time=start_time,
|
||||||
|
error_type=VWBErrorType.ELEMENT_NOT_FOUND,
|
||||||
|
message="Élément non trouvé sur l'écran"
|
||||||
|
)
|
||||||
|
|
||||||
|
x, y = position
|
||||||
|
|
||||||
|
# Appliquer le décalage
|
||||||
|
x += self.decalage_x
|
||||||
|
y += self.decalage_y
|
||||||
|
|
||||||
|
# Effectuer le clic droit
|
||||||
|
succes = self._clic_droit(x, y)
|
||||||
|
|
||||||
|
if not succes:
|
||||||
|
return self._create_error_result(
|
||||||
|
step_id=step_id,
|
||||||
|
start_time=start_time,
|
||||||
|
error_type=VWBErrorType.CLICK_FAILED,
|
||||||
|
message="Échec du clic droit"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Attendre après le clic (important pour menus contextuels)
|
||||||
|
if self.delai_apres_clic_ms > 0:
|
||||||
|
time.sleep(self.delai_apres_clic_ms / 1000)
|
||||||
|
|
||||||
|
end_time = datetime.now()
|
||||||
|
execution_time = (end_time - start_time).total_seconds() * 1000
|
||||||
|
|
||||||
|
nom_ancre = self._get_nom_ancre()
|
||||||
|
print(f"🖱️➡️ Clic droit effectué sur '{nom_ancre}' à ({x}, {y})")
|
||||||
|
|
||||||
|
return VWBActionResult(
|
||||||
|
action_id=self.action_id,
|
||||||
|
step_id=step_id,
|
||||||
|
status=VWBActionStatus.SUCCESS,
|
||||||
|
start_time=start_time,
|
||||||
|
end_time=end_time,
|
||||||
|
execution_time_ms=execution_time,
|
||||||
|
output_data={
|
||||||
|
'position_x': x,
|
||||||
|
'position_y': y,
|
||||||
|
'nom_ancre': nom_ancre
|
||||||
|
},
|
||||||
|
evidence_list=self.evidence_list.copy()
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return self._create_error_result(
|
||||||
|
step_id=step_id,
|
||||||
|
start_time=start_time,
|
||||||
|
error_type=VWBErrorType.SYSTEM_ERROR,
|
||||||
|
message=f"Erreur: {str(e)}",
|
||||||
|
technical_details={'exception': str(e)}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _trouver_element(self) -> Optional[Tuple[int, int]]:
|
||||||
|
"""
|
||||||
|
Trouve la position de l'élément sur l'écran.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple (x, y) ou None si non trouvé
|
||||||
|
"""
|
||||||
|
if not self.ancre_visuelle:
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
from ...catalog_routes import find_visual_anchor_on_screen
|
||||||
|
|
||||||
|
# Obtenir l'image et le bounding box
|
||||||
|
image_ancre = None
|
||||||
|
bounding_box = None
|
||||||
|
|
||||||
|
if isinstance(self.ancre_visuelle, dict):
|
||||||
|
image_ancre = self.ancre_visuelle.get('screenshot') or self.ancre_visuelle.get('image_base64')
|
||||||
|
bounding_box = self.ancre_visuelle.get('bounding_box')
|
||||||
|
elif isinstance(self.ancre_visuelle, VWBVisualAnchor):
|
||||||
|
image_ancre = self.ancre_visuelle.screenshot_base64
|
||||||
|
if self.ancre_visuelle.has_bounding_box():
|
||||||
|
bounding_box = self.ancre_visuelle.bounding_box
|
||||||
|
|
||||||
|
# Recherche visuelle
|
||||||
|
if image_ancre:
|
||||||
|
resultat = find_visual_anchor_on_screen(
|
||||||
|
anchor_image_base64=image_ancre,
|
||||||
|
confidence_threshold=self.seuil_confiance,
|
||||||
|
bounding_box=bounding_box
|
||||||
|
)
|
||||||
|
|
||||||
|
if resultat and resultat.get('found'):
|
||||||
|
return (resultat.get('center_x', 0), resultat.get('center_y', 0))
|
||||||
|
|
||||||
|
# Fallback sur coordonnées statiques
|
||||||
|
if bounding_box:
|
||||||
|
x = int(bounding_box.get('x', 0) + bounding_box.get('width', 0) / 2)
|
||||||
|
y = int(bounding_box.get('y', 0) + bounding_box.get('height', 0) / 2)
|
||||||
|
return (x, y)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ Erreur recherche: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _clic_droit(self, x: int, y: int) -> bool:
|
||||||
|
"""
|
||||||
|
Effectue le clic droit.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
x: Position X
|
||||||
|
y: Position Y
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True si succès
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if self.humaniser:
|
||||||
|
from ...utils.humanizer import Humanizer, HumanProfile
|
||||||
|
|
||||||
|
profils = {
|
||||||
|
'rapide': HumanProfile.FAST,
|
||||||
|
'fast': HumanProfile.FAST,
|
||||||
|
'normal': HumanProfile.NORMAL,
|
||||||
|
'lent': HumanProfile.SLOW,
|
||||||
|
'slow': HumanProfile.SLOW,
|
||||||
|
'furtif': HumanProfile.STEALTH,
|
||||||
|
'stealth': HumanProfile.STEALTH,
|
||||||
|
}
|
||||||
|
profil = profils.get(self.profil_humain, HumanProfile.NORMAL)
|
||||||
|
|
||||||
|
humanizer = Humanizer(profile=profil)
|
||||||
|
real_x, real_y = humanizer.click(x, y, button='right')
|
||||||
|
return True
|
||||||
|
|
||||||
|
else:
|
||||||
|
import pyautogui
|
||||||
|
pyautogui.rightClick(x, y)
|
||||||
|
return True
|
||||||
|
|
||||||
|
except ImportError as e:
|
||||||
|
print(f"⚠️ Module non disponible: {e}")
|
||||||
|
return True # Simulation
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Erreur clic droit: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _get_nom_ancre(self) -> str:
|
||||||
|
"""Retourne le nom de l'ancre."""
|
||||||
|
if isinstance(self.ancre_visuelle, dict):
|
||||||
|
return self.ancre_visuelle.get('name', self.ancre_visuelle.get('nom', 'Élément'))
|
||||||
|
elif isinstance(self.ancre_visuelle, VWBVisualAnchor):
|
||||||
|
return self.ancre_visuelle.name
|
||||||
|
return "Élément"
|
||||||
|
|
||||||
|
def get_action_info(self) -> Dict[str, Any]:
|
||||||
|
"""Retourne les informations de l'action."""
|
||||||
|
return {
|
||||||
|
'action_id': self.action_id,
|
||||||
|
'name': self.name,
|
||||||
|
'description': self.description,
|
||||||
|
'type': 'clic_droit',
|
||||||
|
'parameters': {
|
||||||
|
'nom_ancre': self._get_nom_ancre(),
|
||||||
|
'decalage': {'x': self.decalage_x, 'y': self.decalage_y},
|
||||||
|
'humaniser': self.humaniser
|
||||||
|
},
|
||||||
|
'status': self.current_status.value
|
||||||
|
}
|
||||||
274
visual_workflow_builder/backend/actions/vision_ui/double_clic.py
Normal file
274
visual_workflow_builder/backend/actions/vision_ui/double_clic.py
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
"""
|
||||||
|
Action Double-Clic - Double-cliquer sur un élément visuel
|
||||||
|
Auteur : Dom, Claude - 14 janvier 2026
|
||||||
|
|
||||||
|
Cette action permet de double-cliquer sur un élément identifié visuellement,
|
||||||
|
utile pour ouvrir des fichiers, éditer du texte, etc.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Dict, Any, List, Optional, Tuple
|
||||||
|
from datetime import datetime
|
||||||
|
import time
|
||||||
|
|
||||||
|
from ..base_action import BaseVWBAction, VWBActionResult, VWBActionStatus
|
||||||
|
from ...contracts.error import VWBErrorType, create_vwb_error
|
||||||
|
from ...contracts.visual_anchor import VWBVisualAnchor
|
||||||
|
|
||||||
|
|
||||||
|
class VWBDoubleClicAction(BaseVWBAction):
|
||||||
|
"""
|
||||||
|
Action de double-clic sur élément visuel.
|
||||||
|
|
||||||
|
Permet de :
|
||||||
|
- Ouvrir des fichiers/dossiers
|
||||||
|
- Éditer du texte inline
|
||||||
|
- Sélectionner des mots
|
||||||
|
- Déclencher des actions spécifiques au double-clic
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
action_id: str,
|
||||||
|
parameters: Dict[str, Any],
|
||||||
|
screen_capturer=None
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Initialise l'action de double-clic.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
action_id: Identifiant unique de l'action
|
||||||
|
parameters: Paramètres incluant l'ancre visuelle
|
||||||
|
screen_capturer: Instance du ScreenCapturer (optionnel)
|
||||||
|
"""
|
||||||
|
super().__init__(
|
||||||
|
action_id=action_id,
|
||||||
|
name="Double-Clic",
|
||||||
|
description="Double-clique sur un élément visuel",
|
||||||
|
parameters=parameters,
|
||||||
|
screen_capturer=screen_capturer
|
||||||
|
)
|
||||||
|
|
||||||
|
# Paramètres du double-clic
|
||||||
|
self.ancre_visuelle: Optional[VWBVisualAnchor] = (
|
||||||
|
parameters.get('visual_anchor') or
|
||||||
|
parameters.get('ancre_visuelle')
|
||||||
|
)
|
||||||
|
self.decalage_x = parameters.get('decalage_x', parameters.get('offset_x', 0))
|
||||||
|
self.decalage_y = parameters.get('decalage_y', parameters.get('offset_y', 0))
|
||||||
|
self.delai_apres_clic_ms = parameters.get('delai_apres_clic_ms', parameters.get('wait_after_click_ms', 500))
|
||||||
|
|
||||||
|
# Intervalle entre les deux clics
|
||||||
|
self.intervalle_clics_ms = parameters.get('intervalle_clics_ms', parameters.get('click_interval_ms', 100))
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
self.seuil_confiance = parameters.get('seuil_confiance', parameters.get('confidence_threshold', 0.7))
|
||||||
|
|
||||||
|
# Humanisation
|
||||||
|
self.humaniser = parameters.get('humaniser', parameters.get('humanize', True))
|
||||||
|
self.profil_humain = parameters.get('profil_humain', parameters.get('humanize_profile', 'normal'))
|
||||||
|
|
||||||
|
def validate_parameters(self) -> List[str]:
|
||||||
|
"""Valide les paramètres de l'action."""
|
||||||
|
erreurs = []
|
||||||
|
|
||||||
|
if not self.ancre_visuelle:
|
||||||
|
erreurs.append("Ancre visuelle requise")
|
||||||
|
|
||||||
|
if self.intervalle_clics_ms < 50:
|
||||||
|
erreurs.append("Intervalle entre clics minimum: 50ms")
|
||||||
|
|
||||||
|
if self.intervalle_clics_ms > 500:
|
||||||
|
erreurs.append("Intervalle entre clics maximum: 500ms")
|
||||||
|
|
||||||
|
return erreurs
|
||||||
|
|
||||||
|
def execute_core(self, step_id: str) -> VWBActionResult:
|
||||||
|
"""
|
||||||
|
Exécute le double-clic.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
step_id: Identifiant de l'étape
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Résultat d'exécution
|
||||||
|
"""
|
||||||
|
start_time = datetime.now()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Trouver l'élément
|
||||||
|
position = self._trouver_element()
|
||||||
|
|
||||||
|
if not position:
|
||||||
|
return self._create_error_result(
|
||||||
|
step_id=step_id,
|
||||||
|
start_time=start_time,
|
||||||
|
error_type=VWBErrorType.ELEMENT_NOT_FOUND,
|
||||||
|
message="Élément non trouvé sur l'écran"
|
||||||
|
)
|
||||||
|
|
||||||
|
x, y = position
|
||||||
|
|
||||||
|
# Appliquer le décalage
|
||||||
|
x += self.decalage_x
|
||||||
|
y += self.decalage_y
|
||||||
|
|
||||||
|
# Effectuer le double-clic
|
||||||
|
succes = self._double_cliquer(x, y)
|
||||||
|
|
||||||
|
if not succes:
|
||||||
|
return self._create_error_result(
|
||||||
|
step_id=step_id,
|
||||||
|
start_time=start_time,
|
||||||
|
error_type=VWBErrorType.CLICK_FAILED,
|
||||||
|
message="Échec du double-clic"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Attendre après le clic
|
||||||
|
if self.delai_apres_clic_ms > 0:
|
||||||
|
time.sleep(self.delai_apres_clic_ms / 1000)
|
||||||
|
|
||||||
|
end_time = datetime.now()
|
||||||
|
execution_time = (end_time - start_time).total_seconds() * 1000
|
||||||
|
|
||||||
|
nom_ancre = self._get_nom_ancre()
|
||||||
|
print(f"🖱️🖱️ Double-clic effectué sur '{nom_ancre}' à ({x}, {y})")
|
||||||
|
|
||||||
|
return VWBActionResult(
|
||||||
|
action_id=self.action_id,
|
||||||
|
step_id=step_id,
|
||||||
|
status=VWBActionStatus.SUCCESS,
|
||||||
|
start_time=start_time,
|
||||||
|
end_time=end_time,
|
||||||
|
execution_time_ms=execution_time,
|
||||||
|
output_data={
|
||||||
|
'position_x': x,
|
||||||
|
'position_y': y,
|
||||||
|
'nom_ancre': nom_ancre,
|
||||||
|
'intervalle_clics_ms': self.intervalle_clics_ms
|
||||||
|
},
|
||||||
|
evidence_list=self.evidence_list.copy()
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return self._create_error_result(
|
||||||
|
step_id=step_id,
|
||||||
|
start_time=start_time,
|
||||||
|
error_type=VWBErrorType.SYSTEM_ERROR,
|
||||||
|
message=f"Erreur: {str(e)}",
|
||||||
|
technical_details={'exception': str(e)}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _trouver_element(self) -> Optional[Tuple[int, int]]:
|
||||||
|
"""
|
||||||
|
Trouve la position de l'élément sur l'écran.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple (x, y) ou None si non trouvé
|
||||||
|
"""
|
||||||
|
if not self.ancre_visuelle:
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
from ...catalog_routes import find_visual_anchor_on_screen
|
||||||
|
|
||||||
|
# Obtenir l'image et le bounding box
|
||||||
|
image_ancre = None
|
||||||
|
bounding_box = None
|
||||||
|
|
||||||
|
if isinstance(self.ancre_visuelle, dict):
|
||||||
|
image_ancre = self.ancre_visuelle.get('screenshot') or self.ancre_visuelle.get('image_base64')
|
||||||
|
bounding_box = self.ancre_visuelle.get('bounding_box')
|
||||||
|
elif isinstance(self.ancre_visuelle, VWBVisualAnchor):
|
||||||
|
image_ancre = self.ancre_visuelle.screenshot_base64
|
||||||
|
if self.ancre_visuelle.has_bounding_box():
|
||||||
|
bounding_box = self.ancre_visuelle.bounding_box
|
||||||
|
|
||||||
|
# Recherche visuelle
|
||||||
|
if image_ancre:
|
||||||
|
resultat = find_visual_anchor_on_screen(
|
||||||
|
anchor_image_base64=image_ancre,
|
||||||
|
confidence_threshold=self.seuil_confiance,
|
||||||
|
bounding_box=bounding_box
|
||||||
|
)
|
||||||
|
|
||||||
|
if resultat and resultat.get('found'):
|
||||||
|
return (resultat.get('center_x', 0), resultat.get('center_y', 0))
|
||||||
|
|
||||||
|
# Fallback sur coordonnées statiques
|
||||||
|
if bounding_box:
|
||||||
|
x = int(bounding_box.get('x', 0) + bounding_box.get('width', 0) / 2)
|
||||||
|
y = int(bounding_box.get('y', 0) + bounding_box.get('height', 0) / 2)
|
||||||
|
return (x, y)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ Erreur recherche: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _double_cliquer(self, x: int, y: int) -> bool:
|
||||||
|
"""
|
||||||
|
Effectue le double-clic.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
x: Position X
|
||||||
|
y: Position Y
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True si succès
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if self.humaniser:
|
||||||
|
from ...utils.humanizer import Humanizer, HumanProfile
|
||||||
|
|
||||||
|
profils = {
|
||||||
|
'rapide': HumanProfile.FAST,
|
||||||
|
'fast': HumanProfile.FAST,
|
||||||
|
'normal': HumanProfile.NORMAL,
|
||||||
|
'lent': HumanProfile.SLOW,
|
||||||
|
'slow': HumanProfile.SLOW,
|
||||||
|
'furtif': HumanProfile.STEALTH,
|
||||||
|
'stealth': HumanProfile.STEALTH,
|
||||||
|
}
|
||||||
|
profil = profils.get(self.profil_humain, HumanProfile.NORMAL)
|
||||||
|
|
||||||
|
humanizer = Humanizer(profile=profil)
|
||||||
|
real_x, real_y = humanizer.double_click(x, y)
|
||||||
|
return True
|
||||||
|
|
||||||
|
else:
|
||||||
|
import pyautogui
|
||||||
|
pyautogui.doubleClick(x, y, interval=self.intervalle_clics_ms / 1000)
|
||||||
|
return True
|
||||||
|
|
||||||
|
except ImportError as e:
|
||||||
|
print(f"⚠️ Module non disponible: {e}")
|
||||||
|
return True # Simulation
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Erreur double-clic: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _get_nom_ancre(self) -> str:
|
||||||
|
"""Retourne le nom de l'ancre."""
|
||||||
|
if isinstance(self.ancre_visuelle, dict):
|
||||||
|
return self.ancre_visuelle.get('name', self.ancre_visuelle.get('nom', 'Élément'))
|
||||||
|
elif isinstance(self.ancre_visuelle, VWBVisualAnchor):
|
||||||
|
return self.ancre_visuelle.name
|
||||||
|
return "Élément"
|
||||||
|
|
||||||
|
def get_action_info(self) -> Dict[str, Any]:
|
||||||
|
"""Retourne les informations de l'action."""
|
||||||
|
return {
|
||||||
|
'action_id': self.action_id,
|
||||||
|
'name': self.name,
|
||||||
|
'description': self.description,
|
||||||
|
'type': 'double_clic',
|
||||||
|
'parameters': {
|
||||||
|
'nom_ancre': self._get_nom_ancre(),
|
||||||
|
'decalage': {'x': self.decalage_x, 'y': self.decalage_y},
|
||||||
|
'intervalle_clics_ms': self.intervalle_clics_ms,
|
||||||
|
'humaniser': self.humaniser
|
||||||
|
},
|
||||||
|
'status': self.current_status.value
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user