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:
Dom
2026-01-14 23:11:42 +01:00
parent 34f279cbc1
commit ae100d3da8
3 changed files with 544 additions and 1 deletions

View File

@@ -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'

View 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
}

View 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
}