"""Downloader Class.""" from typing import Any, Dict, Tuple import requests from bs4 import BeautifulSoup, Tag from loguru import logger from src.downloader.download import Downloader from src.downloader.sources import APK_MIRROR_BASE_URL, apk_sources from src.exceptions import APKMirrorAPKDownloadFailure from src.utils import bs4_parser, contains_any_word, request_header class ApkMirror(Downloader): """Files downloader.""" def _extract_force_download_link(self, link: str, app: str) -> Tuple[str, str]: """Extract force download link.""" notes_divs = self._extracted_search_div(link, "tab-pane") apk_type = self._extracted_search_div(link, "apkm-badge").get_text() extension = "zip" if apk_type == "BUNDLE" else "apk" possible_links = notes_divs.find_all("a") for possible_link in possible_links: if possible_link.get("href") and "download.php?id=" in possible_link.get( "href" ): file_name = f"{app}.{extension}" self._download(APK_MIRROR_BASE_URL + possible_link["href"], file_name) return file_name, APK_MIRROR_BASE_URL + possible_link["href"] raise APKMirrorAPKDownloadFailure( f"Unable to extract force download for {app}", url=link ) def extract_download_link(self, page: str, app: str) -> Tuple[str, str]: """Function to extract the download link from apkmirror html page. :param page: Url of the page :param app: Name of the app """ logger.debug(f"Extracting download link from\n{page}") download_button = self._extracted_search_div(page, "center") download_links = download_button.find_all("a") if final_download_link := next( ( download_link["href"] for download_link in download_links if download_link.get("href") and "download/?key=" in download_link.get("href") ), None, ): return self._extract_force_download_link( APK_MIRROR_BASE_URL + final_download_link, app ) raise APKMirrorAPKDownloadFailure( f"Unable to extract link from {app} version list", url=page ) def get_download_page(self, main_page: str) -> str: """Function to get the download page in apk_mirror. :param main_page: Main Download Page in APK mirror(Index) :return: """ list_widget = self._extracted_search_div(main_page, "listWidget") table_rows = list_widget.find_all(class_="table-row") links: Dict[str, str] = {} apk_archs = ["arm64-v8a", "universal", "noarch"] for row in table_rows: if row.find(class_="accent_color"): apk_type = row.find(class_="apkm-badge").get_text() sub_url = row.find(class_="accent_color")["href"] text = row.text.strip() if apk_type == "APK" and (not contains_any_word(text, apk_archs)): continue links[apk_type] = f"{APK_MIRROR_BASE_URL}{sub_url}" if preferred_link := links.get("APK", links.get("BUNDLE")): return preferred_link raise APKMirrorAPKDownloadFailure( "Unable to extract download page", url=main_page ) @staticmethod def _extracted_search_div(url: str, search_class: str) -> Tag: """Extract search div.""" r = requests.get(url, headers=request_header, timeout=60) if r.status_code != 200: raise APKMirrorAPKDownloadFailure( f"Unable to connect with {url} on ApkMirror. Are you blocked by APKMirror or abused apkmirror " f"?.Reason - {r.text}", url=url, ) soup = BeautifulSoup(r.text, bs4_parser) return soup.find(class_=search_class) def specific_version( self, app: str, version: str, main_page: str = "" ) -> Tuple[str, str]: """Function to download the specified version of app from apkmirror. :param app: Name of the application :param version: Version of the application to download :param main_page: Version of the application to download :return: Version of downloaded apk """ if not main_page: version = version.replace(".", "-") apk_main_page = apk_sources[app] version_page = apk_main_page + apk_main_page.split("/")[-2] main_page = f"{version_page}-{version}-release/" download_page = self.get_download_page(main_page) return self.extract_download_link(download_page, app) def latest_version(self, app: str, **kwargs: Any) -> Tuple[str, str]: """Function to download whatever the latest version of app from apkmirror. :param app: Name of the application :return: Version of downloaded apk """ app_main_page = apk_sources[app] versions_div = self._extracted_search_div( app_main_page, "listWidget p-relative" ) app_rows = versions_div.find_all(class_="appRow") version_urls = [ app_row.find(class_="downloadLink")["href"] for app_row in app_rows if "beta" not in app_row.find(class_="appRowTitle").get_text().lower() and "alpha" not in app_row.find(class_="appRowTitle").get_text().lower() ] return self.specific_version( app, "latest", APK_MIRROR_BASE_URL + max(version_urls) )