Esempio n. 1
0
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 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()
Esempio n. 3
0
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
Esempio n. 4
0
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,
                 headless: 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,
                               headless=headless,
                               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

    # --------------------------------------------------------- Destructor ----------------------------------------------------------- #

    def __del__(self):
        self.quit()

    # ------------------------------------------------------ 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],
        made_for_kids: Optional[bool],
        visibility: Optional[Visibility] = Visibility.PUBLIC,
        _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,
                        made_for_kids,
                        visibility,
                        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,
                made_for_kids,
                visibility,
                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],
        made_for_kids: Optional[bool] = False,
        visibility: Optional[Visibility] = Visibility.PUBLIC,
        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: upload initiated')

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

            if made_for_kids:
                kids_selection_name = "MADE_FOR_KIDS"
            else:
                kids_selection_name = "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 %s" % (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')

            public_main_button = self.browser.find(By.NAME, visibility.name)
            self.browser.find(By.ID, 'radioLabel', public_main_button).click()
            print('Upload: set to %s' % (visibility.name))

            progress_element = self.browser.find(
                By.XPATH,
                '/html/body/ytcp-uploads-dialog/paper-dialog/div/ytcp-animatable[2]/div/div[1]/ytcp-video-upload-progress'
            )
            while progress_element.get_attribute('uploading') == 'true':
                time.sleep(1)
            print('Upload: uploaded video')

            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:
                    done_button = self.browser.find(By.ID, 'done-button')
                    if \
                            progress_element.get_attribute('processing') == 'false' and \
                            progress_element.get_attribute('processed') == 'false' and \
                            progress_element.get_attribute('checks-can-start') == 'false':
                        raise RuntimeError(
                            'Status should be either "processing", "processed" or "checks-can-start"'
                        )
                    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 >= 10:
                        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'


# ---------------------------------------------------------------------------------------------------------------------------------------- #
Esempio n. 5
0
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
Esempio n. 6
0
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
Esempio n. 7
0
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
Esempio n. 8
0
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()
Esempio n. 9
0
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()