import os
import sys
import time
from enum import Enum
from typing import Tuple, Optional

import requests
from bs4 import BeautifulSoup
from pySmartDL import SmartDL
from selenium import webdriver
from selenium.common.exceptions import TimeoutException, StaleElementReferenceException
from selenium.webdriver.common.by import By
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
from termcolor import cprint


class Mode(Enum):
    AUTO_COMPLETE = "auto_complete"
    PAGE_BASED = "page_based"


class EgyBest:

    def __init__(self, settings: Optional[dict]):
        if settings:
            self.base_url: str = settings["base_url"]
            self.auto_complete_url: str = settings["auto_complete_url"]
            self.search_base_url: str = settings["search_base_url"]
            self.available_qualities: list = settings["available_qualities"]
            self.chrome_driver_location: str = settings["chrome_driver_location"]
            self.chosen_quality: str = settings["chosen_quality"]

        self.content_url = None
        self.content_type = None
        self.content_name = None

        self.chrome_driver = None
        self.first_run = None

        self.chosen_seasons_numbers_list = []
        self.chosen_seasons_url_list = []
        self.chosen_episodes_number_list = []
        self.chosen_episodes_url_list = []
        self.downloadable_episodes_url_list = []

    def reset(self):
        self.__init__()

    def start(self) -> None:
        """ Start the downloading process, This is the main entrance point """

        while True:
            # getting user input
            try:
                self.get_content_url(*self.get_link_from_keyword())
            except requests.exceptions.ConnectionError:
                cprint("Error,please check your internet connection then press Enter!!, #code:3", "red")
                input()
            except ValueError as e:
                cprint(str(e), "red")
                continue
            else:
                if self.content_type and self.content_url:
                    break
                else:
                    raise ValueError("There was an error during the extraction of the url")
        # Beginning the process of downloading based on the content_type
        if self.content_type == "series":
            self.download_seasons()
        elif self.content_type == "movie":
            self.chosen_episodes_url_list.append(self.content_url)
            self.gather_download_link()

        file_name = self.save_links_to_file()
        self.get_user_download_choice(file_name)

    def get_link_from_keyword(self, mode: Mode = Mode.AUTO_COMPLETE) -> Tuple[str, Mode]:
        """ Generate the page link from the user's input"""

        search_keyword = self.get_string_input("What are you searching for ? :")

        if mode == Mode.AUTO_COMPLETE:
            # Link : ../autoComplete.php?q=
            return (self.auto_complete_url + search_keyword), mode
        elif mode == Mode.PAGE_BASED:
            # Link : ../explore/?output_format=json&output_mode=movies_list&page=#&q=*
            return self.search_base_url.replace("#", 1).replace("*", search_keyword), mode
        else:
            raise ValueError("Mode value is not valid!")

    # function to get the download content
    def get_content_url(self, link: str, mode: Mode):
        """ Selecting the series/movie page """
        cprint("Searching for results !", "green")
        # Chose the method given the mode of scraping
        if mode == Mode.AUTO_COMPLETE:
            self.use_auto_complete(link)
        elif mode == Mode.PAGE_BASED:
            self.use_page_based(link)
        else:
            raise ValueError("None acceptable mode value!")

    def use_auto_complete(self, link: "the auto_complete link + keyword"):
        """ Using the auto complete link to get movies/series json data from a given keyword"""

        def print_content_name(_values):
            """Color printing the elements in the list"""
            for i, e in enumerate(_values, 1):
                content_type = e['u'].split('/')[0]
                print(f"\t{i} -> ", end=' ')
                cprint(f"{e['t']}", 'magenta', attrs=['concealed'], end=' ')
                print(", Type : ", end=' ')
                cprint(f"{content_type}", 'blue', attrs=['reverse'])

        r = requests.get(link)
        # checking the page availability
        if r.status_code == 200:
            # extracting the first element of the dict dynamically
            values = list(iter(r.json().values()))[0]
            if len(values) < 1:
                raise ValueError("\nNo result found, please try another keyword ! #code:4")
            print_content_name(values)
            # looking for user choice
            while True:
                try:
                    try:
                        # get the movies/series index
                        index = self.get_int_input("Enter link number :") - 1
                        if index < 0:
                            raise IndexError()
                    except ValueError as _:
                        raise IndexError()
                    else:
                        # Extracting the values from the sub-dict
                        selected_element = values[index]
                        self.content_url = self.base_url + selected_element['u']
                        self.content_type = selected_element['u'].split("/")[0]
                        self.content_name = selected_element['t']
                        break
                except IndexError as _:
                    retry = self.get_string_input(
                        "Non valid input ,retry ? ([n,no]/ yes : any key) :")
                    if retry.lower() in ['n', 'no']:
                        self.content_type = None
                        break

        else:
            cprint("\nThere was a problem connecting to EgyBest, please check you internet connection or try later ! ",
                   "red")

    def use_page_based(self, link):
        """Legacy code: using the html page response to extract the series/movie link"""
        # Legacy code
        res = self.get_bs4_result(self.search_url, "a", "movie")
        # Displaying the fetched linked
        for i, link in enumerate(res, 1):
            content_type = None
            try:
                content_type = self.get_url_type(link['href']).upper()
                print(i, " ->", link.contents[4].text, ", Type : ", content_type, ",", "IMDB Rating :",
                      link.contents[0].i.i.text, '\n')
            except:
                print(i, " ->", link.contents[2].text,
                      ", Type : ", content_type)
        # looking for user choice
        while res:
            try:
                self.content_url = res[self.get_int_input(
                    "Enter link number :") - 1]['href']
                break
            except:
                retry = self.get_string_input(
                    "Non valid input ,retry ? (y/n) :")
                if retry != "y":
                    self.content_type = None
                    return

        if not res:
            print("Didn't found a thing")
        elif self.content_url:
            self.content_type = self.get_url_type(self.content_url)

    # initializing chrome drivers
    def init_chrome_driver(self):
        """"Initializing chrome drivers"""
        # If there is no instance create one
        if not self.chrome_driver:

            self.first_run = True
            print("Initializing chrome Driver")
            try:
                # Creating chrome options
                caps = DesiredCapabilities().CHROME
                caps["pageLoadStrategy"] = "eager"
                chrome_options = webdriver.ChromeOptions()

                # prefs = {"profile.default_content_setting_values.cookies": 2,
                #          "profile.block_third_party_cookies": True,
                prefs = {"profile.managed_default_content_settings.images": 2,
                         "profile.default_content_settings.images": 2}
                chrome_options.add_experimental_option("prefs", prefs)

                chrome_options.add_argument('--incognito')
                chrome_options.add_argument('--headless')
                chrome_options.add_argument('--log-level=3')
                chrome_options.add_argument('--silent')

                self.chrome_driver = webdriver.Chrome(executable_path=self.chrome_driver_location,
                                                      options=chrome_options)
            except Exception as e:
                raise ConnectionError("Couldn't init chrome drivers" + str(e))

    # closing and destroying chrome driver instant
    def destroy_chrome_driver(self):
        try:
            if self.chrome_driver is not None:
                self.chrome_driver.close()
                print("Driver Closed")
            else:
                print("Driver already closed")
        except Exception as e:
            print(e)

    def make_egybest_happy(self):
        """Clicking on the search area to pop up ads and make EgyBest happy"""

        try:
            self.chrome_driver.find_element_by_css_selector(
                '#head > div > div.topsrch.suba_rel.nohide.td.vam > form > input.q.autoComplete') \
                .click()
            m = "\nClicking on an ad to make EgyBest happy this will take approximately 5s and will run 1 time only !"
            d = f"\n{'-' * len(m)}"
            cprint(d + m + d, "blue")
            time.sleep(5)
            self.close_popups()
        except Exception as _:
            pass

    def gather_download_link(self):
        self.init_chrome_driver()
        print("Accessing EgyBest page")

        # Open the link using selenium
        while len(self.chosen_episodes_url_list) > 0:
            try:
                self.chrome_driver.get(self.chosen_episodes_url_list[0])
                del self.chosen_episodes_url_list[0]
            except TimeoutException:
                response = self.get_string_input(
                    "\nThere was a problem connecting to the EgyBest,The site is down or you have some "
                    "connectivity issues"
                    "\nDo you want to retry (n,no)/yes:any key", "red")
                if response not in ["n", "no"]:
                    continue
                else:
                    self.exit()

        while True:
            if self.first_run:
                self.make_egybest_happy()
                self.first_run = False

            # Chose the link that corresponds with the selected quality
            self.chose_quality()
            in_download_page = True
            # Closing pop-up pages
            for handler in self.chrome_driver.window_handles:
                self.chrome_driver.switch_to.window(handler)
                if len(self.chrome_driver.window_handles) == 1 and "egybest" in self.chrome_driver.current_url:
                    in_download_page = False
                elif "vidstream" not in self.chrome_driver.current_url:
                    self.chrome_driver.close()
            if in_download_page:
                print("Accessing the download page")
                break
            input()

        timeout = 10
        while True:
            try:
                e = WebDriverWait(self.chrome_driver, timeout).until(
                    EC.element_to_be_clickable(
                        (By.XPATH, "/html/body/div[1]/div/p/a[1]")))
                if not e.get_attribute("href"):
                    e.click()
            except TimeoutException:
                print("Timed out waiting for page to load")
            except StaleElementReferenceException as _:
                continue

            try:
                target_button = WebDriverWait(self.chrome_driver, timeout).until(
                    EC.presence_of_element_located(
                        (By.XPATH, '//*[contains(text(), "Download")][@href]')))
                self.close_popups()
                if not target_button.get_attribute("href"):
                    continue
            except TimeoutException as _:
                self.close_popups()
                continue
            print("Obtaining the download link")
            url = target_button.get_attribute("href")
            if url:
                self.downloadable_episodes_url_list.append(url)
                return

    def download_seasons(self, url=None):

        # ToDo : add range eps and clean this spaghetti code
        if url is None:
            url = self.content_url
        res = self.get_bs4_result(url, "div", "movies_small")[0]
        seasons_list = res.find_all("a")
        number_of_seasons = len(seasons_list)
        all_eps = False
        while number_of_seasons:
            print(
                F"There {'are' if number_of_seasons > 1 else 'is'} "
                F"{number_of_seasons} season{'s' if number_of_seasons > 1 else ''} ")
            choice = self.get_string_input(
                "Do you want to download all seasons or specific ones, type['all' or 'spec']:")
            if choice == 'all':
                all_eps = True
                self.chosen_seasons_numbers_list = [
                    i for i in range(1, number_of_seasons + 1)]
                break
            elif choice == 'spec':
                while 1:
                    chosen_number_seasons = self.get_int_input(
                        "Choose how many seasons :")
                    try:
                        if number_of_seasons >= chosen_number_seasons > 0:
                            break
                    except:
                        pass
                # ToDo : check and stop the duplicated seasons
                print("Add the seasons you want to download")
                for i in range(1, chosen_number_seasons + 1):
                    while 1:
                        choice = self.get_int_input(
                            F"{i} --> Choose a season from 1 to {number_of_seasons}:") if not None else 0
                        try:
                            if number_of_seasons >= choice >= 1:
                                print(F"Season {choice} Added")
                                self.chosen_seasons_numbers_list.append(choice)
                                break
                            else:
                                print("Non valid option")
                        except:
                            pass
                break
            print("Non valid option")

        self.chosen_seasons_numbers_list.sort()
        for i in self.chosen_seasons_numbers_list:
            self.chosen_seasons_url_list.append(seasons_list[-i]['href'])
        print("Gathering episodes Urls")
        # print("chosen_seasons_url_list", self.chosen_seasons_url_list)
        for url in self.chosen_seasons_url_list:
            res = self.get_bs4_result(url, "div", "movies_small")[0]
            episodes_list = res.find_all("a")
            number_of_eps = len(episodes_list)
            if number_of_eps:
                print(F"There're {number_of_eps} episodes in {' '.join(url.split('/')[-2].split('-')[1:])}")
                if all_eps:
                    choice = 'all'
                else:
                    choice = self.get_string_input(
                        "Do you want to download all episodes or specific ones, type['all' or 'spec']:")
                if choice == 'all':
                    self.chosen_episodes_number_list = [
                        i for i in range(1, number_of_eps + 1)]
                elif choice == 'spec':
                    while 1:
                        chosen_number_episodes = self.get_int_input(
                            "Choose how many episodes :")
                        try:
                            if number_of_eps >= chosen_number_episodes > 0:
                                break
                        except:
                            pass
                    print("Add the episodes you want to download")
                    for i in range(1, chosen_number_episodes + 1):
                        while 1:
                            # Todo : check for duplicated values on the array
                            choice = self.get_int_input(
                                F"{i} --> Choose an episode from 1 to {number_of_eps}:")
                            try:
                                if number_of_eps >= choice >= 1:
                                    print(F"Episode {choice} Added")
                                    self.chosen_episodes_number_list.append(
                                        choice)
                                    break
                                else:
                                    print("Non valid option")
                            except:
                                pass
            # print("chosen_episodes_number_list",
            #       self.chosen_episodes_number_list)
            # print("episodes_list", episodes_list)
            self.chosen_episodes_number_list.sort()
            for i in self.chosen_episodes_number_list:
                self.chosen_episodes_url_list.append(episodes_list[-i]["href"])
                self.gather_download_link()
            # print(self.downloadable_episodes_url_list)
            # print(self.chosen_episodes_url_list)
        return True

    @staticmethod
    def get_int_input(output_msg: str) -> int:
        """Getting input from user as an int"""
        cprint(output_msg, "green")
        return int(input())

    @staticmethod
    def get_string_input(output_msg: str, color="green") -> str:
        """Getting input from user as a string"""
        cprint(output_msg, color)
        return str(input()).lower()

    @staticmethod
    def get_bs4_result(url: str, html_tag: str, class_name: str):
        print("Requesting link, please wait!")
        r = requests.get(url)
        soup = BeautifulSoup(r.text, "html.parser")
        return soup.find_all(html_tag, class_=class_name)

    @staticmethod
    def get_url_type(url: str) -> str:
        # print(url)
        try:
            return url.split("/")[3]
        except IndexError as _:
            print("None valid link in get_url_type")
        else:
            return None

    @staticmethod
    def get_content_name(url: str) -> str:
        # print(url)
        try:
            return url.split("/")[4]
        except IndexError as _:
            print("None valid link in get_content_name")
        return None

    def exit(self, code="!"):
        self.destroy_chrome_driver()
        sys.exit("\nExited " + code)

    def close_popups(self):
        """ Closing all the popups by reversing the window_handles list"""
        for i in reversed(range(1, len(self.chrome_driver.window_handles))):
            self.chrome_driver.switch_to.window(self.chrome_driver.window_handles[i])
            self.chrome_driver.close()
        self.chrome_driver.switch_to.window(self.chrome_driver.window_handles[0])

    def close_notification_banner(self):
        """
        Closing the notification banner because we need to open a popup ad for and wait for somme seconds,
        to avoid having an infinite loop of the same page.
        """
        try:
            # Waiting for the banner to show
            timeout = 3
            try:
                WebDriverWait(self.chrome_driver, timeout).until(
                    EC.element_to_be_clickable(
                        (By.CSS_SELECTOR, "#NotificationsAskMsg > p > a.NotificationIgnore.api"))) \
                    .click()
                self.close_popups()
            except TimeoutException:
                print("Timed out waiting for page to load")
        except Exception as ex:
            print(str(ex))

    def chose_quality(self):
        """Clicking the download link that corresponds to the chosen quality if that quality is note
        available we select the next best thing in line"""

        try:
            labels = self.chrome_driver.find_elements_by_xpath('//*[@id="watch_dl"]/table/tbody/tr')
            index = None
            for i, label in enumerate(labels, 1):
                if self.chosen_quality in label.text:
                    index = i
                    break
            if index is not None:
                element = self.chrome_driver.find_element_by_xpath(
                    F'//*[@id="watch_dl"]/table/tbody/tr[{index}]/td[4]/a[1]')
            else:
                element = self.chrome_driver.find_element_by_css_selector(
                    F'#watch_dl > table > tbody > tr > td.tar > a.nop.btn.g.dl._open_window')

            self.chrome_driver.get(self.base_url + element.get_attribute("data-url")[1:])
        except Exception as e:
            print(e)
            try:
                _ = self.chrome_driver.find_elements_by_class_name('msg_box.error.table.full')[0]
                print("There are no available download links !")
            except Exception as e:
                raise NotImplementedError("Some changes were made to EgyBest website please check for an update")
            self.exit()

    def get_user_download_choice(self, file_name):
        while 1:
            cprint(f"\n--->Links saved to {file_name.replace('//', '/')}", "blue")
            choice = self.get_string_input(
                "--->Do you want to start [d]ownloading ,[a]ppend to IDM or [q]uit?,"
                "chose: (d/a/q)")
            # if choice == "v":
            #     self.append_to_vlc()
            if choice == "d":
                self.start_downloading()
            elif choice == "a":
                self.append_to_idm()
            elif choice == "p":
                print("\n".join(self.downloadable_episodes_url_list))
            elif choice == "q":
                break
            else:
                print("None valid option !")

    def start_downloading(self):
        print("Note : if you're using pycharm console, it might not show you the progress bar !")
        if not self.downloadable_episodes_url_list:
            print("Array is empty")
        for i, ep_link in enumerate(self.downloadable_episodes_url_list):
            print("Starting to download :", ep_link)
            obj = SmartDL(ep_link, "./Downloads" + os.sep)
            obj.start()
            print("Download location :" + obj.get_dest())

    def append_to_idm(self):
        for i, ep_link in enumerate(self.downloadable_episodes_url_list):
            try:
                os.system(
                    F'"C:\\Program Files (x86)\\Internet Download Manager\\IDMan.exe" /d {ep_link} /n /a')
                print("+1 :", ep_link)
            except Exception as excep:
                print(F"Couldn't add {self.chosen_episodes_url_list[i]} \nException :{excep}")

    def save_links_to_file(self) -> str:
        # ToDo : Change the download folder to conf file
        base_dic = "LinkSaves/"
        os.makedirs(os.path.dirname(base_dic), exist_ok=True)
        file_name = F"{base_dic}/{self.content_type}-{self.content_name}.txt"
        with open(file_name, "w") as f:
            for ep in self.downloadable_episodes_url_list:
                f.write(F"{ep}\n")
        return file_name

    def reset_chrome_driver(self):
        self.destroy_chrome_driver()
        self.init_chrome_driver()

    def append_to_vlc(self):
        for i, ep_link in enumerate(self.downloadable_episodes_url_list):
            try:
                cmd = F'"C:\\Program Files (x86)\\VideoLAN\\VLC\\vlc.exe" --playlist-enqueue "{ep_link}"'
                # print(cmd)
                os.system(F'cmd /c "{cmd}"')
                # print("done")
            except Exception as excep:
                print(F"Couldn't add {self.chosen_episodes_url_list[i]} \nException :{excep}")

import os
import sys
import time
from enum import Enum
from typing import Tuple, Optional

import requests
from bs4 import BeautifulSoup
from pySmartDL import SmartDL
from selenium import webdriver
from selenium.common.exceptions import TimeoutException, StaleElementReferenceException
from selenium.webdriver.common.by import By
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
from termcolor import cprint


class Mode(Enum):
    AUTO_COMPLETE = "auto_complete"
    PAGE_BASED = "page_based"


class EgyBest:

    def __init__(self, settings: Optional[dict]):
        if settings:
            self.base_url: str = settings["base_url"]
            self.auto_complete_url: str = settings["auto_complete_url"]
            self.search_base_url: str = settings["search_base_url"]
            self.available_qualities: list = settings["available_qualities"]
            self.chrome_driver_location: str = settings["chrome_driver_location"]
            self.chosen_quality: str = settings["chosen_quality"]

        self.content_url = None
        self.content_type = None
        self.content_name = None

        self.chrome_driver = None
        self.first_run = None

        self.chosen_seasons_numbers_list = []
        self.chosen_seasons_url_list = []
        self.chosen_episodes_number_list = []
        self.chosen_episodes_url_list = []
        self.downloadable_episodes_url_list = []

    def reset(self):
        self.__init__()

    def start(self) -> None:
        """ Start the downloading process, This is the main entrance point """

        while True:
            # getting user input
            try:
                self.get_content_url(*self.get_link_from_keyword())
            except requests.exceptions.ConnectionError:
                cprint("Error,please check your internet connection then press Enter!!, #code:3", "red")
                input()
            except ValueError as e:
                cprint(str(e), "red")
                continue
            else:
                if self.content_type and self.content_url:
                    break
                else:
                    raise ValueError("There was an error during the extraction of the url")
        # Beginning the process of downloading based on the content_type
        if self.content_type == "series":
            self.download_seasons()
        elif self.content_type == "movie":
            self.chosen_episodes_url_list.append(self.content_url)
            self.gather_download_link()

        file_name = self.save_links_to_file()
        self.get_user_download_choice(file_name)

    def get_link_from_keyword(self, mode: Mode = Mode.AUTO_COMPLETE) -> Tuple[str, Mode]:
        """ Generate the page link from the user's input"""

        search_keyword = self.get_string_input("What are you searching for ? :")

        if mode == Mode.AUTO_COMPLETE:
            # Link : ../autoComplete.php?q=
            return (self.auto_complete_url + search_keyword), mode
        elif mode == Mode.PAGE_BASED:
            # Link : ../explore/?output_format=json&output_mode=movies_list&page=#&q=*
            return self.search_base_url.replace("#", 1).replace("*", search_keyword), mode
        else:
            raise ValueError("Mode value is not valid!")

    # function to get the download content
    def get_content_url(self, link: str, mode: Mode):
        """ Selecting the series/movie page """
        cprint("Searching for results !", "green")
        # Chose the method given the mode of scraping
        if mode == Mode.AUTO_COMPLETE:
            self.use_auto_complete(link)
        elif mode == Mode.PAGE_BASED:
            self.use_page_based(link)
        else:
            raise ValueError("None acceptable mode value!")

    def use_auto_complete(self, link: "the auto_complete link + keyword"):
        """ Using the auto complete link to get movies/series json data from a given keyword"""

        def print_content_name(_values):
            """Color printing the elements in the list"""
            for i, e in enumerate(_values, 1):
                content_type = e['u'].split('/')[0]
                print(f"\t{i} -> ", end=' ')
                cprint(f"{e['t']}", 'magenta', attrs=['concealed'], end=' ')
                print(", Type : ", end=' ')
                cprint(f"{content_type}", 'blue', attrs=['reverse'])

        r = requests.get(link)
        # checking the page availability
        if r.status_code == 200:
            # extracting the first element of the dict dynamically
            values = list(iter(r.json().values()))[0]
            if len(values) < 1:
                raise ValueError("\nNo result found, please try another keyword ! #code:4")
            print_content_name(values)
            # looking for user choice
            while True:
                try:
                    try:
                        # get the movies/series index
                        index = self.get_int_input("Enter link number :") - 1
                        if index < 0:
                            raise IndexError()
                    except ValueError as _:
                        raise IndexError()
                    else:
                        # Extracting the values from the sub-dict
                        selected_element = values[index]
                        self.content_url = self.base_url + selected_element['u']
                        self.content_type = selected_element['u'].split("/")[0]
                        self.content_name = selected_element['t']
                        break
                except IndexError as _:
                    retry = self.get_string_input(
                        "Non valid input ,retry ? ([n,no]/ yes : any key) :")
                    if retry.lower() in ['n', 'no']:
                        self.content_type = None
                        break

        else:
            cprint("\nThere was a problem connecting to EgyBest, please check you internet connection or try later ! ",
                   "red")

    def use_page_based(self, link):
        """Legacy code: using the html page response to extract the series/movie link"""
        # Legacy code
        res = self.get_bs4_result(self.search_url, "a", "movie")
        # Displaying the fetched linked
        for i, link in enumerate(res, 1):
            content_type = None
            try:
                content_type = self.get_url_type(link['href']).upper()
                print(i, " ->", link.contents[4].text, ", Type : ", content_type, ",", "IMDB Rating :",
                      link.contents[0].i.i.text, '\n')
            except:
                print(i, " ->", link.contents[2].text,
                      ", Type : ", content_type)
        # looking for user choice
        while res:
            try:
                self.content_url = res[self.get_int_input(
                    "Enter link number :") - 1]['href']
                break
            except:
                retry = self.get_string_input(
                    "Non valid input ,retry ? (y/n) :")
                if retry != "y":
                    self.content_type = None
                    return

        if not res:
            print("Didn't found a thing")
        elif self.content_url:
            self.content_type = self.get_url_type(self.content_url)

    # initializing chrome drivers
    def init_chrome_driver(self):
        """"Initializing chrome drivers"""
        # If there is no instance create one
        if not self.chrome_driver:

            self.first_run = True
            print("Initializing chrome Driver")
            try:
                # Creating chrome options
                caps = DesiredCapabilities().CHROME
                caps["pageLoadStrategy"] = "eager"
                chrome_options = webdriver.ChromeOptions()

                # prefs = {"profile.default_content_setting_values.cookies": 2,
                #          "profile.block_third_party_cookies": True,
                prefs = {"profile.managed_default_content_settings.images": 2,
                         "profile.default_content_settings.images": 2}
                chrome_options.add_experimental_option("prefs", prefs)

                chrome_options.add_argument('--incognito')
                chrome_options.add_argument('--headless')
                chrome_options.add_argument('--log-level=3')
                chrome_options.add_argument('--silent')

                self.chrome_driver = webdriver.Chrome(executable_path=self.chrome_driver_location,
                                                      options=chrome_options)
            except Exception as e:
                raise ConnectionError("Couldn't init chrome drivers" + str(e))

    # closing and destroying chrome driver instant
    def destroy_chrome_driver(self):
        try:
            if self.chrome_driver is not None:
                self.chrome_driver.close()
                print("Driver Closed")
            else:
                print("Driver already closed")
        except Exception as e:
            print(e)

    def make_egybest_happy(self):
        """Clicking on the search area to pop up ads and make EgyBest happy"""

        try:
            self.chrome_driver.find_element_by_css_selector(
                '#head > div > div.topsrch.suba_rel.nohide.td.vam > form > input.q.autoComplete') \
                .click()
            m = "\nClicking on an ad to make EgyBest happy this will take approximately 5s and will run 1 time only !"
            d = f"\n{'-' * len(m)}"
            cprint(d + m + d, "blue")
            time.sleep(5)
            self.close_popups()
        except Exception as _:
            pass

    def gather_download_link(self):
        self.init_chrome_driver()
        print("Accessing EgyBest page")

        # Open the link using selenium
        while len(self.chosen_episodes_url_list) > 0:
            try:
                self.chrome_driver.get(self.chosen_episodes_url_list[0])
                del self.chosen_episodes_url_list[0]
            except TimeoutException:
                response = self.get_string_input(
                    "\nThere was a problem connecting to the EgyBest,The site is down or you have some "
                    "connectivity issues"
                    "\nDo you want to retry (n,no)/yes:any key", "red")
                if response not in ["n", "no"]:
                    continue
                else:
                    self.exit()

        while True:
            if self.first_run:
                self.make_egybest_happy()
                self.first_run = False

            # Chose the link that corresponds with the selected quality
            self.chose_quality()
            in_download_page = True
            # Closing pop-up pages
            for handler in self.chrome_driver.window_handles:
                self.chrome_driver.switch_to.window(handler)
                if len(self.chrome_driver.window_handles) == 1 and "egybest" in self.chrome_driver.current_url:
                    in_download_page = False
                elif "vidstream" not in self.chrome_driver.current_url:
                    self.chrome_driver.close()
            if in_download_page:
                print("Accessing the download page")
                break
            input()

        timeout = 10
        while True:
            try:
                e = WebDriverWait(self.chrome_driver, timeout).until(
                    EC.element_to_be_clickable(
                        (By.XPATH, "/html/body/div[1]/div/p/a[1]")))
                if not e.get_attribute("href"):
                    e.click()
            except TimeoutException:
                print("Timed out waiting for page to load")
            except StaleElementReferenceException as _:
                continue

            try:
                target_button = WebDriverWait(self.chrome_driver, timeout).until(
                    EC.presence_of_element_located(
                        (By.XPATH, '//*[contains(text(), "Download")][@href]')))
                self.close_popups()
                if not target_button.get_attribute("href"):
                    continue
            except TimeoutException as _:
                self.close_popups()
                continue
            print("Obtaining the download link")
            url = target_button.get_attribute("href")
            if url:
                self.downloadable_episodes_url_list.append(url)
                return

    def download_seasons(self, url=None):

        # ToDo : add range eps and clean this spaghetti code
        if url is None:
            url = self.content_url
        res = self.get_bs4_result(url, "div", "movies_small")[0]
        seasons_list = res.find_all("a")
        number_of_seasons = len(seasons_list)
        all_eps = False
        while number_of_seasons:
            print(
                F"There {'are' if number_of_seasons > 1 else 'is'} "
                F"{number_of_seasons} season{'s' if number_of_seasons > 1 else ''} ")
            choice = self.get_string_input(
                "Do you want to download all seasons or specific ones, type['all' or 'spec']:")
            if choice == 'all':
                all_eps = True
                self.chosen_seasons_numbers_list = [
                    i for i in range(1, number_of_seasons + 1)]
                break
            elif choice == 'spec':
                while 1:
                    chosen_number_seasons = self.get_int_input(
                        "Choose how many seasons :")
                    try:
                        if number_of_seasons >= chosen_number_seasons > 0:
                            break
                    except:
                        pass
                # ToDo : check and stop the duplicated seasons
                print("Add the seasons you want to download")
                for i in range(1, chosen_number_seasons + 1):
                    while 1:
                        choice = self.get_int_input(
                            F"{i} --> Choose a season from 1 to {number_of_seasons}:") if not None else 0
                        try:
                            if number_of_seasons >= choice >= 1:
                                print(F"Season {choice} Added")
                                self.chosen_seasons_numbers_list.append(choice)
                                break
                            else:
                                print("Non valid option")
                        except:
                            pass
                break
            print("Non valid option")

        self.chosen_seasons_numbers_list.sort()
        for i in self.chosen_seasons_numbers_list:
            self.chosen_seasons_url_list.append(seasons_list[-i]['href'])
        print("Gathering episodes Urls")
        # print("chosen_seasons_url_list", self.chosen_seasons_url_list)
        for url in self.chosen_seasons_url_list:
            res = self.get_bs4_result(url, "div", "movies_small")[0]
            episodes_list = res.find_all("a")
            number_of_eps = len(episodes_list)
            if number_of_eps:
                print(F"There're {number_of_eps} episodes in {' '.join(url.split('/')[-2].split('-')[1:])}")
                if all_eps:
                    choice = 'all'
                else:
                    choice = self.get_string_input(
                        "Do you want to download all episodes or specific ones, type['all' or 'spec']:")
                if choice == 'all':
                    self.chosen_episodes_number_list = [
                        i for i in range(1, number_of_eps + 1)]
                elif choice == 'spec':
                    while 1:
                        chosen_number_episodes = self.get_int_input(
                            "Choose how many episodes :")
                        try:
                            if number_of_eps >= chosen_number_episodes > 0:
                                break
                        except:
                            pass
                    print("Add the episodes you want to download")
                    for i in range(1, chosen_number_episodes + 1):
                        while 1:
                            # Todo : check for duplicated values on the array
                            choice = self.get_int_input(
                                F"{i} --> Choose an episode from 1 to {number_of_eps}:")
                            try:
                                if number_of_eps >= choice >= 1:
                                    print(F"Episode {choice} Added")
                                    self.chosen_episodes_number_list.append(
                                        choice)
                                    break
                                else:
                                    print("Non valid option")
                            except:
                                pass
            # print("chosen_episodes_number_list",
            #       self.chosen_episodes_number_list)
            # print("episodes_list", episodes_list)
            self.chosen_episodes_number_list.sort()
            for i in self.chosen_episodes_number_list:
                self.chosen_episodes_url_list.append(episodes_list[-i]["href"])
                self.gather_download_link()
            # print(self.downloadable_episodes_url_list)
            # print(self.chosen_episodes_url_list)
        return True

    @staticmethod
    def get_int_input(output_msg: str) -> int:
        """Getting input from user as an int"""
        cprint(output_msg, "green")
        return int(input())

    @staticmethod
    def get_string_input(output_msg: str, color="green") -> str:
        """Getting input from user as a string"""
        cprint(output_msg, color)
        return str(input()).lower()

    @staticmethod
    def get_bs4_result(url: str, html_tag: str, class_name: str):
        print("Requesting link, please wait!")
        r = requests.get(url)
        soup = BeautifulSoup(r.text, "html.parser")
        return soup.find_all(html_tag, class_=class_name)

    @staticmethod
    def get_url_type(url: str) -> str:
        # print(url)
        try:
            return url.split("/")[3]
        except IndexError as _:
            print("None valid link in get_url_type")
        else:
            return None

    @staticmethod
    def get_content_name(url: str) -> str:
        # print(url)
        try:
            return url.split("/")[4]
        except IndexError as _:
            print("None valid link in get_content_name")
        return None

    def exit(self, code="!"):
        self.destroy_chrome_driver()
        sys.exit("\nExited " + code)

    def close_popups(self):
        """ Closing all the popups by reversing the window_handles list"""
        for i in reversed(range(1, len(self.chrome_driver.window_handles))):
            self.chrome_driver.switch_to.window(self.chrome_driver.window_handles[i])
            self.chrome_driver.close()
        self.chrome_driver.switch_to.window(self.chrome_driver.window_handles[0])

    def close_notification_banner(self):
        """
        Closing the notification banner because we need to open a popup ad for and wait for somme seconds,
        to avoid having an infinite loop of the same page.
        """
        try:
            # Waiting for the banner to show
            timeout = 3
            try:
                WebDriverWait(self.chrome_driver, timeout).until(
                    EC.element_to_be_clickable(
                        (By.CSS_SELECTOR, "#NotificationsAskMsg > p > a.NotificationIgnore.api"))) \
                    .click()
                self.close_popups()
            except TimeoutException:
                print("Timed out waiting for page to load")
        except Exception as ex:
            print(str(ex))

    def chose_quality(self):
        """Clicking the download link that corresponds to the chosen quality if that quality is note
        available we select the next best thing in line"""

        try:
            labels = self.chrome_driver.find_elements_by_xpath('//*[@id="watch_dl"]/table/tbody/tr')
            index = None
            for i, label in enumerate(labels, 1):
                if self.chosen_quality in label.text:
                    index = i
                    break
            if index is not None:
                element = self.chrome_driver.find_element_by_xpath(
                    F'//*[@id="watch_dl"]/table/tbody/tr[{index}]/td[4]/a[1]')
            else:
                element = self.chrome_driver.find_element_by_css_selector(
                    F'#watch_dl > table > tbody > tr > td.tar > a.nop.btn.g.dl._open_window')

            self.chrome_driver.get(self.base_url + element.get_attribute("data-url")[1:])
        except Exception as e:
            print(e)
            try:
                _ = self.chrome_driver.find_elements_by_class_name('msg_box.error.table.full')[0]
                print("There are no available download links !")
            except Exception as e:
                raise NotImplementedError("Some changes were made to EgyBest website please check for an update")
            self.exit()

    def get_user_download_choice(self, file_name):
        while 1:
            cprint(f"\n--->Links saved to {file_name.replace('//', '/')}", "blue")
            choice = self.get_string_input(
                "--->Do you want to start [d]ownloading ,[a]ppend to IDM or [q]uit?,"
                "chose: (d/a/q)")
            # if choice == "v":
            #     self.append_to_vlc()
            if choice == "d":
                self.start_downloading()
            elif choice == "a":
                self.append_to_idm()
            elif choice == "p":
                print("\n".join(self.downloadable_episodes_url_list))
            elif choice == "q":
                break
            else:
                print("None valid option !")

    def start_downloading(self):
        print("Note : if you're using pycharm console, it might not show you the progress bar !")
        if not self.downloadable_episodes_url_list:
            print("Array is empty")
        for i, ep_link in enumerate(self.downloadable_episodes_url_list):
            print("Starting to download :", ep_link)
            obj = SmartDL(ep_link, "./Downloads" + os.sep)
            obj.start()
            print("Download location :" + obj.get_dest())

    def append_to_idm(self):
        for i, ep_link in enumerate(self.downloadable_episodes_url_list):
            try:
                os.system(
                    F'"C:\\Program Files (x86)\\Internet Download Manager\\IDMan.exe" /d {ep_link} /n /a')
                print("+1 :", ep_link)
            except Exception as excep:
                print(F"Couldn't add {self.chosen_episodes_url_list[i]} \nException :{excep}")

    def save_links_to_file(self) -> str:
        # ToDo : Change the download folder to conf file
        base_dic = "LinkSaves/"
        os.makedirs(os.path.dirname(base_dic), exist_ok=True)
        file_name = F"{base_dic}/{self.content_type}-{self.content_name}.txt"
        with open(file_name, "w") as f:
            for ep in self.downloadable_episodes_url_list:
                f.write(F"{ep}\n")
        return file_name

    def reset_chrome_driver(self):
        self.destroy_chrome_driver()
        self.init_chrome_driver()

    def append_to_vlc(self):
        for i, ep_link in enumerate(self.downloadable_episodes_url_list):
            try:
                cmd = F'"C:\\Program Files (x86)\\VideoLAN\\VLC\\vlc.exe" --playlist-enqueue "{ep_link}"'
                # print(cmd)
                os.system(F'cmd /c "{cmd}"')
                # print("done")
            except Exception as excep:
                print(F"Couldn't add {self.chosen_episodes_url_list[i]} \nException :{excep}")

Start the downloading process, This is the main entrance point # getting user input #code:3", "red") # Beginning the process of downloading based on the content_type Generate the page link from the user's input # Link : ../autoComplete.php?q= # Link : ../explore/?output_format=json&output_mode=movies_list&page=#&q=* # function to get the download content Selecting the series/movie page # Chose the method given the mode of scraping Using the auto complete link to get movies/series json data from a given keyword Color printing the elements in the list # checking the page availability # extracting the first element of the dict dynamically #code:4") # looking for user choice # get the movies/series index # Extracting the values from the sub-dict Legacy code: using the html page response to extract the series/movie link # Legacy code # Displaying the fetched linked # looking for user choice # initializing chrome drivers "Initializing chrome drivers # If there is no instance create one # Creating chrome options # prefs = {"profile.default_content_setting_values.cookies": 2, #          "profile.block_third_party_cookies": True, # closing and destroying chrome driver instant Clicking on the search area to pop up ads and make EgyBest happy # Open the link using selenium # Chose the link that corresponds with the selected quality # Closing pop-up pages # ToDo : add range eps and clean this spaghetti code # ToDo : check and stop the duplicated seasons # print("chosen_seasons_url_list", self.chosen_seasons_url_list) # Todo : check for duplicated values on the array # print("chosen_episodes_number_list", #       self.chosen_episodes_number_list) # print("episodes_list", episodes_list) # print(self.downloadable_episodes_url_list) # print(self.chosen_episodes_url_list) Getting input from user as an int Getting input from user as a string # print(url) # print(url) Closing all the popups by reversing the window_handles list Closing the notification banner because we need to open a popup ad for and wait for somme seconds,         to avoid having an infinite loop of the same page. # Waiting for the banner to show Clicking the download link that corresponds to the chosen quality if that quality is note         available we select the next best thing in line # if choice == "v": #     self.append_to_vlc() # ToDo : Change the download folder to conf file # print(cmd) # print("done")