class YouTubeLogin: """Let user login to YouTube and save the cookies""" def __init__(self): self.browser = Firefox(full_screen=False) self.logger = logging.getLogger() @staticmethod def get_cookie_path_from_username(username): appdata_path = QStandardPaths.writableLocation( QStandardPaths.AppDataLocation) general_cookies_folder_path = os.path.join(appdata_path, 'cookies') os.makedirs(general_cookies_folder_path, exist_ok=True) return os.path.join(general_cookies_folder_path, username) @staticmethod def get_all_usernames(): appdata_path = QStandardPaths.writableLocation( QStandardPaths.AppDataLocation) general_cookies_folder_path = os.path.join(appdata_path, 'cookies') os.makedirs(general_cookies_folder_path, exist_ok=True) return next(os.walk(general_cookies_folder_path))[1] @staticmethod def remove_user_cookies(username): cookie_folder = YouTubeLogin.get_cookie_path_from_username(username) shutil.rmtree(cookie_folder) def get_login(self): self.browser.get(Constant.YOUTUBE_URL) time.sleep(Constant.USER_WAITING_TIME) while (avatar := self.browser.find( By.XPATH, Constant.USER_AVATAR_XPATH)) is None: time.sleep(1) avatar.click() username_div = self.browser.find(By.ID, Constant.USERNAME_ID, timeout=30) username = username_div.text logging.info("Logged in as {}".format(username)) if '7x11x13' in username: raise Exception(':ujel:') self.browser.get(Constant.YOUTUBE_URL) time.sleep(Constant.USER_WAITING_TIME) self.browser.cookies_folder_path = self.get_cookie_path_from_username( username) os.makedirs(self.browser.cookies_folder_path, exist_ok=True) self.browser.save_cookies() self.browser.driver.quit() return username
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 YouTubeUploader: """A class for uploading videos on YouTube via Selenium using metadata JSON file to extract its title, description etc""" def __init__(self, video_path: str, metadata_json_path: Optional[str] = None, thumbnail_path: Optional[str] = None) -> None: self.video_path = video_path self.thumbnail_path = thumbnail_path self.metadata_dict = load_metadata(metadata_json_path) current_working_dir = str(Path.cwd()) self.browser = Firefox(current_working_dir, current_working_dir) self.logger = logging.getLogger(__name__) self.logger.setLevel(logging.DEBUG) self.__validate_inputs() def __validate_inputs(self): if not self.metadata_dict[Constant.VIDEO_TITLE]: self.logger.warning( "The video title was not found in a metadata file") self.metadata_dict[Constant.VIDEO_TITLE] = Path( self.video_path).stem self.logger.warning("The video title was set to {}".format( Path(self.video_path).stem)) if not self.metadata_dict[Constant.VIDEO_DESCRIPTION]: self.logger.warning( "The video description was not found in a metadata file") def upload(self): try: self.__login() return self.__upload() except Exception as e: print(e) # self.__quit() # raise def __login(self): self.browser.get(Constant.YOUTUBE_URL) time.sleep(Constant.USER_WAITING_TIME) if self.browser.has_cookies_for_current_website(): self.browser.load_cookies() time.sleep(Constant.USER_WAITING_TIME) self.browser.refresh() else: self.logger.info('Please sign in and then press enter') input() self.browser.get(Constant.YOUTUBE_URL) time.sleep(Constant.USER_WAITING_TIME) self.browser.save_cookies() def __write_in_field(self, field, string, select_all=False): field.click() time.sleep(Constant.USER_WAITING_TIME) if select_all: field.send_keys(Keys.COMMAND + 'a') time.sleep(Constant.USER_WAITING_TIME) field.send_keys(string) def __upload(self) -> (bool, Optional[str]): self.browser.get(Constant.YOUTUBE_URL) time.sleep(Constant.USER_WAITING_TIME) self.browser.get(Constant.YOUTUBE_UPLOAD_URL) time.sleep(Constant.USER_WAITING_TIME) absolute_video_path = str(Path.cwd() / self.video_path) self.browser.find( By.XPATH, Constant.INPUT_FILE_VIDEO).send_keys(absolute_video_path) self.logger.debug('Attached video {}'.format(self.video_path)) if self.thumbnail_path is not None: absolute_thumbnail_path = str(Path.cwd() / self.thumbnail_path) self.browser.find(By.XPATH, Constant.INPUT_FILE_THUMBNAIL).send_keys( absolute_thumbnail_path) change_display = "document.getElementById('file-loader').style = 'display: block! important'" self.browser.driver.execute_script(change_display) self.logger.debug('Attached thumbnail {}'.format( self.thumbnail_path)) title_field = self.browser.find(By.ID, Constant.TEXTBOX, timeout=10) self.__write_in_field(title_field, self.metadata_dict[Constant.VIDEO_TITLE], select_all=True) self.logger.debug('The video title was set to \"{}\"'.format( self.metadata_dict[Constant.VIDEO_TITLE])) video_description = self.metadata_dict[Constant.VIDEO_DESCRIPTION] if video_description: description_container = self.browser.find( By.XPATH, Constant.DESCRIPTION_CONTAINER) # description_field = self.browser.find(By.ID, Constant.TEXTBOX, element=description_container) # NOTE: description_field we fixed! description_field = self.browser.find( By.XPATH, '//*[@id="details" and @class="style-scope ytcp-uploads-dialog"]//*[@id="textbox" and contains(@aria-label, "Tell viewers")]' ) self.__write_in_field( description_field, self.metadata_dict[Constant.VIDEO_DESCRIPTION]) self.logger.debug('The video description was set to \"{}\"'.format( self.metadata_dict[Constant.VIDEO_DESCRIPTION])) kids_section = self.browser.find(By.NAME, Constant.NOT_MADE_FOR_KIDS_LABEL) self.browser.find(By.ID, Constant.RADIO_LABEL, kids_section).click() self.logger.debug('Selected \"{}\"'.format( Constant.NOT_MADE_FOR_KIDS_LABEL)) # Advanced options self.browser.find(By.XPATH, Constant.MORE_BUTTON).click() self.logger.debug('Clicked MORE OPTIONS') # tags_container = self.browser.find(By.XPATH, Constant.TAGS_INPUT_CONTAINER) # tags_field = self.browser.find(By.ID, Constant.TAGS_INPUT, element=tags_container) # NOTE: Seems like we fixed tags_field! tags_field = self.browser.find( By.XPATH, '//*[@id="text-input" and @aria-label="Tags"]') self.__write_in_field( tags_field, ','.join(self.metadata_dict[Constant.VIDEO_TAGS])) self.logger.debug('The tags were set to \"{}\"'.format( self.metadata_dict[Constant.VIDEO_TAGS])) self.browser.find(By.ID, Constant.NEXT_BUTTON).click() self.logger.debug('Clicked {}'.format(Constant.NEXT_BUTTON)) self.browser.find(By.ID, Constant.NEXT_BUTTON).click() self.logger.debug('Clicked another {}'.format(Constant.NEXT_BUTTON)) self.browser.find(By.ID, Constant.NEXT_BUTTON).click() self.logger.debug('Clicked another {}'.format(Constant.NEXT_BUTTON)) # public_main_button = self.browser.find(By.NAME, Constant.PUBLIC_BUTTON) # self.browser.find(By.ID, Constant.RADIO_LABEL, public_main_button).click() # NOTE: We fixed pathf or visibility (PUBLIC) self.browser.find( By.XPATH, '//*[@id="privacy-radios"]/*[@name="PUBLIC"]').click() self.logger.debug('Made the video {}'.format(Constant.PUBLIC_BUTTON)) video_id = self.__get_video_id() status_container = self.browser.find(By.XPATH, Constant.STATUS_CONTAINER) while True: in_process = status_container.text.find(Constant.UPLOADED) != -1 if in_process: time.sleep(Constant.USER_WAITING_TIME) else: break done_button = self.browser.find(By.ID, Constant.DONE_BUTTON) # Catch such error as # "File is a duplicate of a video you have already uploaded" if done_button.get_attribute('aria-disabled') == 'true': error_message = self.browser.find(By.XPATH, Constant.ERROR_CONTAINER).text self.logger.error(error_message) return False, None done_button.click() self.logger.debug( "Published the video with video_id = {}".format(video_id)) time.sleep(Constant.USER_WAITING_TIME) self.browser.get(Constant.YOUTUBE_URL) self.__quit() return True, video_id def __get_video_id(self) -> Optional[str]: video_id = None try: video_url_container = self.browser.find( By.XPATH, Constant.VIDEO_URL_CONTAINER) video_url_element = self.browser.find(By.XPATH, Constant.VIDEO_URL_ELEMENT, element=video_url_container) video_id = video_url_element.get_attribute( Constant.HREF).split('/')[-1] except: self.logger.warning(Constant.VIDEO_NOT_FOUND_ERROR) pass return video_id def __quit(self): self.browser.driver.quit()
class YouTubeUploader: """A class for uploading videos on YouTube via Selenium using metadata JSON file to extract its title, description etc""" def __init__(self, video_path: str, metadata_json_path: Optional[str] = None) -> None: self.video_path = video_path metadata_dict = load_metadata(metadata_json_path) self.meta_title = metadata_dict[Constant.VIDEO_TITLE] self.meta_description = metadata_dict[Constant.VIDEO_DESCRIPTION] current_working_dir = str(Path.cwd()) self.browser = Firefox(current_working_dir, current_working_dir) self.logger = logging.getLogger(__name__) self.logger.setLevel(logging.DEBUG) self.__validate_inputs() def __validate_inputs(self): if not self.meta_title: self.logger.warning( "The video title was not found in a metadata file") self.meta_title = Path(self.video_path).stem self.logger.warning("The video title was set to {}".format( Path(self.video_path).stem)) if not self.meta_description: self.logger.warning( "The video description was not found in a metadata file") def upload(self): try: self.__login() return self.__upload() except Exception as e: print(e) self.__quit() raise def __login(self): self.browser.get(Constant.YOUTUBE_URL) time.sleep(Constant.USER_WAITING_TIME) if self.browser.has_cookies_for_current_website(): self.browser.load_cookies() time.sleep(Constant.USER_WAITING_TIME) self.browser.refresh() else: self.logger.info('Please sign in and then press enter') input() self.browser.get(Constant.YOUTUBE_URL) time.sleep(Constant.USER_WAITING_TIME) self.browser.save_cookies() def __upload(self) -> (bool, Optional[str]): self.browser.get(Constant.YOUTUBE_URL) time.sleep(Constant.USER_WAITING_TIME) self.browser.get(Constant.YOUTUBE_UPLOAD_URL) time.sleep(Constant.USER_WAITING_TIME) absolute_video_path = str(Path.cwd() / self.video_path) self.browser.find( By.XPATH, Constant.INPUT_FILE_VIDEO).send_keys(absolute_video_path) self.logger.debug('Attached video {}'.format(self.video_path)) title_field = self.browser.find(By.ID, Constant.TEXTBOX, timeout=10) title_field.click() time.sleep(Constant.USER_WAITING_TIME) title_field.clear() time.sleep(Constant.USER_WAITING_TIME) title_field.send_keys(Keys.COMMAND + 'a') time.sleep(Constant.USER_WAITING_TIME) title_field.send_keys(self.meta_title) self.logger.debug('The video title was set to \"{}\"'.format( self.meta_title)) video_description = self.meta_description if video_description: description_container = self.browser.find( By.XPATH, Constant.DESCRIPTION_CONTAINER) description_field = self.browser.find( By.ID, Constant.TEXTBOX, element=description_container) description_field.click() time.sleep(Constant.USER_WAITING_TIME) description_field.clear() time.sleep(Constant.USER_WAITING_TIME) description_field.send_keys(self.meta_description) self.logger.debug('The video description was set to \"{}\"'.format( self.meta_description)) kids_section = self.browser.find(By.NAME, Constant.NOT_MADE_FOR_KIDS_LABEL) self.browser.find(By.ID, Constant.RADIO_LABEL, kids_section).click() self.logger.debug('Selected \"{}\"'.format( Constant.NOT_MADE_FOR_KIDS_LABEL)) self.browser.find(By.ID, Constant.NEXT_BUTTON).click() self.logger.debug('Clicked {}'.format(Constant.NEXT_BUTTON)) self.browser.find(By.ID, Constant.NEXT_BUTTON).click() self.logger.debug('Clicked another {}'.format(Constant.NEXT_BUTTON)) public_main_button = self.browser.find(By.NAME, Constant.PUBLIC_BUTTON) self.browser.find(By.ID, Constant.RADIO_LABEL, public_main_button).click() self.logger.debug('Made the video {}'.format(Constant.PUBLIC_BUTTON)) video_id = self.__get_video_id() status_container = self.browser.find(By.XPATH, Constant.STATUS_CONTAINER) while True: in_process = status_container.text.find(Constant.UPLOADED) != -1 if in_process: time.sleep(Constant.USER_WAITING_TIME) else: break done_button = self.browser.find(By.ID, Constant.DONE_BUTTON) # Catch such error as # "File is a duplicate of a video you have already uploaded" if done_button.get_attribute('aria-disabled') == 'true': error_message = self.browser.find(By.XPATH, Constant.ERROR_CONTAINER).text self.logger.error(error_message) return False, None done_button.click() self.logger.debug( "Published the video with video_id = {}".format(video_id)) time.sleep(Constant.USER_WAITING_TIME) self.browser.get(Constant.YOUTUBE_URL) self.__quit() return True, video_id def __get_video_id(self) -> Optional[str]: video_id = None try: video_url_container = self.browser.find( By.XPATH, Constant.VIDEO_URL_CONTAINER) video_url_element = self.browser.find(By.XPATH, Constant.VIDEO_URL_ELEMENT, element=video_url_container) video_id = video_url_element.get_attribute( Constant.HREF).split('/')[-1] except: self.logger.warning(Constant.VIDEO_NOT_FOUND_ERROR) pass return video_id def __quit(self): self.browser.driver.quit()
class YouTubeUploader: """A class for uploading videos on YouTube via Selenium using metadata dict""" def __init__(self, username, cookies_path="") -> None: # Optional cookies_path to override username # for debugging purposes if cookies_path == "": cookies_path = YouTubeLogin.get_cookie_path_from_username(username) self.username = username self.browser = Firefox(full_screen=False, cookies_folder_path=cookies_path, default_find_func_timeout=10) self.logger = logging.getLogger() def __validate_inputs(self): if Constant.VIDEO_TITLE not in self.metadata_dict: self.logger.warning("The video title was not found in metadata") self.metadata_dict[Constant.VIDEO_TITLE] = Path( self.video_path).stem self.logger.warning("The video title was set to {}".format( Path(self.video_path).stem)) for key in (Constant.VIDEO_DESCRIPTION, Constant.PLAYLIST, Constant.TAGS): if key not in self.metadata_dict: self.metadata_dict[key] = "" title = self.metadata_dict[Constant.VIDEO_TITLE] if len(title) > Constant.MAX_TITLE_LENGTH: self.logger.warning("Truncating title to {} characters".format( Constant.MAX_TITLE_LENGTH)) self.metadata_dict[ Constant.VIDEO_TITLE] = title[:Constant.MAX_TITLE_LENGTH] description = self.metadata_dict[Constant.VIDEO_DESCRIPTION] if len(description) > Constant.MAX_DESCRIPTION_LENGTH: self.logger.warning( "Truncating description to {} characters".format( Constant.MAX_DESCRIPTION_LENGTH)) self.metadata_dict[ Constant. VIDEO_DESCRIPTION] = description[:Constant. MAX_DESCRIPTION_LENGTH] tags = self.metadata_dict[Constant.TAGS] if len(tags) > Constant.MAX_TAGS_LENGTH: self.logger.warning("Truncating tags to {} characters".format( Constant.MAX_TAGS_LENGTH)) self.metadata_dict[Constant.TAGS] = tags[:Constant.MAX_TAGS_LENGTH] def upload(self, video_path, metadata) -> (bool, Optional[str]): try: self.video_path = video_path self.metadata_dict = metadata self.__validate_inputs() self.__login() return self.__upload() except Exception as e: self.__quit() raise def __login(self): self.browser.get(Constant.YOUTUBE_URL) time.sleep(Constant.USER_WAITING_TIME) if self.browser.find(By.XPATH, Constant.USER_AVATAR_XPATH, timeout=5) is not None: # already logged in return if self.browser.has_cookies_for_current_website(): self.browser.load_cookies() time.sleep(Constant.USER_WAITING_TIME) self.browser.refresh() else: raise Exception("Could not find cookies at path {}".format( self.browser.cookies_folder_path)) def __find_playlist_checkbox_no_search(self, name): for element in self.browser.find_all(By.XPATH, Constant.PLAYLIST_LABEL): name_element = element.find_element_by_xpath( ".//span/span[@class='label label-text style-scope ytcp-checkbox-group']" ) if name_element.text == name: return element.find_element_by_xpath(".//ytcp-checkbox-lit") return None def __find_playlist_checkbox(self, name): try: checkbox = self.__find_playlist_checkbox_no_search(name) if not checkbox: # sometimes a newly created playlist will not show up in the list of playlists # we can search for it to update the list search = self.browser.find(By.XPATH, Constant.PLAYLIST_SEARCH) # search behaves weird with opening brackets / parentheses, # possibly other characters as well # need to investigate this further: # Uncaught SyntaxError: unterminated character class # Uncaught SyntaxError: unterminated parenthetical phrases = re.split(r"[\[(]", name) phrases.sort(key=lambda p: len(p)) search.click() time.sleep(Constant.USER_WAITING_TIME) search.clear() time.sleep(Constant.USER_WAITING_TIME) search.send_keys(phrases[-1]) checkbox = self.__find_playlist_checkbox_no_search(name) # clear search so we can create new playlist self.browser.find( By.XPATH, Constant.PLAYLIST_SEARCH_CLEAR_BUTTON).click() return checkbox except Exception as e: logging.error(e) return None def __upload(self) -> (bool, Optional[str]): self.browser.get(Constant.YOUTUBE_URL) time.sleep(Constant.USER_WAITING_TIME) self.browser.get(Constant.YOUTUBE_UPLOAD_URL) time.sleep(Constant.USER_WAITING_TIME) absolute_video_path = str(Path.cwd() / self.video_path) self.browser.find( By.XPATH, Constant.INPUT_FILE_VIDEO).send_keys(absolute_video_path) self.logger.debug('Attached video {}'.format(self.video_path)) time.sleep(Constant.USER_WAITING_TIME) if (title_field := self.browser.find( By.ID, Constant.TEXTBOX, timeout=30)) is None: self.logger.error('Could not find title field') return False, None title_field.click() time.sleep(Constant.USER_WAITING_TIME) title_field.clear() time.sleep(Constant.USER_WAITING_TIME) if sys.platform == 'darwin': title_field.send_keys(Keys.COMMAND + 'a') else: title_field.send_keys(Keys.CONTROL + 'a') time.sleep(Constant.USER_WAITING_TIME) title_field.send_keys(self.metadata_dict[Constant.VIDEO_TITLE]) self.logger.debug('The video title was set to \"{}\"'.format( self.metadata_dict[Constant.VIDEO_TITLE])) video_description = self.metadata_dict[Constant.VIDEO_DESCRIPTION] tags = self.metadata_dict[Constant.TAGS] playlist = self.metadata_dict[Constant.PLAYLIST] if video_description: description_container = self.browser.find( By.XPATH, Constant.DESCRIPTION_CONTAINER) description_field = self.browser.find( By.ID, Constant.TEXTBOX, element=description_container) description_field.click() time.sleep(Constant.USER_WAITING_TIME) description_field.clear() time.sleep(Constant.USER_WAITING_TIME) description_field.send_keys(video_description) self.logger.debug('The video description was set to \"{}\"'.format( video_description)) if playlist: self.browser.find(By.XPATH, Constant.PLAYLIST_CONTAINER).click() time.sleep(Constant.USER_WAITING_TIME) checkbox = self.__find_playlist_checkbox(playlist) if checkbox is None: self.logger.info( "Could not find playlist checkbox, attempting to create new playlist" ) playlist_new_button = self.browser.find( By.XPATH, Constant.PLAYLIST_NEW_BUTTON) self.browser.move_to_element(playlist_new_button) time.sleep(Constant.USER_WAITING_TIME) playlist_new_button.click() time.sleep(Constant.USER_WAITING_TIME) playlist_title = self.browser.find(By.XPATH, Constant.PLAYLIST_NEW_TITLE) if playlist_title is None: logging.error("Could not find playlist title field") return False, None playlist_title.click() time.sleep(Constant.USER_WAITING_TIME) playlist_title.send_keys(playlist) time.sleep(Constant.USER_WAITING_TIME) # Set playlist visibility self.browser.find( By.XPATH, Constant.PLAYLIST_VISIBILITY_DROPDOWN).click() time.sleep(Constant.USER_WAITING_TIME) playlist_visibility = self.browser.find( By.XPATH, '//*[@test-id="{}"]'.format( self.metadata_dict['visibility'])) if playlist_visibility is None: logging.error( "Could not find playlist visibility option {}".format( self.metadata_dict['visibility'])) return False, None playlist_visibility.click() time.sleep(Constant.USER_WAITING_TIME) self.browser.find(By.XPATH, Constant.PLAYLIST_CREATE_BUTTON).click() time.sleep(Constant.USER_WAITING_TIME) checkbox = self.__find_playlist_checkbox(playlist) if checkbox is None: logging.error("Could not find playlist: {}".format(playlist)) return False, None else: checkbox.click() time.sleep(Constant.USER_WAITING_TIME) self.browser.find(By.XPATH, Constant.PLAYLIST_DONE_BUTTON).click() time.sleep(Constant.USER_WAITING_TIME) # hide tooltips which can obscure buttons tooltips = self.browser.find_all(By.XPATH, Constant.TOOLTIP) if tooltips is not None: for element in tooltips: self.browser.execute_script_on_element( "arguments[0].style.display = 'none'", element) if tags: self.browser.find(By.XPATH, Constant.MORE_OPTIONS_CONTAINER).click() time.sleep(Constant.USER_WAITING_TIME) self.browser.find(By.XPATH, Constant.TAGS_TEXT_INPUT).send_keys(tags) time.sleep(Constant.USER_WAITING_TIME) time.sleep(Constant.USER_WAITING_TIME) kids_section = self.browser.find(By.NAME, Constant.NOT_MADE_FOR_KIDS_LABEL) self.browser.scroll_to_element(kids_section) time.sleep(Constant.USER_WAITING_TIME) self.browser.find(By.ID, Constant.RADIO_LABEL, kids_section).click() self.logger.debug('Selected \"{}\"'.format( Constant.NOT_MADE_FOR_KIDS_LABEL)) self.browser.find(By.ID, Constant.NEXT_BUTTON).click() self.logger.debug('Clicked {}'.format(Constant.NEXT_BUTTON)) self.browser.find(By.ID, Constant.NEXT_BUTTON).click() self.logger.debug('Clicked another {}'.format(Constant.NEXT_BUTTON)) visibility_button = self.browser.find(By.NAME, self.metadata_dict['visibility']) self.browser.find(By.ID, Constant.RADIO_LABEL, visibility_button).click() self.logger.debug('Made the video {}'.format( self.metadata_dict['visibility'])) video_id = self.__get_video_id() status_container = self.browser.find(By.XPATH, Constant.STATUS_CONTAINER) while True: in_process = status_container.text.find(Constant.UPLOADED) != -1 if in_process: time.sleep(Constant.USER_WAITING_TIME) else: break done_button = self.browser.find(By.ID, Constant.DONE_BUTTON) # Catch such error as # "File is a duplicate of a video you have already uploaded" if done_button.get_attribute('aria-disabled') == 'true': error_message = self.browser.find(By.XPATH, Constant.ERROR_CONTAINER).text self.logger.error(error_message) return False, None done_button.click() self.logger.debug( "Published the video with video_id = {}".format(video_id)) # wait for youtube to save the video info while self.browser.find(By.XPATH, Constant.VIDEO_PUBLISHED_DIALOG) is None: time.sleep(1) return True, video_id
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
class Pinterest: def __init__(self, cookies_folder_path: str, extensions_folder_path: str, host: Optional[str] = None, port: Optional[int] = None, headless: bool = False): self.browser = Firefox(cookies_folder_path, extensions_folder_path, host=host, port=port, headless=headless) try: self.browser.get(PT_URL) time.sleep(1.5) if self.browser.has_cookies_for_current_website(): self.browser.load_cookies() time.sleep(1.5) self.browser.refresh() time.sleep(0.5) else: input('Log in then press enter') self.browser.get(PT_URL) time.sleep(1.5) self.browser.save_cookies() except: traceback.print_exc() self.browser.driver.quit() raise def follow(self, user_name: str) -> bool: try: self.browser.get(UrlCreator.user_url(user_name)) rand.sleep(0.5, 1) follow_container = self.browser.find( By.XPATH, "//div[contains(@data-test-id, 'user-follow-button')]", timeout=2.5) if follow_container: follow_button = self.browser.find( By.XPATH, "//div[contains(@class, 'tBJ dyH iFc yTZ erh tg7 mWe')]", follow_container) if follow_button is not None and follow_button.text == "Follow": follow_button.click() rand.sleep(0.5, 1) else: return False follow_buttons_updated = self.browser.find_all( By.XPATH, "//div[contains(@class, 'tBJ dyH iFc yTZ pBj tg7 mWe')]") for elem in follow_buttons_updated: if elem.text == "Following": print('user.text', elem.text) return True elif follow_container is None: user = self.browser.find( By.XPATH, "//div[contains(@class, 'tBJ dyH iFc yTZ erh tg7 mWe')]") if user.text == "Follow": user.click() rand.sleep(1, 1.5) user_updated = self.browser.find( By.XPATH, "//div[contains(@class, 'tBJ dyH iFc yTZ erh tg7 mWe')]") return user_updated.text == "Following" except: traceback.print_exc() return False def unfollow(self, user_name: str) -> bool: try: self.browser.get(UrlCreator.user_url(user_name)) rand.sleep(1, 2) user_container = self.browser.find( By.XPATH, "//div[contains(@data-test-id, 'user-unfollow-button')]") if user_container is not None: user_element = self.browser.find( By.XPATH, "//button[contains(@class, 'RCK Hsu USg Vxj aZc Zr3 hA- GmH adn a_A gpV hNT iyn BG7 NTm KhY')]", user_container) user_element.click() rand.sleep(0.5, 1) return self.browser.find_by( 'button', attributes={ 'class': 'RCK Hsu USg Vxj aZc Zr3 hA- GmH adn Il7 Jrn hNT iyn BG7 NTm KhY', 'type': 'button' }) is not None else: user = self.browser.find( By.XPATH, "//div[contains(@class, 'tBJ dyH iFc yTZ pBj tg7 mWe')]") if user.text == "Following": user.click() rand.sleep(1, 1.5) user_updated = self.browser.find( By.XPATH, "//div[contains(@class, 'tBJ dyH iFc yTZ erh tg7 mWe')]") return user_updated.text == "Follow" except: traceback.print_exc() return False def repin(self, pin_id: str, board_name: str, needs_repin_id: bool = False) -> Tuple[bool, Optional[str]]: try: self.browser.get(UrlCreator.pin_url(pin_id)) rand.sleep(0.7, 1.2) board_dropdown = self.browser.find( By.XPATH, "//div[contains(@class, 'tBJ dyH iFc _yT pBj DrD IZT swG z-6')]", timeout=3) if board_dropdown is not None: board_dropdown.click() rand.sleep(0.1, 0.5) else: if self.__create_and_save_to_board( board_name) and needs_repin_id: return True, self.__get_link_to_repinned_post() elif self.__create_and_save_to_board( board_name) and needs_repin_id is False: return True, None return False, None boards = self.browser.find_all( By.XPATH, "//div[contains(@class, 'tBJ dyH iFc yTZ pBj DrD IZT mWe z-6')]", timeout=5) for board in boards: if board.text == board_name: board.click() rand.sleep(0.2, 0.5) break else: self.browser.find( By.XPATH, "//div[contains(@class, 'rDA wzk zI7 iyn Hsu')]" ).click() # create board button text_tag = self.browser.find( By.XPATH, "//input[contains(@id, 'boardEditName')]") text_tag.send_keys(board_name) rand.sleep(0.5, 1) self.browser.find( By.XPATH, "//button[contains(@class, 'RCK Hsu USg Vxj aZc Zr3 hA- GmH adn Il7 Jrn hNT iyn BG7 NTm KhY')]" ).click() # create_button if needs_repin_id: return True, self.__get_link_to_repinned_post() else: return self.browser.find( By.XPATH, "//div[contains(@class, 'Eqh Shl s7I zI7 iyn Hsu')]" ) is not None, None rand.sleep(0.5, 1) if needs_repin_id: return self.browser.find( By.XPATH, "//div[contains(@class, 'Eqh Shl s7I zI7 iyn Hsu')]" ) is not None, self.__get_link_to_repinned_post() else: return self.browser.find( By.XPATH, "//div[contains(@class, 'Eqh Shl s7I zI7 iyn Hsu')]" ) is not None, None except: traceback.print_exc() return False, None def __get_link_to_repinned_post(self) -> Optional[str]: try: saved_to_button = self.browser.find( By.XPATH, "//div[contains(@class, 'Shl ujU zI7 iyn Hsu')]", timeout=3) full_link = self.browser.find( By.CSS_SELECTOR, 'a', saved_to_button).get_attribute('href') self.browser.get(full_link) print(full_link) rand.sleep(2.5, 3) latest_image_box = self.browser.find( By.XPATH, "//div[contains(@class, 'Yl- MIw Hb7')]", timeout=5) pin_id = self.browser.find( By.XPATH, "//div[contains(@data-test-id, 'pin')]", latest_image_box).get_attribute('data-test-pin-id') rand.sleep(0.1, 0.3) return pin_id except: traceback.print_exc() return None def __create_and_save_to_board(self, board_name: str) -> bool: try: print('I am in the __create_and_save_to_board func') self.browser.find( By.XPATH, "//div[contains(@class, 'tBJ dyH iFc MF7 erh DrD IZT mWe')]" ).click() # save button self.browser.find( By.XPATH, "//div[contains(@class, 'Umk fte zI7 iyn Hsu')]" ).click() # create_board_button text_tag = self.browser.find( By.XPATH, "//input[contains(@id, 'boardEditName')]") text_tag.send_keys(board_name) rand.sleep(0.5, 0.8) self.browser.find( By.XPATH, "//button[contains(@class, 'RCK Hsu USg Vxj aZc Zr3 hA- GmH adn Il7 Jrn hNT iyn BG7 NTm KhY')]" ).click() # create_button rand.sleep(1, 1.5) return self.browser.find( By.XPATH, "//button[contains(@class, 'RCK Hsu USg Vxj aZc Zr3 hA- GmH adn Il7 Jrn hNT iyn BG7 NTm KhY')]", timeout=3) is None except: traceback.print_exc() return False def get_board_followers( self, user_name: str, board_name: str, ignored_users: List[str], number_of_users_to_follow, full_board_url: str = None ) -> Optional[Tuple[List[str], List[str]]]: try: if full_board_url is not None: self.browser.get(full_board_url) else: self.browser.get(UrlCreator.board_url(user_name, board_name)) rand.sleep(1, 1.5) followers_container = self.browser.find( By.XPATH, "//div[contains(@class, 'rLK iyn eEj FTD L4E DI9 BG7')]") if followers_container is not None: followers_container.click() rand.sleep(1, 1.5) saved_users = 0 final_users = [] while number_of_users_to_follow >= saved_users: try: users_list = self.browser.find_all( By.XPATH, "//div[contains(@class, 'Module User hasText thumb medium')]" ) users_length_before = len(final_users) for user_container in users_list: try: print(user_container.text) print('im in the for') user_url = self.browser.find( By.CSS_SELECTOR, 'a', user_container).get_attribute('href') user_name = user_url.split('.com/')[1].split( '/')[0] if user_name in ignored_users: continue ignored_users.append(user_name) final_users.append(user_name) saved_users += 1 print(saved_users, ':', user_name) if saved_users == number_of_users_to_follow: return (final_users, ignored_users) except: traceback.print_exc() except: traceback.print_exc() users_length_after = len(final_users) see_more_button = self.browser.find( By.XPATH, "//div[contains(@class, 'tBJ dyH iFc yTZ pBj tg7 mWe')]", timeout=1.5) if see_more_button is None or users_length_before == users_length_after: return (final_users, ignored_users) see_more_button.click() rand.sleep(1, 1.5) except: traceback.print_exc() return None def search_pinterest_boards(self, search_term: str, number_of_boards_to_get: int = 35 ) -> Optional[List[Tuple[str, str]]]: try: self.browser.get(UrlCreator.search_board_url(search_term)) rand.sleep(1, 1.5) if self.browser.find(By.XPATH, "//div[contains(@class, 'noResults')]"): return None board_names_container = self.browser.find_all( By.XPATH, "//div[contains(@class, 'Yl- MIw Hb7')]") number_of_saved_boards = 0 board_urls = [] while True: before_scroll = self.browser.current_page_offset_y() for board_name_element in board_names_container: try: full_board_url = self.browser.find( By.CSS_SELECTOR, 'a', board_name_element).get_attribute('href') board_info = full_board_url.split('.com/')[1] user_name = board_info.split('/')[0] board_name = board_info.split('/')[1] if (user_name, board_name) in board_urls: continue board_urls.append((user_name, board_name)) number_of_saved_boards += 1 if number_of_boards_to_get == number_of_saved_boards: return board_urls except: traceback.print_exc() self.browser.scroll(1000) rand.sleep(0.5, 1.5) after_scroll = self.browser.current_page_offset_y() if after_scroll == before_scroll: return board_urls except: traceback.print_exc() return None def get_pins_from_home_feed(self) -> Optional[List[str]]: try: self.browser.get(UrlCreator.home_feed_url()) rand.sleep(1, 1.5) home_pins = [] home_pin_containers = self.browser.find_all( By.XPATH, "//div[contains(@class, 'Yl- MIw Hb7')]") for pin in home_pin_containers: try: full_url = self.browser.find(By.CSS_SELECTOR, 'a', pin).get_attribute('href') if 'pinterest.com' not in full_url: continue if 'pin/' in full_url: pin_id = full_url.split('pin/')[1] home_pins.append(pin_id) except: traceback.print_exc() return home_pins except: traceback.print_exc() return None def post_pin(self, image_path: str, board_name: str, title: Optional[str] = None, description: Optional[str] = None, url: Optional[str] = None) -> Optional[str]: try: self.browser.get(UrlCreator.pin_builder_url()) rand.sleep(1, 1.5) image_box = self.browser.find_by('div', class_='DUt XiG zI7 iyn Hsu') if not image_box: raise 'did not find image_box' image_input = self.browser.find(By.CSS_SELECTOR, 'input', image_box) if not image_input: raise 'did not find image_input' image_input.send_keys(image_path) rand.sleep(1, 1.5) select_board_button = self.browser.find_by( 'button', attributes={'data-test-id': 'board-dropdown-select-button'}) select_board_button.click() rand.sleep(1, 1.5) board_search_field = self.browser.find_by('input', id_='pickerSearchField') self.browser.send_keys_delay_random(board_search_field, board_name) rand.sleep(1, 1.5) boards = self.browser.find_all_by( 'div', class_='tBJ dyH iFc yTZ pBj DrD IZT mWe z-6', timeout=2.5) exists_board = False if boards and len(boards) > 0: for board in boards: if board.text == board_name: exists_board = True board.click() rand.sleep(0.1, 0.5) break if not exists_board: dropdown_boards = self.browser.find_by( 'div', class_='DUt qJc sLG zI7 iyn Hsu') create_board = self.browser.find_by( 'div', class_='rDA wzk zI7 iyn Hsu', in_element=dropdown_boards) if create_board is not None: create_board.click() rand.sleep(0.1, 0.5) board_name_textfield = self.browser.find_by( 'input', id_='boardEditName', timeout=2) if board_name_textfield.get_attribute('value') != board_name: while len(board_name_textfield.get_attribute('value')) > 0: board_name_textfield.send_keys(Keys.BACK_SPACE) board_name_textfield = self.browser.find_by( 'input', id_='boardEditName', timeout=2) self.browser.send_keys_delay_random( board_name_textfield, board_name) rand.sleep(0.5, 1) create_board_button = self.browser.find_by( 'button', class_= 'RCK Hsu USg adn CCY czT F10 xD4 fZz hUC Il7 Jrn hNT BG7 NTm KhY', timeout=2.5) or self.browser.find_by( 'button', {'type': 'submit'}, timeout=2.5) create_board_button.click() rand.sleep(0.5, 1) just_created_board_save_button = self.browser.find_by( 'div', class_='tBJ dyH iFc yTZ erh DrD IZT mWe') just_created_board_save_button.click() rand.sleep(0.5, 1.5) if title: title_box = self.browser.find_by( 'div', class_='CDp xcv L4E zI7 iyn Hsu') if title_box: title_textfield = self.browser.find( By.CSS_SELECTOR, "textarea", title_box) if title_textfield: self.browser.send_keys_delay_random( title_textfield, title) rand.sleep(0.5, 1.5) if description: about_box = self.browser.find_by( 'div', class_='Jea Tte ujU xcv L4E zI7 iyn Hsu', timeout=2) if about_box: about_textfield = self.browser.find_by( 'div', class_='notranslate public-DraftEditor-content', in_element=about_box, timeout=2) if about_textfield: self.browser.send_keys_delay_random( about_textfield, description) rand.sleep(0.5, 1.5) if url: url_box = self.browser.find_by( 'div', {'data-test-id': 'pin-draft-link'}) if url_box: url_textfield = self.browser.find(By.CSS_SELECTOR, "textarea", url_box) if url_textfield: self.browser.send_keys_delay_random(url_textfield, url) rand.sleep(0.5, 1.5) save_button = self.browser.find_by( 'button', {'data-test-id': 'board-dropdown-save-button'}) save_button.click() rand.sleep(0.1, 0.5) see_it_now = self.browser.find_by('div', {'data-test-id': 'seeItNow'}) full_pin_url = self.browser.find(By.CSS_SELECTOR, 'a', see_it_now).get_attribute('href') return full_pin_url.split('pin/')[1] except: traceback.print_exc() return None
class YouTubeUploader: """A class for uploading videos on YouTube via Selenium using metadata JSON file to extract its title, description etc""" def __init__(self, headless: bool, cookies_path: str, channel: str) -> None: self.channel = channel cookies_path = str(Path(cookies_path)) if cookies_path else str( Path.cwd()) assert os.path.isdir( cookies_path), f"Directory '{cookies_path}' does not exist!" self.browser = Firefox(cookies_path, cookies_path, headless=headless) def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): self.browser.driver.quit() def login(self, username: Optional[str], password: Optional[str]) -> bool: self.browser.get(Constant.YOUTUBE_URL) if self.browser.has_cookies_for_current_website(): print("Loading cookies") self.browser.load_cookies() YOUTUBE_STUDIO_URL = f"https://studio.youtube.com/channel/{self.channel}" self.browser.get(YOUTUBE_STUDIO_URL) if self.browser.driver.current_url == YOUTUBE_STUDIO_URL: print("Logged in!") return True if None in {username, password} or "" in { username.strip(), password.strip() }: print("Username or password not provided!") return False # [G] fill in the username (email) print(f"Sending keys: email: '{username}@fel.cvut.cz'") email_field = self.browser.find(By.ID, "identifierId") email_field.click() email_field.clear() email_field.send_keys(f"{username}@fel.cvut.cz") # [G] click 'next' button print("Click: next") self.browser.find(By.ID, "identifierNext").click() if self.browser.find(By.XPATH, Constant.G_LOGIN_FAILED, timeout=2) is not None: print("Invalid username!") return False # [SSO] fill in the username print(f"Sending keys: SSO username: '******'") sso_username_field = self.browser.find(By.ID, "username") sso_username_field.click() sso_username_field.clear() sso_username_field.send_keys(username) # [SSO] fill in the password print(f"Sending keys: SSO password: '******'") sso_password_field = self.browser.find(By.ID, "password") sso_password_field.click() sso_password_field.clear() sso_password_field.send_keys(password) # [SSO] click 'SSO login' button print("Click: SSO login") self.browser.find(By.NAME, "_eventId_proceed").click() if self.browser.find(By.CLASS_NAME, "error-message", timeout=2) is not None: print("Invalid username or password!") return False print("Waiting for Google login...") if self.browser.find(By.ID, "upload-icon", timeout=20) is None: print("Login timeout!") return False if self.browser.driver.current_url != YOUTUBE_STUDIO_URL: print("Login failed!") return False # save cookies print("Saving cookies") self.browser.save_cookies() print("Logged in!") return True def upload(self, video: Video) -> (bool, Optional[str]): self.browser.get(f"https://studio.youtube.com/channel/{self.channel}") print("Click: upload") self.browser.find(By.ID, "upload-icon").click() print(f"Attaching video: '{video.filename}'") self.browser.find(By.XPATH, Constant.INPUT_FILE_VIDEO).send_keys( os.path.abspath(video.filename)) if video.description: print(f"Sending keys: video description: '{video.description}'") description_field = self.browser.find( By.XPATH, Constant.DESCRIPTION_CONTAINER) description_field.click() description_field.clear() description_field.send_keys(video.description) if video.playlist: print("Click: playlist dropdown") self.browser.find(By.CLASS_NAME, "dropdown").click() # TODO: playlist ID print("Selecting the playlist") playlists = self.browser.find_all( By.CSS_SELECTOR, "label.style-scope.ytcp-checkbox-group.ytcp-checkbox-label") for playlist in playlists: playlist_name = self.browser.find(By.CSS_SELECTOR, "span.label.label-text", playlist).text if video.playlist == playlist_name: print(f"Click: playlist checkbox: '{playlist_name}'") self.browser.find(By.CSS_SELECTOR, "ytcp-checkbox-lit.ytcp-checkbox-group", playlist).click() break else: # create a new playlist print("Playlist not found in the list, creating a new one") self.browser.find(By.CLASS_NAME, "new-playlist-button").click() create_playlist_form = self.browser.find( By.ID, "create-playlist-form") print(f"Sending keys: playlist name: '{video.playlist}'") textarea = self.browser.find(By.TAG_NAME, "textarea", element=create_playlist_form) textarea.click() textarea.clear() textarea.send_keys(video.playlist) print("Click: visibility dropdown") self.browser.find(By.CLASS_NAME, "visibility", element=create_playlist_form).click() print(f"Click: visibility: '{video.privacy.upper()}'") self.browser.find( By.CLASS_NAME, f'paper-item[test-id="{video.privacy.upper()}"]', element=create_playlist_form).click() print("Click: create playlist") self.browser.find(By.CLASS_NAME, "create-playlist-button").click() print("Click: done") time.sleep(Constant.USER_WAITING_TIME) self.browser.find(By.CLASS_NAME, "done-button").click() print("Click: not made for kids") kids_section = self.browser.find(By.NAME, Constant.NOT_MADE_FOR_KIDS_LABEL) self.browser.find(By.ID, Constant.RADIO_LABEL, kids_section).click() print(f"Sending keys: video title: '{video.title}'") title_field = self.browser.find(By.ID, Constant.TEXTBOX, timeout=30) title_field.click() title_field.clear() title_field.send_keys(Keys.CONTROL + 'a') title_field.send_keys(video.title) for _ in range(3): print("Click: next") self.browser.find(By.ID, Constant.NEXT_BUTTON).click() if video.privacy: print(f"Click: visibility: '{video.privacy.upper()}'") privacy_button = self.browser.find(By.NAME, video.privacy.upper()) self.browser.find(By.ID, Constant.RADIO_LABEL, privacy_button).click() time.sleep(Constant.USER_WAITING_TIME) video_id = self.__get_video_id() print(f"Video link: https://youtu.be/{video_id}") container = self.browser.find( By.CSS_SELECTOR, ".left-button-area.style-scope.ytcp-uploads-dialog") while True: texts = [ a.text for a in self.browser.find_all( By.CLASS_NAME, "progress-label", container) ] print(f'\r{texts[-1]}\033[K', end="") if any(substring in element for substring in {"complete", "dokončeno"} for element in texts): print() time.sleep(Constant.USER_WAITING_TIME) break else: time.sleep(0.5) print("Click: done") done_button = self.browser.find(By.ID, Constant.DONE_BUTTON) # Catch such error as # "File is a duplicate of a video you have already uploaded" if done_button.get_attribute('aria-disabled') == 'true': error_message = self.browser.find(By.XPATH, Constant.ERROR_CONTAINER).text print(f"Upload ERROR: {error_message}") return False, None done_button.click() self.browser.get(Constant.YOUTUBE_URL) return True, video_id def __get_video_id(self) -> Optional[str]: video_id = None try: video_url_container = self.browser.find( By.XPATH, Constant.VIDEO_URL_CONTAINER) video_url_element = self.browser.find(By.XPATH, Constant.VIDEO_URL_ELEMENT, element=video_url_container) video_id = video_url_element.get_attribute( Constant.HREF).split('/')[-1] except: print(f"ERROR: could not find video_id") return video_id
class YouTubeUploader: """A class for uploading videos on YouTube via Selenium using metadata JSON file to extract its title, description etc""" def __init__(self, video_path: str, metadata_json_path: Optional[str] = None, thumbnail_path: Optional[str] = None) -> None: self.video_path = video_path self.thumbnail_path = thumbnail_path self.metadata_dict = load_metadata(metadata_json_path) current_working_dir = str(Path.cwd()) self.browser = Firefox(current_working_dir, current_working_dir) self.logger = logging.getLogger(__name__) self.logger.setLevel(logging.DEBUG) self.__validate_inputs() def __validate_inputs(self): if not self.metadata_dict[Constant.VIDEO_TITLE]: self.logger.warning("The video title was not found in a metadata file") self.metadata_dict[Constant.VIDEO_TITLE] = Path(self.video_path).stem self.logger.warning("The video title was set to 7777 {}".format(Path(self.video_path).stem)) if not self.metadata_dict[Constant.VIDEO_DESCRIPTION]: self.logger.warning("The video description was not found in a metadata file") # selenium.common.exceptions.WebDriverException: Message: Reached error page: about:neterror?e=nssFailure2&u=https%3A//www.youtube.com/&c=UTF-8&d=%E8%BD%BD%E5%85%A5%E9%A1%B5%E9%9D%A2%E6%97%B6%E4%B8%8E%20www.youtube.com%20%E7%9A%84%E8%BF%9E%E6%8E%A5%E4%B8%AD%E6%96%AD%E3%80%82 def upload(self): try: self.__login() return self.__upload() except Exception as e: print(e) self.__quit() raise def __login(self): self.browser.get(Constant.YOUTUBE_URL) time.sleep(Constant.USER_WAITING_TIME) if self.browser.has_cookies_for_current_website(): self.browser.load_cookies() time.sleep(Constant.USER_WAITING_TIME) self.browser.refresh() else: self.logger.info('Please sign in and then press enter') input() self.browser.get(Constant.YOUTUBE_URL) time.sleep(Constant.USER_WAITING_TIME) self.browser.save_cookies() def __write_in_field(self, field, string, select_all=False): field.click() time.sleep(Constant.USER_WAITING_TIME) if select_all: field.send_keys(Keys.COMMAND + 'a') time.sleep(Constant.USER_WAITING_TIME) field.send_keys(string) def __upload(self) -> (bool, Optional[str]): self.browser.get(Constant.YOUTUBE_URL) time.sleep(Constant.USER_WAITING_TIME) self.browser.get(Constant.YOUTUBE_UPLOAD_URL) time.sleep(Constant.USER_WAITING_TIME) absolute_video_path = str(Path.cwd() / self.video_path) self.browser.find(By.XPATH, Constant.INPUT_FILE_VIDEO).send_keys(absolute_video_path) self.logger.debug('Attached video {}'.format(self.video_path)) if self.thumbnail_path is not None: absolute_thumbnail_path = str(Path.cwd() / self.thumbnail_path) self.browser.find(By.XPATH, Constant.INPUT_FILE_THUMBNAIL).send_keys(absolute_thumbnail_path) change_display = "document.getElementById('file-loader').style = 'display: block! important'" self.browser.driver.execute_script(change_display) self.logger.debug('Attached thumbnail {}'.format(self.thumbnail_path)) title_field = self.browser.find(By.ID, Constant.TEXTBOX, timeout=10) self.__write_in_field(title_field, self.metadata_dict[Constant.VIDEO_TITLE], select_all=True) self.logger.debug('The video title was set to \"{}\"'.format(self.metadata_dict[Constant.VIDEO_TITLE])) video_description = self.metadata_dict[Constant.VIDEO_DESCRIPTION] if video_description: description_container = self.browser.find(By.XPATH, Constant.DESCRIPTION_CONTAINER) description_field = self.browser.find(By.ID, Constant.TEXTBOX, element=description_container) self.__write_in_field(description_field, self.metadata_dict[Constant.VIDEO_DESCRIPTION]) self.logger.debug( 'The video description was set to \"{}\"'.format(self.metadata_dict[Constant.VIDEO_DESCRIPTION])) kids_section = self.browser.find(By.NAME, Constant.NOT_MADE_FOR_KIDS_LABEL) self.browser.find(By.ID, Constant.RADIO_LABEL, kids_section).click() self.logger.debug('Selected \"{}\"'.format(Constant.NOT_MADE_FOR_KIDS_LABEL)) # Advanced options # self.browser.find(By.XPATH, Constant.MORE_BUTTON).click() # self.logger.debug('Clicked MORE OPTIONS') # tags_container = self.browser.find(By.XPATH, # Constant.TAGS_INPUT_CONTAINER) # tags_field = self.browser.find(By.ID, Constant.TAGS_INPUT, element=tags_container) # self.__write_in_field(tags_field, ','.join(self.metadata_dict[Constant.VIDEO_TAGS])) # self.logger.debug( # 'The tags were set to \"{}\"'.format(self.metadata_dict[Constant.VIDEO_TAGS])) self.browser.find(By.ID, Constant.NEXT_BUTTON).click() self.logger.debug('Clicked {}'.format(Constant.NEXT_BUTTON)) self.browser.find(By.ID, Constant.NEXT_BUTTON).click() self.logger.debug('Clicked element another {}'.format(Constant.NEXT_BUTTON)) self.browser.find(By.ID, Constant.NEXT_BUTTON).click() self.logger.debug('Clicked checking another {}'.format(Constant.NEXT_BUTTON)) public_main_button = self.browser.find(By.NAME, Constant.PUBLIC_BUTTON) self.browser.find(By.ID, Constant.RADIO_LABEL, public_main_button).click() self.logger.debug('Made the video {}'.format(Constant.PUBLIC_BUTTON)) video_id = self.__get_video_id() status_container = self.browser.find(By.XPATH, Constant.STATUS_CONTAINER) while True: in_process = status_container.text.find(Constant.UPLOADED) != -1 if in_process: time.sleep(Constant.USER_WAITING_TIME) else: break done_button = self.browser.find(By.ID, Constant.DONE_BUTTON) # Catch such error as # "File is a duplicate of a video you have already uploaded" if done_button.get_attribute('aria-disabled') == 'true': error_message = self.browser.find(By.XPATH, Constant.ERROR_CONTAINER).text self.logger.error(error_message) return False, None done_button.click() self.logger.debug("Published the video with video_id = {}".format(video_id)) time.sleep(Constant.USER_WAITING_TIME) self.browser.get(Constant.YOUTUBE_URL) self.__quit() return True, video_id def __get_video_id(self) -> Optional[str]: video_id = None try: video_url_container = self.browser.find(By.XPATH, Constant.VIDEO_URL_CONTAINER) video_url_element = self.browser.find(By.XPATH, Constant.VIDEO_URL_ELEMENT, element=video_url_container) video_id = video_url_element.get_attribute(Constant.HREF).split('/')[-1] except: self.logger.warning(Constant.VIDEO_NOT_FOUND_ERROR) pass return video_id def __quit(self): self.browser.driver.quit()
class YouTubeUploader: """A class for uploading videos on YouTube via Selenium using metadata JSON file to extract its title, description etc""" def __init__(self, video_path: str, metadata_json_path: Optional[str] = None, thumbnail_path: Optional[str] = None) -> None: self.video_path = video_path self.thumbnail_path = thumbnail_path self.metadata_dict = load_metadata(metadata_json_path) self.current_working_dir = str(Path.cwd()) self.browser = Firefox(self.current_working_dir, self.current_working_dir, headless=True, geckodriver_path="/usr/local/bin/geckodriver") self.logger = logging.getLogger(__name__) self.logger.setLevel(logging.DEBUG) self.__validate_inputs() def __validate_inputs(self): if not self.metadata_dict[Constant.VIDEO_TITLE]: self.logger.warning( "The video title was not found in a metadata file") self.metadata_dict[Constant.VIDEO_TITLE] = Path( self.video_path).stem self.logger.warning("The video title was set to {}".format( Path(self.video_path).stem)) if not self.metadata_dict[Constant.VIDEO_DESCRIPTION]: self.logger.warning( "The video description was not found in a metadata file") def upload(self): try: self.__login() return self.__upload() except Exception as e: print(e) self.__quit() raise def __login(self): self.browser.get(Constant.YOUTUBE_URL) time.sleep(Constant.USER_WAITING_TIME) if self.browser.has_cookies_for_current_website(): self.browser.load_cookies() time.sleep(Constant.USER_WAITING_TIME) self.browser.refresh() time.sleep(Constant.USER_WAITING_TIME) if not self.browser.find(By.XPATH, Constant.YOUTUBE_SIGNIN_BUTTON): return self.logger.debug( 'Couldnt find cookies. attempting login via automation') self.logger.debug('Clicking sign in button on top right corner') self.browser.driver.get_screenshot_as_file('/tmp/ss1.png') self.browser.find(By.XPATH, Constant.YOUTUBE_SIGNIN_BUTTON).click() time.sleep(Constant.USER_WAITING_TIME) self.logger.debug('Attempting to fill email') self.browser.driver.get_screenshot_as_file('/tmp/ss2.png') self.browser.find(By.XPATH, Constant.GOOGLE_SIGNIN_CARD_EMAIL).send_keys( os.getenv("YOUTUBE_USER_EMAIL")) time.sleep(Constant.USER_WAITING_TIME) self.logger.debug('Attempting to click next') self.browser.driver.get_screenshot_as_file('/tmp/ss3.png') self.browser.find(By.XPATH, Constant.GOOGLE_SIGNIN_CARD_EMAIL_NEXT).click() time.sleep(Constant.USER_WAITING_TIME) self.logger.debug('Attempting to fill password') self.browser.driver.get_screenshot_as_file('/tmp/ss4.png') self.browser.find(By.XPATH, Constant.GOOGLE_SIGNIN_CARD_PASSWORD).send_keys( os.getenv("YOUTUBE_USER_PASS")) time.sleep(Constant.USER_WAITING_TIME) self.logger.debug('Attempting to go all in !') self.browser.driver.get_screenshot_as_file('/tmp/ss5.png') self.browser.find(By.XPATH, Constant.GOOGLE_SIGNIN_CARD_PASSWORD_NEXT).click() self.browser.driver.get_screenshot_as_file('/tmp/ss6.png') self.logger.debug( 'Attempting to find Channel Avatar button after signin, on top right corner' ) current_ticker = 0 while current_ticker <= Constant.GOOGLE_SIGNIN_ACCEPTANCE_TIME: if self.browser.find(By.XPATH, Constant.YOUTUBE_CHANNEL_AVATAR_BUTTON): self.logger.debug('Found it! saving youtube cookies...') break self.logger.debug('Sleeping...') time.sleep(1) current_ticker += 1 self.browser.save_cookies() def __write_in_field(self, field, string, select_all=False): field.click() time.sleep(Constant.USER_WAITING_TIME) if select_all: field.send_keys(Keys.COMMAND + 'a') time.sleep(Constant.USER_WAITING_TIME) field.send_keys(string) def __upload(self) -> (bool, Optional[str]): self.browser.get(Constant.YOUTUBE_URL) time.sleep(Constant.USER_WAITING_TIME) self.browser.get(Constant.YOUTUBE_UPLOAD_URL) time.sleep(Constant.USER_WAITING_TIME) absolute_video_path = str(Path.cwd() / self.video_path) self.browser.find( By.XPATH, Constant.INPUT_FILE_VIDEO).send_keys(absolute_video_path) self.logger.debug('Attached video {}'.format(self.video_path)) if self.thumbnail_path is not None: absolute_thumbnail_path = str(Path.cwd() / self.thumbnail_path) time.sleep(Constant.USER_WAITING_TIME_LONG) self.browser.find(By.XPATH, Constant.INPUT_FILE_THUMBNAIL).send_keys( absolute_thumbnail_path) change_display = "document.getElementById('file-loader').style = 'display: block! important'" self.browser.driver.execute_script(change_display) time.sleep(Constant.USER_WAITING_TIME_LONG) self.logger.debug('Attached thumbnail {}'.format( self.thumbnail_path)) # title_field = self.browser.find(By.ID, Constant.TEXTBOX, timeout=10) # self.__write_in_field(title_field, self.metadata_dict[Constant.VIDEO_TITLE], select_all=True) # self.logger.debug('The video title was set to \"{}\"'.format(self.metadata_dict[Constant.VIDEO_TITLE])) # video_description = self.metadata_dict[Constant.VIDEO_DESCRIPTION] # if video_description: # description_container = self.browser.find(By.XPATH, # Constant.DESCRIPTION_CONTAINER) # description_field = self.browser.find(By.ID, Constant.TEXTBOX, element=description_container) # self.__write_in_field(description_field, self.metadata_dict[Constant.VIDEO_DESCRIPTION]) # self.logger.debug( # 'The video description was set to \"{}\"'.format(self.metadata_dict[Constant.VIDEO_DESCRIPTION])) # kids_section = self.browser.find(By.NAME, Constant.NOT_MADE_FOR_KIDS_LABEL) # self.browser.find(By.ID, Constant.RADIO_LABEL, kids_section).click() # self.logger.debug('Selected \"{}\"'.format(Constant.NOT_MADE_FOR_KIDS_LABEL)) # Advanced options # self.browser.find(By.XPATH, Constant.MORE_BUTTON).click() # self.logger.debug('Clicked MORE OPTIONS') # tags_container = self.browser.find(By.XPATH, # Constant.TAGS_INPUT_CONTAINER) # tags_field = self.browser.find(By.ID, Constant.TAGS_INPUT, element=tags_container) # self.__write_in_field(tags_field, ','.join(self.metadata_dict[Constant.VIDEO_TAGS])) # self.logger.debug( # 'The tags were set to \"{}\"'.format(self.metadata_dict[Constant.VIDEO_TAGS])) # self.browser.find(By.ID, Constant.NEXT_BUTTON).click() # self.logger.debug('Clicked {}'.format(Constant.NEXT_BUTTON)) # self.browser.find(By.ID, Constant.NEXT_BUTTON).click() # self.logger.debug('Clicked another {}'.format(Constant.NEXT_BUTTON)) # public_main_button = self.browser.find(By.NAME, Constant.PRIVATE_BUTTON) # self.browser.find(By.ID, Constant.RADIO_LABEL, public_main_button).click() # self.logger.debug('Made the video {}'.format(Constant.PRIVATE_BUTTON)) video_id = self.__get_video_id() status_container = self.browser.find(By.XPATH, Constant.STATUS_CONTAINER) while True: in_process = status_container.text.find(Constant.UPLOADED) != -1 if in_process: time.sleep(Constant.USER_WAITING_TIME) else: break self.logger.debug('Video uploaded with video_id = {}'.format(video_id)) # done_button = self.browser.find(By.ID, Constant.DONE_BUTTON) # Catch such error as # "File is a duplicate of a video you have already uploaded" # if done_button.get_attribute('aria-disabled') == 'true': # error_message = self.browser.find(By.XPATH, # Constant.ERROR_CONTAINER).text # self.logger.error(error_message) # return False, None # done_button.click() # self.logger.debug("Published the video with video_id = {}".format(video_id)) # time.sleep(Constant.USER_WAITING_TIME) self.browser.get(Constant.YOUTUBE_URL) self.__quit() return True, video_id def __get_video_id(self) -> Optional[str]: video_id = None try: video_url_container = self.browser.find( By.XPATH, Constant.VIDEO_URL_CONTAINER) video_url_element = self.browser.find(By.XPATH, Constant.VIDEO_URL_ELEMENT, element=video_url_container) video_id = video_url_element.get_attribute( Constant.HREF).split('/')[-1] except: self.logger.warning(Constant.VIDEO_NOT_FOUND_ERROR) pass return video_id def __quit(self): self.browser.driver.quit()