33from selenium .webdriver .common .by import By
44from selenium .webdriver .support .ui import WebDriverWait
55from selenium .webdriver .support import expected_conditions as EC
6+ from selenium .common .exceptions import (
7+ TimeoutException ,
8+ InvalidSessionIdException ,
9+ NoSuchWindowException ,
10+ StaleElementReferenceException ,
11+ ElementClickInterceptedException ,
12+ )
613
714from webdriver_manager .chrome import ChromeDriverManager
815from webdriver_manager .firefox import GeckoDriverManager
916from webdriver_manager .opera import OperaDriverManager
1017from webdriver_manager .microsoft import IEDriverManager , EdgeChromiumDriverManager
18+ from selenium .webdriver .chrome .service import Service as ChromeService
19+ from selenium .webdriver .firefox .service import Service as FirefoxService
20+ from selenium .webdriver .ie .service import Service as IEService
21+ from selenium .webdriver .edge .service import Service as EdgeService
1122
1223def get_driver (browser ):
1324 """Get the webdriver specified in configuration"""
1425
1526 # Browser name aliases
1627 chrome = ('chrome' , 'google' , 'google chrome' , 'googlechrome' , 'google-chrome' , 'google_chrome' )
1728 firefox = ('firefox' , 'ff' , 'mozilla' , 'gecko' , 'geckodriver' , 'fire fox' , 'fire_fox' , 'fire-fox' )
18- opera = ('opera' , 'opera gx' , 'operagx' , 'opera_gx' , 'opera-gx' )
1929 explorer = ('explorer' , 'ie' , 'internet explorer' , 'internet-explorer' , 'internet_explorer' )
2030 edge = ('edge' , 'microsoft edge' , 'microsoft_edge' , 'microsoft-edge' )
2131
2232 # Download browser binaries according to settings.json
2333 if browser .lower () in chrome :
24- return webdriver .Chrome (ChromeDriverManager ().install ())
34+ # Prefer built-in Selenium Manager; fallback to webdriver_manager only if needed
35+ try :
36+ return webdriver .Chrome ()
37+ except Exception :
38+ service = ChromeService (ChromeDriverManager ().install ())
39+ return webdriver .Chrome (service = service )
2540
2641 elif browser .lower () in firefox :
27- return webdriver .Firefox (executable_path = GeckoDriverManager ().install ())
28-
29- elif browser .lower () in opera :
30- return webdriver .Opera (OperaDriverManager ().install ())
42+ try :
43+ return webdriver .Firefox ()
44+ except Exception :
45+ service = FirefoxService (GeckoDriverManager ().install ())
46+ return webdriver .Firefox (service = service )
3147
3248 elif browser .lower () in explorer :
33- return webdriver .Ie (IEDriverManager ().install ())
49+ try :
50+ return webdriver .Ie ()
51+ except Exception :
52+ service = IEService (IEDriverManager ().install ())
53+ return webdriver .Ie (service = service )
3454
3555 elif browser .lower () in edge :
36- return webdriver .Edge (executable_path = EdgeChromiumDriverManager ().install ())
56+ try :
57+ return webdriver .Edge ()
58+ except Exception :
59+ service = EdgeService (EdgeChromiumDriverManager ().install ())
60+ return webdriver .Edge (service = service )
3761
3862 else :
39- raise RuntimeError ('Browser not found {}' .format (browser .lower ()))
63+ raise RuntimeError ('Browser not found {}. Supported: Chrome, Firefox, Edge, IE ' .format (browser .lower ()))
4064
4165
4266class Bot (object ):
4367 """Handle connections"""
4468
45- def __init__ (self , website , browser ):
69+ def __init__ (self , website , browser , tab_amount = 1 ):
4670 """Initial function"""
4771
72+ self .browser = browser
73+ self .tab_amount = tab_amount
4874 self .driver = get_driver (browser )
49- self .website = website
50- self .wait = WebDriverWait (self .driver , 3 )
75+ self .website = website
76+ # Allow slower page loads/overlays before timing out
77+ self .wait = WebDriverWait (self .driver , 20 )
5178 self .presence = EC .presence_of_element_located
5279 self .visible = EC .visibility_of_element_located
5380
5481 def get_vid (self ):
5582 """Open the YouTube link"""
5683
5784 self .driver .get (self .website )
58-
85+ self ._accept_cookies ()
86+
5987 def play_video (self ):
6088 """Click on the play button"""
6189
62- self .wait .until (self .visible ((By .ID , "video-title" )))
63- self .driver .find_element_by_xpath ("//button[@class='ytp-large-play-button ytp-button']" ).click ()
90+ # Ensure we are on the desired video (avoid autoplay chaining to another video)
91+ if self .website not in self .driver .current_url :
92+ self .driver .get (self .website )
93+
94+ # Wait for player to be ready and ensure play button is clickable
95+ self ._accept_cookies ()
96+ self .wait .until (self .presence ((By .TAG_NAME , "video" )))
97+ self ._dismiss_overlays ()
98+ if not self ._click_play_button ():
99+ # Fallback: try to start video via JS if button not found/clickable
100+ self .driver .execute_script ("""
101+ const v = document.querySelector('video');
102+ if (v) { v.muted = false; v.play().catch(()=>{}); }
103+ """ )
104+
105+ def _dismiss_overlays (self ):
106+ """Attempt to close common YouTube overlays such as cookie consent backdrops."""
107+
108+ # Remove overlay backdrops that block clicks
109+ self .driver .execute_script (
110+ "document.querySelectorAll('tp-yt-iron-overlay-backdrop').forEach(e => e.remove());"
111+ )
112+
113+ # Try to click consent buttons if present
114+ consent_selectors = [
115+ "//button[normalize-space()='I agree']" ,
116+ "//button[normalize-space()='Accept all']" ,
117+ "//button[normalize-space()='Alle akzeptieren']" ,
118+ "//button[normalize-space()='Ich stimme zu']" ,
119+ ]
120+
121+ for selector in consent_selectors :
122+ try :
123+ btn = WebDriverWait (self .driver , 3 ).until (
124+ EC .element_to_be_clickable ((By .XPATH , selector ))
125+ )
126+ btn .click ()
127+ break
128+ except TimeoutException :
129+ continue
130+
131+ def _accept_cookies (self ):
132+ """Accept YouTube/Google cookie banners if present."""
133+
134+ consent_selectors = [
135+ "//button[normalize-space()='I agree']" ,
136+ "//button[normalize-space()='Accept all']" ,
137+ "//button[normalize-space()='Alle akzeptieren']" ,
138+ "//button[normalize-space()='Ich stimme zu']" ,
139+ "//button[normalize-space()='Zustimmen und fortfahren']" ,
140+ "//button[@aria-label='Accept all']" ,
141+ "//button[@aria-label='Alle akzeptieren']" ,
142+ ]
143+
144+ try :
145+ WebDriverWait (self .driver , 5 ).until (lambda d : True )
146+ for selector in consent_selectors :
147+ buttons = self .driver .find_elements (By .XPATH , selector )
148+ for btn in buttons :
149+ if btn .is_displayed () and btn .is_enabled ():
150+ btn .click ()
151+ return
152+ except Exception :
153+ # If anything goes wrong, just proceed; the overlay handler will try again.
154+ pass
155+
156+ def _get_play_button (self ):
157+ """Return a clickable play button element if available."""
158+
159+ try :
160+ return self .wait .until (EC .element_to_be_clickable ((By .CSS_SELECTOR , ".ytp-play-button" )))
161+ except TimeoutException :
162+ buttons = self .driver .find_elements (By .CSS_SELECTOR , ".ytp-play-button" )
163+ for btn in buttons :
164+ if btn .is_displayed () and btn .is_enabled ():
165+ return btn
166+ return None
167+
168+ def _click_play_button (self ):
169+ """Try clicking the play button with retries to avoid stale references."""
170+
171+ for _ in range (3 ):
172+ btn = self ._get_play_button ()
173+ if not btn :
174+ continue
175+ try :
176+ self .driver .execute_script (
177+ "arguments[0].scrollIntoView({block: 'center', inline: 'center'});" , btn
178+ )
179+ self .driver .execute_script ("arguments[0].click();" , btn )
180+ return True
181+ except (StaleElementReferenceException , ElementClickInterceptedException ):
182+ self ._dismiss_overlays ()
183+ continue
184+ return False
64185
65186 def clear_cache (self ):
66187 """Clear the cache"""
@@ -70,15 +191,52 @@ def clear_cache(self):
70191 def refresh (self ):
71192 """Refresh the current tab"""
72193
194+ # Reload the intended video instead of refreshing whatever autoplay selected
73195 self .driver .implicitly_wait (5 )
74- self .driver .refresh ()
196+ self .driver .get (self .website )
197+ self ._accept_cookies ()
75198
76199 def switch_tab (self , tab ):
77200 """Switch to the specified tab"""
78201
79- self .driver .switch_to .window (self .driver .window_handles [tab ])
202+ try :
203+ self .driver .switch_to .window (self .driver .window_handles [tab ])
204+ except (InvalidSessionIdException , NoSuchWindowException , IndexError ):
205+ # Browser/tab closed or session lost; try to rebuild session so script does not exit
206+ self ._restart_session ()
207+ try :
208+ self .driver .switch_to .window (self .driver .window_handles [tab ])
209+ except Exception :
210+ pass
80211
81212 def new_tab (self ):
82213 """Open a blank new tab"""
83214
84215 self .driver .execute_script ("window.open('about:blank');" )
216+
217+ def _restart_session (self ):
218+ """Rebuild the browser session if it was lost."""
219+
220+ try :
221+ self .driver .quit ()
222+ except Exception :
223+ pass
224+
225+ self .driver = get_driver (self .browser )
226+ self .wait = WebDriverWait (self .driver , 20 )
227+
228+ # Reopen tabs and load the target website in each
229+ handles_needed = max (self .tab_amount , 1 )
230+ # Open first tab and load
231+ self .driver .get (self .website )
232+ self ._accept_cookies ()
233+
234+ # Create remaining tabs
235+ for _ in range (handles_needed - 1 ):
236+ self .new_tab ()
237+
238+ # Load website in every tab
239+ for handle in self .driver .window_handles :
240+ self .driver .switch_to .window (handle )
241+ self .driver .get (self .website )
242+ self ._accept_cookies ()
0 commit comments