Added apkeep support

This commit is contained in:
Nikhil Badyal
2025-04-19 11:18:31 +05:30
committed by Nikhil Badyal
parent bb05ec0f6f
commit 05ca33164c
14 changed files with 173 additions and 23 deletions
+44 -13
View File
@@ -50,8 +50,12 @@ class APP(object):
config.global_space_formatted,
)
def download_apk_for_patching(self: Self, config: RevancedConfig) -> None:
"""Download apk to be patched."""
def download_apk_for_patching(
self: Self,
config: RevancedConfig,
download_cache: dict[str, tuple[str, str]],
) -> None:
"""Download apk to be patched, skipping if already downloaded."""
from src.downloader.download import Downloader
from src.downloader.factory import DownloaderFactory
@@ -63,15 +67,23 @@ class APP(object):
logger.info("Downloading apk to be patched by scrapping")
try:
if not self.download_source:
self.download_source = apk_sources[self.app_name].format(self.package_name)
self.download_source = apk_sources[self.app_name.lower()].format(self.package_name)
except KeyError as key:
msg = f"App {self.app_name} not supported officially yet. Please provide download source in env."
raise DownloadError(
msg,
) from key
raise DownloadError(msg) from key
# Skip if already downloaded
if self.download_source in download_cache:
logger.info(f"Skipping download. Reusing APK from cache for {self.app_name}")
self.download_file_name, self.download_dl = download_cache[self.download_source]
return
downloader = DownloaderFactory.create_downloader(config=config, apk_source=self.download_source)
self.download_file_name, self.download_dl = downloader.download(self.app_version, self)
# Save to cache
download_cache[self.download_source] = (self.download_file_name, self.download_dl)
def get_output_file_name(self: Self) -> str:
"""The function returns a string representing the output file name.
@@ -135,7 +147,11 @@ class APP(object):
Downloader(config).direct_download(url, file_name)
return tag, file_name
def download_patch_resources(self: Self, config: RevancedConfig) -> None:
def download_patch_resources(
self: Self,
config: RevancedConfig,
resource_cache: dict[str, tuple[str, str]],
) -> None:
"""The function `download_patch_resources` downloads various resources req. for patching.
Parameters
@@ -143,22 +159,33 @@ class APP(object):
config : RevancedConfig
The `config` parameter is an instance of the `RevancedConfig` class. It is used to provide
configuration settings for the resource download tasks.
resource_cache: dict[str, tuple[str, str]]
"""
logger.info("Downloading resources for patching.")
# Create a list of resource download tasks
download_tasks = [
("cli", self.cli_dl, config, ".*jar"),
("patches", self.patches_dl, config, ".*rvp"),
]
# Using a ThreadPoolExecutor for parallelism
with ThreadPoolExecutor(1) as executor:
futures = {resource_name: executor.submit(self.download, *args) for resource_name, *args in download_tasks}
futures: dict[str, concurrent.futures.Future[tuple[str, str]]] = {}
for resource_name, raw_url, cfg, assets_filter in download_tasks:
url = raw_url.strip()
if url in resource_cache:
logger.info(f"Skipping {resource_name} download, using cached resource: {url}")
tag, file_name = resource_cache[url]
self.resource[resource_name] = {
"file_name": file_name,
"version": tag,
}
continue
futures[resource_name] = executor.submit(self.download, url, cfg, assets_filter)
# Wait for all tasks to complete
concurrent.futures.wait(futures.values())
# Retrieve results from completed tasks
for resource_name, future in futures.items():
try:
tag, file_name = future.result()
@@ -166,8 +193,12 @@ class APP(object):
"file_name": file_name,
"version": tag,
}
resource_cache[download_tasks[["cli", "patches"].index(resource_name)][1].strip()] = (
tag,
file_name,
)
except BuilderError as e:
msg = "Failed to download resource."
msg = f"Failed to download {resource_name} resource."
raise PatchingFailedError(msg) from e
@staticmethod
+66
View File
@@ -0,0 +1,66 @@
"""Apkeep Downloader Class."""
import subprocess
from time import perf_counter
from typing import Any, Self
from loguru import logger
from src.app import APP
from src.downloader.download import Downloader
from src.exceptions import DownloadError
class Apkeep(Downloader):
"""Apkeep-based Downloader."""
def _run_apkeep(self: Self, package_name: str, version: str = "") -> str:
"""Run apkeep CLI to fetch APK from Google Play."""
email = self.config.env.str("APKEEP_EMAIL")
token = self.config.env.str("APKEEP_TOKEN")
if not email or not token:
msg = "APKEEP_EMAIL and APKEEP_TOKEN must be set in environment."
raise DownloadError(msg)
file_name = f"{package_name}.apk"
file_path = self.config.temp_folder / file_name
if file_path.exists():
logger.debug(f"{file_name} already downloaded.")
return file_name
cmd = [
"apkeep",
"-a",
f"{package_name}@{version}" if version and version != "latest" else package_name,
"-d",
"google-play",
"-e",
email,
"-t",
token,
self.config.temp_folder_name,
]
logger.debug(f"Running command: {cmd}")
start = perf_counter()
process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
output = process.stdout
if not output:
msg = "Failed to send request for patching."
raise DownloadError(msg)
for line in output:
logger.debug(line.decode(), flush=True, end="")
process.wait()
if process.returncode != 0:
msg = f"Command failed with exit code {process.returncode} for app {package_name}"
raise DownloadError(msg)
logger.info(f"Downloading completed for app {package_name} in {perf_counter() - start:.2f} seconds.")
return file_name
def latest_version(self: Self, app: APP, **kwargs: Any) -> tuple[str, str]:
"""Download latest version from Google Play via Apkeep."""
file_name = self._run_apkeep(app.package_name)
logger.info(f"Got file name as {file_name}")
return file_name, f"apkeep://google-play/{app.package_name}"
+4
View File
@@ -1,6 +1,7 @@
"""Downloader Factory."""
from src.config import RevancedConfig
from src.downloader.apkkeep import Apkeep
from src.downloader.apkmirror import ApkMirror
from src.downloader.apkmonk import ApkMonk
from src.downloader.apkpure import ApkPure
@@ -12,6 +13,7 @@ from src.downloader.sources import (
APK_MIRROR_BASE_URL,
APK_MONK_BASE_URL,
APK_PURE_BASE_URL,
APKEEP,
APKS_SOS_BASE_URL,
DRIVE_DOWNLOAD_BASE_URL,
GITHUB_BASE_URL,
@@ -47,5 +49,7 @@ class DownloaderFactory(object):
return ApkMonk(config)
if apk_source.startswith(DRIVE_DOWNLOAD_BASE_URL):
return GoogleDrive(config)
if apk_source.startswith(APKEEP):
return Apkeep(config)
msg = "No download factory found."
raise DownloadError(msg, url=apk_source)
+2 -2
View File
@@ -35,7 +35,7 @@ class Github(Downloader):
}
if self.config.personal_access_token:
logger.debug("Using personal access token")
headers["Authorization"] = f"token {self.config.personal_access_token}"
headers["Authorization"] = f"Bearer {self.config.personal_access_token}"
response = requests.get(repo_url, headers=headers, timeout=request_timeout)
handle_request_response(response, repo_url)
if repo_name == "revanced-patches":
@@ -80,7 +80,7 @@ class Github(Downloader):
"Content-Type": "application/vnd.github.v3+json",
}
if config.personal_access_token:
headers["Authorization"] = f"token {config.personal_access_token}"
headers["Authorization"] = f"Bearer {config.personal_access_token}"
response = requests.get(api_url, headers=headers, timeout=request_timeout)
handle_request_response(response, api_url)
update_changelog(f"{github_repo_owner}/{github_repo_name}", response.json())
+1
View File
@@ -19,6 +19,7 @@ APK_COMBO_GENERIC_URL = APK_COMBO_BASE_URL + "/genericApp/{}"
not_found_icon = "https://img.icons8.com/bubbles/500/android-os.png"
revanced_api = "https://api.revanced.app/v2/patches/latest"
APK_MONK_BASE_URL = "https://www.apkmonk.com"
APKEEP = "apkeep"
APK_MONK_APK_URL = APK_MONK_BASE_URL + "/app/{}/"
APK_MONK_ICON_URL = "https://cdn.apkmonk.com/logos/{}"
DRIVE_BASE_URL = "https://drive.google.com"
+1
View File
@@ -244,6 +244,7 @@ class Parser(object):
excluded = set(possible_archs) - set(app.archs_to_build)
for arch in excluded:
args.extend(("--rip-lib", arch))
args.extend(("--purge",))
start = perf_counter()
logger.debug(f"Sending request to revanced cli for building with args java {args}")
process = Popen(["java", *args], stdout=PIPE)