class Youtube: # ------------------------------------------------------------- Init ------------------------------------------------------------- # def __init__(self, cookies_folder_path: str, extensions_folder_path: str, email: Optional[str] = None, password: Optional[str] = None, host: Optional[str] = None, port: Optional[int] = None, screen_size: Optional[Tuple[int, int]] = None, full_screen: bool = False, disable_images: bool = False, login_prompt_callback: Optional[Callable[[str], None]] = None): self.browser = Firefox(cookies_folder_path, extensions_folder_path, host=host, port=port, screen_size=screen_size, full_screen=full_screen, disable_images=disable_images) self.channel_id = None try: self.__internal_channel_id = cookies_folder_path.strip('/').split( '/')[-1] except: self.__internal_channel_id = cookies_folder_path try: if self.browser.login_via_cookies( YT_URL, LOGIN_INFO_COOKIE_NAME) or self.login( email=email, password=password, login_prompt_callback=login_prompt_callback): time.sleep(0.5) self.browser.get(YT_URL) time.sleep(0.5) self.browser.save_cookies() time.sleep(0.5) self.channel_id = self.get_current_channel_id() except Exception as e: print(e) self.quit() raise # ------------------------------------------------------ Public properties ------------------------------------------------------- # @property def is_logged_in(self) -> bool: return self.browser.has_cookie(LOGIN_INFO_COOKIE_NAME) # -------------------------------------------------------- Public methods -------------------------------------------------------- # def login( self, email: Optional[str], password: Optional[str], login_prompt_callback: Optional[Callable[[str], None]] = None) -> bool: org_url = self.browser.driver.current_url self.browser.get(YT_URL) time.sleep(0.5) logged_in = self.is_logged_in if not logged_in and email is not None and password is not None: logged_in = self.__login_auto(email, password) if not logged_in: print('Could not log you in automatically.') logged_in = self.__login_manual( login_prompt_callback=login_prompt_callback) if logged_in: time.sleep(0.5) self.browser.get(YT_URL) time.sleep(0.5) self.browser.save_cookies() time.sleep(0.5) self.browser.get(org_url) return logged_in def upload( self, video_path: str, title: str, description: str, tags: List[str], _timeout: Optional[int] = 60 * 3, # 3 min extra_sleep_after_upload: Optional[int] = None, extra_sleep_before_publish: Optional[int] = None ) -> (bool, Optional[str]): if _timeout is not None: try: with timeout.timeout(_timeout): return self.__upload( video_path, title, description, tags, extra_sleep_after_upload=extra_sleep_after_upload, extra_sleep_before_publish=extra_sleep_before_publish) except Exception as e: print('Upload', e) # self.browser.get(YT_URL) return False, None else: return self.__upload( video_path, title, description, tags, extra_sleep_after_upload=extra_sleep_after_upload, extra_sleep_before_publish=extra_sleep_before_publish) def get_current_channel_id(self) -> Optional[str]: self.browser.get(YT_URL) try: return json.loads( strings.between(self.browser.driver.page_source, 'var ytInitialGuideData = ', '};') + '}')['responseContext']['serviceTrackingParams'][2]['params'][ 0]['value'] except Exception as e: print('get_current_channel_id', e) return None def load_video(self, video_id: str): self.browser.get(self.__video_url(video_id)) def comment_on_video(self, video_id: str, comment: str, pinned: bool = False, _timeout: Optional[int] = 15) -> (bool, bool): if _timeout is not None: try: with timeout.timeout(_timeout): return self.__comment_on_video(video_id, comment, pinned=pinned) except Exception as e: print('Comment', e) # self.browser.get(YT_URL) return False, False else: return self.__comment_on_video(video_id, comment, pinned=pinned) def get_channel_video_ids( self, channel_id: Optional[str] = None, ignored_titles: Optional[List[str]] = None) -> List[str]: video_ids = [] ignored_titles = ignored_titles or [] channel_id = channel_id or self.channel_id try: self.browser.get(self.__channel_videos_url(channel_id)) last_page_source = self.browser.driver.page_source while True: self.browser.scroll(1500) i = 0 max_i = 100 sleep_time = 0.1 should_break = True while i < max_i: i += 1 time.sleep(sleep_time) if len(last_page_source) != len( self.browser.driver.page_source): last_page_source = self.browser.driver.page_source should_break = False break if should_break: break soup = bs(self.browser.driver.page_source, 'lxml') elems = soup.find_all( 'a', { 'id': 'video-title', 'class': 'yt-simple-endpoint style-scope ytd-grid-video-renderer' }) for elem in elems: if 'title' in elem.attrs: should_continue = False title = elem['title'].strip().lower() for ignored_title in ignored_titles: if ignored_title.strip().lower() == title: should_continue = True break if should_continue: continue if 'href' in elem.attrs and '/watch?v=' in elem['href']: vid_id = strings.between(elem['href'], '?v=', '&') if vid_id is not None and vid_id not in video_ids: video_ids.append(vid_id) except Exception as e: print(e) return video_ids def check_analytics(self) -> bool: self.browser.get(YT_STUDIO_URL) try: self.browser.get( self.browser.find( By.XPATH, "//a[@id='menu-item-3']").get_attribute('href')) return True except Exception as e: print(e) return False def quit(self): self.browser.driver.quit() # ------------------------------------------------------- Private methods -------------------------------------------------------- # def __upload( self, video_path: str, title: str, description: str, tags: List[str], extra_sleep_after_upload: Optional[int] = None, extra_sleep_before_publish: Optional[int] = None ) -> (bool, Optional[str]): self.browser.get(YT_URL) time.sleep(1.5) try: self.browser.get(YT_UPLOAD_URL) time.sleep(1.5) self.browser.save_cookies() self.browser.find(By.XPATH, "//input[@type='file']").send_keys(video_path) print('Upload: uploaded video') if extra_sleep_after_upload is not None and extra_sleep_after_upload > 0: time.sleep(extra_sleep_after_upload) title_field = self.browser.find_by('div', id_='textbox') time.sleep(0.5) title_field.send_keys(Keys.BACK_SPACE) time.sleep(0.5) title_field.send_keys(title[:MAX_TITLE_CHAR_LEN]) print('Upload: added title') description_container = self.browser.find( By.XPATH, "/html/body/ytcp-uploads-dialog/paper-dialog/div/ytcp-animatable[1]/ytcp-uploads-details/div/ytcp-uploads-basics/ytcp-mention-textbox[2]" ) description_field = self.browser.find( By.ID, "textbox", element=description_container) description_field.click() time.sleep(0.5) description_field.clear() time.sleep(0.5) description_field.send_keys(description[:MAX_DESCRIPTION_CHAR_LEN]) print('Upload: added description') self.browser.find( By.XPATH, "/html/body/ytcp-uploads-dialog/paper-dialog/div/ytcp-animatable[1]/ytcp-uploads-details/div/div/ytcp-button/div" ).click() print("Upload: clicked more options") tags_container = self.browser.find( By.XPATH, "/html/body/ytcp-uploads-dialog/paper-dialog/div/ytcp-animatable[1]/ytcp-uploads-details/div/ytcp-uploads-advanced/ytcp-form-input-container/div[1]/div[2]/ytcp-free-text-chip-bar/ytcp-chip-bar/div" ) tags_field = self.browser.find(By.ID, "text-input", tags_container) tags_field.send_keys(','.join(tags) + ',') print("Upload: added tags") kids_section = self.browser.find(By.NAME, "NOT_MADE_FOR_KIDS") self.browser.find(By.ID, "radioLabel", kids_section).click() print("Upload: did set NOT_MADE_FOR_KIDS") self.browser.find(By.ID, 'next-button').click() print('Upload: clicked first next') self.browser.find(By.ID, 'next-button').click() print('Upload: clicked second next') public_main_button = self.browser.find(By.NAME, "PUBLIC") self.browser.find(By.ID, 'radioLabel', public_main_button).click() print('Upload: set to public') try: video_url_container = self.browser.find( By.XPATH, "//span[@class='video-url-fadeable style-scope ytcp-video-info']", timeout=2.5) video_url_element = self.browser.find( By.XPATH, "//a[@class='style-scope ytcp-video-info']", element=video_url_container, timeout=2.5) video_id = video_url_element.get_attribute('href').split( '/')[-1] except Exception as e: print(e) video_id = None i = 0 if extra_sleep_before_publish is not None and extra_sleep_before_publish > 0: time.sleep(extra_sleep_before_publish) while True: try: progress_label = self.browser.find_by( 'span', { 'class': 'progress-label style-scope ytcp-video-upload-progress' }, timeout=0.1 ) or self.browser.find_by('span', { 'class': 'progress-label-old style-scope ytcp-video-upload-progress' }, timeout=0.1) progress_label_text = progress_label.text.lower() if 'finished' in progress_label_text or 'process' in progress_label_text: done_button = self.browser.find(By.ID, 'done-button') if done_button.get_attribute( 'aria-disabled') == 'false': done_button.click() print('Upload: published') time.sleep(3) self.browser.get(YT_URL) return True, video_id except Exception as e: print(e) i += 1 if i >= 20: done_button = self.browser.find(By.ID, 'done-button') if done_button.get_attribute( 'aria-disabled') == 'false': done_button.click() print('Upload: published') time.sleep(3) self.browser.get(YT_URL) return True, video_id raise time.sleep(1) except Exception as e: print(e) self.browser.get(YT_URL) return False, None # returns (commented_successfully, pinned_comment_successfully) def __comment_on_video(self, video_id: str, comment: str, pinned: bool = False) -> (bool, bool): self.load_video(video_id) time.sleep(1) self.browser.scroll(150) time.sleep(1) self.browser.scroll(100) time.sleep(1) self.browser.scroll(100) try: header = self.browser.find_by('div', id_='masthead-container', class_='style-scope ytd-app') comment_field = self.browser.find(By.XPATH, "//div[@id='placeholder-area']", timeout=5) self.browser.scroll_to_element(comment_field, header_element=header) time.sleep(0.5) print('comment: clicking comment_field') comment_field.click() print('comment: sending keys') self.browser.find(By.XPATH, "//div[@id='contenteditable-root']", timeout=0.5).send_keys(comment) print('comment: clicking post_comment') self.browser.find( By.XPATH, "//ytd-button-renderer[@id='submit-button' and @class='style-scope ytd-commentbox style-primary size-default']", timeout=0.5).click() if not pinned: return True, False try: dropdown_menu_xpath = "//yt-sort-filter-sub-menu-renderer[@class='style-scope ytd-comments-header-renderer']" dropdown_menu = self.browser.find(By.XPATH, dropdown_menu_xpath) self.browser.scroll_to_element(dropdown_menu, header_element=header) time.sleep(0.5) dropdown_trigger_xpath = "//paper-button[@id='label' and @class='dropdown-trigger style-scope yt-dropdown-menu']" print('comment: clicking dropdown_trigger (open)') self.browser.find(By.XPATH, dropdown_trigger_xpath, element=dropdown_menu, timeout=2.5).click() try: dropdown_menu = self.browser.find(By.XPATH, dropdown_menu_xpath) dropdown_elements = [ elem for elem in self.browser.find_all( By.XPATH, "//a", element=dropdown_menu, timeout=2.5) if 'yt-dropdown-menu' in elem.get_attribute('class') ] last_dropdown_element = dropdown_elements[-1] if last_dropdown_element.get_attribute( 'aria-selected') == 'false': time.sleep(0.25) print('comment: clicking last_dropdown_element') last_dropdown_element.click() else: print( 'comment: clicking dropdown_trigger (close) (did not click last_dropdown_element (did not find it))' ) self.browser.find(By.XPATH, dropdown_trigger_xpath, element=dropdown_menu, timeout=2.5).click() except Exception as e: print(e) print( 'comment: clicking dropdown_trigger (close) (did not click last_dropdown_element (error occured))' ) self.browser.find(By.XPATH, dropdown_trigger_xpath, element=dropdown_menu, timeout=2.5).click() except Exception as e: print(e) # self.browser.scroll(100) time.sleep(2.5) for comment_thread in self.browser.find_all( By.XPATH, "//ytd-comment-thread-renderer[@class='style-scope ytd-item-section-renderer']" ): pinned_element = self.browser.find( By.XPATH, "//yt-icon[@class='style-scope ytd-pinned-comment-badge-renderer']", element=comment_thread, timeout=0.5) pinned = pinned_element is not None and pinned_element.is_displayed( ) if pinned: continue try: # button_3_dots button_3_dots = self.browser.find( By.XPATH, "//yt-icon-button[@id='button' and @class='dropdown-trigger style-scope ytd-menu-renderer']", element=comment_thread, timeout=2.5) self.browser.scroll_to_element(button_3_dots, header_element=header) time.sleep(0.5) print('comment: clicking button_3_dots') button_3_dots.click() popup_renderer_3_dots = self.browser.find( By.XPATH, "//ytd-menu-popup-renderer[@class='style-scope ytd-popup-container']", timeout=2) # dropdown menu item (first) print( 'comment: clicking button_3_dots-dropdown_menu_item (to pin it)' ) self.browser.find( By.XPATH, "//a[@class='yt-simple-endpoint style-scope ytd-menu-navigation-item-renderer']", element=popup_renderer_3_dots, timeout=2.5).click() confirm_button_container = self.browser.find( By.XPATH, "//yt-button-renderer[@id='confirm-button' and @class='style-scope yt-confirm-dialog-renderer style-primary size-default']", timeout=5) # confirm button print('comment: clicking confirm_button') self.browser.find( By.XPATH, "//a[@class='yt-simple-endpoint style-scope yt-button-renderer']", element=confirm_button_container, timeout=2.5).click() time.sleep(2) return True, True except Exception as e: print(e) return True, False # could not find new comment print('no_new_comments') return True, False except Exception as e: print(e) return False, False def __login_auto(self, email: str, password: str) -> bool: self.browser.get(YT_LOGIN_URL) time.sleep(0.5) try: with timeout.timeout(30): email_field = self.browser.find_by('input', { 'id': 'identifierId', 'type': 'email' }) email_field.click() self.browser.send_keys_delay_random(email_field, email) time.sleep(1) self.browser.find_by('div', { 'jscontroller': 'VXdfxd', 'role': 'button' }).click() time.sleep(1) from selenium.webdriver.common.action_chains import ActionChains action = ActionChains(self.browser.driver) pass_container = self.browser.find_by('div', { 'id': 'password', 'jscontroller': 'pxq3x' }) pass_field = self.browser.find_by('input', {'type': 'password'}, in_element=pass_container) action.move_to_element(pass_field).perform() pass_field.click() self.browser.send_keys_delay_random(pass_field, password) time.sleep(1) self.browser.find_by('div', { 'jscontroller': 'VXdfxd', 'role': 'button' }).click() time.sleep(1) except Exception as e: print('__login_auto', e) self.browser.get(YT_URL) time.sleep(0.5) return self.is_logged_in def __login_manual( self, login_prompt_callback: Optional[Callable[[str], None]] = None) -> bool: self.browser.get(YT_LOGIN_URL) time.sleep(0.5) try: if login_prompt_callback is not None: login_prompt_callback(self.__internal_channel_id + ' needs manual input to log in.') input('Log in then press return') except Exception as e: print('__login_manual', e) self.browser.get(YT_URL) time.sleep(0.5) return self.is_logged_in def __video_url(self, video_id: str) -> str: return YT_URL + '/watch?v=' + video_id def __channel_videos_url(self, channel_id: str) -> str: return YT_URL + '/channel/' + channel_id + '/videos?view=0&sort=da&flow=grid' # ---------------------------------------------------------------------------------------------------------------------------------------- #
class Youtube: # ------------------------------------------------------------- Init ------------------------------------------------------------- # def __init__(self, cookies_folder_path: Optional[str] = None, extensions_folder_path: Optional[str] = None, email: Optional[str] = None, password: Optional[str] = None, host: Optional[str] = None, port: Optional[int] = None, screen_size: Optional[Tuple[int, int]] = None, full_screen: bool = False, disable_images: bool = False, user_agent: Optional[str] = None, login_prompt_callback: Optional[Callable[[str], None]] = None, headless: bool = False): self.browser = Firefox(cookies_folder_path, extensions_folder_path, host=host, port=port, screen_size=screen_size, user_agent=user_agent, full_screen=full_screen, disable_images=disable_images, headless=headless) self.channel_id = None self.browser.get(YT_URL) self.__dismiss_alerts() try: self.__internal_channel_id = cookies_folder_path.strip('/').split( '/')[-1] except: self.__internal_channel_id = cookies_folder_path or self.browser.cookies_folder_path try: if self.browser.login_via_cookies(YT_URL, LOGIN_INFO_COOKIE_NAME): time.sleep(0.5) self.browser.get(YT_URL) time.sleep(0.5) self.browser.save_cookies() time.sleep(0.5) self.channel_id = self.get_current_channel_id() elif email and password: self.login(email=email, password=password, login_prompt_callback=login_prompt_callback) except Exception as e: print(e) self.quit() raise # ------------------------------------------------------ Public properties ------------------------------------------------------- # @property def is_logged_in(self) -> bool: return self.browser.has_cookie(LOGIN_INFO_COOKIE_NAME) # -------------------------------------------------------- Public methods -------------------------------------------------------- # def login( self, email: Optional[str], password: Optional[str], login_prompt_callback: Optional[Callable[[str], None]] = None) -> bool: org_url = self.browser.driver.current_url self.browser.get(YT_URL) time.sleep(0.5) logged_in = self.is_logged_in if not logged_in and email is not None and password is not None: try: logged_in = self.__login_auto(email, password, timeout=30) except Exception as e: print('__login_auto', e) logged_in = False if not logged_in: print('Could not log you in automatically.') logged_in = self.__login_manual( login_prompt_callback=login_prompt_callback) if logged_in: time.sleep(0.5) self.browser.get(YT_URL) time.sleep(0.5) self.browser.save_cookies() time.sleep(0.5) self.channel_id = self.get_current_channel_id() time.sleep(0.5) self.browser.get(org_url) return logged_in def watch_video( self, video_id: str, percent_to_watch: float = -1, # 0-100 # -1 means all like: bool = False ) -> Tuple[bool, bool]: # watched, liked watched = False liked = False try: self.browser.get(YT_WATCH_VIDEO_URL + video_id) length_s = float( strings.between(self.browser.driver.page_source, 'detailpage\\\\u0026len=', '\\\\')) play_button = self.browser.find_by( 'button', class_='ytp-large-play-button ytp-button', timeout=0.5) if play_button and play_button.is_displayed(): play_button.click() time.sleep(1) while True: ad = self.browser.find_by('div', class_='video-ads ytp-ad-module', timeout=0.5) if not ad or not ad.is_displayed(): break time.sleep(0.1) watched = True seconds_to_watch = percent_to_watch / 100 * length_s if percent_to_watch >= 0 else length_s if seconds_to_watch > 0: print('Goinng to watch', seconds_to_watch) time.sleep(seconds_to_watch) return watched, self.like( video_id) if like and self.is_logged_in else False except Exception as e: print(e) return watched, liked def like(self, video_id: str) -> bool: self.browser.get(YT_WATCH_VIDEO_URL + video_id) try: buttons_container = self.browser.find_by( 'div', id_='top-level-buttons', class_='style-scope ytd-menu-renderer', timeout=1.5) if buttons_container: button_container = self.browser.find_by( 'ytd-toggle-button-renderer', class_= 'style-scope ytd-menu-renderer force-icon-button style-text', timeout=0.5, in_element=buttons_container) if button_container: button = self.browser.find_by('button', id_='button', timeout=0.5, in_element=button_container) if button: attr = button.get_attribute('aria-pressed') if attr and attr == 'false': button.click() return True return False except Exception as e: print(e) return False def upload( self, video_path: str, title: str, description: str, tags: List[str], made_for_kids: bool = False, visibility: Visibility = Visibility.PUBLIC, thumbnail_image_path: Optional[str] = None, _timeout: Optional[int] = 60 * 3, # 3 min extra_sleep_after_upload: Optional[int] = None, extra_sleep_before_publish: Optional[int] = None ) -> (bool, Optional[str]): res = self.__upload( video_path, title, description, tags, made_for_kids=made_for_kids, visibility=visibility, thumbnail_image_path=thumbnail_image_path, extra_sleep_after_upload=extra_sleep_after_upload, extra_sleep_before_publish=extra_sleep_before_publish, timeout=_timeout) if res == TIME_OUT_ERROR: print('Upload:', TIME_OUT_ERROR) return False, None return res def get_current_channel_id(self, _click_avatar: bool = False, _get_home_url: bool = False) -> Optional[str]: if _get_home_url: self.browser.get(YT_URL) try: if _click_avatar: avatar_button = self.browser.find_by('button', id_='avatar-btn', timeout=0.5) if avatar_button: avatar_button.click() href_containers = self.browser.find_all_by( 'a', class_= 'yt-simple-endpoint style-scope ytd-compact-link-renderer', timeout=0.5) if href_containers: for href_container in href_containers: href = href_container.get_attribute('href') if href and 'channel/' in href: return strings.between(href, 'channel/', '?') except Exception as e: print(e) if not _click_avatar: return self.get_current_channel_id(_click_avatar=True, _get_home_url=_get_home_url) elif not _get_home_url: return self.get_current_channel_id(_click_avatar=False, _get_home_url=True) return None def load_video(self, video_id: str): self.browser.get(self.__video_url(video_id)) def comment_on_video(self, video_id: str, comment: str, pinned: bool = False, _timeout: Optional[int] = 15) -> (bool, bool): res = self.__comment_on_video(video_id, comment, pinned=pinned, timeout=_timeout) if res == TIME_OUT_ERROR: print('Comment:', TIME_OUT_ERROR) return False, False return res def get_channel_video_ids( self, channel_id: Optional[str] = None, ignored_titles: Optional[List[str]] = None) -> List[str]: video_ids = [] ignored_titles = ignored_titles or [] channel_id = channel_id or self.channel_id try: self.browser.get(self.__channel_videos_url(channel_id)) last_page_source = self.browser.driver.page_source while True: self.browser.scroll(1500) i = 0 max_i = 100 sleep_time = 0.1 should_break = True while i < max_i: i += 1 time.sleep(sleep_time) if len(last_page_source) != len( self.browser.driver.page_source): last_page_source = self.browser.driver.page_source should_break = False break if should_break: break soup = bs(self.browser.driver.page_source, 'lxml') elems = soup.find_all( 'a', { 'id': 'video-title', 'class': 'yt-simple-endpoint style-scope ytd-grid-video-renderer' }) for elem in elems: if 'title' in elem.attrs: should_continue = False title = elem['title'].strip().lower() for ignored_title in ignored_titles: if ignored_title.strip().lower() == title: should_continue = True break if should_continue: continue if 'href' in elem.attrs and '/watch?v=' in elem['href']: vid_id = strings.between(elem['href'], '?v=', '&') if vid_id is not None and vid_id not in video_ids: video_ids.append(vid_id) except Exception as e: print(e) return video_ids def check_analytics( self, tab: AnalyticsTab = AnalyticsTab.OVERVIEW, period: AnalyticsPeriod = AnalyticsPeriod.LAST_28_DAYS) -> bool: if not self.channel_id: print('No channel ID found') return False url = YT_STUDIO_URL.rstrip( '/' ) + '/channel/' + self.channel_id + '/analytics/tab-' + tab.value + '/period-' + period.value try: self.browser.get(url) return True except Exception as e: print(e) return False def get_violations(self) -> Tuple[bool, int]: # has_warning, strikes self.browser.get(YT_STUDIO_URL) try: violations_container = self.browser.find_by( 'div', class_='style-scope ytcd-strikes-item') if not violations_container: return False, 0 violations_label = self.browser.find_by( 'div', class_='label style-scope ytcp-badge', in_element=violations_container) if not violations_label: return False, 0 violation_text = violations_label.text.strip().lower() violation_text_number = 0 try: violation_text_number = int(violation_text) except: pass return True, violation_text_number except Exception as e: print(e) return False, 0 def add_endscreen(self, video_id: str, max_wait_seconds_for_processing: float = 0) -> bool: self.browser.get(YT_STUDIO_VIDEO_URL.format(video_id)) try: start_time = time.time() while True: attrs = self.browser.get_attributes( self.browser.find_by('ytcp-text-dropdown-trigger', id_='endscreen-editor-link')) if not attrs or 'disabled' in attrs: if time.time( ) - start_time < max_wait_seconds_for_processing: time.sleep(1) continue return False else: break self.browser.find_by('ytcp-text-dropdown-trigger', id_='endscreen-editor-link').click() time.sleep(0.5) self.browser.find_all_by( 'div', class_='card style-scope ytve-endscreen-template-picker' )[0].click() time.sleep(0.5) self.browser.find_by('ytcp-button', id_='save-button').click() time.sleep(2) return self.browser.find_by('ytve-endscreen-editor-options-panel', class_='style-scope ytve-editor', timeout=0.5) is None except Exception as e: print(e) return False def quit(self): try: self.browser.driver.quit() except: pass # ------------------------------------------------------- Private methods -------------------------------------------------------- # @stopit.signal_timeoutable(default=TIME_OUT_ERROR, timeout_param='timeout') def __upload(self, video_path: str, title: str, description: str, tags: List[str], made_for_kids: bool = False, visibility: Visibility = Visibility.PUBLIC, thumbnail_image_path: Optional[str] = None, extra_sleep_after_upload: Optional[int] = None, extra_sleep_before_publish: Optional[int] = None, timeout: Optional[int] = None) -> (bool, Optional[str]): self.browser.get(YT_URL) time.sleep(1.5) try: self.browser.get(YT_UPLOAD_URL) time.sleep(1.5) self.browser.save_cookies() self.browser.find(By.XPATH, "//input[@type='file']").send_keys(video_path) print('Upload: uploaded video') if extra_sleep_after_upload is not None and extra_sleep_after_upload > 0: time.sleep(extra_sleep_after_upload) title_field = self.browser.find_by( 'div', id_='textbox', timeout=2) or self.browser.find_by( id_='textbox', timeout=2) time.sleep(0.5) title_field.send_keys(Keys.BACK_SPACE) try: time.sleep(0.5) title_field.send_keys( Keys.COMMAND if platform == 'darwin' else Keys.CONTROL, 'a') time.sleep(0.5) title_field.send_keys(Keys.BACK_SPACE) except Exception as e: print(e) time.sleep(0.5) title_field.send_keys('a') time.sleep(0.5) title_field.send_keys(Keys.BACK_SPACE) time.sleep(0.5) title_field.send_keys(title[:MAX_TITLE_CHAR_LEN]) print('Upload: added title') description_container = self.browser.find( By.XPATH, "/html/body/ytcp-uploads-dialog/paper-dialog/div/ytcp-animatable[1]/ytcp-uploads-details/div/ytcp-uploads-basics/ytcp-mention-textbox[2]" ) description_field = self.browser.find( By.ID, "textbox", element=description_container) description_field.click() time.sleep(0.5) description_field.clear() time.sleep(0.5) description_field.send_keys(description[:MAX_DESCRIPTION_CHAR_LEN]) print('Upload: added description') if thumbnail_image_path is not None: try: self.browser.find(By.XPATH, "//input[@id='file-loader']").send_keys( thumbnail_image_path) time.sleep(0.5) print('Upload: added thumbnail') except Exception as e: print('Upload: Thumbnail error: ', e) self.browser.find( By.XPATH, "/html/body/ytcp-uploads-dialog/paper-dialog/div/ytcp-animatable[1]/ytcp-uploads-details/div/div/ytcp-button/div" ).click() print("Upload: clicked more options") tags_container = self.browser.find( By.XPATH, "/html/body/ytcp-uploads-dialog/paper-dialog/div/ytcp-animatable[1]/ytcp-uploads-details/div/ytcp-uploads-advanced/ytcp-form-input-container/div[1]/div[2]/ytcp-free-text-chip-bar/ytcp-chip-bar/div" ) tags_field = self.browser.find(By.ID, 'text-input', tags_container) tags_field.send_keys(','.join( [t for t in tags if len(t) <= MAX_TAG_CHAR_LEN])[:MAX_TAGS_CHAR_LEN - 1] + ',') print("Upload: added tags") kids_selection_name = 'MADE_FOR_KIDS' if made_for_kids else 'NOT_MADE_FOR_KIDS' kids_section = self.browser.find(By.NAME, kids_selection_name) self.browser.find(By.ID, 'radioLabel', kids_section).click() print('Upload: did set', kids_selection_name) self.browser.find(By.ID, 'next-button').click() print('Upload: clicked first next') self.browser.find(By.ID, 'next-button').click() print('Upload: clicked second next') visibility_main_button = self.browser.find(By.NAME, visibility.name) self.browser.find(By.ID, 'radioLabel', visibility_main_button).click() print('Upload: set to', visibility.name) try: video_url_container = self.browser.find( By.XPATH, "//span[@class='video-url-fadeable style-scope ytcp-video-info']", timeout=2.5) video_url_element = self.browser.find( By.XPATH, "//a[@class='style-scope ytcp-video-info']", element=video_url_container, timeout=2.5) video_id = video_url_element.get_attribute('href').split( '/')[-1] except Exception as e: print(e) video_id = None i = 0 if extra_sleep_before_publish is not None and extra_sleep_before_publish > 0: time.sleep(extra_sleep_before_publish) while True: try: upload_progress_element = self.browser.find_by( 'ytcp-video-upload-progress', class_='style-scope ytcp-uploads-dialog', timeout=0.2) upload_status = UploadStatus.get_status( self.browser, upload_progress_element) if upload_status in [ UploadStatus.PROCESSING_SD, UploadStatus.PROCESSED_SD_PROCESSING_HD, UploadStatus.PROCESSED_ALL ]: done_button = self.browser.find(By.ID, 'done-button') if done_button.get_attribute( 'aria-disabled') == 'false': done_button.click() print('Upload: published') time.sleep(3) self.browser.get(YT_URL) return True, video_id except Exception as e: print(e) i += 1 if i >= 20: done_button = self.browser.find(By.ID, 'done-button') if done_button.get_attribute( 'aria-disabled') == 'false': done_button.click() print('Upload: published') time.sleep(3) self.browser.get(YT_URL) return True, video_id raise time.sleep(1) except Exception as e: print(e) self.browser.get(YT_URL) return False, None def save_cookies(self) -> None: self.browser.get(YT_URL) self.browser.save_cookies() # returns (commented_successfully, pinned_comment_successfully) @stopit.signal_timeoutable(default=TIME_OUT_ERROR, timeout_param='timeout') def __comment_on_video(self, video_id: str, comment: str, pinned: bool = False, timeout: Optional[int] = None) -> (bool, bool): self.load_video(video_id) time.sleep(1) self.browser.scroll(150) time.sleep(1) self.browser.scroll(100) time.sleep(1) self.browser.scroll(100) try: # time.sleep(10000) header = self.browser.find_by('div', id_='masthead-container', class_='style-scope ytd-app') print('comment: looking for \'comment_placeholder_area\'') comment_placeholder_area = self.browser.find_by( 'div', id_='placeholder-area', timeout=5) print('comment: scrollinng to \'comment_placeholder_area\'') self.browser.scroll_to_element(comment_placeholder_area, header_element=header) time.sleep(0.5) print('comment: getting focus') try: self.browser.find_by( 'div', id_='simple-box', class_='style-scope ytd-comments-header-renderer', timeout=0.5).click() self.browser.find_by( 'ytd-comment-simplebox-renderer', class_='style-scope ytd-comments-header-renderer', timeout=0.5).click() # comment_placeholder_area.click() self.browser.find_by('div', id_='placeholder-area', timeout=0.5).click() except Exception as e: print(e) print('comment: sending keys') # self.browser.find_by('div', id_='contenteditable-root', timeout=0.5).click() self.browser.find_by('div', id_='contenteditable-root', timeout=0.5).send_keys(comment) print('comment: clicking post_comment') self.browser.find_by( 'ytd-button-renderer', id_='submit-button', class_='style-scope ytd-commentbox style-primary size-default', timeout=0.5).click() # self.browser.find(By.XPATH, "//ytd-button-renderer[@id='submit-button' and @class='style-scope ytd-commentbox style-primary size-default']", timeout=0.5).click() if not pinned: return True, False try: try: dropdown_menu = self.browser.find_by( 'yt-sort-filter-sub-menu-renderer', class_='style-scope ytd-comments-header-renderer') self.browser.scroll_to_element(dropdown_menu, header_element=header) time.sleep(0.5) print('comment: clicking dropdown_trigger (open)') self.browser.find_by( 'paper-button', id_='label', class_='dropdown-trigger style-scope yt-dropdown-menu', in_element=dropdown_menu, timeout=2.5).click() try: dropdown_menu = self.browser.find_by( 'paper-button', id_='label', class_= 'dropdown-trigger style-scope yt-dropdown-menu', in_element=dropdown_menu, timeout=2.5) dropdown_elements = [ elem for elem in self.browser.find_all_by( 'a', in_element=dropdown_menu, timeout=2.5) if 'yt-dropdown-menu' in elem.get_attribute('class') ] last_dropdown_element = dropdown_elements[-1] if last_dropdown_element.get_attribute( 'aria-selected') == 'false': time.sleep(0.25) print('comment: clicking last_dropdown_element') last_dropdown_element.click() else: print( 'comment: clicking dropdown_trigger (close) (did not click last_dropdown_element (did not find it))' ) self.browser.find_by( 'paper-button', id_='label', class_= 'dropdown-trigger style-scope yt-dropdown-menu', in_element=dropdown_menu, timeout=2.5).click() except Exception as e: print(e) self.browser.find_by( 'paper-button', id_='label', class_= 'dropdown-trigger style-scope yt-dropdown-menu', in_element=dropdown_menu, timeout=2.5).click() except Exception as e: print(e) # self.browser.scroll(100) time.sleep(2.5) for comment_thread in self.browser.find_all_by( 'ytd-comment-thread-renderer', class_='style-scope ytd-item-section-renderer'): pinned_element = self.browser.find_by( 'yt-icon', class_='style-scope ytd-pinned-comment-badge-renderer', in_element=comment_thread, timeout=0.5) pinned = pinned_element is not None and pinned_element.is_displayed( ) if pinned: continue try: # button_3_dots button_3_dots = self.browser.find_by( 'yt-icon-button', id_='button', class_= 'dropdown-trigger style-scope ytd-menu-renderer', in_element=comment_thread, timeout=2.5) self.browser.scroll_to_element(button_3_dots, header_element=header) time.sleep(0.5) print('comment: clicking button_3_dots') button_3_dots.click() popup_renderer_3_dots = self.browser.find_by( 'ytd-menu-popup-renderer', class_='ytd-menu-popup-renderer', timeout=2) time.sleep(1.5) try: self.browser.driver.execute_script( "arguments[0].scrollIntoView();", self.browser.find_by( 'a', class_= 'yt-simple-endpoint style-scope ytd-menu-navigation-item-renderer', in_element=popup_renderer_3_dots, timeout=2.5)) self.browser.find_by( 'a', class_= 'yt-simple-endpoint style-scope ytd-menu-navigation-item-renderer', in_element=popup_renderer_3_dots, timeout=2.5).click() except: try: self.browser.find_by( 'ytd-menu-navigation-item-renderer', class_= 'style-scope ytd-menu-popup-renderer', in_element=popup_renderer_3_dots, timeout=2.5).click() except Exception as e: try: self.browser.find_by( 'paper-item', class_= 'style-scope ytd-menu-navigation-item-renderer', in_element=popup_renderer_3_dots, timeout=2.5).click() except Exception as e: pass confirm_button_container = self.browser.find_by( 'yt-button-renderer', id_='confirm-button', class_= 'style-scope yt-confirm-dialog-renderer style-primary size-default', timeout=5) # confirm button print('comment: clicking confirm_button') self.browser.find_by( 'a', class_= 'yt-simple-endpoint style-scope yt-button-renderer', in_element=confirm_button_container, timeout=2.5).click() time.sleep(2) return True, True except Exception as e: print(e) return True, False except Exception as e: print(e) return True, False # could not find new comment print('no_new_comments') return True, False except Exception as e: print('comment error:', e) return False, False @stopit.signal_timeoutable(default=TIME_OUT_ERROR, timeout_param='timeout') def __login_auto(self, email: str, password: str, timeout: Optional[float] = None) -> bool: self.browser.get(YT_LOGIN_URL) time.sleep(0.5) email_field = self.browser.find_by('input', { 'id': 'identifierId', 'type': 'email' }) email_field.click() self.browser.send_keys_delay_random(email_field, email) time.sleep(1) self.browser.find_by('div', { 'jscontroller': 'VXdfxd', 'role': 'button' }).click() time.sleep(1) action = ActionChains(self.browser.driver) pass_container = self.browser.find_by('div', { 'id': 'password', 'jscontroller': 'pxq3x' }) pass_field = self.browser.find_by('input', {'type': 'password'}, in_element=pass_container) action.move_to_element(pass_field).perform() pass_field.click() self.browser.send_keys_delay_random(pass_field, password) time.sleep(1) self.browser.find_by('div', { 'jscontroller': 'VXdfxd', 'role': 'button' }).click() time.sleep(1) self.browser.get(YT_URL) time.sleep(0.5) return self.is_logged_in def __login_manual( self, login_prompt_callback: Optional[Callable[[str], None]] = None) -> bool: self.browser.get(YT_LOGIN_URL) time.sleep(0.5) try: if login_prompt_callback is not None: login_prompt_callback(self.__internal_channel_id + ' needs manual input to log in.') input('Log in then press return') except Exception as e: print('__login_manual', e) self.browser.get(YT_URL) time.sleep(0.5) return self.is_logged_in def __dismiss_alerts(self): dismiss_button_container = self.browser.find_by('div', id_='dismiss-button', timeout=1.5) if dismiss_button_container: dismiss_button = self.browser.find_by( 'paper-button', id_='button', timeout=0.5, in_element=dismiss_button_container) if dismiss_button: dismiss_button.click() iframe = self.browser.find_by( 'iframe', class_='style-scope ytd-consent-bump-lightbox', timeout=2.5) if iframe: self.browser.driver.switch_to.frame(iframe) agree_button = self.browser.find_by('div', id_='introAgreeButton', timeout=2.5) if agree_button: agree_button.click() if iframe: self.browser.driver.switch_to.default_content() def __video_url(self, video_id: str) -> str: return YT_URL + '/watch?v=' + video_id def __channel_videos_url(self, channel_id: str) -> str: return YT_URL + '/channel/' + channel_id + '/videos?view=0&sort=da&flow=grid' # ---------------------------------------------------------------------------------------------------------------------------------------- #s