🚨 Updated lints (#308)

This commit is contained in:
Nikhil Badyal
2023-08-25 15:36:50 +05:30
committed by GitHub
parent 09b815cb21
commit 77377cdd48
26 changed files with 404 additions and 487 deletions
Vendored
BIN
View File
Binary file not shown.
+18 -10
View File
@@ -22,36 +22,43 @@ repos:
- id: requirements-txt-fixer - id: requirements-txt-fixer
- id: trailing-whitespace - id: trailing-whitespace
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: 'v0.0.285'
hooks:
- id: ruff
args:
- "--config=pyproject.toml"
- "--fix"
- repo: https://github.com/psf/black - repo: https://github.com/psf/black
rev: 23.7.0 rev: 23.7.0
hooks: hooks:
- id: black - id: black
args:
- "--config=pyproject.toml"
- repo: https://github.com/pycqa/isort - repo: https://github.com/pycqa/isort
rev: 5.12.0 rev: 5.12.0
hooks: hooks:
- id: isort - id: isort
args: [ "--profile", "black", ] args:
- "--settings-path=pyproject.toml"
- repo: https://github.com/hadialqattan/pycln - repo: https://github.com/hadialqattan/pycln
rev: v2.2.2 rev: v2.2.2
hooks: hooks:
- id: pycln - id: pycln
args: [ --config=setup.cfg ] args:
- "--config=pyproject.toml"
- repo: https://github.com/PyCQA/docformatter - repo: https://github.com/PyCQA/docformatter
rev: v1.7.5 rev: v1.7.5
hooks: hooks:
- id: docformatter - id: docformatter
args: [ --in-place ] args:
- "--config=pyproject.toml"
- "--in-place"
- repo: https://github.com/PyCQA/flake8
rev: 6.1.0
hooks:
- id: flake8
args: [ "--config=setup.cfg" ]
- repo: https://github.com/pre-commit/mirrors-mypy - repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.5.1 rev: v1.5.1
@@ -59,7 +66,8 @@ repos:
- id: mypy - id: mypy
args: args:
- '--strict' - '--strict'
additional_dependencies: [ types-requests ] - "--config=pyproject.toml"
additional_dependencies: [ types-requests,types-beautifulsoup4 ]
ci: ci:
autofix_commit_msg: | autofix_commit_msg: |
+1 -2
View File
@@ -35,8 +35,7 @@ RUN python -m pip install --upgrade pip
RUN pip install -r requirements.txt RUN pip install -r requirements.txt
COPY ./entrypoint /entrypoint COPY ./entrypoint /entrypoint
RUN sed -i 's/\r$//g' /entrypoint RUN sed -i 's/\r$//g' /entrypoint && chmod +x /entrypoint
RUN chmod +x /entrypoint
# copy application code to WORKDIR # copy application code to WORKDIR
COPY . ${APP_HOME} COPY . ${APP_HOME}
+1 -1
View File
@@ -81,7 +81,7 @@ You can use any of the following methods to build.
- 🫠Without Docker - 🫠Without Docker
1. Install Java >= 17 1. Install Java >= 17
2. Install Python 2. Install Python >= 3.11
3. Create virtual environment 3. Create virtual environment
``` ```
python3 -m venv venv python3 -m venv venv
+10 -9
View File
@@ -5,7 +5,7 @@ from environs import Env
from loguru import logger from loguru import logger
from src.config import RevancedConfig from src.config import RevancedConfig
from src.exceptions import AppNotFound, PatchesJsonLoadFailed, PatchingFailed from src.exceptions import AppNotFoundError, PatchesJsonLoadError, PatchingFailedError, UnknownError
from src.parser import Parser from src.parser import Parser
from src.patches import Patches from src.patches import Patches
from src.utils import check_java, extra_downloads from src.utils import check_java, extra_downloads
@@ -19,13 +19,14 @@ def main() -> None:
env.read_env() env.read_env()
config = RevancedConfig(env) config = RevancedConfig(env)
extra_downloads(config) extra_downloads(config)
check_java(config.dry_run) if not config.dry_run:
check_java()
logger.info(f"Will Patch only {config.apps}") logger.info(f"Will Patch only {config.apps}")
for app in config.apps: for possible_app in config.apps:
logger.info(f"Trying to build {app}") logger.info(f"Trying to build {possible_app}")
try: try:
app = APP(app_name=app, config=config) app = APP(app_name=possible_app, config=config)
patcher = Patches(config, app) patcher = Patches(config, app)
parser = Parser(patcher, config) parser = Parser(patcher, config)
app_all_patches = patcher.get_app_configs(app) app_all_patches = patcher.get_app_configs(app)
@@ -33,13 +34,13 @@ def main() -> None:
patcher.include_exclude_patch(app, parser, app_all_patches) patcher.include_exclude_patch(app, parser, app_all_patches)
logger.info(app) logger.info(app)
parser.patch_app(app) parser.patch_app(app)
except AppNotFound as e: except AppNotFoundError as e:
logger.info(e) logger.info(e)
except PatchesJsonLoadFailed: except PatchesJsonLoadError:
logger.exception("Patches.json not found") logger.exception("Patches.json not found")
except PatchingFailed as e: except PatchingFailedError as e:
logger.exception(e) logger.exception(e)
except Exception as e: except UnknownError as e:
logger.exception(f"Failed to build {app} because of {e}") logger.exception(f"Failed to build {app} because of {e}")
+84
View File
@@ -0,0 +1,84 @@
[tool.ruff]
line-length = 120
select = [
"F", # pyflakes
"E", # pycodestyle Error
"W", # pycodestyle Warning
"C90", #mccabe
"I", #isort
"N", #isort
"D", #isort
"UP", # pyupgrade
"ANN", # flake8-annotations
"ASYNC", # flake8-async
"S", # flake8-bandit
"BLE", # flake8-blind-except
"FBT", #flake8-boolean-trap
"B", #flake8-bugbear
"A" , #flake8-builtins
"C4" , #flake8-comprehensions
"DTZ" , #flake8-datetimez
"T10" , #flake8-debugger
"EM" , #flake8-errmsg
"EXE" , #flake8-executable
"ISC" , #flake8-implicit-str-concat
"ICN" , #flake8-import-conventions
"G" , #flake8-logging-format
"INP" , #flake8-no-pep420
"PIE" , #flake8-pie
"PYI" , #flake8-pyi
"RSE" , #flake8-raise
"RET" , #flake8-return
"SLF" , #flake8-self
"SIM" , #flake8-simplify
"TCH" , #flake8-type-checking
"INT" , #flake8-gettext
"ARG" , #flake8-unused-arguments
"PTH" , #flake8-use-pathlib
"ERA" , #eradicate
"PGH" , #pygrep-hooks
"PL" , #Pylint
"TRY" , #tryceratops
"FLY" , #flynt
"PERF" , #flynt
"RUF" , #Ruff-specific rules
]
ignore = [
"D401",
"ANN401",
"S603",
"S607",
"ARG002", #unused-method-argument
"PTH122", #os-path-splitext
"TRY301", #raise-within-try
"PERF203", #try-except-in-loop
]
fix = true
show-fixes = true
[tool.ruff.pydocstyle]
convention = "numpy"
[tool.docformatter]
recursive = true
wrap-summaries = 120
wrap-descriptions = 120
[tool.black]
line-length = 120
[pycodestyle]
max-line-length = 120
exclude = ["venv"]
[tool.mypy]
ignore_missing_imports = true
check_untyped_defs = true
warn_unused_ignores = true
warn_redundant_casts = true
warn_unused_configs = true
[tool.isort]
line_length = 120
skip = ["venv"]
profile = "black"
+1
View File
@@ -0,0 +1 @@
"""Common utilities."""
+26 -26
View File
@@ -1,5 +1,6 @@
"""Status check.""" """Status check."""
import re import re
from pathlib import Path
from typing import List from typing import List
import requests import requests
@@ -7,30 +8,26 @@ from bs4 import BeautifulSoup
from google_play_scraper import app as gplay_app from google_play_scraper import app as gplay_app
from google_play_scraper.exceptions import GooglePlayScraperException from google_play_scraper.exceptions import GooglePlayScraperException
from src.exceptions import APKMirrorIconScrapFailure from src.exceptions import APKComboIconScrapError, APKMirrorIconScrapError, UnknownError
from src.patches import Patches from src.patches import Patches
from src.utils import ( from src.utils import apk_mirror_base_url, apkmirror_status_check, bs4_parser, handle_request_response, request_header
apk_mirror_base_url,
apkmirror_status_check,
bs4_parser,
handle_request_response,
request_header,
)
not_found_icon = "https://img.icons8.com/bubbles/500/android-os.png" not_found_icon = "https://img.icons8.com/bubbles/500/android-os.png"
no_of_col = 6
def apkcombo_scrapper(package_name: str) -> str: def apkcombo_scrapper(package_name: str) -> str:
"""Apkcombo scrapper.""" """Apkcombo scrapper."""
try: try:
apkcombo_url = f"https://apkcombo.com/genericApp/{package_name}" apkcombo_url = f"https://apkcombo.com/genericApp/{package_name}"
r = requests.get( r = requests.get(apkcombo_url, headers=request_header, allow_redirects=True, timeout=10)
apkcombo_url, headers=request_header, allow_redirects=True, timeout=10
)
soup = BeautifulSoup(r.text, bs4_parser) soup = BeautifulSoup(r.text, bs4_parser)
url = soup.select_one("div.avatar > img")["data-src"] icon_element = soup.select_one("div.bubble-wrap > img")
return re.sub(r"=.*$", "", url) if not icon_element:
except Exception: raise APKComboIconScrapError(url=apkcombo_url)
url = icon_element["data-src"]
return re.sub(r"=.*$", "", url) # type: ignore[arg-type]
except UnknownError:
return not_found_icon return not_found_icon
@@ -40,13 +37,16 @@ def apkmirror_scrapper(package_name: str) -> str:
search_url = f"{apk_mirror_base_url}/?s={package_name}" search_url = f"{apk_mirror_base_url}/?s={package_name}"
if response["data"][0]["exists"]: if response["data"][0]["exists"]:
return _extracted_from_apkmirror_scrapper(search_url) return _extracted_from_apkmirror_scrapper(search_url)
raise APKMirrorIconScrapFailure(url=search_url) raise APKMirrorIconScrapError(url=search_url)
def _extracted_from_apkmirror_scrapper(search_url: str) -> str: def _extracted_from_apkmirror_scrapper(search_url: str) -> str:
r = requests.get(search_url, headers=request_header, timeout=60) r = requests.get(search_url, headers=request_header, timeout=60)
soup = BeautifulSoup(r.text, bs4_parser) soup = BeautifulSoup(r.text, bs4_parser)
sub_url = soup.select_one("div.bubble-wrap > img")["src"] icon_element = soup.select_one("div.bubble-wrap > img")
if not icon_element:
raise APKMirrorIconScrapError(url=search_url)
sub_url = str(icon_element["src"])
new_width = 500 new_width = 500
new_height = 500 new_height = 500
new_quality = 100 new_quality = 100
@@ -54,9 +54,7 @@ def _extracted_from_apkmirror_scrapper(search_url: str) -> str:
# regular expression pattern to match w=xx&h=xx&q=xx # regular expression pattern to match w=xx&h=xx&q=xx
pattern = r"(w=\d+&h=\d+&q=\d+)" pattern = r"(w=\d+&h=\d+&q=\d+)"
return apk_mirror_base_url + re.sub( return apk_mirror_base_url + re.sub(pattern, f"w={new_width}&h={new_height}&q={new_quality}", sub_url)
pattern, f"w={new_width}&h={new_height}&q={new_quality}", sub_url
)
def gplay_icon_scrapper(package_name: str) -> str: def gplay_icon_scrapper(package_name: str) -> str:
@@ -68,13 +66,13 @@ def gplay_icon_scrapper(package_name: str) -> str:
) )
if result["icon"]: if result["icon"]:
return str(result["icon"]) return str(result["icon"])
raise GooglePlayScraperException() raise GooglePlayScraperException
except GooglePlayScraperException: except GooglePlayScraperException:
try: try:
return apkmirror_scrapper(package_name) return apkmirror_scrapper(package_name)
except APKMirrorIconScrapFailure: except APKMirrorIconScrapError:
return apkcombo_scrapper(package_name) return apkcombo_scrapper(package_name)
except Exception: except UnknownError:
return not_found_icon return not_found_icon
@@ -85,11 +83,12 @@ def generate_markdown_table(data: List[List[str]]) -> str:
table = ( table = (
"| Package Name | App Icon | PlayStore link | APKMirror link|APKCombo Link| Supported?|\n" "| Package Name | App Icon | PlayStore link | APKMirror link|APKCombo Link| Supported?|\n"
+ "|-------------|----------|----------------|---------------|------------------|----------|\n" "|-------------|----------|----------------|---------------|------------------|----------|\n"
) )
for row in data: for row in data:
if len(row) != 6: if len(row) != no_of_col:
raise ValueError("Each row must contain 6 columns of data.") msg = "Each row must contain 6 columns of data."
raise ValueError(msg)
table += f"| {row[0]} | {row[1]} | {row[2]} | {row[3]} |{row[4]} |{row[5]} |\n" table += f"| {row[0]} | {row[1]} | {row[2]} | {row[3]} |{row[4]} |{row[5]} |\n"
@@ -97,6 +96,7 @@ def generate_markdown_table(data: List[List[str]]) -> str:
def main() -> None: def main() -> None:
"""Entrypoint."""
repo_url = "https://releases.revanced.app/patches" repo_url = "https://releases.revanced.app/patches"
response = requests.get(repo_url, timeout=10) response = requests.get(repo_url, timeout=10)
handle_request_response(response) handle_request_response(response)
@@ -124,7 +124,7 @@ def main() -> None:
] ]
table = generate_markdown_table(data) table = generate_markdown_table(data)
output += table output += table
with open("status.md", "w", encoding="utf_8") as status: with Path("status.md").open("w", encoding="utf_8") as status:
status.write(output) status.write(output)
print(output) print(output)
-29
View File
@@ -1,29 +0,0 @@
[flake8]
max-line-length = 120
exclude = .tox,.git,*/settings/*,*/static/CACHE/*,docs,node_modules,venv
extend-ignore = E203
[pycodestyle]
max-line-length = 120
exclude = .tox,.git,*/settings/*,*/static/CACHE/*,docs,venv
[isort]
line_length = 88
known_first_party = config
multi_line_output = 3
default_section = THIRDPARTY
skip = venv/
include_trailing_comma = true
force_grid_wrap = 0
use_parentheses = true
profile = black
[mypy]
python_version = 3.10
check_untyped_defs = True
ignore_missing_imports = True
warn_unused_ignores = True
warn_redundant_casts = True
warn_unused_configs = True
exclude = ['venv/']
+1
View File
@@ -0,0 +1 @@
"""Main Source Code."""
+39 -78
View File
@@ -3,49 +3,42 @@ import concurrent
import hashlib import hashlib
import pathlib import pathlib
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
from typing import Dict, List from typing import Dict, List, Self
from loguru import logger from loguru import logger
from src.config import RevancedConfig from src.config import RevancedConfig
from src.downloader.sources import apk_sources from src.downloader.sources import apk_sources
from src.exceptions import DownloadFailure, PatchingFailed from src.exceptions import DownloadError, PatchingFailedError, UnknownError
from src.utils import slugify from src.utils import slugify
class APP(object): class APP:
"""Patched APK.""" """Patched APK."""
def __init__(self, app_name: str, config: RevancedConfig): def __init__(self: Self, app_name: str, config: RevancedConfig) -> None:
"""Initialize APP.
Args:
----
app_name (str): Name of the app.
config (RevancedConfig): Configuration object.
"""
from src.patches import Patches from src.patches import Patches
self.app_name = app_name self.app_name = app_name
self.app_version = config.env.str(f"{app_name}_VERSION".upper(), None) self.app_version = config.env.str(f"{app_name}_VERSION".upper(), None)
self.experiment = False self.experiment = False
self.cli_dl = config.env.str(f"{app_name}_CLI_DL".upper(), config.global_cli_dl) self.cli_dl = config.env.str(f"{app_name}_CLI_DL".upper(), config.global_cli_dl)
self.patches_dl = config.env.str( self.patches_dl = config.env.str(f"{app_name}_PATCHES_DL".upper(), config.global_patches_dl)
f"{app_name}_PATCHES_DL".upper(), config.global_patches_dl self.integrations_dl = config.env.str(f"{app_name}_INTEGRATIONS_DL".upper(), config.global_integrations_dl)
) self.patches_json_dl = config.env.str(f"{app_name}_PATCHES_JSON_DL".upper(), config.global_patches_json_dl)
self.integrations_dl = config.env.str( self.exclude_request: List[str] = config.env.list(f"{app_name}_EXCLUDE_PATCH".upper(), [])
f"{app_name}_INTEGRATIONS_DL".upper(), config.global_integrations_dl self.include_request: List[str] = config.env.list(f"{app_name}_INCLUDE_PATCH".upper(), [])
)
self.patches_json_dl = config.env.str(
f"{app_name}_PATCHES_JSON_DL".upper(), config.global_patches_json_dl
)
self.exclude_request: List[str] = config.env.list(
f"{app_name}_EXCLUDE_PATCH".upper(), []
)
self.include_request: List[str] = config.env.list(
f"{app_name}_INCLUDE_PATCH".upper(), []
)
self.resource: Dict[str, str] = {} self.resource: Dict[str, str] = {}
self.no_of_patches: int = 0 self.no_of_patches: int = 0
self.keystore_name = config.env.str( self.keystore_name = config.env.str(f"{app_name}_KEYSTORE_FILE_NAME".upper(), config.global_keystore_name)
f"{app_name}_KEYSTORE_FILE_NAME".upper(), config.global_keystore_name self.archs_to_build = config.env.list(f"{app_name}_ARCHS_TO_BUILD".upper(), config.global_archs_to_build)
)
self.archs_to_build = config.env.list(
f"{app_name}_ARCHS_TO_BUILD".upper(), config.global_archs_to_build
)
self.download_file_name = "" self.download_file_name = ""
self.download_dl = config.env.str(f"{app_name}_DL".upper(), "") self.download_dl = config.env.str(f"{app_name}_DL".upper(), "")
self.download_patch_resources(config) self.download_patch_resources(config)
@@ -53,7 +46,7 @@ class APP(object):
env_package_name = config.env.str(f"{app_name}_PACKAGE_NAME".upper(), None) env_package_name = config.env.str(f"{app_name}_PACKAGE_NAME".upper(), None)
self.package_name = env_package_name or Patches.get_package_name(app_name) self.package_name = env_package_name or Patches.get_package_name(app_name)
def download_apk_for_patching(self, config: RevancedConfig) -> None: def download_apk_for_patching(self: Self, config: RevancedConfig) -> None:
"""Download apk to be patched.""" """Download apk to be patched."""
from src.downloader.download import Downloader from src.downloader.download import Downloader
from src.downloader.factory import DownloaderFactory from src.downloader.factory import DownloaderFactory
@@ -61,31 +54,22 @@ class APP(object):
if self.download_dl: if self.download_dl:
logger.info("Downloading apk to be patched using provided dl") logger.info("Downloading apk to be patched using provided dl")
self.download_file_name = f"{self.app_name}.apk" self.download_file_name = f"{self.app_name}.apk"
Downloader(config).direct_download( Downloader(config).direct_download(self.download_dl, self.download_file_name)
self.download_dl, self.download_file_name
)
else: else:
logger.info("Downloading apk to be patched by scrapping") logger.info("Downloading apk to be patched by scrapping")
try: try:
if not self.download_source: if not self.download_source:
self.download_source = apk_sources[self.app_name].format( self.download_source = apk_sources[self.app_name].format(self.package_name)
self.package_name except KeyError as key:
) msg = f"App {self.app_name} not supported officially yet. Please provide download source in env."
except KeyError: raise DownloadError(
raise DownloadFailure( msg,
f"App {self.app_name} not supported officially yet. Please provide download " ) from key
"source in env." downloader = DownloaderFactory.create_downloader(config=config, apk_source=self.download_source)
) self.download_file_name, self.download_dl = downloader.download(self.app_version, self)
downloader = DownloaderFactory.create_downloader(
config=config, apk_source=self.download_source
)
self.download_file_name, self.download_dl = downloader.download(
self.app_version, self
)
def get_output_file_name(self) -> str: def get_output_file_name(self: Self) -> str:
"""The function returns a string representing the output file name for """The function returns a string representing the output file name.
an APK file appended with version.
Returns Returns
------- -------
@@ -93,32 +77,14 @@ class APP(object):
""" """
return f"Re-{self.app_name}-{slugify(self.app_version)}-output.apk" return f"Re-{self.app_name}-{slugify(self.app_version)}-output.apk"
def set_recommended_version(self, version: str, exp: bool = False) -> None:
"""The function sets the recommended version and experiment flag for an
app.
Parameters
----------
version : str
The version parameter is a string that represents the recommended version of the app.
exp : bool, optional
The "exp" parameter is a boolean flag that indicates whether the specified version is for an
experimental or regular release. If "exp" is set to True, it means the version is for an
experimental release. If "exp" is set to False or not provided, it means the version is for
"""
self.app_version = version
self.experiment = exp
def __str__(self: "APP") -> str: def __str__(self: "APP") -> str:
"""Returns the str representation of the app."""
attrs = vars(self) attrs = vars(self)
return ", ".join([f"{key}: {value}" for key, value in attrs.items()]) return ", ".join([f"{key}: {value}" for key, value in attrs.items()])
@staticmethod @staticmethod
def download( def download(url: str, config: RevancedConfig, assets_filter: str, file_name: str = "") -> str:
url: str, config: RevancedConfig, assets_filter: str, file_name: str = "" """The `download` function downloads a file from a given URL & filters the assets based on a given filter.
) -> str:
"""The `download` function downloads a file from a given URL using a
specified configuration and filters the assets based on a given filter.
Parameters Parameters
---------- ----------
@@ -156,9 +122,8 @@ class APP(object):
Downloader(config).direct_download(url, file_name) Downloader(config).direct_download(url, file_name)
return file_name return file_name
def download_patch_resources(self, config: RevancedConfig) -> None: def download_patch_resources(self: Self, config: RevancedConfig) -> None:
"""The function `download_patch_resources` downloads various resources """The function `download_patch_resources` downloads various resources req. for patching.
for patching in parallel using a ThreadPoolExecutor.
Parameters Parameters
---------- ----------
@@ -177,10 +142,7 @@ class APP(object):
# Using a ThreadPoolExecutor for parallelism # Using a ThreadPoolExecutor for parallelism
with ThreadPoolExecutor(4) as executor: with ThreadPoolExecutor(4) as executor:
futures = { futures = {resource_name: executor.submit(self.download, *args) for resource_name, *args in download_tasks}
resource_name: executor.submit(self.download, *args)
for resource_name, *args in download_tasks
}
# Wait for all tasks to complete # Wait for all tasks to complete
concurrent.futures.wait(futures.values()) concurrent.futures.wait(futures.values())
@@ -189,13 +151,12 @@ class APP(object):
for resource_name, future in futures.items(): for resource_name, future in futures.items():
try: try:
self.resource[resource_name] = future.result() self.resource[resource_name] = future.result()
except Exception as e: except UnknownError as e:
raise PatchingFailed(e) from e raise PatchingFailedError(e) from e
@staticmethod @staticmethod
def generate_filename(url: str) -> str: def generate_filename(url: str) -> str:
"""The function `generate_filename` takes a URL as input and returns a """The function `generate_filename` takes URL as input and returns a hashed version of the URL as the filename.
hashed version of the URL as the filename.
Parameters Parameters
---------- ----------
+8 -18
View File
@@ -1,6 +1,6 @@
"""Revanced Configurations.""" """Revanced Configurations."""
from pathlib import Path from pathlib import Path
from typing import List from typing import List, Self
from environs import Env from environs import Env
from requests import Session from requests import Session
@@ -8,15 +8,13 @@ from requests import Session
default_cli = "https://github.com/revanced/revanced-cli/releases/latest" default_cli = "https://github.com/revanced/revanced-cli/releases/latest"
default_patches = "https://github.com/revanced/revanced-patches/releases/latest" default_patches = "https://github.com/revanced/revanced-patches/releases/latest"
default_patches_json = default_patches default_patches_json = default_patches
default_integrations = ( default_integrations = "https://github.com/revanced/revanced-integrations/releases/latest"
"https://github.com/revanced/revanced-integrations/releases/latest"
)
class RevancedConfig(object): class RevancedConfig:
"""Revanced Configurations.""" """Revanced Configurations."""
def __init__(self, env: Env) -> None: def __init__(self: Self, env: Env) -> None:
from src.utils import default_build, request_header from src.utils import default_build, request_header
self.env = env self.env = env
@@ -34,18 +32,10 @@ class RevancedConfig(object):
self.dry_run = env.bool("DRY_RUN", False) self.dry_run = env.bool("DRY_RUN", False)
self.global_cli_dl = env.str("GLOBAL_CLI_DL", default_cli) self.global_cli_dl = env.str("GLOBAL_CLI_DL", default_cli)
self.global_patches_dl = env.str("GLOBAL_PATCHES_DL", default_patches) self.global_patches_dl = env.str("GLOBAL_PATCHES_DL", default_patches)
self.global_patches_json_dl = env.str( self.global_patches_json_dl = env.str("GLOBAL_PATCHES_JSON_DL", default_patches_json)
"GLOBAL_PATCHES_JSON_DL", default_patches_json self.global_integrations_dl = env.str("GLOBAL_INTEGRATIONS_DL", default_integrations)
) self.global_keystore_name = env.str("GLOBAL_KEYSTORE_FILE_NAME", "revanced.keystore")
self.global_integrations_dl = env.str(
"GLOBAL_INTEGRATIONS_DL", default_integrations
)
self.global_keystore_name = env.str(
"GLOBAL_KEYSTORE_FILE_NAME", "revanced.keystore"
)
self.global_archs_to_build = env.list("GLOBAL_ARCHS_TO_BUILD", []) self.global_archs_to_build = env.list("GLOBAL_ARCHS_TO_BUILD", [])
self.extra_download_files: List[str] = env.list("EXTRA_FILES", []) self.extra_download_files: List[str] = env.list("EXTRA_FILES", [])
self.apk_editor = "apkeditor-output.jar" self.apk_editor = "apkeditor-output.jar"
self.extra_download_files.append( self.extra_download_files.append("https://github.com/REAndroid/APKEditor@apkeditor.jar")
"https://github.com/REAndroid/APKEditor@apkeditor.jar"
)
+1
View File
@@ -0,0 +1 @@
"""Downloader files."""
+25 -40
View File
@@ -1,5 +1,5 @@
"""Downloader Class.""" """Downloader Class."""
from typing import Any, Dict, Tuple from typing import Any, Dict, Self, Tuple
import requests import requests
from bs4 import BeautifulSoup, Tag from bs4 import BeautifulSoup, Tag
@@ -8,31 +8,29 @@ from loguru import logger
from src.app import APP from src.app import APP
from src.downloader.download import Downloader from src.downloader.download import Downloader
from src.downloader.sources import APK_MIRROR_BASE_URL from src.downloader.sources import APK_MIRROR_BASE_URL
from src.exceptions import APKMirrorAPKDownloadFailure from src.downloader.utils import status_code_200
from src.exceptions import APKMirrorAPKDownloadError
from src.utils import bs4_parser, contains_any_word, request_header from src.utils import bs4_parser, contains_any_word, request_header
class ApkMirror(Downloader): class ApkMirror(Downloader):
"""Files downloader.""" """Files downloader."""
def _extract_force_download_link(self, link: str, app: str) -> Tuple[str, str]: def _extract_force_download_link(self: Self, link: str, app: str) -> Tuple[str, str]:
"""Extract force download link.""" """Extract force download link."""
notes_divs = self._extracted_search_div(link, "tab-pane") notes_divs = self._extracted_search_div(link, "tab-pane")
apk_type = self._extracted_search_div(link, "apkm-badge").get_text() apk_type = self._extracted_search_div(link, "apkm-badge").get_text()
extension = "zip" if apk_type == "BUNDLE" else "apk" extension = "zip" if apk_type == "BUNDLE" else "apk"
possible_links = notes_divs.find_all("a") possible_links = notes_divs.find_all("a")
for possible_link in possible_links: for possible_link in possible_links:
if possible_link.get("href") and "download.php?id=" in possible_link.get( if possible_link.get("href") and "download.php?id=" in possible_link.get("href"):
"href"
):
file_name = f"{app}.{extension}" file_name = f"{app}.{extension}"
self._download(APK_MIRROR_BASE_URL + possible_link["href"], file_name) self._download(APK_MIRROR_BASE_URL + possible_link["href"], file_name)
return file_name, APK_MIRROR_BASE_URL + possible_link["href"] return file_name, APK_MIRROR_BASE_URL + possible_link["href"]
raise APKMirrorAPKDownloadFailure( msg = f"Unable to extract force download for {app}"
f"Unable to extract force download for {app}", url=link raise APKMirrorAPKDownloadError(msg, url=link)
)
def extract_download_link(self, page: str, app: str) -> Tuple[str, str]: def extract_download_link(self: Self, page: str, app: str) -> Tuple[str, str]:
"""Function to extract the download link from apkmirror html page. """Function to extract the download link from apkmirror html page.
:param page: Url of the page :param page: Url of the page
@@ -45,19 +43,15 @@ class ApkMirror(Downloader):
( (
download_link["href"] download_link["href"]
for download_link in download_links for download_link in download_links
if download_link.get("href") if download_link.get("href") and "download/?key=" in download_link.get("href")
and "download/?key=" in download_link.get("href")
), ),
None, None,
): ):
return self._extract_force_download_link( return self._extract_force_download_link(APK_MIRROR_BASE_URL + final_download_link, app)
APK_MIRROR_BASE_URL + final_download_link, app msg = f"Unable to extract link from {app} version list"
) raise APKMirrorAPKDownloadError(msg, url=page)
raise APKMirrorAPKDownloadFailure(
f"Unable to extract link from {app} version list", url=page
)
def get_download_page(self, main_page: str) -> str: def get_download_page(self: Self, main_page: str) -> str:
"""Function to get the download page in apk_mirror. """Function to get the download page in apk_mirror.
:param main_page: Main Download Page in APK mirror(Index) :param main_page: Main Download Page in APK mirror(Index)
@@ -77,26 +71,23 @@ class ApkMirror(Downloader):
links[apk_type] = f"{APK_MIRROR_BASE_URL}{sub_url}" links[apk_type] = f"{APK_MIRROR_BASE_URL}{sub_url}"
if preferred_link := links.get("APK", links.get("BUNDLE")): if preferred_link := links.get("APK", links.get("BUNDLE")):
return preferred_link return preferred_link
raise APKMirrorAPKDownloadFailure( msg = "Unable to extract download page"
"Unable to extract download page", url=main_page raise APKMirrorAPKDownloadError(msg, url=main_page)
)
@staticmethod @staticmethod
def _extracted_search_div(url: str, search_class: str) -> Tag: def _extracted_search_div(url: str, search_class: str) -> Tag:
"""Extract search div.""" """Extract search div."""
r = requests.get(url, headers=request_header, timeout=60) r = requests.get(url, headers=request_header, timeout=60)
if r.status_code != 200: if r.status_code != status_code_200:
raise APKMirrorAPKDownloadFailure( msg = f"Unable to connect with {url}. Are you blocked by APKMirror or abused apkmirror ?.Reason - {r.text}"
f"Unable to connect with {url} on ApkMirror. Are you blocked by APKMirror or abused apkmirror " raise APKMirrorAPKDownloadError(
f"?.Reason - {r.text}", msg,
url=url, url=url,
) )
soup = BeautifulSoup(r.text, bs4_parser) soup = BeautifulSoup(r.text, bs4_parser)
return soup.find(class_=search_class) return soup.find(class_=search_class) # type: ignore[return-value]
def specific_version( def specific_version(self: Self, app: APP, version: str, main_page: str = "") -> Tuple[str, str]:
self, app: APP, version: str, main_page: str = ""
) -> Tuple[str, str]:
"""Function to download the specified version of app from apkmirror. """Function to download the specified version of app from apkmirror.
:param app: Name of the application :param app: Name of the application
@@ -112,18 +103,14 @@ class ApkMirror(Downloader):
download_page = self.get_download_page(main_page) download_page = self.get_download_page(main_page)
return self.extract_download_link(download_page, app.app_name) return self.extract_download_link(download_page, app.app_name)
def latest_version(self, app: APP, **kwargs: Any) -> Tuple[str, str]: def latest_version(self: Self, app: APP, **kwargs: Any) -> Tuple[str, str]:
"""Function to download whatever the latest version of app from """Function to download whatever the latest version of app from apkmirror.
apkmirror.
:param app: Name of the application :param app: Name of the application
:return: Version of downloaded apk :return: Version of downloaded apk
""" """
app_main_page = app.download_source app_main_page = app.download_source
versions_div = self._extracted_search_div( versions_div = self._extracted_search_div(app_main_page, "listWidget p-relative")
app_main_page, "listWidget p-relative"
)
app_rows = versions_div.find_all(class_="appRow") app_rows = versions_div.find_all(class_="appRow")
version_urls = [ version_urls = [
app_row.find(class_="downloadLink")["href"] app_row.find(class_="downloadLink")["href"]
@@ -131,6 +118,4 @@ class ApkMirror(Downloader):
if "beta" not in app_row.find(class_="appRowTitle").get_text().lower() if "beta" not in app_row.find(class_="appRowTitle").get_text().lower()
and "alpha" 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( return self.specific_version(app, "latest", APK_MIRROR_BASE_URL + max(version_urls))
app, "latest", APK_MIRROR_BASE_URL + max(version_urls)
)
+3 -4
View File
@@ -1,5 +1,5 @@
"""APK Pure Downloader Class.""" """APK Pure Downloader Class."""
from typing import Any, Tuple from typing import Any, Self, Tuple
from src.app import APP from src.app import APP
from src.downloader.download import Downloader from src.downloader.download import Downloader
@@ -8,9 +8,8 @@ from src.downloader.download import Downloader
class ApkPure(Downloader): class ApkPure(Downloader):
"""Files downloader.""" """Files downloader."""
def latest_version(self, app: APP, **kwargs: Any) -> Tuple[str, str]: def latest_version(self: Self, app: APP, **kwargs: Any) -> Tuple[str, str]:
"""Function to download whatever the latest version of app from """Function to download whatever the latest version of app from apkmirror.
apkmirror.
:param app: Name of the application :param app: Name of the application
:return: Version of downloaded apk :return: Version of downloaded apk
+9 -9
View File
@@ -1,38 +1,38 @@
"""APK SOS Downloader Class.""" """APK SOS Downloader Class."""
from typing import Any, Tuple from typing import Any, Self, Tuple
import requests import requests
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from src.app import APP from src.app import APP
from src.downloader.download import Downloader from src.downloader.download import Downloader
from src.exceptions import APKSosAPKDownloadFailure from src.exceptions import APKSosAPKDownloadError
from src.utils import bs4_parser, request_header from src.utils import bs4_parser, request_header
class ApkSos(Downloader): class ApkSos(Downloader):
"""Files downloader.""" """Files downloader."""
def extract_download_link(self, page: str, app: str) -> Tuple[str, str]: def extract_download_link(self: Self, page: str, app: str) -> Tuple[str, str]:
"""Function to extract the download link from apkmirror html page. """Function to extract the download link from apkmirror html page.
:param page: Url of the page :param page: Url of the page
:param app: Name of the app :param app: Name of the app
""" """
r = requests.get(page, headers=request_header, allow_redirects=True) r = requests.get(page, headers=request_header, allow_redirects=True, timeout=60)
soup = BeautifulSoup(r.text, bs4_parser) soup = BeautifulSoup(r.text, bs4_parser)
download_button = soup.find(class_="col-sm-12 col-md-8 text-center") download_button = soup.find(class_="col-sm-12 col-md-8 text-center")
possible_links = download_button.find_all("a") possible_links = download_button.find_all("a") # type: ignore[union-attr]
for possible_link in possible_links: for possible_link in possible_links:
if possible_link.get("href"): if possible_link.get("href"):
file_name = f"{app}.apk" file_name = f"{app}.apk"
self._download(possible_link["href"], file_name) self._download(possible_link["href"], file_name)
return file_name, possible_link["href"] return file_name, possible_link["href"]
raise APKSosAPKDownloadFailure(f"Unable to download {app}", url=page) msg = f"Unable to download {app}"
raise APKSosAPKDownloadError(msg, url=page)
def latest_version(self, app: APP, **kwargs: Any) -> Tuple[str, str]: def latest_version(self: Self, app: APP, **kwargs: Any) -> Tuple[str, str]:
"""Function to download whatever the latest version of app from """Function to download whatever the latest version of app from apkmirror.
apkmirror.
:param app: Name of the application :param app: Name of the application
:return: Version of downloaded apk :return: Version of downloaded apk
+16 -28
View File
@@ -4,7 +4,7 @@ import subprocess
from pathlib import Path from pathlib import Path
from queue import PriorityQueue from queue import PriorityQueue
from time import perf_counter from time import perf_counter
from typing import Any, Tuple from typing import Any, Self, Tuple
from loguru import logger from loguru import logger
from tqdm import tqdm from tqdm import tqdm
@@ -12,35 +12,25 @@ from tqdm import tqdm
from src.app import APP from src.app import APP
from src.config import RevancedConfig from src.config import RevancedConfig
from src.downloader.utils import implement_method from src.downloader.utils import implement_method
from src.exceptions import DownloadFailure from src.exceptions import DownloadError
from src.utils import handle_request_response from src.utils import handle_request_response
class Downloader(object): class Downloader:
"""Files downloader.""" """Files downloader."""
def __init__(self, config: RevancedConfig): def __init__(self: Self, config: RevancedConfig) -> None:
self._CHUNK_SIZE = 10485760 self._CHUNK_SIZE = 10485760
self._QUEUE: PriorityQueue[Tuple[float, str]] = PriorityQueue() self._QUEUE: PriorityQueue[Tuple[float, str]] = PriorityQueue()
self._QUEUE_LENGTH = 0 self._QUEUE_LENGTH = 0
self.config = config self.config = config
@staticmethod def _download(self: Self, url: str, file_name: str) -> None:
def file_status_check(file_name: Path, dry_run: bool, url: str) -> bool:
"""Check if file already exists."""
if os.path.exists(file_name) or dry_run:
logger.debug(
f"Skipping download of {file_name} from {url}. File already exists or dry running."
)
return True
return False
def _download(self, url: str, file_name: str) -> None:
if not url: if not url:
raise DownloadFailure("No url provided to download") msg = "No url provided to download"
if self.file_status_check( raise DownloadError(msg)
self.config.temp_folder.joinpath(file_name), self.config.dry_run, url if self.config.dry_run or Path(file_name).exists():
): logger.debug(f"Skipping download of {file_name} from {url}. File already exists or dry running.")
return return
logger.info(f"Trying to download {file_name} from {url}") logger.info(f"Trying to download {file_name} from {url}")
self._QUEUE_LENGTH += 1 self._QUEUE_LENGTH += 1
@@ -71,11 +61,11 @@ class Downloader(object):
self._QUEUE.put((perf_counter() - start, file_name)) self._QUEUE.put((perf_counter() - start, file_name))
logger.debug(f"Downloaded {file_name}") logger.debug(f"Downloaded {file_name}")
def extract_download_link(self, page: str, app: str) -> Tuple[str, str]: def extract_download_link(self: Self, page: str, app: str) -> Tuple[str, str]:
"""Extract download link from web page.""" """Extract download link from web page."""
raise NotImplementedError(implement_method) raise NotImplementedError(implement_method)
def specific_version(self, app: APP, version: str) -> Tuple[str, str]: def specific_version(self: Self, app: APP, version: str) -> Tuple[str, str]:
"""Function to download the specified version of app from apkmirror. """Function to download the specified version of app from apkmirror.
:param app: Name of the application :param app: Name of the application
@@ -84,7 +74,7 @@ class Downloader(object):
""" """
raise NotImplementedError(implement_method) raise NotImplementedError(implement_method)
def latest_version(self, app: APP, **kwargs: Any) -> Tuple[str, str]: def latest_version(self: Self, app: APP, **kwargs: Any) -> Tuple[str, str]:
"""Function to download the latest version of app. """Function to download the latest version of app.
:param app: Name of the application :param app: Name of the application
@@ -92,7 +82,7 @@ class Downloader(object):
""" """
raise NotImplementedError(implement_method) raise NotImplementedError(implement_method)
def convert_to_apk(self, file_name: str) -> str: def convert_to_apk(self: Self, file_name: str) -> str:
"""Convert apks to apk.""" """Convert apks to apk."""
if file_name.endswith(".apk"): if file_name.endswith(".apk"):
return file_name return file_name
@@ -122,7 +112,7 @@ class Downloader(object):
base_name, _ = os.path.splitext(filename) base_name, _ = os.path.splitext(filename)
return base_name + new_extension return base_name + new_extension
def download(self, version: str, app: APP, **kwargs: Any) -> Tuple[str, str]: def download(self: Self, version: str, app: APP, **kwargs: Any) -> Tuple[str, str]:
"""Public function to download apk to patch. """Public function to download apk to patch.
:param version: version to download :param version: version to download
@@ -131,9 +121,7 @@ class Downloader(object):
if self.config.dry_run: if self.config.dry_run:
return "", "" return "", ""
if app in self.config.existing_downloaded_apks: if app in self.config.existing_downloaded_apks:
logger.debug( logger.debug(f"Will not download {app.app_name} -v{version} from the internet.")
f"Will not download {app.app_name} -v{version} from the internet."
)
return app.app_name, f"local://{app.app_name}" return app.app_name, f"local://{app.app_name}"
if version and version != "latest": if version and version != "latest":
file_name, app_dl = self.specific_version(app, version) file_name, app_dl = self.specific_version(app, version)
@@ -141,6 +129,6 @@ class Downloader(object):
file_name, app_dl = self.latest_version(app, **kwargs) file_name, app_dl = self.latest_version(app, **kwargs)
return self.convert_to_apk(file_name), app_dl return self.convert_to_apk(file_name), app_dl
def direct_download(self, dl: str, file_name: str) -> None: def direct_download(self: Self, dl: str, file_name: str) -> None:
"""Download from DL.""" """Download from DL."""
self._download(dl, file_name) self._download(dl, file_name)
+12 -17
View File
@@ -5,37 +5,32 @@ from src.downloader.apkpure import ApkPure
from src.downloader.apksos import ApkSos from src.downloader.apksos import ApkSos
from src.downloader.download import Downloader from src.downloader.download import Downloader
from src.downloader.github import Github from src.downloader.github import Github
from src.downloader.sources import ( from src.downloader.sources import APK_MIRROR_BASE_URL, APK_PURE_BASE_URL, APKS_SOS_BASE_URL, GITHUB_BASE_URL
APK_MIRROR_BASE_URL,
APK_PURE_BASE_URL,
APKS_SOS_BASE_URL,
GITHUB_BASE_URL,
)
from src.downloader.uptodown import UptoDown from src.downloader.uptodown import UptoDown
from src.exceptions import DownloadFailure from src.exceptions import DownloadError
class DownloaderFactory(object): class DownloaderFactory:
"""Downloader Factory.""" """Downloader Factory."""
@staticmethod @staticmethod
def create_downloader(config: RevancedConfig, apk_source: str) -> Downloader: def create_downloader(config: RevancedConfig, apk_source: str) -> Downloader:
"""Returns appropriate downloader. """Returns appropriate downloader.
Parameters Args:
---------- ----
app : App Name config : Config
config : Config apk_source : Source URL for APK
apk_source : Source URL for APK
""" """
if apk_source.startswith(GITHUB_BASE_URL): if apk_source.startswith(GITHUB_BASE_URL):
return Github(config) return Github(config)
if apk_source.startswith(APK_PURE_BASE_URL): if apk_source.startswith(APK_PURE_BASE_URL):
return ApkPure(config) return ApkPure(config)
elif apk_source.startswith(APKS_SOS_BASE_URL): if apk_source.startswith(APKS_SOS_BASE_URL):
return ApkSos(config) return ApkSos(config)
elif apk_source.endswith("en.uptodown.com/android"): if apk_source.endswith("en.uptodown.com/android"):
return UptoDown(config) return UptoDown(config)
elif apk_source.startswith(APK_MIRROR_BASE_URL): if apk_source.startswith(APK_MIRROR_BASE_URL):
return ApkMirror(config) return ApkMirror(config)
raise DownloadFailure("No download factory found.") msg = "No download factory found."
raise DownloadError(msg)
+9 -20
View File
@@ -1,6 +1,6 @@
"""Github Downloader.""" """Github Downloader."""
import re import re
from typing import Dict, Tuple from typing import Dict, Self, Tuple
from urllib.parse import urlparse from urllib.parse import urlparse
import requests import requests
@@ -9,23 +9,21 @@ from loguru import logger
from src.app import APP from src.app import APP
from src.config import RevancedConfig from src.config import RevancedConfig
from src.downloader.download import Downloader from src.downloader.download import Downloader
from src.exceptions import DownloadFailure from src.exceptions import DownloadError
from src.utils import handle_request_response, update_changelog from src.utils import handle_request_response, update_changelog
class Github(Downloader): class Github(Downloader):
"""Files downloader.""" """Files downloader."""
def latest_version(self, app: APP, **kwargs: Dict[str, str]) -> Tuple[str, str]: def latest_version(self: Self, app: APP, **kwargs: Dict[str, str]) -> Tuple[str, str]:
"""Function to download files from GitHub repositories. """Function to download files from GitHub repositories.
:param app: App to download :param app: App to download
""" """
logger.debug(f"Trying to download {app.app_name} from github") logger.debug(f"Trying to download {app.app_name} from github")
if self.config.dry_run: if self.config.dry_run:
logger.debug( logger.debug(f"Skipping download of {app.app_name}. File already exists or dry running.")
f"Skipping download of {app.app_name}. File already exists or dry running."
)
return app.app_name, f"local://{app.app_name}" return app.app_name, f"local://{app.app_name}"
owner = str(kwargs["owner"]) owner = str(kwargs["owner"])
repo_name = str(kwargs["name"]) repo_name = str(kwargs["name"])
@@ -56,11 +54,7 @@ class Github(Downloader):
github_repo_name = path_segments[1] github_repo_name = path_segments[1]
release_tag = next( release_tag = next(
( (f"tags/{path_segments[i + 1]}" for i, segment in enumerate(path_segments) if segment == "tag"),
f"tags/{path_segments[i + 1]}"
for i, segment in enumerate(path_segments)
if segment == "tag"
),
"latest", "latest",
) )
return github_repo_owner, github_repo_name, release_tag return github_repo_owner, github_repo_name, release_tag
@@ -86,9 +80,8 @@ class Github(Downloader):
try: try:
filter_pattern = re.compile(asset_filter) filter_pattern = re.compile(asset_filter)
except re.error as e: except re.error as e:
raise DownloadFailure( msg = f"Invalid regex {asset_filter} pattern provided."
f"Invalid regex {asset_filter} pattern provided." raise DownloadError(msg) from e
) from e
for asset in assets: for asset in assets:
assets_url = asset["browser_download_url"] assets_url = asset["browser_download_url"]
assets_name = asset["name"] assets_name = asset["name"]
@@ -98,11 +91,7 @@ class Github(Downloader):
return "" return ""
@staticmethod @staticmethod
def patch_resource( def patch_resource(repo_url: str, assets_filter: str, config: RevancedConfig) -> str:
repo_url: str, assets_filter: str, config: RevancedConfig
) -> str:
"""Fetch patch resource from repo url.""" """Fetch patch resource from repo url."""
repo_owner, repo_name, tag = Github._extract_repo_owner_and_tag(repo_url) repo_owner, repo_name, tag = Github._extract_repo_owner_and_tag(repo_url)
return Github._get_release_assets( return Github._get_release_assets(repo_owner, repo_name, tag, assets_filter, config)
repo_owner, repo_name, tag, assets_filter, config
)
+1
View File
@@ -1,3 +1,4 @@
"""APK Sources used."""
APK_MIRROR_BASE_URL = "https://www.apkmirror.com" APK_MIRROR_BASE_URL = "https://www.apkmirror.com"
APK_MIRROR_BASE_APK_URL = f"{APK_MIRROR_BASE_URL}/apk" APK_MIRROR_BASE_APK_URL = f"{APK_MIRROR_BASE_URL}/apk"
UPTODOWN_BASE_URL = "https://{}.en.uptodown.com/android" UPTODOWN_BASE_URL = "https://{}.en.uptodown.com/android"
+23 -16
View File
@@ -1,5 +1,5 @@
"""Upto Down Downloader.""" """Upto Down Downloader."""
from typing import Any, Tuple from typing import Any, Self, Tuple
import requests import requests
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
@@ -7,27 +7,33 @@ from loguru import logger
from src.app import APP from src.app import APP
from src.downloader.download import Downloader from src.downloader.download import Downloader
from src.exceptions import UptoDownAPKDownloadFailure from src.exceptions import UptoDownAPKDownloadError
from src.utils import bs4_parser, request_header from src.utils import bs4_parser, request_header
class UptoDown(Downloader): class UptoDown(Downloader):
"""Files downloader.""" """Files downloader."""
def extract_download_link(self, page: str, app: str) -> Tuple[str, str]: def extract_download_link(self: Self, page: str, app: str) -> Tuple[str, str]:
"""Extract download link from uptodown url."""
r = requests.get(page, headers=request_header, allow_redirects=True, timeout=60) r = requests.get(page, headers=request_header, allow_redirects=True, timeout=60)
soup = BeautifulSoup(r.text, bs4_parser) soup = BeautifulSoup(r.text, bs4_parser)
soup = soup.find(id="detail-download-button") download_button = soup.find(id="detail-download-button")
download_url = soup.get("data-url") if not download_button:
msg = f"Unable to download {app} from uptodown."
raise UptoDownAPKDownloadError(msg, url=page)
download_url = download_button.get("data-url") # type: ignore[union-attr]
if not download_url: if not download_url:
raise UptoDownAPKDownloadFailure( msg = f"Unable to download {app} from uptodown."
f"Unable to download {app} from uptodown.", url=page raise UptoDownAPKDownloadError(msg, url=page)
)
file_name = f"{app}.apk" file_name = f"{app}.apk"
self._download(download_url, file_name) if isinstance(download_url, str):
return file_name, download_url self._download(download_url, file_name)
return file_name, download_url
msg = f"Unable to download {app} from uptodown."
raise UptoDownAPKDownloadError(msg, url=page)
def specific_version(self, app: APP, version: str) -> Tuple[str, str]: def specific_version(self: Self, app: APP, version: str) -> Tuple[str, str]:
"""Function to download the specified version of app from apkmirror. """Function to download the specified version of app from apkmirror.
:param app: Name of the application :param app: Name of the application
@@ -40,17 +46,18 @@ class UptoDown(Downloader):
soup = BeautifulSoup(html, bs4_parser) soup = BeautifulSoup(html, bs4_parser)
versions_list = soup.find("section", {"id": "versions"}) versions_list = soup.find("section", {"id": "versions"})
download_url = None download_url = None
for version_item in versions_list.find_all("div", {"data-url": True}): for version_item in versions_list.find_all("div", {"data-url": True}): # type: ignore[union-attr]
extracted_version = version_item.find("span", {"class": "version"}).text extracted_version = version_item.find("span", {"class": "version"}).text
if extracted_version == version: if extracted_version == version:
download_url = version_item["data-url"] download_url = version_item["data-url"]
break break
if download_url is None: if download_url is None:
raise UptoDownAPKDownloadFailure( msg = f"Unable to download {app.app_name} from uptodown."
f"Unable to download {app.app_name} from uptodown.", url=url raise UptoDownAPKDownloadError(msg, url=url)
)
return self.extract_download_link(download_url, app.app_name) return self.extract_download_link(download_url, app.app_name)
def latest_version(self, app: APP, **kwargs: Any) -> Tuple[str, str]: def latest_version(self: Self, app: APP, **kwargs: Any) -> Tuple[str, str]:
"""Function to download the latest version of app from uptodown."""
logger.debug("downloading latest version of app from uptodown.")
page = f"{app.download_source}/download" page = f"{app.download_source}/download"
return self.extract_download_link(page, app.app_name) return self.extract_download_link(page, app.app_name)
+1 -1
View File
@@ -1,4 +1,4 @@
"""Utility class.""" """Utility class."""
implement_method = "Please implement the method" implement_method = "Please implement the method"
status_code_200 = 200
+27 -31
View File
@@ -1,13 +1,15 @@
from typing import Any """Possible Exceptions."""
from typing import Any, Self
class APKMirrorIconScrapFailure(Exception): class APKMirrorIconScrapError(Exception):
"""Exception raised when the icon cannot be scraped from apkmirror.""" """Exception raised when the icon cannot be scraped from apkmirror."""
def __init__(self, *args: Any, **kwargs: Any) -> None: def __init__(self: Self, *args: Any, **kwargs: Any) -> None:
"""Initialize the APKMirrorIconScrapFailure exception. """Initialize the APKMirrorIconScrapFailure exception.
Args: Args:
----
*args: Variable length argument list. *args: Variable length argument list.
**kwargs: Arbitrary keyword arguments. **kwargs: Arbitrary keyword arguments.
url (str, optional): The URL of the failed icon scraping. Defaults to None. url (str, optional): The URL of the failed icon scraping. Defaults to None.
@@ -16,13 +18,18 @@ class APKMirrorIconScrapFailure(Exception):
self.url = kwargs.get("url", None) self.url = kwargs.get("url", None)
class DownloadFailure(Exception): class APKComboIconScrapError(APKMirrorIconScrapError):
"""Exception raised when the icon cannot be scraped from apkcombo."""
class DownloadError(Exception):
"""Generic Download failure.""" """Generic Download failure."""
def __init__(self, *args: Any, **kwargs: Any) -> None: def __init__(self: Self, *args: Any, **kwargs: Any) -> None:
"""Initialize the APKMirrorAPKDownloadFailure exception. """Initialize the APKMirrorAPKDownloadFailure exception.
Args: Args:
----
*args: Variable length argument list. *args: Variable length argument list.
**kwargs: Arbitrary keyword arguments. **kwargs: Arbitrary keyword arguments.
url (str, optional): The URL of the failed icon scraping. Defaults to None. url (str, optional): The URL of the failed icon scraping. Defaults to None.
@@ -31,64 +38,53 @@ class DownloadFailure(Exception):
self.url = kwargs.get("url", None) self.url = kwargs.get("url", None)
class APKDownloadFailure(DownloadFailure): class APKDownloadError(DownloadError):
"""Exception raised when the apk cannot be scraped.""" """Exception raised when the apk cannot be scraped."""
pass
class APKMirrorAPKDownloadError(APKDownloadError):
class APKMirrorAPKDownloadFailure(APKDownloadFailure):
"""Exception raised when downloading an APK from apkmirror failed.""" """Exception raised when downloading an APK from apkmirror failed."""
pass
class APKMirrorAPKNotFoundError(APKDownloadError):
class APKMirrorAPKNotFound(APKDownloadFailure):
"""Exception raised when apk doesn't exist on APKMirror.""" """Exception raised when apk doesn't exist on APKMirror."""
pass
class UptoDownAPKDownloadError(APKDownloadError):
class UptoDownAPKDownloadFailure(APKDownloadFailure):
"""Exception raised when downloading an APK from uptodown failed.""" """Exception raised when downloading an APK from uptodown failed."""
pass
class APKPureAPKDownloadError(APKDownloadError):
class APKPureAPKDownloadFailure(APKDownloadFailure):
"""Exception raised when downloading an APK from apkpure failed.""" """Exception raised when downloading an APK from apkpure failed."""
pass
class APKSosAPKDownloadError(APKDownloadError):
class APKSosAPKDownloadFailure(APKDownloadFailure):
"""Exception raised when downloading an APK from apksos failed.""" """Exception raised when downloading an APK from apksos failed."""
pass
class PatchingFailedError(Exception):
class PatchingFailed(Exception):
"""Patching Failed.""" """Patching Failed."""
pass
class AppNotFoundError(ValueError):
class AppNotFound(ValueError):
"""Not a valid Revanced App.""" """Not a valid Revanced App."""
pass
class PatchesJsonLoadError(ValueError):
class PatchesJsonLoadFailed(ValueError):
"""Failed to load patches json.""" """Failed to load patches json."""
def __init__(self, *args: Any, **kwargs: Any) -> None: def __init__(self: Self, *args: Any, **kwargs: Any) -> None:
"""Initialize the PatchesJsonLoadFailed exception. """Initialize the PatchesJsonLoadFailed exception.
Args: Args:
----
*args: Variable length argument list. *args: Variable length argument list.
**kwargs: Arbitrary keyword arguments. **kwargs: Arbitrary keyword arguments.
file_name (str, optional): The name of json file. Defaults to None. file_name (str, optional): The name of json file. Defaults to None.
""" """
super().__init__(*args) super().__init__(*args)
self.file_name = kwargs.get("file_name", None) self.file_name = kwargs.get("file_name", None)
class UnknownError(Exception):
"""Some unknown error."""
+23 -34
View File
@@ -1,18 +1,18 @@
"""Revanced Parser.""" """Revanced Parser."""
from subprocess import PIPE, Popen from subprocess import PIPE, Popen
from time import perf_counter from time import perf_counter
from typing import List from typing import List, Self
from loguru import logger from loguru import logger
from src.app import APP from src.app import APP
from src.config import RevancedConfig from src.config import RevancedConfig
from src.exceptions import PatchingFailed from src.exceptions import PatchingFailedError
from src.patches import Patches from src.patches import Patches
from src.utils import possible_archs from src.utils import possible_archs
class Parser(object): class Parser:
"""Revanced Parser.""" """Revanced Parser."""
CLI_JAR = "-jar" CLI_JAR = "-jar"
@@ -23,13 +23,13 @@ class Parser(object):
KEYSTORE_ARG = "--keystore" KEYSTORE_ARG = "--keystore"
OPTIONS_ARG = "--options" OPTIONS_ARG = "--options"
def __init__(self, patcher: Patches, config: RevancedConfig) -> None: def __init__(self: Self, patcher: Patches, config: RevancedConfig) -> None:
self._PATCHES: List[str] = [] self._PATCHES: List[str] = []
self._EXCLUDED: List[str] = [] self._EXCLUDED: List[str] = []
self.patcher = patcher self.patcher = patcher
self.config = config self.config = config
def include(self, name: str) -> None: def include(self: Self, name: str) -> None:
"""The function `include` adds a given patch to a list of patches. """The function `include` adds a given patch to a list of patches.
Parameters Parameters
@@ -39,9 +39,8 @@ class Parser(object):
""" """
self._PATCHES.extend(["-i", name]) self._PATCHES.extend(["-i", name])
def exclude(self, name: str) -> None: def exclude(self: Self, name: str) -> None:
"""The `exclude` function adds a given patch to the list of excluded """The `exclude` function adds a given patch to the list of excluded patches.
patches.
Parameters Parameters
---------- ----------
@@ -51,9 +50,8 @@ class Parser(object):
self._PATCHES.extend(["-e", name]) self._PATCHES.extend(["-e", name])
self._EXCLUDED.append(name) self._EXCLUDED.append(name)
def get_excluded_patches(self) -> List[str]: def get_excluded_patches(self: Self) -> List[str]:
"""The function `get_excluded_patches` is a getter method that returns """The function `get_excluded_patches` is a getter method that returns a list of excluded patches.
a list of excluded patches.
Returns Returns
------- -------
@@ -61,9 +59,8 @@ class Parser(object):
""" """
return self._EXCLUDED return self._EXCLUDED
def get_all_patches(self) -> List[str]: def get_all_patches(self: Self) -> List[str]:
"""The function "get_all_patches" is a getter method that returns a """The function "get_all_patches" is a getter method that returns a ist of all patches.
list of all patches.
Returns Returns
------- -------
@@ -71,9 +68,8 @@ class Parser(object):
""" """
return self._PATCHES return self._PATCHES
def invert_patch(self, name: str) -> bool: def invert_patch(self: Self, name: str) -> bool:
"""The function `invert_patch` takes a name as input, it toggles the """The function `invert_patch` takes a name as input, it toggles the status of the patch.
status of the patch and returns True, otherwise it returns False.
Parameters Parameters
---------- ----------
@@ -94,27 +90,23 @@ class Parser(object):
self._PATCHES[patch_index - 1] = "-i" self._PATCHES[patch_index - 1] = "-i"
else: else:
self._PATCHES[patch_index - 1] = "-e" self._PATCHES[patch_index - 1] = "-e"
return True
except ValueError: except ValueError:
return False return False
else:
return True
def exclude_all_patches(self) -> None: def exclude_all_patches(self: Self) -> None:
"""The function `exclude_all_patches` replaces all occurrences of "-i" """The function `exclude_all_patches` exclude all the patches."""
with "-e" in the list `self._PATCHES`.
Hence exclude all patches
"""
for idx, item in enumerate(self._PATCHES): for idx, item in enumerate(self._PATCHES):
if item == "-i": if item == "-i":
self._PATCHES[idx] = "-e" self._PATCHES[idx] = "-e"
# noinspection IncorrectFormatting # noinspection IncorrectFormatting
def patch_app( def patch_app(
self, self: Self,
app: APP, app: APP,
) -> None: ) -> None:
"""The function `patch_app` is used to patch an app using the Revanced """The function `patch_app` is used to patch an app using the Revanced CLI tool.
CLI tool.
Parameters Parameters
---------- ----------
@@ -151,16 +143,13 @@ class Parser(object):
for arch in excluded: for arch in excluded:
args.extend(("--rip-lib", arch)) args.extend(("--rip-lib", arch))
start = perf_counter() start = perf_counter()
logger.debug( logger.debug(f"Sending request to revanced cli for building with args java {args}")
f"Sending request to revanced cli for building with args java {args}"
)
process = Popen(["java", *args], stdout=PIPE) process = Popen(["java", *args], stdout=PIPE)
output = process.stdout output = process.stdout
if not output: if not output:
raise PatchingFailed("Failed to send request for patching.") msg = "Failed to send request for patching."
raise PatchingFailedError(msg)
for line in output: for line in output:
logger.debug(line.decode(), flush=True, end="") logger.debug(line.decode(), flush=True, end="")
process.wait() process.wait()
logger.info( logger.info(f"Patching completed for app {app} in {perf_counter() - start:.2f} seconds.")
f"Patching completed for app {app} in {perf_counter() - start:.2f} seconds."
)
+29 -48
View File
@@ -2,19 +2,20 @@
import contextlib import contextlib
import json import json
from typing import Any, Dict, List, Tuple from pathlib import Path
from typing import Any, ClassVar, Dict, List, Self, Tuple
from loguru import logger from loguru import logger
from src.app import APP from src.app import APP
from src.config import RevancedConfig from src.config import RevancedConfig
from src.exceptions import AppNotFound, PatchesJsonLoadFailed from src.exceptions import AppNotFoundError, PatchesJsonLoadError
class Patches(object): class Patches:
"""Revanced Patches.""" """Revanced Patches."""
revanced_package_names = { revanced_package_names: ClassVar[Dict[str, str]] = {
"com.reddit.frontpage": "reddit", "com.reddit.frontpage": "reddit",
"com.ss.android.ugc.trill": "tiktok", "com.ss.android.ugc.trill": "tiktok",
"com.twitter.android": "twitter", "com.twitter.android": "twitter",
@@ -59,9 +60,7 @@ class Patches(object):
@staticmethod @staticmethod
def get_package_name(app: str) -> str: def get_package_name(app: str) -> str:
"""The function `get_package_name` takes an app name as input and """The function `get_package_name` takes an app name as input and returns the corresponding package name.
returns the corresponding package name, or raises an exception if the
app is not supported.
Parameters Parameters
---------- ----------
@@ -75,14 +74,12 @@ class Patches(object):
for package, app_name in Patches.revanced_package_names.items(): for package, app_name in Patches.revanced_package_names.items():
if app_name == app: if app_name == app:
return package return package
raise AppNotFound( msg = f"App {app} not supported officially yet. Please provide package name in env to proceed."
f"App {app} not supported officially yet. Please provide package name in env to proceed." raise AppNotFoundError(msg)
)
@staticmethod @staticmethod
def support_app() -> Dict[str, str]: def support_app() -> Dict[str, str]:
"""The function `support_app()` returns a dictionary of supported app """The function returns a dictionary of supported app IDs.
IDs.
Returns Returns
------- -------
@@ -90,9 +87,8 @@ class Patches(object):
""" """
return Patches.revanced_package_names return Patches.revanced_package_names
def fetch_patches(self, config: RevancedConfig, app: APP) -> None: def fetch_patches(self: Self, config: RevancedConfig, app: APP) -> None:
"""The function fetches patches from a JSON file and organizes them """The function fetches patches from a JSON file.
based on compatibility with different applications.
Parameters Parameters
---------- ----------
@@ -104,9 +100,7 @@ class Patches(object):
""" """
self.patches_dict[app.app_name] = [] self.patches_dict[app.app_name] = []
patch_loader = PatchLoader() patch_loader = PatchLoader()
patches = patch_loader.load_patches( patches = patch_loader.load_patches(f'{config.temp_folder}/{app.resource["patches_json"]}')
f'{config.temp_folder}/{app.resource["patches_json"]}'
)
for patch in patches: for patch in patches:
if not patch["compatiblePackages"]: if not patch["compatiblePackages"]:
@@ -115,9 +109,7 @@ class Patches(object):
p["version"] = "all" p["version"] = "all"
self.patches_dict["universal_patch"].append(p) self.patches_dict["universal_patch"].append(p)
else: else:
for compatible_package, version in [ for compatible_package, version in [(x["name"], x["versions"]) for x in patch["compatiblePackages"]]:
(x["name"], x["versions"]) for x in patch["compatiblePackages"]
]:
if app.package_name == compatible_package: if app.package_name == compatible_package:
p = {x: patch[x] for x in ["name", "description"]} p = {x: patch[x] for x in ["name", "description"]}
p["app"] = compatible_package p["app"] = compatible_package
@@ -126,13 +118,12 @@ class Patches(object):
app.no_of_patches = len(self.patches_dict[app.app_name]) app.no_of_patches = len(self.patches_dict[app.app_name])
def __init__(self, config: RevancedConfig, app: APP) -> None: def __init__(self: Self, config: RevancedConfig, app: APP) -> None:
self.patches_dict: Dict[str, Any] = {"universal_patch": []} self.patches_dict: Dict[str, Any] = {"universal_patch": []}
self.fetch_patches(config, app) self.fetch_patches(config, app)
def get(self, app: str) -> Tuple[List[Dict[str, str]], str]: def get(self: Self, app: str) -> Tuple[List[Dict[str, str]], str]:
"""The function `get` retrieves all patches for a given application and """The function `get` returns all patches and version for a given application.
returns them along with the latest version.
Parameters Parameters
---------- ----------
@@ -146,18 +137,14 @@ class Patches(object):
patches for the given app. The second element is a string representing the version of the patches for the given app. The second element is a string representing the version of the
patches. patches.
""" """
patches = self.patches_dict[app] patches = self.patches_dict[app]
version = "latest" version = "latest"
with contextlib.suppress(StopIteration): with contextlib.suppress(StopIteration):
version = next(i["version"] for i in patches if i["version"] != "all") version = next(i["version"] for i in patches if i["version"] != "all")
return patches, version return patches, version
def include_exclude_patch( def include_exclude_patch(self: Self, app: APP, parser: Any, patches: List[Dict[str, str]]) -> None:
self, app: APP, parser: Any, patches: List[Dict[str, str]] """The function `include_exclude_patch` includes and excludes patches for a given app.
) -> None:
"""The function `include_exclude_patch` includes and excludes patches
for a given app based on certain conditions.
Parameters Parameters
---------- ----------
@@ -173,20 +160,14 @@ class Patches(object):
""" """
for patch in patches: for patch in patches:
normalized_patch = patch["name"].lower().replace(" ", "-") normalized_patch = patch["name"].lower().replace(" ", "-")
parser.include( parser.include(normalized_patch) if normalized_patch not in app.exclude_request else parser.exclude(
normalized_patch
) if normalized_patch not in app.exclude_request else parser.exclude(
normalized_patch normalized_patch
) )
for normalized_patch in app.include_request: for normalized_patch in app.include_request:
parser.include( parser.include(normalized_patch) if normalized_patch not in self.patches_dict["universal_patch"] else ()
normalized_patch
) if normalized_patch not in self.patches_dict["universal_patch"] else ()
def get_app_configs(self, app: "APP") -> List[Dict[str, str]]: def get_app_configs(self: Self, app: "APP") -> List[Dict[str, str]]:
"""The function `get_app_configs` retrieves configurations for a given """The function `get_app_configs` returns configurations for a given app.
app, including patches, version information, and whether it is
experimental.
Parameters Parameters
---------- ----------
@@ -211,7 +192,8 @@ class Patches(object):
): ):
experiment = True experiment = True
recommended_version = app.app_version recommended_version = app.app_version
app.set_recommended_version(recommended_version, experiment) app.app_version = recommended_version
app.experiment = experiment
return total_patches return total_patches
@@ -220,8 +202,7 @@ class PatchLoader:
@staticmethod @staticmethod
def load_patches(file_name: str) -> Any: def load_patches(file_name: str) -> Any:
"""The function `load_patches` loads patches from a file and returns """The function `load_patches` loads patches from a file and returns them.
them.
Parameters Parameters
---------- ----------
@@ -234,8 +215,8 @@ class PatchLoader:
the patches loaded from the file. the patches loaded from the file.
""" """
try: try:
with open(file_name) as f: with Path(file_name).open() as f:
patches = json.load(f) return json.load(f)
return patches
except FileNotFoundError as e: except FileNotFoundError as e:
raise PatchesJsonLoadFailed("File not found", file_name=file_name) from e msg = "File not found"
raise PatchesJsonLoadError(msg, file_name=file_name) from e
+36 -66
View File
@@ -2,6 +2,8 @@
import os import os
import re import re
import subprocess import subprocess
import sys
from pathlib import Path
from typing import Any, Dict, List from typing import Any, Dict, List
import requests import requests
@@ -9,7 +11,8 @@ from loguru import logger
from requests import Response from requests import Response
from src.config import RevancedConfig from src.config import RevancedConfig
from src.exceptions import DownloadFailure from src.downloader.utils import status_code_200
from src.exceptions import DownloadError
default_build = [ default_build = [
"youtube", "youtube",
@@ -28,8 +31,7 @@ bs4_parser = "html.parser"
def update_changelog(name: str, response: Dict[str, str]) -> None: def update_changelog(name: str, response: Dict[str, str]) -> None:
"""The function `update_changelog` updates the changelog file with the """The function `update_changelog` updates the changelog file.
provided name and response.
Parameters Parameters
---------- ----------
@@ -46,8 +48,7 @@ def update_changelog(name: str, response: Dict[str, str]) -> None:
def format_changelog(name: str, response: Dict[str, str], parent_repo: str) -> str: def format_changelog(name: str, response: Dict[str, str], parent_repo: str) -> str:
"""The `format_changelog` function takes in a name, a response dictionary, """The `format_changelog` returns formatted changelog string.
and a parent repository, and returns a formatted changelog string.
Parameters Parameters
---------- ----------
@@ -67,43 +68,28 @@ def format_changelog(name: str, response: Dict[str, str], parent_repo: str) -> s
a formatted changelog as a string. a formatted changelog as a string.
""" """
collapse_start = f"\n<details> <summary>👀 {name} </summary>\n\n" collapse_start = f"\n<details> <summary>👀 {name} </summary>\n\n"
release_version = ( release_version = f"**Release Version** - [{response['tag_name']}]({response['html_url']})<br>"
f"**Release Version** - [{response['tag_name']}]({response['html_url']})<br>"
)
change_log = f"**Changelog** -<br> {response['body']}" change_log = f"**Changelog** -<br> {response['body']}"
publish_time = f"**Published at** -<br> {response['published_at']}" publish_time = f"**Published at** -<br> {response['published_at']}"
footer = ( footer = f"<br><sub>Change logs generated by [Docker Py Revanced]({parent_repo})</sub>\n"
f"<br><sub>Change logs generated by [Docker Py Revanced]({parent_repo})</sub>\n"
)
collapse_end = "</details>" collapse_end = "</details>"
return "".join( return f"{collapse_start}{release_version}{change_log}{publish_time}{footer}{collapse_end}"
[
collapse_start,
release_version,
change_log,
publish_time,
footer,
collapse_end,
]
)
def write_to_file(change_log: str) -> None: def write_to_file(change_log: str) -> None:
"""The function `write_to_file` writes a given changelog string to a file """The function `write_to_file` writes a given changelog string to a file.
named "changelog.md".
Parameters Parameters
---------- ----------
change_log : str change_log : str
A string representing the changelog that you want to write to the file. A string representing the changelog that you want to write to the file.
""" """
with open("changelog.md", "w", encoding="utf_8") as file1: with Path("changelog.md").open("w", encoding="utf_8") as file1:
file1.write(change_log) file1.write(change_log)
def get_parent_repo() -> str: def get_parent_repo() -> str:
"""The function `get_parent_repo()` returns the URL of the parent """The `get_parent_repo()` function returns the URL of the parent repository.
repository.
Returns Returns
------- -------
@@ -113,8 +99,7 @@ def get_parent_repo() -> str:
def handle_request_response(response: Response) -> None: def handle_request_response(response: Response) -> None:
"""The function handles the response of a GET request and raises an """The function handles the response of a GET request and raises an exception if the response code is not 200.
exception if the response code is not 200.
Parameters Parameters
---------- ----------
@@ -124,14 +109,13 @@ def handle_request_response(response: Response) -> None:
server, such as the status code, headers, and response body. server, such as the status code, headers, and response body.
""" """
response_code = response.status_code response_code = response.status_code
if response_code != 200: if response_code != status_code_200:
raise DownloadFailure(f"Unable to downloaded assets. Reason - {response.text}") msg = f"Unable to downloaded assets. Reason - {response.text}"
raise DownloadError(msg)
def slugify(string: str) -> str: def slugify(string: str) -> str:
"""The `slugify` function converts a string to a slug format by converting """The `slugify` function converts a string to a slug format.
it to lowercase, removing special characters, replacing spaces with dashes,
removing consecutive dashes, and removing leading and trailing dashes.
Parameters Parameters
---------- ----------
@@ -155,47 +139,36 @@ def slugify(string: str) -> str:
modified_string = re.sub(r"-+", "-", modified_string) modified_string = re.sub(r"-+", "-", modified_string)
# Remove leading and trailing dashes # Remove leading and trailing dashes
modified_string = modified_string.strip("-") return modified_string.strip("-")
return modified_string
def check_java(dry_run: bool) -> None: def _check_version(output: str) -> None:
"""The function `check_java` checks if Java version 17 or higher is """Check version."""
installed and logs an error message if it is not. if "Runtime Environment" not in output:
raise subprocess.CalledProcessError(-1, "java -version")
if "17" not in output and "20" not in output:
raise subprocess.CalledProcessError(-1, "java -version")
Parameters
---------- def check_java() -> None:
dry_run : bool """The function `check_java` checks if Java version 17 or higher is installed.
The `dry_run` parameter is a boolean flag that determines whether the function should actually
check if Java is installed or just simulate the check. If `dry_run` is `True`, the function will
return without performing the check. If `dry_run` is `False`, the function will execute the
Returns Returns
------- -------
The function `check_java` does not return any value. It has a return type annotation of `None`, The function `check_java` does not return any value.
indicating that it does not return anything.
""" """
try: try:
if dry_run: jd = subprocess.check_output(["java", "-version"], stderr=subprocess.STDOUT).decode("utf-8")
return
jd = subprocess.check_output(
["java", "-version"], stderr=subprocess.STDOUT
).decode("utf-8")
jd = jd[1:-1] jd = jd[1:-1]
if "Runtime Environment" not in jd: _check_version(jd)
raise subprocess.CalledProcessError(-1, "java -version")
if "17" not in jd and "20" not in jd:
raise subprocess.CalledProcessError(-1, "java -version")
logger.debug("Cool!! Java is available") logger.debug("Cool!! Java is available")
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
logger.error("Java>= 17 must be installed") logger.error("Java>= 17 must be installed")
exit(-1) sys.exit(-1)
def extra_downloads(config: RevancedConfig) -> None: def extra_downloads(config: RevancedConfig) -> None:
"""The function `extra_downloads` downloads extra files specified in the """The function `extra_downloads` downloads extra files specified.
`config` object using the `APP.download` method.
Parameters Parameters
---------- ----------
@@ -217,15 +190,11 @@ def extra_downloads(config: RevancedConfig) -> None:
file_name=new_file_name, file_name=new_file_name,
) )
except (ValueError, IndexError): except (ValueError, IndexError):
logger.info( logger.info("Unable to download extra file. Provide input in url@name.apk format.")
"Unable to download extra file. Provide input in url@name.apk format."
)
def apkmirror_status_check(package_name: str) -> Any: def apkmirror_status_check(package_name: str) -> Any:
"""The `apkmirror_status_check` function checks if an app exists on """The `apkmirror_status_check` function checks if an app exists on APKMirror.
APKMirror by making a POST request to the APKMirror API with the package
name as a parameter.
Parameters Parameters
---------- ----------
@@ -239,9 +208,10 @@ def apkmirror_status_check(package_name: str) -> Any:
""" """
api_url = f"{apk_mirror_base_url}/wp-json/apkm/v1/app_exists/" api_url = f"{apk_mirror_base_url}/wp-json/apkm/v1/app_exists/"
body = {"pnames": [package_name]} body = {"pnames": [package_name]}
response = requests.post(api_url, json=body, headers=request_header) response = requests.post(api_url, json=body, headers=request_header, timeout=60)
return response.json() return response.json()
def contains_any_word(string: str, words: List[str]) -> bool: def contains_any_word(string: str, words: List[str]) -> bool:
"""Checks if a string contains any word."""
return any(word in string for word in words) return any(word in string for word in words)