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