mirror of
https://github.com/sotam0316/docker-py-revanced.git
synced 2026-04-24 19:38:36 +09:00
✨ Multi Source patch
This commit is contained in:
committed by
Nikhil Badyal
parent
a2ba0b89d1
commit
4283925f3e
@@ -47,7 +47,7 @@ You can use any of the following methods to build.
|
||||
|
||||
</details>
|
||||
|
||||
5. If the building process is successful, you’ll get your APKs in the<br>
|
||||
5. If the building process is successful, you'll get your APKs in the<br>
|
||||
<img src="https://i.imgur.com/S5d7qAO.png" width="700" style="left">
|
||||
6. Make sure to do below steps once in a while(daily or weekly) to keep the builder bug free.<br>
|
||||
<img src="https://i.imgur.com/CbdH7vM.png" width="700" style="left">
|
||||
@@ -134,20 +134,20 @@ You can use any of the following methods to build.
|
||||
`*` - Can be overridden for individual app.
|
||||
### App Level Config
|
||||
|
||||
| Env Name | Description | Default |
|
||||
|:--------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------:|:-------------------------------|
|
||||
| [~~**APP_NAME**_CLI_DL~~](#global-resources) | DL for CLI to be used for patching **APP_NAME**.(Disabled Temp) | GLOBAL_CLI_DL |
|
||||
| [**APP_NAME**_PATCHES_DL](#global-resources) | DL for Patches to be used for patching **APP_NAME**. | GLOBAL_PATCHES_DL |
|
||||
| [**APP_NAME**_SPACE_FORMATTED_PATCHES](#global-resources) | Whether patches are space formatted. **APP_NAME**. | GLOBAL_SPACE_FORMATTED_PATCHES |
|
||||
| [**APP_NAME**_KEYSTORE_FILE_NAME](#global-keystore-file-name) | Key file to be used for signing **APP_NAME**. | GLOBAL_KEYSTORE_FILE_NAME |
|
||||
| [**APP_NAME**_OLD_KEY](#global-keystore-file-name) | Whether key used was generated with cli > v4(new) <br/><br/>**APP_NAME**. <br/> <br/> | GLOBAL_OLK_KEY |
|
||||
| [**APP_NAME**_ARCHS_TO_BUILD](#global-archs-to-build) | Arch to keep in the patched **APP_NAME**. | GLOBAL_ARCHS_TO_BUILD |
|
||||
| [**APP_NAME**_EXCLUDE_PATCH**](#custom-exclude-patching) | Patches to exclude while patching **APP_NAME**. | [] |
|
||||
| [**APP_NAME**_INCLUDE_PATCH**](#custom-include-patching) | Patches to include while patching **APP_NAME**. | [] |
|
||||
| [**APP_NAME**_VERSION](#app-version) | Version to use for download for patching. | Recommended by patch resources |
|
||||
| [**APP_NAME**_PACKAGE_NAME***](#any-patch-apps) | Package name of the app to be patched | None |
|
||||
| [**APP_NAME**_DL_SOURCE***](#any-patch-apps) | Download source of any of the supported scrapper | None |
|
||||
| [**APP_NAME**_DL***](#app-dl) | Direct download Link for clean apk | None |
|
||||
| Env Name | Description | Default |
|
||||
|:--------------------------------------------------------------|:----------------------------------------------------------------------------------------------------:|:-------------------------------|
|
||||
| [~~**APP_NAME**_CLI_DL~~](#global-resources) | DL for CLI to be used for patching **APP_NAME**.(Disabled Temp) | GLOBAL_CLI_DL |
|
||||
| [**APP_NAME**_PATCHES_DL](#global-resources) | DL for Patches to be used for patching **APP_NAME**. Supports multiple bundles via comma separation. | GLOBAL_PATCHES_DL |
|
||||
| [**APP_NAME**_SPACE_FORMATTED_PATCHES](#global-resources) | Whether patches are space formatted. **APP_NAME**. | GLOBAL_SPACE_FORMATTED_PATCHES |
|
||||
| [**APP_NAME**_KEYSTORE_FILE_NAME](#global-keystore-file-name) | Key file to be used for signing **APP_NAME**. | GLOBAL_KEYSTORE_FILE_NAME |
|
||||
| [**APP_NAME**_OLD_KEY](#global-keystore-file-name) | Whether key used was generated with cli > v4(new) <br/><br/>**APP_NAME**. <br/> <br/> | GLOBAL_OLK_KEY |
|
||||
| [**APP_NAME**_ARCHS_TO_BUILD](#global-archs-to-build) | Arch to keep in the patched **APP_NAME**. | GLOBAL_ARCHS_TO_BUILD |
|
||||
| [**APP_NAME**_EXCLUDE_PATCH**](#custom-exclude-patching) | Patches to exclude while patching **APP_NAME**. | [] |
|
||||
| [**APP_NAME**_INCLUDE_PATCH**](#custom-include-patching) | Patches to include while patching **APP_NAME**. | [] |
|
||||
| [**APP_NAME**_VERSION](#app-version) | Version to use for download for patching. | Recommended by patch resources |
|
||||
| [**APP_NAME**_PACKAGE_NAME***](#any-patch-apps) | Package name of the app to be patched | None |
|
||||
| [**APP_NAME**_DL_SOURCE***](#any-patch-apps) | Download source of any of the supported scrapper | None |
|
||||
| [**APP_NAME**_DL***](#app-dl) | Direct download Link for clean apk | None |
|
||||
|
||||
`**` - By default all patches for a given app are included.<br>
|
||||
`**` - Can be used to included universal patch.<br>
|
||||
@@ -308,10 +308,17 @@ You can use any of the following methods to build.
|
||||
```
|
||||
With the config tool will try to patch YouTube with resources from inotia00 while other global resource will used
|
||||
for patching other apps.<br>
|
||||
|
||||
**Multi-Patching Support**: You can now use multiple patch bundles from different creators for the same app:
|
||||
```dotenv
|
||||
# Comma-separated URLs
|
||||
YOUTUBE_PATCHES_DL=https://github.com/ReVanced/revanced-patches,https://github.com/indrastorm/Dropped-patches
|
||||
```
|
||||
The tool will download all specified patch bundles and apply them together using the ReVanced CLI's multiple `-p` argument support.<br>
|
||||
If you have want to provide resource locally in the apks folder. You can specify that by mentioning filename
|
||||
prefixed with `local://`.<br>
|
||||
*Note* - The link provided must be DLs. Unless they are from GitHub.<br>
|
||||
*Note* - If your patches resource are available on GitHub and you want to select latest resource without excluding
|
||||
_Note_ - The link provided must be DLs. Unless they are from GitHub.<br>
|
||||
_Note_ - If your patches resource are available on GitHub and you want to select latest resource without excluding
|
||||
pre-release you can add `latest-prerelease` to the URL.
|
||||
Example:
|
||||
```dotenv
|
||||
@@ -323,7 +330,7 @@ You can use any of the following methods to build.
|
||||
```
|
||||
For above example tool while selecting latest patches will exclude any pre-release/beta ie. will consider only
|
||||
stable releases..<br>
|
||||
*Note* - Some of the patch source like inotia00 still provides **-** separated patches while revanced shifted to
|
||||
_Note_ - Some of the patch source like inotia00 still provides **-** separated patches while revanced shifted to
|
||||
Space formatted patches. Use `SPACE_FORMATTED_PATCHES` to define the type of patches.
|
||||
|
||||
8. <a id="global-keystore-file-name"></a>If you don't want to use default keystore. You can provide your own by
|
||||
@@ -371,7 +378,7 @@ You can use any of the following methods to build.
|
||||
```dotenv
|
||||
YOUTUBE_ARCHS_TO_BUILD=arm64-v8a,armeabi-v7a
|
||||
```
|
||||
*Note* -
|
||||
_Note_ -
|
||||
1. Possible values are: `armeabi-v7a`,`x86`,`x86_64`,`arm64-v8a`
|
||||
2. Make sure the patching resource(CLI) support this feature.
|
||||
11. <a id="extra-files"></a>If you want to include any extra file to the Github upload. Set comma arguments
|
||||
|
||||
+74
-24
@@ -31,10 +31,17 @@ class APP(object):
|
||||
self.app_version = config.env.str(f"{app_name}_VERSION".upper(), None)
|
||||
self.experiment = False
|
||||
self.cli_dl = config.env.str(f"{app_name}_CLI_DL".upper(), config.global_cli_dl)
|
||||
self.patches_dl = config.env.str(f"{app_name}_PATCHES_DL".upper(), config.global_patches_dl)
|
||||
|
||||
# Support multiple patch bundles via comma-separated URLs
|
||||
patches_dl_raw = config.env.str(f"{app_name}_PATCHES_DL".upper(), config.global_patches_dl)
|
||||
self.patches_dl_list = [url.strip() for url in patches_dl_raw.split(",") if url.strip()]
|
||||
# Keep backward compatibility
|
||||
self.patches_dl = patches_dl_raw
|
||||
|
||||
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, dict[str, str]] = {}
|
||||
self.patch_bundles: list[dict[str, str]] = [] # Store multiple patch bundles
|
||||
self.no_of_patches: int = 0
|
||||
self.keystore_name = config.env.str(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)
|
||||
@@ -113,18 +120,18 @@ class APP(object):
|
||||
----------
|
||||
url : str
|
||||
The `url` parameter is a string that represents the URL of the resource you want to download.
|
||||
It can be a URL from GitHub or a local file URL.
|
||||
It can be a URL from GitHub or a local file URL.
|
||||
config : RevancedConfig
|
||||
The `config` parameter is an instance of the `RevancedConfig` class. It is used to provide
|
||||
configuration settings for the download process.
|
||||
configuration settings for the download process.
|
||||
assets_filter : str
|
||||
The `assets_filter` parameter is a string that is used to filter the assets to be downloaded
|
||||
from a GitHub repository. It is used when the `url` parameter starts with "https://github". The
|
||||
`assets_filter` string is matched against the names of the assets in the repository, and only
|
||||
file_name : str
|
||||
from a GitHub repository. It is used when the `url` parameter starts with "https://github". The
|
||||
`assets_filter` string is matched against the names of the assets in the repository, and only
|
||||
file_name : str
|
||||
The `file_name` parameter is a string that represents the name of the file that will be
|
||||
downloaded. If no value is provided for `file_name`, the function will generate a filename based
|
||||
on the URL of the file being downloaded.
|
||||
downloaded. If no value is provided for `file_name`, the function will generate a filename based
|
||||
on the URL of the file being downloaded.
|
||||
|
||||
Returns
|
||||
-------
|
||||
@@ -148,6 +155,58 @@ class APP(object):
|
||||
Downloader(config).direct_download(url, file_name)
|
||||
return tag, file_name
|
||||
|
||||
def _setup_download_tasks(self: Self) -> list[tuple[str, str, None, str]]:
|
||||
"""Setup download tasks for CLI and patch bundles."""
|
||||
download_tasks = [
|
||||
("cli", self.cli_dl, None, ".*jar"),
|
||||
]
|
||||
|
||||
# Download multiple patch bundles
|
||||
for i, patches_url in enumerate(self.patches_dl_list):
|
||||
bundle_name = f"patches_{i}" if len(self.patches_dl_list) > 1 else "patches"
|
||||
download_tasks.append((bundle_name, patches_url, None, ".*rvp"))
|
||||
|
||||
return download_tasks
|
||||
|
||||
def _handle_cached_resource(self: Self, resource_name: str, tag: str, file_name: str) -> None:
|
||||
"""Handle cached resource and update appropriate data structures."""
|
||||
if resource_name.startswith("patches"):
|
||||
self.patch_bundles.append(
|
||||
{
|
||||
"name": resource_name,
|
||||
"file_name": file_name,
|
||||
"version": tag,
|
||||
},
|
||||
)
|
||||
# Keep backward compatibility for single bundle
|
||||
if resource_name == "patches" or len(self.patches_dl_list) == 1:
|
||||
self.resource["patches"] = {
|
||||
"file_name": file_name,
|
||||
"version": tag,
|
||||
}
|
||||
else:
|
||||
self.resource[resource_name] = {
|
||||
"file_name": file_name,
|
||||
"version": tag,
|
||||
}
|
||||
|
||||
def _handle_downloaded_resource(
|
||||
self: Self,
|
||||
resource_name: str,
|
||||
tag: str,
|
||||
file_name: str,
|
||||
download_tasks: list[tuple[str, str, RevancedConfig, str]],
|
||||
resource_cache: dict[str, tuple[str, str]],
|
||||
) -> None:
|
||||
"""Handle newly downloaded resource and update cache."""
|
||||
self._handle_cached_resource(resource_name, tag, file_name)
|
||||
|
||||
# Update cache for the corresponding URL
|
||||
for task_name, task_url, _, _ in download_tasks:
|
||||
if task_name == resource_name:
|
||||
resource_cache[task_url.strip()] = (tag, file_name)
|
||||
break
|
||||
|
||||
def download_patch_resources(
|
||||
self: Self,
|
||||
config: RevancedConfig,
|
||||
@@ -159,14 +218,15 @@ 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.
|
||||
configuration settings for the resource download tasks.
|
||||
resource_cache: dict[str, tuple[str, str]]
|
||||
"""
|
||||
logger.info("Downloading resources for patching.")
|
||||
|
||||
download_tasks = [
|
||||
("cli", self.cli_dl, config, ".*jar"),
|
||||
("patches", self.patches_dl, config, ".*rvp"),
|
||||
base_tasks = self._setup_download_tasks()
|
||||
# Update download tasks with config
|
||||
download_tasks: list[tuple[str, str, RevancedConfig, str]] = [
|
||||
(name, url, config, filter_pattern) for name, url, _, filter_pattern in base_tasks
|
||||
]
|
||||
|
||||
with ThreadPoolExecutor(1) as executor:
|
||||
@@ -177,10 +237,7 @@ class APP(object):
|
||||
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,
|
||||
}
|
||||
self._handle_cached_resource(resource_name, tag, file_name)
|
||||
continue
|
||||
|
||||
futures[resource_name] = executor.submit(self.download, url, cfg, assets_filter)
|
||||
@@ -190,14 +247,7 @@ class APP(object):
|
||||
for resource_name, future in futures.items():
|
||||
try:
|
||||
tag, file_name = future.result()
|
||||
self.resource[resource_name] = {
|
||||
"file_name": file_name,
|
||||
"version": tag,
|
||||
}
|
||||
resource_cache[download_tasks[["cli", "patches"].index(resource_name)][1].strip()] = (
|
||||
tag,
|
||||
file_name,
|
||||
)
|
||||
self._handle_downloaded_resource(resource_name, tag, file_name, download_tasks, resource_cache)
|
||||
except BuilderError as e:
|
||||
msg = f"Failed to download {resource_name} resource."
|
||||
raise PatchingFailedError(msg) from e
|
||||
|
||||
@@ -51,7 +51,9 @@ class Github(Downloader):
|
||||
"""Extract repo owner and url from github url."""
|
||||
parsed_url = urlparse(url)
|
||||
path_segments = parsed_url.path.strip("/").split("/")
|
||||
|
||||
if len(path_segments) < 2:
|
||||
msg = f"Invalid GitHub URL format: {url}"
|
||||
raise DownloadError(msg)
|
||||
github_repo_owner = path_segments[0]
|
||||
github_repo_name = path_segments[1]
|
||||
tag_position = 3
|
||||
|
||||
+1
-1
@@ -130,4 +130,4 @@ class PatchesJsonLoadError(BuilderError):
|
||||
def __str__(self: Self) -> str:
|
||||
"""Exception message."""
|
||||
base_message = super().__str__()
|
||||
return f"Message - {base_message} Url - {self.file_name}"
|
||||
return f"Message - {base_message} File - {self.file_name}"
|
||||
|
||||
+41
-21
@@ -24,6 +24,9 @@ class Parser(object):
|
||||
OUTPUT_ARG = "-o"
|
||||
KEYSTORE_ARG = "--keystore"
|
||||
OPTIONS_ARG = "-O"
|
||||
ENABLE_ARG = "-e"
|
||||
DISABLE_ARG = "-d"
|
||||
EXCLUSIVE_ARG = "--exclusive"
|
||||
|
||||
def __init__(self: Self, patcher: Patches, config: RevancedConfig) -> None:
|
||||
self._PATCHES: list[str] = []
|
||||
@@ -71,7 +74,7 @@ class Parser(object):
|
||||
for opt in options:
|
||||
pair = self.format_option(opt)
|
||||
self._PATCHES[:0] = [self.OPTIONS_ARG, pair]
|
||||
self._PATCHES[:0] = ["-e", name]
|
||||
self._PATCHES[:0] = [self.ENABLE_ARG, name]
|
||||
|
||||
def exclude(self: Self, name: str) -> None:
|
||||
"""The `exclude` function adds a given patch to the list of excluded patches.
|
||||
@@ -81,7 +84,7 @@ class Parser(object):
|
||||
name : str
|
||||
The `name` parameter is a string that represents the name of the patch to be excluded.
|
||||
"""
|
||||
self._PATCHES.extend(["-d", name])
|
||||
self._PATCHES.extend([self.DISABLE_ARG, name])
|
||||
self._EXCLUDED.append(name)
|
||||
|
||||
def get_excluded_patches(self: Self) -> list[str]:
|
||||
@@ -119,22 +122,27 @@ class Parser(object):
|
||||
name = name.lower().replace(" ", "-")
|
||||
indices = [i for i in range(len(self._PATCHES)) if self._PATCHES[i] == name]
|
||||
for patch_index in indices:
|
||||
if self._PATCHES[patch_index - 1] == "-e":
|
||||
self._PATCHES[patch_index - 1] = "-d"
|
||||
if self._PATCHES[patch_index - 1] == self.ENABLE_ARG:
|
||||
self._PATCHES[patch_index - 1] = self.DISABLE_ARG
|
||||
else:
|
||||
self._PATCHES[patch_index - 1] = "-e"
|
||||
self._PATCHES[patch_index - 1] = self.ENABLE_ARG
|
||||
except ValueError:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def exclude_all_patches(self: Self) -> None:
|
||||
"""The function `exclude_all_patches` exclude all the patches."""
|
||||
for idx, item in enumerate(self._PATCHES):
|
||||
if idx == 0:
|
||||
continue
|
||||
if item == "-e":
|
||||
self._PATCHES[idx] = "-d"
|
||||
def enable_exclusive_mode(self: Self) -> None:
|
||||
"""Enable exclusive mode - only explicitly enabled patches will run, all others disabled by default."""
|
||||
logger.info("Enabling exclusive mode for fast testing - only keeping one patch enabled.")
|
||||
# Clear all patches and keep only the first one enabled
|
||||
if self._PATCHES:
|
||||
# Find the first enable argument and its patch name
|
||||
for idx in range(0, len(self._PATCHES), 2):
|
||||
if idx < len(self._PATCHES) and self._PATCHES[idx] == self.ENABLE_ARG and idx + 1 < len(self._PATCHES):
|
||||
first_patch = self._PATCHES[idx + 1]
|
||||
# Clear all patches and set only the first one
|
||||
self._PATCHES = [self.ENABLE_ARG, first_patch]
|
||||
break
|
||||
|
||||
def fetch_patch_options(self: Self, name: str, options_list: list[dict[str, Any]]) -> dict[str, Any]:
|
||||
"""The function `fetch_patch_options` finds patch options for the patch.
|
||||
@@ -219,15 +227,27 @@ class Parser(object):
|
||||
app.resource["cli"]["file_name"],
|
||||
apk_arg,
|
||||
app.download_file_name,
|
||||
self.PATCHES_ARG,
|
||||
app.resource["patches"]["file_name"],
|
||||
self.OUTPUT_ARG,
|
||||
app.get_output_file_name(),
|
||||
self.KEYSTORE_ARG,
|
||||
app.keystore_name,
|
||||
exp,
|
||||
]
|
||||
args[1::2] = map(self.config.temp_folder.joinpath, args[1::2])
|
||||
|
||||
# Add multiple patch bundles using -p argument
|
||||
if hasattr(app, "patch_bundles") and app.patch_bundles:
|
||||
# Use multiple -p arguments for multiple bundles
|
||||
for bundle in app.patch_bundles:
|
||||
args.extend([self.PATCHES_ARG, bundle["file_name"]])
|
||||
else:
|
||||
# Fallback to single bundle for backward compatibility
|
||||
args.extend([self.PATCHES_ARG, app.resource["patches"]["file_name"]])
|
||||
|
||||
args.extend(
|
||||
[
|
||||
self.OUTPUT_ARG,
|
||||
app.get_output_file_name(),
|
||||
self.KEYSTORE_ARG,
|
||||
app.keystore_name,
|
||||
exp,
|
||||
],
|
||||
)
|
||||
args[1::2] = [str(self.config.temp_folder.joinpath(arg)) for arg in args[1::2]]
|
||||
if app.old_key:
|
||||
# https://github.com/ReVanced/revanced-cli/issues/272#issuecomment-1740587534
|
||||
old_key_flags = [
|
||||
@@ -237,7 +257,7 @@ class Parser(object):
|
||||
]
|
||||
args.extend(old_key_flags)
|
||||
if self.config.ci_test:
|
||||
self.exclude_all_patches()
|
||||
self.enable_exclusive_mode()
|
||||
if self._PATCHES:
|
||||
args.extend(self._PATCHES)
|
||||
if app.app_name in self.config.rip_libs_apps:
|
||||
|
||||
+32
-8
@@ -1,7 +1,7 @@
|
||||
"""Revanced Patches."""
|
||||
|
||||
import contextlib
|
||||
from typing import ClassVar, Self
|
||||
from typing import Any, ClassVar, Self
|
||||
|
||||
from loguru import logger
|
||||
|
||||
@@ -125,11 +125,35 @@ class Patches(object):
|
||||
The `app` parameter is of type `APP`. It represents an instance of the `APP` class.
|
||||
"""
|
||||
self.patches_dict[app.app_name] = []
|
||||
patches = convert_command_output_to_json(
|
||||
f"{config.temp_folder}/{app.resource["cli"]["file_name"]}",
|
||||
f"{config.temp_folder}/{app.resource["patches"]["file_name"]}",
|
||||
)
|
||||
|
||||
# Handle multiple patch bundles
|
||||
if hasattr(app, "patch_bundles") and app.patch_bundles:
|
||||
for bundle in app.patch_bundles:
|
||||
patches = convert_command_output_to_json(
|
||||
f"{config.temp_folder}/{app.resource["cli"]["file_name"]}",
|
||||
f"{config.temp_folder}/{bundle["file_name"]}",
|
||||
)
|
||||
self._process_patches(patches, app)
|
||||
elif "patches" in app.resource:
|
||||
# Fallback to single bundle for backward compatibility
|
||||
patches = convert_command_output_to_json(
|
||||
f"{config.temp_folder}/{app.resource["cli"]["file_name"]}",
|
||||
f"{config.temp_folder}/{app.resource["patches"]["file_name"]}",
|
||||
)
|
||||
self._process_patches(patches, app)
|
||||
|
||||
app.no_of_patches = len(self.patches_dict[app.app_name])
|
||||
|
||||
def _process_patches(self: Self, patches: list[dict[Any, Any]], app: APP) -> None:
|
||||
"""Process patches from a single bundle and add them to the patches dict.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
patches : list[dict[Any, Any]]
|
||||
List of patches from a bundle
|
||||
app : APP
|
||||
The app instance
|
||||
"""
|
||||
for patch in patches:
|
||||
if not patch["compatiblePackages"]:
|
||||
p = {x: patch[x] for x in ["name", "description"]}
|
||||
@@ -142,9 +166,9 @@ class Patches(object):
|
||||
p = {x: patch[x] for x in ["name", "description"]}
|
||||
p["app"] = compatible_package
|
||||
p["version"] = version[-1] if version else "all"
|
||||
self.patches_dict[app.app_name].append(p)
|
||||
|
||||
app.no_of_patches = len(self.patches_dict[app.app_name])
|
||||
# Avoid duplicate patches from multiple bundles
|
||||
if not any(existing["name"] == p["name"] for existing in self.patches_dict[app.app_name]):
|
||||
self.patches_dict[app.app_name].append(p)
|
||||
|
||||
def __init__(self: Self, config: RevancedConfig, app: APP) -> None:
|
||||
self.patches_dict: dict[str, list[dict[str, str]]] = {"universal_patch": []}
|
||||
|
||||
Reference in New Issue
Block a user