@@ -64,6 +64,11 @@ def __add_sync_methods(self, element):
6464 )
6565 element .highlight_overlay = lambda : self .__highlight_overlay (element )
6666 element .mouse_click = lambda : self .__mouse_click (element )
67+ element .click_with_offset = (
68+ lambda * args , ** kwargs : self .__mouse_click_with_offset_async (
69+ element , * args , ** kwargs
70+ )
71+ )
6772 element .mouse_drag = (
6873 lambda destination : self .__mouse_drag (element , destination )
6974 )
@@ -447,6 +452,15 @@ def __mouse_click(self, element):
447452 self .loop .run_until_complete (self .page .wait ())
448453 return result
449454
455+ def __mouse_click_with_offset_async (self , element , * args , ** kwargs ):
456+ result = (
457+ self .loop .run_until_complete (
458+ element .mouse_click_with_offset_async (* args , ** kwargs )
459+ )
460+ )
461+ self .loop .run_until_complete (self .page .wait ())
462+ return result
463+
450464 def __mouse_drag (self , element , destination ):
451465 return (
452466 self .loop .run_until_complete (element .mouse_drag_async (destination ))
@@ -689,10 +703,16 @@ def click(self, selector, timeout=None):
689703 self .__slow_mode_pause_if_set ()
690704 element = self .find_element (selector , timeout = timeout )
691705 element .scroll_into_view ()
692- if element .tag_name == "div" or element .tag_name == "input" :
693- element .mouse_click () # Simulated click (not PyAutoGUI)
706+ tag_name = element .tag_name
707+ if tag_name :
708+ tag_name = tag_name .lower ().strip ()
709+ if (
710+ tag_name in ["a" , "button" , "canvas" , "div" , "input" , "li" , "span" ]
711+ and "contains(" not in selector
712+ ):
713+ element .mouse_click () # Simulated click (NOT PyAutoGUI)
694714 else :
695- element .click ()
715+ element .click () # Standard CDP click
696716 self .__slow_mode_pause_if_set ()
697717 self .loop .run_until_complete (self .page .wait ())
698718
@@ -738,7 +758,7 @@ def click_visible_elements(self, selector, limit=0):
738758 element .scroll_into_view ()
739759 element .click ()
740760 click_count += 1
741- time .sleep (0.042 )
761+ time .sleep (0.044 )
742762 self .__slow_mode_pause_if_set ()
743763 self .loop .run_until_complete (self .page .wait ())
744764 except Exception :
@@ -1757,11 +1777,32 @@ def gui_click_element(self, selector, timeframe=0.25):
17571777 self .__slow_mode_pause_if_set ()
17581778 self .loop .run_until_complete (self .page .wait ())
17591779
1760- def _on_a_cf_turnstile_page (self ):
1761- time .sleep (0.042 )
1762- source = self .get_page_source ()
1780+ def gui_click_with_offset (
1781+ self , selector , x , y , timeframe = 0.25 , center = False
1782+ ):
1783+ """Click an element at an {X,Y}-offset location.
1784+ {0,0} is the top-left corner of the element.
1785+ If center==True, {0,0} becomes the center of the element.
1786+ The timeframe is the time spent moving the mouse."""
1787+ if center :
1788+ px , py = self .get_gui_element_center (selector )
1789+ self .gui_click_x_y (px + x , py + y , timeframe = timeframe )
1790+ else :
1791+ element_rect = self .get_gui_element_rect (selector )
1792+ px = element_rect ["x" ]
1793+ py = element_rect ["y" ]
1794+ self .gui_click_x_y (px + x , py + y , timeframe = timeframe )
1795+
1796+ def click_with_offset (self , selector , x , y , center = False ):
1797+ element = self .find_element (selector )
1798+ element .scroll_into_view ()
1799+ element .click_with_offset (x = x , y = y , center = center )
1800+ self .__slow_mode_pause_if_set ()
1801+ self .loop .run_until_complete (self .page .wait ())
1802+
1803+ def _on_a_cf_turnstile_page (self , source = None ):
17631804 if not source or len (source ) < 400 :
1764- time .sleep (0.22 )
1805+ time .sleep (0.2 )
17651806 source = self .get_page_source ()
17661807 if (
17671808 'data-callback="onCaptchaSuccess"' in source
@@ -1773,20 +1814,21 @@ def _on_a_cf_turnstile_page(self):
17731814 return True
17741815 return False
17751816
1776- def _on_a_g_recaptcha_page (self ):
1777- time .sleep (0.042 )
1778- source = self .get_page_source ()
1817+ def _on_a_g_recaptcha_page (self , source = None ):
17791818 if not source or len (source ) < 400 :
1780- time .sleep (0.22 )
1819+ time .sleep (0.2 )
17811820 source = self .get_page_source ()
17821821 if (
17831822 'id="recaptcha-token"' in source
17841823 or 'title="reCAPTCHA"' in source
17851824 ):
17861825 return True
1826+ elif "/recaptcha/api.js" in source :
1827+ time .sleep (1.6 ) # Still loading
1828+ return True
17871829 return False
17881830
1789- def __gui_click_recaptcha (self ):
1831+ def __gui_click_recaptcha (self , use_cdp = False ):
17901832 selector = None
17911833 if self .is_element_visible ('iframe[title="reCAPTCHA"]' ):
17921834 selector = 'iframe[title="reCAPTCHA"]'
@@ -1797,19 +1839,39 @@ def __gui_click_recaptcha(self):
17971839 element_rect = self .get_gui_element_rect (selector , timeout = 1 )
17981840 e_x = element_rect ["x" ]
17991841 e_y = element_rect ["y" ]
1800- x = e_x + 29
1801- y = e_y + 35
1842+ x_offset = 26
1843+ y_offset = 35
1844+ if shared_utils .is_windows ():
1845+ x_offset = 29
1846+ x = e_x + x_offset
1847+ y = e_y + y_offset
18021848 sb_config ._saved_cf_x_y = (x , y )
18031849 time .sleep (0.08 )
1804- self .gui_click_x_y (x , y )
1850+ if use_cdp :
1851+ self .sleep (0.03 )
1852+ gui_lock = FileLock (constants .MultiBrowser .PYAUTOGUILOCK )
1853+ with gui_lock : # Prevent issues with multiple processes
1854+ self .bring_active_window_to_front ()
1855+ time .sleep (0.056 )
1856+ self .click_with_offset (selector , x_offset , y_offset )
1857+ time .sleep (0.056 )
1858+ else :
1859+ self .gui_click_x_y (x , y )
1860+
1861+ def solve_captcha (self ):
1862+ self .__click_captcha (use_cdp = True )
18051863
18061864 def gui_click_captcha (self ):
1807- if self ._on_a_cf_turnstile_page ():
1808- pass
1809- elif self ._on_a_g_recaptcha_page ():
1810- self .__gui_click_recaptcha ()
1865+ self .__click_captcha (use_cdp = False )
1866+
1867+ def __click_captcha (self , use_cdp = False ):
1868+ """Uses PyAutoGUI unless use_cdp == True"""
1869+ self .sleep (0.056 )
1870+ source = self .get_page_source ()
1871+ if self ._on_a_g_recaptcha_page (source ):
1872+ self .__gui_click_recaptcha (use_cdp )
18111873 return
1812- else :
1874+ elif not self . _on_a_cf_turnstile_page ( source ) :
18131875 return
18141876 selector = None
18151877 if (
@@ -1970,14 +2032,24 @@ def gui_click_captcha(self):
19702032 element_rect = self .get_gui_element_rect (selector , timeout = 1 )
19712033 e_x = element_rect ["x" ]
19722034 e_y = element_rect ["y" ]
1973- x = e_x + 32
1974- if not shared_utils .is_windows ():
1975- y = e_y + 32
1976- else :
1977- y = e_y + 28
2035+ x_offset = 32
2036+ y_offset = 32
2037+ if shared_utils .is_windows ():
2038+ y_offset = 28
2039+ x = e_x + x_offset
2040+ y = e_y + y_offset
19782041 sb_config ._saved_cf_x_y = (x , y )
19792042 time .sleep (0.08 )
1980- self .gui_click_x_y (x , y )
2043+ if use_cdp :
2044+ self .sleep (0.03 )
2045+ gui_lock = FileLock (constants .MultiBrowser .PYAUTOGUILOCK )
2046+ with gui_lock : # Prevent issues with multiple processes
2047+ self .bring_active_window_to_front ()
2048+ time .sleep (0.056 )
2049+ self .click_with_offset (selector , x_offset , y_offset )
2050+ time .sleep (0.056 )
2051+ else :
2052+ self .gui_click_x_y (x , y )
19812053
19822054 def __gui_drag_drop (self , x1 , y1 , x2 , y2 , timeframe = 0.25 , uc_lock = False ):
19832055 self .__install_pyautogui_if_missing ()
0 commit comments